### README (README.md) # `dm_control`: Google DeepMind Infrastructure for Physics-Based Simulation. Google DeepMind's software stack for physics-based simulation and Reinforcement Learning environments, using MuJoCo physics. An **introductory tutorial** for this package is available as a Colaboratory notebook: [](https://colab.research.google.com/github/google-deepmind/dm_control/blob/main/tutorial.ipynb) ## Overview This package consists of the following "core" components: - [`dm_control.mujoco`]: Libraries that provide Python bindings to the MuJoCo physics engine. - [`dm_control.suite`]: A set of Python Reinforcement Learning environments powered by the MuJoCo physics engine. - [`dm_control.viewer`]: An interactive environment viewer. Additionally, the following components are available for the creation of more complex control tasks: - [`dm_control.mjcf`]: A library for composing and modifying MuJoCo MJCF models in Python. - `dm_control.composer`: A library for defining rich RL environments from reusable, self-contained components. - [`dm_control.locomotion`]: Additional libraries for custom tasks. - [`dm_control.locomotion.soccer`]: Multi-agent soccer tasks. If you use this package, please cite our accompanying [publication]: ``` @article{tunyasuvunakool2020, title = {dm_control: Software and tasks for continuous control}, journal = {Software Impacts}, volume = {6}, pages = {100022}, year = {2020}, issn = {2665-9638}, doi = {https://doi.org/10.1016/j.simpa.2020.100022}, url = {https://www.sciencedirect.com/science/article/pii/S2665963820300099}, author = {Saran Tunyasuvunakool and Alistair Muldal and Yotam Doron and Siqi Liu and Steven Bohez and Josh Merel and Tom Erez and Timothy Lillicrap and Nicolas Heess and Yuval Tassa}, } ``` ## Installation Install `dm_control` from PyPI by running ```sh pip install dm_control ``` > **Note**: **`dm_control` cannot be installed in "editable" mode** (i.e. `pip > install -e`). > > While `dm_control` has been largely updated to use the pybind11-based bindings > provided via the `mujoco` package, at this time it still relies on some legacy > components that are automatically generated from MuJoCo header files in a way > that is incompatible with editable mode. Attempting to install `dm_control` in > editable mode will result in import errors like: > > ``` > ImportError: cannot import name 'constants' from partially initialized module 'dm_control.mujoco.wrapper.mjbindings' ... > ``` > > The solution is to `pip uninstall dm_control` and then reinstall it without > the `-e` flag. ## Versioning Starting from version 1.0.0, we adopt semantic versioning. Prior to version 1.0.0, the `dm_control` Python package was versioned `0.0.N`, where `N` was an internal revision number that increased by an arbitrary amount at every single Git commit. If you want to install an unreleased version of `dm_control` directly from our repository, you can do so by running `pip install git+https://github.com/google-deepmind/dm_control.git`. ## Rendering The MuJoCo Python bindings support three different OpenGL rendering backends: EGL (headless, hardware-accelerated), GLFW (windowed, hardware-accelerated), and OSMesa (purely software-based). At least one of these three backends must be available in order render through `dm_control`. * Hardware rendering with a windowing system is supported via GLFW and GLEW. On Linux these can be installed using your distribution's package manager. For example, on Debian and Ubuntu, this can be done by running `sudo apt-get install libglfw3 libglew2.0`. Please note that: - [`dm_control.viewer`] can only be used with GLFW. - GLFW will not work on headless machines. * "Headless" hardware rendering (i.e. without a windowing system such as X11) requires [EXT_platform_device] support in the EGL driver. Recent Nvidia drivers support this. You will also need GLEW. On Debian and Ubuntu, this can be installed via `sudo apt-get install libglew2.0`. * Software rendering requires GLX and OSMesa. On Debian and Ubuntu these can be installed using `sudo apt-get install libgl1-mesa-glx libosmesa6`. By default, `dm_control` will attempt to use GLFW first, then EGL, then OSMesa. You can also specify a particular backend to use by setting the `MUJOCO_GL=` environment variable to `"glfw"`, `"egl"`, or `"osmesa"`, respectively. When rendering with EGL, you can also specify which GPU to use for rendering by setting the environment variable `MUJOCO_EGL_DEVICE_ID=` to the target GPU ID. ## Additional instructions for Homebrew users on macOS 1. The above instructions using `pip` should work, provided that you use a Python interpreter that is installed by Homebrew (rather than the system-default one). 2. Before running, the `DYLD_LIBRARY_PATH` environment variable needs to be updated with the path to the GLFW library. This can be done by running `export DYLD_LIBRARY_PATH=$(brew --prefix)/lib:$DYLD_LIBRARY_PATH`. [EXT_platform_device]: https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_device.txt [Releases page on the MuJoCo GitHub repository]: https://github.com/google-deepmind/mujoco/releases [MuJoCo website]: https://mujoco.org/ [publication]: https://doi.org/10.1016/j.simpa.2020.100022 [`ctypes`]: https://docs.python.org/3/library/ctypes.html [`dm_control.mjcf`]: dm_control/mjcf/README.md [`dm_control.mujoco`]: dm_control/mujoco/README.md [`dm_control.suite`]: dm_control/suite/README.md [`dm_control.viewer`]: dm_control/viewer/README.md [`dm_control.locomotion`]: dm_control/locomotion/README.md [`dm_control.locomotion.soccer`]: dm_control/locomotion/soccer/README.md --- ### CONTRIBUTING (CONTRIBUTING.md) # How to Contribute We'd love to accept your patches and contributions to this project. There are just a few small guidelines you need to follow. ## Contributor License Agreement Contributions to this project must be accompanied by a Contributor License Agreement. You (or your employer) retain the copyright to your contribution, this simply gives us permission to use and redistribute your contributions as part of the project. Head over to to see your current agreements on file or to sign a new one. You generally only need to submit a CLA once, so if you've already submitted one (even if it was for a different project), you probably don't need to do it again. ## Code reviews All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests. --- ### Migration Guide 1.0 (migration_guide_1.0.md) # dm_control: 1.0.0 update guide With 1.0.0, we changed the way dm_control uses the MuJoCo physics simulator, and migrated to new python bindings. For most users, this will require no code changes. However, some more advanced users will need to change their code slightly. Below is a list of known changes that need to be made. Please contact us if you've had to make any further changes that are not listed below. ## Required changes `dm_control.mujoco.wrapper.mjbindings.types` should not be used This module was specific to the previous implementation of the dm_control python bindings. For example, `types.MJRRECT` should be replaced with `mujoco.MjrRect`. ### `MjData.contact` has changed `MjData.contact` (often accessed as Physics.data.contact) used to offer an interface similar to a numpy structured array. For example, `data.contact.geom1` used to be a numpy array of geom IDs. With the recent update, `MjData.contact` will appear as a list of MjContact structs. Code that used to operate on the structured array will have to change. For example, the following code would get an array containing the contact distance for all contacts that involve geom_id: ``` contact = physics.data.contact involves_geom = (contact.geom1 == geom_id) | (contact.geom2 == geom_id) dists = contact[involves_geom].dist ``` After the upgrade: ``` contacts = physics.data.contact dists = [ c.dist for c in contacts if c.geom1 == geom_id or c.geom2 == geom_id ] ``` ### `.ptr.contents` will not work Code that accesses `.ptr.contents` on objects such as `MjvScene` will need to be updated. In most cases, simply using `scene` instead of `scene.ptr.contents` will work. ### Different exceptions will be thrown from MuJoCo Code (mostly in tests) that expects `dm_control.mujoco.wrapper.core.Error` exceptions, will receive different exceptions, thrown by the `mujoco` library. These will often be `ValueError` (for errors caused by input parameters), or `mujoco.FatalError` (for low level errors in MuJoCo). ### Better error handling The Python interpreter no longer crashes out when [`mju_error`](https://mujoco.readthedocs.io/en/latest/APIreference.html#mju-error) is called. Instead, `mju_error` calls are translated into `mujoco.FatalError` exceptions in Python. When Python callables are used as user-defined MuJoCo callbacks, they are now permitted to raise exceptions, which will be correctly propagated back down the Python call stack. ### Change of signature for `mj_saveModel` [`mj_saveModel`](https://mujoco.readthedocs.io/en/latest/APIreference.html#mj-savemodel) now expects a numpy `uint8` array rather than a ctypes string buffer, and doesn't require a "size" parameter (it's inferred from the numpy array size). Before: ``` model_size = mjlib.mj_sizeModel(model.ptr) buf = ctypes.create_string_buffer(model_size) mjlib.mj_saveModel(model.ptr, None, buf, model_size) ``` After: ``` model_size = mujoco.mj_sizeModel(model) buf = np.empty(model_size, np.uint8) mjlib.mj_saveModel(model.ptr, None, buf) ``` ## Optional changes The following are some changes that can make your code more concise, but are not required for it to continue working. ### Use the mujoco module directly, instead of mjlib Existing code that uses `dm_control.mujoco.wrapper.mjbindings.mjlib` can directly replace these modules with mujoco. Code that uses `enums` or `constants` from `dm_control.mujoco.wrapper.mjbindings` can also use mujoco, with slight type changes. All mujoco functions will accept the old enum values or the new ones. Before: ``` import dm_control.mujoco.wrapper.mjbindings mjlib = mjbindings.mjlib mjlib.mj_objectVelocity( physics.model.ptr, physics.data.ptr, enums.mjtObj.mjOBJ_SITE, site_id, vel, 0) ``` After: ``` import mujoco mujoco.mj_objectVelocity( physics.model.ptr, physics.data.ptr, mujoco.mjtObj.mjOBJ_SITE, site_id, vel, 0) ``` ### Assume structs are correctly initialized and memory is managed The MuJoCo C API includes functions that manage the memory for certain structs. Those include functions that allocate memory (e.g. `mj_makeModel`, `mj_makeData`, `mjv_makeScene`), functions that free memory (e.g. `mj_deleteModel`, `mj_deleteData`, `mjv_freeScene`), and functions that reset a struct to its default value (e.g. `mjv_defaultOption`, `mj_defaultVisual`). The new Python bindings take care of this. Wrapper classes like `mujoco.MjvScene` will automatically allocate memory when they're created, and release it when they're deleted, and be created with default values set. As such, allocating and freeing functions are not available through the mujoco Python bindings. The "default" functions are still available, but in most cases the calls can simply be removed. Before: ``` from dm_control.mujoco import wrapper from dm_control.mujoco.wrapper import mjbindings mjlib = mjbindings.mjlib scene_option = wrapper.core.MjvOption() mjlib.mjv_defaultOption(scene_option.ptr) ``` After: ``` from dm_control.mujoco import wrapper scene_option = wrapper.core.MjvOption() ``` --- ### Requirements (requirements.txt) absl-py==2.3.1 dm-env==1.6 dm-tree==0.1.9 glfw==2.9.0 h5py==3.14.0 labmaze==1.0.6 lxml==6.0.1 mock==5.2.0 mujoco==3.11.0 numpy==2.3.2; python_version >= '3.11' numpy==2.2.6; python_version == '3.10' numpy==2.0.2; python_version == '3.9' pillow==11.3.0 protobuf==3.19.4 pyopengl==3.1.10 pyparsing==3.2.3 pytest==8.4.1 pytest-xdist==3.8.0 pytest-timeout==2.4.0 requests==2.32.5 scipy==1.16.1; python_version >= '3.11' scipy==1.15.3; python_version == '3.10' scipy==1.13.1; python_version == '3.9' setuptools==80.9.0 tqdm==4.67.1 --- ### Dm Control/Blender/Mujoco Exporter/README (dm_control/blender/mujoco_exporter/README.md) # Export Mujoco models from Blender ## Prerequisites The MuJoCo exporter works as Blender addon, tested with ver 3.4.1. You can download Blender from [https://www.blender.org](https://www.blender.org/). ## Installation ### Preparing a plugin installer The `install.sh` command will deploy the installable version of the plugin to `./addons/mujoco_model_exporter`. This folder should be zipped: ```shell $ cd ./addons && zip -r mujoco_model_exporter.zip mujoco_model_exporter/* ``` ### Installing the plugin in Blender Open Blender and select `Edit/Preferences` menu option. {style="display:block;margin:auto"} Clicking the Install button will open a file selection dialog that will allow you to select the .zip archive with the plugin. The next window will follow, listing the installed plugin and allowing you to enable it. {style="display:block;margin:auto"} Check the checkbox next to its name to enable it. ## Modelling for Mujoco. The plugin was designed to allow the artists to keep using core Blender features when building models for Mujoco. feature | Mujoco | Blender -------------- | ------------------------- | ----------------------------------- kinematic tree | hierarchy of Bodies | Armatures or hierarchies of Objects geometry | Geoms | Meshes materials | Material + Texture assets | Material + Texture definitions lighting | Lights | Lights ### Node naming The exporter will copy the names of the bones, meshes and lights. Mujoco joints will be named after the bone that owns the respective constraint, with a postfix denoting the degree of freedom they enable. ### Modelling kinematic trees using Armatures and IK Blender Armatures allow to model kinematic capabilities of a Blender model. They comprise a tree of bones, each of which can be further extended with IK Constraints. Only the bones affected by an IK chain will receive appropriate degrees of freedom. This means that if you export a model without any IK chains, it will be exported as a static model. #### IK Constraints and bone limits IK constraints offer the easiest way to verify how a kinematic chain would behave in a physical environment. For that reason we chose to use them, along with IK bone limits, to model Mujoco joints. * Select the bone at the end of a kinematic chain, * Add an IK Bone Constraint to it. [This webpage](https://docs.blender.org/manual/en/latest/animation/constraints/tracking/ik_solver.html) contains detailed instructions. * Add a target bone to the scene and use it as the IK constraint's target. * Adjust the `Chain Length` value to reflect the number of parent bones that should become a part of this IK chain. * For each of the bones in the chain, visit the `Bone/Inverse Kinematics` tab and adjust the locks and bone limits. *NOTE*: you can take advantage of a helpful limit visualization gizmo. ### Armature free joints. Armatures form the kinematic trees, so it makes sense to give their roots all 6 degrees of freedom. This behavior can be changed by disabling the `Armature freejoint` export option. ### Modelling the geometry Mujoco uses parametric geometry comprised of primitive shapes such as cubes, spheres and capsules. It does support triangle based mesh geometry however. Blender on the other hand deals exclusively in meshes, and even though it contains a palette of such primitive shapes, these are not parametric. #### MSH files The exporter therefore exports the geometry as meshes. All meshes referenced by the scene are exported into Mujoco's native .msh format. When this plugin was created, the native .msh format was the only format to support texture mapping. Since MuJoCo 2.1.2, `.obj` files are supported. The `.msh` format is expected to be deprecated and removed soon. Until this plugin is updated to output `.obj` files we recommend all user to convert their `.msh` files to `.obj` files using [this](https://github.com/google-deepmind/mujoco/blob/main/python/mujoco/msh2obj.py) utility. #### Phong lighting model and lack of support for Cycles nodes Mujoco implements a forward rendering model with Phong lightning and support for reflective surfaces. [Mujoco material definition](http://www.mujoco.org/book/XMLreference.html#material) is fixed and limited to: * base color (diffuse color) * specularity coefficient * smoothness coefficient * reflectiveness coefficient These parameters translate well to Blender's default material definition, which should be used to model the materials exported to Mujoco. Due to the diversity and open-ended nature of Cycle's material nodes, the choice was made not to support materials defined using them. #### Division of meshes that employ multiple materials Mujoco renderer supports a single material per mesh, in contrast to Blender's multi material model. In order to support meshes with multiple materials, the exporter divides them into submeshes, each of which uses a single material. Since Blender materials are applied to faces, and a single face may only have a single material assigned, there is no risk that the subdivided mesh will exhibit any overlapping artifacts. #### Effect of mesh division on inertia and mass. The exported submeshes will have different geometry and volume than the original. This will affect the mass and inertia of the geom that uses the exported mesh. Please note that Mujoco derives these quantities not from the mesh itself, but rather from the convex hull calculated for it. The sum of hulls of subdivided meshes are not guaranteed to be equal to the hull of the original mesh. This phenomenon is illustrated on the image below. Blender | Mujoco renderer | Mujoco convex hulls ----------------------------- | ------------------------------- | ------------------- | | A different material was assigned to 2 out of 6 rectangular faces of the right cube, causing that mesh to be split into two submeshes. While they render well in Mujoco, the Moiré effect visible on the convex hull comes from two overlapping hulls, each with a different volume. #### Double-sided materials Double sided materials cause the meshes (or faces) that use them to be exported with the faces duplicated with the reverse face winding order. This is because Mujoco renderer does not support any other face culling modes than back face culling. *CAUTION*: Because this operation affects the exported geometry, it may indirectly affect the physical properties of a geom that references that mesh, such as its mass and inertia. Please use this feature with caution. #### Scaling *CAUTION*: If you are using the scaling transform, the exporter will modify your scene! The exporter will by default reset the scaling transform on all bones and meshes to ensure affine reference frame transformations. This operation can be undo'ed after the exporter is completed, but the exporter doesn't undo it automatically! ### Textures *NOTE*: This feature will be added in the next version of the exporter. #### One texture channel Mujoco renderer supports a single texture channel. It's therefore advised to use a single UV map for meshes that are to be exported to Mujoco. #### Choice between atlases and individual textures Mujoco's fixed rendering pipeline is quite fast, and handles both variants well. Therefore we leave it up to the artist to decide which it prefers using. Please keep in mind that the exporter doesn't export the texture assets. Those should be copied into the folder with the exported model manually. --- ### Dm Control/Locomotion/README (dm_control/locomotion/README.md) # Locomotion task library This package contains reusable components for defining control tasks that are related to locomotion. New users are encouraged to start by browsing the `examples/` subdirectory, which contains preconfigured RL environments associated with various research papers. These examples can serve as starting points or be customized to design new environments using the components available from this library.

