Replace mjs_detachBody and mjs_detachDefault with mjs_detach.

In a follow-up, mjs_detach will be changed to only remove without deleting an element.

PiperOrigin-RevId: 773637351
Change-Id: I9ee4d3584ba88ccc225591337cf036bab806e25c
This commit is contained in:
Alessio Quaglino
2025-06-20 04:11:14 -07:00
committed by Copybara-Service
parent 4ce6293217
commit 0488d9f4eb
15 changed files with 177 additions and 179 deletions
+5 -14
View File
@@ -3783,23 +3783,14 @@ Attachment
Attach child to a parent, return the attached element if success or NULL otherwise.
.. _mjs_detachBody:
.. _mjs_detach:
`mjs_detachBody <#mjs_detachBody>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
`mjs_detach <#mjs_detach>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mjs_detachBody
.. mujoco-include:: mjs_detach
Delete body and descendants from mjSpec, remove all references, return 0 on success.
.. _mjs_detachDefault:
`mjs_detachDefault <#mjs_detachDefault>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mjs_detachDefault
Delete default class and descendants from mjSpec, remove all references, return 0 on success.
Detach but not delete object corresponding to the given element, return 0 on success.
.. _AddTreeElements:
+7
View File
@@ -5,6 +5,13 @@ Changelog
Upcoming version (not yet released)
-----------------------------------
.. admonition:: Breaking API changes
:class: attention
- The functions ``mjs_detachBody`` and ``mjs_detachDefault`` have been replaced by :ref:`mjs_delete`. Additionally,
`:ref:mjs_detach` was added in order to remove an element from the spec without deleting it.
- The Python functions ``element.delete`` have been replaced by ``spec.delete(element)``.
Bug fixes
^^^^^^^^^
- Inverse dynamics were not being computed correctly when :ref:`tendon armature<tendon-spatial-armature>` was present,
+2 -3
View File
@@ -3410,8 +3410,7 @@ void mju_defaultTask(mjTask* task);
void mju_taskJoin(mjTask* task);
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);
int mjs_detach(mjSpec* spec, mjsElement* element);
mjsBody* mjs_addBody(mjsBody* body, const mjsDefault* def);
mjsSite* mjs_addSite(mjsBody* body, const mjsDefault* def);
mjsJoint* mjs_addJoint(mjsBody* body, const mjsDefault* def);
@@ -3420,7 +3419,7 @@ mjsGeom* mjs_addGeom(mjsBody* body, const mjsDefault* def);
mjsCamera* mjs_addCamera(mjsBody* body, const mjsDefault* def);
mjsLight* mjs_addLight(mjsBody* body, const mjsDefault* def);
mjsFrame* mjs_addFrame(mjsBody* body, mjsFrame* parentframe);
int mjs_delete(mjsElement* element);
int mjs_delete(mjSpec* spec, mjsElement* element);
mjsActuator* mjs_addActuator(mjSpec* s, const mjsDefault* def);
mjsSensor* mjs_addSensor(mjSpec* s);
mjsFlex* mjs_addFlex(mjSpec* s);
+1 -1
View File
@@ -607,7 +607,7 @@ For elements that can have children (bodies and defaults), the methods ``spec.de
``spec.detach_default(def)`` remove, respectively, ``body`` and ``def`` from the spec, together with all of their
children. When detaching body subtrees, all elements which reference elements in the subtree, will also be removed. For
all other elements, the method ``delete()`` removes the corresponding element from the spec, e.g.
``spec.geom('my_geom').delete()`` will remove the geom named "my_geom" and all of the elements that reference it.
``spec.delete(spec.geom('my_geom'))`` will remove the geom named "my_geom" and all of the elements that reference it.
Tree traversal
^^^^^^^^^^^^^^
+3 -6
View File
@@ -1411,11 +1411,8 @@ MJAPI void mju_taskJoin(mjTask* task);
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);
// Delete default class and descendants from mjSpec, remove all references, return 0 on success.
MJAPI int mjs_detachDefault(mjSpec* s, mjsDefault* d);
// Detach but not delete object corresponding to the given element, return 0 on success.
MJAPI int mjs_detach(mjSpec* spec, mjsElement* element);
//---------------------------------- Tree elements -------------------------------------------------
@@ -1444,7 +1441,7 @@ MJAPI mjsLight* mjs_addLight(mjsBody* body, const mjsDefault* def);
MJAPI mjsFrame* mjs_addFrame(mjsBody* body, mjsFrame* parentframe);
// Delete object corresponding to the given element, return 0 on success.
MJAPI int mjs_delete(mjsElement* element);
MJAPI int mjs_delete(mjSpec* spec, mjsElement* element);
//---------------------------------- Non-tree elements ---------------------------------------------
+2 -2
View File
@@ -2000,7 +2000,7 @@
"\n",
"# Delete all key frames to avoid name conflicts\n",
"while humanoid.keys:\n",
" humanoid.keys[-1].delete()\n",
" humanoid.delete(keys[-1])\n",
"\n",
"# Create a grid of humanoids by attaching humanoid to spec multiple times\n",
"for i in range(4):\n",
@@ -2335,7 +2335,7 @@
"fly = mj.MjSpec.from_file(fly_file)\n",
"# Remove lights from fly so they are not duplicated in line-up\n",
"for light in fly.lights:\n",
" light.delete()\n",
" fly.delete(light)\n",
"\n",
"small_fly = scale_spec(fly, 1.25)\n",
"large_fly = scale_spec(fly, 2)\n",
+12 -26
View File
@@ -8987,45 +8987,25 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
),
doc='Attach child to a parent, return the attached element if success or NULL otherwise.', # pylint: disable=line-too-long
)),
('mjs_detachBody',
('mjs_detach',
FunctionDecl(
name='mjs_detachBody',
name='mjs_detach',
return_type=ValueType(name='int'),
parameters=(
FunctionParameterDecl(
name='s',
name='spec',
type=PointerType(
inner_type=ValueType(name='mjSpec'),
),
),
FunctionParameterDecl(
name='b',
name='element',
type=PointerType(
inner_type=ValueType(name='mjsBody'),
inner_type=ValueType(name='mjsElement'),
),
),
),
doc='Delete body and descendants from mjSpec, remove all references, return 0 on success.', # pylint: disable=line-too-long
)),
('mjs_detachDefault',
FunctionDecl(
name='mjs_detachDefault',
return_type=ValueType(name='int'),
parameters=(
FunctionParameterDecl(
name='s',
type=PointerType(
inner_type=ValueType(name='mjSpec'),
),
),
FunctionParameterDecl(
name='d',
type=PointerType(
inner_type=ValueType(name='mjsDefault'),
),
),
),
doc='Delete default class and descendants from mjSpec, remove all references, return 0 on success.', # pylint: disable=line-too-long
doc='Detach but not delete object corresponding to the given element, return 0 on success.', # pylint: disable=line-too-long
)),
('mjs_addBody',
FunctionDecl(
@@ -9202,6 +9182,12 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
name='mjs_delete',
return_type=ValueType(name='int'),
parameters=(
FunctionParameterDecl(
name='spec',
type=PointerType(
inner_type=ValueType(name='mjSpec'),
),
),
FunctionParameterDecl(
name='element',
type=PointerType(
+71 -36
View File
@@ -397,7 +397,7 @@ PYBIND11_MODULE(_specs, m) {
},
py::return_value_policy::reference_internal);
mjSpec.def("detach_default", [](MjSpec& self, raw::MjsDefault& def) {
if (mjs_detachDefault(self.ptr, &def) != 0) {
if (mjs_delete(self.ptr, def.element) != 0) {
throw pybind11::value_error(mjs_getError(self.ptr));
}
});
@@ -408,7 +408,7 @@ PYBIND11_MODULE(_specs, m) {
},
py::return_value_policy::reference_internal);
mjSpec.def("detach_body", [](MjSpec& self, raw::MjsBody& body) {
mjs_detachBody(self.ptr, &body);
mjs_delete(self.ptr, body.element);
});
mjSpec.def(
"attach",
@@ -759,7 +759,9 @@ PYBIND11_MODULE(_specs, m) {
// ============================= MJSFRAME ====================================
mjsFrame.def("delete", [](raw::MjsFrame& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsFrame& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsFrame.def("set_frame", [](raw::MjsFrame& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
@@ -796,7 +798,9 @@ PYBIND11_MODULE(_specs, m) {
py::return_value_policy::reference_internal);
// ============================= MJSGEOM =====================================
mjsGeom.def("delete", [](raw::MjsGeom& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsGeom& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsGeom.def("set_frame", [](raw::MjsGeom& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
@@ -824,7 +828,9 @@ PYBIND11_MODULE(_specs, m) {
py::return_value_policy::reference_internal);
// ============================= MJSJOINT ====================================
mjsJoint.def("delete", [](raw::MjsJoint& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsJoint& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsJoint.def("set_frame", [](raw::MjsJoint& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
@@ -852,7 +858,9 @@ PYBIND11_MODULE(_specs, m) {
py::return_value_policy::reference_internal);
// ============================= MJSSITE =====================================
mjsSite.def("delete", [](raw::MjsSite& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsSite& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsSite.def("set_frame", [](raw::MjsSite& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
@@ -897,8 +905,9 @@ PYBIND11_MODULE(_specs, m) {
py::return_value_policy::reference_internal);
// ============================= MJSCAMERA ===================================
mjsCamera.def("delete",
[](raw::MjsCamera& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsCamera& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsCamera.def("set_frame", [](raw::MjsCamera& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
@@ -926,7 +935,9 @@ PYBIND11_MODULE(_specs, m) {
py::return_value_policy::reference_internal);
// ============================= MJSLIGHT ====================================
mjsLight.def("delete", [](raw::MjsLight& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsLight& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsLight.def("set_frame", [](raw::MjsLight& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
@@ -954,8 +965,9 @@ PYBIND11_MODULE(_specs, m) {
py::return_value_policy::reference_internal);
// ============================= MJSMATERIAL =================================
mjsMaterial.def("delete",
[](raw::MjsMaterial& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsMaterial& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsMaterial.def_property(
"classname",
[](raw::MjsMaterial& self) -> raw::MjsDefault* {
@@ -966,7 +978,9 @@ PYBIND11_MODULE(_specs, m) {
});
// ============================= MJSMESH =====================================
mjsMesh.def("delete", [](raw::MjsMesh& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsMesh& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsMesh.def_property(
"classname",
[](raw::MjsMesh& self) -> raw::MjsDefault* {
@@ -977,7 +991,9 @@ PYBIND11_MODULE(_specs, m) {
});
// ============================= MJSPAIR =====================================
mjsPair.def("delete", [](raw::MjsPair& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsPair& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsPair.def_property(
"classname",
[](raw::MjsPair& self) -> raw::MjsDefault* {
@@ -988,8 +1004,9 @@ PYBIND11_MODULE(_specs, m) {
});
// ============================= MJSEQUAL ====================================
mjsEquality.def("delete",
[](raw::MjsEquality& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsEquality& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsEquality.def_property(
"classname",
[](raw::MjsEquality& self) -> raw::MjsDefault* {
@@ -1000,8 +1017,9 @@ PYBIND11_MODULE(_specs, m) {
});
// ============================= MJSACTUATOR =================================
mjsActuator.def("delete",
[](raw::MjsActuator& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsActuator& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsActuator.def_property(
"classname",
[](raw::MjsActuator& self) -> raw::MjsDefault* {
@@ -1101,8 +1119,9 @@ PYBIND11_MODULE(_specs, m) {
py::arg("gain"));
// ============================= MJSTENDON ===================================
mjsTendon.def("delete",
[](raw::MjsTendon& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsTendon& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsTendon.def(
"default",
[](raw::MjsTendon& self) -> raw::MjsDefault* {
@@ -1135,39 +1154,54 @@ PYBIND11_MODULE(_specs, m) {
py::return_value_policy::reference_internal);
// ============================= MJSSENSOR ===================================
mjsSensor.def("delete",
[](raw::MjsSensor& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsSensor& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSFLEX =====================================
mjsFlex.def("delete", [](raw::MjsFlex& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsFlex& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSHFIELD ===================================
mjsHField.def("delete",
[](raw::MjsHField& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsHField& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSSKIN =====================================
mjsSkin.def("delete", [](raw::MjsSkin& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsSkin& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSTEXTURE ==================================
mjsTexture.def("delete",
[](raw::MjsTexture& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsTexture& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSKEY ======================================
mjsKey.def("delete", [](raw::MjsKey& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsKey& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSTEXT =====================================
mjsText.def("delete", [](raw::MjsText& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsText& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSNUMERIC ==================================
mjsNumeric.def("delete",
[](raw::MjsNumeric& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsNumeric& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSEXCLUDE ==================================
mjsExclude.def("delete",
[](raw::MjsExclude& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsExclude& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSTUPLE ====================================
mjsTuple.def("delete", [](raw::MjsTuple& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsTuple& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSPLUGIN ===================================
mjsPlugin.def_property(
@@ -1176,8 +1210,9 @@ PYBIND11_MODULE(_specs, m) {
[](raw::MjsPlugin& self, raw::MjsPlugin* other) {
self.element = other->element;
});
mjsPlugin.def("delete",
[](raw::MjsPlugin& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsPlugin& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsPlugin.def_property(
"config",
[](raw::MjsPlugin& self) -> py::dict {
+4 -4
View File
@@ -838,9 +838,9 @@ class SpecsTest(absltest.TestCase):
self.assertIsNotNone(site)
self.assertEqual(site, spec.site('head'))
site.delete()
spec.sensors[-1].delete()
spec.sensors[-1].delete()
spec.delete(site)
spec.delete(spec.sensors[-1])
spec.delete(spec.sensors[-1])
model = spec.compile()
self.assertIsNotNone(model)
@@ -930,7 +930,7 @@ class SpecsTest(absltest.TestCase):
""")
plugin = spec.plugins[0]
self.assertIsNotNone(plugin)
plugin.delete()
spec.delete(plugin)
model = spec.compile()
self.assertIsNotNone(model)
+25 -33
View File
@@ -284,35 +284,18 @@ const char* mjs_getError(mjSpec* s) {
// detach body from mjSpec, return 0 on success
int mjs_detachBody(mjSpec* s, mjsBody* b) {
int mjs_detach(mjSpec* s, mjsElement* element) {
mjCModel* model = static_cast<mjCModel*>(s->element);
mjCBody* body = static_cast<mjCBody*>(b->element);
try {
*model -= *body;
} catch (mjCError& e) {
model->SetError(e);
if (!element) {
model->SetError(mjCError(0, "Element is null."));
return -1;
}
model->Detach(body);
return 0;
mjCError e(0, "Detach is not implemented yet.");
model->SetError(e);
return -1;
}
// detach default from mjSpec, return 0 on success
int mjs_detachDefault(mjSpec* s, mjsDefault* def) {
mjCModel* modelC = static_cast<mjCModel*>(s->element);
if (!def) {
modelC->SetError(mjCError(0, "Cannot detach, default is null"));
return -1;
}
mjCDef* defC = static_cast<mjCDef*>(def->element);
try {
*modelC -= *defC;
} catch (mjCError& e) {
modelC->SetError(e);
return -1;
}
return 0;
}
// check if model has warnings
int mjs_isWarning(mjSpec* s) {
@@ -372,15 +355,24 @@ int mj_copyBack(mjSpec* s, const mjModel* m) {
// delete object, return 0 on success
int mjs_delete(mjsElement* element) {
mjCModel* model;
if (element->elemtype == mjOBJ_DEFAULT)
model = static_cast<mjCDef*>(element)->model;
else
model = static_cast<mjCBase*>(element)->model;
int mjs_delete(mjSpec* s, mjsElement* element) {
mjCModel* model = static_cast<mjCModel*>(s->element);
if (!element) {
model->SetError(mjCError(0, "Element is null."));
return -1;
}
try {
// it will call the appropriate destructor since ~mjCBase is virtual
model->DeleteElement(element);
if (element->elemtype == mjOBJ_BODY) {
mjCBody* body = static_cast<mjCBody*>(element);
*model -= *body;
model->DeleteElement(body);
} else if (element->elemtype == mjOBJ_DEFAULT) {
mjCDef* def = static_cast<mjCDef*>(element);
*model -= *def;
} else {
// it will call the appropriate destructor since ~mjCBase is virtual
model->DeleteElement(element);
}
return 0;
} catch (mjCError& e) {
model->SetError(e);
@@ -1035,7 +1027,7 @@ const char* mjs_resolveOrientation(double quat[4], mjtByte degree, const char* s
mjsFrame* mjs_bodyToFrame(mjsBody** body) {
mjCBody* bodyC = static_cast<mjCBody*>((*body)->element);
mjCFrame* frameC = bodyC->ToFrame();
bodyC->model->Detach(bodyC);
bodyC->model->DeleteElement((*body)->element);
*body = nullptr;
return &frameC->spec;
}
+3 -6
View File
@@ -76,11 +76,8 @@ MJAPI int mj_copyBack(mjSpec* s, const mjModel* m);
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);
// Detach default from mjSpec, remove all references and delete the default, return 0 on success.
MJAPI int mjs_detachDefault(mjSpec* s, mjsDefault* d);
// Detach but not delete object corresponding to the given element, return 0 on success.
MJAPI int mjs_detach(mjSpec* s, mjsElement* element);
//---------------------------------- Add tree elements ---------------------------------------------
@@ -109,7 +106,7 @@ MJAPI mjsLight* mjs_addLight(mjsBody* body, const mjsDefault* def);
MJAPI mjsFrame* mjs_addFrame(mjsBody* body, mjsFrame* parentframe);
// Delete object corresponding to the given element, return 0 on success.
MJAPI int mjs_delete(mjsElement* element);
MJAPI int mjs_delete(mjSpec* s, mjsElement* element);
//---------------------------------- Add non-tree elements -----------------------------------------
+30 -24
View File
@@ -742,15 +742,44 @@ void deletefromlist(std::vector<T*>* list, mjsElement* element) {
// recursively delete all plugins in the subtree
static void deletesubtreeplugin(mjCBody* subtree, mjCModel* model) {
mjsPlugin* plugin = &(subtree->spec.plugin);
if (plugin->active && plugin->name->empty()) {
model->DeleteElement(plugin->element);
}
for (auto* body : subtree->Bodies()) {
deletesubtreeplugin(body, model);
}
}
// discard all invalid elements from all lists
void mjCModel::DeleteElement(mjsElement* el) {
ResetTreeLists();
if (el->elemtype != mjOBJ_DEFAULT) {
if (static_cast<mjCBase*>(el)->model != this) {
throw mjCError(nullptr, "element is not in this model");
}
} else {
if (static_cast<mjCDef*>(el)->model != this) {
throw mjCError(nullptr, "default is not in this model");
}
}
switch (el->elemtype) {
case mjOBJ_BODY:
{
MakeTreeLists(); // rebuild lists that were reset at the beginning of the function
throw mjCError(nullptr, "bodies cannot be deleted, use detach instead");
mjCBody* subtree = static_cast<mjCBody*>(el);
if (subtree->GetRef() == 1) {
deletesubtreeplugin(subtree, this);
}
subtree->Release();
break;
}
case mjOBJ_DEFAULT:
MakeTreeLists(); // rebuild lists that were reset at the beginning of the function
@@ -828,29 +857,6 @@ void mjCModel::DeleteElement(mjsElement* el) {
// recursively delete all plugins in the subtree
void deletesubtreeplugin(mjCBody* subtree, mjCModel* model) {
mjsPlugin* plugin = &(subtree->spec.plugin);
if (plugin->active && plugin->name->empty()) {
model->DeleteElement(plugin->element);
}
for (auto* body : subtree->Bodies()) {
deletesubtreeplugin(body, model);
}
}
// deletes all plugins in the subtree and then the subtree itself
void mjCModel::Detach(mjCBody* subtree) {
if (subtree->GetRef() == 1) {
deletesubtreeplugin(subtree, this);
}
subtree->Release();
}
// TODO: we should not use C-type casting with multiple C++ inheritance
void mjCModel::CreateObjectLists() {
for (int i = 0; i < mjNOBJECT; ++i) {
-3
View File
@@ -232,9 +232,6 @@ class mjCModel : public mjCModel_, private mjSpec {
// delete default and all descendants
void RemoveDefault(mjCDef* def);
// detach subtree from model
void Detach(mjCBody* subtree);
// API for access to model elements (outside tree)
int NumObjects(mjtObj type); // number of objects in specified list
mjCBase* GetObject(mjtObj type, int id); // pointer to specified object
+1 -1
View File
@@ -3554,7 +3554,7 @@ void mjXReader::Body(XMLElement* section, mjsBody* body, mjsFrame* frame,
}
// delete subtree
if (mjs_detachBody(spec, subtree)) {
if (mjs_delete(spec, subtree->element)) {
throw mjXError(elem, mjs_getError(spec));
}
}
+11 -20
View File
@@ -219,7 +219,7 @@ TEST_F(PluginTest, DeletePlugin) {
EXPECT_THAT(model->actuator_plugin[0], 0);
// delete actuator
mjs_delete(actuator->element);
mjs_delete(spec, actuator->element);
// recompile and check that the plugin is not present
mjModel* newmodel = mj_compile(spec, NULL);
@@ -326,7 +326,7 @@ TEST_F(PluginTest, DetachPlugin) {
// detach the body and compile
mjsBody* body_to_detach = mjs_findBody(parent, "child-body");
EXPECT_THAT(body_to_detach, NotNull());
EXPECT_THAT(mjs_detachBody(parent, body_to_detach), 0);
EXPECT_THAT(mjs_delete(parent, body_to_detach->element), 0);
mjModel* model = mj_compile(parent, nullptr);
EXPECT_THAT(model, NotNull());
EXPECT_THAT(model->nbody, 2);
@@ -1368,12 +1368,8 @@ void TestDetachBody(bool compile) {
mjsBody* body = mjs_findBody(child, "body");
EXPECT_THAT(body, NotNull());
// get an error if trying to delete the body
EXPECT_EQ(mjs_delete(body->element), -1);
EXPECT_THAT(mjs_getError(child), HasSubstr("use detach instead"));
// detach subtree
EXPECT_THAT(mjs_detachBody(child, body), 0);
// delete subtree
EXPECT_THAT(mjs_delete(child, body->element), 0);
// try saving to XML before compiling again
std::array<char, 1024> e;
@@ -1898,12 +1894,12 @@ TEST_F(MujocoTest, PreserveState) {
// detach subtree
mjsBody* body = mjs_findBody(spec, "detachable");
EXPECT_THAT(body, NotNull());
EXPECT_THAT(mjs_detachBody(spec, body), 0);
EXPECT_THAT(mjs_delete(spec, body->element), 0);
// detach mocap
mjsBody* mocap_body = mjs_findBody(spec, "mocap_detach");
EXPECT_THAT(mocap_body, NotNull());
EXPECT_THAT(mjs_detachBody(spec, mocap_body), 0);
EXPECT_THAT(mjs_delete(spec, mocap_body->element), 0);
// add body
mjsBody* newbody = mjs_addBody(mjs_findBody(spec, "world"), 0);
@@ -2787,25 +2783,20 @@ TEST_F(MujocoTest, DetachDefault) {
mjsDefault* child = mjs_findDefault(spec, "child1");
EXPECT_THAT(child, NotNull());
// try using mjs_delete to remove default, should fail
EXPECT_EQ(mjs_delete(child->element), -1);
// detach default
EXPECT_EQ(mjs_detachDefault(spec, child), 0);
// delete default
EXPECT_EQ(mjs_delete(spec, child->element), 0);
child = mjs_findDefault(spec, "child1");
EXPECT_THAT(child, IsNull());
// try and detach previously detached default, should fail
EXPECT_EQ(mjs_detachDefault(spec, child), -1);
EXPECT_EQ(mjs_delete(spec, nullptr), -1);
child = mjs_findDefault(spec, "child1");
EXPECT_THAT(child, IsNull());
EXPECT_THAT(mjs_getError(spec),
HasSubstr("Cannot detach, default is null"));
// detach parent
mjsDefault* parent = mjs_findDefault(spec, "parent");
EXPECT_THAT(parent, NotNull());
mjs_detachDefault(spec, parent);
mjs_delete(spec, parent->element);
// both parent and remaining child should be removed
parent = mjs_findDefault(spec, "parent");
@@ -2816,7 +2807,7 @@ TEST_F(MujocoTest, DetachDefault) {
// error when trying to detach the 'main' default
mjsDefault* main = mjs_findDefault(spec, "main");
EXPECT_THAT(main, NotNull());
EXPECT_EQ(mjs_detachDefault(spec, main), -1);
EXPECT_EQ(mjs_delete(spec, main->element), -1);
EXPECT_THAT(mjs_getError(spec),
HasSubstr("cannot remove the global default ('main')"));