Add replicate element to MJCF. Fixes #553

PiperOrigin-RevId: 629393578
Change-Id: Ia1db1b9e47eba8868117b0f7b85edcd0cc1b6d79
This commit is contained in:
Alessio Quaglino
2024-04-30 06:28:09 -07:00
committed by Copybara-Service
parent 6a5bbb5157
commit 26f23066d1
30 changed files with 8756 additions and 2807 deletions
+70 -3
View File
@@ -110,9 +110,9 @@ Meta elements
These elements are not strictly part of the low-level MJCF format definition, but rather instruct the compiler to
perform some operation on the model. A general property of meta-elements is that they disappear from the model upon
saving the XML. There are currently four meta-elements in MJCF:
saving the XML. There are currently five meta-elements in MJCF:
- :ref:`include<include>` and :ref:`frame<frame>`, which are outside of the schema.
- :ref:`include<include>`, :ref:`frame<frame>`, and :ref:`replicate<replicate>` which are outside of the schema.
- :ref:`composite<body-composite>` and :ref:`flexcomp<body-flexcomp>` which are part of the schema, but serve to
procedurally generate other MJCF elements.
@@ -163,6 +163,72 @@ in their direct children. The attributes of the frame meta-element are documente
Note that in the saved model, the frame elements have disappeared but their transformation was accumulated with those
of their child elements.
.. _replicate:
**replicate** (R)
^^^^^^^^^^^^^^^^^
The replicate element duplicates the enclosed kinematic tree elements with incremental translational and rotational
offsets, adding namespace suffixes to avoid name collisions. Appended suffix strings are integers in the
range ``[0...count-1]`` with the minimum number of digits required to represent the total element count (i.e., if
replicating 200 times, suffixes will be ``000, 001, ...`` etc). All referencing elements are automatically replicated
and namespaced appropriately. Detailed examples of models using replicate can be found in the
`model/replicate/ <https://github.com/google-deepmind/mujoco/tree/main/model/replicate>`__ directory.
.. _replicate-count:
:at:`count`: :at-val:`int, required`
The number of replicas. Must be positive.
.. _replicate-sep:
:at:`sep`: :at-val:`string, optional`
The namespace separator. This optional string is prepended to the namespace suffix string. Note that for nested
replicate elements, the innermost namespace suffixes are appended first.
.. _replicate-offset:
:at:`offset`: :at-val:`real(3), optional`
Translational offset along the three coordinate axes. In general, the frame of the offset is with respect to the
previous replica, except for the first one which is with respect to the replicate element's parent.
If there is no rotation, these values are always in the frame of the replicate element's parent.
.. _replicate-euler:
:at:`euler`: :at-val:`real(3), optional`
Rotation angles around three coordinate axes between two subsequent replicas. The angular units and rotation sequence
respect the global :ref:`angle<compiler-angle>` and :ref:`eulerseq<compiler-eulerseq>` settings. Rotation is always
with respect to the frame of the previous replica, so total rotation is cumulative.
.. collapse:: Usage example of replicate
Loading this model and saving it:
.. code-block:: xml
<mujoco>
<worldbody>
<replicate count="2" offset="0 1 0" euler="90 0 0">
<replicate count="2" sep="-" offset="1 0 0" euler="0 90 0">
<geom name="Alice" size=".1"/>
</replicate>
</replicate>
</worldbody>
</mujoco>
Results in this model:
.. code-block:: xml
<mujoco>
<worldbody>
<geom name="Alice-00" size="0.1"/>
<geom name="Alice-10" size="0.1" pos="1 0 0" quat="1 0 1 0"/>
<geom name="Alice-01" size="0.1" pos="0 1 0" quat="1 1 0 0"/>
<geom name="Alice-11" size="0.1" pos="1 1 0" quat="0.5 0.5 0.5 0.5"/>
</worldbody>
</mujoco>
.. _include:
**include** (*)
@@ -2766,7 +2832,8 @@ coordinates results in compiler error. See :ref:`CComposite` in the modeling gui
geom and 3 orthogonal sliding joints, allowing translation but not rotation. The geom condim and priority attributes
are set to 1 by default. This makes the spheres have frictionless contacts with all other geoms (unless the priority
of some frictional geom is higher). The user can replace the default sliders with multiple joints of kind="particle"
and replace the default sphere with a custom geom.
and replace the default sphere with a custom geom. Note that the particle composite type is deprecated and might be
removed in a future version. Instead of particle, it is recommended to use :ref:`replicate`.
The **grid** type creates a 1D or 2D grid of bodies, each having a sphere geom, a sphere site, and 3 orthogonal
sliding joints by default. The :el:`pin` sub-element can be used to specify that some bodies should not have joints,
+27 -20
View File
@@ -7,15 +7,22 @@ Upcoming version (not yet released)
General
^^^^^^^
1. Added ``mjModel.mesh_scale``: the scaling applied to asset vertices, as specified in the
.. youtube:: 5k0_wsIRAFc
:align: right
:width: 240px
1. Added the :ref:`replicate<replicate>` to MJCF, a :ref:`meta-element<meta-element>` which permits to repeat a subtree
with incremental translational and rotational offsets.
2. Added ``mjModel.mesh_scale``: the scaling applied to asset vertices, as specified in the
:ref:`scale<asset-mesh-scale>` attribute.
2. Added visual properties which are ignored by the native renderer, but can be used by external renderers:
3. Added visual properties which are ignored by the native renderer, but can be used by external renderers:
- :ref:`light/bulbradius<body-light-bulbradius>` attribute and corresponding ``mjModel.light_bulbradius`` field.
- :ref:`material/metallic<asset-material-metallic>` attribute and corresponding ``mjModel.material_metallic`` field.
- :ref:`material/roughness<asset-material-roughness>` attribute and corresponding ``mjModel.material_roughness``
field.
3. The type of the ``size`` argument of :ref:`mj_stackAllocNum` and :ref:`mj_stackAllocInt` was changed from ``int``
4. The type of the ``size`` argument of :ref:`mj_stackAllocNum` and :ref:`mj_stackAllocInt` was changed from ``int``
to ``size_t``.
MJX
@@ -23,33 +30,33 @@ MJX
.. admonition:: Breaking API changes
:class: attention
4. Removed deprecated ``mjx.device_get_into`` and ``mjx.device_put`` functions as they lack critical new
5. Removed deprecated ``mjx.device_get_into`` and ``mjx.device_put`` functions as they lack critical new
functionality.
**Migration:** Use ``mjx.get_data_into`` instead of ``mjx.device_get_into``, and ``mjx.put_data`` instead of
``mjx.device_put``.
5. Added cylinder plane collisions.
6. Added ``efc_type`` to ``mjx.Data`` and ``dim``, ``efc_address`` to ``mjx.Contact``.
7. Added ``geom`` to ``mjx.Contact`` and marked ``geom1``, ``geom2`` deprecated.
8. Added ``ne``, ``nf``, ``nl``, ``nefc``, and ``ncon`` to ``mjx.Data`` to match ``mujoco.MjData``.
9. Given the above added fields, removed ``mjx.get_params``, ``mjx.ncon``, and ``mjx.count_constraints``.
10. Changed the way meshes are organized on device to speed up collision detection when a mesh is replicated for many
6. Added cylinder plane collisions.
7. Added ``efc_type`` to ``mjx.Data`` and ``dim``, ``efc_address`` to ``mjx.Contact``.
8. Added ``geom`` to ``mjx.Contact`` and marked ``geom1``, ``geom2`` deprecated.
9. Added ``ne``, ``nf``, ``nl``, ``nefc``, and ``ncon`` to ``mjx.Data`` to match ``mujoco.MjData``.
10. Given the above added fields, removed ``mjx.get_params``, ``mjx.ncon``, and ``mjx.count_constraints``.
11. Changed the way meshes are organized on device to speed up collision detection when a mesh is replicated for many
geoms.
11. Fixed a bug where capsules might be ignored in broadphase colliision checking.
12. Added cylinder collisions using SDFs.
13. Added support for all :ref:`condim <coContact>`: 1, 3, 4, 6.
14. Add support functions for ``id2name`` and ``name2id``, MJX versions of :ref:`mj_id2name` and :ref:`mj_name2id`.
15. Added support for :ref:`gravcomp<body-gravcomp>` and :ref:`actuatorgravcomp<body-joint-actuatorgravcomp>`.
16. Fixed a bug in ``mjx.ray`` for sometimes allowed negative distances for ray-mesh tests.
12. Fixed a bug where capsules might be ignored in broadphase colliision checking.
13. Added cylinder collisions using SDFs.
14. Added support for all :ref:`condim <coContact>`: 1, 3, 4, 6.
15. Add support functions for ``id2name`` and ``name2id``, MJX versions of :ref:`mj_id2name` and :ref:`mj_name2id`.
16. Added support for :ref:`gravcomp<body-gravcomp>` and :ref:`actuatorgravcomp<body-joint-actuatorgravcomp>`.
17. Fixed a bug in ``mjx.ray`` for sometimes allowed negative distances for ray-mesh tests.
Bug fixes
^^^^^^^^^
17. Defaults of lights were not being saved, now fixed.
18. Prevent overwriting of frame names by body names when saving an XML. Bug introduced in 3.1.4.
19. Fixed bug in Python binding of :ref:`mj_saveModel`: ``buffer`` argument was documented as optional but was actually
18. Defaults of lights were not being saved, now fixed.
19. Prevent overwriting of frame names by body names when saving an XML. Bug introduced in 3.1.4.
20. Fixed bug in Python binding of :ref:`mj_saveModel`: ``buffer`` argument was documented as optional but was actually
not optional.
20. Fixed bug that prevented memory allocations larger than 2.15 GB.
21. Fixed bug that prevented memory allocations larger than 2.15 GB.
Version 3.1.4 (April 10th, 2024)
File diff suppressed because it is too large Load Diff
+76 -436
View File
@@ -27,20 +27,20 @@
<default>
<geom solimp=".9 .9 .01"/>
<default class="object1">
<geom type="capsule" material="object1" size="0.1 0.05"/>
<default class="capsule">
<geom type="capsule" material="capsule" size="0.1 0.05"/>
</default>
<default class="object2">
<geom type="ellipsoid" material="object2" size="0.15 0.1 0.07"/>
<default class="ellipsoid">
<geom type="ellipsoid" material="ellipsoid" size="0.15 0.1 0.07"/>
</default>
<default class="object3">
<geom type="box" material="object3" size="0.15 0.1 0.05"/>
<default class="box">
<geom type="box" material="box" size="0.15 0.1 0.05"/>
</default>
<default class="object4">
<geom type="cylinder" material="object4" size="0.1 0.05" condim="4" friction="1 .01 .01"/>
<default class="cylinder">
<geom type="cylinder" material="cylinder" size="0.1 0.05" condim="4" friction="1 .01 .01"/>
</default>
<default class="object5">
<geom type="sphere" material="object5" size="0.1"/>
<default class="sphere">
<geom type="sphere" material="sphere" size="0.1"/>
</default>
<default class="border">
<geom type="capsule" size="0.4" rgba=".4 .4 .4 1"/>
@@ -56,13 +56,13 @@
rgb1="0.6 0.6 0.6" rgb2="0.6 0.6 0.6" markrgb="1 1 1"/>
<texture name="texplane" type="2d" builtin="checker" rgb1=".4 .4 .4" rgb2=".6 .6 .6"
width="512" height="512"/>
<material name='MatPlane' reflectance='0.3' texture="texplane" texrepeat="1 1" texuniform="true"
<material name="MatPlane" reflectance="0.3" texture="texplane" texrepeat="1 1" texuniform="true"
rgba=".7 .7 .7 1"/>
<material name='object1' texture="texgeom" texuniform="true" rgba=".4 .9 .6 1" />
<material name='object2' texture="texgeom" texuniform="true" rgba=".4 .6 .9 1" />
<material name='object3' texture="texgeom" texuniform="true" rgba=".4 .9 .9 1" />
<material name='object4' texture="texgeom" texuniform="true" rgba=".8 .6 .8 1" />
<material name='object5' texture="texgeom" texuniform="true" rgba=".9 .1 .1 1" />
<material name="capsule" texture="texgeom" texuniform="true" rgba=".4 .9 .6 1" />
<material name="ellipsoid" texture="texgeom" texuniform="true" rgba=".4 .6 .9 1" />
<material name="box" texture="texgeom" texuniform="true" rgba=".4 .9 .9 1" />
<material name="cylinder" texture="texgeom" texuniform="true" rgba=".8 .6 .8 1" />
<material name="sphere" texture="texgeom" texuniform="true" rgba=".9 .1 .1 1" />
</asset>
<visual>
@@ -84,430 +84,70 @@
<geom class="borderpost" pos="3 -3 0"/>
<geom class="borderpost" pos="-3 -3 0"/>
<body pos="-2 -2 1" quat="-0.632456 -0.632456 0.316228 0.316228">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 -2 2" quat="-0.5547 -0.5547 0.5547 0.27735">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 -2 3" quat="-0.471405 -0.471405 0.707107 0.235702">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 -2 4" quat="-0.471405 -0.471405 0.707107 0.235702">
<freejoint/>
<geom class="object1"/>
</body>
<frame pos="-2 -2 2.5">
<replicate count="5" offset="0 1 0" euler="0 180 0">
<frame pos="0 0 -1.5">
<replicate count="4" offset="0 0 1" euler="0 0 60">
<body euler="30 40 0">
<freejoint/>
<geom class="capsule"/>
</body>
</replicate>
</frame>
</replicate>
</frame>
<body pos="-2 -1 1" quat="-0.755929 -0.377964 0.377964 0.377964">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 -1 2" quat="-0.632456 -0.316228 0.632456 0.316228">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 -1 3" quat="-0.516398 -0.258199 0.774597 0.258199">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 -1 4" quat="-0.516398 -0.258199 0.774597 0.258199">
<freejoint/>
<geom class="object1"/>
</body>
<frame pos="-1 -2 2.5">
<replicate count="5" offset="0 1 0" euler="0 180 0">
<frame pos="0 0 -1.5">
<replicate count="4" offset="0 0 1" euler="0 0 60">
<body euler="20 40 60">
<freejoint/>
<geom class="ellipsoid"/>
</body>
</replicate>
</frame>
</replicate>
</frame>
<body pos="-2 0 1" quat="-0.816497 0 0.408248 0.408248">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 0 2" quat="-0.666667 0 0.666667 0.333333">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 0 3" quat="-0.534522 0 0.801784 0.267261">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 0 4" quat="-0.534522 0 0.801784 0.267261">
<freejoint/>
<geom class="object1"/>
</body>
<frame pos="0 -2 3.5">
<replicate count="5" offset="0 1 0" euler="0 180 0">
<frame pos="0 0 -1.5">
<replicate count="4" offset="0 0 1" euler="0 0 60">
<body euler="30 70 110">
<freejoint/>
<geom class="box"/>
</body>
</replicate>
</frame>
</replicate>
</frame>
<body pos="-2 1 1" quat="-0.755929 0.377964 0.377964 0.377964">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 1 2" quat="-0.632456 0.316228 0.632456 0.316228">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 1 3" quat="-0.516398 0.258199 0.774597 0.258199">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 1 4" quat="-0.516398 0.258199 0.774597 0.258199">
<freejoint/>
<geom class="object1"/>
</body>
<frame pos="1 -2 2.5">
<replicate count="5" offset="0 1 0" euler="0 180 0">
<frame pos="0 0 -1.5">
<replicate count="4" offset="0 0 1" euler="0 0 60">
<body euler="60 30 0">
<freejoint/>
<geom class="cylinder"/>
</body>
</replicate>
</frame>
</replicate>
</frame>
<body pos="-2 2 1" quat="-0.632456 0.632456 0.316228 0.316228">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 2 2" quat="-0.5547 0.5547 0.5547 0.27735">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 2 3" quat="-0.471405 0.471405 0.707107 0.235702">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 2 4" quat="-0.471405 0.471405 0.707107 0.235702">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-1 -2 1" quat="-0.377964 -0.755929 0.377964 0.377964">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 -2 2" quat="-0.316228 -0.632456 0.632456 0.316228">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 -2 3" quat="-0.258199 -0.516398 0.774597 0.258199">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 -2 4" quat="-0.258199 -0.516398 0.774597 0.258199">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 -1 1" quat="-0.5 -0.5 0.5 0.5">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 -1 2" quat="-0.377964 -0.377964 0.755929 0.377964">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 -1 3" quat="-0.288675 -0.288675 0.866025 0.288675">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 -1 4" quat="-0.288675 -0.288675 0.866025 0.288675">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 0 1" quat="-0.57735 0 0.57735 0.57735">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 0 2" quat="-0.408248 0 0.816497 0.408248">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 0 3" quat="-0.301511 0 0.904534 0.301511">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 0 4" quat="-0.301511 0 0.904534 0.301511">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 1 1" quat="-0.5 0.5 0.5 0.5">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 1 2" quat="-0.377964 0.377964 0.755929 0.377964">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 1 3" quat="-0.288675 0.288675 0.866025 0.288675">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 1 4" quat="-0.288675 0.288675 0.866025 0.288675">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 2 1" quat="-0.377964 0.755929 0.377964 0.377964">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 2 2" quat="-0.316228 0.632456 0.632456 0.316228">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 2 3" quat="-0.258199 0.516398 0.774597 0.258199">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 2 4" quat="-0.258199 0.516398 0.774597 0.258199">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="0 -2 1" quat="0 -0.816497 0.408248 0.408248">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 -2 2" quat="0 -0.666667 0.666667 0.333333">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 -2 3" quat="0 -0.534522 0.801784 0.267261">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 -2 4" quat="0 -0.534522 0.801784 0.267261">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 -1 1" quat="0 -0.57735 0.57735 0.57735">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 -1 2" quat="0 -0.408248 0.816497 0.408248">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 -1 3" quat="0 -0.301511 0.904534 0.301511">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 -1 4" quat="0 -0.301511 0.904534 0.301511">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0.3 0 1" quat="0 0 0.707107 0.707107">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 0 2" quat="0 0 0.894427 0.447214">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 0 3" quat="0 0 0.948683 0.316228">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 0 4" quat="0 0 0.948683 0.316228">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 1 1" quat="0 0.57735 0.57735 0.57735">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 1 2" quat="0 0.408248 0.816497 0.408248">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 1 3" quat="0 0.301511 0.904534 0.301511">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 1 4" quat="0 0.301511 0.904534 0.301511">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 2 1" quat="0 0.816497 0.408248 0.408248">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 2 2" quat="0 0.666667 0.666667 0.333333">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 2 3" quat="0 0.534522 0.801784 0.267261">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 2 4" quat="0 0.534522 0.801784 0.267261">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="1 -2 1" quat="0.377964 -0.755929 0.377964 0.377964">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 -2 2" quat="0.316228 -0.632456 0.632456 0.316228">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 -2 3" quat="0.258199 -0.516398 0.774597 0.258199">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 -2 4" quat="0.258199 -0.516398 0.774597 0.258199">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 -1 1" quat="0.5 -0.5 0.5 0.5">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 -1 2" quat="0.377964 -0.377964 0.755929 0.377964">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 -1 3" quat="0.288675 -0.288675 0.866025 0.288675">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 -1 4" quat="0.288675 -0.288675 0.866025 0.288675">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 0 1" quat="0.57735 0 0.57735 0.57735">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 0 2" quat="0.408248 0 0.816497 0.408248">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 0 3" quat="0.301511 0 0.904534 0.301511">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 0 4" quat="0.301511 0 0.904534 0.301511">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 1 1" quat="0.5 0.5 0.5 0.5">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 1 2" quat="0.377964 0.377964 0.755929 0.377964">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 1 3" quat="0.288675 0.288675 0.866025 0.288675">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 1 4" quat="0.288675 0.288675 0.866025 0.288675">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 2 1" quat="0.377964 0.755929 0.377964 0.377964">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 2 2" quat="0.316228 0.632456 0.632456 0.316228">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 2 3" quat="0.258199 0.516398 0.774597 0.258199">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 2 4" quat="0.258199 0.516398 0.774597 0.258199">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="2 -2 1" quat="0.632456 -0.632456 0.316228 0.316228">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 -2 2" quat="0.5547 -0.5547 0.5547 0.27735">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 -2 3" quat="0.471405 -0.471405 0.707107 0.235702">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 -2 4" quat="0.471405 -0.471405 0.707107 0.235702">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 -1 1" quat="0.755929 -0.377964 0.377964 0.377964">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 -1 2" quat="0.632456 -0.316228 0.632456 0.316228">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 -1 3" quat="0.516398 -0.258199 0.774597 0.258199">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 -1 4" quat="0.516398 -0.258199 0.774597 0.258199">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 0 1" quat="0.816497 0 0.408248 0.408248">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 0 2" quat="0.666667 0 0.666667 0.333333">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 0 3" quat="0.534522 0 0.801784 0.267261">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 0 4" quat="0.534522 0 0.801784 0.267261">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 1 1" quat="0.755929 0.377964 0.377964 0.377964">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 1 2" quat="0.632456 0.316228 0.632456 0.316228">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 1 3" quat="0.516398 0.258199 0.774597 0.258199">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 1 4" quat="0.516398 0.258199 0.774597 0.258199">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 2 1" quat="0.632456 0.632456 0.316228 0.316228">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 2 2" quat="0.5547 0.5547 0.5547 0.27735">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 2 3" quat="0.471405 0.471405 0.707107 0.235702">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 2 4" quat="0.471405 0.471405 0.707107 0.235702">
<freejoint/>
<geom class="object5"/>
</body>
<frame pos="2 -2 2.5">
<replicate count="5" offset="0 1 0">
<frame pos="0 0 -1.5">
<replicate count="4" offset="0 0 1">
<body>
<freejoint/>
<geom class="sphere"/>
</body>
</replicate>
</frame>
</replicate>
</frame>
</worldbody>
</mujoco>
+27
View File
@@ -0,0 +1,27 @@
# Replicate
Models in this directory provide usage examples of the `<replicate>` meta-element, see
[documentation](https://mujoco.readthedocs.io/en/stable/XMLreference.html#replicate-r).
The models contain several categories:
1. Constructing static geometries:
- cylinder.xml
- bowl.xml
- helix.xml
- container.xml
2. Replicating moving bodies:
- particle.xml
- bunnies.xml
- leaves.xml
- stonehenge.xml
3. Replicas that include tendons:
- newton_cradle.xml
- tendon.xml
4. Replicas with multiple referencing elements:
- references.xml
[![Replicate showcase](https://img.youtube.com/vi/5k0_wsIRAFc/0.jpg)](https://www.youtube.com/watch?v=5k0_wsIRAFc)
+46
View File
@@ -0,0 +1,46 @@
<mujoco model="Bowl">
<statistic meansize=".05"/>
<worldbody>
<light pos=".1 0 .6"/>
<light pos="-.1 0 .6"/>
<geom type="plane" size="1 1 .01"/>
<body pos="-.1 0 .04" euler="20 10 0">
<freejoint/>
<geom type="cylinder" size="0.03 0.002" rgba=".8 0 0 1"/>
<replicate count="20" euler="0 0 18">
<frame pos="-.025 0 .03" euler="0 15 0">
<replicate count="6" euler="0 15 0">
<geom type="box" size=".0044 .009 .002" pos="0 0 -.03" rgba=".8 0 0 1"/>
</replicate>
</frame>
</replicate>
</body>
<body pos="0 0 .02" euler="20 20 0">
<freejoint/>
<geom type="cylinder" size="0.01 0.0015" rgba="0 .8 0 1"/>
<replicate count="20" euler="0 0 18">
<frame pos="-.005 0 .03" euler="0 15 0">
<replicate count="6" euler="0 15 0">
<geom type="box" size=".0044 .006 .0015" pos="0 0 -.03" rgba="0 .8 0 1"/>
</replicate>
</frame>
</replicate>
</body>
<body pos=".09 0 .04" euler="30 30 0">
<freejoint/>
<geom type="cylinder" size="0.03 0.002" rgba="0 0 .8 1"/>
<replicate count="20" euler="0 0 18">
<frame pos="-.025 0 .03" euler="0 15 0">
<replicate count="4" euler="0 15 0">
<geom type="box" size=".0044 .009 .002" pos="0 0 -.03" rgba="0 0 .8 1"/>
</replicate>
</frame>
</replicate>
</body>
</worldbody>
</mujoco>
+25
View File
@@ -0,0 +1,25 @@
<mujoco model="Bunnies">
<include file="container.xml"/>
<size memory="1G"/>
<option solver="CG" tolerance="1e-6" timestep=".01"/>
<asset>
<mesh file="bunny.obj"/>
</asset>
<worldbody>
<replicate count="5" offset=".17 0 0">
<replicate count="5" offset="0 .17 0">
<replicate count="5" offset="0 0 .17">
<body pos="-0.35 -0.35 0.65">
<freejoint/>
<geom type="mesh" mesh="bunny" rgba="0.8 0.2 0.1 1"/>
</body>
</replicate>
</replicate>
</replicate>
</worldbody>
</mujoco>
File diff suppressed because it is too large Load Diff
+31
View File
@@ -0,0 +1,31 @@
<mujoco model="Container">
<statistic extent="1.5" meansize=".05"/>
<visual>
<rgba haze="0.15 0.25 0.35 1"/>
<map shadowscale="0.5" zfar="40"/>
</visual>
<asset>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="64" height="64"/>
<texture name="texplane" type="2d" builtin="checker" rgb1=".2 .3 .4" rgb2=".1 0.15 0.2" width="512" height="512" mark="cross" markrgb=".8 .8 .8"/>
<material name="matplane" reflectance="0.3" texture="texplane" texrepeat="1 1" texuniform="true"/>
</asset>
<worldbody>
<light directional="true" diffuse=".4 .4 .4" specular="0.1 0.1 0.1" pos="0 0 5" castshadow="false"/>
<light diffuse=".6 .6 .6" specular="0.2 0.2 0.2" pos="0 0 4"/>
<geom name="ground" type="plane" size="0 0 1" pos="0 0 0" quat="1 0 0 0" material="matplane" condim="1"/>
<body mocap="true" pos="-.1 .05 0" zaxis=".5 0 1">
<geom type="capsule" size=".1 .1" group="1" condim="1"/>
</body>
</worldbody>
<worldbody>
<replicate count="4" euler="0 0 90">
<geom type="plane" size=".5 .5 .05" zaxis="1 0 0" pos="-.5 0 -.25"/>
</replicate>
</worldbody>
</mujoco>
+31
View File
@@ -0,0 +1,31 @@
<mujoco model="Cylinder">
<statistic meansize=".05"/>
<worldbody>
<light pos=".5 0 3"/>
<light pos="-.5 0 3"/>
<geom type="plane" size="2 2 .01"/>
<body pos="-.3 0 .3" euler="80 10 0">
<freejoint/>
<replicate count="30" euler="0 0 12">
<geom type="box" pos="0 -.12 0" size=".01315 .005 .1" rgba=".8 0 0 1"/>
</replicate>
</body>
<body pos="0 0 .4" euler="20 20 0">
<freejoint/>
<replicate count="10" euler="0 0 36">
<geom type="box" pos="0 -0.067 0" size=".025 .01 .1" rgba="0 .8 0 1"/>
</replicate>
</body>
<body pos=".3 0 .3" euler="30 30 0">
<freejoint/>
<replicate count="4" euler="0 0 90">
<geom type="box" pos="0.005 -0.05 0" size=".05 .005 .08" rgba="0 0 .8 1"/>
</replicate>
</body>
</worldbody>
</mujoco>
+25
View File
@@ -0,0 +1,25 @@
<mujoco model="Helix">
<statistic meansize=".05"/>
<worldbody>
<light pos=".5 0 3"/>
<light pos="-.5 0 3"/>
<geom type="plane" size="2 2 .01"/>
<body pos="0 0 .3">
<freejoint/>
<replicate count="50" offset="0 0 0.01" euler="0 0 25">
<geom type="capsule" size=".01 .05" pos="0 -.23 -.2" euler="0 84.5 0" rgba="0 0 1 1"/>
</replicate>
</body>
<body pos=".6 0 .3" euler="0 0 90">
<freejoint/>
<replicate count="50" offset="0 0 0.01" euler="0 0 15">
<geom type="box" pos=".025 -.2 -.2" size=".032 .04 .005" euler="-24 -12 2" rgba=".8 0 0 1"/>
</replicate>
</body>
</worldbody>
</mujoco>
+50
View File
@@ -0,0 +1,50 @@
<mujoco model="Leaves">
<statistic extent="1.5"/>
<visual>
<rgba haze="0.15 0.25 0.35 1"/>
<map shadowscale="0.5" zfar="40"/>
</visual>
<asset>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="64" height="64"/>
<texture name="texplane" type="2d" builtin="checker" rgb1=".2 .3 .4" rgb2=".1 0.15 0.2" width="512" height="512" mark="cross" markrgb=".8 .8 .8"/>
<material name="matplane" reflectance="0.3" texture="texplane" texrepeat="1 1" texuniform="true"/>
</asset>
<size memory="1G"/>
<option solver="CG" tolerance="1e-6" timestep=".003" density="1.225" viscosity="1.8e-5" integrator="implicitfast"/>
<visual>
<global realtime="0.2"/>
</visual>
<default>
<default class="leaf">
<geom type="ellipsoid" size=".025 .01 0.001" mass="1e-4" fluidshape="ellipsoid" rgba="0 .4 0 1"/>
</default>
</default>
<worldbody>
<camera name="Cinematic" pos="0 -1.6 4.5" xyaxes="1 0 0 0 0.6 0.8" mode="trackcom"/>
<light diffuse="1 1 1" attenuation=".4 .2 0" pos="-1 -1 6" mode="trackcom" dir="1 1 -3"/>
<light diffuse="1 1 1" attenuation=".4 .2 0" pos="-.98 -1.02 6" mode="trackcom" dir="1 1 -3"/>
<geom name="ground" type="plane" size="5 5 .01" pos="0 0 0" quat="1 0 0 0" material="matplane" condim="1"/>
<replicate count="10" offset="0 0 0.07" euler="0 0 30">
<replicate count="6" euler="-3 0 0">
<frame pos="-.3 -.3 3">
<replicate count="5" offset=".05 0 0" euler="50 0 0">
<body euler="50 50 0">
<freejoint/>
<geom class="leaf" pos=".025 0 0" euler="10 0 0"/>
<geom class="leaf" pos="-.025 0 0" euler="-14 0 0" mass="1.2e-4"/>
</body>
</replicate>
</frame>
</replicate>
</replicate>
</worldbody>
</mujoco>
+69
View File
@@ -0,0 +1,69 @@
<mujoco model="Newton's Cradle">
<option timestep="1e-4"/>
<visual>
<global realtime="0.2"/>
</visual>
<asset>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="64" height="64"/>
<texture name="texplane" type="2d" builtin="checker" rgb1=".2 .3 .4" rgb2=".1 0.15 0.2" width="512" height="512" mark="cross" markrgb=".8 .8 .8"/>
<material name="matplane" reflectance="0.5" texture="texplane" texrepeat="3 3" texuniform="true"/>
</asset>
<default>
<geom friction=".5" solref="-1e8 -0" solimp="0 .01 1e-3"/>
<tendon limited="true" range="0 0.146" width="0.001" solreflimit="1e-3 1" rgba="0 0 .7 1"/>
<default class="steel ball">
<geom size=".02" rgba=".7 .7 0 1" density="8000" friction=".4" solref="-1e8 -0" solimp="0 .01 1e-3"/>
</default>
</default>
<worldbody>
<light pos=".5 0 1" dir="-1 0 -2"/>
<light pos="-.5 0 1" dir="1 0 -2"/>
<geom type="plane" size="4 4 .1" material="matplane"/>
<replicate count="5" offset="-.04 0 0">
<site name="a" pos="0 -.04 .2"/>
<site name="c" pos="0 .04 .2"/>
<body name="equilibrium" pos="0 0 .04">
<freejoint/>
<geom class="steel ball"/>
<site name="b" pos="0 -.008 .018"/>
<site name="d" pos="0 .008 .018"/>
</body>
</replicate>
<replicate count="2" offset=".04 0 0">
<site name="e" pos=".04 -.04 .2"/>
<site name="g" pos=".04 .04 .2"/>
<body name="perturbed" pos=".12 0 .061" euler="0 -34 0">
<freejoint/>
<geom class="steel ball"/>
<site name="f" pos="0 -.008 .018"/>
<site name="h" pos="0 .008 .018"/>
</body>
</replicate>
</worldbody>
<tendon>
<spatial>
<site site="a"/>
<site site="b"/>
</spatial>
<spatial>
<site site="c"/>
<site site="d"/>
</spatial>
<spatial>
<site site="e"/>
<site site="f"/>
</spatial>
<spatial>
<site site="g"/>
<site site="h"/>
</spatial>
</tendon>
</mujoco>
+23
View File
@@ -0,0 +1,23 @@
<mujoco model="Particle">
<include file="container.xml"/>
<size memory="1G"/>
<option solver="CG" tolerance="1e-6" timestep=".01"/>
<worldbody>
<replicate count="10" offset=".07 0 0">
<replicate count="10" offset="0 .07 0">
<replicate count="10" offset="0 0 .07">
<body pos="-0.35 -0.35 0.65">
<joint type="slide" axis="1 0 0" group="3"/>
<joint type="slide" axis="0 1 0" group="3"/>
<joint type="slide" axis="0 0 1" group="3"/>
<geom size="0.025" condim="1" priority="1" rgba="0.8 0.2 0.1 1"/>
</body>
</replicate>
</replicate>
</replicate>
</worldbody>
</mujoco>
+40
View File
@@ -0,0 +1,40 @@
<mujoco model="Stonehenge">
<statistic meansize="1"/>
<worldbody>
<light directional="true" pos="-50 -20 10" dir="5 2 -1" diffuse="1 1 1"/>
<geom type="plane" size="30 30 .1" rgba="0 1 0 1"/>
<geom type="plane" pos="38 0 5" size="10 30 .1" euler="0 -30 0" rgba="0 .8 0 1"/>
<body name="wrecking ball" pos="45 0 16">
<freejoint/>
<geom size="5.5"/>
</body>
<replicate count="30" euler="0 0 12">
<body pos="0 -15 2">
<freejoint/>
<geom type="box" size="1 .5 2"/>
</body>
<body pos="1.5 -15 4.4" euler="0 0 6">
<freejoint/>
<geom type="box" size="1.5 .5 .4"/>
</body>
</replicate>
<replicate count="5" euler="0 0 45">
<body pos="-1.2 -10 3">
<freejoint/>
<geom type="box" size=".9 .5 3"/>
</body>
<body pos="1.2 -10 3">
<freejoint/>
<geom type="box" size=".9 .5 3"/>
</body>
<body pos="0 -10 6.4">
<freejoint/>
<geom type="box" size="2.4 .5 .4"/>
</body>
</replicate>
</worldbody>
</mujoco>
+47
View File
@@ -0,0 +1,47 @@
<mujoco model="Tendon">
<option solver="PGS"/>
<default>
<site size=".02" rgba="0 .8 0 1"/>
<geom condim="1"/>
</default>
<worldbody>
<light pos="1 0 4" dir="-1 0 -4"/>
<light pos="-1 0 4" dir="1 0 -4"/>
<geom type="plane" size="2 2 .01"/>
<site name="ceiling" pos="0.01 0.02 2.4"/>
<body pos="0 0 1.5">
<freejoint/>
<site name="hook" pos="0 0 .3"/>
<site name="bulb"/>
<geom name="bulb" size=".3" rgba="0 0 .8 1" density="100" contype="0" conaffinity="0"/>
</body>
<replicate count="18" euler="0 0 20">
<body pos=".2 0 1.5">
<freejoint/>
<site name="a"/>
<geom rgba=".8 0 .4 1" size=".03"/>
</body>
<body pos="1.2 0 1.5">
<freejoint/>
<site name="b"/>
<geom rgba=".8 .4 0 1" size=".1"/>
</body>
</replicate>
</worldbody>
<tendon>
<spatial springlength="0 0.5" stiffness="10000">
<site site="ceiling"/>
<site site="hook"/>
</spatial>
<spatial range="0 0.5">
<site site="a"/>
<geom geom="bulb" sidesite="bulb"/>
<site site="b"/>
</spatial>
</tendon>
</mujoco>
+2 -1
View File
@@ -111,10 +111,11 @@ const char* mjs_getError(mjSpec* s) {
// Detach body from mjSpec, return 0 if success.
int mjs_detachBody(mjSpec* s, const mjsBody* b) {
int mjs_detachBody(mjSpec* s, mjsBody* b) {
mjCModel* model = static_cast<mjCModel*>(s->element);
mjCBody* body = static_cast<mjCBody*>(b->element);
*model -= *body;
mjs_deleteBody(b);
return 0;
}
+2 -2
View File
@@ -778,8 +778,8 @@ MJAPI int mjs_attachBody(mjsFrame* parent, const mjsBody* child,
MJAPI int mjs_attachFrame(mjsBody* parent, const mjsFrame* child,
const char* prefix, const char* suffix);
// Detach body from mjSpec, remove all references, return 0 on success.
MJAPI int mjs_detachBody(mjSpec* s, const mjsBody* b);
// Detach body from mjSpec, remove all references and delete the body, return 0 on success.
MJAPI int mjs_detachBody(mjSpec* s, mjsBody* b);
//---------------------------------- Add tree elements ---------------------------------------------
+21 -13
View File
@@ -230,21 +230,27 @@ mjCModel& mjCModel::operator+=(const mjCModel& other) {
// create global lists
MakeLists(bodies[0]);
CreateObjectLists();
ProcessLists();
ProcessLists(/*checkrepeat=*/false);
// copy all elements not in the tree
std::map<mjCDef*, int> def_map;
int ndefaults = (int)other.defaults.size();
for (int i = 0; i < ndefaults; i++) {
defaults.push_back(new mjCDef(*other.defaults[i]));
if (this != &other) {
defaults.push_back(new mjCDef(*other.defaults[i]));
}
def_map[other.defaults[i]] = i;
}
if (this != &other) {
// do not copy assets for self-attach
CopyList(meshes, other.meshes, def_map, defaults);
CopyList(skins, other.skins, def_map, defaults);
CopyList(hfields, other.hfields, def_map, defaults);
CopyList(textures, other.textures, def_map, defaults);
CopyList(materials, other.materials, def_map, defaults);
CopyList(keys, other.keys, def_map, defaults);
}
CopyList(flexes, other.flexes, def_map, defaults);
CopyList(meshes, other.meshes, def_map, defaults);
CopyList(skins, other.skins, def_map, defaults);
CopyList(hfields, other.hfields, def_map, defaults);
CopyList(textures, other.textures, def_map, defaults);
CopyList(materials, other.materials, def_map, defaults);
CopyList(pairs, other.pairs, def_map, defaults);
CopyList(excludes, other.excludes, def_map, defaults);
CopyList(tendons, other.tendons, def_map, defaults);
@@ -254,7 +260,6 @@ mjCModel& mjCModel::operator+=(const mjCModel& other) {
CopyList(numerics, other.numerics, def_map, defaults);
CopyList(texts, other.texts, def_map, defaults);
CopyList(tuples, other.tuples, def_map, defaults);
CopyList(keys, other.keys, def_map, defaults);
// plugins are global
plugins = other.plugins;
@@ -309,6 +314,7 @@ void mjCModel::RemoveFromList(std::vector<T*>& list, const mjCModel& other) {
element->id -= removed;
try {
// check if the element contains an error
element->NameSpace(&other);
element->CopyFromSpec();
element->ResolveReferences(&other);
} catch (mjCError err) {
@@ -316,6 +322,7 @@ void mjCModel::RemoveFromList(std::vector<T*>& list, const mjCModel& other) {
}
try {
// check if the element references something that was removed
element->NameSpace(this);
element->ResolveReferences(this);
} catch (mjCError err) {
delete element;
@@ -333,7 +340,7 @@ mjCModel& mjCModel::operator-=(const mjCBody& subtree) {
mjCModel oldmodel(*this);
oldmodel.MakeLists(oldmodel.bodies[0]);
oldmodel.CreateObjectLists();
oldmodel.ProcessLists();
oldmodel.ProcessLists(/*checkrepeat=*/false);
// remove body from tree
*bodies[0] -= subtree;
@@ -341,7 +348,7 @@ mjCModel& mjCModel::operator-=(const mjCBody& subtree) {
// create global lists
MakeLists(bodies[0]);
CreateObjectLists();
ProcessLists();
ProcessLists(/*checkrepeat=*/false);
// check if we have to remove anything else
RemoveFromList(pairs, oldmodel);
@@ -3044,15 +3051,16 @@ static void processlist(mjListKeyMap& ids, vector<T*>& list,
// set object ids, check for repeated names
void mjCModel::ProcessLists() {
void mjCModel::ProcessLists(bool checkrepeat) {
for (int i = 0; i < mjNOBJECT; i++) {
if (i != mjOBJ_XBODY && object_lists[i]) {
processlist(ids, *object_lists[i], (mjtObj) i);
ids[i].clear();
processlist(ids, *object_lists[i], (mjtObj) i, checkrepeat);
}
}
// check repeated names in meta elements
processlist(ids, frames, mjOBJ_FRAME);
processlist(ids, frames, mjOBJ_FRAME, checkrepeat);
}
+1 -1
View File
@@ -307,7 +307,7 @@ class mjCModel : public mjCModel_, private mjSpec {
void CreateObjectLists();
// populate objects ids
void ProcessLists();
void ProcessLists(bool checkrepeat = true);
mjListKeyMap ids; // map from object names to ids
mjCError errInfo; // last error info
+175 -60
View File
@@ -728,6 +728,9 @@ mjCBody& mjCBody::operator=(const mjCBody& other) {
lights.clear();
id = other.id;
// copy defaults
def = other.def;
// add elements to lists
*this += other;
}
@@ -797,18 +800,17 @@ mjCBody& mjCBody::operator+=(const mjCFrame& other) {
CopyList(cameras, subtree->cameras, fmap, &other);
CopyList(lights, subtree->lights, fmap, &other);
for (int i=0; i<subtree->bodies.size(); i++) {
int nbodies = (int)subtree->bodies.size();
for (int i=0; i<nbodies; i++) {
if (!other.IsAncestor(subtree->bodies[i]->frame)) {
continue;
}
bodies.push_back(new mjCBody(*subtree->bodies[i], model)); // triggers recursive call
bodies.back()->frame =
subtree->bodies[i]->frame ? frames[fmap[subtree->bodies[i]->frame]] : nullptr;
bodies.back()->NameSpace_(other.model, /*propagate=*/ false);
}
// name space
this->NameSpace(other.model);
// attach referencing elements
*model += *other.model;
@@ -832,14 +834,14 @@ void mjCBody::CopyList(std::vector<T*>& dst, const std::vector<T*>& src,
dst.push_back(new T(*src[i]));
dst.back()->body = this;
dst.back()->model = model;
dst.back()->id = -1;
dst.back()->def = src[i]->def;
// assign dst frame to src frame
dst.back()->frame = src[i]->frame ? frames[fmap[src[i]->frame]] : nullptr;
// set namespace from pframe if given
if (pframe) {
dst.back()->NameSpace(pframe->model);
}
// set namespace
dst.back()->NameSpace(src[i]->model);
}
}
@@ -906,8 +908,25 @@ mjCBody::~mjCBody() {
// apply prefix and suffix, propagate to children
void mjCBody::NameSpace(const mjCModel* m) {
NameSpace_(m, true);
}
// apply prefix and suffix, propagate to all descendents or only to child bodies
void mjCBody::NameSpace_(const mjCModel* m, bool propagate) {
if (!name.empty()) {
name = prefix + name + suffix;
name = m->prefix + name + m->suffix;
}
for (auto& body : bodies) {
body->prefix = m->prefix;
body->suffix = m->suffix;
body->NameSpace_(m, propagate);
}
if (!propagate) {
return;
}
for (auto& joint : joints) {
@@ -929,12 +948,6 @@ void mjCBody::NameSpace(const mjCModel* m) {
for (auto& light : lights) {
light->NameSpace(m);
}
for (auto& body : bodies) {
body->prefix = m->prefix;
body->suffix = m->suffix;
body->NameSpace(m);
}
}
@@ -1836,6 +1849,23 @@ void mjCGeom::CopyFromSpec() {
void mjCGeom::NameSpace(const mjCModel* m) {
if (!name.empty()) {
name = m->prefix + name + m->suffix;
}
if (!spec_material_.empty() && model != m) {
spec_material_ = m->prefix + spec_material_ + m->suffix;
}
if (!spec_hfieldname_.empty() && model != m) {
spec_hfieldname_ = m->prefix + spec_hfieldname_ + m->suffix;
}
if (!spec_meshname_.empty() && model != m) {
spec_meshname_ = m->prefix + spec_meshname_ + m->suffix;
}
}
// compute geom volume
double mjCGeom::GetVolume(void) {
double height;
@@ -2196,7 +2226,7 @@ void mjCGeom::Compile(void) {
}
// check hfield
if ((type==mjGEOM_HFIELD && !hfield) || (type!=mjGEOM_HFIELD && hfield)) {
if ((type==mjGEOM_HFIELD && !hfield) || (type != mjGEOM_HFIELD && hfield)) {
throw mjCError(this, "hfield geom '%s' (id = %d) must have valid hfieldid", name.c_str(), id);
}
@@ -2272,7 +2302,7 @@ void mjCGeom::Compile(void) {
// fit geom if type is not mjGEOM_MESH
double meshpos[3];
if (type!=mjGEOM_MESH && type!=mjGEOM_SDF) {
if (type != mjGEOM_MESH && type != mjGEOM_SDF) {
mesh->FitGeom(this, meshpos);
// remove reference to mesh
@@ -2578,7 +2608,9 @@ void mjCCamera::NameSpace(const mjCModel* m) {
if (!name.empty()) {
name = m->prefix + name + m->suffix;
}
spec_targetbody_ = m->prefix + spec_targetbody_ + m->suffix;
if (!spec_targetbody_.empty()) {
spec_targetbody_ = m->prefix + spec_targetbody_ + m->suffix;
}
}
@@ -2726,7 +2758,9 @@ void mjCLight::NameSpace(const mjCModel* m) {
if (!name.empty()) {
name = m->prefix + name + m->suffix;
}
spec_targetbody_ = m->prefix + spec_targetbody_ + m->suffix;
if (!spec_targetbody_.empty()) {
spec_targetbody_ = m->prefix + spec_targetbody_ + m->suffix;
}
}
@@ -3515,7 +3549,7 @@ void mjCTexture::LoadCubeSingle(string filename, const mjVFS* vfs) {
LoadFlip(filename, vfs, image, w, h);
// check gridsize for compatibility
if (w/gridsize[1]!=h/gridsize[0] || (w%gridsize[1]) || (h%gridsize[0])) {
if (w/gridsize[1] != h/gridsize[0] || (w%gridsize[1]) || (h%gridsize[0])) {
throw mjCError(this,
"PNG size must be integer multiple of gridsize in texture '%s' (id %d)",
(const char*)file_.c_str(), id);
@@ -3621,7 +3655,7 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) {
LoadFlip(filename, vfs, image, w, h);
// PNG must be square
if (w!=h) {
if (w != h) {
throw mjCError(this,
"Non-square PNG file '%s' in cube or skybox id %d",
(const char*)cubefiles_[i].c_str(), id);
@@ -3638,7 +3672,7 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) {
}
// otherwise check size
else if (width!=w) {
else if (width != w) {
throw mjCError(this,
"PNG file '%s' has incompatible size in texture id %d",
(const char*)cubefiles_[i].c_str(), id);
@@ -3674,14 +3708,14 @@ void mjCTexture::Compile(const mjVFS* vfs) {
CopyFromSpec();
// builtin
if (builtin!=mjBUILTIN_NONE) {
if (builtin != mjBUILTIN_NONE) {
// check size
if (width<1 || height<1) {
throw mjCError(this, "Invalid width or height of builtin texture");
}
// adjust height of cube texture
if (type!=mjTEXTURE_2D) {
if (type != mjTEXTURE_2D) {
height = 6*width;
}
@@ -3815,6 +3849,17 @@ void mjCMaterial::CopyFromSpec() {
void mjCMaterial::NameSpace(const mjCModel* m) {
if (!name.empty()) {
name = m->prefix + name + m->suffix;
}
if (!spec_texture_.empty() && model != m) {
spec_texture_ = m->prefix + spec_texture_ + m->suffix;
}
}
// compiler
void mjCMaterial::Compile(void) {
CopyFromSpec();
@@ -3891,8 +3936,8 @@ void mjCPair::NameSpace(const mjCModel* m) {
if (!name.empty()) {
name = m->prefix + name + m->suffix;
}
spec_geomname1_ = m->prefix + spec_geomname1_ + m->suffix;
spec_geomname2_ = m->prefix + spec_geomname2_ + m->suffix;
prefix = m->prefix;
suffix = m->suffix;
}
@@ -3908,18 +3953,32 @@ void mjCPair::CopyFromSpec() {
void mjCPair::ResolveReferences(const mjCModel* m) {
// find geom 1
geomname1_ = prefix + geomname1_ + suffix;
geomname2_ = prefix + geomname2_ + suffix;
geom1 = (mjCGeom*)m->FindObject(mjOBJ_GEOM, geomname1_);
geom2 = (mjCGeom*)m->FindObject(mjOBJ_GEOM, geomname2_);
if (!geom1 && geom2) {
geomname1_ = spec_geomname1_;
geom1 = (mjCGeom*)m->FindObject(mjOBJ_GEOM, geomname1_);
}
if (geom1 && !geom2) {
geomname2_ = spec_geomname2_;
geom2 = (mjCGeom*)m->FindObject(mjOBJ_GEOM, geomname2_);
}
if (!geom1) {
throw mjCError(this, "geom '%s' not found in collision %d", geomname1_.c_str(), id);
}
// find geom 2
geom2 = (mjCGeom*)m->FindObject(mjOBJ_GEOM, geomname2_);
if (!geom2) {
throw mjCError(this, "geom '%s' not found in collision %d", geomname2_.c_str(), id);
}
spec_geomname1_ = geomname1_;
spec_geomname2_ = geomname2_;
prefix.clear();
suffix.clear();
// swap if body1 > body2
if (geom1->body->id > geom2->body->id) {
string nametmp = geomname1_;
@@ -3964,7 +4023,7 @@ void mjCPair::Compile(void) {
}
// set undefined condim, friction, solref, solimp: different priority
if (geom1->priority!=geom2->priority) {
if (geom1->priority != geom2->priority) {
mjCGeom* pgh = (geom1->priority>geom2->priority ? geom1 : geom2);
// condim
@@ -4101,8 +4160,8 @@ void mjCBodyPair::NameSpace(const mjCModel* m) {
if (!name.empty()) {
name = m->prefix + name + m->suffix;
}
spec_bodyname1_ = m->prefix + spec_bodyname1_ + m->suffix;
spec_bodyname2_ = m->prefix + spec_bodyname2_ + m->suffix;
prefix = m->prefix;
suffix = m->suffix;
}
@@ -4118,18 +4177,32 @@ void mjCBodyPair::CopyFromSpec() {
void mjCBodyPair::ResolveReferences(const mjCModel* m) {
// find body 1
bodyname1_ = prefix + bodyname1_ + suffix;
bodyname2_ = prefix + bodyname2_ + suffix;
mjCBody* pb1 = (mjCBody*)m->FindObject(mjOBJ_BODY, bodyname1_);
mjCBody* pb2 = (mjCBody*)m->FindObject(mjOBJ_BODY, bodyname2_);
if (!pb1 && pb2) {
bodyname1_ = spec_bodyname1_;
pb1 = (mjCBody*)m->FindObject(mjOBJ_BODY, bodyname1_);
}
if (pb1 && !pb2) {
bodyname2_ = spec_bodyname2_;
pb2 = (mjCBody*)m->FindObject(mjOBJ_BODY, bodyname2_);
}
if (!pb1) {
throw mjCError(this, "body '%s' not found in bodypair %d", bodyname1_.c_str(), id);
}
// find body 2
mjCBody* pb2 = (mjCBody*)m->FindObject(mjOBJ_BODY, bodyname2_);
if (!pb2) {
throw mjCError(this, "body '%s' not found in bodypair %d", bodyname2_.c_str(), id);
}
spec_bodyname1_ = bodyname1_;
spec_bodyname2_ = bodyname2_;
prefix.clear();
suffix.clear();
// swap if body1 > body2
if (pb1->id > pb2->id) {
string nametmp = bodyname1_;
@@ -4391,9 +4464,8 @@ void mjCTendon::NameSpace(const mjCModel* m) {
if (!name.empty()) {
name = m->prefix + name + m->suffix;
}
for (int i=0; i<path.size(); i++) {
path[i]->NameSpace(m);
}
prefix = m->prefix;
suffix = m->suffix;
}
@@ -4517,9 +4589,28 @@ mjCWrap* mjCTendon::GetWrap(int id) {
void mjCTendon::ResolveReferences(const mjCModel* m) {
int nfailure = 0;
for (int i=0; i<path.size(); i++) {
path[i]->ResolveReferences(m);
std::string pname = path[i]->name;
std::string psidesite = path[i]->sidesite;
try {
// look for wrapped element with namespace
path[i]->name = prefix + pname + suffix;
path[i]->sidesite = prefix + psidesite + suffix;
path[i]->ResolveReferences(m);
} catch(mjCError) {
// remove namespace from wrap names
path[i]->name = pname;
path[i]->sidesite = psidesite;
path[i]->ResolveReferences(m);
nfailure++;
}
}
if (nfailure==path.size()) {
throw mjCError(this, "tendon '%s' (id = %d): no attached reference found", name.c_str(), id);
}
prefix.clear();
suffix.clear();
}
@@ -4543,7 +4634,7 @@ void mjCTendon::Compile(void) {
}
// determine type
bool spatial = (path[0]->type!=mjWRAP_JOINT);
bool spatial = (path[0]->type != mjWRAP_JOINT);
// require at least two objects in spatial path
if (spatial && sz<2) {
@@ -4564,7 +4655,7 @@ void mjCTendon::Compile(void) {
// fixed
if (!spatial) {
// make sure all objects are joints
if (path[i]->type!=mjWRAP_JOINT) {
if (path[i]->type != mjWRAP_JOINT) {
throw mjCError(this, "tendon '%s' (id = %d): spatial object found in fixed path at pos %d",
name.c_str(), id, i);
}
@@ -4607,7 +4698,7 @@ void mjCTendon::Compile(void) {
case mjWRAP_SPHERE:
case mjWRAP_CYLINDER:
// geom must be bracketed by sites
if (i==0 || i==sz-1 || path[i-1]->type!=mjWRAP_SITE || path[i+1]->type!=mjWRAP_SITE) {
if (i==0 || i==sz-1 || path[i-1]->type != mjWRAP_SITE || path[i+1]->type != mjWRAP_SITE) {
throw mjCError(this,
"tendon '%s' (id = %d): geom at pos %d not bracketed by sites",
name.c_str(), id, i);
@@ -5187,8 +5278,8 @@ void mjCSensor::NameSpace(const mjCModel* m) {
if (!name.empty()) {
name = m->prefix + name + m->suffix;
}
spec_objname_ = m->prefix + spec_objname_ + m->suffix;
spec_refname_ = m->prefix + spec_refname_ + m->suffix;
prefix = m->prefix;
suffix = m->suffix;
}
@@ -5210,15 +5301,35 @@ void mjCSensor::CopyFromSpec() {
void mjCSensor::ResolveReferences(const mjCModel* m) {
objname_ = prefix + objname_ + suffix;
refname_ = prefix + refname_ + suffix;
// get references using the namespace
if (objtype != mjOBJ_UNKNOWN) {
obj = m->FindObject(objtype, objname_);
}
if (reftype != mjOBJ_UNKNOWN) {
ref = m->FindObject(reftype, refname_);
}
// if failure and both were requested, use namespace only on one
if (objtype != mjOBJ_UNKNOWN && reftype != mjOBJ_UNKNOWN && !obj && ref) {
objname_ = spec_objname_;
obj = m->FindObject(objtype, objname_);
}
if (objtype != mjOBJ_UNKNOWN && reftype != mjOBJ_UNKNOWN && obj && !ref) {
refname_ = spec_refname_;
ref = m->FindObject(reftype, refname_);
}
// get objid from objtype and objname
if (objtype!=mjOBJ_UNKNOWN) {
if (objtype != mjOBJ_UNKNOWN) {
// check for missing object name
if (objname_.empty()) {
throw mjCError(this, "missing name of sensorized object in sensor");
}
// find name
obj = m->FindObject(objtype, objname_);
if (!obj) {
throw mjCError(this, "unrecognized name '%s' of sensorized object", objname_.c_str());
}
@@ -5234,21 +5345,20 @@ void mjCSensor::ResolveReferences(const mjCModel* m) {
}
// get refid from reftype and refname
if (reftype!=mjOBJ_UNKNOWN) {
if (reftype != mjOBJ_UNKNOWN) {
// check for missing object name
if (refname_.empty()) {
throw mjCError(this, "missing name of reference frame object in sensor");
}
// find name
ref = m->FindObject(reftype, refname_);
if (!ref) {
throw mjCError(this, "unrecognized name '%s' of reference frame object", refname_.c_str());
}
// must be attached to object with spatial frame
if (reftype!=mjOBJ_BODY && reftype!=mjOBJ_XBODY &&
reftype!=mjOBJ_GEOM && reftype!=mjOBJ_SITE && reftype!=mjOBJ_CAMERA) {
if (reftype != mjOBJ_BODY && reftype != mjOBJ_XBODY &&
reftype != mjOBJ_GEOM && reftype != mjOBJ_SITE && reftype != mjOBJ_CAMERA) {
throw mjCError(this,
"reference frame object must be (x)body, geom, site or camera in sensor");
}
@@ -5256,6 +5366,11 @@ void mjCSensor::ResolveReferences(const mjCModel* m) {
// get sensorized object id
refid = ref->id;
}
spec_objname_ = objname_;
spec_refname_ = refname_;
prefix.clear();
suffix.clear();
}
@@ -5537,7 +5652,7 @@ void mjCSensor::Compile(void) {
}
// make sure dim is consistent with datatype
if (datatype==mjDATATYPE_AXIS && dim!=3) {
if (datatype==mjDATATYPE_AXIS && dim != 3) {
throw mjCError(this,
"datatype AXIS requires dim=3 in sensor");
}
@@ -5843,7 +5958,7 @@ void mjCTuple::ResolveReferences(const mjCModel* m) {
}
// check for size conflict
if (objtype_.size()!=objname_.size() || objtype_.size()!=objprm_.size()) {
if (objtype_.size() != objname_.size() || objtype_.size() != objprm_.size()) {
throw mjCError(this,
"tuple '%s' (id = %d) has object arrays with different sizes", name.c_str(), id);
}
@@ -5984,7 +6099,7 @@ void mjCKey::Compile(const mjModel* m) {
for (int i=0; i<m->nq; i++) {
qpos_[i] = (double)m->qpos0[i];
}
} else if (qpos_.size()!=m->nq) {
} else if (qpos_.size() != m->nq) {
throw mjCError(this, "key %d: invalid qpos size, expected length %d", nullptr, id, m->nq);
}
@@ -5994,7 +6109,7 @@ void mjCKey::Compile(const mjModel* m) {
for (int i=0; i<m->nv; i++) {
qvel_[i] = 0;
}
} else if (qvel_.size()!=m->nv) {
} else if (qvel_.size() != m->nv) {
throw mjCError(this, "key %d: invalid qvel size, expected length %d", nullptr, id, m->nv);
}
@@ -6004,7 +6119,7 @@ void mjCKey::Compile(const mjModel* m) {
for (int i=0; i<m->na; i++) {
act_[i] = 0;
}
} else if (act_.size()!=m->na) {
} else if (act_.size() != m->na) {
throw mjCError(this, "key %d: invalid act size, expected length %d", nullptr, id, m->na);
}
@@ -6021,7 +6136,7 @@ void mjCKey::Compile(const mjModel* m) {
}
}
}
} else if (mpos_.size()!=3*m->nmocap) {
} else if (mpos_.size() != 3*m->nmocap) {
throw mjCError(this, "key %d: invalid mpos size, expected length %d", nullptr, id, 3*m->nmocap);
}
@@ -6039,7 +6154,7 @@ void mjCKey::Compile(const mjModel* m) {
}
}
}
} else if (mquat_.size()!=4*m->nmocap) {
} else if (mquat_.size() != 4*m->nmocap) {
throw mjCError(this, "key %d: invalid mquat size, expected length %d", nullptr, id, 4*m->nmocap);
}
@@ -6049,7 +6164,7 @@ void mjCKey::Compile(const mjModel* m) {
for (int i=0; i<m->nu; i++) {
ctrl_[i] = 0;
}
} else if (ctrl_.size()!=m->nu) {
} else if (ctrl_.size() != m->nu) {
throw mjCError(this, "key %d: invalid ctrl size, expected length %d", nullptr, id, m->nu);
}
}
+3
View File
@@ -322,6 +322,7 @@ class mjCBody : public mjCBody_, private mjsBody {
void CopyFromSpec(); // copy spec into attributes
void PointToLocal(void);
void NameSpace_(const mjCModel* m, bool propagate = true);
// copy src list of elements into dst; set body, model and frame
template <typename T>
@@ -496,6 +497,7 @@ class mjCGeom : public mjCGeom_, private mjsGeom {
void ComputeAABB(void); // compute axis-aligned bounding box
void CopyFromSpec(void);
void PointToLocal(void);
void NameSpace(const mjCModel* m);
// inherited
using mjCBase::classname;
@@ -1080,6 +1082,7 @@ class mjCMaterial : public mjCMaterial_, private mjsMaterial {
void CopyFromSpec();
void PointToLocal();
void NameSpace(const mjCModel* m);
std::string get_texture() { return texture_; }
void del_texture() { texture_.clear(); }
+81 -5
View File
@@ -74,6 +74,16 @@ void ReadPluginConfigs(tinyxml2::XMLElement* elem, mjsPlugin* p) {
mjs_setPluginAttributes(p, &config_attribs);
}
}
static void UpdateString(std::string& psuffix, int count, int i) {
int ndigits = std::to_string(count).length();
std::string i_string = std::to_string(i);
std::string prefix = "";
while (ndigits-- > i_string.length()) {
prefix += '0';
}
psuffix += prefix + i_string;
}
} // namespace
@@ -870,11 +880,6 @@ void mjXReader::Parse(XMLElement* root) {
Asset(section);
}
for (XMLElement* section = FirstChildElement(root, "worldbody"); section;
section = NextSiblingElement(section, "worldbody")) {
Body(section, mjs_findBody(model, "world"), nullptr);
}
for (XMLElement* section = FirstChildElement(root, "contact"); section;
section = NextSiblingElement(section, "contact")) {
Contact(section);
@@ -909,6 +914,11 @@ void mjXReader::Parse(XMLElement* root) {
section = NextSiblingElement(section, "keyframe")) {
Keyframe(section);
}
for (XMLElement* section = FirstChildElement(root, "worldbody"); section;
section = NextSiblingElement(section, "worldbody")) {
Body(section, mjs_findBody(model, "world"), nullptr);
}
}
@@ -3371,6 +3381,72 @@ void mjXReader::Body(XMLElement* section, mjsBody* pbody, mjsFrame* frame) {
Body(elem, pbody, pframe);
}
// replicate sub-element
else if (name=="replicate") {
int count;
double offset[3] = {0, 0, 0};
double euler[3] = {0, 0, 0};
std::string separator = "";
ReadAttr(elem, "count", 1, &count, text, true);
ReadAttr(elem, "offset", 3, offset, text);
ReadAttr(elem, "euler", 3, euler, text);
ReadAttrTxt(elem, "sep", separator);
// store rotation difference
mjsOrientation alt;
mjs_defaultOrientation(alt);
alt.type = mjORIENTATION_EULER;
mjuu_copyvec(alt.euler, euler, 3);
double rotation[4] = {1, 0, 0, 0};
mjs_resolveOrientation(rotation, model->degree, model->euler, &alt);
// read childdef
mjsDefault* childdef = 0;
if (ReadAttrTxt(elem, "childclass", text)) {
childdef = mjs_findDefault(model, text.c_str());
mjs_findDefault(model, text.c_str());
if (!childdef) {
throw mjXError(elem, "unknown default childclass");
}
}
// create subtree
mjsBody* subtree = mjs_addBody(pbody, childdef);
double pos[3] = {0, 0, 0};
double quat[4] = {1, 0, 0, 0};
for (int i = 0; i < count; i++) {
// create parent frame
mjsFrame* pframe = mjs_addFrame(subtree, frame);
mjs_setString(pframe->info, ("line = " + std::to_string(elem->GetLineNum())).c_str());
mjs_setDefault(pframe->element, childdef ? childdef : def);
// accumulate rotation
mjuu_setvec(pframe->pos, pos[0], pos[1], pos[2]);
mjuu_frameaccum(pos, quat, offset, rotation);
// overwrite orientation to increase precision
alt.euler[0] = i*euler[0];
alt.euler[1] = i*euler[1];
alt.euler[2] = i*euler[2];
mjs_resolveOrientation(quat, model->degree, model->euler, &alt);
mjuu_setvec(pframe->quat, quat[0], quat[1], quat[2], quat[3]);
// process suffix
std::string suffix = separator;
UpdateString(suffix, count, i);
// process subtree
Body(elem, subtree, pframe);
// attach to parent
mjs_attachFrame(pbody, pframe, /*prefix=*/"", suffix.c_str());
}
// delete subtree
mjs_detachBody(model, subtree);
}
// body sub-element
else if (name=="body") {
// read childdef
+31 -38
View File
@@ -811,7 +811,7 @@ string mjXWriter::Write(char *error, size_t error_sz) {
Extension(root);
Custom(root);
Asset(root);
Body(InsertEnd(root, "worldbody"), model->GetWorld(), /*frame=*/nullptr);
Body(InsertEnd(root, "worldbody"), model->GetWorld());
Contact(root);
Deformable(root);
Equality(root);
@@ -1456,20 +1456,31 @@ void mjXWriter::Asset(XMLElement* root) {
XMLElement* mjXWriter::OneFrame(XMLElement* elem, mjCFrame* frame) {
if (!frame) {
return elem;
}
if (frame->name.empty() && frame->classname.empty()) {
return elem;
}
XMLElement* frame_elem = InsertEnd(elem, "frame");
WriteAttrTxt(frame_elem, "name", frame->name);
WriteAttrTxt(frame_elem, "childclass", frame->classname);
return frame_elem;
}
// recursive body and frame writer
void mjXWriter::Body(XMLElement* elem, mjCBody* body, mjCFrame* frame) {
void mjXWriter::Body(XMLElement* elem, mjCBody* body) {
double unitq[4] = {1, 0, 0, 0};
if (!body) {
throw mjXError(0, "missing body in XML write"); // SHOULD NOT OCCUR
}
// write frame if defined
if (frame) {
WriteAttrTxt(elem, "name", frame->name);
WriteAttrTxt(elem, "childclass", frame->classname);
}
// write body attributes and inertial
else if (body!=model->GetWorld()) {
WriteAttrTxt(elem, "name", body->name);
@@ -1504,32 +1515,32 @@ void mjXWriter::Body(XMLElement* elem, mjCBody* body, mjCFrame* frame) {
// write joints
for (int i=0; i<body->joints.size(); i++) {
if (body->joints[i]->frame != frame) continue;
OneJoint(InsertEnd(elem, "joint"), body->joints[i], body->joints[i]->def);
XMLElement* celem = OneFrame(elem, body->joints[i]->frame);
OneJoint(InsertEnd(celem, "joint"), body->joints[i], body->joints[i]->def);
}
// write geoms
for (int i=0; i<body->geoms.size(); i++) {
if (body->geoms[i]->frame != frame) continue;
OneGeom(InsertEnd(elem, "geom"), body->geoms[i], body->geoms[i]->def);
XMLElement* celem = OneFrame(elem, body->geoms[i]->frame);
OneGeom(InsertEnd(celem, "geom"), body->geoms[i], body->geoms[i]->def);
}
// write sites
for (int i=0; i<body->sites.size(); i++) {
if (body->sites[i]->frame != frame) continue;
OneSite(InsertEnd(elem, "site"), body->sites[i], body->sites[i]->def);
XMLElement* celem = OneFrame(elem, body->sites[i]->frame);
OneSite(InsertEnd(celem, "site"), body->sites[i], body->sites[i]->def);
}
// write cameras
for (int i=0; i<body->cameras.size(); i++) {
if (body->cameras[i]->frame != frame) continue;
OneCamera(InsertEnd(elem, "camera"), body->cameras[i], body->cameras[i]->def);
XMLElement* celem = OneFrame(elem, body->cameras[i]->frame);
OneCamera(InsertEnd(celem, "camera"), body->cameras[i], body->cameras[i]->def);
}
// write lights
for (int i=0; i<body->lights.size(); i++) {
if (body->lights[i]->frame != frame) continue;
OneLight(InsertEnd(elem, "light"), body->lights[i], body->lights[i]->def);
XMLElement* celem = OneFrame(elem, body->lights[i]->frame);
OneLight(InsertEnd(celem, "light"), body->lights[i], body->lights[i]->def);
}
// write plugin
@@ -1537,28 +1548,10 @@ void mjXWriter::Body(XMLElement* elem, mjCBody* body, mjCFrame* frame) {
OnePlugin(InsertEnd(elem, "plugin"), &body->plugin);
}
// write frames
for (int i=0; i<body->frames.size(); i++) {
// skip current frame
if (body->frames[i]->frame != frame) {
continue;
}
// write frame if named or has defaults
if (!body->frames[i]->name.empty() || !body->frames[i]->classname.empty()) {
Body(InsertEnd(elem, "frame"), body, body->frames[i]);
}
// otherwise skip
else {
Body(elem, body, body->frames[i]);
}
}
// write child bodies recursively
for (int i=0; i<body->bodies.size(); i++) {
if (body->bodies[i]->frame != frame) continue;
Body(InsertEnd(elem, "body"), body->bodies[i], nullptr);
XMLElement* celem = OneFrame(elem, body->bodies[i]->frame);
Body(InsertEnd(celem, "body"), body->bodies[i]);
}
}
+2 -1
View File
@@ -49,7 +49,7 @@ class mjXWriter : public mjXBase {
void Extension(tinyxml2::XMLElement* root); // extension section
void Custom(tinyxml2::XMLElement* root); // custom section
void Asset(tinyxml2::XMLElement* root); // asset section
void Body(tinyxml2::XMLElement* elem, mjCBody* body, mjCFrame* frame); // body/world section
void Body(tinyxml2::XMLElement* elem, mjCBody* body); // body/world section
void Contact(tinyxml2::XMLElement* root); // contact section
void Deformable(tinyxml2::XMLElement* root); // deformable section
void Equality(tinyxml2::XMLElement* root); // equality section
@@ -73,6 +73,7 @@ class mjXWriter : public mjXBase {
void OneTendon(tinyxml2::XMLElement* elem, mjCTendon* ptendon, mjCDef* def);
void OneActuator(tinyxml2::XMLElement* elem, mjCActuator* pactuator, mjCDef* def);
void OnePlugin(tinyxml2::XMLElement* elem, mjsPlugin* plugin);
tinyxml2::XMLElement* OneFrame(tinyxml2::XMLElement* elem, mjCFrame* frame);
bool writingdefaults; // true during defaults write
};
+2 -1
View File
@@ -344,7 +344,8 @@ bool mjXSchema::NameMatch(XMLElement* elem, int level) {
if (name_ == "body" &&
((level == 1 && !strcmp(elem->Value(), "worldbody")) ||
(level != 1 && !strcmp(elem->Value(), "body")) ||
(level >= 1 && !strcmp(elem->Value(), "frame")))) {
(level >= 1 && !strcmp(elem->Value(), "frame")) ||
(level >= 1 && !strcmp(elem->Value(), "replicate")))) {
return true;
}
+1 -1
View File
@@ -560,7 +560,7 @@ TEST_F(EnginePluginTest, SensorPlugin) {
testing::ElementsAreArray<int>({3*(i+1), 6*j, 3*j}));
EXPECT_THAT(*reinterpret_cast<mjtNum(*)[3]>(d->plugin_state +
m->plugin_stateadr[4]),
testing::ElementsAreArray<int>({4*(i+1), 8*j, 4*j}));
testing::ElementsAreArray<int>({5*(i+1), 10*j, 5*j}));
EXPECT_THAT(*reinterpret_cast<mjtNum(*)[18]>(d->sensordata),
testing::ElementsAreArray<int>({ i+1, 2*j, j,
5*(i+1), 10*j, 5*j,
+14 -8
View File
@@ -191,7 +191,7 @@ auto Compare(T val1, T val2) {
ReturnType magnitude = mju_abs(val1) + mju_abs(val2);
error = mju_abs(val1/magnitude - val2/magnitude) / magnitude;
}
ReturnType safety_factor = 10;
ReturnType safety_factor = 200;
return error < safety_factor * std::numeric_limits<ReturnType>::epsilon()
? 0
: error;
@@ -213,13 +213,19 @@ mjtNum CompareModel(const mjModel* m1, const mjModel* m2,
if (maxdif > 0) return maxdif;
// compare arrays
#define X(type, name, nr, nc) \
for (int r=0; r < m1->nr; r++) \
for (int c=0; c < nc; c++) { \
dif = Compare(m1->name[r*nc+c], m2->name[r*nc+c]); \
if (dif > maxdif) { maxdif = dif; field = #name;} }
MJMODEL_POINTERS
#undef X
#define X(type, name, nr, nc) \
for (int r = 0; r < m1->nr; r++) \
for (int c = 0; c < nc; c++) { \
dif = Compare(m1->name[r * nc + c], m2->name[r * nc + c]); \
if (dif > maxdif) { \
maxdif = dif; \
field = #name; \
field += " row: " + std::to_string(r); \
field += " col: " + std::to_string(c); \
} \
} // NOLINT
MJMODEL_POINTERS
#undef X
// compare scalars in mjOption
#define X(type, name) \
+30 -6
View File
@@ -204,12 +204,17 @@ static constexpr char xml_child[] = R"(
</default>
</default>
<asset>
<texture name="texture" type="2d" builtin="checker" width="32" height="32"/>
<material name="material" texture="texture" texrepeat="1 1" texuniform="true"/>
</asset>
<worldbody>
<frame name="pframe">
<frame name="cframe">
<body name="body">
<joint type="hinge" name="hinge"/>
<geom class="cylinder"/>
<geom class="cylinder" material="material"/>
<light mode="targetbody" target="targetbody"/>
<body name="targetbody"/>
<body/>
@@ -254,10 +259,15 @@ TEST_F(MujocoTest, AttachSame) {
</default>
</default>
<asset>
<texture name="texture" type="2d" builtin="checker" width="32" height="32"/>
<material name="material" texture="texture" texrepeat="1 1" texuniform="true"/>
</asset>
<worldbody>
<body name="body">
<joint type="hinge" name="hinge"/>
<geom class="cylinder"/>
<geom class="cylinder" material="material"/>
<light mode="targetbody" target="targetbody"/>
<body name="targetbody"/>
<body/>
@@ -266,7 +276,7 @@ TEST_F(MujocoTest, AttachSame) {
<frame name="frame" pos=".1 0 0" euler="0 90 0">
<body name="attached-body-1">
<joint type="hinge" name="attached-hinge-1"/>
<geom class="cylinder"/>
<geom class="cylinder" material="material"/>
<light mode="targetbody" target="attached-targetbody-1"/>
<body name="attached-targetbody-1"/>
<body/>
@@ -374,6 +384,11 @@ TEST_F(MujocoTest, AttachDifferent) {
</default>
</default>
<asset>
<texture name="attached-texture-1" type="2d" builtin="checker" width="32" height="32"/>
<material name="attached-material-1" texture="attached-texture-1" texrepeat="1 1" texuniform="true"/>
</asset>
<worldbody>
<body name="sphere">
<freejoint/>
@@ -381,7 +396,7 @@ TEST_F(MujocoTest, AttachDifferent) {
<frame name="frame" pos=".1 0 0" euler="0 90 0">
<body name="attached-body-1">
<joint type="hinge" name="attached-hinge-1"/>
<geom class="attached-cylinder-1"/>
<geom class="attached-cylinder-1" material="attached-material-1"/>
<light mode="targetbody" target="attached-targetbody-1"/>
<body name="attached-targetbody-1"/>
<body/>
@@ -478,6 +493,11 @@ TEST_F(MujocoTest, AttachFrame) {
</default>
</default>
<asset>
<texture name="attached-texture-1" type="2d" builtin="checker" width="32" height="32"/>
<material name="attached-material-1" texture="attached-texture-1" texrepeat="1 1" texuniform="true"/>
</asset>
<worldbody>
<body name="sphere">
<freejoint/>
@@ -487,7 +507,7 @@ TEST_F(MujocoTest, AttachFrame) {
<frame name="cframe">
<body name="attached-body-1">
<joint type="hinge" name="attached-hinge-1"/>
<geom class="attached-cylinder-1"/>
<geom class="attached-cylinder-1" material="attached-material-1"/>
<light mode="targetbody" target="attached-targetbody-1"/>
<body name="attached-targetbody-1"/>
<body/>
@@ -568,6 +588,11 @@ TEST_F(MujocoTest, DetachBody) {
static constexpr char xml_result[] = R"(
<mujoco>
<asset>
<texture name="texture" type="2d" builtin="checker" width="32" height="32"/>
<material name="material" texture="texture" texrepeat="1 1" texuniform="true"/>
</asset>
<worldbody>
<frame name="pframe">
<frame name="cframe">
@@ -605,7 +630,6 @@ TEST_F(MujocoTest, DetachBody) {
// destroy everything
mjs_deleteSpec(child);
mjs_deleteBody(body);
mj_deleteModel(m_detached);
mj_deleteModel(m_expected);
}
+167
View File
@@ -27,6 +27,8 @@
#include <mujoco/mujoco.h>
#include "src/cc/array_safety.h"
#include "src/engine/engine_util_errmem.h"
#include "src/user/user_api.h"
#include "src/xml/xml.h"
#include "test/fixture.h"
namespace mujoco {
@@ -888,6 +890,171 @@ TEST_F(XMLReaderTest, DuplicateFrameName) {
EXPECT_THAT(error.data(), HasSubstr("repeated name 'frame1'"));
}
// ---------------------- test replicate parsing -------------------------------
TEST_F(XMLReaderTest, ParseReplicate) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<texture name="texture" type="2d" builtin="checker" width="32" height="32"/>
<material name="material" texture="texture" texrepeat="1 1" texuniform="true"/>
</asset>
<worldbody>
<replicate count="2" offset="1 0 0">
<replicate count="2" offset="0 1 0" sep="_">
<geom name="geom" size="1" pos="0 0 1" material="material"/>
<site name="site" pos="1 0 0"/>
</replicate>
</replicate>
<replicate count="101" euler="0 0 1.8">
<geom name="g" size="1" pos="0 -1 0"/>
</replicate>
</worldbody>
<sensor>
<framepos name="sensor" objtype="site" objname="site"/>
</sensor>
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, testing::NotNull()) << error.data();
EXPECT_THAT(m->ngeom, 105);
EXPECT_THAT(m->nsensor, 4);
// check that the separator is used correctly
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 2; ++j) {
char geom_name[mjMAXUINAME] = "";
util::strcat_arr(geom_name, m->names + m->name_geomadr[2*i+j]);
EXPECT_THAT(m->geom_pos[6*i+3*j+0], i);
EXPECT_THAT(m->geom_pos[6*i+3*j+1], j);
EXPECT_THAT(m->geom_pos[6*i+3*j+2], 1);
EXPECT_THAT(std::string(geom_name),
"geom_" + std::to_string(j) + std::to_string(i));
}
}
// check that 3 digits are used if count > 99
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 10; ++j) {
for (int k = 0; k < 10; ++k) {
int ngeom = 100*i+10*j+k;
if (ngeom > 99) break;
char geom_name[mjMAXUINAME] = "";
util::strcat_arr(geom_name, m->names + m->name_geomadr[4+ngeom]);
EXPECT_THAT(
std::string(geom_name),
"g" + std::to_string(i) + std::to_string(j) + std::to_string(k));
}
}
}
// check that the final pose is correct
int n = 104;
EXPECT_NEAR(m->geom_pos[3*n+0], 0, 1e-8);
EXPECT_NEAR(m->geom_pos[3*n+1], 1, 1e-8);
EXPECT_EQ(m->geom_pos[3*n+2], 0);
EXPECT_NEAR(m->geom_quat[4*n+0], 0, 1e-8);
EXPECT_EQ(m->geom_quat[4*n+1], 0);
EXPECT_EQ(m->geom_quat[4*n+2], 0);
EXPECT_EQ(m->geom_quat[4*n+3], 1);
mj_deleteModel(m);
}
TEST_F(XMLReaderTest, ParseReplicatePartialReference) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<geom name="floor" type="plane" size="1 1 1"/>
<site name="world"/>
<replicate count="2" offset="1 0 0">
<body name="replicated">
<freejoint/>
<site name="replicated"/>
<geom name="replicated" size="1"/>
</body>
</replicate>
</worldbody>
<contact>
<pair geom1="floor" geom2="replicated" condim="1"/>
<exclude body1="world" body2="replicated" />
</contact>
<tendon>
<spatial>
<site site="world"/>
<site site="replicated"/>
</spatial>
</tendon>
<sensor>
<framepos name="sensor" objtype="site" objname="replicated" reftype="site" refname="world"/>
</sensor>
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, testing::NotNull()) << error.data();
EXPECT_THAT(m->nbody, 3);
EXPECT_THAT(m->ngeom, 3);
EXPECT_THAT(m->npair, 2);
EXPECT_THAT(m->nexclude, 2);
EXPECT_THAT(m->ntendon, 2);
EXPECT_THAT(m->nsensor, 2);
mj_deleteModel(m);
}
TEST_F(XMLReaderTest, ParseReplicateDefaultPropagate) {
static constexpr char xml[] = R"(
<mujoco>
<default>
<default class="body">
<geom type="capsule"/>
</default>
</default>
<worldbody>
<replicate count="2" euler="0 0 16.36" sep="-">
<body name="torso" pos="-5 0 1.282" childclass="body">
<body name="head" pos="0 0 .19">
<geom name="head" type="sphere" size=".09"/>
</body>
</body>
</replicate>
</worldbody>
</mujoco>
)";
std::array<char, 1024> error;
mjSpec* spec = ParseSpecFromString(xml, error.data(), error.size());
EXPECT_THAT(spec, NotNull()) << error.data();
mjsBody* torso = mjs_findBody(spec, "torso-0");
EXPECT_THAT(torso, NotNull());
mjsDefault* def = mjs_getDefault(torso->element);
EXPECT_THAT(def, NotNull());
EXPECT_THAT(def->geom->type, mjGEOM_CAPSULE);
mjsBody* head = mjs_findBody(spec, "head-0");
EXPECT_THAT(head, NotNull());
def = mjs_getDefault(head->element);
EXPECT_THAT(def, NotNull());
EXPECT_THAT(def->geom->type, mjGEOM_CAPSULE);
mjs_deleteSpec(spec);
}
// ----------------------- test camera parsing ---------------------------------
TEST_F(XMLReaderTest, CameraInvalidFovyAndSensorsize) {