google-deepmind / dm_control

Google DeepMind's software stack for physics-based simulation and Reinforcement Learning environments, using MuJoCo.
Apache License 2.0
3.64k stars 648 forks source link

How to better avoid "Error: free joint can only be used on top level" when combining two models #333

Open XinyuWang2 opened 1 year ago

XinyuWang2 commented 1 year ago

I want to create separate xml files for robot, object, arena and combine them together later. Because all the objects are very similar, I decided to put all of them in one xml file. Each object are should move freely. Something like this:

<mujoco model="objects">
  <worldbody>
    <body >
      <freejoint/>
      <geom>
    </body>

    <body >
      <freejoint/>
      <geom>
    </body>
  </worldbody>

Then I use .attach to combine two models: modelArena.attach(modelObject)

But, this will create an outside <body that wraps all <body from modelObject. Something like this:

<mujoco model="arena>
  <worldbody>

<body name="objects">    
    <body>
          <freejoint/>
          <geom>
    </body>
    <body>
          <freejoint/>
          <geom>
    </body>
</body>
  </worldbody>

Then I will get a problem "Error: free joint can only be used on top level".

I know one solution would be to create xml file for each object individually and let the attachment frame add('freejoint'). But this is not very elegant, and it will create many redundant attributes (like <default, because each xml file has the same default setting)

So is there any better way to achieve the same result? I tried to find a way to do without creating another <body that wraps all these but still haven't found one. Thanks!

wookayin commented 1 year ago

From the MJCF documentation, it seems that the following is the only way at the moment to add a freejoint:

attachment_frame = parent_model.attach(child_model)
attachment_frame.add('freejoint', name=...)

UPDATE: But this still may produce child_name/... namespace (e.g., <freejoint name="child_name/some_body" />), and when instantiating a Physics from the MJCF-generated xml the error will happen due to the nested namespace.

Balint-H commented 1 year ago

Have you considered doing the combination in the XML, using the <include /> element? See the hammock.xml file in the example models, which can be thought of as the arena, and the humanoid.xml it includes, which could be thought of as the objects.

nikhilxb commented 1 year ago

For easy reference, the "hammock.xml"/"humanoid_body.xml" mentioned: https://github.com/deepmind/mujoco/tree/main/model/hammock

@Balint-H: My problem is that even though the <include /> format in your "hammock.xml" example works and is necessary when testing in "mujoco.viewer", once I try to programmatically attach the "humanoid_body.xml" to a different scene (e.g. when using dm_control.composer), then I get the error.

For example, this does not work:

from dm_control import mjcf, composer
from dm_control.locomotion import arenas

HUMANOID_XML_PATH = 'test/humanoid_body.xml'

class Humanoid(composer.Entity):
    def _build(self):
        self._mjcf_model = mjcf.from_path(HUMANOID_XML_PATH)

    @property
    def mjcf_model(self):
        return self._mjcf_model

robot = Humanoid()     # composer.Entity
arena = arenas.Floor() # composer.Entity
arena.attach(robot)

print(arena.mjcf_model.to_xml_string())

because it produces:

...
<worldbody>
    <geom name="groundplane" class="/" type="plane" size="8 8 0.25" material="groundplane"/>
    <camera name="top_camera" class="/" fovy="10.058147163570894" pos="0 0 100" quat="1 0 0 0"/>
    <body name="Humanoid body/">
      <body name="Humanoid body/torso" childclass="Humanoid body/body" pos="0 0 1.5">
        <camera name="Humanoid body/back" mode="trackcom" pos="-3 0 1" xyaxes="0 -1 0 1 0 2"/>
        <camera name="Humanoid body/side" mode="trackcom" pos="0 -3 1" xyaxes="1 0 0 0 1 2"/>
        <freejoint name="Humanoid body/root"/>
...

resulting in:

Error: free joint can only be used on top level

So the problem is what works for (1) mujoco.viewer doesn't work for (2) dm_control.composer. If I remove the <freejoint/> in the XML file, then use arena.add_free_entity(robot) then (2) works but (1) shows a fixed humanoid. Ideally, would like to find a solution for both to work. Any ideas?