diff --git a/doc/XMLreference.rst b/doc/XMLreference.rst index 72842f40..4975845c 100644 --- a/doc/XMLreference.rst +++ b/doc/XMLreference.rst @@ -3996,33 +3996,50 @@ Associate this body with an :ref:`engine plugin`. Either :at:`plugin` :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`, 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`. 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 +first be defined as an :ref:`asset`. When creating an attachment, a frame, body or the entire child model in the +child model is specified, and all referencing elements outside the kinematic tree (e.g., sensors and actuators), are also copied into +the parent model. Additionally, any elements referenced from within the attached subtree (e.g. defaults and assets) +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 -:ref:`attachment` functionality. As such, it shares the same limitations as described there. In addition, -when the :el:`attach` element is used, it is not possible to attach an entire model (i.e. including all elements, -referenced or not). +:ref:`attachment` functionality. As such, it shares the same limitations as described there. See example `here +`__. + +.. 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`, 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: :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: :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 - specified, the contents of the world body will be attached in a new :ref:`frame`. + Name of the body in the child model to attach here. The body and its subtree will be attached. If neither this + attribute nor :ref:`frame` is specified (only one allowed), the contents of the world body will + be attached in a new :ref:`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` is + specified (only one allowed), the contents of the world body will be attached in a new :ref:`frame`. .. _body-attach-prefix: :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. diff --git a/doc/XMLschema.rst b/doc/XMLschema.rst old mode 100755 new mode 100644 index f15c15f7..a85dabd2 --- a/doc/XMLschema.rst +++ b/doc/XMLschema.rst @@ -983,6 +983,9 @@ .. grid-item:: :ref:`body` + .. grid-item:: + :ref:`frame` + .. grid-item:: :ref:`prefix` diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index 6b396efc..d298148a 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -470,7 +470,7 @@ std::vector MJCF[nMJCF] = { {"config", "*", "key", "value"}, {">"}, {">"}, - {"attach", "*", "model", "body", "prefix"}, + {"attach", "*", "model", "body", "frame", "prefix"}, {"site", "*", "name", "class", "type", "group", "pos", "quat", "material", "size", "fromto", "axisangle", "xyaxes", "zaxis", "euler", "rgba", "user"}, {"camera", "*", "name", "class", "projection", @@ -4170,35 +4170,40 @@ void mjXReader::Body(XMLElement* section, mjsBody* body, mjsFrame* frame, // attachment 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, "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); - 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); - if (!child_body) { + if (!child) { mjSpec* asset = mjs_findSpec(spec, model_name.c_str()); if (!asset) { throw mjXError(elem, "could not find model '%s'", model_name.c_str()); } - mjsElement* child; - if (body_name.empty()) { + if (child_name.empty()) { child = asset->element; } else { - child_body = mjs_findBody(asset, body_name.c_str()); - if (!child_body) { - throw mjXError(elem, "could not find body '%s''%s'", body_name.c_str()); + child = mjs_findElement(asset, type, child_name.c_str()); + if (!child) { + 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(), "")) { throw mjXError(elem, "%s", stripError(mjs_getError(spec))); } } else { // 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))); } } diff --git a/test/xml/testdata/child.xml b/test/xml/testdata/child.xml index 2d63790b..305b681a 100644 --- a/test/xml/testdata/child.xml +++ b/test/xml/testdata/child.xml @@ -1,7 +1,18 @@ - + + + + + + + + + + + + diff --git a/test/xml/testdata/parent.xml b/test/xml/testdata/parent.xml index f0c112b8..8cd9085f 100644 --- a/test/xml/testdata/parent.xml +++ b/test/xml/testdata/parent.xml @@ -3,9 +3,23 @@ + - - + + + + + + + + + + + + + + +