## Terminology This library facilitates the creation of environments that require **walkers** to perform a **task** in an **arena**. - **walkers** refer to detached bodies that can move around in the environment. - **arenas** refer to the surroundings in which the walkers and possibly other objects exist. - **tasks** refer to the specification of observations and rewards that are passed from the "environment" to the "agent", along with runtime details such as initialization and termination logic. ## Installation and requirements See [the documentation for `dm_control`][installation-and-requirements]. ## Quickstart ```python from dm_control import composer from dm_control.locomotion.examples import basic_cmu_2019 import numpy as np # Build an example environment. env = basic_cmu_2019.cmu_humanoid_run_walls() # Get the `action_spec` describing the control inputs. action_spec = env.action_spec() # Step through the environment for one episode with random actions. time_step = env.reset() while not time_step.last(): action = np.random.uniform(action_spec.minimum, action_spec.maximum, size=action_spec.shape) time_step = env.step(action) print("reward = {}, discount = {}, observations = {}.".format( time_step.reward, time_step.discount, time_step.observation)) ``` [`dm_control.viewer`] can also be used to visualize and interact with the environment, e.g.: ```python from dm_control import viewer viewer.launch(environment_loader=basic_cmu_2019.cmu_humanoid_run_walls) ``` ## Publications This library contains environments that were adapted from several research papers. Relevant references include: - [Emergence of Locomotion Behaviours in Rich Environments (2017)][heess2017]. - [Learning human behaviors from motion capture by adversarial imitation (2017)][merel2017]. - [Hierarchical visuomotor control of humanoids (2019)][merel2019a]. - [Neural probabilistic motor primitives for humanoid control (2019)][merel2019b]. - [Deep neuroethology of a virtual rodent (2020)][merel2020]. - [CoMic: Complementary Task Learning & Mimicry for Reusable Skills (2020)][hasenclever2020] [installation-and-requirements]: ../../README.md#installation-and-requirements [`dm_control.viewer`]: ../viewer/README.md [heess2017]: https://arxiv.org/abs/1707.02286 [merel2017]: https://arxiv.org/abs/1707.02201 [merel2019a]: https://arxiv.org/abs/1811.09656 [merel2019b]: https://arxiv.org/abs/1811.11711 [merel2020]: https://openreview.net/pdf?id=SyxrxR4KPS [hasenclever2020]: http://proceedings.mlr.press/v119/hasenclever20a.html --- ### Dm Control/Locomotion/Soccer/README (dm_control/locomotion/soccer/README.md) # DeepMind MuJoCo Multi-Agent Soccer Environment. This submodule contains the components and environment used in the following works. * [Emergent Coordination through Competition][boxhead] ([dynamic team play](https://www.youtube.com/watch?v=8nU35D8vAlo), [defensive team play](https://www.youtube.com/watch?v=-gFQqB8L_mI)). * [From Motor Control to Team Play in Simulated Humanoid Football][humanoid]. ## Quickstart ```python import numpy as np from dm_control.locomotion import soccer as dm_soccer # Instantiates a 2-vs-2 BOXHEAD soccer environment with episodes of 10 seconds # each. Upon scoring, the environment reset player positions and the episode # continues. In this example, players can physically block each other and the # ball is trapped within an invisible box encapsulating the field. env = dm_soccer.load(team_size=2, time_limit=10.0, disable_walker_contacts=False, enable_field_box=True, terminate_on_goal=False, walker_type=dm_soccer.WalkerType.BOXHEAD) # Retrieves action_specs for all 4 players. action_specs = env.action_spec() # Step through the environment for one episode with random actions. timestep = env.reset() while not timestep.last(): actions = [] for action_spec in action_specs: action = np.random.uniform( action_spec.minimum, action_spec.maximum, size=action_spec.shape) actions.append(action) timestep = env.step(actions) for i in range(len(action_specs)): print( "Player {}: reward = {}, discount = {}, observations = {}.".format( i, timestep.reward[i], timestep.discount, timestep.observation[i])) ``` ## Rewards The environment provides a reward of +1 to each player when their team scores a goal, -1 when their team concedes a goal, or 0 if neither team scored on the current timestep. In addition to the sparse reward returned the environment, the player observations also contain various environment statistics that may be used to derive custom per-player shaping rewards. See `environment.observation_spec()` for the additional statistics available to the agents. ## Episode terminations If `terminate_on_goal` is set to `True`, episodes will terminate immediately with a discount factor of 0 when either side scores a goal or if the `time_limit` elapsed. If neither team scores within this time then the episode will terminate with a discount factor of `1.0`. If `terminate_on_goal` is set to `False`, players and ball positions are randomly initialized if either team scores a goal. Episodes always terminate after `time_limit` with a discount factor of `1.0`. ## Environment Viewer To visualize an example environment instance using the `dm_control` interactive viewer, execute `dm_control/locomotion/soccer/explore.py`. [boxhead]: http://arxiv.org/abs/1902.07151 [humanoid]: https://arxiv.org/abs/2105.12196 --- ### Dm Control/Locomotion/Tasks/Reference Pose/README (dm_control/locomotion/tasks/reference_pose/README.md) # Reference pose tasks This directory contains components to define tasks based on reference poses (e.g motion capture data) as well as a motion capture tracking tasks. The tasks and associated utils were developed as part of [CoMic: Complementary Task Learning & Mimicry for Reusable Skills (2020)][hasenclever2020]. The reference data is stored in HDF5 files, which can be loaded using the `HDF5TrajectoryLoader` class in `dm_control/locomotion/mocap/loader.py`. To download the data used in the CoMic project, please use `dm_control/locomotion/mocap/cmu_mocap_data.py`. In the reference pose tasks, reference trajectories are represented as `Trajectory` objects (see `dm_control/locomotion/mocap/trajectory.py`). For an example of how to construct a task, see `dm_control/locomotion/examples/cmu_2020_tracking.py`. [hasenclever2020]: https://proceedings.icml.cc/static/paper_files/icml/2020/5013-Paper.pdf --- ### Dm Control/Mjcf/README (dm_control/mjcf/README.md) # PyMJCF IMPORTANT: If you find yourself stuck while using PyMJCF, check out the various IMPORTANT boxes on this page and the [Common gotchas](#common-gotchas) section at the bottom to see if any of them is relevant. This library provides a Python object model for MuJoCo's XML-based [MJCF](http://www.mujoco.org/book/modeling.html) physics modeling language. The goal of the library is to allow users to easily interact with and modify MJCF models in Python, similarly to what the JavaScript DOM does for HTML. A key feature of this library is the ability to easily compose multiple separate MJCF models into a larger one. Disambiguation of duplicated names from different models, or multiple instances of the same model, is handled automatically. The following snippet provides a quick example of this library's typical use case. Here, the `UpperBody` class can simply instantiate two copies of `Arm`, thus reducing code duplication. The names of bodies, joints, or geoms of each `Arm` are automatically prefixed by their parent's names, and so no name collision occurs. ```python from dm_control import mjcf class Arm: def __init__(self, name): self.mjcf_model = mjcf.RootElement(model=name) self.upper_arm = self.mjcf_model.worldbody.add('body', name='upper_arm') self.shoulder = self.upper_arm.add('joint', name='shoulder', type='ball') self.upper_arm.add('geom', name='upper_arm', type='capsule', pos=[0, 0, -0.15], size=[0.045, 0.15]) self.forearm = self.upper_arm.add('body', name='forearm', pos=[0, 0, -0.3]) self.elbow = self.forearm.add('joint', name='elbow', type='hinge', axis=[0, 1, 0]) self.forearm.add('geom', name='forearm', type='capsule', pos=[0, 0, -0.15], size=[0.045, 0.15]) class UpperBody: def __init__(self): self.mjcf_model = mjcf.RootElement() self.mjcf_model.worldbody.add( 'geom', name='torso', type='box', size=[0.15, 0.045, 0.25]) left_shoulder_site = self.mjcf_model.worldbody.add( 'site', size=[1e-6]*3, pos=[-0.15, 0, 0.25]) right_shoulder_site = self.mjcf_model.worldbody.add( 'site', size=[1e-6]*3, pos=[0.15, 0, 0.25]) self.left_arm = Arm(name='left_arm') left_shoulder_site.attach(self.left_arm.mjcf_model) self.right_arm = Arm(name='right_arm') right_shoulder_site.attach(self.right_arm.mjcf_model) body = UpperBody() physics = mjcf.Physics.from_mjcf_model(body.mjcf_model) ``` ## Basic operations ### Creating an MJCF model In PyMJCF, the basic building block of a model is an `mjcf.Element`. This corresponds to an element in the generated XML. However, user code _cannot_ instantiate a generic `mjcf.Element` object directly. A valid model always consists of a single root `` element. This is represented as the special `mjcf.RootElement` type in PyMJCF, which _can_ be instantiated in user code to create an empty model. ```python from dm_control import mjcf mjcf_model = mjcf.RootElement() print(mjcf_model) # MJCF Element: ``` ### Adding new elements Attributes of the new element can be passed as kwargs: ```python my_box = mjcf_model.worldbody.add('geom', name='my_box', type='box', pos=[0, .1, 0]) print(my_box) # MJCF Element: ``` ### Parsing an existing XML document Alternatively, if an existing XML file already exists, PyMJCF can parse it to create a Python object: ```python from dm_control import mjcf # Parse from path mjcf_model = mjcf.from_path(filename) # Parse from file with open(filename) as f: mjcf_model = mjcf.from_file(f) # Parse from string with open(filename) as f: xml_string = f.read() mjcf_model = mjcf.from_xml_string(xml_string) print(type(mjcf_model)) # ``` ### Traversing through a model Consider the following MJCF model: ```xml ``` The child elements and XML attributes of an `Element` object are exposed as Python attributes. These attributes all have the same names as their XML counterparts, with one exception: the `class` XML attribute is named `dclass` in order to avoid a clash with the Python `class` keyword: ```python my_geom = mjcf_model.worldbody.body['foo'].body['bar'].geom['my_geom'] print(isinstance(mjcf_model, mjcf.Element)) # True print(my_geom.name) # 'my_geom' print(my_geom.pos) # np.array([0., 1., 2.], dtype=float) print(my_geom.class) # SyntaxError print(my_geom.dclass) # 'brick' ``` Note that attribute values in the object model are **not** affected by defaults: ```python print(mjcf_model.default.default['brick'].geom.rgba) # [1, 0, 0, 1] print(my_geom.rgba) # None ``` ### Finding elements without traversing We can also find elements directly without having to traverse through the object hierarchy: ```python found_geom = mjcf_model.find('geom', 'my_geom') print(found_geom == my_geom) # True ``` Find all elements of a given type: ```python # Note that is also considered a joint joints = mjcf_model.find_all('joint') print(len(joints)) # 2 print(joints[0] == mjcf_model.worldbody.body['foo'].freejoint) # True print(joints[1] == mjcf_model.worldbody.body['foo'].body['bar'].joint[0]) # True ``` Note that the order of elements returned by `find_all` is the same as the order in which they are declared in the model. ### Modifying XML attributes Attributes can be modified, added, or removed: ```python my_geom.pos = [1, 2, 3] print(my_geom.pos) # np.array([1., 2., 3.], dtype=float) my_geom.quat = [0, 1, 0, 0] print(my_geom.quat) # np.array([0., 1., 0., 0.], dtype=float) del my_geom.quat print(my_geom.quat) # None ``` Schema violations result in errors: ```python print(my_geom.poss) # raise AttributeError (no child or attribute called poss) my_geom.pos = 'invalid' # raise ValueError (assigning string to array) my_geom.pos = [1, 2, 3, 4, 5, 6] # raise ValueError (array length is too long) # raise ValueError (mass is a required attribute of ) del mjcf_model.find('body', 'foo').inertial.mass ``` ### Uniqueness of identifiers PyMJCF enforces the uniqueness of "identifier" attributes within a model. Identifiers consist of the `class` attribute of a ``, and all `name` attributes. Their uniqueness is only enforced within a particular namespace. For example, a `` is allowed to have the same name as a ``, whereas `` and `` actuators cannot have the same name. ```python mjcf_model.worldbody.add('geom', name='my_geom') foo = mjcf_model.worldbody.find('body', 'foo') foo.add('my_geom') # Error, duplicated geom name foo.add('foo') # OK, a geom can have the same name as a body mjcf_model.find('geom', 'foo').name = 'my_geom' # Error, duplicated geom name ``` ### Reference attributes Some attributes are references to other elements. For example, the `joint` attribute of an actuator refers to a `` element in the model. An `mjcf.Element` can be directly assigned to these reference attributes: ```python my_hinge = mjcf_model.find('joint', 'my_hinge') my_actuator = mjcf_model.actuator.add('velocity', joint=my_hinge) ``` This is the recommended way to assign reference attributes, since it guarantees that the reference is not invalidated if the referenced element is renamed. Alternatively, a string can also be assigned to reference attributes. In this case, PyMJCF does **not** attempt to verify that the named element actually exists in the model. IMPORTANT: If the element being referenced is in a different model to the reference attribute (e.g. in an attached model), the reference **must** be created by directly assigning an `mjcf.Element` object to the attribute rather than a string. Strings assigned to reference attributes cannot contain '/', since they are automatically scoped by PyMJCF upon attachment. ## Attaching models In this section we will refer to an `mjcf.RootElement` simply as a "model". Models can be _attached_ to other models in order to create compositional scenes. ```python arena = mjcf.RootElement() arena.worldbody.add('geom', name='ground', type='plane', size=[10, 10, 1]) robot = mjcf.from_xml_file('robot.xml') arena.attach(robot) ``` We refer to `arena` as the _parent model_, and `robot` as the _child model_ (or the _attached model_). ### Attachment frames When a model is attached to a site, an empty body is created in the parent model. This empty body is called an _attachment frame_. The attachment frame is created as a child of the body that contains the attachment site, and it has the same position and orientation as the site. When the XML is generated, the attachment frame's contents shadow the contents of the attached model's ``. The attachment frame's name in the generated XML is the child's `fully/qualified/prefix/`. The trailing slash ensures that the attachment frame's name never collides with a user-defined body. More concretely, if we have the following parent and child models: ```xml ``` Then the final generated XML will be: ```xml ``` IMPORTANT: The attachment frame is created _transparently_ to the user. In particular, it is NOT treated as a regular `body` by PyMJCF. Its name in the generated XML should be considered implementation detail and should NOT be relied on. Having said that, it is sometimes necessary to access the attachment frame, for example to add a joint between the parent and the child model. The easiest way to do this is to hold a reference to the object returned by a call to `attach`: ```python attachment_frame = parent_model.attach('child') attachment_frame.add('freejoint') ``` Alternatively, if a model has already been attached, the `find` function can be used with the `attachment_frame` namespace in order to retrieve the attachment frame. The `get_attachment_frame` convenience function in `mjcf.traversal_utils` can find the child model's attachment frame without needing access to the parent model. ```python frame_1 = parent_model.find('attachment_frame', 'child') # Convenience function: get the attachment frame directly from a child model frame_2 = mjcf.traversal_utils.get_attachment_frame(child_model) print(frame_1 == frame_2) # True ``` IMPORTANT: To encourage good modeling practices, the only allowed direct children of an attachment frame are `` and ``. Other types of elements should instead add be added to the `` of the attached model. ### Element ownership IMPORTANT: Elements of child models do **not** appear when traversing through the parent model. ### Default classes PyMJCF ensures that default classes of a parent model _never_ affect any of its child models. This minimises the possibility that two models become subtly "incompatible", as a model always behaves in the same way regardless of what it is attached to. The way that PyMJCF achieves this in practice is to move everything in a model's global `` context into a default class named `/`. In other words, a PyMJCF-generated model never has anything in the global default context. Instead, the generated model always looks like: ```xml ``` IMPORTANT: This transformation is _transparent_ to the user. Within Python, the above geom rgba setting is accessed as if it were a global default, i.e. `mjcf_model.default.geom.rgba`. Generally speaking, users should never have to worry about PyMJCF's internal handling of defaults. When a model is attached, its `/` default class turns into `fully/qualified/prefix/`. The trailing slash ensures that this transformation never conflicts with a user-named default class. More specifically, if we have the following parent and child models: ```xml ``` Then the final generated XML will be: ```xml ``` ### Global options A model cannot be attached to another model if _any_ of the global options are different. Global options consist of attributes of ``, `