From 159f23e6b40d3554022bce24ed97283abe373531 Mon Sep 17 00:00:00 2001 From: Alessio Quaglino Date: Thu, 24 Oct 2024 08:29:03 -0700 Subject: [PATCH] Remember compiler options during attach. Note: - Move compiler options into `mjsCompiler` struct. - The compiler options from the attached model are not written by mj_SaveXML. PiperOrigin-RevId: 689391440 Change-Id: I1d63c146a32f87c737b7a55b64a54b0ffe3aecc9 --- doc/APIreference/APItypes.rst | 10 +++ doc/includes/references.h | 19 +++-- include/mujoco/mjspec.h | 21 +++-- introspect/structs.py | 84 ++++++++++-------- .../mujoco/codegen/generate_spec_bindings.py | 8 +- python/mujoco/raw.h | 1 + python/mujoco/specs.cc | 1 + python/mujoco/specs_test.py | 40 ++++----- src/user/user_api.cc | 11 ++- src/user/user_flexcomp.cc | 7 +- src/user/user_flexcomp.h | 2 +- src/user/user_init.c | 22 ++--- src/user/user_mesh.cc | 4 +- src/user/user_model.cc | 46 ++++------ src/user/user_objects.cc | 85 +++++++++++++------ src/user/user_objects.h | 3 +- src/xml/xml.cc | 4 +- src/xml/xml_native_reader.cc | 38 ++++----- src/xml/xml_native_writer.cc | 20 ++--- src/xml/xml_urdf.cc | 4 +- test/user/user_api_test.cc | 48 ++++++++--- test/user/user_model_test.cc | 4 +- test/user/user_objects_test.cc | 4 +- unity/Runtime/Bindings/MjBindings.cs | 20 +++++ 24 files changed, 302 insertions(+), 204 deletions(-) diff --git a/doc/APIreference/APItypes.rst b/doc/APIreference/APItypes.rst index d9ca5fd3..fba959b2 100644 --- a/doc/APIreference/APItypes.rst +++ b/doc/APIreference/APItypes.rst @@ -1140,6 +1140,16 @@ behavior. .. mujoco-include:: mjsElement +.. _mjsCompiler: + +mjsCompiler +~~~~~~~~~~ + +Compiler options. + +.. mujoco-include:: mjsCompiler + + .. _mjsBody: mjsBody diff --git a/doc/includes/references.h b/doc/includes/references.h index 9f348881..f1cd64a7 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -1683,22 +1683,15 @@ typedef enum mjtOrientation_ { // type of orientation specifier typedef struct mjsElement_ { // element type, do not modify mjtObj elemtype; // element type } mjsElement; -typedef struct mjSpec_ { // model specification - mjsElement* element; // element type - mjString* modelname; // model name - - // compiler settings +typedef struct mjsCompiler_ { // compiler options mjtByte autolimits; // infer "limited" attribute based on range double boundmass; // enforce minimum body mass double boundinertia; // enforce minimum body diagonal inertia double settotalmass; // rescale masses and inertias; <=0: ignore mjtByte balanceinertia; // automatically impose A + B >= C rule - mjtByte strippath; // automatically strip paths from mesh files mjtByte fitaabb; // meshfit to aabb instead of inertia box mjtByte degree; // angles in radians or degrees char eulerseq[3]; // sequence for euler rotations - mjString* meshdir; // mesh and hfield directory - mjString* texturedir; // texture directory mjtByte discardvisual; // discard visual geoms in parser mjtByte convexhull; // compute mesh convex hulls mjtByte usethread; // use multiple threads to speed up compiler @@ -1707,6 +1700,16 @@ typedef struct mjSpec_ { // model specification int inertiagrouprange[2]; // range of geom groups used to compute inertia int alignfree; // align free joints with inertial frame mjLROpt LRopt; // options for lengthrange computation +} mjsCompiler; +typedef struct mjSpec_ { // model specification + mjsElement* element; // element type + mjString* modelname; // model name + + // compiler data + mjsCompiler compiler; // compiler options + mjtByte strippath; // automatically strip paths from mesh files + mjString* meshdir; // mesh and hfield directory + mjString* texturedir; // texture directory // engine data mjOption option; // physics options diff --git a/include/mujoco/mjspec.h b/include/mujoco/mjspec.h index c6d38c79..58feb8f8 100644 --- a/include/mujoco/mjspec.h +++ b/include/mujoco/mjspec.h @@ -121,22 +121,15 @@ typedef struct mjsElement_ { // element type, do not modify } mjsElement; -typedef struct mjSpec_ { // model specification - mjsElement* element; // element type - mjString* modelname; // model name - - // compiler settings +typedef struct mjsCompiler_ { // compiler options mjtByte autolimits; // infer "limited" attribute based on range double boundmass; // enforce minimum body mass double boundinertia; // enforce minimum body diagonal inertia double settotalmass; // rescale masses and inertias; <=0: ignore mjtByte balanceinertia; // automatically impose A + B >= C rule - mjtByte strippath; // automatically strip paths from mesh files mjtByte fitaabb; // meshfit to aabb instead of inertia box mjtByte degree; // angles in radians or degrees char eulerseq[3]; // sequence for euler rotations - mjString* meshdir; // mesh and hfield directory - mjString* texturedir; // texture directory mjtByte discardvisual; // discard visual geoms in parser mjtByte convexhull; // compute mesh convex hulls mjtByte usethread; // use multiple threads to speed up compiler @@ -145,6 +138,18 @@ typedef struct mjSpec_ { // model specification int inertiagrouprange[2]; // range of geom groups used to compute inertia int alignfree; // align free joints with inertial frame mjLROpt LRopt; // options for lengthrange computation +} mjsCompiler; + + +typedef struct mjSpec_ { // model specification + mjsElement* element; // element type + mjString* modelname; // model name + + // compiler data + mjsCompiler compiler; // compiler options + mjtByte strippath; // automatically strip paths from mesh files + mjString* meshdir; // mesh and hfield directory + mjString* texturedir; // texture directory // engine data mjOption option; // physics options diff --git a/introspect/structs.py b/introspect/structs.py index 449ac230..9f8c8b4c 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -8800,25 +8800,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([ ), ), )), - ('mjSpec', + ('mjsCompiler', StructDecl( - name='mjSpec', - declname='struct mjSpec_', + name='mjsCompiler', + declname='struct mjsCompiler_', fields=( - StructFieldDecl( - name='element', - type=PointerType( - inner_type=ValueType(name='mjsElement'), - ), - doc='element type', - ), - StructFieldDecl( - name='modelname', - type=PointerType( - inner_type=ValueType(name='mjString'), - ), - doc='model name', - ), StructFieldDecl( name='autolimits', type=ValueType(name='mjtByte'), @@ -8844,11 +8830,6 @@ STRUCTS: Mapping[str, StructDecl] = dict([ type=ValueType(name='mjtByte'), doc='automatically impose A + B >= C rule', ), - StructFieldDecl( - name='strippath', - type=ValueType(name='mjtByte'), - doc='automatically strip paths from mesh files', - ), StructFieldDecl( name='fitaabb', type=ValueType(name='mjtByte'), @@ -8867,20 +8848,6 @@ STRUCTS: Mapping[str, StructDecl] = dict([ ), doc='sequence for euler rotations', ), - StructFieldDecl( - name='meshdir', - type=PointerType( - inner_type=ValueType(name='mjString'), - ), - doc='mesh and hfield directory', - ), - StructFieldDecl( - name='texturedir', - type=PointerType( - inner_type=ValueType(name='mjString'), - ), - doc='texture directory', - ), StructFieldDecl( name='discardvisual', type=ValueType(name='mjtByte'), @@ -8924,6 +8891,51 @@ STRUCTS: Mapping[str, StructDecl] = dict([ type=ValueType(name='mjLROpt'), doc='options for lengthrange computation', ), + ), + )), + ('mjSpec', + StructDecl( + name='mjSpec', + declname='struct mjSpec_', + fields=( + StructFieldDecl( + name='element', + type=PointerType( + inner_type=ValueType(name='mjsElement'), + ), + doc='element type', + ), + StructFieldDecl( + name='modelname', + type=PointerType( + inner_type=ValueType(name='mjString'), + ), + doc='model name', + ), + StructFieldDecl( + name='compiler', + type=ValueType(name='mjsCompiler'), + doc='compiler options', + ), + StructFieldDecl( + name='strippath', + type=ValueType(name='mjtByte'), + doc='automatically strip paths from mesh files', + ), + StructFieldDecl( + name='meshdir', + type=PointerType( + inner_type=ValueType(name='mjString'), + ), + doc='mesh and hfield directory', + ), + StructFieldDecl( + name='texturedir', + type=PointerType( + inner_type=ValueType(name='mjString'), + ), + doc='texture directory', + ), StructFieldDecl( name='option', type=ValueType(name='mjOption'), diff --git a/python/mujoco/codegen/generate_spec_bindings.py b/python/mujoco/codegen/generate_spec_bindings.py index e02733c1..700ff883 100644 --- a/python/mujoco/codegen/generate_spec_bindings.py +++ b/python/mujoco/codegen/generate_spec_bindings.py @@ -39,8 +39,12 @@ def _value_binding_code( fullvarname = 'ptr->' + varname if field.name.startswith('mjs'): # all other mjs are raw structs fulltype = field.name.replace('mjs', 'raw::Mjs') - if field.name == 'mjsPlugin' or field.name == 'mjsOrientation': - fulltype = fulltype + '&' # plugin and orientation are not pointers + if ( + field.name == 'mjsPlugin' + or field.name == 'mjsOrientation' + or field.name == 'mjsCompiler' + ): + fulltype = fulltype + '&' # plugin, orientation, compiler are not pointers else: fulltype = fulltype + '*' # non-mjs structs diff --git a/python/mujoco/raw.h b/python/mujoco/raw.h index 3d481a12..7776e88c 100644 --- a/python/mujoco/raw.h +++ b/python/mujoco/raw.h @@ -58,6 +58,7 @@ using MjsText = ::mjsText; using MjsTuple = ::mjsTuple; using MjsKey = ::mjsKey; using MjsDefault = ::mjsDefault; +using MjsCompiler = ::mjsCompiler; using MjOption = ::mjOption; using MjSolverStat = ::mjSolverStat; using MjStatistic = ::mjStatistic; diff --git a/python/mujoco/specs.cc b/python/mujoco/specs.cc index b70ac9a7..2359de42 100644 --- a/python/mujoco/specs.cc +++ b/python/mujoco/specs.cc @@ -194,6 +194,7 @@ PYBIND11_MODULE(_specs, m) { py::class_ mjOption(m, "MjOption"); py::class_ mjStatistic(m, "MjStatistic"); py::class_ mjVisual(m, "MjVisual"); + py::class_ mjsCompiler(m, "MjsCompiler"); DefineArray(m, "MjCharVec"); DefineArray(m, "MjStringVec"); DefineArray(m, "MjByteVec"); diff --git a/python/mujoco/specs_test.py b/python/mujoco/specs_test.py index e04292cc..b5955e5c 100644 --- a/python/mujoco/specs_test.py +++ b/python/mujoco/specs_test.py @@ -34,28 +34,28 @@ class SpecsTest(absltest.TestCase): spec = mujoco.MjSpec() # Check that euler sequence order is set correctly. - self.assertEqual(spec.eulerseq[0], 'x') - spec.eulerseq = ['z', 'y', 'x'] - self.assertEqual(spec.eulerseq[0], 'z') + self.assertEqual(spec.compiler.eulerseq[0], 'x') + spec.compiler.eulerseq = ['z', 'y', 'x'] + self.assertEqual(spec.compiler.eulerseq[0], 'z') # Change single elements of euler sequence. - spec.eulerseq[0] = 'y' - spec.eulerseq[1] = 'z' - self.assertEqual(spec.eulerseq[0], 'y') - self.assertEqual(spec.eulerseq[1], 'z') + spec.compiler.eulerseq[0] = 'y' + spec.compiler.eulerseq[1] = 'z' + self.assertEqual(spec.compiler.eulerseq[0], 'y') + self.assertEqual(spec.compiler.eulerseq[1], 'z') # eulerseq is iterable - self.assertEqual('yzx', ''.join(spec.eulerseq)) + self.assertEqual('yzx', ''.join(spec.compiler.eulerseq)) # supports `len` - self.assertLen(spec.eulerseq, 3) + self.assertLen(spec.compiler.eulerseq, 3) # field checks for out-of-bound access on read and on write with self.assertRaises(IndexError): - spec.eulerseq[3] = 'x' + spec.compiler.eulerseq[3] = 'x' with self.assertRaises(IndexError): - spec.eulerseq[-1] = 'x' + spec.compiler.eulerseq[-1] = 'x' # Add a body, check that it has default orientation. body = spec.worldbody.add_body() @@ -843,19 +843,15 @@ class SpecsTest(absltest.TestCase): with self.assertRaises(IndexError): material.textures[-1] = 'x' - def test_attach_error(self): + def test_attach_units(self): child = mujoco.MjSpec() parent = mujoco.MjSpec() - parent.degree = not child.degree - body = parent.worldbody.add_body() - frame = child.worldbody.add_frame() - with self.assertRaises(ValueError) as cm: - body.attach_frame(frame, '_', '') - self.assertEqual( - str(cm.exception), - 'Error: cannot attach mjSpecs with incompatible compiler/angle' - ' attribute', - ) + parent.compiler.degree = not child.compiler.degree + body = child.worldbody.add_body(euler=[90, 0, 0]) + frame = parent.worldbody.add_frame(euler=[-mujoco.mjPI / 2, 0, 0]) + frame.attach_body(body, 'child-', '') + 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() diff --git a/src/user/user_api.cc b/src/user/user_api.cc index a1412146..6b5a7769 100644 --- a/src/user/user_api.cc +++ b/src/user/user_api.cc @@ -69,7 +69,13 @@ mjSpec* mj_makeSpec() { // copy model mjSpec* mj_copySpec(const mjSpec* s) { - mjCModel* modelC = new mjCModel(*static_cast(s->element)); + mjCModel* modelC = nullptr; + try { + modelC = new mjCModel(*static_cast(s->element)); + } catch (mjCError& e) { + mju_error("Failed to copy spec: %s", e.message); + return nullptr; + } return &modelC->spec; } @@ -177,7 +183,8 @@ mjsBody* mjs_attachToSite(mjsSite* parent, const mjsBody* child, frame->spec.quat[1] = site->spec.quat[1]; frame->spec.quat[2] = site->spec.quat[2]; frame->spec.quat[3] = site->spec.quat[3]; - mjs_resolveOrientation(frame->spec.quat, spec->degree, spec->eulerseq, &site->spec.alt); + mjs_resolveOrientation(frame->spec.quat, spec->compiler.degree, + spec->compiler.eulerseq, &site->spec.alt); return mjs_attachBody(&frame->spec, child, prefix, suffix); } diff --git a/src/user/user_flexcomp.cc b/src/user/user_flexcomp.cc index 2f4aa428..732d87ba 100644 --- a/src/user/user_flexcomp.cc +++ b/src/user/user_flexcomp.cc @@ -98,8 +98,9 @@ mjCFlexcomp::mjCFlexcomp(void) { // make flexcomp object -bool mjCFlexcomp::Make(mjSpec* spec, mjsBody* body, char* error, int error_sz) { - mjCModel* model = (mjCModel*)spec->element; +bool mjCFlexcomp::Make(mjsBody* body, char* error, int error_sz) { + mjCModel* model = static_cast(body->element)->model; + mjsCompiler* compiler = static_cast(body->element)->compiler; mjsFlex* dflex = def.spec.flex; bool radial = (type == mjFCOMPTYPE_BOX || @@ -147,7 +148,7 @@ bool mjCFlexcomp::Make(mjSpec* spec, mjsBody* body, char* error, int error_sz) { } // compute orientation - const char* alterr = mjs_resolveOrientation(quat, model->spec.degree, model->spec.eulerseq, &alt); + const char* alterr = mjs_resolveOrientation(quat, compiler->degree, compiler->eulerseq, &alt); if (alterr) { return comperr(error, alterr, error_sz); } diff --git a/src/user/user_flexcomp.h b/src/user/user_flexcomp.h index d090022b..92ce884c 100644 --- a/src/user/user_flexcomp.h +++ b/src/user/user_flexcomp.h @@ -42,7 +42,7 @@ typedef enum _mjtFcompType { class mjCFlexcomp { public: mjCFlexcomp(void); - bool Make(mjSpec* spec, mjsBody* body, char* error, int error_sz); + bool Make(mjsBody* body, char* error, int error_sz); bool MakeGrid(char* error, int error_sz); bool MakeBox(char* error, int error_sz); diff --git a/src/user/user_init.c b/src/user/user_init.c index d2ef5798..b5d15413 100644 --- a/src/user/user_init.c +++ b/src/user/user_init.c @@ -33,17 +33,17 @@ void mjs_defaultSpec(mjSpec* spec) { spec->stat.center[0] = mjNAN; // compiler settings - spec->autolimits = 1; - spec->settotalmass = -1; - spec->degree = 1; - spec->eulerseq[0] = 'x'; - spec->eulerseq[1] = 'y'; - spec->eulerseq[2] = 'z'; - spec->convexhull = 1; - spec->usethread = 1; - spec->inertiafromgeom = mjINERTIAFROMGEOM_AUTO; - spec->inertiagrouprange[1] = mjNGROUP-1; - mj_defaultLROpt(&spec->LRopt); + spec->compiler.autolimits = 1; + spec->compiler.settotalmass = -1; + spec->compiler.degree = 1; + spec->compiler.eulerseq[0] = 'x'; + spec->compiler.eulerseq[1] = 'y'; + spec->compiler.eulerseq[2] = 'z'; + spec->compiler.convexhull = 1; + spec->compiler.usethread = 1; + spec->compiler.inertiafromgeom = mjINERTIAFROMGEOM_AUTO; + spec->compiler.inertiagrouprange[1] = mjNGROUP-1; + mj_defaultLROpt(&spec->compiler.LRopt); // engine data mj_defaultOption(&spec->option); diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc index fbe2383c..d1e20f46 100644 --- a/src/user/user_mesh.cc +++ b/src/user/user_mesh.cc @@ -563,7 +563,7 @@ void mjCMesh::Compile(const mjVFS* vfs) { } // make graph describing convex hull - if ((model->convexhull && needhull_) || face_.empty()) { + if ((model->compiler.convexhull && needhull_) || face_.empty()) { MakeGraph(); } @@ -739,7 +739,7 @@ void mjCMesh::FitGeom(mjCGeom* geom, double* meshpos) { mjuu_copyvec(meshpos, GetPosPtr(geom->typeinertia), 3); // use inertial box - if (!model->fitaabb) { + if (!model->compiler.fitaabb) { // get inertia box type (shell or volume) double* boxsz = GetInertiaBoxPtr(geom->typeinertia); switch (geom->type) { diff --git a/src/user/user_model.cc b/src/user/user_model.cc index 82e3d941..99ab9a11 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -227,8 +227,10 @@ void mjCModel::CopyList(std::vector& dest, } // copy the element from the other model to this model source[i]->ForgetKeyframes(); + mjSpec* origin = FindSpec(mjs_getString(source[i]->model->spec.modelname)); dest.push_back(candidate); dest.back()->model = this; + dest.back()->compiler = origin ? &origin->compiler : &spec.compiler; dest.back()->id = -1; } if (!dest.empty()) { @@ -354,20 +356,6 @@ static bool IsPluginActive( mjCModel& mjCModel::operator+=(const mjCModel& other) { - // TODO: use compiler settings stored in specs_ during compilation - std::string msg = "cannot attach mjSpecs with incompatible compiler/"; - if (other.spec.degree != spec.degree) { - throw mjCError(nullptr, (msg + "angle attribute").c_str()); - } - if (other.spec.autolimits != spec.autolimits) { - throw mjCError(nullptr, (msg + "autolimits attribute").c_str()); - } - if (other.spec.eulerseq[0] != spec.eulerseq[0] || - other.spec.eulerseq[1] != spec.eulerseq[1] || - other.spec.eulerseq[2] != spec.eulerseq[2]) { - throw mjCError(nullptr, (msg + "eulerseq attribute").c_str()); - } - // create global lists mjCBody *world = bodies_[0]; ResetTreeLists(); @@ -1784,8 +1772,8 @@ void mjCModel::LengthRange(mjModel* m, mjData* data) { mjOption saveopt = m->opt; m->opt.disableflags = mjDSBL_FRICTIONLOSS | mjDSBL_CONTACT | mjDSBL_PASSIVE | mjDSBL_GRAVITY | mjDSBL_ACTUATION; - if (LRopt.timestep>0) { - m->opt.timestep = LRopt.timestep; + if (compiler.LRopt.timestep>0) { + m->opt.timestep = compiler.LRopt.timestep; } // number of threads available @@ -1800,14 +1788,14 @@ void mjCModel::LengthRange(mjModel* m, mjData* data) { m->actuator_biastype[i]==mjBIAS_MUSCLE); int isuser = (m->actuator_gaintype[i]==mjGAIN_USER || m->actuator_biastype[i]==mjBIAS_USER); - if ((LRopt.mode==mjLRMODE_NONE) || - (LRopt.mode==mjLRMODE_MUSCLE && !ismuscle) || - (LRopt.mode==mjLRMODE_MUSCLEUSER && !ismuscle && !isuser)) { + if ((compiler.LRopt.mode==mjLRMODE_NONE) || + (compiler.LRopt.mode==mjLRMODE_MUSCLE && !ismuscle) || + (compiler.LRopt.mode==mjLRMODE_MUSCLEUSER && !ismuscle && !isuser)) { continue; } // use existing length range if available - if (LRopt.useexisting && + if (compiler.LRopt.useexisting && (m->actuator_lengthrange[2*i] < m->actuator_lengthrange[2*i+1])) { continue; } @@ -1817,10 +1805,10 @@ void mjCModel::LengthRange(mjModel* m, mjData* data) { } // single thread - if (!usethread || cnt<2 || nthread<2) { + if (!compiler.usethread || cnt<2 || nthread<2) { char err[200]; for (int i=0; inu; i++) { - if (!mj_setLengthRange(m, data, i, &LRopt, err, 200)) { + if (!mj_setLengthRange(m, data, i, &compiler.LRopt, err, 200)) { throw mjCError(0, "%s", err); } } @@ -1844,7 +1832,7 @@ void mjCModel::LengthRange(mjModel* m, mjData* data) { // prepare thread function arguments, clear errors LRThreadArg arg[kMaxCompilerThreads]; for (int i=0; i 1) { + if (compiler.usethread && meshes_.size() > 1) { // multi-threaded mesh compile CompileMeshes(vfs); } else { @@ -3981,10 +3969,10 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) { reassignid(excludes_); // resolve asset references, compute sizes - IndexAssets(discardvisual); + IndexAssets(compiler.discardvisual); SetSizes(); // fuse static if enabled - if (fusestatic) { + if (compiler.fusestatic) { FuseStatic(); } @@ -4144,8 +4132,8 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) { CopyObjects(m); // scale mass - if (settotalmass>0) { - mj_setTotalmass(m, settotalmass); + if (compiler.settotalmass>0) { + mj_setTotalmass(m, compiler.settotalmass); } // set arena size into m->narena diff --git a/src/user/user_objects.cc b/src/user/user_objects.cc index 4c084b22..e795f49a 100644 --- a/src/user/user_objects.cc +++ b/src/user/user_objects.cc @@ -749,6 +749,7 @@ void mjCBase::SetFrame(mjCFrame* _frame) { mjCBody::mjCBody(mjCModel* _model) { // set model pointer model = _model; + if (_model) compiler = &_model->spec.compiler; mjs_defaultBody(&spec); elemtype = mjOBJ_BODY; @@ -785,6 +786,8 @@ mjCBody::mjCBody(mjCModel* _model) { mjCBody::mjCBody(const mjCBody& other, mjCModel* _model) { model = _model; + mjSpec* origin = model->FindSpec(mjs_getString(other.model->spec.modelname)); + compiler = origin ? &origin->compiler : &model->spec.compiler; *this = other; } @@ -868,9 +871,11 @@ mjCBody& mjCBody::operator+=(const mjCFrame& other) { } // copy input frame + mjSpec* origin = model->FindSpec(mjs_getString(other.model->spec.modelname)); frames.push_back(new mjCFrame(other)); 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); int i = frames.size(); @@ -931,9 +936,11 @@ void mjCBody::CopyList(std::vector& dst, const std::vector& src, if (pframe && !pframe->IsAncestor(src[i]->frame)) { continue; // skip if the element is not inside pframe } + mjSpec* origin = model->FindSpec(mjs_getString(src[i]->model->spec.modelname)); dst.push_back(new T(*src[i])); dst.back()->body = this; dst.back()->model = model; + dst.back()->compiler = origin ? &origin->compiler : &model->spec.compiler; dst.back()->id = -1; dst.back()->classname = src[i]->classname; @@ -1413,8 +1420,8 @@ void mjCBody::InertiaFromGeom(void) { // select geoms based on group sel.clear(); for (int i=0; igroup>=model->inertiagrouprange[0] && - geoms[i]->group<=model->inertiagrouprange[1]) { + if (geoms[i]->group>=compiler->inertiagrouprange[0] && + geoms[i]->group<=compiler->inertiagrouprange[1]) { sel.push_back(geoms[i]); } } @@ -1559,7 +1566,7 @@ void mjCBody::Compile(void) { // check and process orientation alternatives for body if (alt.type != mjORIENTATION_QUAT) { - const char* err = ResolveOrientation(quat, model->degree, model->eulerseq, alt); + const char* err = ResolveOrientation(quat, compiler->degree, compiler->eulerseq, alt); if (err) { throw mjCError(this, "error '%s' in frame alternative", err); } @@ -1582,7 +1589,7 @@ void mjCBody::Compile(void) { } if (ialt.type != mjORIENTATION_QUAT) { - const char* err = ResolveOrientation(iquat, model->degree, model->eulerseq, ialt); + const char* err = ResolveOrientation(iquat, compiler->degree, compiler->eulerseq, ialt); if (err) { throw mjCError(this, "error '%s' in inertia alternative", err); } @@ -1591,15 +1598,15 @@ void mjCBody::Compile(void) { // compile all geoms for (int i=0; iinferinertia = id>0 && - (!explicitinertial || model->inertiafromgeom == mjINERTIAFROMGEOM_TRUE) && - geoms[i]->spec.group >= model->inertiagrouprange[0] && - geoms[i]->spec.group <= model->inertiagrouprange[1]; + (!explicitinertial || compiler->inertiafromgeom == mjINERTIAFROMGEOM_TRUE) && + geoms[i]->spec.group >= compiler->inertiagrouprange[0] && + geoms[i]->spec.group <= compiler->inertiagrouprange[1]; geoms[i]->Compile(); } // set inertial frame from geoms if necessary - if (id>0 && (model->inertiafromgeom==mjINERTIAFROMGEOM_TRUE || - (!mjuu_defined(ipos[0]) && model->inertiafromgeom==mjINERTIAFROMGEOM_AUTO))) { + if (id>0 && (compiler->inertiafromgeom==mjINERTIAFROMGEOM_TRUE || + (!mjuu_defined(ipos[0]) && compiler->inertiafromgeom==mjINERTIAFROMGEOM_AUTO))) { InertiaFromGeom(); } @@ -1612,10 +1619,10 @@ void mjCBody::Compile(void) { // check and correct mass and inertia if (id>0) { // fix minimum - mass = std::max(mass, model->boundmass); - inertia[0] = std::max(inertia[0], model->boundinertia); - inertia[1] = std::max(inertia[1], model->boundinertia); - inertia[2] = std::max(inertia[2], model->boundinertia); + mass = std::max(mass, compiler->boundmass); + inertia[0] = std::max(inertia[0], compiler->boundinertia); + inertia[1] = std::max(inertia[1], compiler->boundinertia); + inertia[2] = std::max(inertia[2], compiler->boundinertia); // check for negative values if (mass<0 || inertia[0]<0 || inertia[1]<0 ||inertia[2]<0) { @@ -1626,7 +1633,7 @@ void mjCBody::Compile(void) { if (inertia[0] + inertia[1] < inertia[2] || inertia[0] + inertia[2] < inertia[1] || inertia[1] + inertia[2] < inertia[0]) { - if (model->balanceinertia) { + if (compiler->balanceinertia) { inertia[0] = inertia[1] = inertia[2] = (inertia[0] + inertia[1] + inertia[2])/3.0; } else { throw mjCError(this, "inertia must satisfy A + B >= C; use 'balanceinertia' to fix"); @@ -1654,7 +1661,7 @@ void mjCBody::Compile(void) { bodies.empty() && // no child bodies AND (joints[0]->spec.align == 1 || // either joint.align="true" (joints[0]->spec.align == 2 && // or joint.align="auto" - model->alignfree))); // and compiler.align="true" + compiler->alignfree))); // and compiler->align="true" // free-joint alignment, phase 1 (this body + child geoms) double ipos_inverse[3], iquat_inverse[4]; @@ -1740,7 +1747,7 @@ void mjCBody::Compile(void) { } // if discarding visual geoms, use explicit inertias - if (model->discardvisual) { + if (compiler->discardvisual) { for (int j=0; jIsVisual()) { explicitinertial = true; @@ -1782,6 +1789,7 @@ mjCFrame::mjCFrame(mjCModel* _model, mjCFrame* _frame) { elemtype = mjOBJ_FRAME; compiled = false; model = _model; + if (_model) compiler = &_model->spec.compiler; body = NULL; frame = _frame ? _frame : NULL; last_attached = nullptr; @@ -1905,7 +1913,7 @@ void mjCFrame::Compile() { } CopyFromSpec(); - const char* err = ResolveOrientation(quat, model->spec.degree, model->spec.eulerseq, alt); + const char* err = ResolveOrientation(quat, compiler->degree, compiler->eulerseq, alt); if (err) { throw mjCError(this, "orientation specification error '%s' in site %d", err, id); } @@ -1940,6 +1948,7 @@ mjCJoint::mjCJoint(mjCModel* _model, mjCDef* _def) { // set model, def model = _model; + if (_model) compiler = &_model->spec.compiler; classname = _def ? _def->name : "main"; // point to local @@ -2069,7 +2078,7 @@ int mjCJoint::Compile(void) { // otherwise if limited is auto, check consistency wrt auto-limits else if (limited == mjLIMITED_AUTO) { bool hasrange = !(range[0]==0 && range[1]==0); - checklimited(this, model->autolimits, "joint", "", limited, hasrange); + checklimited(this, compiler->autolimits, "joint", "", limited, hasrange); } // resolve limits @@ -2083,7 +2092,7 @@ int mjCJoint::Compile(void) { } // convert limits to radians - if (model->degree && (type==mjJNT_HINGE || type==mjJNT_BALL)) { + if (compiler->degree && (type==mjJNT_HINGE || type==mjJNT_BALL)) { if (range[0]) { range[0] *= mjPI/180.0; } @@ -2100,7 +2109,7 @@ int mjCJoint::Compile(void) { // otherwise if actfrclimited is auto, check consistency wrt auto-limits else if (actfrclimited == mjLIMITED_AUTO) { bool hasrange = !(actfrcrange[0]==0 && actfrcrange[1]==0); - checklimited(this, model->autolimits, "joint", "", actfrclimited, hasrange); + checklimited(this, compiler->autolimits, "joint", "", actfrclimited, hasrange); } // resolve actuator force range limits @@ -2144,7 +2153,7 @@ int mjCJoint::Compile(void) { } // convert reference angles to radians for hinge joints - if (type==mjJNT_HINGE && model->degree) { + if (type==mjJNT_HINGE && compiler->degree) { ref *= mjPI/180.0; springref *= mjPI/180.0; } @@ -2193,6 +2202,7 @@ mjCGeom::mjCGeom(mjCModel* _model, mjCDef* _def) { // set model, def model = _model; + if (_model) compiler = &_model->spec.compiler; classname = _def ? _def->name : "main"; // point to local @@ -2892,7 +2902,7 @@ void mjCGeom::Compile(void) { // not 'fromto': try alternative else { - const char* err = ResolveOrientation(quat, model->degree, model->eulerseq, alt); + const char* err = ResolveOrientation(quat, compiler->degree, compiler->eulerseq, alt); if (err) { throw mjCError(this, "orientation specification error '%s' in geom %d", err, id); } @@ -3034,6 +3044,7 @@ mjCSite::mjCSite(mjCModel* _model, mjCDef* _def) { // set model, def model = _model; + if (_model) compiler = &_model->spec.compiler; classname = _def ? _def->name : "main"; } @@ -3140,7 +3151,7 @@ void mjCSite::Compile(void) { // alternative orientation else { - const char* err = ResolveOrientation(quat, model->degree, model->eulerseq, alt); + const char* err = ResolveOrientation(quat, compiler->degree, compiler->eulerseq, alt); if (err) { throw mjCError(this, "orientation specification error '%s' in site %d", err, id); } @@ -3179,6 +3190,7 @@ mjCCamera::mjCCamera(mjCModel* _model, mjCDef* _def) { // set model, def model = _model; + if (_model) compiler = &_model->spec.compiler; classname = _def ? _def->name : "main"; // point to local @@ -3248,7 +3260,7 @@ void mjCCamera::Compile(void) { userdata_.resize(model->nuser_cam); // process orientation specifications - const char* err = ResolveOrientation(quat, model->degree, model->eulerseq, alt); + const char* err = ResolveOrientation(quat, compiler->degree, compiler->eulerseq, alt); if (err) { throw mjCError(this, "orientation specification error '%s' in camera %d", err, id); } @@ -3330,6 +3342,7 @@ mjCLight::mjCLight(mjCModel* _model, mjCDef* _def) { // set model, def model = _model; + if (_model) compiler = &_model->spec.compiler; classname = _def ? _def->name : "main"; PointToLocal(); @@ -3423,6 +3436,7 @@ mjCHField::mjCHField(mjCModel* _model) { // set model pointer model = _model; + if (_model) compiler = &_model->spec.compiler; // clear variables data.clear(); @@ -3678,6 +3692,7 @@ mjCTexture::mjCTexture(mjCModel* _model) { // set model pointer model = _model; + if (_model) compiler = &_model->spec.compiler; // clear user settings: single file spec_file_.clear(); @@ -4511,6 +4526,7 @@ mjCMaterial::mjCMaterial(mjCModel* _model, mjCDef* _def) { } model = _model; + if (_model) compiler = &_model->spec.compiler; classname = _def ? _def->name : "main"; PointToLocal(); @@ -4597,6 +4613,7 @@ mjCPair::mjCPair(mjCModel* _model, mjCDef* _def) { // set model, def model = _model; + if (_model) compiler = &_model->spec.compiler; classname = _def ? _def->name : "main"; // point to local @@ -4817,6 +4834,7 @@ void mjCPair::Compile(void) { mjCBodyPair::mjCBodyPair(mjCModel* _model) { // set model pointer model = _model; + if (_model) compiler = &_model->spec.compiler; elemtype = mjOBJ_EXCLUDE; // set defaults @@ -4955,6 +4973,7 @@ mjCEquality::mjCEquality(mjCModel* _model, mjCDef* _def) { // set model, def model = _model; + if (_model) compiler = &_model->spec.compiler; classname = _def ? _def->name : "main"; // point to local @@ -5116,6 +5135,7 @@ mjCTendon::mjCTendon(mjCModel* _model, mjCDef* _def) { // set model, def model = _model; + if (_model) compiler = &_model->spec.compiler; classname = _def ? _def->name : "main"; // point to local @@ -5201,6 +5221,7 @@ mjCTendon::~mjCTendon() { void mjCTendon::SetModel(mjCModel* _model) { model = _model; + if (_model) compiler = &_model->spec.compiler; for (int i=0; imodel = _model; } @@ -5423,7 +5444,7 @@ void mjCTendon::Compile(void) { // if limited is auto, set to 1 if range is specified, otherwise unlimited if (limited == mjLIMITED_AUTO) { bool hasrange = !(range[0]==0 && range[1]==0); - checklimited(this, model->autolimits, "tendon", "", limited, hasrange); + checklimited(this, compiler->autolimits, "tendon", "", limited, hasrange); } // check limits @@ -5447,6 +5468,7 @@ mjCWrap::mjCWrap(mjCModel* _model, mjCTendon* _tendon) { // set model and tendon pointer model = _model; + if (_model) compiler = &_model->spec.compiler; tendon = _tendon; // clear variables @@ -5591,6 +5613,7 @@ mjCActuator::mjCActuator(mjCModel* _model, mjCDef* _def) { // set model, def model = _model; + if (_model) compiler = &_model->spec.compiler; classname = _def ? _def->name : "main"; // in case this actuator is not compiled @@ -5846,15 +5869,15 @@ void mjCActuator::Compile(void) { // if limited is auto, check for inconsistency wrt to autolimits if (forcelimited == mjLIMITED_AUTO) { bool hasrange = !(forcerange[0]==0 && forcerange[1]==0); - checklimited(this, model->autolimits, "actuator", "force", forcelimited, hasrange); + checklimited(this, compiler->autolimits, "actuator", "force", forcelimited, hasrange); } if (ctrllimited == mjLIMITED_AUTO) { bool hasrange = !(ctrlrange[0]==0 && ctrlrange[1]==0); - checklimited(this, model->autolimits, "actuator", "ctrl", ctrllimited, hasrange); + checklimited(this, compiler->autolimits, "actuator", "ctrl", ctrllimited, hasrange); } if (actlimited == mjLIMITED_AUTO) { bool hasrange = !(actrange[0]==0 && actrange[1]==0); - checklimited(this, model->autolimits, "actuator", "act", actlimited, hasrange); + checklimited(this, compiler->autolimits, "actuator", "act", actlimited, hasrange); } // check limits @@ -5951,6 +5974,7 @@ mjCSensor::mjCSensor(mjCModel* _model) { // set model model = _model; + if (_model) compiler = &_model->spec.compiler; // clear private variables spec_objname_.clear(); @@ -6477,6 +6501,7 @@ mjCNumeric::mjCNumeric(mjCModel* _model) { // set model pointer model = _model; + if (_model) compiler = &_model->spec.compiler; // clear variables spec_data_.clear(); @@ -6566,6 +6591,7 @@ mjCText::mjCText(mjCModel* _model) { // set model pointer model = _model; + if (_model) compiler = &_model->spec.compiler; // clear variables spec_data_.clear(); @@ -6643,6 +6669,7 @@ mjCTuple::mjCTuple(mjCModel* _model) { // set model pointer model = _model; + if (_model) compiler = &_model->spec.compiler; // clear variables spec_objtype_.clear(); @@ -6776,6 +6803,7 @@ mjCKey::mjCKey(mjCModel* _model) { // set model pointer model = _model; + if (_model) compiler = &_model->spec.compiler; // clear variables spec_qpos_.clear(); @@ -6952,6 +6980,7 @@ mjCPlugin::mjCPlugin(mjCModel* _model) { plugin_slot = -1; parent = this; model = _model; + if (_model) compiler = &_model->spec.compiler; name.clear(); plugin_name.clear(); diff --git a/src/user/user_objects.h b/src/user/user_objects.h index 85db8da8..1018fb8b 100644 --- a/src/user/user_objects.h +++ b/src/user/user_objects.h @@ -212,7 +212,8 @@ class mjCBase : public mjCBase_ { mjCBase& operator=(const mjCBase& other); mjCFrame* frame; // pointer to frame transformation - mjCModel* model; // pointer to model that created object + mjCModel* model; // pointer to model that owns object + mjsCompiler* compiler; // pointer to the compiler options virtual ~mjCBase() = default; // destructor diff --git a/src/xml/xml.cc b/src/xml/xml.cc index abcd8c13..5277a534 100644 --- a/src/xml/xml.cc +++ b/src/xml/xml.cc @@ -365,8 +365,8 @@ mjSpec* ParseXML(const char* filename, const mjVFS* vfs, // set reasonable default for parsing a URDF // this is separate from the Parser to allow multiple URDFs to be loaded. spec->strippath = true; - spec->fusestatic = true; - spec->discardvisual = true; + spec->compiler.fusestatic = true; + spec->compiler.discardvisual = true; parser.SetModel(spec); parser.Parse(root); diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index f145afbd..854459cb 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -972,19 +972,19 @@ void mjXReader::Compiler(XMLElement* section, mjSpec* spec) { // top-level attributes if (MapValue(section, "autolimits", &n, bool_map, 2)) { - spec->autolimits = (n==1); + spec->compiler.autolimits = (n==1); } - ReadAttr(section, "boundmass", 1, &spec->boundmass, text); - ReadAttr(section, "boundinertia", 1, &spec->boundinertia, text); - ReadAttr(section, "settotalmass", 1, &spec->settotalmass, text); + ReadAttr(section, "boundmass", 1, &spec->compiler.boundmass, text); + ReadAttr(section, "boundinertia", 1, &spec->compiler.boundinertia, text); + ReadAttr(section, "settotalmass", 1, &spec->compiler.settotalmass, text); if (MapValue(section, "balanceinertia", &n, bool_map, 2)) { - spec->balanceinertia = (n==1); + spec->compiler.balanceinertia = (n==1); } if (MapValue(section, "strippath", &n, bool_map, 2)) { spec->strippath = (n==1); } if (MapValue(section, "fitaabb", &n, bool_map, 2)) { - spec->fitaabb = (n==1); + spec->compiler.fitaabb = (n==1); } if (MapValue(section, "coordinate", &n, coordinate_map, 2)) { if (n==1) { @@ -993,13 +993,13 @@ void mjXReader::Compiler(XMLElement* section, mjSpec* spec) { } } if (MapValue(section, "angle", &n, angle_map, 2)) { - spec->degree = (n==1); + spec->compiler.degree = (n==1); } if (ReadAttrTxt(section, "eulerseq", text)) { if (text.size()!=3) { throw mjXError(section, "euler format must have length 3"); } - memcpy(spec->eulerseq, text.c_str(), 3); + memcpy(spec->compiler.eulerseq, text.c_str(), 3); } if (ReadAttrTxt(section, "assetdir", text)) { mjs_setString(spec->meshdir, text.c_str()); @@ -1014,27 +1014,27 @@ void mjXReader::Compiler(XMLElement* section, mjSpec* spec) { mjs_setString(spec->texturedir, texturedir.c_str()); } if (MapValue(section, "discardvisual", &n, bool_map, 2)) { - spec->discardvisual = (n==1); + spec->compiler.discardvisual = (n==1); } if (MapValue(section, "convexhull", &n, bool_map, 2)) { - spec->convexhull = (n==1); + spec->compiler.convexhull = (n==1); } if (MapValue(section, "usethread", &n, bool_map, 2)) { - spec->usethread = (n==1); + spec->compiler.usethread = (n==1); } if (MapValue(section, "fusestatic", &n, bool_map, 2)) { - spec->fusestatic = (n==1); + spec->compiler.fusestatic = (n==1); } - MapValue(section, "inertiafromgeom", &spec->inertiafromgeom, TFAuto_map, 3); - ReadAttr(section, "inertiagrouprange", 2, spec->inertiagrouprange, text); + MapValue(section, "inertiafromgeom", &spec->compiler.inertiafromgeom, TFAuto_map, 3); + ReadAttr(section, "inertiagrouprange", 2, spec->compiler.inertiagrouprange, text); if (MapValue(section, "alignfree", &n, bool_map, 2)) { - spec->alignfree = (n==1); + spec->compiler.alignfree = (n==1); } // lengthrange subelement XMLElement* elem = FindSubElem(section, "lengthrange"); if (elem) { - mjLROpt* opt = &(spec->LRopt); + mjLROpt* opt = &(spec->compiler.LRopt); // flags MapValue(elem, "mode", &opt->mode, lrmode_map, lrmode_sz); @@ -2759,7 +2759,7 @@ void mjXReader::OneFlexcomp(XMLElement* elem, mjsBody* body, const mjVFS* vfs) { // make flexcomp char error[200]; - bool res = fcomp.Make(spec, body, error, 200); + bool res = fcomp.Make(body, error, 200); // throw error if (!res) { @@ -3575,7 +3575,7 @@ void mjXReader::Body(XMLElement* section, mjsBody* body, mjsFrame* frame, alt.type = mjORIENTATION_EULER; mjuu_copyvec(alt.euler, euler, 3); double rotation[4] = {1, 0, 0, 0}; - mjs_resolveOrientation(rotation, spec->degree, spec->eulerseq, &alt); + mjs_resolveOrientation(rotation, spec->compiler.degree, spec->compiler.eulerseq, &alt); // read childdef mjsDefault* childdef = 0; @@ -3610,7 +3610,7 @@ void mjXReader::Body(XMLElement* section, mjsBody* body, mjsFrame* frame, alt.euler[0] = i*euler[0]; alt.euler[1] = i*euler[1]; alt.euler[2] = i*euler[2]; - mjs_resolveOrientation(quat, spec->degree, spec->eulerseq, &alt); + mjs_resolveOrientation(quat, spec->compiler.degree, spec->compiler.eulerseq, &alt); mjuu_setvec(pframe->quat, quat[0], quat[1], quat[2], quat[3]); // process suffix diff --git a/src/xml/xml_native_writer.cc b/src/xml/xml_native_writer.cc index 02c265a3..907920a6 100644 --- a/src/xml/xml_native_writer.cc +++ b/src/xml/xml_native_writer.cc @@ -920,8 +920,8 @@ void mjXWriter::Compiler(XMLElement* root) { XMLElement* section = InsertEnd(root, "compiler"); // settings - if (!model->convexhull) { - WriteAttrTxt(section, "convexhull", FindValue(bool_map, 2, model->convexhull)); + if (!model->compiler.convexhull) { + WriteAttrTxt(section, "convexhull", FindValue(bool_map, 2, model->compiler.convexhull)); } WriteAttrTxt(section, "angle", "radian"); if (!model->get_meshdir().empty()) { @@ -930,20 +930,20 @@ void mjXWriter::Compiler(XMLElement* root) { if (!model->get_texturedir().empty()) { WriteAttrTxt(section, "texturedir", model->get_texturedir()); } - if (!model->usethread) { + if (!model->compiler.usethread) { WriteAttrTxt(section, "usethread", "false"); } - if (model->boundmass) { - WriteAttr(section, "boundmass", 1, &model->boundmass); + if (model->compiler.boundmass) { + WriteAttr(section, "boundmass", 1, &model->compiler.boundmass); } - if (model->boundinertia) { - WriteAttr(section, "boundinertia", 1, &model->boundinertia); + if (model->compiler.boundinertia) { + WriteAttr(section, "boundinertia", 1, &model->compiler.boundinertia); } - if (model->alignfree) { + if (model->compiler.alignfree) { WriteAttrTxt(section, "alignfree", "true"); } - if (!model->autolimits) { + if (!model->compiler.autolimits) { WriteAttrTxt(section, "autolimits", "false"); } } @@ -1611,7 +1611,7 @@ void mjXWriter::Body(XMLElement* elem, mjCBody* body, mjCFrame* frame, string_vi WriteVector(elem, "user", body->get_userdata()); // write inertial - if (body->explicitinertial && model->inertiafromgeom!=mjINERTIAFROMGEOM_TRUE) { + if (body->explicitinertial && model->compiler.inertiafromgeom!=mjINERTIAFROMGEOM_TRUE) { XMLElement* inertial = InsertEnd(elem, "inertial"); WriteAttr(inertial, "pos", 3, body->ipos); WriteAttr(inertial, "quat", 4, body->iquat, unitq); diff --git a/src/xml/xml_urdf.cc b/src/xml/xml_urdf.cc index 3dc98b65..301ac22a 100644 --- a/src/xml/xml_urdf.cc +++ b/src/xml/xml_urdf.cc @@ -109,7 +109,7 @@ void mjXURDF::Parse( } // enforce required compiler defaults for URDF - spec->degree = false; + spec->compiler.degree = false; // get model name std::string modelname; @@ -316,7 +316,7 @@ void mjXURDF::Body(XMLElement* body_elem) { } } // create geom if not discarded - if (!spec->discardvisual) { + if (!spec->compiler.discardvisual) { pgeom = Geom(elem, pbody, false); // save color diff --git a/test/user/user_api_test.cc b/test/user/user_api_test.cc index 7922d1d8..ab824637 100644 --- a/test/user/user_api_test.cc +++ b/test/user/user_api_test.cc @@ -1839,23 +1839,43 @@ TEST_F(MujocoTest, RepeatedAttachKeyframe) { mj_deleteModel(model_2); } -TEST_F(MujocoTest, DifferentUnitsNotAllowed) { - mjSpec* spec_1 = mj_makeSpec(); - mjSpec* spec_2 = mj_makeSpec(); - spec_1->degree = 1; - spec_2->degree = 0; +TEST_F(MujocoTest, DifferentUnitsAllowed) { + mjSpec* child = mj_makeSpec(); + child->compiler.degree = 1; + mjsBody* body = mjs_addBody(mjs_findBody(child, "world"), 0); + body->alt.type = mjORIENTATION_EULER; + body->alt.euler[0] = 90; - mjsBody* body = mjs_addBody(mjs_findBody(spec_1, "world"), 0); - mjsFrame* frame = mjs_addFrame(mjs_findBody(spec_2, "world"), 0); + mjSpec* parent = mj_makeSpec(); + parent->compiler.degree = 0; + mjsFrame* frame = mjs_addFrame(mjs_findBody(parent, "world"), 0); + frame->alt.type = mjORIENTATION_EULER; + frame->alt.euler[0] = -mjPI / 2; - constexpr char msg[] = "mjSpecs with incompatible compiler/angle"; - EXPECT_THAT(mjs_attachBody(frame, body, "child-", ""), IsNull()); - EXPECT_THAT(mjs_attachFrame(body, frame, "child-", ""), IsNull()); - EXPECT_THAT(mjs_getError(spec_1), HasSubstr(msg)); - EXPECT_THAT(mjs_getError(spec_2), HasSubstr(msg)); + EXPECT_THAT(mjs_attachBody(frame, body, "child-", ""), NotNull()); + mjModel* model = mj_compile(parent, 0); + EXPECT_THAT(model, NotNull()); + EXPECT_NEAR(model->body_quat[4], 1, 1e-12); + EXPECT_NEAR(model->body_quat[5], 0, 1e-12); + EXPECT_NEAR(model->body_quat[6], 0, 1e-12); + EXPECT_NEAR(model->body_quat[7], 0, 1e-12); - mj_deleteSpec(spec_1); - mj_deleteSpec(spec_2); + mjSpec* copy = mj_copySpec(parent); + EXPECT_THAT(copy, NotNull()); + mj_deleteModel(model); + mj_deleteSpec(child); + mj_deleteSpec(parent); + + // check that deleting `parent` or `child` does not invalidate the copy + mjModel* copy_model = mj_compile(copy, 0); + EXPECT_THAT(copy_model, NotNull()); + EXPECT_NEAR(copy_model->body_quat[0], 1, 1e-12); + EXPECT_NEAR(copy_model->body_quat[1], 0, 1e-12); + EXPECT_NEAR(copy_model->body_quat[2], 0, 1e-12); + EXPECT_NEAR(copy_model->body_quat[3], 0, 1e-12); + + mj_deleteModel(copy_model); + mj_deleteSpec(copy); } TEST_F(MujocoTest, CopyAttachedSpec) { diff --git a/test/user/user_model_test.cc b/test/user/user_model_test.cc index 74a14f77..24467d97 100644 --- a/test/user/user_model_test.cc +++ b/test/user/user_model_test.cc @@ -480,8 +480,8 @@ TEST_F(LengthRangeTest, LengthRangeThreading) { DoubleNear(std::sqrt(5.0), 1e-3)); // recompile without threads - ASSERT_EQ(spec->usethread, 1); - spec->usethread = 0; + ASSERT_EQ(spec->compiler.usethread, 1); + spec->compiler.usethread = 0; mjModel* model2 = mj_compile(spec, 0); EXPECT_THAT(model2, NotNull()) << error; diff --git a/test/user/user_objects_test.cc b/test/user/user_objects_test.cc index 2a97ec58..5d6891c3 100644 --- a/test/user/user_objects_test.cc +++ b/test/user/user_objects_test.cc @@ -1176,7 +1176,7 @@ TEST_F(MjCJointTest, AlignFree) { std::array err; mjSpec* s = mj_parseXML(xml_path.c_str(), nullptr, err.data(), err.size()); ASSERT_THAT(s, NotNull()) << err.data(); - s->alignfree = 1; // auto-aligned free joint + s->compiler.alignfree = 1; // auto-aligned free joint mjModel* m = mj_compile(s, nullptr); ASSERT_THAT(m, NotNull()); @@ -1186,7 +1186,7 @@ TEST_F(MjCJointTest, AlignFree) { EXPECT_EQ(m->dof_simplenum[0], 6); // make unaligned model - s->alignfree = 0; // unaligned free joint + s->compiler.alignfree = 0; // unaligned free joint mjModel* m_u = mj_compile(s, nullptr); ASSERT_THAT(m_u, NotNull()); diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index 9bd3c6bb..dbc5364b 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -5716,6 +5716,26 @@ public unsafe struct mjrContext_ { public int readDepthMap; } +[StructLayout(LayoutKind.Sequential)] +public unsafe struct mjsCompiler_ { + public byte autolimits; + public double boundmass; + public double boundinertia; + public double settotalmass; + public byte balanceinertia; + public byte fitaabb; + public byte degree; + public fixed sbyte eulerseq[3]; + public byte discardvisual; + public byte convexhull; + public byte usethread; + public byte fusestatic; + public int inertiafromgeom; + public fixed int inertiagrouprange[2]; + public int alignfree; + public mjLROpt_ LRopt; +} + [StructLayout(LayoutKind.Sequential)] public unsafe struct mjuiState_ { public int nrect;