Add "frame" attribute to <attach> element.
The `<attach>` element now supports a "frame" attribute, allowing users to specify either a "body" or a "frame" to attach to, but not both. The XML parser has been updated to handle this new attribute and the mutual exclusivity constraint. PiperOrigin-RevId: 941245904 Change-Id: I4c0edeed5f3a9e456aed96c373b1647fe79841d9
This commit is contained in:
committed by
Copybara-Service
parent
1f6cf4035c
commit
c6c3ec3149
+29
-12
@@ -3996,33 +3996,50 @@ Associate this body with an :ref:`engine plugin<exPlugin>`. Either :at:`plugin`
|
|||||||
:el-prefix:`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.
|
The :el:`attach` element is used to insert elements from another (child) model into this (parent) model's kinematic tree.
|
||||||
Unlike :ref:`include<include>`, which is implemented in the parser and is equivalent to copying and pasting XML from one
|
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
|
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<asset-model>`. When creating an attachment, the top body of the attached subtree is
|
first be defined as an :ref:`asset<asset-model>`. When creating an attachment, a frame, body or the entire child model in the
|
||||||
specified, and all referencing elements outside the kinematic tree (e.g., sensors and actuators), are also copied into
|
child model 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)
|
the parent 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
|
will be copied in to the parent model. :el:`attach` is a :ref:`meta-element`, so upon saving all attachments will
|
||||||
appear in the saved XML file. Note that this element is a subset of the functionality of the procedural
|
appear in the saved XML file. Note that this element is a subset of the functionality of the procedural
|
||||||
:ref:`attachment<meAttachment>` functionality. As such, it shares the same limitations as described there. In addition,
|
:ref:`attachment<meAttachment>` functionality. As such, it shares the same limitations as described there. See example `here
|
||||||
when the :el:`attach` element is used, it is not possible to attach an entire model (i.e. including all elements,
|
<https://github.com/google-deepmind/mujoco/blob/main/test/xml/testdata/parent.xml>`__.
|
||||||
referenced or not).
|
|
||||||
|
.. admonition:: Known issues
|
||||||
|
:class: note
|
||||||
|
|
||||||
|
The following known limitations exist, to be addressed in a future release:
|
||||||
|
|
||||||
|
- All assets from the child model will be copied in, whether they are referenced or not.
|
||||||
|
- Circular references are not checked for and will lead to infinite loops.
|
||||||
|
- When attaching a model with :ref:`keyframes<keyframe>`, model compilation is required for the re-indexing to be
|
||||||
|
finalized. If a second attachment is performed without compilation, the keyframes from the first attachment will be
|
||||||
|
lost.
|
||||||
|
|
||||||
.. _body-attach-model:
|
.. _body-attach-model:
|
||||||
|
|
||||||
:at:`model`: :at-val:`string, required`
|
:at:`model`: :at-val:`string, required`
|
||||||
The sub-model from which to attach a subtree.
|
The child model from which to attach a subtree or a frame.
|
||||||
|
|
||||||
.. _body-attach-body:
|
.. _body-attach-body:
|
||||||
|
|
||||||
:at:`body`: :at-val:`string, optional`
|
: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. If this attribute is not
|
Name of the body in the child model to attach here. The body and its subtree will be attached. If neither this
|
||||||
specified, the contents of the world body will be attached in a new :ref:`frame<body-frame>`.
|
attribute nor :ref:`frame<body-attach-frame>` is specified (only one allowed), the contents of the world body will
|
||||||
|
be attached in a new :ref:`frame<body-frame>`.
|
||||||
|
|
||||||
|
.. _body-attach-frame:
|
||||||
|
|
||||||
|
:at:`frame`: :at-val:`string, optional`
|
||||||
|
Name of the frame in the child model to attach here. If neither this attribute nor :ref:`body<body-attach-body>` is
|
||||||
|
specified (only one allowed), the contents of the world body will be attached in a new :ref:`frame<body-frame>`.
|
||||||
|
|
||||||
.. _body-attach-prefix:
|
.. _body-attach-prefix:
|
||||||
|
|
||||||
:at:`prefix`: :at-val:`string, required`
|
:at:`prefix`: :at-val:`string, required`
|
||||||
Prefix to prepend to names of elements in the sub-model. This attribute is required to prevent name collisions with
|
Prefix to prepend to names of elements in the child model. This attribute is required to prevent name collisions with
|
||||||
the parent or when attaching the same sub-tree multiple times.
|
the parent or when attaching the same sub-tree multiple times.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Executable → Regular
+3
@@ -983,6 +983,9 @@
|
|||||||
.. grid-item::
|
.. grid-item::
|
||||||
:ref:`body<body-attach-body>`
|
:ref:`body<body-attach-body>`
|
||||||
|
|
||||||
|
.. grid-item::
|
||||||
|
:ref:`frame<body-attach-frame>`
|
||||||
|
|
||||||
.. grid-item::
|
.. grid-item::
|
||||||
:ref:`prefix<body-attach-prefix>`
|
:ref:`prefix<body-attach-prefix>`
|
||||||
|
|
||||||
|
|||||||
@@ -470,7 +470,7 @@ std::vector<const char*> MJCF[nMJCF] = {
|
|||||||
{"config", "*", "key", "value"},
|
{"config", "*", "key", "value"},
|
||||||
{">"},
|
{">"},
|
||||||
{">"},
|
{">"},
|
||||||
{"attach", "*", "model", "body", "prefix"},
|
{"attach", "*", "model", "body", "frame", "prefix"},
|
||||||
{"site", "*", "name", "class", "type", "group", "pos", "quat", "material",
|
{"site", "*", "name", "class", "type", "group", "pos", "quat", "material",
|
||||||
"size", "fromto", "axisangle", "xyaxes", "zaxis", "euler", "rgba", "user"},
|
"size", "fromto", "axisangle", "xyaxes", "zaxis", "euler", "rgba", "user"},
|
||||||
{"camera", "*", "name", "class", "projection",
|
{"camera", "*", "name", "class", "projection",
|
||||||
@@ -4170,35 +4170,40 @@ void mjXReader::Body(XMLElement* section, mjsBody* body, mjsFrame* frame,
|
|||||||
|
|
||||||
// attachment
|
// attachment
|
||||||
else if (name == "attach") {
|
else if (name == "attach") {
|
||||||
string model_name, body_name, prefix;
|
string model_name, child_name, prefix;
|
||||||
ReadAttrTxt(elem, "model", model_name, /*required=*/true);
|
ReadAttrTxt(elem, "model", model_name, /*required=*/true);
|
||||||
ReadAttrTxt(elem, "body", body_name, /*required=*/false);
|
bool has_body = ReadAttrTxt(elem, "body", child_name, /*required=*/false);
|
||||||
|
bool has_frame = ReadAttrTxt(elem, "frame", child_name, /*required=*/false);
|
||||||
ReadAttrTxt(elem, "prefix", prefix, /*required=*/true);
|
ReadAttrTxt(elem, "prefix", prefix, /*required=*/true);
|
||||||
|
|
||||||
mjsBody* child_body = mjs_findBody(spec, (prefix+body_name).c_str());
|
if (has_body && has_frame) {
|
||||||
|
throw mjXError(elem, "only one of body or frame can be specified in attach");
|
||||||
|
}
|
||||||
|
mjtObj type = has_body ? mjOBJ_BODY : mjOBJ_FRAME;
|
||||||
|
|
||||||
|
mjsElement* child = mjs_findElement(spec, type, (prefix+child_name).c_str());
|
||||||
mjsFrame* pframe = frame ? frame : mjs_addFrame(body, nullptr);
|
mjsFrame* pframe = frame ? frame : mjs_addFrame(body, nullptr);
|
||||||
|
|
||||||
if (!child_body) {
|
if (!child) {
|
||||||
mjSpec* asset = mjs_findSpec(spec, model_name.c_str());
|
mjSpec* asset = mjs_findSpec(spec, model_name.c_str());
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
throw mjXError(elem, "could not find model '%s'", model_name.c_str());
|
throw mjXError(elem, "could not find model '%s'", model_name.c_str());
|
||||||
}
|
}
|
||||||
mjsElement* child;
|
if (child_name.empty()) {
|
||||||
if (body_name.empty()) {
|
|
||||||
child = asset->element;
|
child = asset->element;
|
||||||
} else {
|
} else {
|
||||||
child_body = mjs_findBody(asset, body_name.c_str());
|
child = mjs_findElement(asset, type, child_name.c_str());
|
||||||
if (!child_body) {
|
if (!child) {
|
||||||
throw mjXError(elem, "could not find body '%s''%s'", body_name.c_str());
|
throw mjXError(elem, "could not find %s",
|
||||||
|
(string(mju_type2Str(type)) + " '" + child_name + "'").c_str());
|
||||||
}
|
}
|
||||||
child = child_body->element;
|
|
||||||
}
|
}
|
||||||
if (!mjs_attach(pframe->element, child, prefix.c_str(), "")) {
|
if (!mjs_attach(pframe->element, child, prefix.c_str(), "")) {
|
||||||
throw mjXError(elem, "%s", stripError(mjs_getError(spec)));
|
throw mjXError(elem, "%s", stripError(mjs_getError(spec)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// only set frame to existing body
|
// only set frame to existing body
|
||||||
if (mjs_setFrame(child_body->element, pframe)) {
|
if (mjs_setFrame(child, pframe)) {
|
||||||
throw mjXError(elem, "%s", stripError(mjs_getError(spec)));
|
throw mjXError(elem, "%s", stripError(mjs_getError(spec)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+12
-1
@@ -1,7 +1,18 @@
|
|||||||
<mujoco model="child">
|
<mujoco model="child">
|
||||||
<worldbody>
|
<worldbody>
|
||||||
<body name="body">
|
<body name="body">
|
||||||
<geom name="geom" size="1" pos="2 0 0"/>
|
<geom name="geom" size="1" pos="0 0 -2" rgba="1 0 0 1"/>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
<frame name="frame">
|
||||||
|
<body name="body2" pos="-1 0 0">
|
||||||
|
<freejoint/>
|
||||||
|
<geom name="geom2" type="box" size="1 1 1" rgba="0 1 0 1"/>
|
||||||
|
</body>
|
||||||
|
<body name="body3" pos="1 0 0">
|
||||||
|
<freejoint/>
|
||||||
|
<geom name="geom3" type="box" size="1 1 1" rgba="0 1 0 1"/>
|
||||||
|
</body>
|
||||||
|
</frame>
|
||||||
</worldbody>
|
</worldbody>
|
||||||
</mujoco>
|
</mujoco>
|
||||||
|
|||||||
Vendored
+16
-2
@@ -3,9 +3,23 @@
|
|||||||
<model name="other" file="child.xml"/>
|
<model name="other" file="child.xml"/>
|
||||||
</asset>
|
</asset>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
|
<!-- attach body to body -->
|
||||||
<body name="parent">
|
<body name="parent">
|
||||||
<geom name="geom" size="2"/>
|
<geom name="geom" size="1" pos="0 0 2"/>
|
||||||
<attach model="other" body="body" prefix="other"/>
|
<attach model="other" body="body" prefix="other1"/>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
<!-- attach frame to body (world) -->
|
||||||
|
<attach model="other" frame="frame" prefix="other2"/>
|
||||||
|
|
||||||
|
<!-- attach frame to frame -->
|
||||||
|
<frame name="top" pos="0 0 4">
|
||||||
|
<attach model="other" frame="frame" prefix="other3"/>
|
||||||
|
</frame>
|
||||||
|
|
||||||
|
<!-- attach body to frame -->
|
||||||
|
<frame name="bottom-right" pos="2 0 -1">
|
||||||
|
<attach model="other" body="body" prefix="other4"/>
|
||||||
|
</frame>
|
||||||
</worldbody>
|
</worldbody>
|
||||||
</mujoco>
|
</mujoco>
|
||||||
|
|||||||
Reference in New Issue
Block a user