Improve mjSpec documentation. Fixes #2074.
PiperOrigin-RevId: 692251710 Change-Id: Ia46bbbe5b7eaa890433b38fed66824f716cc9ea0
This commit is contained in:
committed by
Copybara-Service
parent
a51f346059
commit
b6037d1759
@@ -7,6 +7,9 @@ Upcoming version (not yet released)
|
|||||||
|
|
||||||
General
|
General
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
||||||
|
- The :doc:`Model Editing<programming/modeledit>` framework afforded by :ref:`mjSpec`, introduced in 3.2.0 as an
|
||||||
|
in-development feature, is now stable and recommended for general use.
|
||||||
- The global compiler flag ``exactmeshinertia`` has been removed and replaced with the mesh-specific
|
- The global compiler flag ``exactmeshinertia`` has been removed and replaced with the mesh-specific
|
||||||
:ref:`inertia<asset-mesh-inertia>` attribute.
|
:ref:`inertia<asset-mesh-inertia>` attribute.
|
||||||
- The not-useful ``convexhull`` compiler option (to disable computation of mesh convex hulls) has been removed.
|
- The not-useful ``convexhull`` compiler option (to disable computation of mesh convex hulls) has been removed.
|
||||||
|
|||||||
+13
-13
@@ -136,21 +136,21 @@ the model. We start with an example.
|
|||||||
.. code-block:: xml
|
.. code-block:: xml
|
||||||
|
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<default class="main">
|
<default class="main">
|
||||||
<geom rgba="1 0 0 1"/>
|
<geom rgba="1 0 0 1"/>
|
||||||
<default class="sub">
|
<default class="sub">
|
||||||
<geom rgba="0 1 0 1"/>
|
<geom rgba="0 1 0 1"/>
|
||||||
</default>
|
|
||||||
</default>
|
</default>
|
||||||
|
</default>
|
||||||
|
|
||||||
<worldbody>
|
<worldbody>
|
||||||
<geom type="box"/>
|
<geom type="box"/>
|
||||||
<body childclass="sub">
|
<body childclass="sub">
|
||||||
<geom type="ellipsoid"/>
|
<geom type="ellipsoid"/>
|
||||||
<geom type="sphere" rgba="0 0 1 1"/>
|
<geom type="sphere" rgba="0 0 1 1"/>
|
||||||
<geom type="cylinder" class="main"/>
|
<geom type="cylinder" class="main"/>
|
||||||
</body>
|
</body>
|
||||||
</worldbody>
|
</worldbody>
|
||||||
</mujoco>
|
</mujoco>
|
||||||
|
|
||||||
This example will not actually compile because some required information is missing, but here we are only interested
|
This example will not actually compile because some required information is missing, but here we are only interested
|
||||||
|
|||||||
+107
-43
@@ -1,13 +1,13 @@
|
|||||||
Model Editing
|
Model Editing
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
.. admonition:: Unstable API
|
.. admonition:: New API
|
||||||
:class: attention
|
:class: note
|
||||||
|
|
||||||
The API described below is new and unstable. There may be latent bugs and function signatures may change. Early
|
The API described below is new but feature complete. It is recommended for general use, but latent bugs are still
|
||||||
adopters are welcome (indeed, encouraged) to try it out and report any issues on GitHub.
|
possible. Please report any issues on GitHub.
|
||||||
|
|
||||||
As of MuJoCo 3.2, it is possible to create and modify models using the :ref:`mjSpec` struct and related API.
|
As of MuJoCo 3.2.0, it is possible to create and modify models using the :ref:`mjSpec` struct and related API.
|
||||||
This datastructure is in one-to-one correspondence with MJCF and indeed, MuJoCo's own XML parsers (both MJCF and URDF)
|
This datastructure is in one-to-one correspondence with MJCF and indeed, MuJoCo's own XML parsers (both MJCF and URDF)
|
||||||
use this API when loading a model.
|
use this API when loading a model.
|
||||||
|
|
||||||
@@ -21,25 +21,28 @@ The new API augments the traditional workflow of creating and editing models usi
|
|||||||
*compile* steps. As summarized in the :ref:`Overview chapter<Instance>`, the traditional workflow is:
|
*compile* steps. As summarized in the :ref:`Overview chapter<Instance>`, the traditional workflow is:
|
||||||
|
|
||||||
1. Create an XML model description file (MJCF or URDF) and associated assets. |br|
|
1. Create an XML model description file (MJCF or URDF) and associated assets. |br|
|
||||||
2. Call :ref:`mj_loadXML`, obtain an :ref:`mjModel` instance.
|
2. Call :ref:`mj_loadXML`, obtain an mjModel instance.
|
||||||
|
|
||||||
The new workflow is:
|
The new workflow using :ref:`mjSpec` is:
|
||||||
|
|
||||||
1. :ref:`Create<mj_makeSpec>` an empty :ref:`mjSpec` or :ref:`parse<mj_parseXML>` an existing XML file to an
|
1. :ref:`Create<mj_makeSpec>` an empty mjSpec or :ref:`parse<mj_parseXML>` an existing XML file.
|
||||||
:ref:`mjSpec`.
|
2. Programmatically edit the mjSpec datastructure by adding, modifying and removing elements.
|
||||||
2. Edit the mutable :ref:`mjSpec` datastructure adding, changing and removing elements.
|
3. :ref:`Compile<mj_compile>` the mjSpec to an mjModel instance.
|
||||||
3. Compile the :ref:`mjSpec` at any point, obtaining an updated :ref:`mjModel` instance. After compilation, the
|
|
||||||
:ref:`mjSpec` remains editable, so steps 2 and 3 are interchangeable.
|
After compilation, the mjSpec remains editable, so steps 2 and 3 are interchangeable.
|
||||||
|
|
||||||
|
|
||||||
.. _meUsage:
|
.. _meUsage:
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
~~~~~
|
~~~~~
|
||||||
Here we describe the C API for procedural model editing, but it is also exposed in the
|
|
||||||
:ref:`Python bindings<PyModelEdit>`.
|
Here we describe the C API for procedural model editing, but it is also exposed in the :ref:`Python
|
||||||
After creating a new :ref:`mjSpec` or parsing an existing XML file to an :ref:`mjSpec`, procedural editing corresponds
|
bindings<PyModelEdit>`. Advanced users can refer to `user_api_test.cc
|
||||||
to setting attributes. For example, in order to change the timestep, one can do:
|
<https://github.com/google-deepmind/mujoco/blob/main/test/user/user_api_test.cc>`__ and the MJCF parser in
|
||||||
|
`xml_native_reader.cc <https://github.com/google-deepmind/mujoco/blob/main/src/xml/xml_native_reader.cc>`__ for more
|
||||||
|
usage examples. After creating a new :ref:`mjSpec` or parsing an existing XML file to an :ref:`mjSpec`, procedural
|
||||||
|
editing corresponds to setting attributes. For example, in order to change the timestep, one can do:
|
||||||
|
|
||||||
.. code-block:: C
|
.. code-block:: C
|
||||||
|
|
||||||
@@ -55,61 +58,122 @@ In C one uses the provided :ref:`getters<AttributeGetters>` and :ref:`setters<At
|
|||||||
|
|
||||||
mjs_setString(model->modelname, "my_model");
|
mjs_setString(model->modelname, "my_model");
|
||||||
|
|
||||||
In C++ one can use these directly:
|
In C++, one can use vectors and strings directly:
|
||||||
|
|
||||||
.. code-block:: C++
|
.. code-block:: C++
|
||||||
|
|
||||||
std::string modelname = "my_model";
|
std::string modelname = "my_model";
|
||||||
*spec->modelname = modelname;
|
*spec->modelname = modelname;
|
||||||
|
|
||||||
|
Loading a spec from XML can be done as follows:
|
||||||
|
|
||||||
|
.. code-block:: C
|
||||||
|
|
||||||
|
std::array<char, 1000> error;
|
||||||
|
mjSpec* s = mj_parseXML(filename, vfs, error.data(), error.size());
|
||||||
|
|
||||||
.. _meMjsElements:
|
.. _meMjsElements:
|
||||||
|
|
||||||
Model elements
|
Model elements
|
||||||
^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^
|
||||||
|
Model elements coresponding to MJCF are exposed to the user as C structs with the ``mjs`` prefix, the definitions are
|
||||||
|
listed under the :ref:`Model Editing<tySpecStructure>` section of the struct reference. For example, an MJCF
|
||||||
|
:ref:`geom<body-geom>` corresponds to an :ref:`mjsGeom`.
|
||||||
|
|
||||||
Model elements corresponding to MJCF are added to the spec using the corresponding functions. For example, to add a box
|
Global defaults for all elements are set by :ref:`initializers<ElementInitialization>` like :ref:`mjs_defaultGeom`.
|
||||||
geom to the world body, one would do
|
These functions are defined in `user_init.c
|
||||||
|
<https://github.com/google-deepmind/mujoco/blob/main/src/user/user_init.c>`__ and are the source of truth for all
|
||||||
|
default values.
|
||||||
|
|
||||||
|
Elements cannot be created directly; they are returned to the user by the corresponding constructor function, e.g.
|
||||||
|
:ref:`mjs_addGeom`. For example, to add a box geom to the world body, one would do
|
||||||
|
|
||||||
.. code-block:: C
|
.. code-block:: C
|
||||||
|
|
||||||
mjSpec* spec = mj_makeSpec();
|
mjSpec* spec = mj_makeSpec(); // make an empty spec
|
||||||
mjsBody* world = mjs_findBody(spec, "world");
|
mjsBody* world = mjs_findBody(spec, "world"); // find the world body
|
||||||
mjsGeom* my_geom = mjs_addGeom(world, NULL);
|
mjsGeom* my_geom = mjs_addGeom(world, NULL); // add a geom to the world
|
||||||
my_geom->type = mjGEOM_BOX;
|
my_geom->type = mjGEOM_BOX; // set geom type
|
||||||
my_geom->size[0] = my_geom->size[1] = my_geom->size[2] = 0.5;
|
my_geom->size[0] = my_geom->size[1] = my_geom->size[2] = 0.5; // set box size
|
||||||
mjModel* model = mj_compile(spec);
|
mjModel* model = mj_compile(spec); // compile to mjModel
|
||||||
|
|
||||||
The ``NULL`` second argument to :ref:`mjs_addGeom` is the optional default class pointer. When using defaults
|
The ``NULL`` second argument to :ref:`mjs_addGeom` is the optional default class pointer. When using defaults
|
||||||
procedurally, default classes are passed in explicitly to element constructors. The global defaults of all elements
|
procedurally, default classes are passed in explicitly to element constructors. The global defaults of all elements
|
||||||
(used when no default class is passed in) can be inspected in
|
(used when no default class is passed in) can be inspected in
|
||||||
`user_init.c <https://github.com/google-deepmind/mujoco/blob/main/src/user/user_init.c>`__.
|
`user_init.c <https://github.com/google-deepmind/mujoco/blob/main/src/user/user_init.c>`__.
|
||||||
|
|
||||||
|
|
||||||
.. _meAttachment:
|
.. _meAttachment:
|
||||||
|
|
||||||
Attachment
|
Attachment
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
The new framework introduces a powerful new feature: attaching and detaching model subtrees. Attachment allows the user
|
This framework introduces a powerful new feature: attaching and detaching model subtrees. Attachment allows the user
|
||||||
copy a subtree from one model into another, while also copying related referenced assets and referencing elements from
|
copy a subtree from one model into another, while also copying related referenced assets and referencing elements from
|
||||||
outside the kinematic tree (e.g., actuators and sensors). Similarly, detaching a subtree will remove all associated
|
outside the kinematic tree (e.g., actuators and sensors). Similarly, detaching a subtree will remove all associated
|
||||||
elements from the model.
|
elements from the model. This feature is already used to power the :ref:`attach<body-attach>` and
|
||||||
|
:ref:`replicate<replicate>` meta-elements in MJCF. It is possible to :ref:`attach a body to a frame<mjs_attachBody>` and
|
||||||
|
to :ref:`attach a body to a site<mjs_attachToSite>`:
|
||||||
|
|
||||||
This feature is incomplete and will be described in detail once it is fully implemented, but it is already used to power
|
.. code-block:: C
|
||||||
the :ref:`attach<body-attach>` and :ref:`replicate<replicate>` meta-elements in MJCF.
|
|
||||||
|
|
||||||
|
mjSpec* parent = mj_makeSpec();
|
||||||
|
mjSpec* child = mj_makeSpec();
|
||||||
|
mjsFrame* frame = mjs_addFrame(mjs_findBody(parent, "world"), NULL);
|
||||||
|
mjsSite* site = mjs_addSite(mjs_findBody(parent, "world"), NULL);
|
||||||
|
mjsBody* body = mjs_addBody(mjs_findBody(child, "world"), NULL);
|
||||||
|
mjsBody* attached_body_1 = mjs_attachBody(frame, body, "attached-", "-1");
|
||||||
|
mjsBody* attached_body_2 = mjs_attachToSite(site, body, "attached-", "-2");
|
||||||
|
|
||||||
.. _meKnownIssues:
|
or :ref:`attach a frame to a body<mjs_attachFrame>`:
|
||||||
|
|
||||||
Known issues
|
.. code-block:: C
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
- Better documentation is still missing and will be added in the future. In the meantime, advanced users can refer
|
mjSpec* parent = mj_makeSpec();
|
||||||
to `user_api_test.cc <https://github.com/google-deepmind/mujoco/blob/main/test/user/user_api_test.cc>`__ and the MJCF
|
mjSpec* child = mj_makeSpec();
|
||||||
parser in `xml_native_reader.cc <https://github.com/google-deepmind/mujoco/blob/main/src/xml/xml_native_reader.cc>`__,
|
mjsBody* body = mjs_addBody(mjs_findBody(parent, "world"), NULL);
|
||||||
which is already using this API.
|
mjsFrame* frame = mjs_addFrame(mjs_findBody(child, "world"), NULL);
|
||||||
- One of the central design considerations of the new API is incremental compilation, meaning that after making small
|
mjsFrame* attached_frame = mjs_attachFrame(body, frame, "attached-", "-1");
|
||||||
changes to a spec that has already been compiled, subsequent re-compilation will be very fast. While the code is
|
|
||||||
written to support incremental compilation, this functionality is not fully implemented and will be added in the
|
.. _meDefault:
|
||||||
future, resulting in faster re-compilation times.
|
|
||||||
- Since the main test for the new API is the MJCF parser, which always constructs a model from scratch, there
|
Default classes
|
||||||
might be latent bugs related to model editing. Please report such bugs if you encounter them.
|
^^^^^^^^^^^^^^^
|
||||||
|
Default classes are fully supported in the new API, however using them requires an understanding of how defaults
|
||||||
|
are implemented. As explained in the :ref:`Default settings <CDefault>` section, default classes are first loaded as a
|
||||||
|
tree of dummy elements, which are then used to initialize elements which reference them. When editing models with
|
||||||
|
defaults, this initialization is explicit:
|
||||||
|
|
||||||
|
.. code-block:: C
|
||||||
|
|
||||||
|
mjSpec* spec = mj_makeSpec();
|
||||||
|
mjsDefault* main = mjs_getSpecDefault(spec);
|
||||||
|
main->geom.type = mjGEOM_BOX;
|
||||||
|
mjsGeom* geom = mjs_addGeom(mjs_findBody(spec, "world"), main);
|
||||||
|
|
||||||
|
Importantly, changing a default class after it has been used to initialize elements will not change the properties of
|
||||||
|
already initialized elements.
|
||||||
|
|
||||||
|
.. admonition:: Possible future change
|
||||||
|
:class: note
|
||||||
|
|
||||||
|
The behaviour described above, where defaults are only applied at initialization, is a remnant of the old, XML-only
|
||||||
|
loading pipeline. A future API change could allow defaults to be changed and applied after initialization. If you
|
||||||
|
think this feature is important to you, please let us know on GitHub.
|
||||||
|
|
||||||
|
.. _meSaving:
|
||||||
|
|
||||||
|
XML saving
|
||||||
|
^^^^^^^^^^
|
||||||
|
Specs can be saved to an XML file or string using :ref:`mj_saveXML` or :ref:`mj_saveXMLString`, respectively.
|
||||||
|
Saving requires that the spec first be compiled.
|
||||||
|
Importantly, the saved XML will take into account any defined defaults. This is useful when a model has many repeated
|
||||||
|
values, for example if loaded from URDF, which does not support defaults. In such a case one can add default classes,
|
||||||
|
set the class of the relevant elements, and save; the resulting XML will use the defaults and be more human-readable.
|
||||||
|
|
||||||
|
.. _meRecompilation:
|
||||||
|
|
||||||
|
In-place recompilation
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Compilation with :ref:`mj_compile` can be called at any point to obtain a new mjModel instance. In contrast,
|
||||||
|
:ref:`mj_recompile` updates an existing mjModel and mjData pair in-place, while preserving the simulation state. This
|
||||||
|
allows model editing to occur **during simulation**, for example adding or removing bodies.
|
||||||
|
|||||||
+73
-15
@@ -469,14 +469,11 @@ the raw callback pointer, and the GIL will **not** be acquired each time the cal
|
|||||||
|
|
||||||
Model editing
|
Model editing
|
||||||
=============
|
=============
|
||||||
The :doc:`Model Editing<programming/modeledit>` framework which allows for procedural model manipulation is exposed
|
The C API for model editing is documented in the :doc:`Programming<../programming/modeledit>` chapter.
|
||||||
via Python. In many ways this API is conceptually similar to ``dm_control``'s
|
This functionality is mirrored in the Python API, with the addition of several convenience methods.
|
||||||
`PyMJCF module <https://github.com/google-deepmind/dm_control/tree/main/dm_control/mjcf#readme>`__, where ``MjSpec``
|
Below is a minimal usage example, more examples can be found in the Model Editing
|
||||||
plays the role of ``mjcf_model``. The largest difference between these two APIs is speed. Native model manipulation via
|
`colab notebook <https://colab.research.google.com/github/google-deepmind/mujoco/blob/main/python/mjspec.ipynb>`__.
|
||||||
``MjSpec`` is around ~100x faster than PyMJCF.
|
|
||||||
|
|
||||||
Below is a simple example of how to use the model editing API. For more examples, please refer to
|
|
||||||
`specs_test.py <https://github.com/google-deepmind/mujoco/blob/main/python/mujoco/specs_test.py>`__.
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@@ -495,18 +492,79 @@ Below is a simple example of how to use the model editing API. For more examples
|
|||||||
...
|
...
|
||||||
model = spec.compile()
|
model = spec.compile()
|
||||||
|
|
||||||
.. admonition:: Missing features
|
Construction
|
||||||
:class: attention
|
------------
|
||||||
|
|
||||||
We are aware of multiple missing features in the Python API, including:
|
The ``MjSpec`` object wraps the :ref:`mjSpec` struct and can be constructed in three ways:
|
||||||
|
|
||||||
- Better tree traversal utilities like :python:`children = body.children()` etc.
|
1. Create an empty spec: ``spec = mujoco.MjSpec()``
|
||||||
- PyMJCF's notion of "binding", allowing access to :ref:`mjModel` and :ref:`mjData` values via the associated ``mjs``
|
2. Load the spec from XML string: ``spec = mujoco.MjSpec.from_string(xml_string)``
|
||||||
elements.
|
3. Load the spec from XML file: ``spec = mujoco.MjSpec.from_file(file_path)``
|
||||||
|
|
||||||
There are certainly other missing features that we are not aware of. Please contact us on GitHub with feature
|
Note the ``from_string()`` and ``from_file()`` methods can only be called at construction time.
|
||||||
requests or bug reports and we will prioritize accordingly.
|
|
||||||
|
|
||||||
|
Convenience methods
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
The Python bindings provide a number of convenience methods and attributes not directly available in the C API in order
|
||||||
|
to make model editing easier:
|
||||||
|
|
||||||
|
Element lists
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
Lists of all elements in a spec can be accessed using named properties, using the plural form. For example,
|
||||||
|
``spec.meshes`` returns a list of all meshes in the spec.
|
||||||
|
|
||||||
|
The following properties are implemented: ``sites``, ``geoms``, ``joints``, ``lights``, ``cameras``, ``bodies``,
|
||||||
|
``frames``, ``materials``, ``meshes``, ``pairs``, ``equalities``, ``tendons``, ``actuators``, ``skins``, ``textures``,
|
||||||
|
``texts``, ``tuples``, ``flexes``, ``hfields``, ``keys``, ``numerics``, ``excludes``, ``sensors``, ``plugins``.
|
||||||
|
|
||||||
|
Tree traversal
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
Traversal of the kinematic tree is aided by the following methods which return tree-related lists of elements:
|
||||||
|
|
||||||
|
Direct children:
|
||||||
|
Like the spec-level element lists described above, bodies have properties which return lists of all direct children.
|
||||||
|
For example, ``body.geoms`` returns a list of all geoms that are direct children of the body. This works for all
|
||||||
|
in tree elements namely ``bodies``, ``joints``, ``geoms``, ``sites``, ``cameras``, ``lights`` and ``frames``.
|
||||||
|
|
||||||
|
Recursive search:
|
||||||
|
``body.find_all()`` returns a list of all elements of the given type which are in the subtree of the given body.
|
||||||
|
Element types can be specified with the :ref:`mjtObj` enum, or with the corresponding string. For example either
|
||||||
|
``body.find_all(mujoco.mjtObj.mjOBJ_SITE)`` or ``body.find_all('site')`` will return a list of all sites under the
|
||||||
|
body.
|
||||||
|
|
||||||
|
|
||||||
|
Relationship to ``PyMJCF``
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
`dm_control <https://github.com/google-deepmind/dm_control/tree/main>`__'s
|
||||||
|
`PyMJCF <https://github.com/google-deepmind/dm_control/blob/main/dm_control/mjcf/README.md>`__ module provides similar
|
||||||
|
functionality to the native model editing API described here, but is roughly two orders of magnitude slower due to its
|
||||||
|
reliance on Python manipulation of strings.
|
||||||
|
|
||||||
|
For users familiar with ``PyMJCF``, the ``MjSpec`` object is conceptually similar to ``dm_control``'s
|
||||||
|
``mjcf_model``. A more detailed migration guide could be added here in the future; in the meantime, note that the
|
||||||
|
Model Editing
|
||||||
|
`colab notebook <https://colab.research.google.com/github/google-deepmind/mujoco/blob/main/python/mjspec.ipynb>`__
|
||||||
|
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:
|
||||||
|
|
||||||
|
.. 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]
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
|
||||||
|
- :ref:`mj_recompile` works differently than in the C API. In the C API, it modifies the model and the data in place,
|
||||||
|
while in the Python API it returns new :ref:`MjModel` and :ref:`MjData` objects. This is to avoid dangling references.
|
||||||
|
|
||||||
.. _PyBuild:
|
.. _PyBuild:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user