diff --git a/doc/APIreference/functions.rst b/doc/APIreference/functions.rst index 590aaf72..91fdbd7b 100644 --- a/doc/APIreference/functions.rst +++ b/doc/APIreference/functions.rst @@ -4175,6 +4175,15 @@ Find element in spec by name. Find child body by name. +.. _mjs_getParent: + +`mjs_getParent <#mjs_getParent>`__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. mujoco-include:: mjs_getParent + +Get parent body. + .. _mjs_findFrame: `mjs_findFrame <#mjs_findFrame>`__ diff --git a/doc/includes/references.h b/doc/includes/references.h index 0303015e..d9742797 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -3607,6 +3607,7 @@ 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); +mjsBody* mjs_getParent(mjsElement* element); mjsFrame* mjs_findFrame(mjSpec* s, const char* name); mjsDefault* mjs_getDefault(mjsElement* element); const mjsDefault* mjs_findDefault(mjSpec* s, const char* classname); diff --git a/doc/python.rst b/doc/python.rst index 35c27d73..90f861a2 100644 --- a/doc/python.rst +++ b/doc/python.rst @@ -591,6 +591,8 @@ Recursive search: ``body.find_all(mujoco.mjtObj.mjOBJ_SITE)`` or ``body.find_all('site')`` will return a list of all sites under the body. +Additionally, the parent body of a given element - including bodies and frames - can be accessed via the ``parent`` +property. For example, the parent of a site can be accessed via ``site.parent``. Relationship to ``PyMJCF`` -------------------------- diff --git a/include/mujoco/mujoco.h b/include/mujoco/mujoco.h index 9e4d25ff..52d73f79 100644 --- a/include/mujoco/mujoco.h +++ b/include/mujoco/mujoco.h @@ -1546,6 +1546,9 @@ MJAPI mjsElement* mjs_findElement(mjSpec* s, mjtObj type, const char* name); // Find child body by name. MJAPI mjsBody* mjs_findChild(mjsBody* body, const char* name); +// Get parent body. +MJAPI mjsBody* mjs_getParent(mjsElement* element); + // Find frame by name. MJAPI mjsFrame* mjs_findFrame(mjSpec* s, const char* name); diff --git a/introspect/functions.py b/introspect/functions.py index fa2141a9..4fd2374a 100644 --- a/introspect/functions.py +++ b/introspect/functions.py @@ -9828,6 +9828,22 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([ ), doc='Find child body by name.', )), + ('mjs_getParent', + FunctionDecl( + name='mjs_getParent', + return_type=PointerType( + inner_type=ValueType(name='mjsBody'), + ), + parameters=( + FunctionParameterDecl( + name='element', + type=PointerType( + inner_type=ValueType(name='mjsElement'), + ), + ), + ), + doc='Get parent body.', + )), ('mjs_findFrame', FunctionDecl( name='mjs_findFrame', diff --git a/python/mujoco/specs.cc b/python/mujoco/specs.cc index f8167182..a0dc13e4 100644 --- a/python/mujoco/specs.cc +++ b/python/mujoco/specs.cc @@ -680,6 +680,12 @@ PYBIND11_MODULE(_specs, m) { return mjs_getSpec(self.element); }, py::return_value_policy::reference_internal); + mjsBody.def_property_readonly( + "parent", + [](raw::MjsBody& self) -> raw::MjsBody* { + return mjs_getParent(self.element); + }, + py::return_value_policy::reference_internal); mjsBody.def( "attach_frame", [](raw::MjsBody& self, raw::MjsFrame& frame, @@ -713,6 +719,12 @@ PYBIND11_MODULE(_specs, m) { mjsFrame.def("set_frame", [](raw::MjsFrame& self, raw::MjsFrame& frame) { mjs_setFrame(self.element, &frame); }); + mjsFrame.def_property_readonly( + "parent", + [](raw::MjsFrame& self) -> raw::MjsBody* { + return mjs_getParent(self.element); + }, + py::return_value_policy::reference_internal); mjsFrame.def( "attach_body", [](raw::MjsFrame& self, raw::MjsBody& body, @@ -760,6 +772,12 @@ PYBIND11_MODULE(_specs, m) { mjsGeom.def("set_default", [](raw::MjsGeom& self, raw::MjsDefault& def) { mjs_setDefault(self.element, &def); }); + mjsGeom.def_property_readonly( + "parent", + [](raw::MjsGeom& self) -> raw::MjsBody* { + return mjs_getParent(self.element); + }, + py::return_value_policy::reference_internal); mjsGeom.def( "default", [](raw::MjsGeom& self) -> raw::MjsDefault* { @@ -775,6 +793,12 @@ PYBIND11_MODULE(_specs, m) { mjsJoint.def("set_default", [](raw::MjsJoint& self, raw::MjsDefault& def) { mjs_setDefault(self.element, &def); }); + mjsJoint.def_property_readonly( + "parent", + [](raw::MjsJoint& self) -> raw::MjsBody* { + return mjs_getParent(self.element); + }, + py::return_value_policy::reference_internal); mjsJoint.def( "default", [](raw::MjsJoint& self) -> raw::MjsDefault* { @@ -790,6 +814,12 @@ PYBIND11_MODULE(_specs, m) { mjsSite.def("set_default", [](raw::MjsSite& self, raw::MjsDefault& def) { mjs_setDefault(self.element, &def); }); + mjsSite.def_property_readonly( + "parent", + [](raw::MjsSite& self) -> raw::MjsBody* { + return mjs_getParent(self.element); + }, + py::return_value_policy::reference_internal); mjsSite.def( "default", [](raw::MjsSite& self) -> raw::MjsDefault* { @@ -845,6 +875,12 @@ PYBIND11_MODULE(_specs, m) { mjsCamera.def("set_default", [](raw::MjsCamera& self, raw::MjsDefault& def) { mjs_setDefault(self.element, &def); }); + mjsCamera.def_property_readonly( + "parent", + [](raw::MjsCamera& self) -> raw::MjsBody* { + return mjs_getParent(self.element); + }, + py::return_value_policy::reference_internal); mjsCamera.def( "default", [](raw::MjsCamera& self) -> raw::MjsDefault* { @@ -860,6 +896,12 @@ PYBIND11_MODULE(_specs, m) { mjsLight.def("set_default", [](raw::MjsLight& self, raw::MjsDefault& def) { mjs_setDefault(self.element, &def); }); + mjsLight.def_property_readonly( + "parent", + [](raw::MjsLight& self) -> raw::MjsBody* { + return mjs_getParent(self.element); + }, + py::return_value_policy::reference_internal); mjsLight.def( "default", [](raw::MjsLight& self) -> raw::MjsDefault* { diff --git a/python/mujoco/specs_test.py b/python/mujoco/specs_test.py index d7524372..dd8a6df7 100644 --- a/python/mujoco/specs_test.py +++ b/python/mujoco/specs_test.py @@ -646,6 +646,10 @@ class SpecsTest(absltest.TestCase): self.assertEqual(spec.bodies[2].name, 'body2') self.assertEqual(spec.bodies[3].name, 'body3') self.assertEqual(spec.bodies[4].name, 'body4') + self.assertEqual(spec.bodies[1].parent, spec.worldbody) + self.assertEqual(spec.bodies[2].parent, spec.worldbody) + self.assertEqual(spec.bodies[3].parent, spec.bodies[1]) + self.assertEqual(spec.bodies[4].parent, spec.bodies[3]) self.assertLen(spec.worldbody.find_all(bodytype), 4) self.assertLen(spec.bodies[1].find_all(bodytype), 2) self.assertLen(spec.bodies[3].find_all(bodytype), 1) @@ -671,6 +675,11 @@ class SpecsTest(absltest.TestCase): self.assertEqual(spec.bodies[3].sites[2].name, 'site3') self.assertEqual(spec.bodies[3].sites[3].name, 'site4') self.assertEqual(spec.bodies[4].sites[0].name, 'site5') + self.assertEqual(spec.bodies[3].sites[0].parent, spec.bodies[3]) + self.assertEqual(spec.bodies[3].sites[1].parent, spec.bodies[3]) + self.assertEqual(spec.bodies[3].sites[2].parent, spec.bodies[3]) + self.assertEqual(spec.bodies[3].sites[3].parent, spec.bodies[3]) + self.assertEqual(spec.bodies[4].sites[0].parent, spec.bodies[4]) with self.assertRaises(ValueError) as cm: spec.worldbody.find_all('actuator') self.assertEqual( diff --git a/src/user/user_api.cc b/src/user/user_api.cc index 7e819007..8eb612a7 100644 --- a/src/user/user_api.cc +++ b/src/user/user_api.cc @@ -649,6 +649,30 @@ mjsBody* mjs_findChild(mjsBody* bodyspec, const char* name) { +// get parent body +mjsBody* mjs_getParent(mjsElement* element) { + switch (element->elemtype) { + case mjOBJ_BODY: + return &(static_cast(element)->GetParent()->spec); + case mjOBJ_FRAME: + return &(static_cast(element)->GetParent()->spec); + case mjOBJ_JOINT: + return &(static_cast(element)->GetParent()->spec); + case mjOBJ_GEOM: + return &(static_cast(element)->GetParent()->spec); + case mjOBJ_SITE: + return &(static_cast(element)->GetParent()->spec); + case mjOBJ_CAMERA: + return &(static_cast(element)->GetParent()->spec); + case mjOBJ_LIGHT: + return &(static_cast(element)->GetParent()->spec); + default: + return nullptr; + } +} + + + // find frame by name mjsFrame* mjs_findFrame(mjSpec* s, const char* name) { mjsElement* frame = mjs_findElement(s, mjOBJ_FRAME, name); diff --git a/src/user/user_api.h b/src/user/user_api.h index ac545e74..b6722710 100644 --- a/src/user/user_api.h +++ b/src/user/user_api.h @@ -201,6 +201,9 @@ MJAPI mjsElement* mjs_findElement(mjSpec* s, mjtObj type, const char* name); // Find child body by name. MJAPI mjsBody* mjs_findChild(mjsBody* body, const char* name); +// Get parent body. +MJAPI mjsBody* mjs_getParent(mjsElement* element); + // Find frame by name. MJAPI mjsFrame* mjs_findFrame(mjSpec* s, const char* name); diff --git a/src/user/user_objects.h b/src/user/user_objects.h index bf11a7be..ca13a741 100644 --- a/src/user/user_objects.h +++ b/src/user/user_objects.h @@ -347,6 +347,7 @@ class mjCBody : public mjCBody_, private mjsBody { // set parent of this body void SetParent(mjCBody* _body) { parent = _body; } + mjCBody* GetParent() const { return parent; } private: mjCBody(const mjCBody& other, mjCModel* _model); // copy constructor @@ -411,6 +412,7 @@ class mjCFrame : public mjCFrame_, private mjsFrame { void CopyFromSpec(void); void PointToLocal(void); void SetParent(mjCBody* _body) { body = _body; } + mjCBody* GetParent() const { return body; } mjCFrame& operator+=(const mjCBody& other); @@ -462,6 +464,7 @@ class mjCJoint : public mjCJoint_, private mjsJoint { void CopyFromSpec(void); void SetParent(mjCBody* _body) { body = _body; } + mjCBody* GetParent() const { return body; } // used by mjXWriter and mjCModel const std::vector& get_userdata() const { return userdata_; } @@ -543,6 +546,7 @@ class mjCGeom : public mjCGeom_, private mjsGeom { bool IsVisual(void) const { return visual_; } void SetNotVisual(void) { visual_ = false; } void SetParent(mjCBody* _body) { body = _body; } + mjCBody* GetParent() const { return body; } mjtGeom Type() const { return type; } // Compute all coefs modeling the interaction with the surrounding fluid. @@ -608,6 +612,7 @@ class mjCSite : public mjCSite_, private mjsSite { // site's body mjCBody* Body() const { return body; } void SetParent(mjCBody* _body) { body = _body; } + mjCBody* GetParent() const { return body; } // use strings from mjCBase rather than mjStrings from mjsSite using mjCBase::name; @@ -661,6 +666,7 @@ class mjCCamera : public mjCCamera_, private mjsCamera { const std::vector& get_userdata() const { return userdata_; } void SetParent(mjCBody* _body) { body = _body; } + mjCBody* GetParent() const { return body; } private: void Compile(void); // compiler @@ -701,6 +707,7 @@ class mjCLight : public mjCLight_, private mjsLight { const std::string& get_targetbody() const { return targetbody_; } void SetParent(mjCBody* _body) { body = _body; } + mjCBody* GetParent() const { return body; } private: void Compile(void); // compiler