diff --git a/doc/APIreference/functions.rst b/doc/APIreference/functions.rst index 627b6871..029fac76 100644 --- a/doc/APIreference/functions.rst +++ b/doc/APIreference/functions.rst @@ -3808,41 +3808,14 @@ Free all pointers with ``mju_free()``. Attachment ^^^^^^^^^^ -.. _mjs_attachBody: +.. _mjs_attach: -`mjs_attachBody <#mjs_attachBody>`__ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`mjs_attach <#mjs_attach>`__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. mujoco-include:: mjs_attachBody +.. mujoco-include:: mjs_attach -Attach child body to a parent frame, return the attached body if success or NULL otherwise. - -.. _mjs_attachFrame: - -`mjs_attachFrame <#mjs_attachFrame>`__ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. mujoco-include:: mjs_attachFrame - -Attach child frame to a parent body, return the attached frame if success or NULL otherwise. - -.. _mjs_attachToSite: - -`mjs_attachToSite <#mjs_attachToSite>`__ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. mujoco-include:: mjs_attachToSite - -Attach child body to a parent site, return the attached body if success or NULL otherwise. - -.. _mjs_attachFrameToSite: - -`mjs_attachFrameToSite <#mjs_attachFrameToSite>`__ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. mujoco-include:: mjs_attachFrameToSite - -Attach child frame to a parent site, return the attached frame if success or NULL otherwise. +Attach child to a parent, return the attached element if success or NULL otherwise. .. _mjs_detachBody: diff --git a/doc/XMLreference.rst b/doc/XMLreference.rst index 1c1854bb..1f0c6b99 100644 --- a/doc/XMLreference.rst +++ b/doc/XMLreference.rst @@ -175,11 +175,10 @@ replicating 200 times, suffixes will be ``000, 001, ...`` etc). All referencing and namespaced appropriately. Detailed examples of models using replicate can be found in the `model/replicate/ `__ directory. -There is a caveat concerning :ref:`keyframes` when using replicate. Since :ref:`mjs_attachFrame` is used to +There is a caveat concerning :ref:`keyframes` when using replicate. Since :ref:`mjs_attach` is used to self-attach multiple times the enclosed kinematic tree, if this tree contains further :ref:`attach` elements, keyframes will not be replicated nor namespaced by :ref:`replicate`, but they will be attached and -namespaced once by the innermost call of :ref:`mjs_attachFrame` or :ref:`mjs_attachBody`. See the limitations discussed -in :ref:`attach`. +namespaced once by the innermost call of :ref:`mjs_attach`. See the limitations discussed in :ref:`attach`. .. _replicate-count: diff --git a/doc/changelog.rst b/doc/changelog.rst index ad3f10cc..4af1fae5 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -10,6 +10,8 @@ Upcoming version (not yet released) - The default value of the flag for toggling :ref:`internal flex contacts` was changed from "true" to "false". This feature has proven to be counterintuitive for users. + - All of the attach functions (``mjs_attachBody``, ``mjs_attachFrame``, ``mjs_attachToSite``, + ``mjs_attachFrameToSite``) have been removed and replaced by a single function :ref:`mjs_attach`. General ^^^^^^^ diff --git a/doc/includes/references.h b/doc/includes/references.h index d3e7ba18..8d1cf9c8 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -3613,14 +3613,8 @@ void mju_threadPoolEnqueue(mjThreadPool* thread_pool, mjTask* task); void mju_threadPoolDestroy(mjThreadPool* thread_pool); void mju_defaultTask(mjTask* task); void mju_taskJoin(mjTask* task); -mjsBody* mjs_attachBody(mjsFrame* parent, const mjsBody* child, - const char* prefix, const char* suffix); -mjsFrame* mjs_attachFrame(mjsBody* parent, const mjsFrame* child, - const char* prefix, const char* suffix); -mjsBody* mjs_attachToSite(mjsSite* parent, const mjsBody* child, - const char* prefix, const char* suffix); -mjsFrame* mjs_attachFrameToSite(mjsSite* parent, const mjsFrame* child, - const char* prefix, const char* suffix); +mjsElement* mjs_attach(mjsElement* parent, const mjsElement* child, + const char* prefix, const char* suffix); int mjs_detachBody(mjSpec* s, mjsBody* b); int mjs_detachDefault(mjSpec* s, mjsDefault* d); mjsBody* mjs_addBody(mjsBody* body, const mjsDefault* def); diff --git a/doc/programming/modeledit.rst b/doc/programming/modeledit.rst index cec8f03d..ac74efdf 100644 --- a/doc/programming/modeledit.rst +++ b/doc/programming/modeledit.rst @@ -111,9 +111,10 @@ This framework introduces a powerful new feature: attaching and detaching model to power the :ref:`attach` an :ref:`replicate` meta-elements in MJCF. Attachment allows the user to move or copy a subtree from one model into another, while also copying or moving related referenced assets and referencing elements from outside the kinematic tree (e.g., actuators and sensors). Similarly, detaching a subtree will -remove all associated elements from the model. The default behavior is to move during attach. The user can select to -instead copy by passing the corresponding flag to ``mjs_setDeepCopy``. This flag is temporary set to true while parsing -XMLs. It is possible to :ref:`attach a body to a frame`: +remove all associated elements from the model. The default behavior is to move the child into the parent while +attaching, so subsequent changes to the child will also change the parent. Alternatively, the user can choose to make an +entirely new copy during attach using :ref:`mjs_setDeepCopy`. This flag is temporarily set to true while parsing XMLs. +It is possible to :ref:`attach a body to a frame`: .. code-block:: C @@ -121,29 +122,29 @@ XMLs. It is possible to :ref:`attach a body to a frame`: mjSpec* child = mj_makeSpec(); parent->compiler.degree = 0; child->compiler.degree = 1; - mjsFrame* frame = mjs_addFrame(mjs_findBody(parent, "world"), NULL); - mjsBody* body = mjs_addBody(mjs_findBody(child, "world"), NULL); - mjsBody* attached_body_1 = mjs_attachBody(frame, body, "attached-", "-1"); + mjsElement* frame = mjs_addFrame(mjs_findBody(parent, "world"), NULL)->element; + mjsElement* body = mjs_addBody(mjs_findBody(child, "world"), NULL)->element; + mjsBody* attached_body_1 = mjs_asBody(mjs_attach(frame, body, "attached-", "-1")); -or :ref:`attach a body to a site`: +or :ref:`attach a body to a site`: .. code-block:: C mjSpec* parent = mj_makeSpec(); mjSpec* child = mj_makeSpec(); - mjsSite* site = mjs_addSite(mjs_findBody(parent, "world"), NULL); - mjsBody* body = mjs_addBody(mjs_findBody(child, "world"), NULL); - mjsBody* attached_body_2 = mjs_attachToSite(site, body, "attached-", "-2"); + mjsElement* site = mjs_addSite(mjs_findBody(parent, "world"), NULL)->element; + mjsElement* body = mjs_addBody(mjs_findBody(child, "world"), NULL)->element; + mjsBody* attached_body_2 = mjs_asBody(mjs_attach(site, body, "attached-", "-2")); -or :ref:`attach a frame to a body`: +or :ref:`attach a frame to a body`: .. code-block:: C mjSpec* parent = mj_makeSpec(); mjSpec* child = mj_makeSpec(); - mjsBody* body = mjs_addBody(mjs_findBody(parent, "world"), NULL); - mjsFrame* frame = mjs_addFrame(mjs_findBody(child, "world"), NULL); - mjsFrame* attached_frame = mjs_attachFrame(body, frame, "attached-", "-1"); + mjsElement* body = mjs_addBody(mjs_findBody(parent, "world"), NULL)->element; + mjsElement* frame = mjs_addFrame(mjs_findBody(child, "world"), NULL)->element; + mjsFrame* attached_frame = mjs_asFrame(mjs_attach(body, frame, "attached-", "-1")); Note that in the above examples, the parent and child models have different values for ``compiler.degree``, corresponding to the :ref:`compiler/angle` attribute, specifying the units in which angles are diff --git a/include/mujoco/mujoco.h b/include/mujoco/mujoco.h index 84496910..c17c7e47 100644 --- a/include/mujoco/mujoco.h +++ b/include/mujoco/mujoco.h @@ -1414,21 +1414,9 @@ MJAPI void mju_taskJoin(mjTask* task); //---------------------------------- Attachment ---------------------------------------------------- -// Attach child body to a parent frame, return the attached body if success or NULL otherwise. -MJAPI mjsBody* mjs_attachBody(mjsFrame* parent, const mjsBody* child, - const char* prefix, const char* suffix); - -// Attach child frame to a parent body, return the attached frame if success or NULL otherwise. -MJAPI mjsFrame* mjs_attachFrame(mjsBody* parent, const mjsFrame* child, - const char* prefix, const char* suffix); - -// Attach child body to a parent site, return the attached body if success or NULL otherwise. -MJAPI mjsBody* mjs_attachToSite(mjsSite* parent, const mjsBody* child, - const char* prefix, const char* suffix); - -// Attach child frame to a parent site, return the attached frame if success or NULL otherwise. -MJAPI mjsFrame* mjs_attachFrameToSite(mjsSite* parent, const mjsFrame* child, - const char* prefix, const char* suffix); +// Attach child to a parent, return the attached element if success or NULL otherwise. +MJAPI mjsElement* mjs_attach(mjsElement* parent, const mjsElement* child, + const char* prefix, const char* suffix); // Delete body and descendants from mjSpec, remove all references, return 0 on success. MJAPI int mjs_detachBody(mjSpec* s, mjsBody* b); diff --git a/python/mujoco/introspect/functions.py b/python/mujoco/introspect/functions.py index d2955e80..9e61b828 100644 --- a/python/mujoco/introspect/functions.py +++ b/python/mujoco/introspect/functions.py @@ -9000,23 +9000,23 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([ ), doc='Wait for a task to complete.', )), - ('mjs_attachBody', + ('mjs_attach', FunctionDecl( - name='mjs_attachBody', + name='mjs_attach', return_type=PointerType( - inner_type=ValueType(name='mjsBody'), + inner_type=ValueType(name='mjsElement'), ), parameters=( FunctionParameterDecl( name='parent', type=PointerType( - inner_type=ValueType(name='mjsFrame'), + inner_type=ValueType(name='mjsElement'), ), ), FunctionParameterDecl( name='child', type=PointerType( - inner_type=ValueType(name='mjsBody', is_const=True), + inner_type=ValueType(name='mjsElement', is_const=True), ), ), FunctionParameterDecl( @@ -9032,109 +9032,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([ ), ), ), - doc='Attach child body to a parent frame, return the attached body if success or NULL otherwise.', # pylint: disable=line-too-long - )), - ('mjs_attachFrame', - FunctionDecl( - name='mjs_attachFrame', - return_type=PointerType( - inner_type=ValueType(name='mjsFrame'), - ), - parameters=( - FunctionParameterDecl( - name='parent', - type=PointerType( - inner_type=ValueType(name='mjsBody'), - ), - ), - FunctionParameterDecl( - name='child', - type=PointerType( - inner_type=ValueType(name='mjsFrame', is_const=True), - ), - ), - FunctionParameterDecl( - name='prefix', - type=PointerType( - inner_type=ValueType(name='char', is_const=True), - ), - ), - FunctionParameterDecl( - name='suffix', - type=PointerType( - inner_type=ValueType(name='char', is_const=True), - ), - ), - ), - doc='Attach child frame to a parent body, return the attached frame if success or NULL otherwise.', # pylint: disable=line-too-long - )), - ('mjs_attachToSite', - FunctionDecl( - name='mjs_attachToSite', - return_type=PointerType( - inner_type=ValueType(name='mjsBody'), - ), - parameters=( - FunctionParameterDecl( - name='parent', - type=PointerType( - inner_type=ValueType(name='mjsSite'), - ), - ), - FunctionParameterDecl( - name='child', - type=PointerType( - inner_type=ValueType(name='mjsBody', is_const=True), - ), - ), - FunctionParameterDecl( - name='prefix', - type=PointerType( - inner_type=ValueType(name='char', is_const=True), - ), - ), - FunctionParameterDecl( - name='suffix', - type=PointerType( - inner_type=ValueType(name='char', is_const=True), - ), - ), - ), - doc='Attach child body to a parent site, return the attached body if success or NULL otherwise.', # pylint: disable=line-too-long - )), - ('mjs_attachFrameToSite', - FunctionDecl( - name='mjs_attachFrameToSite', - return_type=PointerType( - inner_type=ValueType(name='mjsFrame'), - ), - parameters=( - FunctionParameterDecl( - name='parent', - type=PointerType( - inner_type=ValueType(name='mjsSite'), - ), - ), - FunctionParameterDecl( - name='child', - type=PointerType( - inner_type=ValueType(name='mjsFrame', is_const=True), - ), - ), - FunctionParameterDecl( - name='prefix', - type=PointerType( - inner_type=ValueType(name='char', is_const=True), - ), - ), - FunctionParameterDecl( - name='suffix', - type=PointerType( - inner_type=ValueType(name='char', is_const=True), - ), - ), - ), - doc='Attach child frame to a parent site, return the attached frame if success or NULL otherwise.', # pylint: disable=line-too-long + doc='Attach child to a parent, return the attached element if success or NULL otherwise.', # pylint: disable=line-too-long )), ('mjs_detachBody', FunctionDecl( diff --git a/python/mujoco/specs.cc b/python/mujoco/specs.cc index 64afef62..62f4efe8 100644 --- a/python/mujoco/specs.cc +++ b/python/mujoco/specs.cc @@ -540,7 +540,7 @@ PYBIND11_MODULE(_specs, m) { SetFrame(worldbody, mjOBJ_CAMERA, worldframe); const char* p = prefix.has_value() ? prefix.value().c_str() : ""; const char* s = suffix.has_value() ? suffix.value().c_str() : ""; - raw::MjsFrame* attached_frame = nullptr; + raw::MjsElement* attached_frame = nullptr; if (frame.has_value()) { raw::MjsFrame* frame_ptr = nullptr; try { @@ -560,11 +560,12 @@ PYBIND11_MODULE(_specs, m) { if (!parent_body) { throw pybind11::value_error("Frame does not have a parent body."); } - attached_frame = mjs_attachFrame(parent_body, worldframe, p, s); + attached_frame = + mjs_attach(parent_body->element, worldframe->element, p, s); if (!attached_frame) { throw pybind11::value_error(mjs_getError(self.ptr)); } - if (mjs_setFrame(attached_frame->element, frame_ptr) != 0) { + if (mjs_setFrame(attached_frame, frame_ptr) != 0) { throw pybind11::value_error(mjs_getError(self.ptr)); } } @@ -583,7 +584,8 @@ PYBIND11_MODULE(_specs, m) { throw pybind11::value_error( "Site spec does not match parent spec."); } - attached_frame = mjs_attachFrameToSite(site_ptr, worldframe, p, s); + attached_frame = + mjs_attach(site_ptr->element, worldframe->element, p, s); if (!attached_frame) { throw pybind11::value_error(mjs_getError(self.ptr)); } @@ -597,7 +599,7 @@ PYBIND11_MODULE(_specs, m) { self.assets[asset.first] = asset.second; } child.parent = &self; - return attached_frame; + return mjs_asFrame(attached_frame); }, py::arg("child"), py::arg("prefix") = py::none(), py::arg("suffix") = py::none(), py::arg("site") = py::none(), @@ -829,11 +831,11 @@ PYBIND11_MODULE(_specs, m) { std::optional& suffix) -> raw::MjsFrame* { const char* p = prefix.has_value() ? prefix.value().c_str() : ""; const char* s = suffix.has_value() ? suffix.value().c_str() : ""; - auto new_frame = mjs_attachFrame(&self, &frame, p, s); + auto new_frame = mjs_attach(self.element, frame.element, p, s); if (!new_frame) { throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element))); } - return new_frame; + return mjs_asFrame(new_frame); }, py::arg("frame"), py::arg("prefix") = py::none(), py::arg("suffix") = py::none(), @@ -870,12 +872,12 @@ PYBIND11_MODULE(_specs, m) { std::optional& suffix) -> raw::MjsBody* { const char* p = prefix.has_value() ? prefix.value().c_str() : ""; const char* s = suffix.has_value() ? suffix.value().c_str() : ""; - auto new_body = mjs_attachBody(&self, &body, p, s); + auto new_body = mjs_attach(self.element, body.element, p, s); if (!new_body) { throw pybind11::value_error( mjs_getError(mjs_getSpec(self.element))); } - return new_body; + return mjs_asBody(new_body); }, py::arg("body"), py::arg("prefix") = py::none(), py::arg("suffix") = py::none(), @@ -953,12 +955,12 @@ PYBIND11_MODULE(_specs, m) { std::optional& suffix) -> raw::MjsBody* { const char* p = prefix.has_value() ? prefix.value().c_str() : ""; const char* s = suffix.has_value() ? suffix.value().c_str() : ""; - auto new_body = mjs_attachToSite(&self, &body, p, s); + auto new_body = mjs_attach(self.element, body.element, p, s); if (!new_body) { throw pybind11::value_error( mjs_getError(mjs_getSpec(self.element))); } - return new_body; + return mjs_asBody(new_body); }, py::arg("body"), py::arg("prefix") = py::none(), py::arg("suffix") = py::none(), diff --git a/src/user/user_api.cc b/src/user/user_api.cc index 646bb986..a3d90e3a 100644 --- a/src/user/user_api.cc +++ b/src/user/user_api.cc @@ -73,7 +73,7 @@ mjSpec* mj_copySpec(const mjSpec* s) { try { modelC = new mjCModel(*static_cast(s->element)); } catch (mjCError& e) { - mju_error("Failed to copy spec: %s", e.message); + modelC->SetError(e); return nullptr; } return &modelC->spec; @@ -121,100 +121,125 @@ mjModel* mj_compile(mjSpec* s, const mjVFS* vfs) { // attach body to a frame of the parent -mjsBody* mjs_attachBody(mjsFrame* parent, const mjsBody* child, - const char* prefix, const char* suffix) { - if (!parent) { - mju_error("parent frame is null"); - return nullptr; - } - mjCFrame* frame_parent = static_cast(parent->element); - mjCBody* child_body = static_cast(child->element); +static mjsElement* attachBody(mjCFrame* parent, const mjCBody* child, + const char* prefix, const char* suffix) { try { - *frame_parent += std::string(prefix) + *child_body + std::string(suffix); + *parent += std::string(prefix) + *(mjCBody*)child + std::string(suffix); } catch (mjCError& e) { - frame_parent->model->SetError(e); + parent->model->SetError(e); return nullptr; } - mjsBody* attached_body = frame_parent->last_attached; - frame_parent->last_attached = nullptr; - return attached_body; + mjsBody* attached_body = parent->last_attached; + parent->last_attached = nullptr; + return attached_body->element; } // attach frame to a parent body -mjsFrame* mjs_attachFrame(mjsBody* parent, const mjsFrame* child, - const char* prefix, const char* suffix) { - if (!parent) { - mju_error("parent body is null"); - return nullptr; - } - mjCBody* body_parent = static_cast(parent->element); - mjCFrame* child_frame = static_cast(child->element); +static mjsElement* attachFrame(mjCBody* parent, const mjCFrame* child, + const char* prefix, const char* suffix) { try { - *body_parent += std::string(prefix) + *child_frame + std::string(suffix); + *parent += std::string(prefix) + *(mjCFrame*)child + std::string(suffix); } catch (mjCError& e) { - body_parent->model->SetError(e); + parent->model->SetError(e); return nullptr; } - mjsFrame* attached_frame = body_parent->last_attached; - body_parent->last_attached = nullptr; - return attached_frame; + mjsFrame* attached_frame = parent->last_attached; + parent->last_attached = nullptr; + return attached_frame->element; } // attach child body to a parent site -mjsBody* mjs_attachToSite(mjsSite* parent, const mjsBody* child, - const char* prefix, const char* suffix) { - if (!parent) { - mju_error("parent site is null"); - return nullptr; - } - mjSpec* spec = mjs_getSpec(parent->element); - mjCSite* site = static_cast(parent->element); - mjCBody* body = site->Body(); - mjCFrame* frame = body->AddFrame(site->frame); +static mjsElement* attachToSite(mjCSite* parent, const mjCBody* child, + const char* prefix, const char* suffix) { + mjSpec* spec = mjs_getSpec(parent->spec.element); + mjCBody* body = parent->Body(); + mjCFrame* frame = body->AddFrame(parent->frame); frame->SetParent(body); - frame->spec.pos[0] = site->spec.pos[0]; - frame->spec.pos[1] = site->spec.pos[1]; - frame->spec.pos[2] = site->spec.pos[2]; - frame->spec.quat[0] = site->spec.quat[0]; - frame->spec.quat[1] = site->spec.quat[1]; - frame->spec.quat[2] = site->spec.quat[2]; - frame->spec.quat[3] = site->spec.quat[3]; + frame->spec.pos[0] = parent->spec.pos[0]; + frame->spec.pos[1] = parent->spec.pos[1]; + frame->spec.pos[2] = parent->spec.pos[2]; + frame->spec.quat[0] = parent->spec.quat[0]; + frame->spec.quat[1] = parent->spec.quat[1]; + frame->spec.quat[2] = parent->spec.quat[2]; + frame->spec.quat[3] = parent->spec.quat[3]; mjs_resolveOrientation(frame->spec.quat, spec->compiler.degree, - spec->compiler.eulerseq, &site->spec.alt); - return mjs_attachBody(&frame->spec, child, prefix, suffix); + spec->compiler.eulerseq, &parent->spec.alt); + return attachBody(frame, child, prefix, suffix); } // attach child frame to a parent site -mjsFrame* mjs_attachFrameToSite(mjsSite* parent, const mjsFrame* child, - const char* prefix, const char* suffix) { +static mjsElement* attachFrameToSite(mjCSite* parent, const mjCFrame* child, + const char* prefix, const char* suffix) { + mjSpec* spec = mjs_getSpec(parent->spec.element); + mjCBody* body = parent->Body(); + mjCFrame* frame = body->AddFrame(parent->frame); + frame->SetParent(body); + frame->spec.pos[0] = parent->spec.pos[0]; + frame->spec.pos[1] = parent->spec.pos[1]; + frame->spec.pos[2] = parent->spec.pos[2]; + frame->spec.quat[0] = parent->spec.quat[0]; + frame->spec.quat[1] = parent->spec.quat[1]; + frame->spec.quat[2] = parent->spec.quat[2]; + frame->spec.quat[3] = parent->spec.quat[3]; + mjs_resolveOrientation(frame->spec.quat, spec->compiler.degree, + spec->compiler.eulerseq, &parent->spec.alt); + + mjsElement* attached_frame = attachFrame(body, child, prefix, suffix); + mjs_setFrame(attached_frame, &frame->spec); + return attached_frame; +} + + +mjsElement* mjs_attach(mjsElement* parent, const mjsElement* child, + const char* prefix, const char* suffix) { if (!parent) { - mju_error("parent site is null"); + mju_error("parent element is null"); return nullptr; } - mjSpec* spec = mjs_getSpec(parent->element); - mjCSite* site = static_cast(parent->element); - mjCBody* body = site->Body(); - mjCFrame* frame = body->AddFrame(site->frame); - frame->SetParent(body); - frame->spec.pos[0] = site->spec.pos[0]; - frame->spec.pos[1] = site->spec.pos[1]; - frame->spec.pos[2] = site->spec.pos[2]; - frame->spec.quat[0] = site->spec.quat[0]; - frame->spec.quat[1] = site->spec.quat[1]; - frame->spec.quat[2] = site->spec.quat[2]; - frame->spec.quat[3] = site->spec.quat[3]; - mjs_resolveOrientation(frame->spec.quat, spec->compiler.degree, - spec->compiler.eulerseq, &site->spec.alt); - - mjsFrame* attached_frame = mjs_attachFrame(&body->spec, child, prefix, suffix); - mjs_setFrame(attached_frame->element, &frame->spec); - return attached_frame; + if (!child) { + mju_error("child element is null"); + return nullptr; + } + mjCModel* model = static_cast(mjs_getSpec(parent)->element); + switch (parent->elemtype) { + case mjOBJ_FRAME: + if (child->elemtype == mjOBJ_BODY) { + return attachBody(static_cast(parent), + static_cast(child), prefix, suffix); + } else { + model->SetError(mjCError(0, "child element is not a body")); + return nullptr; + } + case mjOBJ_BODY: + if (child->elemtype == mjOBJ_FRAME) { + return attachFrame(static_cast(parent), + static_cast(child), prefix, suffix); + } else { + model->SetError(mjCError(0, "child element is not a frame")); + return nullptr; + } + case mjOBJ_SITE: + if (child->elemtype == mjOBJ_BODY) { + return attachToSite(static_cast(parent), + static_cast(child), prefix, suffix); + } else if (child->elemtype == mjOBJ_FRAME) { + return attachFrameToSite(static_cast(parent), + static_cast(child), prefix, suffix); + } else { + model->SetError(mjCError(0, "child element is not a body or frame")); + return nullptr; + } + default: + model->SetError(mjCError(0, "parent element is not a frame, body or site")); + return nullptr; + } + return nullptr; } diff --git a/src/user/user_api.h b/src/user/user_api.h index 01aa9856..d01b5172 100644 --- a/src/user/user_api.h +++ b/src/user/user_api.h @@ -69,21 +69,9 @@ MJAPI int mjs_setDeepCopy(mjSpec* s, int deepcopy); //---------------------------------- Attachment ---------------------------------------------------- -// Attach child body to a parent frame, return the attached body if success or NULL otherwise. -MJAPI mjsBody* mjs_attachBody(mjsFrame* parent, const mjsBody* child, - const char* prefix, const char* suffix); - -// Attach child frame to a parent body, return the attached frame if success or NULL otherwise. -MJAPI mjsFrame* mjs_attachFrame(mjsBody* parent, const mjsFrame* child, - const char* prefix, const char* suffix); - -// Attach child body to a parent site, return the attached body if success or NULL otherwise. -MJAPI mjsBody* mjs_attachToSite(mjsSite* parent, const mjsBody* child, - const char* prefix, const char* suffix); - -// Attach child frame to a parent site, return the attached frame if success or NULL otherwise. -MJAPI mjsFrame* mjs_attachFrameToSite(mjsSite* parent, const mjsFrame* child, - const char* prefix, const char* suffix); +// Attach child to a parent, return the attached element if success or NULL otherwise. +MJAPI mjsElement* mjs_attach(mjsElement* parent, const mjsElement* child, + const char* prefix, const char* suffix); // Detach body from mjSpec, remove all references and delete the body, return 0 on success. MJAPI int mjs_detachBody(mjSpec* s, mjsBody* b); diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index 7fda10cd..94c61755 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -3566,7 +3566,7 @@ void mjXReader::Body(XMLElement* section, mjsBody* body, mjsFrame* frame, UpdateString(suffix, count, i); // attach to parent - if (!mjs_attachFrame(body, pframe, /*prefix=*/"", suffix.c_str())) { + if (!mjs_attach(body->element, pframe->element, /*prefix=*/"", suffix.c_str())) { throw mjXError(elem, mjs_getError(spec)); } } @@ -3642,7 +3642,7 @@ void mjXReader::Body(XMLElement* section, mjsBody* body, mjsFrame* frame, if (!child) { throw mjXError(elem, "could not find body '%s''%s'", body_name.c_str()); } - if (!mjs_attachBody(pframe, child, prefix.c_str(), "")) { + if (!mjs_attach(pframe->element, child->element, prefix.c_str(), "")) { throw mjXError(elem, mjs_getError(spec)); } } else { diff --git a/test/user/user_api_test.cc b/test/user/user_api_test.cc index d07f0388..c9bc6bd1 100644 --- a/test/user/user_api_test.cc +++ b/test/user/user_api_test.cc @@ -279,20 +279,23 @@ TEST_F(PluginTest, AttachPlugin) { EXPECT_THAT(body_1, NotNull()); mjsFrame* attachment_frame = mjs_addFrame(body_1, 0); EXPECT_THAT(attachment_frame, NotNull()); - mjs_attachBody(attachment_frame, mjs_findBody(spec_1, "body"), "child-", ""); + mjs_attach(attachment_frame->element, mjs_findBody(spec_1, "body")->element, + "child-", ""); mjModel* model_1 = mj_compile(parent, nullptr); EXPECT_THAT(model_1, NotNull()); EXPECT_THAT(model_1->nbody, 3); // attach it a second time to test namespacing and compile ASSERT_THAT(spec_2, NotNull()) << err.data(); - mjs_attachBody(attachment_frame, mjs_findBody(spec_2, "body"), "copy-", ""); + mjs_attach(attachment_frame->element, mjs_findBody(spec_2, "body")->element, + "copy-", ""); mjModel* model_2 = mj_compile(parent, nullptr); EXPECT_THAT(model_2, NotNull()); EXPECT_THAT(model_2->nbody, 4); // attach a body not referencing the plugin and compile - mjs_attachBody(attachment_frame, mjs_findBody(spec_3, "empty"), "empty-", ""); + mjs_attach(attachment_frame->element, mjs_findBody(spec_3, "empty")->element, + "empty-", ""); mjModel* model_3 = mj_compile(parent, nullptr); EXPECT_THAT(model_3, NotNull()); EXPECT_THAT(model_3->nbody, 5); @@ -314,9 +317,9 @@ TEST_F(PluginTest, DetachPlugin) { ASSERT_THAT(child, NotNull()) << err.data(); // attach a body referencing the plugin to the frame - mjsFrame* frame = mjs_addFrame(mjs_findBody(parent, "world"), 0); - mjsBody* body = mjs_findBody(child, "body"); - EXPECT_THAT(mjs_attachBody(frame, body, "child-", ""), NotNull()); + mjsElement* frame = mjs_addFrame(mjs_findBody(parent, "world"), 0)->element; + mjsElement* body = mjs_findBody(child, "body")->element; + EXPECT_THAT(mjs_attach(frame, body, "child-", ""), NotNull()); // detach the body and compile mjsBody* body_to_detach = mjs_findBody(parent, "child-body"); @@ -372,7 +375,8 @@ TEST_F(PluginTest, AttachExplicitPlugin) { mjsFrame* attachment_frame = mjs_addFrame(body_parent, 0); EXPECT_THAT(attachment_frame, NotNull()); - mjs_attachBody(attachment_frame, mjs_findBody(child, "body"), "child-", ""); + mjs_attach(attachment_frame->element, mjs_findBody(child, "body")->element, + "child-", ""); mjModel* model = mj_compile(parent, nullptr); EXPECT_THAT(model, NotNull()); EXPECT_THAT(model->nplugin, 1); @@ -937,7 +941,8 @@ TEST_F(MujocoTest, AttachSame) { EXPECT_THAT(body, NotNull()); // attach child to parent frame - mjsBody* attached = mjs_attachBody(frame, body, "attached-", "-1"); + mjsBody* attached = + mjs_asBody(mjs_attach(frame->element, body->element, "attached-", "-1")); EXPECT_THAT(attached, mjs_findBody(parent, "attached-body-1")); // check that the spec was not copied @@ -1073,7 +1078,8 @@ TEST_F(MujocoTest, AttachDifferent) { EXPECT_THAT(body, NotNull()); // attach child to parent frame - mjsBody* attached = mjs_attachBody(frame, body, "attached-", "-1"); + mjsBody* attached = + mjs_asBody(mjs_attach(frame->element, body->element, "attached-", "-1")); EXPECT_THAT(attached, mjs_findBody(parent, "attached-body-1")); // check that the spec was copied @@ -1212,7 +1218,8 @@ TEST_F(MujocoTest, AttachFrame) { EXPECT_THAT(frame, NotNull()); // attach child frame to parent body - mjsFrame* attached = mjs_attachFrame(body, frame, "attached-", "-1"); + mjsFrame* attached = + mjs_asFrame(mjs_attach(body->element, frame->element, "attached-", "-1")); EXPECT_THAT(attached, mjs_findFrame(parent, "attached-pframe-1")); // check that the spec was copied @@ -1286,7 +1293,7 @@ TEST_F(MujocoTest, AttachCompiled) { // attach child body to the frame mjsBody* to_attach = mjs_findBody(child, "base"); EXPECT_THAT(to_attach, NotNull()) << mjs_getError(child); - mjs_attachBody(frame, to_attach, "", ""); + mjs_attach(frame->element, to_attach->element, "", ""); // check that attached model can be compiled mjModel* m_attached = mj_compile(parent, 0); @@ -1433,7 +1440,8 @@ TEST_F(MujocoTest, AttachToSite) { EXPECT_THAT(site, NotNull()); mjsBody* body = mjs_findBody(child, "sphere"); EXPECT_THAT(body, NotNull()); - mjsBody* attached = mjs_attachToSite(site, body, "attached-", "-1"); + mjsBody* attached = + mjs_asBody(mjs_attach(site->element, body->element, "attached-", "-1")); EXPECT_THAT(attached, NotNull()); mjModel* model = mj_compile(parent, 0); @@ -1498,7 +1506,8 @@ TEST_F(MujocoTest, AttachFrameToSite) { EXPECT_THAT(site, NotNull()); mjsFrame* frame = mjs_findFrame(child, "frame"); EXPECT_THAT(frame, NotNull()); - mjsFrame* attached = mjs_attachFrameToSite(site, frame, "attached-", "-1"); + mjsFrame* attached = + mjs_asFrame(mjs_attach(site->element, frame->element, "attached-", "-1")); EXPECT_THAT(attached, NotNull()); mjModel* model = mj_compile(parent, 0); @@ -1573,7 +1582,8 @@ TEST_F(MujocoTest, BodyToFrame) { EXPECT_THAT(frame, NotNull()); mjsBody* body = mjs_findBody(child1, "sphere"); EXPECT_THAT(body, NotNull()); - mjsBody* attached = mjs_attachBody(frame, body, "attached-", "-1"); + mjsBody* attached = + mjs_asBody(mjs_attach(frame->element, body->element, "attached-", "-1")); EXPECT_THAT(attached, NotNull()); mjModel* model1 = mj_compile(parent, 0); EXPECT_THAT(model1, NotNull()); @@ -1581,7 +1591,8 @@ TEST_F(MujocoTest, BodyToFrame) { // attach the world to the same frame and convert it to a frame mjsBody* world = mjs_findBody(child2, "world"); EXPECT_THAT(world, NotNull()); - mjsBody* child_world = mjs_attachBody(frame, world, "attached-", "-2"); + mjsBody* child_world = + mjs_asBody(mjs_attach(frame->element, world->element, "attached-", "-2")); EXPECT_THAT(child_world, NotNull()); mjsFrame* frame_world = mjs_bodyToFrame(&child_world); EXPECT_THAT(frame_world, NotNull()); @@ -1662,7 +1673,8 @@ TEST_F(MujocoTest, AttachSpecToSite) { mjs_setFrame(mjs_firstChild(world, mjOBJ_CAMERA, 0), frame); // attach the entire spec to the site - mjsFrame* worldframe = mjs_attachFrameToSite(site, frame, "attached-", "-1"); + mjsFrame* worldframe = + mjs_asFrame(mjs_attach(site->element, frame->element, "attached-", "-1")); EXPECT_THAT(worldframe, NotNull()); // compile and compare @@ -1739,7 +1751,8 @@ TEST_F(MujocoTest, AttachSpecToBody) { mjs_setFrame(mjs_firstChild(world, mjOBJ_CAMERA, 0), frame); // attach the entire spec to the site - mjsFrame* worldframe = mjs_attachFrame(body, frame, "attached-", "-1"); + mjsFrame* worldframe = + mjs_asFrame(mjs_attach(body->element, frame->element, "attached-", "-1")); EXPECT_THAT(worldframe, NotNull()); worldframe->pos[0] = 1; worldframe->pos[1] = 2; @@ -1937,8 +1950,8 @@ TEST_F(MujocoTest, RecompileAttach) { mjSpec* child2 = mj_parseXMLString(xml, 0, er.data(), er.size()); EXPECT_THAT(child2, NotNull()); - mjsFrame* frame1 = mjs_addFrame(mjs_findBody(parent, "world"), 0); - mjs_attachBody(frame1, mjs_findBody(child1, "body"), "child-", "-1"); + mjsElement* frame1 = mjs_addFrame(mjs_findBody(parent, "world"), 0)->element; + mjs_attach(frame1, mjs_findBody(child1, "body")->element, "child-", "-1"); mjModel* model = mj_compile(parent, 0); EXPECT_THAT(model, NotNull()); @@ -1950,8 +1963,8 @@ TEST_F(MujocoTest, RecompileAttach) { mj_step(model, data); } - mjsFrame* frame2 = mjs_addFrame(mjs_findBody(parent, "world"), 0); - mjs_attachBody(frame2, mjs_findBody(child2, "body"), "child-", "-2"); + mjsElement* frame2 = mjs_addFrame(mjs_findBody(parent, "world"), 0)->element; + mjs_attach(frame2, mjs_findBody(child2, "body")->element, "child-", "-2"); EXPECT_EQ(mj_recompile(parent, 0, model, data), 0); EXPECT_THAT(model, NotNull()); @@ -2003,8 +2016,8 @@ TEST_F(MujocoTest, AttachMocap) { mjsBody* world = mjs_findBody(spec, "world"); EXPECT_THAT(world, NotNull()); - mjsFrame* frame = mjs_addFrame(world, NULL); - mjs_attachBody(frame, body, "attached-", "-1"); + mjsElement* frame = mjs_addFrame(world, NULL)->element; + mjs_attach(frame, body->element, "attached-", "-1"); mjsBody* attached_body = mjs_findBody(spec, "attached-mocap-1"); EXPECT_THAT(attached_body, NotNull()); @@ -2083,7 +2096,7 @@ TEST_F(MujocoTest, AttachUnnamedAssets) { geom->type = mjGEOM_MESH; mjSpec* spec = mj_makeSpec(); - mjs_attachFrame(mjs_findBody(spec, "world"), frame, "_", ""); + mjs_attach(mjs_findBody(spec, "world")->element, frame->element, "_", ""); mjModel* model = mj_compile(spec, vfs.get()); EXPECT_THAT(model, NotNull()); @@ -2229,8 +2242,8 @@ void AttachNestedKeyframe(bool compile) { mjs_setDeepCopy(child, true); // attach gchild to child - mjs_attachBody(mjs_findFrame(child, "frame"), - mjs_findBody(gchild, "body"), "gchild-", ""); + mjs_attach(mjs_findFrame(child, "frame")->element, + mjs_findBody(gchild, "body")->element, "gchild-", ""); // compile required before further attachment mjModel* m_child = compile ? mj_compile(child, 0) : nullptr; @@ -2243,8 +2256,8 @@ void AttachNestedKeyframe(bool compile) { }; // attach child to parent - mjs_attachBody(mjs_findFrame(parent, "frame"), - mjs_findBody(child, "body"), "child-", ""); + mjs_attach(mjs_findFrame(parent, "frame")->element, + mjs_findBody(child, "body")->element, "child-", ""); EXPECT_THAT(warning, HasSubstr(compile ? "" : "model has pending keyframes")); @@ -2305,11 +2318,11 @@ TEST_F(MujocoTest, RepeatedAttachKeyframe) { EXPECT_THAT(child, NotNull()) << er.data(); mjsBody* body_1 = mjs_findBody(parent, "body"); - mjsFrame* attachment_frame = mjs_addFrame(body_1, 0); - mjs_attachBody(attachment_frame, mjs_findBody(child, "b1"), "b1-", ""); + mjsElement* attachment_frame = mjs_addFrame(body_1, 0)->element; + mjs_attach(attachment_frame, mjs_findBody(child, "b1")->element, "b1-", ""); mjModel* model_1 = mj_compile(parent, 0); EXPECT_THAT(model_1, NotNull()); - mjs_attachBody(attachment_frame, mjs_findBody(child, "b2"), "b2-", ""); + mjs_attach(attachment_frame, mjs_findBody(child, "b2")->element, "b2-", ""); mjModel* model_2 = mj_compile(parent, 0); EXPECT_THAT(model_2, NotNull()); @@ -2374,8 +2387,8 @@ TEST_F(MujocoTest, ResizeParentKeyframe) { mjSpec* child = mj_parseXMLString(xml_child, 0, er.data(), er.size()); EXPECT_THAT(child, NotNull()) << er.data(); - mjs_attachBody(mjs_findFrame(parent, "frame"), mjs_findBody(child, "body"), - "child-", ""); + mjs_attach(mjs_findFrame(parent, "frame")->element, + mjs_findBody(child, "body")->element, "child-", ""); mjModel* model = mj_compile(parent, 0); EXPECT_THAT(model, NotNull()); @@ -2466,12 +2479,10 @@ TEST_F(MujocoTest, DifferentUnitsAllowed) { mjSpec* child = mj_parseXMLString(child_xml, 0, error.data(), error.size()); mjSpec* spec = mj_parseXMLString(parent_xml, 0, error.data(), error.size()); ASSERT_THAT(spec, NotNull()) << error.data(); - mjs_attachBody(mjs_findFrame(child, "frame"), - mjs_findBody(gchild, "gchild"), - "gchild_", ""); - mjs_attachBody(mjs_findFrame(spec, "frame"), - mjs_findBody(child, "child"), - "child_", ""); + mjs_attach(mjs_findFrame(child, "frame")->element, + mjs_findBody(gchild, "gchild")->element, "gchild_", ""); + mjs_attach(mjs_findFrame(spec, "frame")->element, + mjs_findBody(child, "child")->element, "child_", ""); mjModel* model = mj_compile(spec, 0); EXPECT_THAT(model, NotNull()); @@ -2544,7 +2555,8 @@ TEST_F(MujocoTest, DifferentOptionsInAttachedFrame) { EXPECT_THAT(world, NotNull()); mjsFrame* child_frame = mjs_findFrame(child, "child"); EXPECT_THAT(child_frame, NotNull()); - mjsFrame* attached_frame = mjs_attachFrame(world, child_frame, "child-", ""); + mjsElement* attached_frame = + mjs_attach(world->element, child_frame->element, "child-", ""); EXPECT_THAT(attached_frame, NotNull()); // wrap the child frame in the parent frame and compile @@ -2659,8 +2671,9 @@ TEST_F(MujocoTest, ApplyNameSpaceToDefaults) { mjSpec* parent = mj_parseXMLString(xml_p, 0, err.data(), err.size()); EXPECT_THAT(parent, NotNull()) << err.data(); - mjsBody* attached = mjs_attachBody(mjs_findFrame(parent, "parent"), - mjs_findBody(child, "body"), "child-", ""); + mjsElement* attached = + mjs_attach(mjs_findFrame(parent, "parent")->element, + mjs_findBody(child, "body")->element, "child-", ""); EXPECT_THAT(attached, NotNull()); mjModel* model = mj_compile(parent, vfs.get()); @@ -2774,7 +2787,7 @@ TEST_F(MujocoTest, ErrorWhenCompilingOrphanedSpec) { EXPECT_THAT(body, NotNull()); mjsFrame* frame = mjs_addFrame(mjs_findBody(parent, "world"), nullptr); EXPECT_THAT(frame, NotNull()); - mjs_attachBody(frame, body, "child-", ""); + mjs_attach(frame->element, body->element, "child-", ""); mj_deleteSpec(parent); mjModel* model = mj_compile(child, 0); EXPECT_THAT(model, IsNull()); diff --git a/test/user/user_model_test.cc b/test/user/user_model_test.cc index 9f85669e..8b00753d 100644 --- a/test/user/user_model_test.cc +++ b/test/user/user_model_test.cc @@ -623,7 +623,7 @@ TEST_F(MujocoTest, Modeldir) { mjSpec* spec = mj_makeSpec(); mjs_setDeepCopy(spec, true); mjs_setString(spec->meshdir, "asset"); - mjs_attachFrame(mjs_findBody(spec, "world"), frame, "_", ""); + mjs_attach(mjs_findBody(spec, "world")->element, frame->element, "_", ""); mjModel* model = mj_compile(spec, vfs.get()); EXPECT_THAT(model, NotNull());