diff --git a/python/mujoco/codegen/generate_spec_bindings.py b/python/mujoco/codegen/generate_spec_bindings.py index e6983263..78448097 100644 --- a/python/mujoco/codegen/generate_spec_bindings.py +++ b/python/mujoco/codegen/generate_spec_bindings.py @@ -43,6 +43,13 @@ def _value_binding_code( fulltype = fulltype + '&' # plugin and orientation are not pointers else: fulltype = fulltype + '*' + # non-mjs structs + rawclassname = rawclassname.replace('mjOption', 'raw::MjOption') + rawclassname = rawclassname.replace('mjVisual', 'raw::MjVisual') + rawclassname = rawclassname.replace('mjStatistic', 'raw::MjStatistic') + fulltype = fulltype.replace('mjOption', 'raw::MjOption') + fulltype = fulltype.replace('mjVisual', 'raw::MjVisual') + fulltype = fulltype.replace('mjStatistic', 'raw::MjStatistic') def_property_args = ( f'"{varname}"', @@ -68,6 +75,9 @@ def _array_binding_code( raise NotImplementedError() innertype = field.inner_type.decl() rawclassname = classname.replace('mjs', 'raw::Mjs') + rawclassname = rawclassname.replace('mjOption', 'raw::MjOption') + rawclassname = rawclassname.replace('mjVisual', 'raw::MjVisual') + rawclassname = rawclassname.replace('mjStatistic', 'raw::MjStatistic') fullvarname = varname if classname == 'mjSpec': # raw mjSpec has a wrapper rawclassname = classname.replace('mjS', 'MjS') @@ -209,12 +219,17 @@ def _ptr_binding_code( }} }}, py::return_value_policy::reference_internal);""" - raise NotImplementedError() + raise NotImplementedError( + 'Unsupported array type: ' + vartype + ' in ' + classname + ) def _binding_code(field: ast_nodes.StructFieldDecl, key: str) -> str: if isinstance(field.type, ast_nodes.ValueType): return _value_binding_code(field.type, key, field.name) + elif isinstance(field.type, ast_nodes.AnonymousStructDecl): + field.type = ast_nodes.ValueType(name='mjVisual'+field.name.title()) + return _value_binding_code(field.type, key, field.name) elif isinstance(field.type, ast_nodes.PointerType): return _ptr_binding_code(field.type, key, field.name) elif isinstance(field.type, ast_nodes.ArrayType): @@ -224,7 +239,10 @@ def _binding_code(field: ast_nodes.StructFieldDecl, key: str) -> str: def generate() -> None: for key in structs.STRUCTS.keys(): - if (key.startswith('mjs') or key == 'mjSpec') and key != 'mjsElement': + if ( + key.startswith('mjs') + or key in ['mjSpec', 'mjOption', 'mjVisual', 'mjStatistic'] + ) and key != 'mjsElement': print('\n // ' + key) for field in structs.STRUCTS[key].fields: code = _binding_code(field, key) diff --git a/python/mujoco/specs.cc b/python/mujoco/specs.cc index 04200af6..07d52fc3 100644 --- a/python/mujoco/specs.cc +++ b/python/mujoco/specs.cc @@ -109,6 +109,9 @@ PYBIND11_MODULE(_specs, m) { py::class_ mjsPlugin(m, "MjsPlugin"); py::class_ mjsOrientation(m, "MjsOrientation"); py::class_ mjsWrap(m, "MjsWrap"); + py::class_ mjOption(m, "MjOption"); + py::class_ mjStatistic(m, "MjStatistic"); + py::class_ mjVisual(m, "MjVisual"); // ============================= MJSPEC ===================================== mjSpec.def(py::init<>()); diff --git a/python/mujoco/specs_test.py b/python/mujoco/specs_test.py index 8ddaf8c1..8fed347e 100644 --- a/python/mujoco/specs_test.py +++ b/python/mujoco/specs_test.py @@ -717,5 +717,30 @@ class SpecsTest(absltest.TestCase): self.assertIsNotNone(model) self.assertEqual(model.nplugin, 0) + def test_access_option_stat_visual(self): + spec = mujoco.MjSpec() + spec.from_string(textwrap.dedent(""" + + + """)) + self.assertEqual(spec.option.timestep, 0.001) + self.assertEqual(spec.stat.meansize, 0.05) + self.assertEqual(spec.visual.quality.shadowsize, 4096) + + spec.option.timestep = 0.002 + spec.stat.meansize = 0.06 + spec.visual.quality.shadowsize = 8192 + + model = spec.compile() + + self.assertEqual(model.opt.timestep, 0.002) + self.assertEqual(model.stat.meansize, 0.06) + self.assertEqual(model.vis.quality.shadowsize, 8192) + if __name__ == '__main__': absltest.main()