Do not copy the spec during attach.
Use a reference count for managing the memory. PiperOrigin-RevId: 716169486 Change-Id: Id270c4858c17b9250115e9544d5ea143584e2d5f
This commit is contained in:
committed by
Copybara-Service
parent
6436055c6c
commit
c2138c3fb0
@@ -1510,6 +1510,15 @@ Free memory allocation in mjSpec.
|
||||
|
||||
Activate plugin. Returns 0 on success.
|
||||
|
||||
.. _mjs_setDeepCopy:
|
||||
|
||||
`mjs_setDeepCopy <#mjs_setDeepCopy>`__
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. mujoco-include:: mjs_setDeepCopy
|
||||
|
||||
Turn deep copy on or off attach. Returns 0 on success.
|
||||
|
||||
.. _Errorandmemory:
|
||||
|
||||
Error and memory
|
||||
|
||||
+7
-2
@@ -2,8 +2,13 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
Version 3.2.7 (Jan 14, 2025)
|
||||
----------------------------
|
||||
Upcoming version (not yet released)
|
||||
-----------------------------------
|
||||
|
||||
- Added ``mjs_setDeepCopy`` API function. When the deep copy flag is 0, attaching a model will not copy it to the
|
||||
parent, so the original references to the child allow to modify the parent as well. The default behavior is to perform
|
||||
such a shallow copy. The old behavioud of creating a deep copy of the child model while attaching can be restored by
|
||||
setting the deep copy flag to 1.
|
||||
|
||||
Python bindings
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -3181,6 +3181,7 @@ mjSpec* mj_makeSpec(void);
|
||||
mjSpec* mj_copySpec(const mjSpec* s);
|
||||
void mj_deleteSpec(mjSpec* s);
|
||||
int mjs_activatePlugin(mjSpec* s, const char* name);
|
||||
int mjs_setDeepCopy(mjSpec* s, int deepcopy);
|
||||
void mj_printFormattedModel(const mjModel* m, const char* filename, const char* float_format);
|
||||
void mj_printModel(const mjModel* m, const char* filename);
|
||||
void mj_printFormattedData(const mjModel* m, mjData* d, const char* filename,
|
||||
|
||||
@@ -106,12 +106,14 @@ procedurally, default classes are passed in explicitly to element constructors.
|
||||
|
||||
Attachment
|
||||
^^^^^^^^^^
|
||||
This framework introduces a powerful new feature: attaching and detaching model subtrees. Attachment allows the user
|
||||
copy a subtree from one model into another, while also copying 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. This feature is already used to power the :ref:`attach<body-attach>` and
|
||||
:ref:`replicate<replicate>` meta-elements in MJCF. It is possible to :ref:`attach a body to a frame<mjs_attachBody>` and
|
||||
to :ref:`attach a body to a site<mjs_attachToSite>`:
|
||||
|
||||
This framework introduces a powerful new feature: attaching and detaching model subtrees. This feature is already used
|
||||
to power the :ref:`attach<body-attach>` an :ref:`replicate<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<mjs_attachBody>`:
|
||||
|
||||
.. code-block:: C
|
||||
|
||||
@@ -120,9 +122,17 @@ to :ref:`attach a body to a site<mjs_attachToSite>`:
|
||||
parent->compiler.degree = 0;
|
||||
child->compiler.degree = 1;
|
||||
mjsFrame* frame = mjs_addFrame(mjs_findBody(parent, "world"), NULL);
|
||||
mjsSite* site = mjs_addSite(mjs_findBody(parent, "world"), NULL);
|
||||
mjsBody* body = mjs_addBody(mjs_findBody(child, "world"), NULL);
|
||||
mjsBody* attached_body_1 = mjs_attachBody(frame, body, "attached-", "-1");
|
||||
|
||||
or :ref:`attach a body to a site<mjs_attachToSite>`:
|
||||
|
||||
.. 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");
|
||||
|
||||
or :ref:`attach a frame to a body<mjs_attachFrame>`:
|
||||
|
||||
+7
-3
@@ -531,15 +531,19 @@ Attachment
|
||||
It is possible to combine multiple specs by using attachments. The following options are possible:
|
||||
|
||||
- Attach a body from the child spec to a frame in the parent spec: ``body.attach_body(body, prefix, suffix)``, returns
|
||||
the newly createdbody in the parent spec.
|
||||
the reference to the attached body, which should be identical to the body used as input.
|
||||
- Attach a frame from the child spec to a body in the parent spec: ``body.attach_frame(frame, prefix, suffix)``,
|
||||
returns the newly created frame in the parent spec.
|
||||
returns the reference to the attached frame, which should be identical to the frame used as input.
|
||||
- Attach a body from the child spec to a site in the parent spec: ``site.attach(body, prefix, suffix)``, returns the
|
||||
newly created body in the parent spec.
|
||||
reference to the attached body, which should be identical to the body used as input.
|
||||
- Attach the worldbody from the child spec to a frame in the parent spec and transform it to a frame:
|
||||
``body.attach(spec, prefix, suffix)``, returns the newly created frame that the child worldbody was transformed
|
||||
into.
|
||||
|
||||
Attaching does not copy, so all the child reference are still valid in the parent and therefore modifying the child will
|
||||
modify the parent. This is not true for the attach :ref:`attach<body-attach>` an :ref:`replicate<replicate>`
|
||||
meta-elements in MJCF, which create deep copies while attaching.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import mujoco
|
||||
|
||||
@@ -242,6 +242,9 @@ MJAPI void mj_deleteSpec(mjSpec* s);
|
||||
// Activate plugin. Returns 0 on success.
|
||||
MJAPI int mjs_activatePlugin(mjSpec* s, const char* name);
|
||||
|
||||
// Turn deep copy on or off attach. Returns 0 on success.
|
||||
MJAPI int mjs_setDeepCopy(mjSpec* s, int deepcopy);
|
||||
|
||||
|
||||
//---------------------------------- Printing ------------------------------------------------------
|
||||
|
||||
|
||||
@@ -1045,6 +1045,24 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
|
||||
),
|
||||
doc='Activate plugin. Returns 0 on success.',
|
||||
)),
|
||||
('mjs_setDeepCopy',
|
||||
FunctionDecl(
|
||||
name='mjs_setDeepCopy',
|
||||
return_type=ValueType(name='int'),
|
||||
parameters=(
|
||||
FunctionParameterDecl(
|
||||
name='s',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='mjSpec'),
|
||||
),
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='deepcopy',
|
||||
type=ValueType(name='int'),
|
||||
),
|
||||
),
|
||||
doc='Turn deep copy on or off attach. Returns 0 on success.',
|
||||
)),
|
||||
('mj_printFormattedModel',
|
||||
FunctionDecl(
|
||||
name='mj_printFormattedModel',
|
||||
|
||||
@@ -350,6 +350,14 @@ PYBIND11_MODULE(_specs, m) {
|
||||
mjSpec.def("copy", [](const MjSpec& self) -> MjSpec {
|
||||
return MjSpec(self);
|
||||
});
|
||||
mjSpec.def_property(
|
||||
"copy_during_attach",
|
||||
[](MjSpec& self) {
|
||||
throw pybind11::value_error("copy_during_attach can only be set.");
|
||||
},
|
||||
[](MjSpec& self, bool deepcopy) {
|
||||
return mjs_setDeepCopy(self.ptr, deepcopy);
|
||||
});
|
||||
mjSpec.def_property_readonly(
|
||||
"worldbody",
|
||||
[](MjSpec& self) -> raw::MjsBody* {
|
||||
|
||||
+24
-16
@@ -918,27 +918,31 @@ class SpecsTest(absltest.TestCase):
|
||||
model = parent.compile()
|
||||
np.testing.assert_almost_equal(model.body_quat[1], [1, 0, 0, 0])
|
||||
|
||||
def test_attach_body_to_site(self):
|
||||
child = mujoco.MjSpec()
|
||||
def test_attach_to_site(self):
|
||||
parent = mujoco.MjSpec()
|
||||
site = parent.worldbody.add_site(pos=[1, 2, 3], quat=[0, 0, 0, 1])
|
||||
body = child.worldbody.add_body()
|
||||
|
||||
# Attach body to site and compile.
|
||||
self.assertIsNotNone(site.attach_body(body, prefix='_'))
|
||||
child1 = mujoco.MjSpec()
|
||||
body1 = child1.worldbody.add_body()
|
||||
self.assertIs(body1, site.attach_body(body1, prefix='_'))
|
||||
body1.pos = [1, 1, 1]
|
||||
model1 = parent.compile()
|
||||
self.assertIsNotNone(model1)
|
||||
self.assertEqual(model1.nbody, 2)
|
||||
np.testing.assert_array_equal(model1.body_pos[1], [1, 2, 3])
|
||||
np.testing.assert_array_equal(model1.body_pos[1], [0, 1, 4])
|
||||
np.testing.assert_array_equal(model1.body_quat[1], [0, 0, 0, 1])
|
||||
|
||||
# Attach entire spec to site and compile again.
|
||||
self.assertIsNotNone(site.attach(child, prefix='child-'))
|
||||
child2 = mujoco.MjSpec()
|
||||
body2 = child2.worldbody.add_body(name='body')
|
||||
self.assertIsNotNone(site.attach(child2, prefix='child-'))
|
||||
body2.pos = [-1, -1, -1]
|
||||
model2 = parent.compile()
|
||||
self.assertIsNotNone(model2)
|
||||
self.assertEqual(model2.nbody, 3)
|
||||
np.testing.assert_array_equal(model2.body_pos[1], [1, 2, 3])
|
||||
np.testing.assert_array_equal(model2.body_pos[2], [1, 2, 3])
|
||||
np.testing.assert_array_equal(model2.body_pos[1], [0, 1, 4])
|
||||
np.testing.assert_array_equal(model2.body_pos[2], [2, 3, 2])
|
||||
np.testing.assert_array_equal(model2.body_quat[1], [0, 0, 0, 1])
|
||||
np.testing.assert_array_equal(model2.body_quat[2], [0, 0, 0, 1])
|
||||
|
||||
@@ -949,27 +953,31 @@ class SpecsTest(absltest.TestCase):
|
||||
frame = body.to_frame()
|
||||
np.testing.assert_array_equal(frame.pos, [1, 2, 3])
|
||||
|
||||
def test_attach_spec_to_frame(self):
|
||||
child = mujoco.MjSpec()
|
||||
def test_attach_to_frame(self):
|
||||
parent = mujoco.MjSpec()
|
||||
frame = parent.worldbody.add_frame(pos=[1, 2, 3], quat=[0, 0, 0, 1])
|
||||
body = child.worldbody.add_body()
|
||||
|
||||
# Attach body to frame and compile.
|
||||
self.assertIsNotNone(frame.attach_body(body, prefix='_'))
|
||||
child1 = mujoco.MjSpec()
|
||||
body1 = child1.worldbody.add_body()
|
||||
self.assertIs(body1, frame.attach_body(body1, prefix='_'))
|
||||
body1.pos = [1, 1, 1]
|
||||
model1 = parent.compile()
|
||||
self.assertIsNotNone(model1)
|
||||
self.assertEqual(model1.nbody, 2)
|
||||
np.testing.assert_array_equal(model1.body_pos[1], [1, 2, 3])
|
||||
np.testing.assert_array_equal(model1.body_pos[1], [0, 1, 4])
|
||||
np.testing.assert_array_equal(model1.body_quat[1], [0, 0, 0, 1])
|
||||
|
||||
# Attach entire spec to frame and compile again.
|
||||
self.assertIsNotNone(frame.attach(child, prefix='child-'))
|
||||
child2 = mujoco.MjSpec()
|
||||
body2 = child2.worldbody.add_body(name='body')
|
||||
self.assertIsNotNone(frame.attach(child2, prefix='child-'))
|
||||
body2.pos = [-1, -1, -1]
|
||||
model2 = parent.compile()
|
||||
self.assertIsNotNone(model2)
|
||||
self.assertEqual(model2.nbody, 3)
|
||||
np.testing.assert_array_equal(model2.body_pos[1], [1, 2, 3])
|
||||
np.testing.assert_array_equal(model2.body_pos[2], [1, 2, 3])
|
||||
np.testing.assert_array_equal(model2.body_pos[1], [0, 1, 4])
|
||||
np.testing.assert_array_equal(model2.body_pos[2], [2, 3, 2])
|
||||
np.testing.assert_array_equal(model2.body_quat[1], [0, 0, 0, 1])
|
||||
np.testing.assert_array_equal(model2.body_quat[2], [0, 0, 0, 1])
|
||||
|
||||
|
||||
+11
-2
@@ -208,7 +208,7 @@ int mjs_detachBody(mjSpec* s, mjsBody* b) {
|
||||
model->SetError(e);
|
||||
return -1;
|
||||
}
|
||||
delete body;
|
||||
model->Detach(body);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -254,6 +254,15 @@ int mjs_activatePlugin(mjSpec* s, const char* name) {
|
||||
|
||||
|
||||
|
||||
// set deep copy flag
|
||||
int mjs_setDeepCopy(mjSpec* s, int deepcopy) {
|
||||
mjCModel* model = static_cast<mjCModel*>(s->element);
|
||||
model->SetDeepCopy(deepcopy);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// delete object, return 0 if success
|
||||
int mjs_delete(mjsElement* element) {
|
||||
mjCBase* object = static_cast<mjCBase*>(element);
|
||||
@@ -705,7 +714,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();
|
||||
delete bodyC;
|
||||
bodyC->model->Detach(bodyC);
|
||||
*body = nullptr;
|
||||
return &frameC->spec;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,9 @@ MJAPI void mjs_addSpec(mjSpec* s, mjSpec* child);
|
||||
// Activate plugin, return 0 on success.
|
||||
MJAPI int mjs_activatePlugin(mjSpec* s, const char* name);
|
||||
|
||||
// Turn deep copy on or off attach. Returns 0 on success.
|
||||
MJAPI int mjs_setDeepCopy(mjSpec* s, int deepcopy);
|
||||
|
||||
|
||||
//---------------------------------- Attachment ----------------------------------------------------
|
||||
|
||||
|
||||
@@ -278,9 +278,6 @@ void mjCMesh::CopyPlugin() {
|
||||
mjCMesh::~mjCMesh() {
|
||||
if (center_) mju_free(center_);
|
||||
if (graph_) mju_free(graph_);
|
||||
if (spec.plugin.active && spec.plugin.name->empty() && model) {
|
||||
model->DeleteElement(spec.plugin.element);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+102
-26
@@ -139,6 +139,7 @@ mjCModel::mjCModel() {
|
||||
center_auto[0] = center_auto[1] = center_auto[2] = 0;
|
||||
#endif
|
||||
|
||||
deepcopy_ = false;
|
||||
nplugin = 0;
|
||||
Clear();
|
||||
|
||||
@@ -181,6 +182,7 @@ mjCModel::mjCModel(const mjCModel& other) {
|
||||
|
||||
|
||||
mjCModel& mjCModel::operator=(const mjCModel& other) {
|
||||
deepcopy_ = true;
|
||||
if (this != &other) {
|
||||
this->spec = other.spec;
|
||||
*static_cast<mjCModel_*>(this) = static_cast<const mjCModel_&>(other);
|
||||
@@ -210,6 +212,7 @@ mjCModel& mjCModel::operator=(const mjCModel& other) {
|
||||
ids[i] = other.ids[i];
|
||||
}
|
||||
}
|
||||
deepcopy_ = other.deepcopy_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -222,20 +225,29 @@ void mjCModel::CopyList(std::vector<T*>& dest,
|
||||
// loop over the elements from the other model
|
||||
int nsource = (int)source.size();
|
||||
for (int i = 0; i < nsource; i++) {
|
||||
T* candidate = new T(*source[i]);
|
||||
T* candidate = deepcopy_ ? new T(*source[i]) : source[i];
|
||||
try {
|
||||
// try to find the referenced object in this model
|
||||
candidate->NameSpace(source[i]->model);
|
||||
mjCModel* source_model = source[i]->model;
|
||||
candidate->model = this;
|
||||
candidate->NameSpace(source_model);
|
||||
candidate->CopyFromSpec();
|
||||
candidate->ResolveReferences(this);
|
||||
} catch (mjCError err) {
|
||||
// if not present, skip the element
|
||||
// TODO: do not skip elements that contain user errors
|
||||
delete candidate;
|
||||
if (deepcopy_) {
|
||||
candidate->model = nullptr;
|
||||
delete candidate;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// copy the element from the other model to this model
|
||||
source[i]->ForgetKeyframes();
|
||||
if (deepcopy_) {
|
||||
source[i]->ForgetKeyframes();
|
||||
} else {
|
||||
candidate->AddRef();
|
||||
}
|
||||
mjSpec* origin = FindSpec(source[i]->compiler);
|
||||
dest.push_back(candidate);
|
||||
dest.back()->model = this;
|
||||
@@ -324,9 +336,12 @@ void mjCModel::CopyExplicitPlugin(T* obj) {
|
||||
return;
|
||||
}
|
||||
mjCPlugin* origin = static_cast<mjCPlugin*>(obj->spec.plugin.element);
|
||||
mjCPlugin* candidate = new mjCPlugin(*origin);
|
||||
mjCPlugin* candidate = deepcopy_ ? new mjCPlugin(*origin) : origin;
|
||||
candidate->id = plugins_.size();
|
||||
candidate->model = this;
|
||||
if (!deepcopy_) {
|
||||
candidate->AddRef();
|
||||
}
|
||||
plugins_.push_back(candidate);
|
||||
obj->spec.plugin.element = candidate;
|
||||
}
|
||||
@@ -566,7 +581,7 @@ void deletefromlist(std::vector<T*>* list, mjsElement* element) {
|
||||
for (int j = 0; j < list->size(); ++j) {
|
||||
list->at(j)->id = -1;
|
||||
if (list->at(j) == element) {
|
||||
delete list->at(j);
|
||||
list->at(j)->Release();
|
||||
list->erase(list->begin() + j);
|
||||
j--;
|
||||
}
|
||||
@@ -577,8 +592,9 @@ void deletefromlist(std::vector<T*>* list, mjsElement* element) {
|
||||
|
||||
// discard all invalid elements from all lists
|
||||
void mjCModel::DeleteElement(mjsElement* el) {
|
||||
mjCBody *world = bodies_[0];
|
||||
mjCBody *world = nullptr;
|
||||
if (compiled) {
|
||||
world = bodies_[0];
|
||||
ResetTreeLists();
|
||||
}
|
||||
|
||||
@@ -588,8 +604,14 @@ void mjCModel::DeleteElement(mjsElement* el) {
|
||||
break;
|
||||
|
||||
case mjOBJ_GEOM:
|
||||
deletefromlist(&(static_cast<mjCGeom*>(el)->body->geoms), el);
|
||||
{
|
||||
mjCGeom* geom = static_cast<mjCGeom*>(el);
|
||||
if (geom->plugin.active && geom->plugin.name->empty() && geom->GetRef() == 1) {
|
||||
DeleteElement(geom->plugin.element);
|
||||
}
|
||||
deletefromlist(&(geom->body->geoms), el);
|
||||
break;
|
||||
}
|
||||
|
||||
case mjOBJ_SITE:
|
||||
deletefromlist(&(static_cast<mjCSite*>(el)->body->sites), el);
|
||||
@@ -607,6 +629,36 @@ void mjCModel::DeleteElement(mjsElement* el) {
|
||||
deletefromlist(&(static_cast<mjCCamera*>(el)->body->cameras), el);
|
||||
break;
|
||||
|
||||
case mjOBJ_MESH:
|
||||
{
|
||||
mjCMesh* mesh = static_cast<mjCMesh*>(el);
|
||||
if (mesh->plugin.active && mesh->plugin.name->empty() && mesh->GetRef() == 1) {
|
||||
DeleteElement(mesh->plugin.element);
|
||||
}
|
||||
deletefromlist(object_lists_[mjOBJ_MESH], el);
|
||||
break;
|
||||
}
|
||||
|
||||
case mjOBJ_ACTUATOR:
|
||||
{
|
||||
mjCActuator* actuator = static_cast<mjCActuator*>(el);
|
||||
if (actuator->plugin.active && actuator->plugin.name->empty() && actuator->GetRef() == 1) {
|
||||
DeleteElement(actuator->plugin.element);
|
||||
}
|
||||
deletefromlist(object_lists_[mjOBJ_ACTUATOR], el);
|
||||
break;
|
||||
}
|
||||
|
||||
case mjOBJ_SENSOR:
|
||||
{
|
||||
mjCSensor* sensor = static_cast<mjCSensor*>(el);
|
||||
if (sensor->plugin.active && sensor->plugin.name->empty() && sensor->GetRef() == 1) {
|
||||
DeleteElement(sensor->plugin.element);
|
||||
}
|
||||
deletefromlist(object_lists_[mjOBJ_SENSOR], el);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
deletefromlist(object_lists_[el->elemtype], el);
|
||||
break;
|
||||
@@ -621,6 +673,29 @@ 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) {
|
||||
@@ -688,28 +763,28 @@ mjCModel::~mjCModel() {
|
||||
compiled = false;
|
||||
|
||||
// delete kinematic tree and all objects allocated in it
|
||||
delete bodies_[0];
|
||||
bodies_[0]->Release();
|
||||
|
||||
// delete objects allocated in mjCModel
|
||||
for (int i=0; i<flexes_.size(); i++) delete flexes_[i];
|
||||
for (int i=0; i<meshes_.size(); i++) delete meshes_[i];
|
||||
for (int i=0; i<skins_.size(); i++) delete skins_[i];
|
||||
for (int i=0; i<hfields_.size(); i++) delete hfields_[i];
|
||||
for (int i=0; i<textures_.size(); i++) delete textures_[i];
|
||||
for (int i=0; i<materials_.size(); i++) delete materials_[i];
|
||||
for (int i=0; i<pairs_.size(); i++) delete pairs_[i];
|
||||
for (int i=0; i<excludes_.size(); i++) delete excludes_[i];
|
||||
for (int i=0; i<equalities_.size(); i++) delete equalities_[i];
|
||||
for (int i=0; i<tendons_.size(); i++) delete tendons_[i]; // also deletes wraps
|
||||
for (int i=0; i<actuators_.size(); i++) delete actuators_[i];
|
||||
for (int i=0; i<sensors_.size(); i++) delete sensors_[i];
|
||||
for (int i=0; i<numerics_.size(); i++) delete numerics_[i];
|
||||
for (int i=0; i<texts_.size(); i++) delete texts_[i];
|
||||
for (int i=0; i<tuples_.size(); i++) delete tuples_[i];
|
||||
for (int i=0; i<keys_.size(); i++) delete keys_[i];
|
||||
for (int i=0; i<flexes_.size(); i++) flexes_[i]->Release();
|
||||
for (int i=0; i<meshes_.size(); i++) meshes_[i]->Release();
|
||||
for (int i=0; i<skins_.size(); i++) skins_[i]->Release();
|
||||
for (int i=0; i<hfields_.size(); i++) hfields_[i]->Release();
|
||||
for (int i=0; i<textures_.size(); i++) textures_[i]->Release();
|
||||
for (int i=0; i<materials_.size(); i++) materials_[i]->Release();
|
||||
for (int i=0; i<pairs_.size(); i++) pairs_[i]->Release();
|
||||
for (int i=0; i<excludes_.size(); i++) excludes_[i]->Release();
|
||||
for (int i=0; i<equalities_.size(); i++) equalities_[i]->Release();
|
||||
for (int i=0; i<tendons_.size(); i++) tendons_[i]->Release(); // also deletes wraps
|
||||
for (int i=0; i<actuators_.size(); i++) actuators_[i]->Release();
|
||||
for (int i=0; i<sensors_.size(); i++) sensors_[i]->Release();
|
||||
for (int i=0; i<numerics_.size(); i++) numerics_[i]->Release();
|
||||
for (int i=0; i<texts_.size(); i++) texts_[i]->Release();
|
||||
for (int i=0; i<tuples_.size(); i++) tuples_[i]->Release();
|
||||
for (int i=0; i<keys_.size(); i++) keys_[i]->Release();
|
||||
for (int i=0; i<defaults_.size(); i++) delete defaults_[i];
|
||||
for (int i=0; i<specs_.size(); i++) mj_deleteSpec(specs_[i]);
|
||||
for (int i=0; i<plugins_.size(); i++) delete plugins_[i];
|
||||
for (int i=0; i<plugins_.size(); i++) plugins_[i]->Release();
|
||||
|
||||
// clear sizes and pointer lists created in Compile
|
||||
Clear();
|
||||
@@ -933,6 +1008,7 @@ mjCPlugin* mjCModel::AddPlugin() {
|
||||
|
||||
// append spec to spec
|
||||
void mjCModel::AppendSpec(mjSpec* spec) {
|
||||
// TODO: check if the spec is already in the list
|
||||
specs_.push_back(spec);
|
||||
}
|
||||
|
||||
|
||||
@@ -221,6 +221,9 @@ class mjCModel : public mjCModel_, private mjSpec {
|
||||
// delete object from the corresponding list
|
||||
void DeleteElement(mjsElement* el);
|
||||
|
||||
// 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
|
||||
@@ -307,6 +310,9 @@ class mjCModel : public mjCModel_, private mjSpec {
|
||||
// get the spec from which this model was created
|
||||
mjSpec* GetSourceSpec() const;
|
||||
|
||||
// set deepcopy flag
|
||||
void SetDeepCopy(bool deepcopy) { deepcopy_ = deepcopy; }
|
||||
|
||||
private:
|
||||
// settings for each defaults class
|
||||
std::vector<mjCDef*> defaults_;
|
||||
@@ -351,7 +357,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
|
||||
std::vector<mjSpec*> specs_; // list of attached specs
|
||||
|
||||
// pointers to objects created inside kinematic tree
|
||||
std::vector<mjCBody*> bodies_; // list of bodies
|
||||
@@ -410,5 +416,6 @@ class mjCModel : public mjCModel_, private mjSpec {
|
||||
mjListKeyMap ids; // map from object names to ids
|
||||
mjCError errInfo; // last error info
|
||||
std::vector<mjKeyInfo> key_pending_; // attached keyframes
|
||||
bool deepcopy_; // copy objects when attaching
|
||||
};
|
||||
#endif // MUJOCO_SRC_USER_USER_MODEL_H_
|
||||
|
||||
+136
-72
@@ -763,6 +763,7 @@ mjCBody::mjCBody(mjCModel* _model) {
|
||||
model = _model;
|
||||
if (_model) compiler = &_model->spec.compiler;
|
||||
|
||||
refcount = 1;
|
||||
mjs_defaultBody(&spec);
|
||||
elemtype = mjOBJ_BODY;
|
||||
parent = nullptr;
|
||||
@@ -872,22 +873,28 @@ mjCBody& mjCBody::operator+=(const mjCFrame& other) {
|
||||
other.model->prefix = other.prefix;
|
||||
other.model->suffix = other.suffix;
|
||||
other.model->StoreKeyframes(model);
|
||||
mjCModel* other_model = other.model;
|
||||
|
||||
// attach defaults
|
||||
if (other.model != model) {
|
||||
mjCDef* subdef = new mjCDef(*other.model->Default());
|
||||
subdef->NameSpace(other.model);
|
||||
if (other_model != model) {
|
||||
mjCDef* subdef = new mjCDef(*other_model->Default());
|
||||
subdef->NameSpace(other_model);
|
||||
*model += *subdef;
|
||||
}
|
||||
|
||||
// copy input frame
|
||||
mjSpec* origin = model->FindSpec(other.compiler);
|
||||
frames.push_back(new mjCFrame(other));
|
||||
mjCFrame* newframe(model->deepcopy_ ? new mjCFrame(other) : (mjCFrame*)&other);
|
||||
frames.push_back(newframe);
|
||||
frames.back()->body = this;
|
||||
frames.back()->model = model;
|
||||
frames.back()->compiler = origin ? &origin->compiler : &model->spec.compiler;
|
||||
frames.back()->frame = other.frame;
|
||||
frames.back()->NameSpace(other.model);
|
||||
if (model->deepcopy_) {
|
||||
frames.back()->NameSpace(other_model);
|
||||
} else {
|
||||
frames.back()->AddRef();
|
||||
}
|
||||
int i = frames.size();
|
||||
last_attached = &frames.back()->spec;
|
||||
|
||||
@@ -909,30 +916,43 @@ mjCBody& mjCBody::operator+=(const mjCFrame& other) {
|
||||
CopyList(cameras, subtree->cameras, fmap, &other);
|
||||
CopyList(lights, subtree->lights, fmap, &other);
|
||||
|
||||
if (!model->deepcopy_) {
|
||||
subtree->SetModel(model);
|
||||
subtree->NameSpace(other_model);
|
||||
}
|
||||
|
||||
int nbodies = (int)subtree->bodies.size();
|
||||
for (int i=0; i<nbodies; i++) {
|
||||
if (!other.IsAncestor(subtree->bodies[i]->frame)) {
|
||||
continue;
|
||||
}
|
||||
bodies.push_back(new mjCBody(*subtree->bodies[i], model)); // triggers recursive call
|
||||
if (model->deepcopy_) {
|
||||
mjCBody* newbody(new mjCBody(*subtree->bodies[i], model)); // triggers recursive call
|
||||
bodies.push_back(newbody);
|
||||
subtree->bodies[i]->ForgetKeyframes();
|
||||
bodies.back()->NameSpace_(other_model, /*propagate=*/ false);
|
||||
} else {
|
||||
bodies.push_back(subtree->bodies[i]);
|
||||
bodies.back()->SetModel(model);
|
||||
bodies.back()->ResetId();
|
||||
bodies.back()->AddRef();
|
||||
}
|
||||
bodies.back()->parent = this;
|
||||
bodies.back()->frame =
|
||||
subtree->bodies[i]->frame ? frames[fmap[subtree->bodies[i]->frame]] : nullptr;
|
||||
bodies.back()->NameSpace_(other.model, /*propagate=*/ false);
|
||||
subtree->bodies[i]->ForgetKeyframes();
|
||||
}
|
||||
|
||||
// attach referencing elements
|
||||
*model += *other.model;
|
||||
*model += *other_model;
|
||||
|
||||
// leave the source model in a clean state
|
||||
if (other.model != model) {
|
||||
other.model->key_pending_.clear();
|
||||
if (other_model != model) {
|
||||
other_model->key_pending_.clear();
|
||||
}
|
||||
|
||||
// clear namespace and return body
|
||||
other.model->prefix.clear();
|
||||
other.model->suffix.clear();
|
||||
other_model->prefix.clear();
|
||||
other_model->suffix.clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -948,7 +968,8 @@ void mjCBody::CopyList(std::vector<T*>& dst, const std::vector<T*>& src,
|
||||
continue; // skip if the element is not inside pframe
|
||||
}
|
||||
mjSpec* origin = model->FindSpec(src[i]->compiler);
|
||||
dst.push_back(new T(*src[i]));
|
||||
T* new_obj = model->deepcopy_ ? new T(*src[i]) : src[i];
|
||||
dst.push_back(new_obj);
|
||||
dst.back()->body = this;
|
||||
dst.back()->model = model;
|
||||
dst.back()->compiler = origin ? &origin->compiler : &model->spec.compiler;
|
||||
@@ -956,6 +977,11 @@ void mjCBody::CopyList(std::vector<T*>& dst, const std::vector<T*>& src,
|
||||
dst.back()->CopyPlugin();
|
||||
dst.back()->classname = src[i]->classname;
|
||||
|
||||
// increment refcount if shallow copy is made
|
||||
if (!model->deepcopy_) {
|
||||
dst.back()->AddRef();
|
||||
}
|
||||
|
||||
// assign dst frame to src frame
|
||||
dst.back()->frame = src[i]->frame ? frames[fmap[src[i]->frame]] : nullptr;
|
||||
|
||||
@@ -981,6 +1007,73 @@ mjCBody& mjCBody::operator-=(const mjCBody& subtree) {
|
||||
|
||||
|
||||
|
||||
// set model of this body and its subtree
|
||||
void mjCBody::SetModel(mjCModel* _model) {
|
||||
model = _model;
|
||||
mjSpec* origin = model->FindSpec(mjs_getString(model->spec.modelname));
|
||||
compiler = origin ? &origin->compiler : &model->spec.compiler;
|
||||
|
||||
for (auto& body : bodies) {
|
||||
body->SetModel(_model);
|
||||
}
|
||||
for (auto& frame : frames) {
|
||||
frame->model = _model;
|
||||
frame->compiler = compiler;
|
||||
}
|
||||
for (auto& geom : geoms) {
|
||||
geom->model = _model;
|
||||
geom->compiler = compiler;
|
||||
}
|
||||
for (auto& joint : joints) {
|
||||
joint->model = _model;
|
||||
joint->compiler = compiler;
|
||||
}
|
||||
for (auto& site : sites) {
|
||||
site->model = _model;
|
||||
site->compiler = compiler;
|
||||
}
|
||||
for (auto& camera : cameras) {
|
||||
camera->model = _model;
|
||||
camera->compiler = compiler;
|
||||
}
|
||||
for (auto& light : lights) {
|
||||
light->model = _model;
|
||||
light->compiler = compiler;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// reset ids of all objects in this body
|
||||
void mjCBody::ResetId() {
|
||||
id = -1;
|
||||
for (auto& body : bodies) {
|
||||
body->ResetId();
|
||||
}
|
||||
for (auto& frame : frames) {
|
||||
frame->id = -1;
|
||||
}
|
||||
for (auto& geom : geoms) {
|
||||
geom->id = -1;
|
||||
}
|
||||
for (auto& joint : joints) {
|
||||
joint->id = -1;
|
||||
joint->qposadr_ = -1;
|
||||
joint->dofadr_ = -1;
|
||||
}
|
||||
for (auto& site : sites) {
|
||||
site->id = -1;
|
||||
}
|
||||
for (auto& camera : cameras) {
|
||||
camera->id = -1;
|
||||
}
|
||||
for (auto& light : lights) {
|
||||
light->id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void mjCBody::PointToLocal() {
|
||||
spec.element = static_cast<mjsElement*>(this);
|
||||
spec.name = &name;
|
||||
@@ -1012,26 +1105,13 @@ void mjCBody::CopyPlugin() {
|
||||
|
||||
// destructor
|
||||
mjCBody::~mjCBody() {
|
||||
// delete objects allocated here
|
||||
for (int i=0; i<bodies.size(); i++) delete bodies[i];
|
||||
for (int i=0; i<geoms.size(); i++) delete geoms[i];
|
||||
for (int i=0; i<frames.size(); i++) delete frames[i];
|
||||
for (int i=0; i<joints.size(); i++) delete joints[i];
|
||||
for (int i=0; i<sites.size(); i++) delete sites[i];
|
||||
for (int i=0; i<cameras.size(); i++) delete cameras[i];
|
||||
for (int i=0; i<lights.size(); i++) delete lights[i];
|
||||
|
||||
bodies.clear();
|
||||
geoms.clear();
|
||||
frames.clear();
|
||||
joints.clear();
|
||||
sites.clear();
|
||||
cameras.clear();
|
||||
lights.clear();
|
||||
|
||||
if (spec.plugin.active && spec.plugin.name->empty() && model) {
|
||||
model->DeleteElement(spec.plugin.element);
|
||||
}
|
||||
for (int i=0; i<bodies.size(); i++) bodies[i]->Release();
|
||||
for (int i=0; i<geoms.size(); i++) geoms[i]->Release();
|
||||
for (int i=0; i<frames.size(); i++) frames[i]->Release();
|
||||
for (int i=0; i<joints.size(); i++) joints[i]->Release();
|
||||
for (int i=0; i<sites.size(); i++) sites[i]->Release();
|
||||
for (int i=0; i<cameras.size(); i++) cameras[i]->Release();
|
||||
for (int i=0; i<lights.size(); i++) lights[i]->Release();
|
||||
}
|
||||
|
||||
|
||||
@@ -1840,19 +1920,27 @@ mjCFrame& mjCFrame::operator+=(const mjCBody& other) {
|
||||
other.model->StoreKeyframes(model);
|
||||
other.model->prefix = "";
|
||||
other.model->suffix = "";
|
||||
mjCModel* other_model = other.model;
|
||||
|
||||
mjCBody* subtree = new mjCBody(other, model);
|
||||
other.ForgetKeyframes();
|
||||
other.model->prefix = subtree->prefix;
|
||||
other.model->suffix = subtree->suffix;
|
||||
// attach or copy the subtree
|
||||
mjCBody* subtree = model->deepcopy_ ? new mjCBody(other, model) : (mjCBody*)&other;
|
||||
if (model->deepcopy_) {
|
||||
other.ForgetKeyframes();
|
||||
} else {
|
||||
subtree->SetModel(model);
|
||||
subtree->ResetId();
|
||||
subtree->AddRef();
|
||||
}
|
||||
other_model->prefix = subtree->prefix;
|
||||
other_model->suffix = subtree->suffix;
|
||||
subtree->SetParent(body);
|
||||
subtree->SetFrame(this);
|
||||
subtree->NameSpace(other.model);
|
||||
subtree->NameSpace(other_model);
|
||||
|
||||
// attach defaults
|
||||
if (other.model != model) {
|
||||
mjCDef* subdef = new mjCDef(*other.model->Default());
|
||||
subdef->NameSpace(other.model);
|
||||
if (other_model != model) {
|
||||
mjCDef* subdef = new mjCDef(*other_model->Default());
|
||||
subdef->NameSpace(other_model);
|
||||
*model += *subdef;
|
||||
}
|
||||
|
||||
@@ -1861,16 +1949,16 @@ mjCFrame& mjCFrame::operator+=(const mjCBody& other) {
|
||||
last_attached = &body->bodies.back()->spec;
|
||||
|
||||
// attach referencing elements
|
||||
*model += *other.model;
|
||||
*model += *other_model;
|
||||
|
||||
// leave the source model in a clean state
|
||||
if (other.model != model) {
|
||||
other.model->key_pending_.clear();
|
||||
if (other_model != model) {
|
||||
other_model->key_pending_.clear();
|
||||
}
|
||||
|
||||
// clear suffixes and return
|
||||
other.model->suffix.clear();
|
||||
other.model->prefix.clear();
|
||||
other_model->suffix.clear();
|
||||
other_model->prefix.clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -2221,14 +2309,6 @@ mjCGeom::mjCGeom(const mjCGeom& other) {
|
||||
|
||||
|
||||
|
||||
mjCGeom::~mjCGeom() {
|
||||
if (spec.plugin.active && spec.plugin.name->empty() && model) {
|
||||
model->DeleteElement(spec.plugin.element);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
mjCGeom& mjCGeom::operator=(const mjCGeom& other) {
|
||||
if (this != &other) {
|
||||
this->spec = other.spec;
|
||||
@@ -4591,7 +4671,7 @@ void mjCMaterial::CopyFromSpec() {
|
||||
void mjCMaterial::NameSpace(const mjCModel* m) {
|
||||
mjCBase::NameSpace(m);
|
||||
for (int i=0; i<mjNTEXROLE; i++) {
|
||||
if (!spec_textures_[i].empty() && model != m) {
|
||||
if (!spec_textures_[i].empty()) {
|
||||
spec_textures_[i] = m->prefix + spec_textures_[i] + m->suffix;
|
||||
}
|
||||
}
|
||||
@@ -5657,14 +5737,6 @@ mjCActuator::mjCActuator(const mjCActuator& other) {
|
||||
|
||||
|
||||
|
||||
mjCActuator::~mjCActuator() {
|
||||
if (spec.plugin.active && spec.plugin.name->empty() && model) {
|
||||
model->DeleteElement(spec.plugin.element);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
mjCActuator& mjCActuator::operator=(const mjCActuator& other) {
|
||||
if (this != &other) {
|
||||
this->spec = other.spec;
|
||||
@@ -6033,14 +6105,6 @@ mjCSensor::mjCSensor(const mjCSensor& other) {
|
||||
|
||||
|
||||
|
||||
mjCSensor::~mjCSensor() {
|
||||
if (spec.plugin.active && spec.plugin.name->empty() && model) {
|
||||
model->DeleteElement(spec.plugin.element);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
mjCSensor& mjCSensor::operator=(const mjCSensor& other) {
|
||||
if (this != &other) {
|
||||
this->spec = other.spec;
|
||||
|
||||
+23
-3
@@ -224,9 +224,23 @@ class mjCBase : public mjCBase_ {
|
||||
virtual void ForgetKeyframes() {}
|
||||
virtual void ForgetKeyframes() const {}
|
||||
|
||||
// increment and decrement reference count
|
||||
// release uses the argument to delete the plugin
|
||||
// which may be still owned by the source spec during shallow attach
|
||||
virtual void AddRef() { ++refcount; }
|
||||
virtual int GetRef() { return refcount; }
|
||||
virtual void Release() {
|
||||
if (--refcount == 0) {
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
mjCBase(); // constructor
|
||||
mjCBase(const mjCBase& other); // copy constructor
|
||||
|
||||
// reference count for allowing deleting an attached object
|
||||
int refcount = 1;
|
||||
};
|
||||
|
||||
|
||||
@@ -349,6 +363,15 @@ class mjCBody : public mjCBody_, private mjsBody {
|
||||
void SetParent(mjCBody* _body) { parent = _body; }
|
||||
mjCBody* GetParent() const { return parent; }
|
||||
|
||||
// set model of this body
|
||||
void SetModel(mjCModel* _model);
|
||||
|
||||
// reset ids of all objects in this body
|
||||
void ResetId();
|
||||
|
||||
// getters
|
||||
std::vector<mjCBody*> Bodies() const { return bodies; }
|
||||
|
||||
private:
|
||||
mjCBody(const mjCBody& other, mjCModel* _model); // copy constructor
|
||||
mjCBody& operator=(const mjCBody& other); // copy assignment
|
||||
@@ -537,7 +560,6 @@ class mjCGeom : public mjCGeom_, private mjsGeom {
|
||||
mjCGeom(mjCModel* = nullptr, mjCDef* = nullptr);
|
||||
mjCGeom(const mjCGeom& other);
|
||||
mjCGeom& operator=(const mjCGeom& other);
|
||||
~mjCGeom();
|
||||
|
||||
using mjCBase::name;
|
||||
mjsGeom spec; // variables set by user
|
||||
@@ -1505,7 +1527,6 @@ class mjCActuator : public mjCActuator_, private mjsActuator {
|
||||
mjCActuator(mjCModel* = nullptr, mjCDef* = nullptr);
|
||||
mjCActuator(const mjCActuator& other);
|
||||
mjCActuator& operator=(const mjCActuator& other);
|
||||
~mjCActuator();
|
||||
|
||||
mjsActuator spec;
|
||||
using mjCBase::name;
|
||||
@@ -1567,7 +1588,6 @@ class mjCSensor : public mjCSensor_, private mjsSensor {
|
||||
mjCSensor(mjCModel*);
|
||||
mjCSensor(const mjCSensor& other);
|
||||
mjCSensor& operator=(const mjCSensor& other);
|
||||
~mjCSensor();
|
||||
|
||||
mjsSensor spec;
|
||||
using mjCBase::name;
|
||||
|
||||
@@ -945,10 +945,16 @@ void mjXReader::Parse(XMLElement* root, const mjVFS* vfs) {
|
||||
Keyframe(section);
|
||||
}
|
||||
|
||||
// set deepcopy flag to true to copy child specs during attach calls
|
||||
mjs_setDeepCopy(spec, true);
|
||||
|
||||
for (XMLElement* section = FirstChildElement(root, "worldbody"); section;
|
||||
section = NextSiblingElement(section, "worldbody")) {
|
||||
Body(section, mjs_findBody(spec, "world"), nullptr, vfs);
|
||||
}
|
||||
|
||||
// set deepcopy flag to false to disable copying during attach in all future calls
|
||||
mjs_setDeepCopy(spec, false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+121
-38
@@ -262,35 +262,48 @@ TEST_F(PluginTest, AttachPlugin) {
|
||||
</mujoco>)";
|
||||
|
||||
std::array<char, 1000> err;
|
||||
mjSpec* spec_1 = mj_parseXMLString(xml_1, 0, err.data(), err.size());
|
||||
mjSpec* parent = mj_parseXMLString(xml_1, 0, err.data(), err.size());
|
||||
ASSERT_THAT(parent, NotNull()) << err.data();
|
||||
mjSpec* spec_1 = mj_parseXMLString(xml_2, 0, err.data(), err.size());
|
||||
ASSERT_THAT(spec_1, NotNull()) << err.data();
|
||||
mjSpec* spec_2 = mj_parseXMLString(xml_2, 0, err.data(), err.size());
|
||||
ASSERT_THAT(spec_2, NotNull()) << err.data();
|
||||
|
||||
mjsBody* body_1 = mjs_findBody(spec_1, "body");
|
||||
// do a copy before attaching
|
||||
mjSpec* spec_2 = mj_copySpec(spec_1);
|
||||
mjs_setString(spec_2->modelname, "first_copy");
|
||||
mjSpec* spec_3 = mj_copySpec(spec_1);
|
||||
mjs_setString(spec_3->modelname, "second_copy");
|
||||
ASSERT_THAT(spec_3, NotNull()) << err.data();
|
||||
|
||||
// attach a body referencing the plugin to the frame and compile
|
||||
mjsBody* body_1 = mjs_findBody(parent, "body");
|
||||
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_2, "body"), "child-", "");
|
||||
mjModel* model_1 = mj_compile(spec_1, nullptr);
|
||||
mjs_attachBody(attachment_frame, mjs_findBody(spec_1, "body"), "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
|
||||
// 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-", "");
|
||||
mjModel* model_2 = mj_compile(spec_1, nullptr);
|
||||
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
|
||||
mjs_attachBody(attachment_frame, mjs_findBody(spec_2, "empty"), "empty-", "");
|
||||
mjModel* model_3 = mj_compile(spec_1, nullptr);
|
||||
// attach a body not referencing the plugin and compile
|
||||
mjs_attachBody(attachment_frame, mjs_findBody(spec_3, "empty"), "empty-", "");
|
||||
mjModel* model_3 = mj_compile(parent, nullptr);
|
||||
EXPECT_THAT(model_3, NotNull());
|
||||
EXPECT_THAT(model_3->nbody, 5);
|
||||
|
||||
mj_deleteModel(model_1);
|
||||
mj_deleteModel(model_2);
|
||||
mj_deleteModel(model_3);
|
||||
mj_deleteSpec(parent);
|
||||
mj_deleteSpec(spec_1);
|
||||
mj_deleteSpec(spec_2);
|
||||
mj_deleteSpec(spec_3);
|
||||
}
|
||||
|
||||
TEST_F(PluginTest, AttachExplicitPlugin) {
|
||||
@@ -858,6 +871,7 @@ TEST_F(MujocoTest, AttachSame) {
|
||||
// create parent
|
||||
mjSpec* parent = mj_parseXMLString(xml_child, 0, er.data(), er.size());
|
||||
EXPECT_THAT(parent, NotNull()) << er.data();
|
||||
mjs_setDeepCopy(parent, true); // needed for self-attach
|
||||
|
||||
// get frame
|
||||
mjsFrame* frame = mjs_findFrame(parent, "frame");
|
||||
@@ -903,7 +917,7 @@ TEST_F(MujocoTest, AttachDifferent) {
|
||||
std::string field = "";
|
||||
|
||||
static constexpr char xml_parent[] = R"(
|
||||
<mujoco>
|
||||
<mujoco model="parent">
|
||||
<default>
|
||||
<default class="geom_size">
|
||||
<geom size="0.1"/>
|
||||
@@ -924,7 +938,7 @@ TEST_F(MujocoTest, AttachDifferent) {
|
||||
</mujoco>)";
|
||||
|
||||
static constexpr char xml_result[] = R"(
|
||||
<mujoco>
|
||||
<mujoco model="parent">
|
||||
<default>
|
||||
<default class="geom_size">
|
||||
<geom size="0.1"/>
|
||||
@@ -1048,7 +1062,7 @@ TEST_F(MujocoTest, AttachFrame) {
|
||||
std::string field = "";
|
||||
|
||||
static constexpr char xml_parent[] = R"(
|
||||
<mujoco>
|
||||
<mujoco model="parent">
|
||||
<worldbody>
|
||||
<body name="sphere">
|
||||
<freejoint/>
|
||||
@@ -1063,7 +1077,7 @@ TEST_F(MujocoTest, AttachFrame) {
|
||||
</mujoco>)";
|
||||
|
||||
static constexpr char xml_result[] = R"(
|
||||
<mujoco>
|
||||
<mujoco model="parent">
|
||||
<default>
|
||||
<default class="attached-cylinder-1">
|
||||
<geom type="cylinder" size=".1 1 0"/>
|
||||
@@ -1371,13 +1385,15 @@ TEST_F(MujocoTest, AttachWorld) {
|
||||
|
||||
mjSpec* parent = mj_parseXMLString(xml_parent, 0, er.data(), er.size());
|
||||
EXPECT_THAT(parent, NotNull()) << er.data();
|
||||
mjSpec* child = mj_parseXMLString(xml_child, 0, er.data(), er.size());
|
||||
EXPECT_THAT(child, NotNull()) << er.data();
|
||||
mjSpec* child1 = mj_parseXMLString(xml_child, 0, er.data(), er.size());
|
||||
EXPECT_THAT(child1, NotNull()) << er.data();
|
||||
mjSpec* child2 = mj_parseXMLString(xml_child, 0, er.data(), er.size());
|
||||
EXPECT_THAT(child2, NotNull()) << er.data();
|
||||
|
||||
// attach a body to the frame
|
||||
mjsFrame* frame = mjs_findFrame(parent, "frame");
|
||||
EXPECT_THAT(frame, NotNull());
|
||||
mjsBody* body = mjs_findBody(child, "sphere");
|
||||
mjsBody* body = mjs_findBody(child1, "sphere");
|
||||
EXPECT_THAT(body, NotNull());
|
||||
mjsBody* attached = mjs_attachBody(frame, body, "attached-", "-1");
|
||||
EXPECT_THAT(attached, NotNull());
|
||||
@@ -1385,7 +1401,7 @@ TEST_F(MujocoTest, AttachWorld) {
|
||||
EXPECT_THAT(model1, NotNull());
|
||||
|
||||
// attach the world to the same frame and convert it to a frame
|
||||
mjsBody* world = mjs_findBody(child, "world");
|
||||
mjsBody* world = mjs_findBody(child2, "world");
|
||||
EXPECT_THAT(world, NotNull());
|
||||
mjsBody* child_world = mjs_attachBody(frame, world, "attached-", "-2");
|
||||
EXPECT_THAT(child_world, NotNull());
|
||||
@@ -1403,7 +1419,8 @@ TEST_F(MujocoTest, AttachWorld) {
|
||||
<< "Different field: " << field << '\n';
|
||||
|
||||
mj_deleteSpec(parent);
|
||||
mj_deleteSpec(child);
|
||||
mj_deleteSpec(child1);
|
||||
mj_deleteSpec(child2);
|
||||
mj_deleteModel(model1);
|
||||
mj_deleteModel(model2);
|
||||
mj_deleteModel(expected);
|
||||
@@ -1577,11 +1594,13 @@ TEST_F(MujocoTest, RecompileAttach) {
|
||||
mjSpec* parent = mj_makeSpec();
|
||||
EXPECT_THAT(parent, NotNull());
|
||||
|
||||
mjSpec* child = mj_parseXMLString(xml, 0, er.data(), er.size());
|
||||
EXPECT_THAT(child, NotNull());
|
||||
mjSpec* child1 = mj_parseXMLString(xml, 0, er.data(), er.size());
|
||||
EXPECT_THAT(child1, NotNull());
|
||||
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(child, "body"), "child-", "-1");
|
||||
mjs_attachBody(frame1, mjs_findBody(child1, "body"), "child-", "-1");
|
||||
|
||||
mjModel* model = mj_compile(parent, 0);
|
||||
EXPECT_THAT(model, NotNull());
|
||||
@@ -1594,7 +1613,7 @@ TEST_F(MujocoTest, RecompileAttach) {
|
||||
}
|
||||
|
||||
mjsFrame* frame2 = mjs_addFrame(mjs_findBody(parent, "world"), 0);
|
||||
mjs_attachBody(frame2, mjs_findBody(child, "body"), "child-", "-2");
|
||||
mjs_attachBody(frame2, mjs_findBody(child2, "body"), "child-", "-2");
|
||||
|
||||
EXPECT_EQ(mj_recompile(parent, 0, model, data), 0);
|
||||
EXPECT_THAT(model, NotNull());
|
||||
@@ -1604,7 +1623,8 @@ TEST_F(MujocoTest, RecompileAttach) {
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
mj_deleteSpec(child);
|
||||
mj_deleteSpec(child1);
|
||||
mj_deleteSpec(child2);
|
||||
mj_deleteSpec(parent);
|
||||
}
|
||||
|
||||
@@ -1637,6 +1657,7 @@ TEST_F(MujocoTest, AttachMocap) {
|
||||
|
||||
mjSpec* spec = mj_parseXMLString(xml, 0, er.data(), er.size());
|
||||
EXPECT_THAT(spec, NotNull()) << er.data();
|
||||
mjs_setDeepCopy(spec, true); // needed for self-attach
|
||||
|
||||
mjsBody* body = mjs_findBody(spec, "mocap");
|
||||
EXPECT_THAT(body, NotNull());
|
||||
@@ -1732,8 +1753,8 @@ TEST_F(MujocoTest, AttachUnnamedAssets) {
|
||||
EXPECT_STREQ(mj_id2name(model, mjOBJ_MESH, 0), "_cube");
|
||||
|
||||
mj_deleteVFS(vfs.get());
|
||||
mj_deleteSpec(child);
|
||||
mj_deleteSpec(spec);
|
||||
mj_deleteSpec(child);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
@@ -1866,6 +1887,9 @@ void AttachNestedKeyframe(bool compile) {
|
||||
mjSpec* gchild = mj_parseXMLString(gchild_xml, 0, er.data(), er.size());
|
||||
EXPECT_THAT(gchild, NotNull()) << er.data();
|
||||
|
||||
mjs_setDeepCopy(parent, true);
|
||||
mjs_setDeepCopy(child, true);
|
||||
|
||||
// attach gchild to child
|
||||
mjs_attachBody(mjs_findFrame(child, "frame"),
|
||||
mjs_findBody(gchild, "body"), "gchild-", "");
|
||||
@@ -1937,18 +1961,18 @@ TEST_F(MujocoTest, RepeatedAttachKeyframe) {
|
||||
</mujoco>)";
|
||||
|
||||
std::array<char, 1000> er;
|
||||
mjSpec* spec_1 = mj_parseXMLString(xml_1, 0, er.data(), er.size());
|
||||
EXPECT_THAT(spec_1, NotNull()) << er.data();
|
||||
mjSpec* spec_2 = mj_parseXMLString(xml_2, 0, er.data(), er.size());
|
||||
EXPECT_THAT(spec_2, NotNull()) << er.data();
|
||||
mjSpec* parent = mj_parseXMLString(xml_1, 0, er.data(), er.size());
|
||||
EXPECT_THAT(parent, NotNull()) << er.data();
|
||||
mjSpec* child = mj_parseXMLString(xml_2, 0, er.data(), er.size());
|
||||
EXPECT_THAT(child, NotNull()) << er.data();
|
||||
|
||||
mjsBody* body_1 = mjs_findBody(spec_1, "body");
|
||||
mjsBody* body_1 = mjs_findBody(parent, "body");
|
||||
mjsFrame* attachment_frame = mjs_addFrame(body_1, 0);
|
||||
mjs_attachBody(attachment_frame, mjs_findBody(spec_2, "b1"), "b1-", "");
|
||||
mjModel* model_1 = mj_compile(spec_1, 0);
|
||||
mjs_attachBody(attachment_frame, mjs_findBody(child, "b1"), "b1-", "");
|
||||
mjModel* model_1 = mj_compile(parent, 0);
|
||||
EXPECT_THAT(model_1, NotNull());
|
||||
mjs_attachBody(attachment_frame, mjs_findBody(spec_2, "b2"), "b2-", "");
|
||||
mjModel* model_2 = mj_compile(spec_1, 0);
|
||||
mjs_attachBody(attachment_frame, mjs_findBody(child, "b2"), "b2-", "");
|
||||
mjModel* model_2 = mj_compile(parent, 0);
|
||||
EXPECT_THAT(model_2, NotNull());
|
||||
|
||||
EXPECT_EQ(model_1->nkey, 1);
|
||||
@@ -1956,8 +1980,8 @@ TEST_F(MujocoTest, RepeatedAttachKeyframe) {
|
||||
EXPECT_STREQ(mj_id2name(model_2, mjOBJ_KEY, 0), "b1-home");
|
||||
EXPECT_STREQ(mj_id2name(model_2, mjOBJ_KEY, 1), "b2-home");
|
||||
|
||||
mj_deleteSpec(spec_1);
|
||||
mj_deleteSpec(spec_2);
|
||||
mj_deleteSpec(parent);
|
||||
mj_deleteSpec(child);
|
||||
mj_deleteModel(model_1);
|
||||
mj_deleteModel(model_2);
|
||||
}
|
||||
@@ -2177,5 +2201,64 @@ TEST_F(MujocoTest, CopyAttachedSpec) {
|
||||
mj_deleteVFS(vfs.get());
|
||||
}
|
||||
|
||||
TEST_F(MujocoTest, ApplyNameSpaceToDefaults) {
|
||||
static constexpr char xml_c[] = R"(
|
||||
<mujoco>
|
||||
<default>
|
||||
<default class="mesh">
|
||||
<mesh scale="0.001 0.001 0.001"/>
|
||||
</default>
|
||||
</default>
|
||||
<asset>
|
||||
<mesh file="cube.obj" class="mesh"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<body name="body">
|
||||
<geom type="mesh" mesh="cube"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>)";
|
||||
|
||||
static constexpr char xml_p[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<frame name="parent"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
static constexpr char cube[] = R"(
|
||||
v -0.500000 -0.500000 0.500000
|
||||
v 0.500000 -0.500000 0.500000
|
||||
v -0.500000 0.500000 0.500000
|
||||
v 0.500000 0.500000 0.500000
|
||||
v -0.500000 0.500000 -0.500000
|
||||
v 0.500000 0.500000 -0.500000
|
||||
v -0.500000 -0.500000 -0.500000
|
||||
v 0.500000 -0.500000 -0.500000)";
|
||||
|
||||
auto vfs = std::make_unique<mjVFS>();
|
||||
mj_defaultVFS(vfs.get());
|
||||
mj_addBufferVFS(vfs.get(), "cube.obj", cube, sizeof(cube));
|
||||
|
||||
std::array<char, 1024> err;
|
||||
mjSpec* child = mj_parseXMLString(xml_c, vfs.get(), err.data(), err.size());
|
||||
EXPECT_THAT(child, NotNull()) << err.data();
|
||||
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-", "");
|
||||
EXPECT_THAT(attached, NotNull());
|
||||
|
||||
mjModel* model = mj_compile(parent, vfs.get());
|
||||
EXPECT_THAT(model, NotNull());
|
||||
|
||||
mj_deleteSpec(child);
|
||||
mj_deleteSpec(parent);
|
||||
mj_deleteModel(model);
|
||||
mj_deleteVFS(vfs.get());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
|
||||
@@ -545,6 +545,7 @@ TEST_F(MujocoTest, Modeldir) {
|
||||
|
||||
// parent attaching the child
|
||||
mjSpec* spec = mj_makeSpec();
|
||||
mjs_setDeepCopy(spec, true);
|
||||
mjs_setString(spec->meshdir, "asset");
|
||||
mjs_attachFrame(mjs_findBody(spec, "world"), frame, "_", "");
|
||||
mjModel* model = mj_compile(spec, vfs.get());
|
||||
|
||||
Reference in New Issue
Block a user