Documentation pass for model editing, model encoding, and mjz.

PiperOrigin-RevId: 951665744
Change-Id: I1dfe3b40f451cca686431c263815039a9c38efc8
This commit is contained in:
Sam Haves
2026-07-21 13:24:50 -07:00
committed by Copybara-Service
parent 259e6c4dc6
commit 1f1bfa9e4e
8 changed files with 226 additions and 127 deletions
+4 -11
View File
@@ -66,18 +66,11 @@ Parse spec from a file.
.. mujoco-include:: mj_encode
Encode :ref:`mjSpec` or :ref:`mjModel` to a file. The output format is determined by the file extension (case insensitive) or
``content_type``. Returns the number of bytes written on success, -1 on failure.
Encode :ref:`mjSpec` or :ref:`mjModel` to a file. The output format is determined by the file extension
(case insensitive) or ``content_type``. Returns the number of bytes written on success, -1 on failure.
The following formats are supported natively, without a registered encoder:
- **MJCF XML** — extension: ``.xml``, content_type: ``text/xml``. If an :ref:`mjSpec` is provided, saves via
:ref:`mj_saveXML`. Otherwise falls back to :ref:`mj_saveLastXML`, which requires a compiled :ref:`mjModel`.
- **MJB** — extension: ``.mjb``. MuJoCo binary format. Requires a compiled :ref:`mjModel`.
- **TXT** — extension: ``.txt``, content_type: ``text/plain``. Human-readable text dump via :ref:`mj_printModel`.
Requires a compiled :ref:`mjModel`.
For all other formats, a registered encoder is looked up via :ref:`mjp_findEncoder`.
For detailed documentation, supported output formats (``.xml``, ``.mjb``, ``.txt``, ``.mjz``), and custom encoder
plugins, see :ref:`Model Encoding & Saving <meSaving>`.
*Nullable:* ``s``, ``m``, ``vfs``, ``error``
+4 -11
View File
@@ -74,18 +74,11 @@ Save spec to XML file, return 0 on success, -1 otherwise. XML saving requires th
.. _mj_encode:
Encode :ref:`mjSpec` or :ref:`mjModel` to a file. The output format is determined by the file extension (case insensitive) or
``content_type``. Returns the number of bytes written on success, -1 on failure.
Encode :ref:`mjSpec` or :ref:`mjModel` to a file. The output format is determined by the file extension
(case insensitive) or ``content_type``. Returns the number of bytes written on success, -1 on failure.
The following formats are supported natively, without a registered encoder:
- **MJCF XML** — extension: ``.xml``, content_type: ``text/xml``. If an :ref:`mjSpec` is provided, saves via
:ref:`mj_saveXML`. Otherwise falls back to :ref:`mj_saveLastXML`, which requires a compiled :ref:`mjModel`.
- **MJB** — extension: ``.mjb``. MuJoCo binary format. Requires a compiled :ref:`mjModel`.
- **TXT** — extension: ``.txt``, content_type: ``text/plain``. Human-readable text dump via :ref:`mj_printModel`.
Requires a compiled :ref:`mjModel`.
For all other formats, a registered encoder is looked up via :ref:`mjp_findEncoder`.
For detailed documentation, supported output formats (``.xml``, ``.mjb``, ``.txt``, ``.mjz``), and custom encoder
plugins, see :ref:`Model Encoding & Saving <meSaving>`.
*Nullable:* ``s``, ``m``, ``vfs``, ``error``
+13 -7
View File
@@ -143,11 +143,11 @@ Compiler
- The :ref:`attach<body-attach>` element now supports self-attachment (attaching elements of the current model to
itself) by omitting the :ref:`model<body-attach-model>` attribute. It also supports attaching a frame via the new
:ref:`frame<body-attach-frame>` attribute, which is mutually exclusive with :ref:`body<body-attach-body>`.
- Fixed loading of ``.mjz`` archives in :ref:`simulate<saSimulate>`: the archive was unmounted before model compilation,
so assets failed to load. Failures in the ``mjz`` decoder now emit a warning with the underlying error instead of the
generic "could not decode content" message.
- The ``mjz`` decoder now searches for ``model.xml`` at the root of the archive as a fallback if the archive-named XML
is not found.
- Fixed loading of :ref:`.mjz <MJZArchives>` archives in :ref:`simulate<saSimulate>`: the archive was unmounted
before model compilation, so assets failed to load. Failures in the :ref:`mjz <MJZArchives>` decoder now emit a
warning with the underlying error instead of the generic "could not decode content" message.
- The :ref:`mjz <MJZArchives>` decoder now searches for ``model.xml`` at the root of the archive as a fallback if the
archive-named XML is not found.
- Added support for resource writing via :ref:`mju_writeResource` and the ``write`` callback in
:ref:`mjpResourceProvider`.
@@ -166,6 +166,12 @@ Python bindings
^^^^^^^^^^^^^^^
- The bindings now support free threading (`PEP 703 <https://peps.python.org/pep-0703/>`__) for Python 3.14.
Documentation
^^^^^^^^^^^^^
- Expanded documentation for :ref:`spec.encode <meSaving>` workflows and added detailed documentation for the
:ref:`MJZ Archive <MJZArchives>` format (``.mjz`` / ``.zip``).
Version 3.10.0 (June 22, 2026)
------------------------------
@@ -234,8 +240,8 @@ Bug fixes
15. :commit:`a8eaccd2` Fixed a vulnerability in the System Identification toolbox where loading a trajectory or time
series called ``np.load`` with ``allow_pickle=True``, allowing arbitrary code execution from a malicious ``.npz``
file. Signal metadata is now serialized as JSON and loaded with ``allow_pickle=False``.
16. :commit:`b9fb817a` Fixed a bug in the ``mjz`` :ref:`decoder <mjpDecoder>` where unnormalized paths would fail to be
read.
16. :commit:`b9fb817a` Fixed a bug in the :ref:`mjz <MJZArchives>` :ref:`decoder <mjpDecoder>` where unnormalized paths
would fail to be read.
17. :commit:`986d73c0` Fixed a bug where the mesh compiler would produce non-unit convex hull polygon normals.
Version 3.9.0 (May 27, 2026)
+13 -71
View File
@@ -5,91 +5,33 @@ Modeling
Introduction
------------
MuJoCo can load XML model files in its native **MJCF** format, as well as in the popular but more limited **URDF**
format. This chapter is the MJCF modeling guide. The reference manual is available in the :doc:`XMLreference`
chapter. The URDF documentation can be found elsewhere; here we only describe
MuJoCo-specific :ref:`URDF extensions <CURDF>`.
MuJoCo's native model format is **MJCF**, an XML-based language designed to describe complex dynamical systems. This
chapter is the primary MJCF modeling guide. The complete element and attribute reference manual is available in the
:doc:`XMLreference` chapter. MuJoCo also supports loading models from other formats such as URDF (see :ref:`URDF
extensions <CURDF>`), MJZ Zip archives (see :ref:`MJZ Archives <MJZArchives>`), and OpenUSD (see :doc:`OpenUSD/index`).
MJCF models can represent complex dynamical systems with a wide range of features and model elements. Accessing all
these features requires a rich modeling format, which can become cumbersome if it is not designed with usability in
mind. Therefore we have made an effort to design MJCF as a scalable format, allowing users to start small and build
more detailed models later. Particularly helpful in this regard is the extensive :ref:`default setting <CDefault>`
mechanism inspired by the idea of Cascading Style Sheets (CSS) inlined in HTML. It enables users to rapidly create
new models and experiment with them. Experimentation is further aided by numerous :ref:`options <option>` which
can be used to reconfigure the simulation pipeline, and by quick re-loading that makes model editing an interactive
process.
mechanism inspired by the idea of Cascading Style Sheets (CSS) inlined in HTML. It enables users to rapidly create new
models and experiment with them. Experimentation is further aided by numerous :ref:`options <option>` which can be used
to reconfigure the simulation pipeline, and by quick re-loading that makes model editing an interactive process.
One can think of MJCF as a hybrid between a modeling format and a programming language. There is a built-in compiler,
which is a concept normally associated with programming languages. While MJCF does not have the power of a
general-purpose programming language, a number of sophisticated compile-time computations are invoked automatically
depending on how the model is designed.
.. _Load:
.. _ProceduralModeling:
Loading models
~~~~~~~~~~~~~~
Procedural Modeling
~~~~~~~~~~~~~~~~~~~~~
As explained in :ref:`Model instances <Instance>` in the Overview chapter, MuJoCo models can be loaded from plain-text
XML files in the MJCF or URDF formats, and then compiled into a low-level mjModel. Alternatively a previously saved
mjModel can be loaded directly from a binary MJB file -- whose format is not documented but is essentially a copy of the
mjModel memory buffer. MJCF and URDF files are loaded with :ref:`mj_loadXML` while MJB files are loaded with
:ref:`mj_loadModel`.
This chapter covers high-level model design. For detailed documentation on programmatic modeling via the :ref:`mjSpec`
API—including loading, editing, compiling, and saving models in C/C++ or Python—see the
:doc:`Model Editing <programming/modeledit>` chapter.
When an XML file is loaded, it is first parsed into a document object model (DOM) using the TinyXML parser internally.
This DOM is then processed and converted into a high-level :ref:`mjSpec` object. The conversion depends on the model
format -- which is inferred from the top-level element in the XML file, and not from the file extension. Recall that a
valid XML file has a unique top-level element. This element must be :el:`mujoco` for MJCF, and :el:`robot` for URDF.
.. _Compile:
Compiling models
~~~~~~~~~~~~~~~~
Once a high-level :ref:`mjSpec` is created---by loading an MJCF file or a URDF file, or
:doc:`programmatically<programming/modeledit>`---it is compiled into :ref:`mjModel`.
Compilation is independent of loading, meaning that the compiler works in the same way regardless of how :ref:`mjSpec`
was created. Both the parser and the compiler perform extensive error checking, and abort
when the first error is encountered. The resulting error messages contain the row and column number in the XML file,
and are self-explanatory so we do not document them here. The parser uses a custom schema to make sure that the file
structure, elements and attributes are valid. The compiler then applies many additional semantic checks. Finally, one
simulation step of the compiled model is performed and any runtime errors are intercepted. The latter is done by
(temporarily) setting :ref:`mju_user_error` to point to a function that throws C++
exceptions; the user can implement similar error-interception functionality at runtime if desired.
The entire process of parsing and compilation is very fast -- less than a second if the model does not contain large
meshes or actuator lengthranges that need to be computed via simulation. This makes it possible to design models
interactively, by re-loading often and visualizing the changes. Note that the :ref:`simulate.cc <saSimulate>` code
sample has a keyboard shortcut for re-loading the current model (Ctrl+L).
.. _Save:
Saving models
~~~~~~~~~~~~~
An MJCF model can consist of multiple (included) XML files as well as meshes, height fields and textures referenced
from the XML. After compilation, the contents of all these files are assembled into mjModel, which can be saved into a
binary MJB file with :ref:`mj_saveModel`. The MJB is a stand-alone file and does not
refer to any other files. It also loads faster. So we recommend saving commonly used models as MJB and loading them
when needed for simulation.
It is also possible to save a compiled :ref:`mjSpec` as MJCF with :ref:`mj_saveLastXML`. If any real-valued fields in
the corresponding mjModel were modified after compilation (which is unusual but can happen in system identification
applications for example), the modifications are automatically copied back into :ref:`mjSpec` before saving. Note that
structural changes cannot be made in the compiled model. The XML writer attempts to generate the smallest MJCF file
which is guaranteed to compile into the same model, modulo negligible numeric differences caused by the plain text
representation of real values. The resulting file may not have the same structure as the original because MJCF has many
user convenience features, allowing the same model to be specified in different ways. The XML writer uses a "canonical"
subset of MJCF where all coordinates are local and all body positions, orientations and inertial properties are
explicitly specified. In the Computation chapter we showed an `example <_static/example.xml>`__ MJCF file and the
corresponding `saved example <_static/example_saved.xml>`__.
.. _EditModel:
Editing models
~~~~~~~~~~~~~~
As of MuJoCo 3.2, it is possible to create and modify models using the :ref:`mjSpec` struct and related API.
For further documentation, please see the :doc:`Model Editing<programming/modeledit>` chapter.
.. _Mechanisms:
+12 -9
View File
@@ -166,31 +166,34 @@ There are several entities called "model" in MuJoCo. The user defines the model
The software can then create multiple instances of the same model in different media (file or memory) and on different
levels of description (high or low). All combinations are possible as shown in the following table:
+------------+---------------------------+----------------------------+
| | High level | Low level |
+============+===========================+============================+
| **File** | MJCF/URDF (XML) | MJB (binary) |
+------------+---------------------------+----------------------------+
| **Memory** | :ref:`mjSpec` (C struct) | :ref:`mjModel` (C struct) |
+------------+---------------------------+----------------------------+
+------------+-------------------------------------+----------------------------+
| | High level | Low level |
+============+=====================================+============================+
| **File** | MJCF/URDF (XML), MJZ (Zip archive) | MJB (binary) |
+------------+-------------------------------------+----------------------------+
| **Memory** | :ref:`mjSpec` (C struct) | :ref:`mjModel` (C struct) |
+------------+-------------------------------------+----------------------------+
All runtime computations are performed with :ref:`mjModel` which is too complex to create manually. This is why we have
two levels of modeling. The high level exists for user convenience: its sole purpose is to be compiled into a low level
model on which computations can be performed. The resulting :ref:`mjModel` can be loaded and saved into a binary file
(MJB), however those are version-specific and cannot be decompiled, thus models should always be maintained as XML
files.
files or packaged into MJZ archives.
The :ref:`mjSpec` C struct is in one-to-one correspondence with the MJCF file format. The XML loader interprets the MJCF
or URDF file, creates the corresponding :ref:`mjSpec` and compiles it to :ref:`mjModel`. The user can create
:ref:`mjSpec` programmatically and then save it to MJCF or compile it. Procedural model creation and editing is
:ref:`mjSpec` programmatically and then save it to MJCF/MJZ or compile it. Procedural model creation and editing is
described in the :doc:`Model Editing <programming/modeledit>` chapter.
The following diagram shows the different paths to obtaining an :ref:`mjModel`:
- (text editor) → MJCF/URDF file → (MuJoCo parser → mjSpec → compiler) → mjModel
- MJZ archive → (MJZ decoder → mjSpec → compiler) → mjModel
- (user code) → mjSpec → (MuJoCo compiler) → mjModel
- MJB file → (model loader) → mjModel
Models and specs can also be serialized or packaged back to disk using :ref:`mj_encode`.
.. _Examples:
Examples
+56
View File
@@ -470,6 +470,62 @@ or content type:
</asset>
.. _exEncoder:
Encoders
~~~~~~~~
Encoder plugins extend asset serialization and model saving capabilities beyond native formats (XML, MJB, TXT).
Encoders are :ref:`registered <mjPLUGIN_LIB_INIT>` similarly to other MuJoCo plugins.
MuJoCo ships with a built-in Zip encoder for ``.mjz`` archives (``src/xml/mjz/mjz_encoder.cc``).
.. _exEncoderInterface:
Encoder interface
^^^^^^^^^^^^^^^^^
An encoder is described by the :ref:`mjpEncoder` struct, which has the following fields:
``content_type``
A MIME-like content type string identifying the output format (e.g. ``"application/zip"``). When :ref:`mj_encode` is
called with an explicit ``content_type`` argument, this string is used to find the appropriate encoder.
``extension``
A file extension string (including the dot) used for format matching when no content type is specified. Multiple
extensions can be separated by pipes (`|`) such as ``.mjz|.zip``.
``encode``
A callback of type :ref:`mjfEncode` that performs the actual serialization. It receives an :ref:`mjSpec`, an
optional compiled :ref:`mjModel`, an optional :ref:`mjVFS`, and an output :ref:`mjResource`. Returns the number of
bytes written on success, or -1 on failure.
``close_resource``
An optional callback that frees any memory allocated inside ``mjResource.data`` by the ``encode`` callback.
.. _exEncoderRegistration:
Registration
^^^^^^^^^^^^
Encoders must be registered before they can be used via :ref:`mj_encode`. Registration is performed via
:ref:`mjp_registerEncoder`. The :ref:`mjp_defaultEncoder` function initializes an :ref:`mjpEncoder` struct with default
values. The :ref:`mjPLUGIN_LIB_INIT` macro defines the initialization function that registers the encoder when the
plugin library is loaded.
.. code-block:: C
mjPLUGIN_LIB_INIT(my_format_encoder) {
mjpEncoder encoder;
mjp_defaultEncoder(&encoder);
encoder.content_type = "application/x-myformat";
encoder.extension = ".myf";
encoder.encode = MyEncode;
encoder.close_resource = MyCloseResource;
mjp_registerEncoder(&encoder);
}
.. _exProvider:
Resource providers
+108 -16
View File
@@ -26,6 +26,114 @@ The workflow using :ref:`mjSpec` is:
After compilation, the :ref:`mjSpec` remains editable, so steps 2 and 3 are interchangeable.
.. _Load:
.. _meLoading:
Model Parsing & Loading
~~~~~~~~~~~~~~~~~~~~~~~
As summarized in :ref:`Model instances <Instance>`, model description files (MJCF, MJZ, URDF, USD) are parsed into an
:ref:`mjSpec` using :ref:`mj_parse` (or ``mjSpec.from_file()`` / ``mjSpec.from_string()`` in Python). The model format is
inferred from the content type or file extension, and parsing into an :ref:`mjSpec` is delegated to the appropriate
:ref:`decoder <exDecoder>` plugin.
.. code-block:: C
char error[1000] = "";
mjSpec* spec = mj_parse(vfs, "robot.xml", NULL, NULL, error, sizeof(error));
For convenience, :ref:`mj_loadXML` (or Python ``MjModel.from_xml_path()``) combines parsing and compilation into a
single step, returning a compiled :ref:`mjModel` directly from an XML file or ``.mjz`` archive.
Alternatively, a pre-compiled :ref:`mjModel` can be loaded directly from a binary MJB file using :ref:`mj_loadModel`
(or Python ``MjModel.from_binary_path()``).
.. _Compile:
.. _meCompilation:
Model Compilation
~~~~~~~~~~~~~~~~~
Once a high-level :ref:`mjSpec` is created---by parsing a file, loading an archive, or constructing it
programmatically---it is compiled into an :ref:`mjModel` using :ref:`mj_compile`.
Compilation is independent of loading, working in the exact same way regardless of how :ref:`mjSpec` was constructed.
Both the parser and the compiler perform extensive error checking and abort when the first error is encountered. The
parser uses a custom schema to validate file structure, elements, and attributes, while the compiler applies semantic
checks and executes a test simulation step to catch runtime errors.
Parsing and compilation are extremely fast—typically less than a second—making interactive model design, live editing,
and rapid reloading seamless.
.. _Save:
.. _meSaving:
Model Encoding & Saving
~~~~~~~~~~~~~~~~~~~~~~~
Model specs and compiled models can be serialized to files using :ref:`mj_encode`, or directly saved to XML strings
using :ref:`mj_saveXMLString` or :ref:`mj_saveXML`.
The :ref:`mj_encode` function provides a unified entry point for serializing models:
.. code-block:: C
char error[1024] = "";
mjtSize bytes_written = mj_encode(spec, model, "robot.mjz", NULL, vfs, error, sizeof(error));
The output format is selected automatically based on the file extension (case-insensitive) or explicit ``content_type``:
- **MJCF XML** (``.xml``): Flattens the spec into a single MJCF XML file using :ref:`mj_saveXML`. If an explicit
:ref:`mjModel` argument is passed, :ref:`mj_encode` will copy modified values back from ``mjModel`` into the spec
prior to saving. In the Computation chapter we show an `example <_static/example.xml>`__ MJCF file and the
corresponding `saved example <_static/example_saved.xml>`__.
- **MJZ Archive** (``.mjz`` or ``.zip``): Bundles the spec and all associated external assets (meshes, textures,
included XMLs) into a self-contained Zip archive via the built-in ``mjz_encoder``.
- **MJB Binary** (``.mjb``): Serializes the compiled :ref:`mjModel` in MuJoCo binary format via :ref:`mj_saveModel`.
MJB files are standalone, do not refer to external files, and load faster than XML, but are version-specific and
cannot be decompiled back to XML. Requires a compiled ``model``; does **not** serialize anything from ``spec``.
- **TXT** (``.txt``): Writes a human-readable text dump via :ref:`mj_printModel`. Useful for diffing and debugging.
Requires a compiled ``model``; does **not** serialize anything from ``spec``.
Importantly, 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.
.. _MJZArchives:
MJZ Archives
~~~~~~~~~~~~
Complex MuJoCo models often consist of multiple files: a main MJCF XML file, included XML sub-trees, and external asset
files (meshes, textures, height fields). The **MJZ** format (extension ``.mjz`` or ``.zip``) provides a convenient way
to bundle an entire model and all of its referenced assets into a single **Zip archive**.
Root XML Discovery
^^^^^^^^^^^^^^^^^^
When decoding an ``.mjz`` archive, MuJoCo searches for the root model XML file in the following order:
1. `<archive_stem>.xml` at the root of the archive (e.g. ``my_model.xml`` inside ``my_model.mjz``). This is
considered **best practice**.
2. `<archive_stem>/<archive_stem>.xml` inside a top-level directory matching the archive name (e.g.
``my_model/my_model.xml``).
3. `model.xml` at the root of the archive (common zipped MJCF fallback).
VFS Requirement
^^^^^^^^^^^^^^^
Parsing and compilation of an ``.mjz`` archive (and all of its contained asset files) require using the **exact same
VFS instance**.
.. _meCustomFormats:
Custom formats
~~~~~~~~~~~~~~
Adding support for new file formats can be done with :ref:`mjp_registerDecoder` and :ref:`mjp_registerEncoder`.
When :ref:`mj_parse` and :ref:`mj_encode` are called for a non-native extension or content type, the appropriate plugins
are found via :ref:`mjp_findDecoder` and :ref:`mjp_findEncoder`. For further details on writing custom format plugins,
see :ref:`Decoders <exDecoder>` and :ref:`Encoders <exEncoder>`.
.. _meUsage:
Usage
@@ -59,13 +167,6 @@ In C++, one can use vectors and strings directly:
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
@@ -258,15 +359,6 @@ already initialized elements.
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:
+16 -2
View File
@@ -597,11 +597,25 @@ Compiled ``MjSpec`` objects can be saved to XML string with the ``to_xml()`` met
</worldbody>
</mujoco>
Alternatively, the spec can be saved directly to a file using ``encode()``:
Alternatively, specs or compiled models can be serialized to a file using ``encode()``:
.. code-block:: python
spec.encode('model.xml', model)
# Save spec directly to an MJCF XML file
spec.encode('model.xml')
# Save XML from a compiled model; copies modified model values back to spec prior to saving
spec.encode('model.xml', model=model)
# Save compiled model to binary MJB format (serializes mjModel only, not spec)
spec.encode('model.mjb', model=model)
# Package spec and all referenced external assets (meshes, textures) into an MJZ zip archive
spec.encode('robot.mjz', model=model)
The ``encode()`` method accepts the target filename, an optional compiled ``model`` (:ref:`MjModel`), an optional
``vfs`` (:ref:`MjVFS`), and an optional ``content_type``. The target format is automatically determined by the file
extension (``.xml``, ``.mjb``, ``.txt``, ``.mjz``) or content type.
Attachment
----------