diff --git a/doc/APIreference/functions.rst b/doc/APIreference/functions.rst index 441a72d1..0ce805ce 100644 --- a/doc/APIreference/functions.rst +++ b/doc/APIreference/functions.rst @@ -4139,6 +4139,15 @@ mjs_getSpec Get spec from body. +.. _mjs_findSpec: + +mjs_findSpec +~~~~~~~~~~~~ + +.. mujoco-include:: mjs_findSpec + +Find spec (model asset) by name. + .. _mjs_findBody: mjs_findBody diff --git a/doc/includes/references.h b/doc/includes/references.h index 4da16480..9f348881 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -3600,6 +3600,7 @@ mjsSkin* mjs_addSkin(mjSpec* s); mjsTexture* mjs_addTexture(mjSpec* s); mjsMaterial* mjs_addMaterial(mjSpec* s, mjsDefault* def); mjSpec* mjs_getSpec(mjsElement* element); +mjSpec* mjs_findSpec(mjSpec* spec, const char* name); mjsBody* mjs_findBody(mjSpec* s, const char* name); mjsElement* mjs_findElement(mjSpec* s, mjtObj type, const char* name); mjsBody* mjs_findChild(mjsBody* body, const char* name); diff --git a/include/mujoco/mujoco.h b/include/mujoco/mujoco.h index 5f60763e..751c6cdf 100644 --- a/include/mujoco/mujoco.h +++ b/include/mujoco/mujoco.h @@ -1534,6 +1534,9 @@ MJAPI mjsMaterial* mjs_addMaterial(mjSpec* s, mjsDefault* def); // Get spec from body. MJAPI mjSpec* mjs_getSpec(mjsElement* element); +// Find spec (model asset) by name. +MJAPI mjSpec* mjs_findSpec(mjSpec* spec, const char* name); + // Find body in spec by name. MJAPI mjsBody* mjs_findBody(mjSpec* s, const char* name); diff --git a/introspect/functions.py b/introspect/functions.py index 377ba4a6..c0a3a196 100644 --- a/introspect/functions.py +++ b/introspect/functions.py @@ -9742,6 +9742,28 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([ ), doc='Get spec from body.', )), + ('mjs_findSpec', + FunctionDecl( + name='mjs_findSpec', + return_type=PointerType( + inner_type=ValueType(name='mjSpec'), + ), + parameters=( + FunctionParameterDecl( + name='spec', + type=PointerType( + inner_type=ValueType(name='mjSpec'), + ), + ), + FunctionParameterDecl( + name='name', + type=PointerType( + inner_type=ValueType(name='char', is_const=True), + ), + ), + ), + doc='Find spec (model asset) by name.', + )), ('mjs_findBody', FunctionDecl( name='mjs_findBody', diff --git a/src/user/user_model.cc b/src/user/user_model.cc index 51eb89c0..82e3d941 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -388,6 +388,9 @@ mjCModel& mjCModel::operator+=(const mjCModel& other) { } CopyList(numerics_, other.numerics_); CopyList(texts_, other.texts_); + for (const auto* s : other.specs_) { + specs_.push_back(mj_copySpec(s)); + } } CopyList(flexes_, other.flexes_); CopyList(pairs_, other.pairs_); @@ -3648,12 +3651,6 @@ mjModel* mjCModel::Compile(const mjVFS* vfs, mjModel** m) { return nullptr; } - // destroy attached specs - for (auto spec : specs_) { - mj_deleteSpec(spec); - } - specs_.clear(); - // restore error handler, mark as compiled, return mjModel _mjPRIVATE__set_tls_error_fn(save_error); _mjPRIVATE__set_tls_warning_fn(save_warning); diff --git a/src/xml/xml.cc b/src/xml/xml.cc index 55aefeb5..abcd8c13 100644 --- a/src/xml/xml.cc +++ b/src/xml/xml.cc @@ -387,11 +387,11 @@ mjSpec* ParseXML(const char* filename, const mjVFS* vfs, return spec; } -mjSpec* ParseSpecFromString(std::string_view xml, char* error, int nerror) { +mjSpec* ParseSpecFromString(std::string_view xml, const mjVFS* vfs, char* error, int nerror) { RegisterResourceProvider(); std::string xml2 = {xml.begin(), xml.end()}; std::string str = "LoadModelFromString:" + xml2; - return ParseXML(str.c_str(), nullptr, error, nerror); + return ParseXML(str.c_str(), vfs, error, nerror); } // Main writer function - calls mjXWrite diff --git a/src/xml/xml.h b/src/xml/xml.h index bc615d90..44b9b23e 100644 --- a/src/xml/xml.h +++ b/src/xml/xml.h @@ -26,7 +26,8 @@ mjSpec* ParseXML(const char* filename, const mjVFS* vfs, char* error, int nerror // Returns a newly-allocated mjSpec, loaded from the contents of xml. // On failure returns nullptr and populates the error array if present. -mjSpec* ParseSpecFromString(std::string_view xml, char* error = nullptr, int nerror = 0); +mjSpec* ParseSpecFromString(std::string_view xml, const mjVFS* vfs = nullptr, + char* error = nullptr, int nerror = 0); // Main writer function std::string WriteXML(const mjModel* m, const mjSpec* spec, char* error, int nerror); diff --git a/src/xml/xml_api.cc b/src/xml/xml_api.cc index f808c23b..8fadc4cb 100644 --- a/src/xml/xml_api.cc +++ b/src/xml/xml_api.cc @@ -221,7 +221,7 @@ mjSpec* mj_parseXML(const char* filename, const mjVFS* vfs, char* error, int err // parse spec from string mjSpec* mj_parseXMLString(const char* xml, const mjVFS* vfs, char* error, int error_sz) { - return ParseSpecFromString(xml, error, error_sz); + return ParseSpecFromString(xml, vfs, error, error_sz); } diff --git a/test/user/user_api_test.cc b/test/user/user_api_test.cc index 6e33580f..23521001 100644 --- a/test/user/user_api_test.cc +++ b/test/user/user_api_test.cc @@ -1849,5 +1849,57 @@ TEST_F(MujocoTest, DifferentUnitsNotAllowed) { mj_deleteSpec(spec_2); } +TEST_F(MujocoTest, CopyAttachedSpec) { + static constexpr char xml_parent[] = R"( + + + + + + + + + + + + )"; + + static constexpr char xml_child[] = R"( + + + + + + + + )"; + + auto vfs = std::make_unique(); + mj_defaultVFS(vfs.get()); + mj_addBufferVFS(vfs.get(), "xml_child.xml", xml_child, sizeof(xml_child)); + + std::array er; + mjSpec* spec = mj_parseXMLString(xml_parent, vfs.get(), er.data(), er.size()); + EXPECT_THAT(spec, NotNull()) << er.data(); + + mjModel* model = mj_compile(spec, vfs.get()); + EXPECT_THAT(model, NotNull()) << er.data(); + + mjSpec* child = mjs_findSpec(spec, "child"); + EXPECT_THAT(child, NotNull()); + + mjSpec* copy = mj_copySpec(spec); + EXPECT_THAT(copy, NotNull()); + + mjSpec* child_copy = mjs_findSpec(copy, "child"); + EXPECT_THAT(child_copy, NotNull()); + EXPECT_NE(child_copy, child); + + mj_deleteSpec(spec); + mj_deleteSpec(copy); + mj_deleteModel(model); + mj_deleteVFS(vfs.get()); +} + } // namespace } // namespace mujoco