Skip to content

Commit b839fe7

Browse files
quaglacopybara-github
authored andcommitted
Allow to attach an entire mjSpec in mjs_attach.
PiperOrigin-RevId: 742737713 Change-Id: Ie172568b8c35232e1f13d75aebee20346e76e95b
1 parent e5912c3 commit b839fe7

File tree

4 files changed

+63
-42
lines changed

4 files changed

+63
-42
lines changed

doc/XMLreference.rst

+3-2
Original file line numberDiff line numberDiff line change
@@ -3716,8 +3716,9 @@ all attachments will appear in the saved XML file.
37163716

37173717
.. _body-attach-body:
37183718

3719-
:at:`body`: :at-val:`string, required`
3720-
Name of the body in the sub-model to attach here. The body and its subtree will be attached.
3719+
:at:`body`: :at-val:`string, optional`
3720+
Name of the body in the sub-model to attach here. The body and its subtree will be attached. If this attribute is not
3721+
specified, the contents of the world body will be attached in a new :ref:`frame<body-frame>`.
37213722

37223723
.. _body-attach-prefix:
37233724

python/mujoco/specs.cc

+2-31
Original file line numberDiff line numberDiff line change
@@ -269,16 +269,6 @@ py::list FindAllImpl(raw::MjsBody& body, mjtObj objtype, bool recursive) {
269269
return list; // list of pointers, so they can be copied
270270
}
271271

272-
void SetFrame(raw::MjsBody* body, mjtObj objtype, raw::MjsFrame* frame) {
273-
mjsElement* el = mjs_firstChild(body, objtype, 0);
274-
while (el) {
275-
if (frame->element != el && mjs_getFrame(el) == nullptr) {
276-
mjs_setFrame(el, frame);
277-
}
278-
el = mjs_nextChild(body, el, 0);
279-
}
280-
}
281-
282272
PYBIND11_MODULE(_specs, m) {
283273
auto structs_m = py::module::import("mujoco._structs");
284274
py::function mjmodel_from_raw_ptr =
@@ -526,18 +516,6 @@ PYBIND11_MODULE(_specs, m) {
526516
throw pybind11::value_error(
527517
"Only one of frame or site can be specified.");
528518
}
529-
auto worldbody = mjs_findBody(child.ptr, "world");
530-
if (!worldbody) {
531-
throw pybind11::value_error("Child does not have a world body.");
532-
}
533-
auto worldframe = mjs_addFrame(worldbody, nullptr);
534-
SetFrame(worldbody, mjOBJ_BODY, worldframe);
535-
SetFrame(worldbody, mjOBJ_SITE, worldframe);
536-
SetFrame(worldbody, mjOBJ_FRAME, worldframe);
537-
SetFrame(worldbody, mjOBJ_JOINT, worldframe);
538-
SetFrame(worldbody, mjOBJ_GEOM, worldframe);
539-
SetFrame(worldbody, mjOBJ_LIGHT, worldframe);
540-
SetFrame(worldbody, mjOBJ_CAMERA, worldframe);
541519
const char* p = prefix.has_value() ? prefix.value().c_str() : "";
542520
const char* s = suffix.has_value() ? suffix.value().c_str() : "";
543521
raw::MjsElement* attached_frame = nullptr;
@@ -556,18 +534,11 @@ PYBIND11_MODULE(_specs, m) {
556534
throw pybind11::value_error(
557535
"Frame spec does not match parent spec.");
558536
}
559-
raw::MjsBody* parent_body = mjs_getParent(frame_ptr->element);
560-
if (!parent_body) {
561-
throw pybind11::value_error("Frame does not have a parent body.");
562-
}
563537
attached_frame =
564-
mjs_attach(parent_body->element, worldframe->element, p, s);
538+
mjs_attach(frame_ptr->element, child.ptr->element, p, s);
565539
if (!attached_frame) {
566540
throw pybind11::value_error(mjs_getError(self.ptr));
567541
}
568-
if (mjs_setFrame(attached_frame, frame_ptr) != 0) {
569-
throw pybind11::value_error(mjs_getError(self.ptr));
570-
}
571542
}
572543
if (site.has_value()) {
573544
raw::MjsSite* site_ptr = nullptr;
@@ -585,7 +556,7 @@ PYBIND11_MODULE(_specs, m) {
585556
"Site spec does not match parent spec.");
586557
}
587558
attached_frame =
588-
mjs_attach(site_ptr->element, worldframe->element, p, s);
559+
mjs_attach(site_ptr->element, child.ptr->element, p, s);
589560
if (!attached_frame) {
590561
throw pybind11::value_error(mjs_getError(self.ptr));
591562
}

src/user/user_api.cc

+44-1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,18 @@ mjModel* mj_compile(mjSpec* s, const mjVFS* vfs) {
119119
}
120120

121121

122+
// set frame for all elements of a body
123+
static void SetFrame(mjsBody* body, mjtObj objtype, mjsFrame* frame) {
124+
mjsElement* el = mjs_firstChild(body, objtype, 0);
125+
while (el) {
126+
if (frame->element != el && mjs_getFrame(el) == nullptr) {
127+
mjs_setFrame(el, frame);
128+
}
129+
el = mjs_nextChild(body, el, 0);
130+
}
131+
}
132+
133+
122134

