Add bind() method to MjModel and MjData.
This method allows users to bind a spec to a model or data object, making it less verbose to access its arrays. PiperOrigin-RevId: 699952211 Change-Id: I6b71a88d5a6968bc0f902bd49db7c568b46a4dfb
This commit is contained in:
committed by
Copybara-Service
parent
13b6055098
commit
c6ba8f27fd
+2
-5
@@ -6,11 +6,6 @@ Changelog
|
||||
Upcoming version (not yet released)
|
||||
-----------------------------------
|
||||
|
||||
General
|
||||
^^^^^^^
|
||||
- Removed id attribute from :ref:`mjSpec` objects in Python bindings. Using ids is error prone in scenarios of repeated
|
||||
attachment and detachment. Python users are encouraged to use names for unique identification of model elements.
|
||||
|
||||
MJX
|
||||
^^^
|
||||
- Added muscle actuators.
|
||||
@@ -18,6 +13,8 @@ MJX
|
||||
Python bindings
|
||||
^^^^^^^^^^^^^^^
|
||||
- Provide prebuilt wheels for Python 3.13.
|
||||
- Added ``bind`` method and removed id attribute from :ref:`mjSpec` objects. Using ids is error prone in scenarios of repeated attachment and
|
||||
detachment. Python users are encouraged to use names for unique identification of model elements.
|
||||
|
||||
Bug fixes
|
||||
^^^^^^^^^
|
||||
|
||||
+8
-7
@@ -585,16 +585,17 @@ Model Editing
|
||||
includes a reimplementation of the ``PyMJCF`` example in the ``dm_control``
|
||||
`tutorial notebook <https://github.com/google-deepmind/dm_control/blob/main/dm_control/mjcf/tutorial.ipynb>`__.
|
||||
|
||||
``PyMJCF`` provides a notion of "binding", giving access to :ref:`mjModel` and :ref:`mjData` values via the constructing
|
||||
elements. In the native API, this is done with object ids. For example, say we have multiple geoms containing the string
|
||||
"torso" in their name. We want to get their Cartesian positions in the XY plane from ``mjData``. This can be done as
|
||||
follows:
|
||||
``PyMJCF`` provides a notion of "binding", giving access to :ref:`mjModel` and :ref:`mjData` values via a helper class.
|
||||
In the native API, the helper class is not needed, so it is possible to directly bind an ``mjs`` object to
|
||||
:ref:`mjModel` and :ref:`mjData`. This requires the objects to have a non-empty name. For example, say we have multiple
|
||||
geoms containing the string "torso" in their name. We want to get their Cartesian positions in the XY plane from
|
||||
``mjData``. This can be done as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
torsos = [geom.id for geom in spec.geoms if 'torso' in geom.name]
|
||||
pos_x = data.geom_xpos[torsos, 0]
|
||||
pos_y = data.geom_xpos[torsos, 1]
|
||||
torsos = [data.bind(geom) for geom in spec.geoms if 'torso' in geom.name]
|
||||
pos_x = [torso.xpos[0] for torso in torsos]
|
||||
pos_y = [torso.xpos[1] for torso in torsos]
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
+3
-3
@@ -457,9 +457,9 @@
|
||||
"pos_x = []\n",
|
||||
"pos_y = []\n",
|
||||
"geoms = arena.worldbody.find_all(mj.mjtObj.mjOBJ_GEOM)\n",
|
||||
"torsos_data = [data.geom(geom.name) for geom in geoms if 'torso' in geom.name]\n",
|
||||
"torsos_model = [model.geom(geom.name) for geom in geoms if 'torso' in geom.name]\n",
|
||||
"actuators = [data.actuator(actuator.name) for actuator in arena.actuators]\n",
|
||||
"torsos_data = [data.bind(geom) for geom in geoms if 'torso' in geom.name]\n",
|
||||
"torsos_model = [model.bind(geom) for geom in geoms if 'torso' in geom.name]\n",
|
||||
"actuators = [data.bind(actuator) for actuator in arena.actuators]\n",
|
||||
"\n",
|
||||
"# Control signal frequency, phase, amplitude.\n",
|
||||
"freq = 5\n",
|
||||
|
||||
@@ -307,6 +307,28 @@
|
||||
XGROUP( MjModelTupleViews, tuple, ntuple, MJMODEL_TUPLE ) \
|
||||
XGROUP( MjModelKeyframeViews, key, nkey, MJMODEL_KEYFRAME )
|
||||
|
||||
#define MJMODEL_BIND_GROUPS \
|
||||
XGROUP( mjsActuator, actuator) \
|
||||
XGROUP( mjsBody, body ) \
|
||||
XGROUP( mjsCamera, cam ) \
|
||||
XGROUP( mjsEquality, eq ) \
|
||||
XGROUP( mjsExclude, exclude ) \
|
||||
XGROUP( mjsGeom, geom ) \
|
||||
XGROUP( mjsHField, hfield ) \
|
||||
XGROUP( mjsJoint, jnt ) \
|
||||
XGROUP( mjsLight, light ) \
|
||||
XGROUP( mjsMaterial, mat ) \
|
||||
XGROUP( mjsMesh, mesh ) \
|
||||
XGROUP( mjsNumeric, numeric ) \
|
||||
XGROUP( mjsPair, pair ) \
|
||||
XGROUP( mjsSensor, sensor ) \
|
||||
XGROUP( mjsSite, site ) \
|
||||
XGROUP( mjsSkin, skin ) \
|
||||
XGROUP( mjsTendon, tendon ) \
|
||||
XGROUP( mjsTexture, tex ) \
|
||||
XGROUP( mjsTuple, tuple ) \
|
||||
XGROUP( mjsKey, key )
|
||||
|
||||
#define MJMODEL_VIEW_GROUPS_ALTNAMES \
|
||||
XGROUP( cam, camera, MJMODEL_CAMERA ) \
|
||||
XGROUP( eq, equality, MJMODEL_EQUALITY ) \
|
||||
@@ -399,6 +421,17 @@
|
||||
XGROUP( MjDataSiteViews, site, nsite, MJDATA_SITE ) \
|
||||
XGROUP( MjDataTendonViews, tendon, ntendon, MJDATA_TENDON )
|
||||
|
||||
#define MJDATA_BIND_GROUPS \
|
||||
XGROUP( mjsActuator, actuator) \
|
||||
XGROUP( mjsBody, body ) \
|
||||
XGROUP( mjsCamera, cam ) \
|
||||
XGROUP( mjsGeom, geom ) \
|
||||
XGROUP( mjsJoint, jnt ) \
|
||||
XGROUP( mjsLight, light ) \
|
||||
XGROUP( mjsSensor, sensor ) \
|
||||
XGROUP( mjsSite, site ) \
|
||||
XGROUP( mjsTendon, tendon )
|
||||
|
||||
#define MJDATA_VIEW_GROUPS_ALTNAMES \
|
||||
XGROUP( cam, camera, MJDATA_CAMERA ) \
|
||||
XGROUP( jnt, joint, MJDATA_JOINT ) \
|
||||
|
||||
@@ -94,9 +94,12 @@ class SpecsTest(absltest.TestCase):
|
||||
|
||||
# Compile the spec and check for expected values in the model.
|
||||
model = spec.compile()
|
||||
data = mujoco.MjData(model)
|
||||
mujoco.mj_forward(model, data)
|
||||
self.assertEqual(model.nbody, 2) # 2 bodies, including the world body
|
||||
np.testing.assert_array_equal(model.body('baz').pos, [1, 2, 3])
|
||||
np.testing.assert_array_equal(model.body('baz').quat, [0, 1, 0, 0])
|
||||
np.testing.assert_array_equal(model.bind(body).pos, [1, 2, 3])
|
||||
np.testing.assert_array_equal(model.bind(body).quat, [0, 1, 0, 0])
|
||||
np.testing.assert_array_equal(data.bind(body).xpos, [1, 2, 3])
|
||||
self.assertEqual(model.nsite, 1)
|
||||
self.assertEqual(model.nuser_site, 6)
|
||||
np.testing.assert_array_equal(model.site_user[0], [1, 2, 3, 4, 5, 6])
|
||||
|
||||
@@ -1716,6 +1716,18 @@ This is useful for example when the MJB is not available as a file on disk.)"));
|
||||
MJMODEL_VIEW_GROUPS
|
||||
#undef XGROUP
|
||||
|
||||
#define XGROUP(spectype, field) \
|
||||
mjModel.def( \
|
||||
"bind", \
|
||||
[](MjModelWrapper& m, spectype& spec) -> auto& { \
|
||||
return m.indexer().field##_by_name(mjs_getString(spec.name)); \
|
||||
}, \
|
||||
py::return_value_policy::reference_internal, \
|
||||
py::arg_v("spec", py::none()));
|
||||
|
||||
MJMODEL_BIND_GROUPS
|
||||
#undef XGROUP
|
||||
|
||||
#define XGROUP(field, altname, FIELD_XMACROS) \
|
||||
mjModel.def( \
|
||||
#altname, \
|
||||
@@ -2050,6 +2062,18 @@ This is useful for example when the MJB is not available as a file on disk.)"));
|
||||
MJDATA_VIEW_GROUPS
|
||||
#undef XGROUP
|
||||
|
||||
#define XGROUP(spectype, field) \
|
||||
mjData.def( \
|
||||
"bind", \
|
||||
[](MjDataWrapper& d, spectype& spec) -> auto& { \
|
||||
return d.indexer().field##_by_name(mjs_getString(spec.name)); \
|
||||
}, \
|
||||
py::return_value_policy::reference_internal, \
|
||||
py::arg_v("spec", py::none()));
|
||||
|
||||
MJDATA_BIND_GROUPS
|
||||
#undef XGROUP
|
||||
|
||||
#define XGROUP(field, altname, FIELD_XMACROS) \
|
||||
mjData.def( \
|
||||
#altname, \
|
||||
|
||||
Reference in New Issue
Block a user