Skip to content

Commit f607d95

Browse files
quaglacopybara-github
authored andcommitted
Check that there are no pending keyframes while writing a spec.
This can happen when a body is detached without compiling or recompiling. Also added error catching when trying to delete a body instead of detaching it. Fixes #2327 PiperOrigin-RevId: 712526730 Change-Id: I4b48df83120fca12d475c85b3a54893841449653
1 parent b5df2c1 commit f607d95

File tree

9 files changed

+37
-15
lines changed

9 files changed

+37
-15
lines changed

doc/APIreference/functions.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3918,7 +3918,7 @@ Add frame to body.
39183918

39193919
.. mujoco-include:: mjs_delete
39203920

3921-
Delete object corresponding to the given element.
3921+
Delete object corresponding to the given element, return 0 on success.
39223922

39233923
.. _AddNonTreeElements:
39243924

doc/includes/references.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3578,7 +3578,7 @@ mjsGeom* mjs_addGeom(mjsBody* body, const mjsDefault* def);
35783578
mjsCamera* mjs_addCamera(mjsBody* body, const mjsDefault* def);
35793579
mjsLight* mjs_addLight(mjsBody* body, const mjsDefault* def);
35803580
mjsFrame* mjs_addFrame(mjsBody* body, mjsFrame* parentframe);
3581-
void mjs_delete(mjsElement* element);
3581+
int mjs_delete(mjsElement* element);
35823582
mjsActuator* mjs_addActuator(mjSpec* s, const mjsDefault* def);
35833583
mjsSensor* mjs_addSensor(mjSpec* s);
35843584
mjsFlex* mjs_addFlex(mjSpec* s);

include/mujoco/mujoco.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ MJAPI void mj_freeLastXML(void);
121121
// If length of the output buffer is too small, returns the required size.
122122
MJAPI int mj_saveXMLString(const mjSpec* s, char* xml, int xml_sz, char* error, int error_sz);
123123

124-
// Save spec to XML file, return 1 on success, 0 otherwise.
124+
// Save spec to XML file, return 0 on success, -1 otherwise.
125125
MJAPI int mj_saveXML(const mjSpec* s, const char* filename, char* error, int error_sz);
126126

127127

@@ -1453,8 +1453,8 @@ MJAPI mjsLight* mjs_addLight(mjsBody* body, const mjsDefault* def);
14531453
// Add frame to body.
14541454
MJAPI mjsFrame* mjs_addFrame(mjsBody* body, mjsFrame* parentframe);
14551455

1456-
// Delete object corresponding to the given element.
1457-
MJAPI void mjs_delete(mjsElement* element);
1456+
// Delete object corresponding to the given element, return 0 on success.
1457+
MJAPI int mjs_delete(mjsElement* element);
14581458

14591459

14601460
//---------------------------------- Non-tree elements ---------------------------------------------

introspect/functions.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@
379379
type=ValueType(name='int'),
380380
),
381381
),
382-
doc='Save spec to XML file, return 1 on success, 0 otherwise.',
382+
doc='Save spec to XML file, return 0 on success, -1 otherwise.',
383383
)),
384384
('mj_step',
385385
FunctionDecl(
@@ -9277,7 +9277,7 @@
92779277
('mjs_delete',
92789278
FunctionDecl(
92799279
name='mjs_delete',
9280-
return_type=ValueType(name='void'),
9280+
return_type=ValueType(name='int'),
92819281
parameters=(
92829282
FunctionParameterDecl(
92839283
name='element',
@@ -9286,7 +9286,7 @@
92869286
),
92879287
),
92889288
),
9289-
doc='Delete object corresponding to the given element.',
9289+
doc='Delete object corresponding to the given element, return 0 on success.', # pylint: disable=line-too-long
92909290
)),
92919291
('mjs_addActuator',
92929292
FunctionDecl(

src/user/user_api.cc

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,10 +254,17 @@ int mjs_activatePlugin(mjSpec* s, const char* name) {
254254

255255

256256

257-
// delete object, it will call the appropriate destructor since ~mjCBase is virtual
258-
void mjs_delete(mjsElement* element) {
257+
// delete object, return 0 if success
258+
int mjs_delete(mjsElement* element) {
259259
mjCBase* object = static_cast<mjCBase*>(element);
260-
object->model->DeleteElement(element);
260+
try {
261+
// it will call the appropriate destructor since ~mjCBase is virtual
262+
object->model->DeleteElement(element);
263+
return 0;
264+
} catch (mjCError& e) {
265+
object->model->SetError(e);
266+
return -1;
267+
}
261268
}
262269

263270

src/user/user_api.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ MJAPI mjsLight* mjs_addLight(mjsBody* body, const mjsDefault* def);
108108
// Add frame to body.
109109
MJAPI mjsFrame* mjs_addFrame(mjsBody* body, mjsFrame* parentframe);
110110

111-
// Delete object corresponding to the given element.
112-
MJAPI void mjs_delete(mjsElement* element);
111+
// Delete object corresponding to the given element, return 0 on success.
112+
MJAPI int mjs_delete(mjsElement* element);
113113

114114

115115
//---------------------------------- Add non-tree elements -----------------------------------------

src/user/user_model.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,6 @@ mjCModel& mjCModel::operator-=(const mjCBody& subtree) {
530530
ResetTreeLists();
531531
}
532532

533-
PointToLocal();
534533
return *this;
535534
}
536535

src/xml/xml_native_writer.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ mjXWriter::mjXWriter(void) {
858858
// cast model
859859
void mjXWriter::SetModel(const mjSpec* _spec, const mjModel* m) {
860860
if (_spec) {
861-
model = (mjCModel*)_spec->element;
861+
model = static_cast<mjCModel*>(_spec->element);
862862
}
863863
if (m) {
864864
model->CopyBack(m);
@@ -2207,6 +2207,10 @@ void mjXWriter::Keyframe(XMLElement* root) {
22072207
// create section
22082208
XMLElement* section = InsertEnd(root, "keyframe");
22092209

2210+
if (!model->key_pending_.empty()) {
2211+
throw mjXError(0, "Model has pending keyframes. It must be (re)compiled before writing XML.");
2212+
}
2213+
22102214
// write all keyframes
22112215
for (int i=0; i<model->nkey; i++) {
22122216
XMLElement* elem = InsertEnd(section, "key");

test/user/user_api_test.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,9 +1222,21 @@ void TestDetachBody(bool compile) {
12221222
mjsBody* body = mjs_findBody(child, "body");
12231223
EXPECT_THAT(body, NotNull());
12241224

1225+
// get an error if trying to delete the body
1226+
EXPECT_EQ(mjs_delete(body->element), -1);
1227+
EXPECT_THAT(mjs_getError(child), HasSubstr("use detach instead"));
1228+
12251229
// detach subtree
12261230
EXPECT_THAT(mjs_detachBody(child, body), 0);
12271231

1232+
// try saving to XML before compiling again
1233+
std::array<char, 1024> e;
1234+
std::array<char, 1024> s;
1235+
EXPECT_EQ(mj_saveXMLString(child, s.data(), 1024, e.data(), 1024), -1);
1236+
EXPECT_THAT(e.data(), compile
1237+
? HasSubstr("Model has pending keyframes")
1238+
: HasSubstr("Only compiled model can be written"));
1239+
12281240
// compile new model
12291241
mjModel* m_detached = mj_compile(child, 0);
12301242
EXPECT_THAT(m_detached, NotNull());

0 commit comments

Comments
 (0)