diff --git a/doc/APIreference/functions.rst b/doc/APIreference/functions.rst
index ed317aea..fbec781a 100644
--- a/doc/APIreference/functions.rst
+++ b/doc/APIreference/functions.rst
@@ -4205,7 +4205,7 @@ mjs_firstChild
.. mujoco-include:: mjs_firstChild
-Return body's first child of given type.
+Return body's first child of given type. If recurse is nonzero, also search the body's subtree.
.. _mjs_nextChild:
@@ -4215,6 +4215,7 @@ mjs_nextChild
.. mujoco-include:: mjs_nextChild
Return body's next child of the same type; return NULL if child is last.
+If recurse is nonzero, also search the body's subtree.
.. _mjs_firstElement:
diff --git a/doc/includes/references.h b/doc/includes/references.h
index 5f5ecafd..19ea8a36 100644
--- a/doc/includes/references.h
+++ b/doc/includes/references.h
@@ -3598,8 +3598,8 @@ mjsDefault* mjs_getDefault(mjsElement* element);
mjsDefault* mjs_findDefault(mjSpec* s, const char* classname);
mjsDefault* mjs_getSpecDefault(mjSpec* s);
int mjs_getId(mjsElement* element);
-mjsElement* mjs_firstChild(mjsBody* body, mjtObj type);
-mjsElement* mjs_nextChild(mjsBody* body, mjsElement* child);
+mjsElement* mjs_firstChild(mjsBody* body, mjtObj type, int recurse);
+mjsElement* mjs_nextChild(mjsBody* body, mjsElement* child, int recurse);
mjsElement* mjs_firstElement(mjSpec* s, mjtObj type);
mjsElement* mjs_nextElement(mjSpec* s, mjsElement* element);
void mjs_setBuffer(mjByteVec* dest, const void* array, int size);
diff --git a/include/mujoco/mujoco.h b/include/mujoco/mujoco.h
index e9a41e0d..0df9b7e9 100644
--- a/include/mujoco/mujoco.h
+++ b/include/mujoco/mujoco.h
@@ -1550,11 +1550,12 @@ MJAPI mjsDefault* mjs_getSpecDefault(mjSpec* s);
// Get element id.
MJAPI int mjs_getId(mjsElement* element);
-// Return body's first child of given type.
-MJAPI mjsElement* mjs_firstChild(mjsBody* body, mjtObj type);
+// Return body's first child of given type. If recurse is nonzero, also search the body's subtree.
+MJAPI mjsElement* mjs_firstChild(mjsBody* body, mjtObj type, int recurse);
// Return body's next child of the same type; return NULL if child is last.
-MJAPI mjsElement* mjs_nextChild(mjsBody* body, mjsElement* child);
+// If recurse is nonzero, also search the body's subtree.
+MJAPI mjsElement* mjs_nextChild(mjsBody* body, mjsElement* child, int recurse);
// Return spec's first element of selected type.
MJAPI mjsElement* mjs_firstElement(mjSpec* s, mjtObj type);
diff --git a/introspect/functions.py b/introspect/functions.py
index 08f3d44c..25f21010 100644
--- a/introspect/functions.py
+++ b/introspect/functions.py
@@ -9859,8 +9859,12 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
name='type',
type=ValueType(name='mjtObj'),
),
+ FunctionParameterDecl(
+ name='recurse',
+ type=ValueType(name='int'),
+ ),
),
- doc="Return body's first child of given type.",
+ doc="Return body's first child of given type. If recurse is nonzero, also search the body's subtree.", # pylint: disable=line-too-long
)),
('mjs_nextChild',
FunctionDecl(
@@ -9881,8 +9885,12 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
inner_type=ValueType(name='mjsElement'),
),
),
+ FunctionParameterDecl(
+ name='recurse',
+ type=ValueType(name='int'),
+ ),
),
- doc="Return body's next child of the same type; return NULL if child is last.", # pylint: disable=line-too-long
+ doc="Return body's next child of the same type; return NULL if child is last. If recurse is nonzero, also search the body's subtree.", # pylint: disable=line-too-long
)),
('mjs_firstElement',
FunctionDecl(
diff --git a/python/mujoco/specs.cc b/python/mujoco/specs.cc
index 01e9aaf8..8a1e7ecc 100644
--- a/python/mujoco/specs.cc
+++ b/python/mujoco/specs.cc
@@ -385,6 +385,50 @@ PYBIND11_MODULE(_specs, m) {
return mjs_getDefault(self.element);
},
py::return_value_policy::reference_internal);
+ mjsBody.def(
+ "find_all",
+ [](raw::MjsBody& self, mjtObj objtype) -> py::list {
+ py::list list;
+ raw::MjsElement* el = mjs_firstChild(&self, objtype, true);
+ std::string error = mjs_getError(mjs_getSpec(&self));
+ if (!el && !error.empty()) {
+ throw pybind11::value_error(error);
+ }
+ while (el) {
+ switch (objtype) {
+ case mjOBJ_BODY:
+ list.append(mjs_asBody(el));
+ break;
+ case mjOBJ_CAMERA:
+ list.append(mjs_asCamera(el));
+ break;
+ case mjOBJ_FRAME:
+ list.append(mjs_asFrame(el));
+ break;
+ case mjOBJ_GEOM:
+ list.append(mjs_asGeom(el));
+ break;
+ case mjOBJ_JOINT:
+ list.append(mjs_asJoint(el));
+ break;
+ case mjOBJ_LIGHT:
+ list.append(mjs_asLight(el));
+ break;
+ case mjOBJ_SITE:
+ list.append(mjs_asSite(el));
+ break;
+ default:
+ // this should never happen
+ throw pybind11::value_error(
+ "body.find_all supports the types: body, frame, geom, site, "
+ "light, camera.");
+ break;
+ }
+ el = mjs_nextChild(&self, el, true);
+ }
+ return list;
+ },
+ py::return_value_policy::reference_internal);
mjsBody.def(
"find_child",
[](raw::MjsBody& self, std::string& name) -> raw::MjsBody* {
@@ -394,85 +438,85 @@ PYBIND11_MODULE(_specs, m) {
mjsBody.def(
"first_body",
[](raw::MjsBody& self) -> raw::MjsBody* {
- return mjs_asBody(mjs_firstChild(&self, mjOBJ_BODY));
+ return mjs_asBody(mjs_firstChild(&self, mjOBJ_BODY, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"next_body",
[](raw::MjsBody& self, raw::MjsBody& child) -> raw::MjsBody* {
- return mjs_asBody(mjs_nextChild(&self, child.element));
+ return mjs_asBody(mjs_nextChild(&self, child.element, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_camera",
[](raw::MjsBody& self) -> raw::MjsCamera* {
- return mjs_asCamera(mjs_firstChild(&self, mjOBJ_CAMERA));
+ return mjs_asCamera(mjs_firstChild(&self, mjOBJ_CAMERA, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"next_camera",
[](raw::MjsBody& self, raw::MjsCamera& child) -> raw::MjsCamera* {
- return mjs_asCamera(mjs_nextChild(&self, child.element));
+ return mjs_asCamera(mjs_nextChild(&self, child.element, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_light",
[](raw::MjsBody& self) -> raw::MjsLight* {
- return mjs_asLight(mjs_firstChild(&self, mjOBJ_LIGHT));
+ return mjs_asLight(mjs_firstChild(&self, mjOBJ_LIGHT, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"next_light",
[](raw::MjsBody& self, raw::MjsLight& child) -> raw::MjsLight* {
- return mjs_asLight(mjs_nextChild(&self, child.element));
+ return mjs_asLight(mjs_nextChild(&self, child.element, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_joint",
[](raw::MjsBody& self) -> raw::MjsJoint* {
- return mjs_asJoint(mjs_firstChild(&self, mjOBJ_JOINT));
+ return mjs_asJoint(mjs_firstChild(&self, mjOBJ_JOINT, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"next_joint",
[](raw::MjsBody& self, raw::MjsJoint& child) -> raw::MjsJoint* {
- return mjs_asJoint(mjs_nextChild(&self, child.element));
+ return mjs_asJoint(mjs_nextChild(&self, child.element, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_geom",
[](raw::MjsBody& self) -> raw::MjsGeom* {
- return mjs_asGeom(mjs_firstChild(&self, mjOBJ_GEOM));
+ return mjs_asGeom(mjs_firstChild(&self, mjOBJ_GEOM, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"next_geom",
[](raw::MjsBody& self, raw::MjsGeom& child) -> raw::MjsGeom* {
- return mjs_asGeom(mjs_nextChild(&self, child.element));
+ return mjs_asGeom(mjs_nextChild(&self, child.element, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_site",
[](raw::MjsBody& self) -> raw::MjsSite* {
- return mjs_asSite(mjs_firstChild(&self, mjOBJ_SITE));
+ return mjs_asSite(mjs_firstChild(&self, mjOBJ_SITE, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"next_site",
[](raw::MjsBody& self, raw::MjsSite& child) -> raw::MjsSite* {
- return mjs_asSite(mjs_nextChild(&self, child.element));
+ return mjs_asSite(mjs_nextChild(&self, child.element, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_frame",
[](raw::MjsBody& self) -> raw::MjsFrame* {
- return mjs_asFrame(mjs_firstChild(&self, mjOBJ_FRAME));
+ return mjs_asFrame(mjs_firstChild(&self, mjOBJ_FRAME, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"next_frame",
[](raw::MjsBody& self, raw::MjsFrame& child) -> raw::MjsFrame* {
- return mjs_asFrame(mjs_nextChild(&self, child.element));
+ return mjs_asFrame(mjs_nextChild(&self, child.element, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
diff --git a/python/mujoco/specs_test.py b/python/mujoco/specs_test.py
index 5c9c160e..68e0f0cb 100644
--- a/python/mujoco/specs_test.py
+++ b/python/mujoco/specs_test.py
@@ -580,7 +580,9 @@ class SpecsTest(absltest.TestCase):