diff --git a/doc/APIreference/APItypes.rst b/doc/APIreference/APItypes.rst index fd7b8a4a..143c6b11 100644 --- a/doc/APIreference/APItypes.rst +++ b/doc/APIreference/APItypes.rst @@ -1420,6 +1420,16 @@ Compiler options. .. mujoco-include:: mjsCompiler +.. _mjsAuthored: + +mjsAuthored +~~~~~~~~~~~ + +Authored tracking bitmasks for ``mjModel`` structs. + +.. mujoco-include:: mjsAuthored + + .. _mjsBody: mjsBody diff --git a/doc/includes/references.h b/doc/includes/references.h index 1a768253..b47fa5cb 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -1588,7 +1588,20 @@ typedef struct mjsCompiler_ { // compiler options mjLROpt LRopt; // options for lengthrange computation mjString* meshdir; // mesh and hfield directory mjString* texturedir; // texture directory + uint64_t authored; // bitmask of authored compiler fields } mjsCompiler; +typedef struct mjsAuthored_ { // authored tracking bitmasks for mjModel structs + uint64_t option; // authored mjOption fields + int disableflags; // individual authored disable flags + int enableflags; // individual authored enable flags + int disableactuator; // individual authored actuator groups + uint64_t visual_global; // authored visual.global fields + uint64_t visual_quality; // authored visual.quality fields + uint64_t visual_headlight; // authored visual.headlight fields + uint64_t visual_map; // authored visual.map fields + uint64_t visual_scale; // authored visual.scale fields + uint64_t visual_rgba; // authored visual.rgba fields +} mjsAuthored; typedef struct mjSpec_ { // model specification mjsElement* element; // element type mjString* modelname; // model name @@ -1625,6 +1638,9 @@ typedef struct mjSpec_ { // model specification // other mjtByte hasImplicitPluginElem; // already encountered an implicit plugin sensor/actuator + + // authored tracking bitmasks for mjModel structs + mjsAuthored authored; } mjSpec; typedef struct mjsOrientation_ { // alternative orientation specifiers mjtOrientation type; // active orientation specifier diff --git a/include/mujoco/mjassert.h b/include/mujoco/mjassert.h index 36651ac5..58e055ac 100644 --- a/include/mujoco/mjassert.h +++ b/include/mujoco/mjassert.h @@ -20,19 +20,24 @@ #include #include #include +#include #include #include #include +#include #if defined(__cplusplus) #define MJ_ASSERT_SIZE(type, size) \ static_assert(sizeof(type) == (size), #type " must be " #size " bytes for MuJoCo ABI stability") + #define MJ_STATIC_ASSERT(expr) static_assert(expr) #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L #define MJ_ASSERT_SIZE(type, size) \ _Static_assert(sizeof(type) == (size), #type " must be " #size " bytes for MuJoCo ABI stability") + #define MJ_STATIC_ASSERT(expr) _Static_assert(expr, #expr) #else #define MJ_ASSERT_SIZE(type, size) \ typedef char mj_assert_##type[sizeof(type) == (size) ? 1 : -1] + #define MJ_STATIC_ASSERT(expr) #endif // primitive types @@ -125,6 +130,27 @@ MJ_ASSERT_SIZE(mjtSection, 4); // mjplugin.h MJ_ASSERT_SIZE(mjtPluginCapabilityBit, 4); +// authored bitmask field count assertions +// each authored bitmask is uint64_t, so each FIELDS macro must have <= 64 entries +#define X(type, name, dim) +1 +#define XVEC(type, name, dim) +1 + +// mjsCompiler +MJ_STATIC_ASSERT((0 MJSCOMPILER_FIELDS) <= 64); + +// mjOption and mjVisual +MJ_STATIC_ASSERT((0 MJOPTION_FIELDS) <= 64); +MJ_STATIC_ASSERT((0 MJVISUAL_GLOBAL_FIELDS) <= 64); +MJ_STATIC_ASSERT((0 MJVISUAL_QUALITY_FIELDS) <= 64); +MJ_STATIC_ASSERT((0 MJVISUAL_HEADLIGHT_FIELDS) <= 64); +MJ_STATIC_ASSERT((0 MJVISUAL_MAP_FIELDS) <= 64); +MJ_STATIC_ASSERT((0 MJVISUAL_SCALE_FIELDS) <= 64); +MJ_STATIC_ASSERT((0 MJVISUAL_RGBA_FIELDS) <= 64); + +#undef X +#undef XVEC + #undef MJ_ASSERT_SIZE +#undef MJ_STATIC_ASSERT #endif // MUJOCO_MJASSERT_H_ diff --git a/include/mujoco/mjspec.h b/include/mujoco/mjspec.h index ad33cca3..f8deaecb 100644 --- a/include/mujoco/mjspec.h +++ b/include/mujoco/mjspec.h @@ -174,9 +174,24 @@ typedef struct mjsCompiler_ { // compiler options mjLROpt LRopt; // options for lengthrange computation mjString* meshdir; // mesh and hfield directory mjString* texturedir; // texture directory + uint64_t authored; // bitmask of authored compiler fields } mjsCompiler; +typedef struct mjsAuthored_ { // authored tracking bitmasks for mjModel structs + uint64_t option; // authored mjOption fields + int disableflags; // individual authored disable flags + int enableflags; // individual authored enable flags + int disableactuator; // individual authored actuator groups + uint64_t visual_global; // authored visual.global fields + uint64_t visual_quality; // authored visual.quality fields + uint64_t visual_headlight; // authored visual.headlight fields + uint64_t visual_map; // authored visual.map fields + uint64_t visual_scale; // authored visual.scale fields + uint64_t visual_rgba; // authored visual.rgba fields +} mjsAuthored; + + typedef struct mjSpec_ { // model specification mjsElement* element; // element type mjString* modelname; // model name @@ -213,6 +228,9 @@ typedef struct mjSpec_ { // model specification // other mjtByte hasImplicitPluginElem; // already encountered an implicit plugin sensor/actuator + + // authored tracking bitmasks for mjModel structs + mjsAuthored authored; } mjSpec; diff --git a/include/mujoco/mjspecmacro.h b/include/mujoco/mjspecmacro.h index 81e861e1..fe3f6804 100644 --- a/include/mujoco/mjspecmacro.h +++ b/include/mujoco/mjspecmacro.h @@ -43,7 +43,8 @@ X ( int, alignfree, 1 ) \ X ( mjLROpt, LRopt, 1 ) \ X ( mjString*, meshdir, 1 ) \ - X ( mjString*, texturedir, 1 ) + X ( mjString*, texturedir, 1 ) \ + X ( uint64_t, authored, 1 ) //-------------------------------- mjSpec ---------------------------------------------------------- diff --git a/python/mujoco/codegen/generate_spec_bindings.py b/python/mujoco/codegen/generate_spec_bindings.py index 2b1d4b14..2728a0d5 100644 --- a/python/mujoco/codegen/generate_spec_bindings.py +++ b/python/mujoco/codegen/generate_spec_bindings.py @@ -82,8 +82,10 @@ def _value_binding_code( field.name == 'mjsPlugin' or field.name == 'mjsOrientation' or field.name == 'mjsCompiler' + or field.name == 'mjsAuthored' ): - fulltype = fulltype + '&' # plugin, orientation, compiler aren't pointers + # plugin, orientation, compiler, authored aren't pointers + fulltype = fulltype + '&' else: fulltype = fulltype + '*' # non-mjs structs diff --git a/python/mujoco/introspect/structs.py b/python/mujoco/introspect/structs.py index 420f15ee..478e564c 100644 --- a/python/mujoco/introspect/structs.py +++ b/python/mujoco/introspect/structs.py @@ -6996,6 +6996,68 @@ STRUCTS: Mapping[str, StructDecl] = dict([ ), doc='texture directory', ), + StructFieldDecl( + name='authored', + type=ValueType(name='uint64_t'), + doc='bitmask of authored compiler fields', + ), + ), + )), + ('mjsAuthored', + StructDecl( + name='mjsAuthored', + declname='struct mjsAuthored_', + fields=( + StructFieldDecl( + name='option', + type=ValueType(name='uint64_t'), + doc='authored mjOption fields', + ), + StructFieldDecl( + name='disableflags', + type=ValueType(name='int'), + doc='individual authored disable flags', + ), + StructFieldDecl( + name='enableflags', + type=ValueType(name='int'), + doc='individual authored enable flags', + ), + StructFieldDecl( + name='disableactuator', + type=ValueType(name='int'), + doc='individual authored actuator groups', + ), + StructFieldDecl( + name='visual_global', + type=ValueType(name='uint64_t'), + doc='authored visual.global fields', + ), + StructFieldDecl( + name='visual_quality', + type=ValueType(name='uint64_t'), + doc='authored visual.quality fields', + ), + StructFieldDecl( + name='visual_headlight', + type=ValueType(name='uint64_t'), + doc='authored visual.headlight fields', + ), + StructFieldDecl( + name='visual_map', + type=ValueType(name='uint64_t'), + doc='authored visual.map fields', + ), + StructFieldDecl( + name='visual_scale', + type=ValueType(name='uint64_t'), + doc='authored visual.scale fields', + ), + StructFieldDecl( + name='visual_rgba', + type=ValueType(name='uint64_t'), + doc='authored visual.rgba fields', + ), ), )), ('mjSpec', @@ -7136,6 +7198,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([ type=ValueType(name='mjtByte'), doc='already encountered an implicit plugin sensor/actuator', ), + StructFieldDecl( + name='authored', + type=ValueType(name='mjsAuthored'), + doc='authored tracking bitmasks for mjModel structs', + ), ), )), ('mjsOrientation', diff --git a/python/mujoco/raw.h b/python/mujoco/raw.h index 0d97d55a..920a419c 100644 --- a/python/mujoco/raw.h +++ b/python/mujoco/raw.h @@ -61,6 +61,7 @@ using MjsTuple = ::mjsTuple; using MjsKey = ::mjsKey; using MjsDefault = ::mjsDefault; using MjsCompiler = ::mjsCompiler; +using MjsAuthored = ::mjsAuthored; using MjOption = ::mjOption; using MjSolverStat = ::mjSolverStat; using MjStatistic = ::mjStatistic; diff --git a/python/mujoco/specs.cc b/python/mujoco/specs.cc index f8abf3ad..4d5784cb 100644 --- a/python/mujoco/specs.cc +++ b/python/mujoco/specs.cc @@ -267,6 +267,7 @@ PYBIND11_MODULE(_specs, m) { py::class_ mjVisualHeadlight(m, "MjVisualHeadlight"); py::class_ mjVisualRgba(m, "MjVisualRgba"); py::class_ mjsCompiler(m, "MjsCompiler"); + py::class_ mjsAuthored(m, "MjsAuthored"); DefineArray(m, "MjCharVec"); DefineArray(m, "MjStringVec"); DefineArray(m, "MjByteVec"); diff --git a/python/mujoco/specs_test.py b/python/mujoco/specs_test.py index 8bae2be1..047584a3 100644 --- a/python/mujoco/specs_test.py +++ b/python/mujoco/specs_test.py @@ -2102,5 +2102,76 @@ class SpecsTest(absltest.TestCase): model = spec.compile() self.assertIsNotNone(model) + def test_authored_struct(self): + spec = mujoco.MjSpec() + # authored struct should be accessible with correct fields + self.assertEqual(spec.authored.option, 0) + self.assertEqual(spec.authored.disableflags, 0) + self.assertEqual(spec.authored.enableflags, 0) + self.assertEqual(spec.authored.disableactuator, 0) + self.assertEqual(spec.authored.visual_global, 0) + self.assertEqual(spec.authored.visual_quality, 0) + self.assertEqual(spec.authored.visual_headlight, 0) + self.assertEqual(spec.authored.visual_map, 0) + self.assertEqual(spec.authored.visual_scale, 0) + self.assertEqual(spec.authored.visual_rgba, 0) + # compiler authored should be zero + self.assertEqual(spec.compiler.authored, 0) + + def test_authored_flags_from_xml(self): + spec = mujoco.MjSpec.from_string(""" + + + + + + + + + + """) + # disable/enable flags should be tracked + self.assertNotEqual( + spec.authored.disableflags & mujoco.mjtDisableBit.mjDSBL_CONSTRAINT, 0) + self.assertEqual( + spec.authored.disableflags & mujoco.mjtDisableBit.mjDSBL_CONTACT, 0) + self.assertNotEqual( + spec.authored.enableflags & mujoco.mjtEnableBit.mjENBL_ENERGY, 0) + self.assertEqual( + spec.authored.enableflags & mujoco.mjtEnableBit.mjENBL_OVERRIDE, 0) + + # option authored bitmask should be nonzero (timestep was authored) + self.assertNotEqual(spec.authored.option, 0) + + # compiler authored bitmask should be nonzero (boundmass was authored) + self.assertNotEqual(spec.compiler.authored, 0) + + # visual authored bitmask should be nonzero (fovy, shadowsize were authored) + self.assertNotEqual(spec.authored.visual_global, 0) + self.assertNotEqual(spec.authored.visual_quality, 0) + + # visual sections that were not authored should be zero + self.assertEqual(spec.authored.visual_headlight, 0) + self.assertEqual(spec.authored.visual_map, 0) + self.assertEqual(spec.authored.visual_scale, 0) + self.assertEqual(spec.authored.visual_rgba, 0) + + def test_authored_defaults_zero(self): + spec = mujoco.MjSpec.from_string(""" + + + + """) + # nothing authored in an empty model + self.assertEqual(spec.authored.option, 0) + self.assertEqual(spec.authored.disableflags, 0) + self.assertEqual(spec.authored.enableflags, 0) + self.assertEqual(spec.compiler.authored, 0) + self.assertEqual(spec.authored.visual_global, 0) + self.assertEqual(spec.authored.visual_quality, 0) + self.assertEqual(spec.authored.visual_map, 0) + if __name__ == '__main__': absltest.main() diff --git a/src/user/user_api.cc b/src/user/user_api.cc index 3c7f4580..ad4f4c36 100644 --- a/src/user/user_api.cc +++ b/src/user/user_api.cc @@ -31,6 +31,8 @@ #include #include +#include +#include #include "engine/engine_support.h" #include "engine/engine_util_errmem.h" #include "user/user_cache.h" @@ -2373,3 +2375,169 @@ mjCache* mj_getCache() { }(); return &cache_cwrapper; } + + +// return 1 if a field was authored, 0 otherwise +int mjs_isAuthored(const void* elem_ptr, const void* field_ptr) { + if (!elem_ptr || !field_ptr) return 0; + const mjsElement* el = *reinterpret_cast(elem_ptr); + if (!el) return 0; + + // model-level sub-structs (compiler, option, visual) + if (el->elemtype == mjOBJ_MODEL) { + const mjCModel* cel = static_cast(el); + int idx = 0; + +#define CHECK_FIELD(FIELD_PATH, AUTHORED_MASK) \ + if (field_ptr == &FIELD_PATH) return (AUTHORED_MASK & (1ULL << idx)) != 0; \ + idx++; +#define CHECK_FIELD_VEC(FIELD_PATH, AUTHORED_MASK) \ + if (field_ptr == FIELD_PATH || field_ptr == &FIELD_PATH) \ + return (AUTHORED_MASK & (1ULL << idx)) != 0; \ + idx++; + +#define X(type, name, dim) CHECK_FIELD(cel->spec.compiler.name, cel->spec.compiler.authored) +#define XVEC(type, name, dim) CHECK_FIELD_VEC(cel->spec.compiler.name, cel->spec.compiler.authored) + idx = 0; + MJSCOMPILER_FIELDS +#undef X +#undef XVEC + +#define X(type, name, dim) CHECK_FIELD(cel->spec.option.name, cel->spec.authored.option) +#define XVEC(type, name, dim) CHECK_FIELD_VEC(cel->spec.option.name, cel->spec.authored.option) + idx = 0; + MJOPTION_FIELDS +#undef X +#undef XVEC + +#define X(type, name, dim) \ + CHECK_FIELD(cel->spec.visual.global.name, cel->spec.authored.visual_global) + idx = 0; + MJVISUAL_GLOBAL_FIELDS +#undef X + +#define X(type, name, dim) \ + CHECK_FIELD(cel->spec.visual.quality.name, cel->spec.authored.visual_quality) + idx = 0; + MJVISUAL_QUALITY_FIELDS +#undef X + +#define X(type, name, dim) \ + CHECK_FIELD(cel->spec.visual.headlight.name, cel->spec.authored.visual_headlight) +#define XVEC(type, name, dim) \ + CHECK_FIELD_VEC(cel->spec.visual.headlight.name, cel->spec.authored.visual_headlight) + idx = 0; + MJVISUAL_HEADLIGHT_FIELDS +#undef X +#undef XVEC + +#define X(type, name, dim) CHECK_FIELD(cel->spec.visual.map.name, cel->spec.authored.visual_map) + idx = 0; + MJVISUAL_MAP_FIELDS +#undef X + +#define X(type, name, dim) CHECK_FIELD(cel->spec.visual.scale.name, cel->spec.authored.visual_scale) + idx = 0; + MJVISUAL_SCALE_FIELDS +#undef X + +#define XVEC(type, name, dim) \ + CHECK_FIELD_VEC(cel->spec.visual.rgba.name, cel->spec.authored.visual_rgba) + idx = 0; + MJVISUAL_RGBA_FIELDS +#undef XVEC + +#undef CHECK_FIELD +#undef CHECK_FIELD_VEC + } + + return 0; +} + + + +// record explicit authoring of an element's field +void mjs_setAuthored(const void* elem_ptr, const void* field_ptr, int authored) { + if (!elem_ptr || !field_ptr) return; + mjsElement* el = const_cast(*reinterpret_cast(elem_ptr)); + if (!el) return; + +#define SET_FIELD(FIELD_PATH, AUTHORED_MASK) \ + if (field_ptr == &FIELD_PATH) { \ + if (authored) \ + AUTHORED_MASK |= (1ULL << idx); \ + else \ + AUTHORED_MASK &= ~(1ULL << idx); \ + return; \ + } \ + idx++; + +#define SET_FIELD_VEC(FIELD_PATH, AUTHORED_MASK) \ + if (field_ptr == FIELD_PATH || field_ptr == &FIELD_PATH) { \ + if (authored) \ + AUTHORED_MASK |= (1ULL << idx); \ + else \ + AUTHORED_MASK &= ~(1ULL << idx); \ + return; \ + } \ + idx++; + + // model-level sub-structs (compiler, option, visual) + if (el->elemtype == mjOBJ_MODEL) { + mjCModel* cel = static_cast(el); + int idx = 0; + +#define X(type, name, dim) SET_FIELD(cel->spec.compiler.name, cel->spec.compiler.authored) +#define XVEC(type, name, dim) SET_FIELD_VEC(cel->spec.compiler.name, cel->spec.compiler.authored) + idx = 0; + MJSCOMPILER_FIELDS +#undef X +#undef XVEC + +#define X(type, name, dim) SET_FIELD(cel->spec.option.name, cel->spec.authored.option) +#define XVEC(type, name, dim) SET_FIELD_VEC(cel->spec.option.name, cel->spec.authored.option) + idx = 0; + MJOPTION_FIELDS +#undef X +#undef XVEC + +#define X(type, name, dim) SET_FIELD(cel->spec.visual.global.name, cel->spec.authored.visual_global) + idx = 0; + MJVISUAL_GLOBAL_FIELDS +#undef X + +#define X(type, name, dim) \ + SET_FIELD(cel->spec.visual.quality.name, cel->spec.authored.visual_quality) + idx = 0; + MJVISUAL_QUALITY_FIELDS +#undef X + +#define X(type, name, dim) \ + SET_FIELD(cel->spec.visual.headlight.name, cel->spec.authored.visual_headlight) +#define XVEC(type, name, dim) \ + SET_FIELD_VEC(cel->spec.visual.headlight.name, cel->spec.authored.visual_headlight) + idx = 0; + MJVISUAL_HEADLIGHT_FIELDS +#undef X +#undef XVEC + +#define X(type, name, dim) SET_FIELD(cel->spec.visual.map.name, cel->spec.authored.visual_map) + idx = 0; + MJVISUAL_MAP_FIELDS +#undef X + +#define X(type, name, dim) SET_FIELD(cel->spec.visual.scale.name, cel->spec.authored.visual_scale) + idx = 0; + MJVISUAL_SCALE_FIELDS +#undef X + +#define XVEC(type, name, dim) \ + SET_FIELD_VEC(cel->spec.visual.rgba.name, cel->spec.authored.visual_rgba) + idx = 0; + MJVISUAL_RGBA_FIELDS +#undef XVEC + } + +#undef SET_FIELD +#undef SET_FIELD_VEC +} diff --git a/src/user/user_api.h b/src/user/user_api.h index 53b250e7..a9bd4e83 100644 --- a/src/user/user_api.h +++ b/src/user/user_api.h @@ -431,6 +431,12 @@ MJAPI const void* mjs_getPluginAttributes(const mjsPlugin* plugin); //---------------------------------- Other utilities ----------------------------------------------- +// Return 1 if a field was authored or mutated relative to its inherited default, 0 otherwise. +MJAPI int mjs_isAuthored(const void* elem_ptr, const void* field_ptr); + +// Record explicit authoring of an element's field. +MJAPI void mjs_setAuthored(const void* elem_ptr, const void* field_ptr, int authored); + // Set element's default. MJAPI void mjs_setDefault(mjsElement* element, const mjsDefault* def); diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index 7bb36644..817f8607 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -25,17 +25,14 @@ #include #include #include +#include #include #include #include #include -#include #include #include -#include "engine/engine_plugin.h" -#include "engine/engine_support.h" -#include "engine/engine_util_errmem.h" #include "engine/engine_util_misc.h" #include #include "user/user_api.h" @@ -53,6 +50,81 @@ using std::vector; using mujoco::user::FilePath; using tinyxml2::XMLElement; + + +//---------------------------------- helper utilities ---------------------------------------------- + +// GetAttrPtr: overload for scalar and pointer fields +template +inline auto GetAttrPtr(T& val) -> std::enable_if_t, decltype(&val)> { + if constexpr (std::is_pointer_v) { + return val; + } else { + return &val; + } +} + +// GetAttrPtr: overload for array fields +template +inline T* GetAttrPtr(T (&arr)[N]) { return arr; } + + +// helper class for reading attributes while recording authored bits +struct Reader { + Reader(XMLElement* xml_node, const void* elem) : xml_node_(xml_node), elem_(elem) {} + + template + int operator()(const char* attr, int len, T& data, bool required = false, bool exact = true) { + int res = mjXReader::ReadAttr(xml_node_, attr, len, GetAttrPtr(data), text_, required, exact); + if (res) mjs_setAuthored(elem_, &data, 1); + return res; + } + + template + bool operator()(const char* attr, T& data, const mjMap* map, int mapsz, bool required = false) { + int map_val_temp; + bool res = mjXReader::MapValue(xml_node_, attr, &map_val_temp, map, mapsz, required); + if (res) { + data = static_cast(map_val_temp); + mjs_setAuthored(elem_, &data, 1); + } + return res; + } + + bool operator()(const char* attr, int& data, bool required = false) { + bool res = mjXUtil::ReadAttrInt(xml_node_, attr, &data, required); + if (res) mjs_setAuthored(elem_, &data, 1); + return res; + } + + bool operator()(const char* attr, mjString* target) { + std::string txt_temp; + bool res = mjXUtil::ReadAttrTxt(xml_node_, attr, txt_temp); + if (res) { + mjs_setString(target, txt_temp.c_str()); + mjs_setAuthored(elem_, target, 1); + } + return res; + } + + template + bool txt(const char* attr, T& target, void (&set_func)(T&, const char*)) { + std::string txt_temp; + bool res = mjXUtil::ReadAttrTxt(xml_node_, attr, txt_temp); + if (res) { + set_func(target, txt_temp.c_str()); + mjs_setAuthored(elem_, &target, 1); + } + return res; + } + + void set_node(XMLElement* node) { xml_node_ = node; } + + XMLElement* xml_node_; + const void* elem_; + std::string text_; +}; + void ReadPluginConfigs(tinyxml2::XMLElement* elem, mjsPlugin* p) { std::map > config_attribs; XMLElement* child = FirstChildElement(elem); @@ -1023,7 +1095,7 @@ void mjXReader::Parse(XMLElement* root, const mjVFS* vfs) { for (XMLElement* section = FirstChildElement(root, "option"); section; section = NextSiblingElement(section, "option")) { - Option(section, &spec->option); + Option(section, spec, &spec->option); } for (XMLElement* section = FirstChildElement(root, "size"); section; @@ -1116,73 +1188,49 @@ void mjXReader::Parse(XMLElement* root, const mjVFS* vfs) { // compiler section parser void mjXReader::Compiler(XMLElement* section, mjSpec* s) { - string text; - int n; + Reader read(section, s); // top-level attributes - if (MapValue(section, "autolimits", &n, bool_map, 2)) { - s->compiler.autolimits = (n == 1); - } - ReadAttr(section, "boundmass", 1, &s->compiler.boundmass, text); - ReadAttr(section, "boundinertia", 1, &s->compiler.boundinertia, text); - ReadAttr(section, "settotalmass", 1, &s->compiler.settotalmass, text); - if (MapValue(section, "balanceinertia", &n, bool_map, 2)) { - s->compiler.balanceinertia = (n == 1); - } - if (MapValue(section, "strippath", &n, bool_map, 2)) { - s->strippath = (n == 1); - } - if (MapValue(section, "fitaabb", &n, bool_map, 2)) { - s->compiler.fitaabb = (n == 1); - } - if (MapValue(section, "coordinate", &n, coordinate_map, 2)) { + read("autolimits", s->compiler.autolimits, bool_map, 2); + read("boundmass", 1, s->compiler.boundmass); + read("boundinertia", 1, s->compiler.boundinertia); + read("settotalmass", 1, s->compiler.settotalmass); + read("balanceinertia", s->compiler.balanceinertia, bool_map, 2); + read("strippath", s->strippath, bool_map, 2); + read("fitaabb", s->compiler.fitaabb, bool_map, 2); + if (int n = 0; MapValue(section, "coordinate", &n, coordinate_map, 2)) { if (n == 1) { throw mjXError(section, "global coordinates no longer supported. To convert existing models, " "load and save them in MuJoCo 2.3.3 or older"); } } - if (MapValue(section, "angle", &n, angle_map, 2)) { - s->compiler.degree = (n == 1); - } - if (ReadAttrTxt(section, "eulerseq", text)) { - if (text.size() != 3) { + read("angle", s->compiler.degree, angle_map, 2); + if (ReadAttrTxt(section, "eulerseq", read.text_)) { + if (read.text_.size() != 3) { throw mjXError(section, "euler format must have length 3"); } - memcpy(s->compiler.eulerseq, text.c_str(), 3); + memcpy(s->compiler.eulerseq, read.text_.c_str(), 3); + mjs_setAuthored(s, &s->compiler.eulerseq, 1); } - if (ReadAttrTxt(section, "assetdir", text)) { - mjs_setString(s->compiler.meshdir, text.c_str()); - mjs_setString(s->compiler.texturedir, text.c_str()); + if (ReadAttrTxt(section, "assetdir", read.text_)) { + mjs_setString(s->compiler.meshdir, read.text_.c_str()); + mjs_setString(s->compiler.texturedir, read.text_.c_str()); } // meshdir and texturedir take precedence over assetdir - string meshdir, texturedir; - if (ReadAttrTxt(section, "meshdir", meshdir)) { - mjs_setString(s->compiler.meshdir, meshdir.c_str()); - }; - if (ReadAttrTxt(section, "texturedir", texturedir)) { - mjs_setString(s->compiler.texturedir, texturedir.c_str()); - } - if (MapValue(section, "discardvisual", &n, bool_map, 2)) { - s->compiler.discardvisual = (n == 1); - } - if (MapValue(section, "usethread", &n, bool_map, 2)) { - s->compiler.usethread = (n == 1); - } - if (MapValue(section, "fusestatic", &n, bool_map, 2)) { - s->compiler.fusestatic = (n == 1); - } - MapValue(section, "inertiafromgeom", &s->compiler.inertiafromgeom, TFAuto_map, 3); - ReadAttr(section, "inertiagrouprange", 2, s->compiler.inertiagrouprange, text); - if (MapValue(section, "alignfree", &n, bool_map, 2)) { - s->compiler.alignfree = (n == 1); - } - if (MapValue(section, "saveinertial", &n, bool_map, 2)) { - s->compiler.saveinertial = (n == 1); - } + read("meshdir", s->compiler.meshdir); + read("texturedir", s->compiler.texturedir); + read("discardvisual", s->compiler.discardvisual, bool_map, 2); + read("usethread", s->compiler.usethread, bool_map, 2); + read("fusestatic", s->compiler.fusestatic, bool_map, 2); + read("inertiafromgeom", s->compiler.inertiafromgeom, TFAuto_map, 3); + read("inertiagrouprange", 2, s->compiler.inertiagrouprange); + read("alignfree", s->compiler.alignfree, bool_map, 2); + read("saveinertial", s->compiler.saveinertial, bool_map, 2); // lengthrange subelement XMLElement* elem = FindSubElem(section, "lengthrange"); if (elem) { + int n; mjLROpt* opt = &(s->compiler.LRopt); // flags @@ -1195,6 +1243,7 @@ void mjXReader::Compiler(XMLElement* section, mjSpec* s) { } // algorithm parameters + string text; ReadAttr(elem, "accel", 1, &opt->accel, text); ReadAttr(elem, "maxforce", 1, &opt->maxforce, text); ReadAttr(elem, "timeconst", 1, &opt->timeconst, text); @@ -1208,45 +1257,44 @@ void mjXReader::Compiler(XMLElement* section, mjSpec* s) { // option section parser -void mjXReader::Option(XMLElement* section, mjOption* opt) { - string text; - int n; +void mjXReader::Option(XMLElement* section, mjSpec* s, mjOption* opt) { + Reader read(section, s); // read options - ReadAttr(section, "timestep", 1, &opt->timestep, text); - ReadAttr(section, "impratio", 1, &opt->impratio, text); - ReadAttr(section, "tolerance", 1, &opt->tolerance, text); - ReadAttr(section, "ls_tolerance", 1, &opt->ls_tolerance, text); - ReadAttr(section, "noslip_tolerance", 1, &opt->noslip_tolerance, text); - ReadAttr(section, "ccd_tolerance", 1, &opt->ccd_tolerance, text); - ReadAttr(section, "sleep_tolerance", 1, &opt->sleep_tolerance, text); - ReadAttr(section, "gravity", 3, opt->gravity, text); - ReadAttr(section, "wind", 3, opt->wind, text); - ReadAttr(section, "magnetic", 3, opt->magnetic, text); - ReadAttr(section, "density", 1, &opt->density, text); - ReadAttr(section, "viscosity", 1, &opt->viscosity, text); + read("timestep", 1, opt->timestep); + read("impratio", 1, opt->impratio); + read("tolerance", 1, opt->tolerance); + read("ls_tolerance", 1, opt->ls_tolerance); + read("noslip_tolerance", 1, opt->noslip_tolerance); + read("ccd_tolerance", 1, opt->ccd_tolerance); + read("sleep_tolerance", 1, opt->sleep_tolerance); + read("gravity", 3, opt->gravity); + read("wind", 3, opt->wind); + read("magnetic", 3, opt->magnetic); + read("density", 1, opt->density); + read("viscosity", 1, opt->viscosity); - ReadAttr(section, "o_margin", 1, &opt->o_margin, text); - ReadAttr(section, "o_solref", mjNREF, opt->o_solref, text, false, false); - ReadAttr(section, "o_solimp", mjNIMP, opt->o_solimp, text, false, false); - ReadAttr(section, "o_friction", 5, opt->o_friction, text, false, false); + read("o_margin", 1, opt->o_margin); + read("o_solref", mjNREF, opt->o_solref, false, false); + read("o_solimp", mjNIMP, opt->o_solimp, false, false); + read("o_friction", 5, opt->o_friction, false, false); - MapValue(section, "integrator", &opt->integrator, integrator_map, integrator_sz); - MapValue(section, "cone", &opt->cone, cone_map, cone_sz); - MapValue(section, "jacobian", &opt->jacobian, jac_map, jac_sz); - MapValue(section, "solver", &opt->solver, solver_map, solver_sz); - ReadAttrInt(section, "iterations", &opt->iterations); - ReadAttrInt(section, "ls_iterations", &opt->ls_iterations); - ReadAttrInt(section, "noslip_iterations", &opt->noslip_iterations); - ReadAttrInt(section, "ccd_iterations", &opt->ccd_iterations); - ReadAttrInt(section, "sdf_iterations", &opt->sdf_iterations); - ReadAttrInt(section, "sdf_initpoints", &opt->sdf_initpoints); + read("integrator", opt->integrator, integrator_map, integrator_sz); + read("cone", opt->cone, cone_map, cone_sz); + read("jacobian", opt->jacobian, jac_map, jac_sz); + read("solver", opt->solver, solver_map, solver_sz); + read("iterations", opt->iterations); + read("ls_iterations", opt->ls_iterations); + read("noslip_iterations", opt->noslip_iterations); + read("ccd_iterations", opt->ccd_iterations); + read("sdf_iterations", opt->sdf_iterations); + read("sdf_initpoints", opt->sdf_initpoints); // actuatorgroupdisable constexpr int num_bitflags = 31; int disabled_act_groups[num_bitflags]; - int num_found = ReadAttr(section, "actuatorgroupdisable", num_bitflags, disabled_act_groups, - text, false, false); + int num_found = read("actuatorgroupdisable", num_bitflags, + disabled_act_groups, false, false); for (int i=0; i < num_found; i++) { int group = disabled_act_groups[i]; if (group < 0) { @@ -1256,15 +1304,19 @@ void mjXReader::Option(XMLElement* section, mjOption* opt) { throw mjXError(section, "disabled actuator group value cannot exceed 30"); } opt->disableactuator |= (1 << group); + s->authored.disableactuator |= (1 << group); } // read disable sub-element XMLElement* elem = FindSubElem(section, "flag"); if (elem) { -#define READDSBL(NAME, MASK) \ - if (MapValue(elem, NAME, &n, enable_map, 2)) { \ - opt->disableflags ^= (opt->disableflags & MASK); \ - opt->disableflags |= (n ? 0 : MASK); } + int n = 0; +#define READDSBL(NAME, MASK) \ + if (MapValue(elem, NAME, &n, enable_map, 2)) { \ + opt->disableflags ^= (opt->disableflags & MASK); \ + opt->disableflags |= (n ? 0 : MASK); \ + s->authored.disableflags |= MASK; \ + } READDSBL("constraint", mjDSBL_CONSTRAINT) READDSBL("equality", mjDSBL_EQUALITY) @@ -1288,10 +1340,12 @@ void mjXReader::Option(XMLElement* section, mjOption* opt) { READDSBL("multiccd", mjDSBL_MULTICCD) #undef READDSBL -#define READENBL(NAME, MASK) \ - if (MapValue(elem, NAME, &n, enable_map, 2)) { \ - opt->enableflags ^= (opt->enableflags & MASK); \ - opt->enableflags |= (n ? MASK : 0); } +#define READENBL(NAME, MASK) \ + if (MapValue(elem, NAME, &n, enable_map, 2)) { \ + opt->enableflags ^= (opt->enableflags & MASK); \ + opt->enableflags |= (n ? MASK : 0); \ + s->authored.enableflags |= MASK; \ + } READENBL("override", mjENBL_OVERRIDE) READENBL("energy", mjENBL_ENERGY) @@ -3247,129 +3301,125 @@ void mjXReader::Custom(XMLElement* section) { // visual section parser void mjXReader::Visual(XMLElement* section) { - string text, name; + string name; XMLElement* elem; mjVisual* vis = &spec->visual; - int n; + Reader read(section, spec); // iterate over child elements elem = FirstChildElement(section); while (elem) { // get sub-element name name = elem->Value(); + read.set_node(elem); // global sub-element if (name == "global") { - ReadAttrInt(elem, "cameraid", &vis->global.cameraid); - if (MapValue(elem, "orthographic", &n, bool_map, 2)) { - vis->global.orthographic = (n == 1); - } - ReadAttr(elem, "fovy", 1, &vis->global.fovy, text); - ReadAttr(elem, "ipd", 1, &vis->global.ipd, text); - ReadAttr(elem, "azimuth", 1, &vis->global.azimuth, text); - ReadAttr(elem, "elevation", 1, &vis->global.elevation, text); - ReadAttr(elem, "linewidth", 1, &vis->global.linewidth, text); - ReadAttr(elem, "glow", 1, &vis->global.glow, text); - ReadAttrInt(elem, "offwidth", &vis->global.offwidth); - ReadAttrInt(elem, "offheight", &vis->global.offheight); - if (ReadAttr(elem, "realtime", 1, &vis->global.realtime, text)) { + read("cameraid", vis->global.cameraid); + read("orthographic", vis->global.orthographic, bool_map, 2); + read("fovy", 1, vis->global.fovy); + read("ipd", 1, vis->global.ipd); + read("azimuth", 1, vis->global.azimuth); + read("elevation", 1, vis->global.elevation); + read("linewidth", 1, vis->global.linewidth); + read("glow", 1, vis->global.glow); + read("offwidth", vis->global.offwidth); + read("offheight", vis->global.offheight); + if (read("realtime", 1, vis->global.realtime)) { if (vis->global.realtime <= 0) { throw mjXError(elem, "realtime must be greater than 0"); } } - if (MapValue(elem, "ellipsoidinertia", &n, bool_map, 2)) { - vis->global.ellipsoidinertia = (n == 1); - } - if (MapValue(elem, "bvactive", &n, bool_map, 2)) { - vis->global.bvactive = (n == 1); - } + read("ellipsoidinertia", vis->global.ellipsoidinertia, bool_map, 2); + read("bvactive", vis->global.bvactive, bool_map, 2); } // quality sub-element else if (name == "quality") { - ReadAttrInt(elem, "shadowsize", &vis->quality.shadowsize); - ReadAttrInt(elem, "offsamples", &vis->quality.offsamples); - ReadAttrInt(elem, "numslices", &vis->quality.numslices); - ReadAttrInt(elem, "numstacks", &vis->quality.numstacks); - ReadAttrInt(elem, "numquads", &vis->quality.numquads); + read("shadowsize", vis->quality.shadowsize); + read("offsamples", vis->quality.offsamples); + read("numslices", vis->quality.numslices); + read("numstacks", vis->quality.numstacks); + read("numquads", vis->quality.numquads); } // headlight sub-element else if (name == "headlight") { - ReadAttr(elem, "ambient", 3, vis->headlight.ambient, text); - ReadAttr(elem, "diffuse", 3, vis->headlight.diffuse, text); - ReadAttr(elem, "specular", 3, vis->headlight.specular, text); - ReadAttrInt(elem, "active", &vis->headlight.active); + read("ambient", 3, vis->headlight.ambient); + read("diffuse", 3, vis->headlight.diffuse); + read("specular", 3, vis->headlight.specular); + read("active", vis->headlight.active); } // map sub-element else if (name == "map") { - ReadAttr(elem, "stiffness", 1, &vis->map.stiffness, text); - ReadAttr(elem, "stiffnessrot", 1, &vis->map.stiffnessrot, text); - ReadAttr(elem, "force", 1, &vis->map.force, text); - ReadAttr(elem, "torque", 1, &vis->map.torque, text); - ReadAttr(elem, "alpha", 1, &vis->map.alpha, text); - ReadAttr(elem, "fogstart", 1, &vis->map.fogstart, text); - ReadAttr(elem, "fogend", 1, &vis->map.fogend, text); - ReadAttr(elem, "znear", 1, &vis->map.znear, text); - if (vis->map.znear <= 0) { - throw mjXError(elem, "znear must be strictly positive"); + read("stiffness", 1, vis->map.stiffness); + read("stiffnessrot", 1, vis->map.stiffnessrot); + read("force", 1, vis->map.force); + read("torque", 1, vis->map.torque); + read("alpha", 1, vis->map.alpha); + read("fogstart", 1, vis->map.fogstart); + read("fogend", 1, vis->map.fogend); + if (read("znear", 1, vis->map.znear)) { + if (vis->map.znear <= 0) { + throw mjXError(elem, "znear must be strictly positive"); + } } - ReadAttr(elem, "zfar", 1, &vis->map.zfar, text); - ReadAttr(elem, "haze", 1, &vis->map.haze, text); - ReadAttr(elem, "shadowclip", 1, &vis->map.shadowclip, text); - ReadAttr(elem, "shadowscale", 1, &vis->map.shadowscale, text); - ReadAttr(elem, "actuatortendon", 1, &vis->map.actuatortendon, text); + read("zfar", 1, vis->map.zfar); + read("haze", 1, vis->map.haze); + read("shadowclip", 1, vis->map.shadowclip); + read("shadowscale", 1, vis->map.shadowscale); + read("actuatortendon", 1, vis->map.actuatortendon); } // scale sub-element else if (name == "scale") { - ReadAttr(elem, "forcewidth", 1, &vis->scale.forcewidth, text); - ReadAttr(elem, "contactwidth", 1, &vis->scale.contactwidth, text); - ReadAttr(elem, "contactheight", 1, &vis->scale.contactheight, text); - ReadAttr(elem, "connect", 1, &vis->scale.connect, text); - ReadAttr(elem, "com", 1, &vis->scale.com, text); - ReadAttr(elem, "camera", 1, &vis->scale.camera, text); - ReadAttr(elem, "light", 1, &vis->scale.light, text); - ReadAttr(elem, "selectpoint", 1, &vis->scale.selectpoint, text); - ReadAttr(elem, "jointlength", 1, &vis->scale.jointlength, text); - ReadAttr(elem, "jointwidth", 1, &vis->scale.jointwidth, text); - ReadAttr(elem, "actuatorlength", 1, &vis->scale.actuatorlength, text); - ReadAttr(elem, "actuatorwidth", 1, &vis->scale.actuatorwidth, text); - ReadAttr(elem, "framelength", 1, &vis->scale.framelength, text); - ReadAttr(elem, "framewidth", 1, &vis->scale.framewidth, text); - ReadAttr(elem, "constraint", 1, &vis->scale.constraint, text); - ReadAttr(elem, "slidercrank", 1, &vis->scale.slidercrank, text); - ReadAttr(elem, "frustum", 1, &vis->scale.frustum, text); + read("forcewidth", 1, vis->scale.forcewidth); + read("contactwidth", 1, vis->scale.contactwidth); + read("contactheight", 1, vis->scale.contactheight); + read("connect", 1, vis->scale.connect); + read("com", 1, vis->scale.com); + read("camera", 1, vis->scale.camera); + read("light", 1, vis->scale.light); + read("selectpoint", 1, vis->scale.selectpoint); + read("jointlength", 1, vis->scale.jointlength); + read("jointwidth", 1, vis->scale.jointwidth); + read("actuatorlength", 1, vis->scale.actuatorlength); + read("actuatorwidth", 1, vis->scale.actuatorwidth); + read("framelength", 1, vis->scale.framelength); + read("framewidth", 1, vis->scale.framewidth); + read("constraint", 1, vis->scale.constraint); + read("slidercrank", 1, vis->scale.slidercrank); + read("frustum", 1, vis->scale.frustum); } // rgba sub-element else if (name == "rgba") { - ReadAttr(elem, "fog", 4, vis->rgba.fog, text); - ReadAttr(elem, "haze", 4, vis->rgba.haze, text); - ReadAttr(elem, "force", 4, vis->rgba.force, text); - ReadAttr(elem, "inertia", 4, vis->rgba.inertia, text); - ReadAttr(elem, "joint", 4, vis->rgba.joint, text); - ReadAttr(elem, "actuator", 4, vis->rgba.actuator, text); - ReadAttr(elem, "actuatornegative", 4, vis->rgba.actuatornegative, text); - ReadAttr(elem, "actuatorpositive", 4, vis->rgba.actuatorpositive, text); - ReadAttr(elem, "com", 4, vis->rgba.com, text); - ReadAttr(elem, "camera", 4, vis->rgba.camera, text); - ReadAttr(elem, "light", 4, vis->rgba.light, text); - ReadAttr(elem, "selectpoint", 4, vis->rgba.selectpoint, text); - ReadAttr(elem, "connect", 4, vis->rgba.connect, text); - ReadAttr(elem, "contactpoint", 4, vis->rgba.contactpoint, text); - ReadAttr(elem, "contactforce", 4, vis->rgba.contactforce, text); - ReadAttr(elem, "contactfriction", 4, vis->rgba.contactfriction, text); - ReadAttr(elem, "contacttorque", 4, vis->rgba.contacttorque, text); - ReadAttr(elem, "contactgap", 4, vis->rgba.contactgap, text); - ReadAttr(elem, "rangefinder", 4, vis->rgba.rangefinder, text); - ReadAttr(elem, "constraint", 4, vis->rgba.constraint, text); - ReadAttr(elem, "slidercrank", 4, vis->rgba.slidercrank, text); - ReadAttr(elem, "crankbroken", 4, vis->rgba.crankbroken, text); - ReadAttr(elem, "frustum", 4, vis->rgba.frustum, text); - ReadAttr(elem, "bv", 4, vis->rgba.bv, text); - ReadAttr(elem, "bvactive", 4, vis->rgba.bvactive, text); + read("fog", 4, vis->rgba.fog); + read("haze", 4, vis->rgba.haze); + read("force", 4, vis->rgba.force); + read("inertia", 4, vis->rgba.inertia); + read("joint", 4, vis->rgba.joint); + read("actuator", 4, vis->rgba.actuator); + read("actuatornegative", 4, vis->rgba.actuatornegative); + read("actuatorpositive", 4, vis->rgba.actuatorpositive); + read("com", 4, vis->rgba.com); + read("camera", 4, vis->rgba.camera); + read("light", 4, vis->rgba.light); + read("selectpoint", 4, vis->rgba.selectpoint); + read("connect", 4, vis->rgba.connect); + read("contactpoint", 4, vis->rgba.contactpoint); + read("contactforce", 4, vis->rgba.contactforce); + read("contactfriction", 4, vis->rgba.contactfriction); + read("contacttorque", 4, vis->rgba.contacttorque); + read("contactgap", 4, vis->rgba.contactgap); + read("rangefinder", 4, vis->rgba.rangefinder); + read("constraint", 4, vis->rgba.constraint); + read("slidercrank", 4, vis->rgba.slidercrank); + read("crankbroken", 4, vis->rgba.crankbroken); + read("frustum", 4, vis->rgba.frustum); + read("bv", 4, vis->rgba.bv); + read("bvactive", 4, vis->rgba.bvactive); } // advance to next element diff --git a/src/xml/xml_native_reader.h b/src/xml/xml_native_reader.h index 8c568b22..0f506efc 100644 --- a/src/xml/xml_native_reader.h +++ b/src/xml/xml_native_reader.h @@ -44,7 +44,7 @@ class mjXReader : public mjXBase { // XML sections embedded in all formats static void Compiler(tinyxml2::XMLElement* section, mjSpec* s); // compiler section - static void Option(tinyxml2::XMLElement* section, mjOption* opt); // option section + static void Option(tinyxml2::XMLElement* section, mjSpec* s, mjOption* opt); // option section static void Size(tinyxml2::XMLElement* section, mjSpec* s); // size section private: diff --git a/src/xml/xml_urdf.cc b/src/xml/xml_urdf.cc index e077789c..d927069c 100644 --- a/src/xml/xml_urdf.cc +++ b/src/xml/xml_urdf.cc @@ -99,7 +99,7 @@ void mjXURDF::Parse( } if ((section = FindSubElem(mjc, "option"))) { - mjXReader::Option(section, &spec->option); + mjXReader::Option(section, spec, &spec->option); } if ((section = FindSubElem(mjc, "size"))) { diff --git a/src/xml/xml_util.cc b/src/xml/xml_util.cc index 7bffe9c2..14dcdb41 100644 --- a/src/xml/xml_util.cc +++ b/src/xml/xml_util.cc @@ -845,14 +845,15 @@ template int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, int len, // throw error if identically zero int mjXUtil::ReadQuat(XMLElement* elem, const char* attr, double* data, std::string& text, bool required) { - ReadAttr(elem, attr, /*len=*/4, data, text, required, /*exact=*/true); + int n = ReadAttr(elem, attr, /*len=*/4, data, text, required, /*exact=*/true); + if (n == 0) return 0; // check for 0 quaternion if (data[0] == 0 && data[1] == 0 && data[2] == 0 && data[3] == 0) { throw mjXError(elem, "zero quaternion is not allowed"); } - return 4; + return n; } // read DOUBLE array into C++ vector, return number read diff --git a/test/mjspecmacro_test.cc b/test/mjspecmacro_test.cc index d94b42ba..d5dcf359 100644 --- a/test/mjspecmacro_test.cc +++ b/test/mjspecmacro_test.cc @@ -30,7 +30,10 @@ namespace expected { struct mjsElement { MJSELEMENT_FIELDS }; struct mjsCompiler { MJSCOMPILER_FIELDS }; -struct mjSpec { MJSPEC_FIELDS }; +struct mjSpec { + MJSPEC_FIELDS + mjsAuthored authored; +}; struct mjsOrientation { MJSORIENTATION_FIELDS }; struct mjsPlugin { MJSPLUGIN_FIELDS }; struct mjsBody { MJSBODY_FIELDS }; diff --git a/test/xml/xml_native_reader_test.cc b/test/xml/xml_native_reader_test.cc index ee36ca6c..8bd977ef 100644 --- a/test/xml/xml_native_reader_test.cc +++ b/test/xml/xml_native_reader_test.cc @@ -29,6 +29,7 @@ #include #include "src/cc/array_safety.h" #include "src/engine/engine_util_errmem.h" +#include "src/user/user_api.h" #include "src/xml/xml_api.h" #include "test/compare_model.h" #include "test/fixture.h" @@ -64,6 +65,73 @@ TEST_F(XMLReaderTest, UniqueElementTest) { EXPECT_THAT(error.data(), HasSubstr("unique element 'flag' found 2 times")); } +TEST_F(XMLReaderTest, AuthoredFromXml) { + static constexpr char xml[] = R"( + + + + + + + + + + )"; + + std::array error; + mjSpec* spec = mj_parseXMLString(xml, 0, error.data(), error.size()); + ASSERT_THAT(spec, NotNull()) << error.data(); + + // option: timestep authored, gravity authored, density not authored + EXPECT_EQ(mjs_isAuthored(spec, &spec->option.timestep), 1); + EXPECT_EQ(mjs_isAuthored(spec, spec->option.gravity), 1); + EXPECT_EQ(mjs_isAuthored(spec, &spec->option.density), 0); + + // compiler: boundmass authored, boundinertia not authored + EXPECT_EQ(mjs_isAuthored(spec, &spec->compiler.boundmass), 1); + EXPECT_EQ(mjs_isAuthored(spec, &spec->compiler.boundinertia), 0); + + // flags: constraint disable authored, contact not authored + EXPECT_NE(spec->authored.disableflags & mjDSBL_CONSTRAINT, 0); + EXPECT_EQ(spec->authored.disableflags & mjDSBL_CONTACT, 0); + + // visual global: fovy authored, ipd not authored + EXPECT_EQ(mjs_isAuthored(spec, &spec->visual.global.fovy), 1); + EXPECT_EQ(mjs_isAuthored(spec, &spec->visual.global.ipd), 0); + + // visual quality: shadowsize authored, offsamples not authored + EXPECT_EQ(mjs_isAuthored(spec, &spec->visual.quality.shadowsize), 1); + EXPECT_EQ(mjs_isAuthored(spec, &spec->visual.quality.offsamples), 0); + + mj_deleteSpec(spec); +} + +TEST_F(XMLReaderTest, AuthoredDefaultsZero) { + static constexpr char xml[] = R"( + + + + )"; + + std::array error; + mjSpec* spec = mj_parseXMLString(xml, 0, error.data(), error.size()); + ASSERT_THAT(spec, NotNull()) << error.data(); + + // nothing authored in an empty model + EXPECT_EQ(mjs_isAuthored(spec, &spec->option.timestep), 0); + EXPECT_EQ(mjs_isAuthored(spec, &spec->option.gravity), 0); + EXPECT_EQ(mjs_isAuthored(spec, &spec->compiler.boundmass), 0); + EXPECT_EQ(spec->authored.disableflags, 0); + EXPECT_EQ(spec->authored.enableflags, 0); + EXPECT_EQ(spec->authored.disableactuator, 0); + EXPECT_EQ(mjs_isAuthored(spec, &spec->visual.global.fovy), 0); + EXPECT_EQ(mjs_isAuthored(spec, &spec->visual.map.znear), 0); + + mj_deleteSpec(spec); +} + TEST_F(XMLReaderTest, MemorySize) { std::array error; { diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index 26bfe6d6..0da6ee5b 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -5868,6 +5868,21 @@ public unsafe struct mjsCompiler_ { public mjLROpt_ LRopt; public void* meshdir; public void* texturedir; + public UInt64 authored; +} + +[StructLayout(LayoutKind.Sequential)] +public unsafe struct mjsAuthored_ { + public UInt64 option; + public int disableflags; + public int enableflags; + public int disableactuator; + public UInt64 visual_global; + public UInt64 visual_quality; + public UInt64 visual_headlight; + public UInt64 visual_map; + public UInt64 visual_scale; + public UInt64 visual_rgba; } [StructLayout(LayoutKind.Sequential)] diff --git a/wasm/codegen/generated/bindings.cc b/wasm/codegen/generated/bindings.cc index 5302989a..74455c6e 100644 --- a/wasm/codegen/generated/bindings.cc +++ b/wasm/codegen/generated/bindings.cc @@ -1503,6 +1503,75 @@ struct MjWarningStat { bool owned_ = false; }; +struct MjsAuthored { + explicit MjsAuthored(mjsAuthored *ptr); + mjsAuthored* get() const; + void set(mjsAuthored* ptr); + uint64_t option() const { + return ptr_->option; + } + void set_option(uint64_t value) { + ptr_->option = value; + } + int disableflags() const { + return ptr_->disableflags; + } + void set_disableflags(int value) { + ptr_->disableflags = value; + } + int enableflags() const { + return ptr_->enableflags; + } + void set_enableflags(int value) { + ptr_->enableflags = value; + } + int disableactuator() const { + return ptr_->disableactuator; + } + void set_disableactuator(int value) { + ptr_->disableactuator = value; + } + uint64_t visual_global() const { + return ptr_->visual_global; + } + void set_visual_global(uint64_t value) { + ptr_->visual_global = value; + } + uint64_t visual_quality() const { + return ptr_->visual_quality; + } + void set_visual_quality(uint64_t value) { + ptr_->visual_quality = value; + } + uint64_t visual_headlight() const { + return ptr_->visual_headlight; + } + void set_visual_headlight(uint64_t value) { + ptr_->visual_headlight = value; + } + uint64_t visual_map() const { + return ptr_->visual_map; + } + void set_visual_map(uint64_t value) { + ptr_->visual_map = value; + } + uint64_t visual_scale() const { + return ptr_->visual_scale; + } + void set_visual_scale(uint64_t value) { + ptr_->visual_scale = value; + } + uint64_t visual_rgba() const { + return ptr_->visual_rgba; + } + void set_visual_rgba(uint64_t value) { + ptr_->visual_rgba = value; + } + + private: + mjsAuthored* ptr_; +}; + struct MjsElement { explicit MjsElement(mjsElement *ptr); mjsElement* get() const; @@ -2273,6 +2342,12 @@ struct MjsCompiler { *(ptr_->texturedir) = value; } } + uint64_t authored() const { + return ptr_->authored; + } + void set_authored(uint64_t value) { + ptr_->authored = value; + } private: mjsCompiler* ptr_; @@ -5866,6 +5941,7 @@ struct MjSpec { MjOption option; MjVisual visual; MjStatistic stat; + MjsAuthored authored; }; struct MjsActuator { @@ -7672,6 +7748,14 @@ void MjWarningStat::set(mjWarningStat* ptr) { ptr_ = ptr; } +MjsAuthored::MjsAuthored(mjsAuthored *ptr) : ptr_(ptr) {} +mjsAuthored* MjsAuthored::get() const { + return ptr_; +} +void MjsAuthored::set(mjsAuthored* ptr) { + ptr_ = ptr; +} + MjsElement::MjsElement(mjsElement *ptr) : ptr_(ptr) {} mjsElement* MjsElement::get() const { return ptr_; @@ -8514,7 +8598,8 @@ MjSpec::MjSpec() compiler(&ptr_->compiler), option(&ptr_->option), visual(&ptr_->visual), - stat(&ptr_->stat) { + stat(&ptr_->stat), + authored(&ptr_->authored) { owned_ = true; mjs_defaultSpec(ptr_); }; @@ -8525,7 +8610,8 @@ MjSpec::MjSpec(mjSpec *ptr) compiler(&ptr_->compiler), option(&ptr_->option), visual(&ptr_->visual), - stat(&ptr_->stat) {} + stat(&ptr_->stat), + authored(&ptr_->authored) {} MjSpec::MjSpec(const MjSpec &other) : ptr_(mj_copySpec(other.get())), @@ -8533,7 +8619,8 @@ MjSpec::MjSpec(const MjSpec &other) compiler(&ptr_->compiler), option(&ptr_->option), visual(&ptr_->visual), - stat(&ptr_->stat) { + stat(&ptr_->stat), + authored(&ptr_->authored) { owned_ = true; } @@ -12630,6 +12717,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) { emscripten::class_("MjSpec") .constructor() .property("timer", &MjSpec::timer) + .property("authored", &MjSpec::authored, reference()) .property("comment", &MjSpec::comment, &MjSpec::set_comment, reference()) .property("compiler", &MjSpec::compiler, reference()) .property("element", &MjSpec::element, reference()) @@ -12810,6 +12898,17 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) { .property("target", &MjsActuator::target, &MjsActuator::set_target, reference()) .property("trntype", &MjsActuator::trntype, &MjsActuator::set_trntype, reference()) .property("userdata", &MjsActuator::userdata, reference()); + emscripten::class_("MjsAuthored") + .property("disableactuator", &MjsAuthored::disableactuator, &MjsAuthored::set_disableactuator, reference()) + .property("disableflags", &MjsAuthored::disableflags, &MjsAuthored::set_disableflags, reference()) + .property("enableflags", &MjsAuthored::enableflags, &MjsAuthored::set_enableflags, reference()) + .property("option", &MjsAuthored::option, &MjsAuthored::set_option, reference()) + .property("visual_global", &MjsAuthored::visual_global, &MjsAuthored::set_visual_global, reference()) + .property("visual_headlight", &MjsAuthored::visual_headlight, &MjsAuthored::set_visual_headlight, reference()) + .property("visual_map", &MjsAuthored::visual_map, &MjsAuthored::set_visual_map, reference()) + .property("visual_quality", &MjsAuthored::visual_quality, &MjsAuthored::set_visual_quality, reference()) + .property("visual_rgba", &MjsAuthored::visual_rgba, &MjsAuthored::set_visual_rgba, reference()) + .property("visual_scale", &MjsAuthored::visual_scale, &MjsAuthored::set_visual_scale, reference()); emscripten::class_("MjsBody") .property("alt", &MjsBody::alt, reference()) .property("childclass", &MjsBody::childclass, &MjsBody::set_childclass, reference()) @@ -12852,6 +12951,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) { emscripten::class_("MjsCompiler") .property("LRopt", &MjsCompiler::LRopt, reference()) .property("alignfree", &MjsCompiler::alignfree, &MjsCompiler::set_alignfree, reference()) + .property("authored", &MjsCompiler::authored, &MjsCompiler::set_authored, reference()) .property("autolimits", &MjsCompiler::autolimits, &MjsCompiler::set_autolimits, reference()) .property("balanceinertia", &MjsCompiler::balanceinertia, &MjsCompiler::set_balanceinertia, reference()) .property("boundinertia", &MjsCompiler::boundinertia, &MjsCompiler::set_boundinertia, reference()) @@ -13390,6 +13490,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) { .property("translate", &MjvScene::translate); emscripten::register_optional(); emscripten::register_optional(); + emscripten::register_optional(); emscripten::register_optional(); emscripten::register_optional(); emscripten::register_optional(); diff --git a/wasm/codegen/templates/bindings.cc b/wasm/codegen/templates/bindings.cc index dd76de48..df506497 100644 --- a/wasm/codegen/templates/bindings.cc +++ b/wasm/codegen/templates/bindings.cc @@ -666,7 +666,8 @@ MjSpec::MjSpec() compiler(&ptr_->compiler), option(&ptr_->option), visual(&ptr_->visual), - stat(&ptr_->stat) { + stat(&ptr_->stat), + authored(&ptr_->authored) { owned_ = true; mjs_defaultSpec(ptr_); }; @@ -677,7 +678,8 @@ MjSpec::MjSpec(mjSpec *ptr) compiler(&ptr_->compiler), option(&ptr_->option), visual(&ptr_->visual), - stat(&ptr_->stat) {} + stat(&ptr_->stat), + authored(&ptr_->authored) {} MjSpec::MjSpec(const MjSpec &other) : ptr_(mj_copySpec(other.get())), @@ -685,7 +687,8 @@ MjSpec::MjSpec(const MjSpec &other) compiler(&ptr_->compiler), option(&ptr_->option), visual(&ptr_->visual), - stat(&ptr_->stat) { + stat(&ptr_->stat), + authored(&ptr_->authored) { owned_ = true; }