diff --git a/wasm/codegen/generated/bindings.cc b/wasm/codegen/generated/bindings.cc index c49629ee..d8b69429 100644 --- a/wasm/codegen/generated/bindings.cc +++ b/wasm/codegen/generated/bindings.cc @@ -8329,7 +8329,7 @@ MjSpec::~MjSpec() { mjSpec *MjSpec::get() const { return ptr_; } void MjSpec::set(mjSpec *ptr) { ptr_ = ptr; } -std::unique_ptr mj_loadXML_wrapper(std::string filename) { +std::unique_ptr mj_loadXML_wrapper_1(std::string filename) { char error[1000]; mjModel *model = mj_loadXML(filename.c_str(), nullptr, error, sizeof(error)); if (!model) { @@ -8338,6 +8338,15 @@ std::unique_ptr mj_loadXML_wrapper(std::string filename) { return std::unique_ptr(new MjModel(model)); } +std::unique_ptr mj_loadXML_wrapper_2(std::string filename, const MjVFS& vfs) { + char error[1000]; + mjModel *model = mj_loadXML(filename.c_str(), vfs.get(), error, sizeof(error)); + if (!model) { + mju_error("Loading error: %s\n", error); + } + return std::unique_ptr(new MjModel(model)); +} + void mj_saveModel_wrapper(const MjModel& m, const StringOrNull& filename, const val& buffer) { UNPACK_NULLABLE_STRING(filename); UNPACK_NULLABLE_VALUE(uint8_t, buffer); @@ -11581,8 +11590,9 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) { .property("useexisting", &MjLROpt::useexisting, &MjLROpt::set_useexisting, reference()) .property("uselimit", &MjLROpt::uselimit, &MjLROpt::set_uselimit, reference()); emscripten::class_("MjModel") - .class_function("mj_loadXML", &mj_loadXML_wrapper, take_ownership()) - .class_function("mj_loadBinary", &mj_loadModel_wrapper, take_ownership()) + .class_function("mj_loadXML", emscripten::select_overload(std::string)>(&mj_loadXML_wrapper_1)) + .class_function("mj_loadXML", emscripten::select_overload(std::string, const MjVFS&)>(&mj_loadXML_wrapper_2)) + .class_function("mj_loadModel", &mj_loadModel_wrapper) .constructor() // Binds the functions on MjModel that return accessors. #define X_ACCESSOR(NAME, Name, OBJTYPE, field_name, nfield) \ diff --git a/wasm/codegen/generators/structs.py b/wasm/codegen/generators/structs.py index f6282693..0517dba9 100644 --- a/wasm/codegen/generators/structs.py +++ b/wasm/codegen/generators/structs.py @@ -549,14 +549,19 @@ def _build_struct_bindings( MJDATA_ACCESSORS #undef X_ACCESSOR""".lstrip()) elif w == "MjModel": - f1 = common.wrapped_function_name( - introspect_functions.FUNCTIONS["mj_loadXML"] + builder.line( + '.class_function("mj_loadXML",' + " emscripten::select_overload(std::string)>(&mj_loadXML_wrapper_1))" + ) + builder.line( + '.class_function("mj_loadXML",' + " emscripten::select_overload(std::string," + " const MjVFS&)>(&mj_loadXML_wrapper_2))" ) - builder.line(f'.class_function("mj_loadXML", &{f1}, take_ownership())') f2 = common.wrapped_function_name( introspect_functions.FUNCTIONS["mj_loadModel"] ) - builder.line(f'.class_function("mj_loadBinary", &{f2}, take_ownership())') + builder.line(f'.class_function("mj_loadModel", &{f2})') builder.line(".constructor()") builder.line(""" // Binds the functions on MjModel that return accessors. diff --git a/wasm/codegen/templates/bindings.cc b/wasm/codegen/templates/bindings.cc index 8e29a080..8f2a1ecf 100644 --- a/wasm/codegen/templates/bindings.cc +++ b/wasm/codegen/templates/bindings.cc @@ -714,7 +714,7 @@ MjSpec::~MjSpec() { mjSpec *MjSpec::get() const { return ptr_; } void MjSpec::set(mjSpec *ptr) { ptr_ = ptr; } -std::unique_ptr mj_loadXML_wrapper(std::string filename) { +std::unique_ptr mj_loadXML_wrapper_1(std::string filename) { char error[1000]; mjModel *model = mj_loadXML(filename.c_str(), nullptr, error, sizeof(error)); if (!model) { @@ -723,6 +723,15 @@ std::unique_ptr mj_loadXML_wrapper(std::string filename) { return std::unique_ptr(new MjModel(model)); } +std::unique_ptr mj_loadXML_wrapper_2(std::string filename, const MjVFS& vfs) { + char error[1000]; + mjModel *model = mj_loadXML(filename.c_str(), vfs.get(), error, sizeof(error)); + if (!model) { + mju_error("Loading error: %s\n", error); + } + return std::unique_ptr(new MjModel(model)); +} + void mj_saveModel_wrapper(const MjModel& m, const StringOrNull& filename, const val& buffer) { UNPACK_NULLABLE_STRING(filename); UNPACK_NULLABLE_VALUE(uint8_t, buffer); diff --git a/wasm/tests/bindings_test.ts b/wasm/tests/bindings_test.ts index 8fe9afe0..0ed482cc 100644 --- a/wasm/tests/bindings_test.ts +++ b/wasm/tests/bindings_test.ts @@ -2512,7 +2512,7 @@ describe('MuJoCo WASM Bindings', () => { vfs = new mujoco.MjVFS(); vfs.addBuffer(objFilename, new TextEncoder().encode(cube1)); - binaryModel = mujoco.MjModel.mj_loadBinary(mjbFilename, vfs); + binaryModel = mujoco.MjModel.mj_loadModel(mjbFilename, vfs); assertExists(binaryModel); expect(mujoco.mj_sizeModel(binaryModel)) @@ -2542,7 +2542,7 @@ describe('MuJoCo WASM Bindings', () => { const bufSize = mujoco.mj_sizeModel(model!); vfs = new mujoco.MjVFS(); - binaryModel = mujoco.MjModel.mj_loadBinary(mjbFilename, vfs); + binaryModel = mujoco.MjModel.mj_loadModel(mjbFilename, vfs); assertExists(binaryModel); expect(mujoco.mj_sizeModel(binaryModel)).toEqual(bufSize); @@ -2558,4 +2558,49 @@ describe('MuJoCo WASM Bindings', () => { } }); + it('should load XML with assets from VFS', () => { + const xml = ` + + + + + + + + `; + + const cube1 = ` + v -1 -1 1 + v 1 -1 1 + v -1 1 1 + v 1 1 1 + v -1 1 -1 + v 1 1 -1 + v -1 -1 -1 + v 1 -1 -1`; + + const xmlFilename = '/tmp/with_vfs.xml'; + writeXMLFile(xmlFilename, xml); + + let model: MjModel|null = null; + let vfs: MjVFS|null = null; + try { + vfs = new mujoco.MjVFS(); + vfs.addBuffer('cube.obj', new TextEncoder().encode(cube1)); + assertExists(vfs); + + model = mujoco.MjModel.mj_loadXML(xmlFilename, vfs); + assertExists(model); + expect(model.nmesh).toBe(1); + + const meshId = + mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_MESH.value, 'cube'); + expect(meshId).toBeGreaterThanOrEqual(0); + } finally { + model?.delete(); + vfs?.delete(); + unlinkXMLFile(xmlFilename); + } + }); + });