diff --git a/python/mujoco/codegen/generate_spec_bindings.py b/python/mujoco/codegen/generate_spec_bindings.py index ef4aedf4..50b40d1b 100644 --- a/python/mujoco/codegen/generate_spec_bindings.py +++ b/python/mujoco/codegen/generate_spec_bindings.py @@ -39,8 +39,10 @@ def _value_binding_code( fullvarname = 'ptr->' + varname if field.name.startswith('mjs'): # all other mjs are raw structs fulltype = field.name.replace('mjs', 'raw::Mjs') - if field.name != 'mjsPlugin' and field.name != 'mjsOrientation': - fulltype = fulltype + '*' # plugin and orientation are pointers + if field.name == 'mjsPlugin' or field.name == 'mjsOrientation': + fulltype = fulltype + '&' # plugin and orientation are not pointers + else: + fulltype = fulltype + '*' def_property_args = ( f'"{varname}"', diff --git a/python/mujoco/specs.cc b/python/mujoco/specs.cc index c280911e..0f4ab874 100644 --- a/python/mujoco/specs.cc +++ b/python/mujoco/specs.cc @@ -1017,9 +1017,12 @@ PYBIND11_MODULE(_specs, m) { mjsTuple.def("delete", [](raw::MjsTuple& self) { mjs_delete(self.element); }); // ============================= MJSPLUGIN =================================== - mjsPlugin.def_property_readonly("id", [](raw::MjsPlugin& self) -> int { - return mjs_getId(self.instance); - }); + mjsPlugin.def_property( + "id", + [](raw::MjsPlugin& self) -> int { return mjs_getId(self.instance); }, + [](raw::MjsPlugin& self, raw::MjsPlugin* other) { + self.instance = other->instance; + }); mjsPlugin.def("delete", [](raw::MjsPlugin& self) { mjs_delete(self.instance); }); diff --git a/python/mujoco/specs_test.py b/python/mujoco/specs_test.py index ad068f91..a860f443 100644 --- a/python/mujoco/specs_test.py +++ b/python/mujoco/specs_test.py @@ -322,5 +322,35 @@ class SpecsTest(absltest.TestCase): self.assertEqual(model.nsite, 10) self.assertEqual(model.nsensor, 9) + def test_plugin(self): + xml = """ + + + + + + """ + + spec = mujoco.MjSpec() + spec.from_string(xml) + self.assertIsNotNone(spec.worldbody) + + body = spec.worldbody.add_body() + body.plugin.name = 'mujoco.elasticity.cable' + body.plugin.id = spec.add_plugin() + body.plugin.active = True + self.assertEqual(body.plugin.id, 0) + + geom = body.add_geom() + geom.type = mujoco.mjtGeom.mjGEOM_BOX + geom.size[0] = 1 + geom.size[1] = 1 + geom.size[2] = 1 + + model = spec.compile() + self.assertIsNotNone(model) + self.assertEqual(model.nplugin, 1) + self.assertEqual(model.body_plugin[1], 0) + if __name__ == '__main__': absltest.main() diff --git a/src/user/user_api.cc b/src/user/user_api.cc index bf3a98c6..4743f83b 100644 --- a/src/user/user_api.cc +++ b/src/user/user_api.cc @@ -606,6 +606,9 @@ const char* mjs_resolveOrientation(double quat[4], mjtByte degree, const char* s // get id int mjs_getId(mjsElement* element) { + if (!element) { + return -1; + } return static_cast(element)->id; } diff --git a/test/user/user_api_test.cc b/test/user/user_api_test.cc index 5eb68356..346f179d 100644 --- a/test/user/user_api_test.cc +++ b/test/user/user_api_test.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -104,6 +105,41 @@ TEST_F(MujocoTest, TreeTraversal) { mj_deleteSpec(spec); } +TEST_F(PluginTest, ActivatePlugin) { + std::string plugin_name = "mujoco.elasticity.cable"; + mjSpec* spec = mj_makeSpec(); + + // get slot of requested plugin + int plugin_slot = -1; + const mjpPlugin* plugin = mjp_getPlugin(plugin_name.c_str(), &plugin_slot); + EXPECT_THAT(plugin, NotNull()); + + // activated plugin in the slot + std::vector> active_plugins; + active_plugins.emplace_back(std::make_pair(plugin, plugin_slot)); + mjs_setActivePlugins(spec, &active_plugins); + + // associate plugin to body + mjsBody* body = mjs_addBody(mjs_findBody(spec, "world"), 0); + mjs_setString(body->plugin.name, plugin_name.c_str()); + body->plugin.instance = mjs_addPlugin(spec)->instance; + body->plugin.active = true; + mjsGeom* geom = mjs_addGeom(body, 0); + geom->type = mjGEOM_BOX; + geom->size[0] = 1; + geom->size[1] = 1; + geom->size[2] = 1; + + // compile and check that the plugin is present + mjModel* model = mj_compile(spec, NULL); + EXPECT_THAT(model, NotNull()); + EXPECT_THAT(model->nplugin, 1); + EXPECT_THAT(model->body_plugin[1], 0); + + mj_deleteSpec(spec); + mj_deleteModel(model); +} + // ------------------- test recompilation multiple files ----------------------- TEST_F(PluginTest, RecompileCompare) { mjtNum tol = 0;