From 696477ca77014eec58488c150505a05d5174ecd3 Mon Sep 17 00:00:00 2001 From: Jeremy Schoemaker Date: Mon, 24 Aug 2026 10:51:15 -0500 Subject: [PATCH] Restore schema declarations lost in the regenerated schema.xml The schema regeneration in 985d0940 dropped three declarations that MuJoCo 3.12 still accepts, breaking previously working PyMJCF models: - sensor lost its contact child element, so parsing a model with raises KeyError while raw MuJoCo loads it fine. - jointinparent on the nine actuator elements degraded from type="reference" reference_namespace="joint" to type="string", so attach() no longer prefixes the joint name and the composed model fails to compile with "unknown transmission target". - custom/numeric data degraded from a float array to a string, so add('numeric', name='x', data=[1, 2, 3]) raises ValueError. Restore the three declarations as they were before the regeneration and add regression tests covering all three. --- dm_control/mjcf/schema.xml | 42 ++++++++++++++++++++++++++-------- dm_control/mjcf/schema_test.py | 41 +++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 10 deletions(-) diff --git a/dm_control/mjcf/schema.xml b/dm_control/mjcf/schema.xml index cf376fa8..d9c13bd1 100644 --- a/dm_control/mjcf/schema.xml +++ b/dm_control/mjcf/schema.xml @@ -2193,7 +2193,7 @@ - + @@ -2232,7 +2232,7 @@ - + @@ -2260,7 +2260,7 @@ - + @@ -2291,7 +2291,7 @@ - + @@ -2322,7 +2322,7 @@ - + @@ -2372,7 +2372,7 @@ - + @@ -2400,7 +2400,7 @@ - + @@ -2431,7 +2431,7 @@ - + @@ -2486,7 +2486,7 @@ - + @@ -3128,6 +3128,28 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -3182,7 +3204,7 @@ - + diff --git a/dm_control/mjcf/schema_test.py b/dm_control/mjcf/schema_test.py index 1eda0e8c..6f52630d 100644 --- a/dm_control/mjcf/schema_test.py +++ b/dm_control/mjcf/schema_test.py @@ -187,5 +187,46 @@ def constructible(spec_node, path): + '\n'.join(failures[:20])) +class SchemaRegressionTest(absltest.TestCase): + """Declarations that MuJoCo accepts must stay representable in PyMJCF.""" + + def test_contact_sensor_parses_and_compiles(self): + xml_string = """ + + + + + + + + + + + """ + root = mjcf.from_xml_string(xml_string) + physics = mjcf.Physics.from_mjcf_model(root) + self.assertEqual(physics.model.nsensor, 1) + self.assertEqual(root.find('sensor', 'cs').geom1.name, 'g') + + def test_jointinparent_is_scoped_on_attach(self): + child = mjcf.RootElement(model='child') + body = child.worldbody.add('body', name='b') + body.add('joint', name='j', type='hinge') + body.add('geom', name='g', size=[0.1]) + child.actuator.add('general', name='a', jointinparent='j') + parent = mjcf.RootElement(model='parent') + parent.attach(child) + self.assertIn('jointinparent="child/j"', parent.to_xml_string()) + physics = mjcf.Physics.from_mjcf_model(parent) + self.assertEqual(physics.model.nu, 1) + + def test_custom_numeric_accepts_array_data(self): + root = mjcf.RootElement(model='m') + root.custom.add('numeric', name='x', data=[1, 2, 3]) + physics = mjcf.Physics.from_mjcf_model(root) + self.assertEqual(physics.model.nnumericdata, 3) + + if __name__ == '__main__': absltest.main()