Improve mjSpec documentation. Fixes #2074.
PiperOrigin-RevId: 692251710 Change-Id: Ia46bbbe5b7eaa890433b38fed66824f716cc9ea0
This commit is contained in:
committed by
Copybara-Service
parent
a51f346059
commit
b6037d1759
+107
-43
@@ -1,13 +1,13 @@
|
||||
Model Editing
|
||||
-------------
|
||||
|
||||
.. admonition:: Unstable API
|
||||
:class: attention
|
||||
.. admonition:: New API
|
||||
:class: note
|
||||
|
||||
The API described below is new and unstable. There may be latent bugs and function signatures may change. Early
|
||||
adopters are welcome (indeed, encouraged) to try it out and report any issues on GitHub.
|
||||
The API described below is new but feature complete. It is recommended for general use, but latent bugs are still
|
||||
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)
|
||||
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:
|
||||
|
||||
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
|
||||
:ref:`mjSpec`.
|
||||
2. Edit the mutable :ref:`mjSpec` datastructure adding, changing and removing elements.
|
||||
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.
|
||||
1. :ref:`Create<mj_makeSpec>` an empty mjSpec or :ref:`parse<mj_parseXML>` an existing XML file.
|
||||
2. Programmatically edit the mjSpec datastructure by adding, modifying and removing elements.
|
||||
3. :ref:`Compile<mj_compile>` the mjSpec to an mjModel instance.
|
||||
|
||||
After compilation, the mjSpec remains editable, so steps 2 and 3 are interchangeable.
|
||||
|
||||
|
||||
.. _meUsage:
|
||||
|
||||
Usage
|
||||
~~~~~
|
||||
Here we describe the C API for procedural model editing, but it is also exposed in the
|
||||
:ref:`Python bindings<PyModelEdit>`.
|
||||
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:
|
||||
|
||||
Here we describe the C API for procedural model editing, but it is also exposed in the :ref:`Python
|
||||
bindings<PyModelEdit>`. Advanced users can refer to `user_api_test.cc
|
||||
<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
|
||||
|
||||
@@ -55,61 +58,122 @@ In C one uses the provided :ref:`getters<AttributeGetters>` and :ref:`setters<At
|
||||
|
||||
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++
|
||||
|
||||
std::string modelname = "my_model";
|
||||
*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:
|
||||
|
||||
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
|
||||
geom to the world body, one would do
|
||||
Global defaults for all elements are set by :ref:`initializers<ElementInitialization>` like :ref:`mjs_defaultGeom`.
|
||||
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
|
||||
|
||||
mjSpec* spec = mj_makeSpec();
|
||||
mjsBody* world = mjs_findBody(spec, "world");
|
||||
mjsGeom* my_geom = mjs_addGeom(world, NULL);
|
||||
my_geom->type = mjGEOM_BOX;
|
||||
my_geom->size[0] = my_geom->size[1] = my_geom->size[2] = 0.5;
|
||||
mjModel* model = mj_compile(spec);
|
||||
mjSpec* spec = mj_makeSpec(); // make an empty spec
|
||||
mjsBody* world = mjs_findBody(spec, "world"); // find the world body
|
||||
mjsGeom* my_geom = mjs_addGeom(world, NULL); // add a geom to the world
|
||||
my_geom->type = mjGEOM_BOX; // set geom type
|
||||
my_geom->size[0] = my_geom->size[1] = my_geom->size[2] = 0.5; // set box size
|
||||
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
|
||||
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
|
||||
`user_init.c <https://github.com/google-deepmind/mujoco/blob/main/src/user/user_init.c>`__.
|
||||
|
||||
|
||||
.. _meAttachment:
|
||||
|
||||
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
|
||||
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
|
||||
the :ref:`attach<body-attach>` and :ref:`replicate<replicate>` meta-elements in MJCF.
|
||||
.. code-block:: C
|
||||
|
||||
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
|
||||
to `user_api_test.cc <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>`__,
|
||||
which is already using this API.
|
||||
- One of the central design considerations of the new API is incremental compilation, meaning that after making small
|
||||
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
|
||||
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
|
||||
might be latent bugs related to model editing. Please report such bugs if you encounter them.
|
||||
mjSpec* parent = mj_makeSpec();
|
||||
mjSpec* child = mj_makeSpec();
|
||||
mjsBody* body = mjs_addBody(mjs_findBody(parent, "world"), NULL);
|
||||
mjsFrame* frame = mjs_addFrame(mjs_findBody(child, "world"), NULL);
|
||||
mjsFrame* attached_frame = mjs_attachFrame(body, frame, "attached-", "-1");
|
||||
|
||||
.. _meDefault:
|
||||
|
||||
Default classes
|
||||
^^^^^^^^^^^^^^^
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user