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
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:`composite<body-composite>` and :ref:`flexcomp<body-flexcomp>` which are part of the schema, but serve to
procedurally generate other MJCF elements.
- :ref:`composite<body-composite>`, :ref:`flexcomp<body-flexcomp>` and :ref:`attach<body-attach>` which are part of the
schema, but serve to procedurally generate other MJCF elements.
.. _frame:
@@ -1739,6 +1739,24 @@ properties are grouped together.
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:
**(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.
.. _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:
: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.
.. _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:
:el-prefix:`body/` |-| **frame** (*)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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.
+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>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| 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:: |
| :ref:`body | R | :class: mjcf-attributes |
| <body>` | | |
@@ -261,6 +268,13 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| 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 |
| <body-site>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+15 -12
View File
@@ -39,27 +39,30 @@ General
:align: right
: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>`
attributes, respectively.
8. 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 support for ``ball`` joints in the URDF parser ("spherical" in URDF).
11. Replaced ``mjUSEDOUBLE`` which was previously hard-coded in
9. Added :ref:`maxhullvert<asset-mesh-maxhullvert>`, the maximum number of vertices in a mesh's convex hull.
10. Added :ref:`mj_setKeyframe` for saving the current state into a model keyframe.
11. Added support for ``ball`` joints in the URDF parser ("spherical" in URDF).
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>`__
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`.
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
``mjData->qpos`` will be normalized.
MJX
~~~
13. 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.
15. Fixed a bug in the Newton solver that sometimes resulted in less-optimal gradients.
15. Added support for :ref:`elliptic friction cones<option-cone>`.
16. Fixed a bug that resulted in less-optimal linesearch solutions for some difficult constraint settings.
17. Fixed a bug in the Newton solver that sometimes resulted in less-optimal gradients.
.. youtube:: P83tKA1iz2Y
@@ -68,14 +71,14 @@ MJX
Simulate
^^^^^^^^
16. Added improved tutorial video.
17. Improved the Brownian noise generator.
18. Added improved tutorial video.
19. Improved the Brownian noise generator.
|br| |br| |br| |br|
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)
---------------------------
+6 -193
View File
@@ -20,82 +20,11 @@
<asset>
<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"
rgb1="0.8 0.6 0.4" rgb2="0.8 0.6 0.4" markrgb="1 1 1"/>
<material name="body" texture="body" texuniform="true" rgba="0.8 0.6 .4 1"/>
<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"/>
<texture name="floor" type="2d" builtin="checker" width="512" height="512" rgb1=".1 .2 .3" rgb2=".2 .3 .4"/>
<material name="floor" texture="floor" texrepeat="1 1" texuniform="true" reflectance=".2"/>
<model name="humanoid" file="humanoid.xml"/>
</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>
<map force="0.1" zfar="30"/>
<rgba haze="0.15 0.25 0.35 1"/>
@@ -104,127 +33,11 @@
</visual>
<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 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="-">
<body name="torso" pos="-5 0 1.282" childclass="body">
<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 count="22" euler="0 0 16.36" sep="-" offset="1 0 0">
<attach model="humanoid" body="torso" prefix="_"/>
</replicate>
</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>
+18
View File
@@ -101,6 +101,7 @@ int mjs_attachBody(mjsFrame* parent, const mjsBody* child,
try {
*frame_parent += std::string(prefix) + *child_body + std::string(suffix);
} catch (mjCError& e) {
frame_parent->model->SetError(e);
return -1;
}
return 0;
@@ -116,6 +117,7 @@ int mjs_attachFrame(mjsBody* parent, const mjsFrame* child,
try {
*body_parent += std::string(prefix) + *child_frame + std::string(suffix);
} catch (mjCError& e) {
body_parent->model->SetError(e);
return -1;
}
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
void mjs_delete(mjsElement* 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
mjsDefault* mjs_getDefault(mjsElement* element) {
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.
MJAPI void mj_deleteSpec(mjSpec* s);
// Add spec (model asset) to spec.
MJAPI void mjs_addSpec(mjSpec* s, mjSpec* child);
//---------------------------------- Attachment ----------------------------------------------------
@@ -182,6 +185,9 @@ MJAPI mjsMaterial* mjs_addMaterial(mjSpec* s, mjsDefault* def);
// Get spec from 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.
MJAPI mjsBody* mjs_findBody(mjSpec* s, const char* name);
+25 -1
View File
@@ -16,6 +16,7 @@
#include <algorithm>
#include <cmath>
#include <array>
#include <csetjmp>
#include <cstddef>
#include <cstdint>
@@ -33,7 +34,6 @@
#include <mujoco/mjspec.h>
#include <mujoco/mjtnum.h>
#include <mujoco/mjplugin.h>
#include <mujoco/mjvisualize.h>
#include "cc/array_safety.h"
#include "engine/engine_forward.h"
#include "engine/engine_io.h"
@@ -338,6 +338,7 @@ mjCModel& mjCModel::operator-=(const mjCBody& subtree) {
// add default tree to this model
mjCModel_& mjCModel::operator+=(mjCDef& 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
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 ---------------------------------------
@@ -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
bool mjCModel::IsNullPose(const mjtNum* pos, const mjtNum* quat) const {
bool result = true;
@@ -3247,6 +3265,12 @@ mjModel* mjCModel::Compile(const mjVFS* vfs, mjModel** m) {
return nullptr;
}
// destroy attached specs
for (auto spec : specs_) {
mj_deleteSpec(spec);
}
specs_.clear();
// restore error handler, mark as compiled, return mjModel
_mjPRIVATE__set_tls_error_fn(save_error);
_mjPRIVATE__set_tls_warning_fn(save_warning);
+4 -1
View File
@@ -192,7 +192,7 @@ class mjCModel : public mjCModel_, private mjSpec {
mjCTuple* AddTuple();
mjCKey* AddKey();
mjCPlugin* AddPlugin();
void AppendSpec(mjSpec* spec);
// delete elements marked as discard=true
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
bool IsCompiled() const; // is model already compiled
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
mjCDef* FindDefault(std::string name); // find defaults class name
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
mjCBody* FindBody(mjCBody* body, std::string name); // find body 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
void SetActivePlugins(const std::vector<std::pair<const mjpPlugin*, int>>&& 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<mjCKey*> keys_; // list of keyframe fields
std::vector<mjCPlugin*> plugins_; // list of plugin instances
std::vector<mjSpec*> specs_; // list of specs
// pointers to objects created inside kinematic tree
std::vector<mjCBody*> bodies_; // list of bodies
+15
View File
@@ -697,6 +697,9 @@ void mjCBase::NameSpace(const mjCModel* m) {
if (!name.empty()) {
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()) {
name = m->prefix + name + m->suffix;
}
if (!classname.empty() && m != model) {
classname = m->prefix + classname + m->suffix;
}
for (auto& body : bodies) {
body->prefix = m->prefix;
@@ -2004,6 +2010,9 @@ void mjCGeom::NameSpace(const mjCModel* m) {
if (!name.empty()) {
name = m->prefix + name + m->suffix;
}
if (!classname.empty() && m != model) {
classname = m->prefix + classname + m->suffix;
}
if (!spec_material_.empty() && model != m) {
spec_material_ = m->prefix + spec_material_ + m->suffix;
}
@@ -2768,6 +2777,9 @@ void mjCCamera::NameSpace(const mjCModel* m) {
if (!name.empty()) {
name = m->prefix + name + m->suffix;
}
if (!classname.empty() && m != model) {
classname = m->prefix + classname + m->suffix;
}
if (!spec_targetbody_.empty()) {
spec_targetbody_ = m->prefix + spec_targetbody_ + m->suffix;
}
@@ -2917,6 +2929,9 @@ void mjCLight::NameSpace(const mjCModel* m) {
if (!name.empty()) {
name = m->prefix + name + m->suffix;
}
if (!classname.empty() && m != model) {
classname = m->prefix + classname + m->suffix;
}
if (!spec_targetbody_.empty()) {
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
parser.SetModel(spec);
parser.Parse(root);
parser.Parse(root, vfs);
}
else if (!strcasecmp(root->Value(), "robot")) {
+2 -1
View File
@@ -19,6 +19,7 @@
#include <string>
#include "tinyxml2.h"
#include <mujoco/mjmodel.h>
#include <mujoco/mjspec.h>
#include "xml/xml_util.h"
@@ -79,7 +80,7 @@ class mjXBase : public mjXUtil {
virtual ~mjXBase() = default;
// 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
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 <array>
#include <cstddef>
#include <cstdio>
#include <cstring>
@@ -38,6 +39,7 @@
#include "engine/engine_util_errmem.h"
#include "engine/engine_util_misc.h"
#include <mujoco/mjspec.h>
#include "user/user_api.h"
#include "user/user_composite.h"
#include "user/user_flexcomp.h"
#include "user/user_util.h"
@@ -243,6 +245,7 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
"hflip", "vflip"},
{"material", "*", "12", "name", "class", "texture", "texrepeat", "texuniform",
"emission", "specular", "shininess", "reflectance", "metallic", "roughness", "rgba"},
{"model", "*", "2", "name", "file"},
{">"},
{"body", "R", "11", "name", "childclass", "pos", "quat", "mocap",
@@ -267,6 +270,7 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
{"config", "*", "2", "key", "value"},
{">"},
{">"},
{"attach", "*", "3", "model", "body", "prefix"},
{"site", "*", "15", "name", "class", "type", "group", "pos", "quat",
"material", "size", "fromto", "axisangle", "xyaxes", "zaxis", "euler", "rgba", "user"},
{"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
// mjCModel is allocated here; caller is responsible for deallocation
void mjXReader::Parse(XMLElement* root) {
void mjXReader::Parse(XMLElement* root, const mjVFS* vfs) {
// check schema
if (!schema.GetError().empty()) {
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;
section = NextSiblingElement(section, "asset")) {
Asset(section);
Asset(section, vfs);
}
for (XMLElement* section = FirstChildElement(root, "contact"); section;
@@ -3045,7 +3049,7 @@ void mjXReader::Visual(XMLElement* section) {
// asset section parser
void mjXReader::Asset(XMLElement* section) {
void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) {
int n;
string text, name, texname, content_type;
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
elem = NextSiblingElement(elem);
}
@@ -3437,8 +3462,8 @@ void mjXReader::Body(XMLElement* section, mjsBody* pbody, mjsFrame* frame) {
Body(elem, subtree, pframe);
// attach to parent
if (mjs_attachFrame(pbody, pframe, /*prefix=*/"", suffix.c_str()) < 0) {
throw mjXError(elem, "failed to attach frame");
if (mjs_attachFrame(pbody, pframe, /*prefix=*/"", suffix.c_str()) != 0) {
throw mjXError(elem, mjs_getError(model));
}
}
@@ -3496,6 +3521,35 @@ void mjXReader::Body(XMLElement* section, mjsBody* pbody, mjsFrame* frame) {
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
else {
throw mjXError(elem, "unrecognized model element '%s'", name.c_str());
+3 -3
View File
@@ -30,7 +30,7 @@ class mjXReader : public mjXBase {
mjXReader(); // constructor
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 SetModelFileDir(std::string modelfiledir);
@@ -53,7 +53,7 @@ class mjXReader : public mjXBase {
void Custom(tinyxml2::XMLElement* section); // custom section
void Visual(tinyxml2::XMLElement* section); // visual 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,
mjsFrame* pframe); // body/world section
void Contact(tinyxml2::XMLElement* section); // contact section
@@ -99,7 +99,7 @@ class mjXReader : public mjXBase {
};
// MJCF schema
#define nMJCF 230
#define nMJCF 232
extern const char* MJCF[nMJCF][mjXATTRNUM];
#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};
mjuu_setvec(pos, 0, 0, 0);
double quat[4] = {1, 0, 0, 0};
+2 -1
View File
@@ -19,6 +19,7 @@
#include <unordered_set>
#include <vector>
#include <mujoco/mjmodel.h>
#include <mujoco/mjspec.h>
#include "xml/xml_base.h"
#include "tinyxml2.h"
@@ -41,7 +42,7 @@ class mjXURDF : public mjXBase {
double* pos,
double* quat,
bool static_body);
void Parse(tinyxml2::XMLElement* root); // main parser
void Parse(tinyxml2::XMLElement* root, const mjVFS* vfs = nullptr); // main parser
private:
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"/>
<geom class="attached-cylinder-1" material="attached-material-1"/>
<light mode="targetbody" target="attached-targetbody-1"/>
<body name="attached-targetbody-1"/>
<body/>
<body name="attached-targetbody-1"/>
<body/>
</body>
</frame>
</body>
@@ -618,8 +618,8 @@ TEST_F(MujocoTest, AttachFrame) {
<joint type="hinge" name="attached-hinge-1"/>
<geom class="attached-cylinder-1" material="attached-material-1"/>
<light mode="targetbody" target="attached-targetbody-1"/>
<body name="attached-targetbody-1"/>
<body/>
<body name="attached-targetbody-1"/>
<body/>
</body>
</frame>
</frame>
@@ -650,7 +650,7 @@ TEST_F(MujocoTest, AttachFrame) {
mjSpec* parent = mj_parseXMLString(xml_parent, 0, er.data(), er.size());
EXPECT_THAT(parent, NotNull()) << er.data();
// get frame
// get body
mjsBody* body = mjs_findBody(parent, "sphere");
EXPECT_THAT(body, NotNull());
@@ -662,7 +662,7 @@ TEST_F(MujocoTest, AttachFrame) {
mjsFrame* frame = mjs_findFrame(child, "pframe");
EXPECT_THAT(frame, NotNull());
// attach child to parent frame
// attach child frame to parent body
EXPECT_THAT(
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_F(XMLReaderTest, ParseReplicate) {
static constexpr char xml[] = R"(
<mujoco>
@@ -1253,7 +1254,146 @@ TEST_F(XMLReaderTest, ParseReplicateRepeatedName) {
std::array<char, 1024> error;
mjSpec* spec = mj_parseXMLString(xml, 0, error.data(), error.size());
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 ---------------------------------