Add attach tag to MJCF.

PiperOrigin-RevId: 651117953
Change-Id: I57b0e53ed44607d479b3785d908e0837c0804cf3
This commit is contained in:
Alessio Quaglino
2024-07-10 12:40:50 -07:00
committed by Copybara-Service
parent 5b080bacc4
commit 27b9ddda5b
19 changed files with 419 additions and 248 deletions
+83 -22
View File
@@ -110,11 +110,11 @@ Meta elements
These elements are not strictly part of the low-level MJCF format definition, but rather instruct the compiler to 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 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 five meta-elements in MJCF: saving the XML. There are currently six meta-elements in MJCF:
- :ref:`include<include>`, :ref:`frame<frame>`, and :ref:`replicate<replicate>` 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 - :ref:`composite<body-composite>`, :ref:`flexcomp<body-flexcomp>` and :ref:`attach<body-attach>` which are part of the
procedurally generate other MJCF elements. schema, but serve to procedurally generate other MJCF elements.
.. _frame: .. _frame:
@@ -1739,6 +1739,24 @@ properties are grouped together.
definition could in fact come from a defaults class. The remaining material properties always apply. definition could in fact come from a defaults class. The remaining material properties always apply.
.. _asset-model:
:el-prefix:`asset/` |-| **model** (*)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element specifies other MJCF models which may be used for :ref:`attachment<body-attach>` in the current model.
.. _asset-model-name:
:at:`name`: :at-val:`string, required`
Name of the sub-model, used for referencing in :ref:`attach<body-attach>`. If unspecified, the
:ref:`model name<mujoco-model>` is used.
.. _asset-model-file:
:at:`file`: :at-val:`string, required`
The file from which the sub-model will be loaded. Note that the sub-model must be a valid MJCF model.
.. _body: .. _body:
**(world)body** (R) **(world)body** (R)
@@ -2816,24 +2834,6 @@ the direction specified by the dir attribute. It does not have a full spatial fr
The specular color of the light. The specular color of the light.
.. _body-plugin:
:el-prefix:`body/` |-| **plugin** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Associate this body with an :ref:`engine plugin<exPlugin>`. Either :at:`plugin` or :at:`instance` are required.
.. _body-plugin-plugin:
:at:`plugin`: :at-val:`string, optional`
Plugin identifier, used for implicit plugin instantiation.
.. _body-plugin-instance:
:at:`instance`: :at-val:`string, optional`
Instance name, used for explicit plugin instantiation.
.. _body-composite: .. _body-composite:
:el-prefix:`body/` |-| **composite** (*) :el-prefix:`body/` |-| **composite** (*)
@@ -3638,10 +3638,71 @@ Associate this flexcomp with an :ref:`engine plugin<exPlugin>`. Either :at:`plug
Instance name, used for explicit plugin instantiation. Instance name, used for explicit plugin instantiation.
.. _body-plugin:
:el-prefix:`body/` |-| **plugin** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Associate this body with an :ref:`engine plugin<exPlugin>`. Either :at:`plugin` or :at:`instance` are required.
.. _body-plugin-plugin:
:at:`plugin`: :at-val:`string, optional`
Plugin identifier, used for implicit plugin instantiation.
.. _body-plugin-instance:
:at:`instance`: :at-val:`string, optional`
Instance name, used for explicit plugin instantiation.
.. _body-attach:
:el-prefix:`body/` |-| **attach** (*)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The :el:`attach` element is used to insert a sub-tree of bodies from another model into this model's kinematic tree.
Unlike :ref:`include<include>`, which is implemented in the parser and is equivalent to copying and pasting XML from
one file into another, :el:`attach` is implemented in the model compiler. In order to use this element, the sub-model
must first be defined as an :ref:`asset<model-asset>`. When creating an attachment, the top body of the attached subtree
is specified, and all referencing elements outside the kinematic tree (e.g., sensors and actuators), are
also copied into the top-level model. Additionally, any elements referenced from within the attached subtree (e.g.
defaults and assets) will be copied in to the top-level model. :el:`attach` is a :ref:`meta-element`, so upon saving
all attachments will appear in the saved XML file.
.. admonition:: Known issues
:class: attention
The :el:`attach` meta-element is new and not well tested. Please report any issues you encounter to the development
team. Additionally, the following known limitations exist, to be addressed in a future release:
- The world body cannot be attached.
- An entire model cannot be attached (i.e. including all elements, referenced or not).
- All assets from the child model will be copied in, whether they are referenced or not.
- Self-attach or circular references are not checked for and will lead to infinite loops.
- :ref:`Keyframes<keyframe>` are not yet supported. When attaching, all keyframes will be deleted.
.. _body-attach-model:
:at:`model`: :at-val:`string, optional`
The sub-model from which to attach a subtree.
.. _body-attach-body:
:at:`body`: :at-val:`string, optional`
Name of the body in the sub-model to attach here. The body and its subtree will be attached.
.. _body-attach-prefix:
:at:`prefix`: :at-val:`string, optional`
Prefix to prepend to names of elements in the sub-model. If empty, the names are unchanged. This attribute is
required to prevent name collisions with the parent or when attaching the same sub-tree multiple times.
.. _body-frame: .. _body-frame:
:el-prefix:`body/` |-| **frame** (*) :el-prefix:`body/` |-| **frame** (*)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Frames specify a coordinate transformation which is applied to all child elements. They disappear during compilation Frames specify a coordinate transformation which is applied to all child elements. They disappear during compilation
and the transformation they encode is accumulated in their direct children. See :ref:`frame<frame>` for examples. and the transformation they encode is accumulated in their direct children. See :ref:`frame<frame>` for examples.
+14
View File
@@ -177,6 +177,13 @@
| | | | :ref:`reflectance<asset-material-reflectance>` | :ref:`metallic<asset-material-metallic>` | :ref:`roughness<asset-material-roughness>` | :ref:`rgba<asset-material-rgba>` | | | | | | :ref:`reflectance<asset-material-reflectance>` | :ref:`metallic<asset-material-metallic>` | :ref:`roughness<asset-material-roughness>` | :ref:`rgba<asset-material-rgba>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| asset |br| |_| |L| | | .. table:: |
| :ref:`model | \* | :class: mjcf-attributes |
| <asset-model>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`name<asset-model-name>` | :ref:`file<asset-model-file>` | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| mujoco |br| |L| | | .. table:: | | mujoco |br| |L| | | .. table:: |
| :ref:`body | R | :class: mjcf-attributes | | :ref:`body | R | :class: mjcf-attributes |
| <body>` | | | | <body>` | | |
@@ -261,6 +268,13 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| body |br| |_| |L| | | .. table:: | | |_| body |br| |_| |L| | | .. table:: |
| :ref:`attach | \* | :class: mjcf-attributes |
| <body-attach>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`model<body-attach-model>` | :ref:`body<body-attach-body>` | :ref:`prefix<body-attach-prefix>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| body |br| |_| |L| | | .. table:: |
| :ref:`site | \* | :class: mjcf-attributes | | :ref:`site | \* | :class: mjcf-attributes |
| <body-site>` | | | | <body-site>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+15 -12
View File
@@ -39,27 +39,30 @@ General
:align: right :align: right
:width: 240px :width: 240px
7. Added support for orthographic cameras. This is available for both fixed cameras and the free camera, using the 7. Add :ref:`attach<body-attach>` meta-element to MJCF, which allows attaching a model to a body.
8. Added support for orthographic cameras. This is available for both fixed cameras and the free camera, using the
:ref:`camera/orthographic<body-camera-orthographic>` and :ref:`global/orthographic<visual-global-orthographic>` :ref:`camera/orthographic<body-camera-orthographic>` and :ref:`global/orthographic<visual-global-orthographic>`
attributes, respectively. attributes, respectively.
8. Added :ref:`maxhullvert<asset-mesh-maxhullvert>`, the maximum number of vertices in a mesh's convex hull. 9. Added :ref:`maxhullvert<asset-mesh-maxhullvert>`, the maximum number of vertices in a mesh's convex hull.
9. Added :ref:`mj_setKeyframe` for saving the current state into a model keyframe. 10. Added :ref:`mj_setKeyframe` for saving the current state into a model keyframe.
10. Added support for ``ball`` joints in the URDF parser ("spherical" in URDF). 11. Added support for ``ball`` joints in the URDF parser ("spherical" in URDF).
11. Replaced ``mjUSEDOUBLE`` which was previously hard-coded in 12. Deprecated :ref:`mju_rotVecMat` and :ref:`mju_rotVecMatT` in favor of :ref:`mju_mulMatVec3` and
:ref:`mju_mulMatTVec3`. These functions names and argument ordering are more consistent with the rest of the API.
13. Replaced ``mjUSEDOUBLE`` which was previously hard-coded in
`mjtnum.h <https://github.com/google-deepmind/mujoco/blob/main/include/mujoco/mjtnum.h>`__ `mjtnum.h <https://github.com/google-deepmind/mujoco/blob/main/include/mujoco/mjtnum.h>`__
with the build-time flag ``mjUSESINGLE``. If this symbol is not defined, MuJoCo will use double-precision floating with the build-time flag ``mjUSESINGLE``. If this symbol is not defined, MuJoCo will use double-precision floating
point, as usual. If ``mjUSESINGLE`` is defined, MuJoCo will use single-precision floating point. See :ref:`mjtNum`. point, as usual. If ``mjUSESINGLE`` is defined, MuJoCo will use single-precision floating point. See :ref:`mjtNum`.
Relatedly, fixed various type errors that prevented building with single-precision. Relatedly, fixed various type errors that prevented building with single-precision.
12. Quaternions in ``mjData->qpos`` and ``mjData->mocap_quat`` are no longer normalized in-place by 14. Quaternions in ``mjData->qpos`` and ``mjData->mocap_quat`` are no longer normalized in-place by
:ref:`mj_kinematics`. Instead they are normalized when they are used. After the first step, quaternions in :ref:`mj_kinematics`. Instead they are normalized when they are used. After the first step, quaternions in
``mjData->qpos`` will be normalized. ``mjData->qpos`` will be normalized.
MJX MJX
~~~ ~~~
13. Added support for :ref:`elliptic friction cones<option-cone>`. 15. Added support for :ref:`elliptic friction cones<option-cone>`.
14. Fixed a bug that resulted in less-optimal linesearch solutions for some difficult constraint settings. 16. Fixed a bug that resulted in less-optimal linesearch solutions for some difficult constraint settings.
15. Fixed a bug in the Newton solver that sometimes resulted in less-optimal gradients. 17. Fixed a bug in the Newton solver that sometimes resulted in less-optimal gradients.
.. youtube:: P83tKA1iz2Y .. youtube:: P83tKA1iz2Y
@@ -68,14 +71,14 @@ MJX
Simulate Simulate
^^^^^^^^ ^^^^^^^^
16. Added improved tutorial video. 18. Added improved tutorial video.
17. Improved the Brownian noise generator. 19. Improved the Brownian noise generator.
|br| |br| |br| |br| |br| |br| |br| |br|
Python bindings Python bindings
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
18. Fixed a memory leak when using ``copy.deepcopy()`` on a ``mujoco.MjData`` instance (:github:issue:`1572`). 20. Fixed a memory leak when using ``copy.deepcopy()`` on a ``mujoco.MjData`` instance (:github:issue:`1572`).
Version 3.1.6 (Jun 3, 2024) Version 3.1.6 (Jun 3, 2024)
--------------------------- ---------------------------
+6 -193
View File
@@ -20,82 +20,11 @@
<asset> <asset>
<texture type="skybox" builtin="gradient" rgb1=".3 .5 .7" rgb2="0 0 0" width="512" height="512"/> <texture type="skybox" builtin="gradient" rgb1=".3 .5 .7" rgb2="0 0 0" width="512" height="512"/>
<texture name="body" type="cube" builtin="flat" mark="cross" width="128" height="128" <texture name="floor" type="2d" builtin="checker" width="512" height="512" rgb1=".1 .2 .3" rgb2=".2 .3 .4"/>
rgb1="0.8 0.6 0.4" rgb2="0.8 0.6 0.4" markrgb="1 1 1"/> <material name="floor" texture="floor" texrepeat="1 1" texuniform="true" reflectance=".2"/>
<material name="body" texture="body" texuniform="true" rgba="0.8 0.6 .4 1"/> <model name="humanoid" file="humanoid.xml"/>
<texture name="grid" type="2d" builtin="checker" width="512" height="512" rgb1=".1 .2 .3" rgb2=".2 .3 .4"/>
<material name="grid" texture="grid" texrepeat="1 1" texuniform="true" reflectance=".2"/>
</asset> </asset>
<default>
<motor ctrlrange="-1 1" ctrllimited="true"/>
<default class="body">
<!-- geoms -->
<geom type="capsule" condim="1" friction=".7" solimp=".9 .99 .003" solref=".015 1" material="body" group="1"/>
<default class="thigh">
<geom size=".06"/>
</default>
<default class="shin">
<geom fromto="0 0 0 0 0 -.3" size=".049"/>
</default>
<default class="foot">
<geom size=".027"/>
<default class="foot1">
<geom fromto="-.07 -.01 0 .14 -.03 0"/>
</default>
<default class="foot2">
<geom fromto="-.07 .01 0 .14 .03 0"/>
</default>
</default>
<default class="arm_upper">
<geom size=".04"/>
</default>
<default class="arm_lower">
<geom size=".031"/>
</default>
<default class="hand">
<geom type="sphere" size=".04"/>
</default>
<!-- joints -->
<joint type="hinge" damping=".2" stiffness="1" armature=".01" limited="true" solimplimit="0 .99 .01"/>
<default class="joint_big">
<joint damping="5" stiffness="10"/>
<default class="hip_x">
<joint range="-30 10"/>
</default>
<default class="hip_z">
<joint range="-60 35"/>
</default>
<default class="hip_y">
<joint axis="0 1 0" range="-150 20"/>
</default>
<default class="joint_big_stiff">
<joint stiffness="20"/>
</default>
</default>
<default class="knee">
<joint pos="0 0 .02" axis="0 -1 0" range="-160 2"/>
</default>
<default class="ankle">
<joint range="-50 50"/>
<default class="ankle_y">
<joint pos="0 0 .08" axis="0 1 0" stiffness="6"/>
</default>
<default class="ankle_x">
<joint pos="0 0 .04" stiffness="3"/>
</default>
</default>
<default class="shoulder">
<joint range="-85 60"/>
</default>
<default class="elbow">
<joint range="-100 50" stiffness="0"/>
</default>
</default>
</default>
<visual> <visual>
<map force="0.1" zfar="30"/> <map force="0.1" zfar="30"/>
<rgba haze="0.15 0.25 0.35 1"/> <rgba haze="0.15 0.25 0.35 1"/>
@@ -104,127 +33,11 @@
</visual> </visual>
<worldbody> <worldbody>
<geom name="floor" size="10 10 .05" type="plane" material="grid" condim="3"/> <geom name="floor" size="10 10 .05" type="plane" material="floor" condim="3"/>
<light directional="true" diffuse=".4 .4 .4" specular="0.1 0.1 0.1" pos="0 0 5" dir="0 0 -1" castshadow="false"/> <light directional="true" diffuse=".4 .4 .4" specular="0.1 0.1 0.1" pos="0 0 5" dir="0 0 -1" castshadow="false"/>
<light name="spotlight" mode="targetbodycom" target="world" diffuse="1 1 1" specular="0.3 0.3 0.3" pos="-6 -6 4" cutoff="60"/> <light name="spotlight" mode="targetbodycom" target="world" diffuse="1 1 1" specular="0.3 0.3 0.3" pos="-6 -6 4" cutoff="60"/>
<replicate count="22" euler="0 0 16.36" sep="-"> <replicate count="22" euler="0 0 16.36" sep="-" offset="1 0 0">
<body name="torso" pos="-5 0 1.282" childclass="body"> <attach model="humanoid" body="torso" prefix="_"/>
<camera name="back" pos="-3 0 1" xyaxes="0 -1 0 1 0 2" mode="trackcom"/>
<camera name="side" pos="0 -3 1" xyaxes="1 0 0 0 1 2" mode="trackcom"/>
<freejoint name="root"/>
<geom name="torso" fromto="0 -.07 0 0 .07 0" size=".07"/>
<geom name="waist_upper" fromto="-.01 -.06 -.12 -.01 .06 -.12" size=".06"/>
<body name="head" pos="0 0 .19">
<geom name="head" type="sphere" size=".09"/>
<camera name="egocentric" pos=".09 0 0" xyaxes="0 -1 0 .1 0 1" fovy="80"/>
</body>
<body name="waist_lower" pos="-.01 0 -.26">
<geom name="waist_lower" fromto="0 -.06 0 0 .06 0" size=".06"/>
<joint name="abdomen_z" pos="0 0 .065" axis="0 0 1" range="-45 45" class="joint_big_stiff"/>
<joint name="abdomen_y" pos="0 0 .065" axis="0 1 0" range="-75 30" class="joint_big"/>
<body name="pelvis" pos="0 0 -.165">
<joint name="abdomen_x" pos="0 0 .1" axis="1 0 0" range="-35 35" class="joint_big"/>
<geom name="butt" fromto="-.02 -.07 0 -.02 .07 0" size=".09"/>
<body name="thigh_right" pos="0 -.1 -.04">
<joint name="hip_x_right" axis="1 0 0" class="hip_x"/>
<joint name="hip_z_right" axis="0 0 1" class="hip_z"/>
<joint name="hip_y_right" class="hip_y"/>
<geom name="thigh_right" fromto="0 0 0 0 .01 -.34" class="thigh"/>
<body name="shin_right" pos="0 .01 -.4">
<joint name="knee_right" class="knee"/>
<geom name="shin_right" class="shin"/>
<body name="foot_right" pos="0 0 -.39">
<joint name="ankle_y_right" class="ankle_y"/>
<joint name="ankle_x_right" class="ankle_x" axis="1 0 .5"/>
<geom name="foot1_right" class="foot1"/>
<geom name="foot2_right" class="foot2"/>
</body>
</body>
</body>
<body name="thigh_left" pos="0 .1 -.04">
<joint name="hip_x_left" axis="-1 0 0" class="hip_x"/>
<joint name="hip_z_left" axis="0 0 -1" class="hip_z"/>
<joint name="hip_y_left" class="hip_y"/>
<geom name="thigh_left" fromto="0 0 0 0 -.01 -.34" class="thigh"/>
<body name="shin_left" pos="0 -.01 -.4">
<joint name="knee_left" class="knee"/>
<geom name="shin_left" fromto="0 0 0 0 0 -.3" class="shin"/>
<body name="foot_left" pos="0 0 -.39">
<joint name="ankle_y_left" class="ankle_y"/>
<joint name="ankle_x_left" class="ankle_x" axis="-1 0 -.5"/>
<geom name="foot1_left" class="foot1"/>
<geom name="foot2_left" class="foot2"/>
</body>
</body>
</body>
</body>
</body>
<body name="upper_arm_right" pos="0 -.17 .06">
<joint name="shoulder1_right" axis="2 1 1" class="shoulder"/>
<joint name="shoulder2_right" axis="0 -1 1" class="shoulder"/>
<geom name="upper_arm_right" fromto="0 0 0 .16 -.16 -.16" class="arm_upper"/>
<body name="lower_arm_right" pos=".18 -.18 -.18">
<joint name="elbow_right" axis="0 -1 1" class="elbow"/>
<geom name="lower_arm_right" fromto=".01 .01 .01 .17 .17 .17" class="arm_lower"/>
<body name="hand_right" pos=".18 .18 .18">
<geom name="hand_right" zaxis="1 1 1" class="hand"/>
</body>
</body>
</body>
<body name="upper_arm_left" pos="0 .17 .06">
<joint name="shoulder1_left" axis="-2 1 -1" class="shoulder"/>
<joint name="shoulder2_left" axis="0 -1 -1" class="shoulder"/>
<geom name="upper_arm_left" fromto="0 0 0 .16 .16 -.16" class="arm_upper"/>
<body name="lower_arm_left" pos=".18 .18 -.18">
<joint name="elbow_left" axis="0 -1 -1" class="elbow"/>
<geom name="lower_arm_left" fromto=".01 -.01 .01 .17 -.17 .17" class="arm_lower"/>
<body name="hand_left" pos=".18 -.18 .18">
<geom name="hand_left" zaxis="1 -1 1" class="hand"/>
</body>
</body>
</body>
</body>
</replicate> </replicate>
</worldbody> </worldbody>
<contact>
<exclude body1="waist_lower" body2="thigh_right"/>
<exclude body1="waist_lower" body2="thigh_left"/>
</contact>
<tendon>
<fixed name="hamstring_right" limited="true" range="-0.3 2">
<joint joint="hip_y_right" coef=".5"/>
<joint joint="knee_right" coef="-.5"/>
</fixed>
<fixed name="hamstring_left" limited="true" range="-0.3 2">
<joint joint="hip_y_left" coef=".5"/>
<joint joint="knee_left" coef="-.5"/>
</fixed>
</tendon>
<actuator>
<motor name="abdomen_z" gear="40" joint="abdomen_z"/>
<motor name="abdomen_y" gear="40" joint="abdomen_y"/>
<motor name="abdomen_x" gear="40" joint="abdomen_x"/>
<motor name="hip_x_right" gear="40" joint="hip_x_right"/>
<motor name="hip_z_right" gear="40" joint="hip_z_right"/>
<motor name="hip_y_right" gear="120" joint="hip_y_right"/>
<motor name="knee_right" gear="80" joint="knee_right"/>
<motor name="ankle_y_right" gear="20" joint="ankle_y_right"/>
<motor name="ankle_x_right" gear="20" joint="ankle_x_right"/>
<motor name="hip_x_left" gear="40" joint="hip_x_left"/>
<motor name="hip_z_left" gear="40" joint="hip_z_left"/>
<motor name="hip_y_left" gear="120" joint="hip_y_left"/>
<motor name="knee_left" gear="80" joint="knee_left"/>
<motor name="ankle_y_left" gear="20" joint="ankle_y_left"/>
<motor name="ankle_x_left" gear="20" joint="ankle_x_left"/>
<motor name="shoulder1_right" gear="20" joint="shoulder1_right"/>
<motor name="shoulder2_right" gear="20" joint="shoulder2_right"/>
<motor name="elbow_right" gear="40" joint="elbow_right"/>
<motor name="shoulder1_left" gear="20" joint="shoulder1_left"/>
<motor name="shoulder2_left" gear="20" joint="shoulder2_left"/>
<motor name="elbow_left" gear="40" joint="elbow_left"/>
</actuator>
</mujoco> </mujoco>
+18
View File
@@ -101,6 +101,7 @@ int mjs_attachBody(mjsFrame* parent, const mjsBody* child,
try { try {
*frame_parent += std::string(prefix) + *child_body + std::string(suffix); *frame_parent += std::string(prefix) + *child_body + std::string(suffix);
} catch (mjCError& e) { } catch (mjCError& e) {
frame_parent->model->SetError(e);
return -1; return -1;
} }
return 0; return 0;
@@ -116,6 +117,7 @@ int mjs_attachFrame(mjsBody* parent, const mjsFrame* child,
try { try {
*body_parent += std::string(prefix) + *child_frame + std::string(suffix); *body_parent += std::string(prefix) + *child_frame + std::string(suffix);
} catch (mjCError& e) { } catch (mjCError& e) {
body_parent->model->SetError(e);
return -1; return -1;
} }
return 0; return 0;
@@ -158,6 +160,14 @@ void mj_deleteSpec(mjSpec* s) {
// add spec (model asset) to spec
void mjs_addSpec(mjSpec* s, mjSpec* child) {
mjCModel* model = static_cast<mjCModel*>(s->element);
model->AppendSpec(child);
}
// delete object, it will call the appropriate destructor since ~mjCBase is virtual // delete object, it will call the appropriate destructor since ~mjCBase is virtual
void mjs_delete(mjsElement* element) { void mjs_delete(mjsElement* element) {
mjCBase* object = static_cast<mjCBase*>(element); mjCBase* object = static_cast<mjCBase*>(element);
@@ -466,6 +476,14 @@ mjSpec* mjs_getSpec(mjsBody* body) {
// find spec (model asset) by name
mjSpec* mjs_findSpec(mjSpec* s, const char* name) {
mjCModel* model = static_cast<mjCModel*>(s->element);
return model->FindSpec(name);
}
// get default // get default
mjsDefault* mjs_getDefault(mjsElement* element) { mjsDefault* mjs_getDefault(mjsElement* element) {
mjCModel* model = static_cast<mjCBase*>(element)->model; mjCModel* model = static_cast<mjCBase*>(element)->model;
+6
View File
@@ -60,6 +60,9 @@ MJAPI void mj_copyBack(mjSpec* s, const mjModel* m);
// Delete spec. // Delete spec.
MJAPI void mj_deleteSpec(mjSpec* s); MJAPI void mj_deleteSpec(mjSpec* s);
// Add spec (model asset) to spec.
MJAPI void mjs_addSpec(mjSpec* s, mjSpec* child);
//---------------------------------- Attachment ---------------------------------------------------- //---------------------------------- Attachment ----------------------------------------------------
@@ -182,6 +185,9 @@ MJAPI mjsMaterial* mjs_addMaterial(mjSpec* s, mjsDefault* def);
// Get spec from body. // Get spec from body.
MJAPI mjSpec* mjs_getSpec(mjsBody* body); MJAPI mjSpec* mjs_getSpec(mjsBody* body);
// Find spec (model asset) by name.
MJAPI mjSpec* mjs_findSpec(mjSpec* spec, const char* name);
// Find body in model by name. // Find body in model by name.
MJAPI mjsBody* mjs_findBody(mjSpec* s, const char* name); MJAPI mjsBody* mjs_findBody(mjSpec* s, const char* name);
+25 -1
View File
@@ -16,6 +16,7 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <array>
#include <csetjmp> #include <csetjmp>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
@@ -33,7 +34,6 @@
#include <mujoco/mjspec.h> #include <mujoco/mjspec.h>
#include <mujoco/mjtnum.h> #include <mujoco/mjtnum.h>
#include <mujoco/mjplugin.h> #include <mujoco/mjplugin.h>
#include <mujoco/mjvisualize.h>
#include "cc/array_safety.h" #include "cc/array_safety.h"
#include "engine/engine_forward.h" #include "engine/engine_forward.h"
#include "engine/engine_io.h" #include "engine/engine_io.h"
@@ -338,6 +338,7 @@ mjCModel& mjCModel::operator-=(const mjCBody& subtree) {
// add default tree to this model // add default tree to this model
mjCModel_& mjCModel::operator+=(mjCDef& subtree) { mjCModel_& mjCModel::operator+=(mjCDef& subtree) {
defaults_.push_back(&subtree); defaults_.push_back(&subtree);
def_map[subtree.name] = &subtree;
// set parent to the main default if this is not the only default in the model // set parent to the main default if this is not the only default in the model
if (!subtree.parent && &subtree != defaults_[0]) { if (!subtree.parent && &subtree != defaults_[0]) {
@@ -660,6 +661,11 @@ mjCPlugin* mjCModel::AddPlugin() {
} }
// append spec to spec
void mjCModel::AppendSpec(mjSpec* spec) {
specs_.push_back(spec);
}
//------------------------ API FOR ACCESS TO MODEL ELEMENTS --------------------------------------- //------------------------ API FOR ACCESS TO MODEL ELEMENTS ---------------------------------------
@@ -886,6 +892,18 @@ mjCFrame* mjCModel::FindFrame(mjCBody* body, std::string name) const{
// find spec by name
mjSpec* mjCModel::FindSpec(std::string name) const {
for (auto spec : specs_) {
if (mjs_getString(spec->modelname) == name) {
return spec;
}
}
return nullptr;
}
// detect null pose // detect null pose
bool mjCModel::IsNullPose(const mjtNum* pos, const mjtNum* quat) const { bool mjCModel::IsNullPose(const mjtNum* pos, const mjtNum* quat) const {
bool result = true; bool result = true;
@@ -3247,6 +3265,12 @@ mjModel* mjCModel::Compile(const mjVFS* vfs, mjModel** m) {
return nullptr; return nullptr;
} }
// destroy attached specs
for (auto spec : specs_) {
mj_deleteSpec(spec);
}
specs_.clear();
// restore error handler, mark as compiled, return mjModel // restore error handler, mark as compiled, return mjModel
_mjPRIVATE__set_tls_error_fn(save_error); _mjPRIVATE__set_tls_error_fn(save_error);
_mjPRIVATE__set_tls_warning_fn(save_warning); _mjPRIVATE__set_tls_warning_fn(save_warning);
+4 -1
View File
@@ -192,7 +192,7 @@ class mjCModel : public mjCModel_, private mjSpec {
mjCTuple* AddTuple(); mjCTuple* AddTuple();
mjCKey* AddKey(); mjCKey* AddKey();
mjCPlugin* AddPlugin(); mjCPlugin* AddPlugin();
void AppendSpec(mjSpec* spec);
// delete elements marked as discard=true // delete elements marked as discard=true
template <class T> void Delete(std::vector<T*>& elements, template <class T> void Delete(std::vector<T*>& elements,
@@ -209,12 +209,14 @@ class mjCModel : public mjCModel_, private mjSpec {
// API for access to other variables // API for access to other variables
bool IsCompiled() const; // is model already compiled bool IsCompiled() const; // is model already compiled
const mjCError& GetError() const; // get reference of error object const mjCError& GetError() const; // get reference of error object
void SetError(const mjCError& error) { errInfo = error; } // set value of error object
mjCBody* GetWorld(); // pointer to world body mjCBody* GetWorld(); // pointer to world body
mjCDef* FindDefault(std::string name); // find defaults class name mjCDef* FindDefault(std::string name); // find defaults class name
mjCDef* AddDefault(std::string name, mjCDef* parent = nullptr); // add defaults class to array mjCDef* AddDefault(std::string name, mjCDef* parent = nullptr); // add defaults class to array
mjCBase* FindObject(mjtObj type, std::string name) const; // find object given type and name mjCBase* FindObject(mjtObj type, std::string name) const; // find object given type and name
mjCBody* FindBody(mjCBody* body, std::string name); // find body given name mjCBody* FindBody(mjCBody* body, std::string name); // find body given name
mjCFrame* FindFrame(mjCBody* body, std::string name) const; // find frame given name mjCFrame* FindFrame(mjCBody* body, std::string name) const; // find frame given name
mjSpec* FindSpec(std::string name) const; // find spec given name
bool IsNullPose(const mjtNum* pos, const mjtNum* quat) const; // detect null pose bool IsNullPose(const mjtNum* pos, const mjtNum* quat) const; // detect null pose
void SetActivePlugins(const std::vector<std::pair<const mjpPlugin*, int>>&& active_plugins) { void SetActivePlugins(const std::vector<std::pair<const mjpPlugin*, int>>&& active_plugins) {
active_plugins_ = std::move(active_plugins); active_plugins_ = std::move(active_plugins);
@@ -312,6 +314,7 @@ class mjCModel : public mjCModel_, private mjSpec {
std::vector<mjCTuple*> tuples_; // list of tuple fields std::vector<mjCTuple*> tuples_; // list of tuple fields
std::vector<mjCKey*> keys_; // list of keyframe fields std::vector<mjCKey*> keys_; // list of keyframe fields
std::vector<mjCPlugin*> plugins_; // list of plugin instances std::vector<mjCPlugin*> plugins_; // list of plugin instances
std::vector<mjSpec*> specs_; // list of specs
// pointers to objects created inside kinematic tree // pointers to objects created inside kinematic tree
std::vector<mjCBody*> bodies_; // list of bodies std::vector<mjCBody*> bodies_; // list of bodies
+15
View File
@@ -697,6 +697,9 @@ void mjCBase::NameSpace(const mjCModel* m) {
if (!name.empty()) { if (!name.empty()) {
name = m->prefix + name + m->suffix; name = m->prefix + name + m->suffix;
} }
if (!classname.empty() && m != model) {
classname = m->prefix + classname + m->suffix;
}
} }
@@ -1000,6 +1003,9 @@ void mjCBody::NameSpace_(const mjCModel* m, bool propagate) {
if (!name.empty()) { if (!name.empty()) {
name = m->prefix + name + m->suffix; name = m->prefix + name + m->suffix;
} }
if (!classname.empty() && m != model) {
classname = m->prefix + classname + m->suffix;
}
for (auto& body : bodies) { for (auto& body : bodies) {
body->prefix = m->prefix; body->prefix = m->prefix;
@@ -2004,6 +2010,9 @@ void mjCGeom::NameSpace(const mjCModel* m) {
if (!name.empty()) { if (!name.empty()) {
name = m->prefix + name + m->suffix; name = m->prefix + name + m->suffix;
} }
if (!classname.empty() && m != model) {
classname = m->prefix + classname + m->suffix;
}
if (!spec_material_.empty() && model != m) { if (!spec_material_.empty() && model != m) {
spec_material_ = m->prefix + spec_material_ + m->suffix; spec_material_ = m->prefix + spec_material_ + m->suffix;
} }
@@ -2768,6 +2777,9 @@ void mjCCamera::NameSpace(const mjCModel* m) {
if (!name.empty()) { if (!name.empty()) {
name = m->prefix + name + m->suffix; name = m->prefix + name + m->suffix;
} }
if (!classname.empty() && m != model) {
classname = m->prefix + classname + m->suffix;
}
if (!spec_targetbody_.empty()) { if (!spec_targetbody_.empty()) {
spec_targetbody_ = m->prefix + spec_targetbody_ + m->suffix; spec_targetbody_ = m->prefix + spec_targetbody_ + m->suffix;
} }
@@ -2917,6 +2929,9 @@ void mjCLight::NameSpace(const mjCModel* m) {
if (!name.empty()) { if (!name.empty()) {
name = m->prefix + name + m->suffix; name = m->prefix + name + m->suffix;
} }
if (!classname.empty() && m != model) {
classname = m->prefix + classname + m->suffix;
}
if (!spec_targetbody_.empty()) { if (!spec_targetbody_.empty()) {
spec_targetbody_ = m->prefix + spec_targetbody_ + m->suffix; spec_targetbody_ = m->prefix + spec_targetbody_ + m->suffix;
} }
+1 -1
View File
@@ -369,7 +369,7 @@ mjSpec* mjParseXML(const char* filename, const mjVFS* vfs,
// parse MuJoCo model // parse MuJoCo model
parser.SetModel(spec); parser.SetModel(spec);
parser.Parse(root); parser.Parse(root, vfs);
} }
else if (!strcasecmp(root->Value(), "robot")) { else if (!strcasecmp(root->Value(), "robot")) {
+2 -1
View File
@@ -19,6 +19,7 @@
#include <string> #include <string>
#include "tinyxml2.h" #include "tinyxml2.h"
#include <mujoco/mjmodel.h>
#include <mujoco/mjspec.h> #include <mujoco/mjspec.h>
#include "xml/xml_util.h" #include "xml/xml_util.h"
@@ -79,7 +80,7 @@ class mjXBase : public mjXUtil {
virtual ~mjXBase() = default; virtual ~mjXBase() = default;
// parse: implemented in derived parser classes // parse: implemented in derived parser classes
virtual void Parse(tinyxml2::XMLElement* root) {}; virtual void Parse(tinyxml2::XMLElement* root, const mjVFS* vfs = nullptr) {};
// write: implemented in derived writer class // write: implemented in derived writer class
virtual std::string Write(char *error, std::size_t error_sz) { virtual std::string Write(char *error, std::size_t error_sz) {
+59 -5
View File
@@ -14,6 +14,7 @@
#include "xml/xml_native_reader.h" #include "xml/xml_native_reader.h"
#include <array>
#include <cstddef> #include <cstddef>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
@@ -38,6 +39,7 @@
#include "engine/engine_util_errmem.h" #include "engine/engine_util_errmem.h"
#include "engine/engine_util_misc.h" #include "engine/engine_util_misc.h"
#include <mujoco/mjspec.h> #include <mujoco/mjspec.h>
#include "user/user_api.h"
#include "user/user_composite.h" #include "user/user_composite.h"
#include "user/user_flexcomp.h" #include "user/user_flexcomp.h"
#include "user/user_util.h" #include "user/user_util.h"
@@ -243,6 +245,7 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
"hflip", "vflip"}, "hflip", "vflip"},
{"material", "*", "12", "name", "class", "texture", "texrepeat", "texuniform", {"material", "*", "12", "name", "class", "texture", "texrepeat", "texuniform",
"emission", "specular", "shininess", "reflectance", "metallic", "roughness", "rgba"}, "emission", "specular", "shininess", "reflectance", "metallic", "roughness", "rgba"},
{"model", "*", "2", "name", "file"},
{">"}, {">"},
{"body", "R", "11", "name", "childclass", "pos", "quat", "mocap", {"body", "R", "11", "name", "childclass", "pos", "quat", "mocap",
@@ -267,6 +270,7 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
{"config", "*", "2", "key", "value"}, {"config", "*", "2", "key", "value"},
{">"}, {">"},
{">"}, {">"},
{"attach", "*", "3", "model", "body", "prefix"},
{"site", "*", "15", "name", "class", "type", "group", "pos", "quat", {"site", "*", "15", "name", "class", "type", "group", "pos", "quat",
"material", "size", "fromto", "axisangle", "xyaxes", "zaxis", "euler", "rgba", "user"}, "material", "size", "fromto", "axisangle", "xyaxes", "zaxis", "euler", "rgba", "user"},
{"camera", "*", "20", "name", "class", "orthographic", "fovy", "ipd", "resolution", "pos", {"camera", "*", "20", "name", "class", "orthographic", "fovy", "ipd", "resolution", "pos",
@@ -805,7 +809,7 @@ void mjXReader::PrintSchema(std::stringstream& str, bool html, bool pad) {
// main entry point for XML parser // main entry point for XML parser
// mjCModel is allocated here; caller is responsible for deallocation // mjCModel is allocated here; caller is responsible for deallocation
void mjXReader::Parse(XMLElement* root) { void mjXReader::Parse(XMLElement* root, const mjVFS* vfs) {
// check schema // check schema
if (!schema.GetError().empty()) { if (!schema.GetError().empty()) {
throw mjXError(0, "XML Schema Construction Error: %s\n", throw mjXError(0, "XML Schema Construction Error: %s\n",
@@ -880,7 +884,7 @@ void mjXReader::Parse(XMLElement* root) {
for (XMLElement* section = FirstChildElement(root, "asset"); section; for (XMLElement* section = FirstChildElement(root, "asset"); section;
section = NextSiblingElement(section, "asset")) { section = NextSiblingElement(section, "asset")) {
Asset(section); Asset(section, vfs);
} }
for (XMLElement* section = FirstChildElement(root, "contact"); section; for (XMLElement* section = FirstChildElement(root, "contact"); section;
@@ -3045,7 +3049,7 @@ void mjXReader::Visual(XMLElement* section) {
// asset section parser // asset section parser
void mjXReader::Asset(XMLElement* section) { void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) {
int n; int n;
string text, name, texname, content_type; string text, name, texname, content_type;
XMLElement* elem; XMLElement* elem;
@@ -3209,6 +3213,27 @@ void mjXReader::Asset(XMLElement* section) {
} }
} }
// model sub-element
else if (name=="model") {
auto filename = modelfiledir_ + ReadAttrFile(elem, "file", "").value();
// parse the child
std::array<char, 1024> error;
mjSpec* child = mj_parseXML(filename.c_str(), vfs, error.data(), error.size());
if (!child) {
throw mjXError(elem, "could not parse model file with error: %s", error.data());
}
// overwrite model name if given
std::string modelname = "";
if (ReadAttrTxt(elem, "name", modelname)) {
mjs_setString(child->modelname, modelname.c_str());
}
// store child spec in model
mjs_addSpec(model, child);
}
// advance to next element // advance to next element
elem = NextSiblingElement(elem); elem = NextSiblingElement(elem);
} }
@@ -3437,8 +3462,8 @@ void mjXReader::Body(XMLElement* section, mjsBody* pbody, mjsFrame* frame) {
Body(elem, subtree, pframe); Body(elem, subtree, pframe);
// attach to parent // attach to parent
if (mjs_attachFrame(pbody, pframe, /*prefix=*/"", suffix.c_str()) < 0) { if (mjs_attachFrame(pbody, pframe, /*prefix=*/"", suffix.c_str()) != 0) {
throw mjXError(elem, "failed to attach frame"); throw mjXError(elem, mjs_getError(model));
} }
} }
@@ -3496,6 +3521,35 @@ void mjXReader::Body(XMLElement* section, mjsBody* pbody, mjsFrame* frame) {
Body(elem, pchild, nullptr); Body(elem, pchild, nullptr);
} }
// attachment
else if (name=="attach") {
std::string model_name, body_name, prefix;
ReadAttrTxt(elem, "model", model_name);
ReadAttrTxt(elem, "body", body_name);
ReadAttrTxt(elem, "prefix", prefix);
mjsBody* child = mjs_findBody(model, (prefix+body_name).c_str());
mjsFrame* pframe = frame ? frame : mjs_addFrame(pbody, nullptr);
if (!child) {
mjSpec* asset = mjs_findSpec(model, model_name.c_str());
if (!asset) {
throw mjXError(0, "could not find model '%s'", model_name.c_str());
}
child = mjs_findBody(asset, body_name.c_str());
if (!child) {
throw mjXError(0, "could not find body '%s''%s'", body_name.c_str());
}
if (mjs_attachBody(pframe, child, prefix.c_str(), "") != 0) {
mj_deleteSpec(asset);
throw mjXError(elem, mjs_getError(model));
}
} else {
// only set frame to existing body
mjs_setFrame(child->element, pframe);
}
}
// no match // no match
else { else {
throw mjXError(elem, "unrecognized model element '%s'", name.c_str()); throw mjXError(elem, "unrecognized model element '%s'", name.c_str());
+3 -3
View File
@@ -30,7 +30,7 @@ class mjXReader : public mjXBase {
mjXReader(); // constructor mjXReader(); // constructor
virtual ~mjXReader() = default; // destructor virtual ~mjXReader() = default; // destructor
void Parse(tinyxml2::XMLElement* root); // parse XML document void Parse(tinyxml2::XMLElement* root, const mjVFS* vfs = nullptr); // parse XML document
void PrintSchema(std::stringstream& str, bool html, bool pad); // print text or HTML schema void PrintSchema(std::stringstream& str, bool html, bool pad); // print text or HTML schema
void SetModelFileDir(std::string modelfiledir); void SetModelFileDir(std::string modelfiledir);
@@ -53,7 +53,7 @@ class mjXReader : public mjXBase {
void Custom(tinyxml2::XMLElement* section); // custom section void Custom(tinyxml2::XMLElement* section); // custom section
void Visual(tinyxml2::XMLElement* section); // visual section void Visual(tinyxml2::XMLElement* section); // visual section
void Statistic(tinyxml2::XMLElement* section); // statistic section void Statistic(tinyxml2::XMLElement* section); // statistic section
void Asset(tinyxml2::XMLElement* section); // asset section void Asset(tinyxml2::XMLElement* section, const mjVFS* vfs); // asset section
void Body(tinyxml2::XMLElement* section, mjsBody* pbody, void Body(tinyxml2::XMLElement* section, mjsBody* pbody,
mjsFrame* pframe); // body/world section mjsFrame* pframe); // body/world section
void Contact(tinyxml2::XMLElement* section); // contact section void Contact(tinyxml2::XMLElement* section); // contact section
@@ -99,7 +99,7 @@ class mjXReader : public mjXBase {
}; };
// MJCF schema // MJCF schema
#define nMJCF 230 #define nMJCF 232
extern const char* MJCF[nMJCF][mjXATTRNUM]; extern const char* MJCF[nMJCF][mjXATTRNUM];
#endif // MUJOCO_SRC_XML_XML_NATIVE_READER_H_ #endif // MUJOCO_SRC_XML_XML_NATIVE_READER_H_
+1 -1
View File
@@ -364,7 +364,7 @@ void mjXURDF::Body(XMLElement* body_elem) {
} }
} }
void mjXURDF::Parse(XMLElement* root) { void mjXURDF::Parse(XMLElement* root, const mjVFS* vfs) {
double pos[3] = {0}; double pos[3] = {0};
mjuu_setvec(pos, 0, 0, 0); mjuu_setvec(pos, 0, 0, 0);
double quat[4] = {1, 0, 0, 0}; double quat[4] = {1, 0, 0, 0};
+2 -1
View File
@@ -19,6 +19,7 @@
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
#include <mujoco/mjmodel.h>
#include <mujoco/mjspec.h> #include <mujoco/mjspec.h>
#include "xml/xml_base.h" #include "xml/xml_base.h"
#include "tinyxml2.h" #include "tinyxml2.h"
@@ -41,7 +42,7 @@ class mjXURDF : public mjXBase {
double* pos, double* pos,
double* quat, double* quat,
bool static_body); bool static_body);
void Parse(tinyxml2::XMLElement* root); // main parser void Parse(tinyxml2::XMLElement* root, const mjVFS* vfs = nullptr); // main parser
private: private:
std::string GetPrefixedName(const std::string& name); // get prefix/name of element std::string GetPrefixedName(const std::string& name); // get prefix/name of element
+6 -6
View File
@@ -502,8 +502,8 @@ TEST_F(MujocoTest, AttachDifferent) {
<joint type="hinge" name="attached-hinge-1"/> <joint type="hinge" name="attached-hinge-1"/>
<geom class="attached-cylinder-1" material="attached-material-1"/> <geom class="attached-cylinder-1" material="attached-material-1"/>
<light mode="targetbody" target="attached-targetbody-1"/> <light mode="targetbody" target="attached-targetbody-1"/>
<body name="attached-targetbody-1"/> <body name="attached-targetbody-1"/>
<body/> <body/>
</body> </body>
</frame> </frame>
</body> </body>
@@ -618,8 +618,8 @@ TEST_F(MujocoTest, AttachFrame) {
<joint type="hinge" name="attached-hinge-1"/> <joint type="hinge" name="attached-hinge-1"/>
<geom class="attached-cylinder-1" material="attached-material-1"/> <geom class="attached-cylinder-1" material="attached-material-1"/>
<light mode="targetbody" target="attached-targetbody-1"/> <light mode="targetbody" target="attached-targetbody-1"/>
<body name="attached-targetbody-1"/> <body name="attached-targetbody-1"/>
<body/> <body/>
</body> </body>
</frame> </frame>
</frame> </frame>
@@ -650,7 +650,7 @@ TEST_F(MujocoTest, AttachFrame) {
mjSpec* parent = mj_parseXMLString(xml_parent, 0, er.data(), er.size()); mjSpec* parent = mj_parseXMLString(xml_parent, 0, er.data(), er.size());
EXPECT_THAT(parent, NotNull()) << er.data(); EXPECT_THAT(parent, NotNull()) << er.data();
// get frame // get body
mjsBody* body = mjs_findBody(parent, "sphere"); mjsBody* body = mjs_findBody(parent, "sphere");
EXPECT_THAT(body, NotNull()); EXPECT_THAT(body, NotNull());
@@ -662,7 +662,7 @@ TEST_F(MujocoTest, AttachFrame) {
mjsFrame* frame = mjs_findFrame(child, "pframe"); mjsFrame* frame = mjs_findFrame(child, "pframe");
EXPECT_THAT(frame, NotNull()); EXPECT_THAT(frame, NotNull());
// attach child to parent frame // attach child frame to parent body
EXPECT_THAT( EXPECT_THAT(
mjs_attachFrame(body, frame, /*prefix=*/"attached-", /*suffix=*/"-1"), 0); mjs_attachFrame(body, frame, /*prefix=*/"attached-", /*suffix=*/"-1"), 0);
+7
View File
@@ -0,0 +1,7 @@
<mujoco model="child">
<worldbody>
<body name="body">
<geom name="geom" size="1" pos="2 0 0"/>
</body>
</worldbody>
</mujoco>
+11
View File
@@ -0,0 +1,11 @@
<mujoco>
<asset>
<model name="other" file="child.xml"/>
</asset>
<worldbody>
<body name="parent">
<geom name="geom" size="2"/>
<attach model="other" body="body" prefix="other"/>
</body>
</worldbody>
</mujoco>
+141 -1
View File
@@ -1054,6 +1054,7 @@ TEST_F(XMLReaderTest, DuplicateFrameName) {
// ---------------------- test replicate parsing ------------------------------- // ---------------------- test replicate parsing -------------------------------
TEST_F(XMLReaderTest, ParseReplicate) { TEST_F(XMLReaderTest, ParseReplicate) {
static constexpr char xml[] = R"( static constexpr char xml[] = R"(
<mujoco> <mujoco>
@@ -1253,7 +1254,146 @@ TEST_F(XMLReaderTest, ParseReplicateRepeatedName) {
std::array<char, 1024> error; std::array<char, 1024> error;
mjSpec* spec = mj_parseXMLString(xml, 0, error.data(), error.size()); mjSpec* spec = mj_parseXMLString(xml, 0, error.data(), error.size());
EXPECT_THAT(spec, IsNull()) << error.data(); EXPECT_THAT(spec, IsNull()) << error.data();
EXPECT_THAT(error.data(), HasSubstr("failed to attach frame")); EXPECT_THAT(error.data(), HasSubstr("repeated name 'b' in actuator"));
EXPECT_THAT(error.data(), HasSubstr("Element 'replicate'"));
}
// ---------------------- test spec assets parsing -----------------------------
TEST_F(XMLReaderTest, ParseSpecAssets) {
std::array<char, 1024> er;
static const char* const kParentPath =
"xml/testdata/parent.xml";
static const char* const kChildPath =
"xml/testdata/child.xml";
const std::string xml_parent = GetTestDataFilePath(kParentPath);
const std::string xml_child = GetTestDataFilePath(kChildPath);
mjModel* parent = mj_loadXML(xml_parent.c_str(), 0, er.data(), er.size());
EXPECT_THAT(parent, NotNull()) << er.data();
mjModel* child = mj_loadXML(xml_child.c_str(), 0, er.data(), er.size());
EXPECT_THAT(child, NotNull()) << er.data();
mj_deleteModel(parent);
mj_deleteModel(child);
}
TEST_F(XMLReaderTest, AttachSpecAssets) {
static constexpr char xml_parent[] = R"(
<mujoco>
<asset>
<model name="child" file="xml_child.xml"/>
</asset>
<worldbody>
<body name="parent">
<geom name="geom" size="2"/>
<replicate count="2" offset="0 0 1">
<attach model="child" body="body" prefix="other"/>
</replicate>
</body>
</worldbody>
</mujoco>
)";
static constexpr char xml_child[] = R"(
<mujoco>
<worldbody>
<body name="body">
<geom name="geom" size="1" pos="2 0 0"/>
</body>
</worldbody>
</mujoco>
)";
static constexpr char xml_expected[] = R"(
<mujoco>
<worldbody>
<body name="parent">
<geom name="geom" size="2"/>
<body name="otherbody0">
<geom name="othergeom0" size="1" pos="2 0 0"/>
</body>
<body name="otherbody1" pos="0 0 1">
<geom name="othergeom1" size="1" pos="2 0 0"/>
</body>
</body>
</worldbody>
</mujoco>
)";
auto vfs = std::make_unique<mjVFS>();
mj_defaultVFS(vfs.get());
mj_addBufferVFS(vfs.get(), "xml_child.xml", xml_child, sizeof(xml_child));
std::array<char, 1024> er;
mjModel* model =
LoadModelFromString(xml_parent, er.data(), er.size(), vfs.get());
EXPECT_THAT(model, NotNull()) << er.data();
mjModel* expected = LoadModelFromString(xml_expected, er.data(), er.size());
EXPECT_THAT(expected, NotNull()) << er.data();
mjtNum tol = 0;
std::string field = "";
EXPECT_LE(CompareModel(model, expected, field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';;
mj_deleteModel(model);
mj_deleteModel(expected);
mj_deleteVFS(vfs.get());
}
TEST_F(XMLReaderTest, InvalidAttach) {
static constexpr char xml_parent[] = R"(
<mujoco>
<asset>
<model name="other" file="child.xml"/>
</asset>
<worldbody>
<body name="parent">
<joint name="joint1"/>
<geom size="2"/>
<attach model="other" body="body"/>
</body>
</worldbody>
<actuator>
<motor name="actuator" joint="joint1"/>
</actuator>
</mujoco>
)";
static constexpr char xml_child[] = R"(
<mujoco model="child">
<worldbody>
<body name="body">
<joint name="joint2"/>
<geom size="1" pos="2 0 0"/>
</body>
</worldbody>
<actuator>
<motor name="actuator" joint="joint2"/>
</actuator>
</mujoco>
)";
auto vfs = std::make_unique<mjVFS>();
mj_defaultVFS(vfs.get());
mj_addBufferVFS(vfs.get(), "child.xml", xml_child, sizeof(xml_child));
std::array<char, 1024> er;
mjModel* model =
LoadModelFromString(xml_parent, er.data(), er.size(), vfs.get());
EXPECT_THAT(model, IsNull()) << er.data();
EXPECT_THAT(er.data(), HasSubstr("repeated name 'actuator' in actuator"));
EXPECT_THAT(er.data(), HasSubstr("Element 'attach'"));
mj_deleteVFS(vfs.get());
} }
// ----------------------- test camera parsing --------------------------------- // ----------------------- test camera parsing ---------------------------------