Add error handling to mj_recompile.

This CL adds error handling to the mj_recompile function. If the recompile fails, the function will return -1 and set the error message in the spec. The Python wrapper for mj_recompile will catch the error and raise a ValueError exception.

PiperOrigin-RevId: 666784579
Change-Id: I225eca1769ea839c782be3c03fc4ff1ea5885a48
This commit is contained in:
Alessio Quaglino
2024-08-23 07:17:26 -07:00
committed by Copybara-Service
parent 088079eff0
commit 505d01a1c1
10 changed files with 77 additions and 20 deletions
+5 -5
View File
@@ -47,7 +47,9 @@ mj_compile
.. mujoco-include:: mj_compile
Compile spec to model.
Compile :ref:`mjSpec` to :ref:`mjModel`. A spec can be edited and compiled multiple times, returning a new
:ref:`mjModel` instance that takes the edits into account.
If compilation fails, :ref:`mj_compile` returns ``NULL``; the error can be read with :ref:`mjs_getError`.
.. _mj_recompile:
@@ -63,10 +65,8 @@ reallocate existing :ref:`mjModel` and :ref:`mjData` instances in-place. Second,
newly added or removed degrees of freedom. This allows the user to continue simulation with the same model and data
struct pointers while editing the model programmatically.
.. admonition:: Incomplete implementation
:class: attention
This function is currently incomplete, preserving only ``mjData.qpos`` and ``mjData.qvel``.
:ref:`mj_recompile` returns 0 if compilation succeed. In the case of failure, the given :ref:`mjModel` and :ref:`mjData`
instances will be deleted; as in :ref:`mj_compile`, the compilation error can be read with :ref:`mjs_getError`.
.. _mj_saveLastXML:
+8 -4
View File
@@ -29,6 +29,12 @@ The key function here is :ref:`mj_loadXML`. It invokes the built-in parser and c
a valid mjModel, or NULL - in which case the user should check the error information in the user-provided string.
The model and all files referenced in it can be loaded from disk or from a VFS when provided.
.. _mj_compile:
Compile :ref:`mjSpec` to :ref:`mjModel`. A spec can be edited and compiled multiple times, returning a new
:ref:`mjModel` instance that takes the edits into account.
If compilation fails, :ref:`mj_compile` returns ``NULL``; the error can be read with :ref:`mjs_getError`.
.. _mj_recompile:
Recompile spec to model, preserving the state. Like :ref:`mj_compile`, this function compiles an :ref:`mjSpec` to an
@@ -38,10 +44,8 @@ reallocate existing :ref:`mjModel` and :ref:`mjData` instances in-place. Second,
newly added or removed degrees of freedom. This allows the user to continue simulation with the same model and data
struct pointers while editing the model programmatically.
.. admonition:: Incomplete implementation
:class: attention
This function is currently incomplete, preserving only ``mjData.qpos`` and ``mjData.qvel``.
:ref:`mj_recompile` returns 0 if compilation succeed. In the case of failure, the given :ref:`mjModel` and :ref:`mjData`
instances will be deleted; as in :ref:`mj_compile`, the compilation error can be read with :ref:`mjs_getError`.
.. _Mainsimulation:
+1 -1
View File
@@ -3103,7 +3103,7 @@ mjModel* mj_loadXML(const char* filename, const mjVFS* vfs, char* error, int err
mjSpec* mj_parseXML(const char* filename, const mjVFS* vfs, char* error, int error_sz);
mjSpec* mj_parseXMLString(const char* xml, const mjVFS* vfs, char* error, int error_sz);
mjModel* mj_compile(mjSpec* s, const mjVFS* vfs);
void mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d);
int mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d);
int mj_saveLastXML(const char* filename, const mjModel* m, char* error, int error_sz);
void mj_freeLastXML(void);
void mj_copyBack(mjSpec* s, const mjModel* m);
+2 -2
View File
@@ -106,8 +106,8 @@ MJAPI mjSpec* mj_parseXMLString(const char* xml, const mjVFS* vfs, char* error,
// Compile spec to model.
MJAPI mjModel* mj_compile(mjSpec* s, const mjVFS* vfs);
// Recompile spec to model, preserving the state.
MJAPI void mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d);
// Recompile spec to model, preserving the state, return 0 on success.
MJAPI int mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d);
// Update XML data structures with info from low-level model, save as MJCF.
// If error is not NULL, it must have size error_sz.
+2 -2
View File
@@ -251,7 +251,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
('mj_recompile',
FunctionDecl(
name='mj_recompile',
return_type=ValueType(name='void'),
return_type=ValueType(name='int'),
parameters=(
FunctionParameterDecl(
name='s',
@@ -278,7 +278,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
),
),
),
doc='Recompile spec to model, preserving the state.',
doc='Recompile spec to model, preserving the state, return 0 on success.', # pylint: disable=line-too-long
)),
('mj_saveLastXML',
FunctionDecl(
+24
View File
@@ -352,5 +352,29 @@ class SpecsTest(absltest.TestCase):
self.assertEqual(model.nplugin, 1)
self.assertEqual(model.body_plugin[1], 0)
def test_recompile_error(self):
main_xml = """
<mujoco>
<worldbody>
<body>
<geom size="0.1"/>
</body>
</worldbody>
</mujoco>
"""
spec = mujoco.MjSpec()
spec.from_string(main_xml)
model = spec.compile()
data = mujoco.MjData(model)
spec.add_material().name = 'yellow'
spec.add_material().name = 'yellow'
with self.assertRaisesRegex(
ValueError, "Error: repeated name 'yellow' in material"
):
spec.recompile(model, data)
if __name__ == '__main__':
absltest.main()
+3 -1
View File
@@ -456,7 +456,9 @@ py::tuple RecompileSpec(raw::MjSpec* spec, const MjModelWrapper& old_m,
raw::MjModel* m = static_cast<raw::MjModel*>(mju_malloc(sizeof(mjModel)));
m->buffer = nullptr;
raw::MjData* d = mj_copyData(nullptr, old_m.get(), old_d.get());
mj_recompile(spec, nullptr, m, d);
if (mj_recompile(spec, nullptr, m, d)) {
throw py::value_error(mjs_getError(spec));
}
py::object m_pyobj = py::cast((MjModelWrapper(m)));
py::object d_pyobj =
+9 -3
View File
@@ -90,8 +90,8 @@ mjModel* mj_compile(mjSpec* s, const mjVFS* vfs) {
// recompile spec into existing model and data while preserving the state
void mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d) {
// recompile spec to model, preserving the state, return 0 on success
[[nodiscard]] int mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d) {
mjCModel* modelC = static_cast<mjCModel*>(s->element);
std::string state_name = "state";
mjtNum time = 0;
@@ -99,13 +99,19 @@ void mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d) {
time = d->time;
modelC->SaveState(state_name, d->qpos, d->qvel, d->act, d->ctrl, d->mocap_pos, d->mocap_quat);
}
modelC->Compile(vfs, &m);
if (!modelC->Compile(vfs, &m)) {
if (d) {
mj_deleteData(d);
}
return -1;
};
if (d) {
modelC->MakeData(m, &d);
modelC->RestoreState(state_name, m->qpos0, m->body_pos, m->body_quat, d->qpos, d->qvel,
d->act, d->ctrl, d->mocap_pos, d->mocap_quat);
d->time = time;
}
return 0;
}
+2 -2
View File
@@ -42,8 +42,8 @@ MJAPI mjSpec* mj_makeSpec(void);
// Compile spec to model.
MJAPI mjModel* mj_compile(mjSpec* s, const mjVFS* vfs);
// Recompile spec to model preserving the current state.
MJAPI void mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d);
// Recompile spec to model, preserving the state, return 0 on success.
MJAPI int mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d);
// Copy spec.
MJAPI mjSpec* mj_copySpec(const mjSpec* s);
+21
View File
@@ -140,6 +140,27 @@ TEST_F(PluginTest, ActivatePlugin) {
mj_deleteModel(model);
}
TEST_F(MujocoTest, RecompileFails) {
mjSpec* spec = mj_makeSpec();
mjsBody* body = mjs_addBody(mjs_findBody(spec, "world"), 0);
mjsGeom* geom = mjs_addGeom(body, 0);
geom->type = mjGEOM_SPHERE;
geom->size[0] = 1;
mjModel* model = mj_compile(spec, 0);
mjData* data = mj_makeData(model);
mjsMaterial* mat1 = mjs_addMaterial(spec, 0);
mjsMaterial* mat2 = mjs_addMaterial(spec, 0);
mjs_setString(mat1->name, "yellow");
mjs_setString(mat2->name, "yellow");
EXPECT_EQ(mj_recompile(spec, 0, model, data), -1);
EXPECT_STREQ(mjs_getError(spec), "Error: repeated name 'yellow' in material");
mj_deleteSpec(spec);
}
// ------------------- test recompilation multiple files -----------------------
TEST_F(PluginTest, RecompileCompare) {
mjtNum tol = 0;