Throw an error if a child attached by reference to a parent is compiled.

Fixes #2482.

PiperOrigin-RevId: 735665098
Change-Id: Ia3436fdd2ff572f8f0c1ded434742a4a9186f2f7
This commit is contained in:
Alessio Quaglino
2025-03-11 01:23:31 -07:00
committed by Copybara-Service
parent 14dc6fd33b
commit 019e0a806c
5 changed files with 38 additions and 2 deletions
+4 -2
View File
@@ -147,8 +147,10 @@ or :ref:`attach a frame to a body<mjs_attachFrame>`:
Note that in the above examples, the parent and child models have different values for ``compiler.degree``,
corresponding to the :ref:`compiler/angle<compiler-angle>` attribute, specifying the units in which angles are
interperted. Compiler options are carried over during attachment, so the child model will be compiled using X, while the
parent will be compiled using Y.
interperted. Compiler flags are carried over during attachment, so the child model will be compiled using the child
flags, while the parent will be compiled using the parent flags.
Note also that once a child is attached by reference to a parent, the child cannot be compiled on its own.
.. _meDefault:
+3
View File
@@ -3972,6 +3972,9 @@ mjModel* mjCModel::Compile(const mjVFS* vfs, mjModel** m) {
warningtext[0] = 0;
try {
if (attached_) {
throw mjCError(0, "cannot compile child spec if attached by reference to a parent spec");
}
if (setjmp(error_jmp_buf) != 0) {
// TryCompile resulted in an mju_error which was converted to a longjmp.
std::string error_msg = errortext;
+4
View File
@@ -317,6 +317,9 @@ class mjCModel : public mjCModel_, private mjSpec {
// set deepcopy flag
void SetDeepCopy(bool deepcopy) { deepcopy_ = deepcopy; }
// set attached flag
void SetAttached(bool deepcopy) { attached_ |= !deepcopy; }
private:
// settings for each defaults class
std::vector<mjCDef*> defaults_;
@@ -428,5 +431,6 @@ class mjCModel : public mjCModel_, private mjSpec {
mjCError errInfo; // last error info
std::vector<mjKeyInfo> key_pending_; // attached keyframes
bool deepcopy_; // copy objects when attaching
bool attached_ = false; // true if model is attached to a parent model
};
#endif // MUJOCO_SRC_USER_USER_MODEL_H_
+2
View File
@@ -941,6 +941,7 @@ mjCBody& mjCBody::operator+=(const mjCFrame& other) {
}
// attach referencing elements
other_model->SetAttached(model->deepcopy_);
*model += *other_model;
// leave the source model in a clean state
@@ -1947,6 +1948,7 @@ mjCFrame& mjCFrame::operator+=(const mjCBody& other) {
last_attached = &body->bodies.back()->spec;
// attach referencing elements
other_model->SetAttached(model->deepcopy_);
*model += *other_model;
// leave the source model in a clean state
+25
View File
@@ -2544,5 +2544,30 @@ TEST_F(MujocoTest, ApplyNameSpaceToDefaults) {
mj_deleteVFS(vfs.get());
}
TEST_F(MujocoTest, ErrorWhenCompilingOrphanedSpec) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<body name="a"/>
</worldbody>
</mujoco>
)";
std::array<char, 1024> er;
mjSpec* child = mj_parseXMLString(xml, 0, er.data(), er.size());
EXPECT_THAT(child, NotNull()) << er.data();
mjSpec* parent = mj_makeSpec();
EXPECT_THAT(parent, NotNull());
mjsBody* body = mjs_findBody(child, "a");
EXPECT_THAT(body, NotNull());
mjsFrame* frame = mjs_addFrame(mjs_findBody(parent, "world"), nullptr);
EXPECT_THAT(frame, NotNull());
mjs_attachBody(frame, body, "child-", "");
mj_deleteSpec(parent);
mjModel* model = mj_compile(child, 0);
EXPECT_THAT(model, IsNull());
EXPECT_THAT(mjs_getError(child), HasSubstr("by reference to a parent"));
mj_deleteSpec(child);
}
} // namespace
} // namespace mujoco