diff --git a/doc/APIreference/functions.rst b/doc/APIreference/functions.rst index bec885ae..71ab1fa6 100644 --- a/doc/APIreference/functions.rst +++ b/doc/APIreference/functions.rst @@ -4580,6 +4580,15 @@ Find and get utilities Get spec from body. +.. _mjs_getCompiler: + +`mjs_getCompiler <#mjs_getCompiler>`__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. mujoco-include:: mjs_getCompiler + +Get compiler associated with element's origin spec. + .. _mjs_findSpec: `mjs_findSpec <#mjs_findSpec>`__ diff --git a/doc/changelog.rst b/doc/changelog.rst index 81b32760..78278f9f 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -15,6 +15,9 @@ General ``ten_J_colind`` have been moved from :ref:`mjData` to :ref:`mjModel` and are no longer computed at run time by ``mj_tendon`` but at compile time. +- Added :ref:`mjs_getCompiler` C API function and a ``compiler`` read-only property to all Python spec element types. + This allows querying the compiler settings (e.g., ``meshdir``) from any element, with the correct originating spec's + compiler preserved after attachment. - Added a new ``strain`` :ref:`equality constraint` type for trilinear and quadratic :ref:`dofs`. - Flexes now support collisions with SDF geoms. diff --git a/doc/includes/references.h b/doc/includes/references.h index fd005344..c0bf11bc 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -3643,6 +3643,7 @@ mjsTexture* mjs_addTexture(mjSpec* s); mjsMaterial* mjs_addMaterial(mjSpec* s, const mjsDefault* def); int mjs_makeMesh(mjsMesh* mesh, mjtMeshBuiltin builtin, double* params, int nparams); mjSpec* mjs_getSpec(mjsElement* element); +mjsCompiler* mjs_getCompiler(mjsElement* element); mjSpec* mjs_findSpec(mjSpec* spec, const char* name); mjsBody* mjs_findBody(mjSpec* s, const char* name); mjsElement* mjs_findElement(mjSpec* s, mjtObj type, const char* name); diff --git a/include/mujoco/mujoco.h b/include/mujoco/mujoco.h index 2c47c936..4578bc85 100644 --- a/include/mujoco/mujoco.h +++ b/include/mujoco/mujoco.h @@ -1734,6 +1734,9 @@ MJAPI int mjs_makeMesh(mjsMesh* mesh, mjtMeshBuiltin builtin, double* params, in // Get spec from body. MJAPI mjSpec* mjs_getSpec(mjsElement* element); +// Get compiler associated with element's origin spec. +MJAPI mjsCompiler* mjs_getCompiler(mjsElement* element); + // Find spec (model asset) by name. MJAPI mjSpec* mjs_findSpec(mjSpec* spec, const char* name); diff --git a/python/mujoco/codegen/generate_spec_bindings.py b/python/mujoco/codegen/generate_spec_bindings.py index 5e0d68ae..a3ce6ab9 100644 --- a/python/mujoco/codegen/generate_spec_bindings.py +++ b/python/mujoco/codegen/generate_spec_bindings.py @@ -947,6 +947,24 @@ def generate_name() -> None: print(code) +def generate_compiler() -> None: + """Generate compiler property for all spec element types.""" + for key, _, _, _, _ in SPECS: + elem = key.removeprefix('mjs') + titlecase = 'Mjs' + elem + code = f"""\n + {key}.def_property_readonly("compiler", + [](raw::{titlecase}& self) -> raw::MjsCompiler& {{ + ::mjsCompiler* compiler = mjs_getCompiler(self.element); + if (!compiler) {{ + throw pybind11::value_error("Element is not attached to a spec."); + }} + return *compiler; + }}, py::return_value_policy::reference_internal); + """ + print(code) + + def main(argv: Sequence[str]) -> None: if len(argv) > 1: raise app.UsageError('Too many command-line arguments.') @@ -956,6 +974,7 @@ def main(argv: Sequence[str]) -> None: generate_signature() generate_id() generate_name() + generate_compiler() if __name__ == '__main__': diff --git a/python/mujoco/introspect/functions.py b/python/mujoco/introspect/functions.py index 787dacff..a1e2d806 100644 --- a/python/mujoco/introspect/functions.py +++ b/python/mujoco/introspect/functions.py @@ -10823,6 +10823,22 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([ ), doc='Get spec from body.', )), + ('mjs_getCompiler', + FunctionDecl( + name='mjs_getCompiler', + return_type=PointerType( + inner_type=ValueType(name='mjsCompiler'), + ), + parameters=( + FunctionParameterDecl( + name='element', + type=PointerType( + inner_type=ValueType(name='mjsElement'), + ), + ), + ), + doc="Get compiler associated with element's origin spec.", + )), ('mjs_findSpec', FunctionDecl( name='mjs_findSpec', diff --git a/python/mujoco/specs_test.py b/python/mujoco/specs_test.py index c5976e3b..8b472a3c 100644 --- a/python/mujoco/specs_test.py +++ b/python/mujoco/specs_test.py @@ -1188,6 +1188,36 @@ class SpecsTest(absltest.TestCase): model = parent.compile() np.testing.assert_almost_equal(model.body_quat[1], [1, 0, 0, 0]) + def test_compiler_from_element(self): + child = mujoco.MjSpec() + child.meshdir = '/child/meshes' + child.texturedir = '/child/textures' + child_body = child.worldbody.add_body() + child_geom = child_body.add_geom() + child_geom.size[0] = 1 + child_site = child_body.add_site() + + parent = mujoco.MjSpec() + parent.meshdir = '/parent/meshes' + parent.texturedir = '/parent/textures' + parent_geom = parent.worldbody.add_geom() + parent_geom.size[0] = 1 + parent_site = parent.worldbody.add_site() + + self.assertEqual(parent_geom.compiler.meshdir, '/parent/meshes') + self.assertEqual(parent_geom.compiler.texturedir, '/parent/textures') + self.assertEqual(child_geom.compiler.meshdir, '/child/meshes') + self.assertEqual(child_site.compiler.meshdir, '/child/meshes') + + frame = parent.worldbody.add_frame() + frame.attach_body(child_body, prefix='child-') + + self.assertEqual(parent_geom.compiler.meshdir, '/parent/meshes') + self.assertEqual(parent_site.compiler.meshdir, '/parent/meshes') + self.assertEqual(child_geom.compiler.meshdir, '/child/meshes') + self.assertEqual(child_geom.compiler.texturedir, '/child/textures') + self.assertEqual(child_site.compiler.meshdir, '/child/meshes') + def test_attach_to_site(self): parent = mujoco.MjSpec() parent.assets = {'path/cube.obj': 'cube_content'} diff --git a/src/user/user_api.cc b/src/user/user_api.cc index 46b44ee1..2202478e 100644 --- a/src/user/user_api.cc +++ b/src/user/user_api.cc @@ -1074,6 +1074,12 @@ mjSpec* mjs_getSpec(mjsElement* element) { +mjsCompiler* mjs_getCompiler(mjsElement* element) { + return static_cast(element)->compiler; +} + + + // find spec (model asset) by name mjSpec* mjs_findSpec(mjSpec* s, const char* name) { mjCModel* model = static_cast(s->element); diff --git a/wasm/codegen/generated/bindings.cc b/wasm/codegen/generated/bindings.cc index 5cb9a5bd..9c83a652 100644 --- a/wasm/codegen/generated/bindings.cc +++ b/wasm/codegen/generated/bindings.cc @@ -9668,6 +9668,14 @@ std::optional mjs_firstElement_wrapper(MjSpec& s, mjtObj type) { return MjsElement(result); } +std::optional mjs_getCompiler_wrapper(MjsElement& element) { + mjsCompiler* result = mjs_getCompiler(element.get()); + if (result == nullptr) { + return std::nullopt; + } + return MjsCompiler(result); +} + std::optional mjs_getDefault_wrapper(MjsElement& element) { mjsDefault* result = mjs_getDefault(element.get()); if (result == nullptr) { @@ -13191,6 +13199,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) { function("mjs_findSpec", &mjs_findSpec_wrapper); function("mjs_firstChild", &mjs_firstChild_wrapper); function("mjs_firstElement", &mjs_firstElement_wrapper); + function("mjs_getCompiler", &mjs_getCompiler_wrapper); function("mjs_getDefault", &mjs_getDefault_wrapper); function("mjs_getError", &mjs_getError_wrapper); function("mjs_getFrame", &mjs_getFrame_wrapper);