123135
// attach body to a frame of the parent
124136
static mjsElement* attachBody(mjCFrame* parent, const mjCBody* child,
@@ -207,13 +219,44 @@ mjsElement* mjs_attach(mjsElement* parent, const mjsElement* child,
207219
return nullptr;
208220
}
209221
mjCModel* model = static_cast<mjCModel*>(mjs_getSpec(parent)->element);
222+
if (child->elemtype == mjOBJ_MODEL) {
223+
mjCModel* child_model = static_cast<mjCModel*>((mjsElement*)child);
224+
mjsBody* worldbody = mjs_findBody(&child_model->spec, "world");
225+
if (!worldbody) {
226+
model->SetError(mjCError(0, "Child does not have a world body."));
227+
return nullptr;
228+
}
229+
mjsFrame* worldframe = mjs_addFrame(worldbody, nullptr);
230+
SetFrame(worldbody, mjOBJ_BODY, worldframe);
231+
SetFrame(worldbody, mjOBJ_SITE, worldframe);
232+
SetFrame(worldbody, mjOBJ_FRAME, worldframe);
233+
SetFrame(worldbody, mjOBJ_JOINT, worldframe);
234+
SetFrame(worldbody, mjOBJ_GEOM, worldframe);
235+
SetFrame(worldbody, mjOBJ_LIGHT, worldframe);
236+
SetFrame(worldbody, mjOBJ_CAMERA, worldframe);
237+
child = worldframe->element;
238+
}
210239
switch (parent->elemtype) {
211240
case mjOBJ_FRAME:
212241
if (child->elemtype == mjOBJ_BODY) {
213242
return attachBody(static_cast<mjCFrame*>(parent),
214243
static_cast<const mjCBody*>(child), prefix, suffix);
244+
} else if (child->elemtype == mjOBJ_FRAME) {
245+
mjsBody* parent_body = mjs_getParent(parent);
246+
if (!parent_body) {
247+
model->SetError(mjCError(0, "Frame does not have a parent body."));
248+
return nullptr;
249+
}
250+
mjCFrame* frame = static_cast<mjCFrame*>(parent);
251+
mjsElement* attached_frame =
252+
attachFrame(static_cast<mjCBody*>(parent_body->element),
253+
static_cast<const mjCFrame*>(child), prefix, suffix);
254+
if (mjs_setFrame(attached_frame, &frame->spec)) {
255+
return nullptr;
256+
}
257+
return attached_frame;
215258
} else {
216-
model->SetError(mjCError(0, "child element is not a body"));
259+
model->SetError(mjCError(0, "child element is not a body or frame"));
217260
return nullptr;
218261
}
219262
case mjOBJ_BODY:

src/xml/xml_native_reader.cc

+14-8
Original file line numberDiff line numberDiff line change
@@ -3627,27 +3627,33 @@ void mjXReader::Body(XMLElement* section, mjsBody* body, mjsFrame* frame,
36273627
else if (name == "attach") {
36283628
string model_name, body_name, prefix;
36293629
ReadAttrTxt(elem, "model", model_name, /*required=*/true);
3630-
ReadAttrTxt(elem, "body", body_name, /*required=*/true);
3630+
ReadAttrTxt(elem, "body", body_name, /*required=*/false);
36313631
ReadAttrTxt(elem, "prefix", prefix, /*required=*/true);
36323632

3633-
mjsBody* child = mjs_findBody(spec, (prefix+body_name).c_str());
3633+
mjsBody* child_body = mjs_findBody(spec, (prefix+body_name).c_str());
36343634
mjsFrame* pframe = frame ? frame : mjs_addFrame(body, nullptr);
36353635

3636-
if (!child) {
3636+
if (!child_body) {
36373637
mjSpec* asset = mjs_findSpec(spec, model_name.c_str());
36383638
if (!asset) {
36393639
throw mjXError(elem, "could not find model '%s'", model_name.c_str());
36403640
}
3641-
child = mjs_findBody(asset, body_name.c_str());
3642-
if (!child) {
3643-
throw mjXError(elem, "could not find body '%s''%s'", body_name.c_str());
3641+
mjsElement* child;
3642+
if (body_name.empty()) {
3643+
child = asset->element;
3644+
} else {
3645+
child_body = mjs_findBody(asset, body_name.c_str());
3646+
if (!child_body) {
3647+
throw mjXError(elem, "could not find body '%s''%s'", body_name.c_str());
3648+
}
3649+
child = child_body->element;
36443650
}
3645-
if (!mjs_attach(pframe->element, child->element, prefix.c_str(), "")) {
3651+
if (!mjs_attach(pframe->element, child, prefix.c_str(), "")) {
36463652
throw mjXError(elem, mjs_getError(spec));
36473653
}
36483654
} else {
36493655
// only set frame to existing body
3650-
if (mjs_setFrame(child->element, pframe)) {
3656+
if (mjs_setFrame(child_body->element, pframe)) {
36513657
throw mjXError(elem, mjs_getError(spec));
36523658
}
36533659
}

0 commit comments

Comments
 (0)