Skip to content

Support include below the root element - #549

Open
shubhxho wants to merge 1 commit into
google-deepmind:mainfrom
shubhxho:nested-include
Open

Support include below the root element#549
shubhxho wants to merge 1 commit into
google-deepmind:mainfrom
shubhxho:nested-include

Conversation

@shubhxho

Copy link
Copy Markdown

Fixes #529.

Problem

_parse looks for includes with xml_root.findall('include'), which only finds direct children of <mujoco>. MuJoCo allows <include/> wherever a child element is allowed. Anything deeper is left in the tree, reaches the schema, and comes back as an error about the tag rather than about includes:

mjcf.from_path('main.xml')
KeyError: "Line 3: error while parsing element <include>: 'include'"

for

<mujoco model="main">
  <default>
    <include file="parts.xml"/>
  </default>
  ...

which mujoco.MjModel.from_xml_path accepts. The motivating case in the issue is musculoskeletal models that group defaults per class:

<default class="forearm_muscle">
  <muscle ctrllimited="true" ctrlrange="-1 1"/>
  <tendon width="0.002"/>
</default>

Fix

Expand non-root includes in place before parsing: read the named file, check its root is <mujoco.*>, and put its children where the tag was. Recursive, so a file included from a subdirectory can include further files relative to itself.

Top level includes are left alone. They keep going through include_copy as whole parsed models, so their own model_dir and asset handling are unchanged, and no existing model parses differently.

After:

<mujoco model="main">
  <default>
    <default class="/">
      <default class="forearm_muscle">
        <tendon width="0.002"/>
      </default>
    </default>
  </default>
  ...

Testing

Three new assets and a test that the included content lands in the enclosing element rather than being merged at the root - a body included inside <worldbody> has worldbody as its parent, and defaults included inside a class end up in that class. The three existing parameterised parse tests gain a WithNestedInclude case.

$ pytest dm_control/mjcf
158 passed

The four new tests fail without the change to parser.py. I also checked the round trip compiles: mujoco.MjModel.from_xml_string(model.to_xml_string()) on a model with an include nested two levels deep, in a subdirectory, that itself includes another file.

Note

This picks the splice-in-place reading of nested includes, which is what MuJoCo does, rather than extending the whole-model include_copy path downwards. Happy to change the approach if you would rather nested includes behave like the top level ones.

MuJoCo allows <include/> wherever a child element is allowed and puts the
contents of the named file in its place, but the parser only looked for it
as a child of <mujoco>. Anything deeper was left in the tree and reached the
schema, which reported it as a KeyError on the tag rather than as anything
to do with includes.

Expand those in place before parsing, recursively, so a file included from a
subdirectory can include further files relative to itself. Top level includes
still go through include_copy as whole models so they keep their own asset
directories. Fixes google-deepmind#529.

Signed-off-by: Shubh <shubh@shubhxho.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mjcf parser only supports include at root level

1 participant