Rewrite `` `` (MuJoCo's inline-data form) to `` `` by emitting an OBJ next to the rest of the mesh assets. Returns the number of inline meshes materialized. MuJoCo accepts meshes declared inline via ``vertex`` (flat list of 3-tuple floats
(root, mesh_base: Path)
| 218 | elif elem.tag == "compiler": |
| 219 | # Paths are now baked into each file=, so clear the dir prefixes. |
| 220 | for attr in ("meshdir", "texturedir", "assetdir"): |
| 221 | new_elem.attrib.pop(attr, None) |
| 222 | |
| 223 | for child in list(elem): |
| 224 | _append_expanded(new_elem, child, src_dir, root_dir, |
| 225 | meshdir, texturedir, assetdir, visited) |
| 226 | |
| 227 | |
| 228 | def flatten_includes(root, root_dir: Path): |
| 229 | """Resolve every <include> into a single self-contained <mujoco> tree. |
| 230 | |
| 231 | gym-aloha (and many MJCF models) split a robot across <include> fragments |
| 232 | rooted in <mujocoinclude>, including files referenced from inside <worldbody>. |
| 233 | Unreal's importer must see one flat document so all <asset>/<compiler>/body |
| 234 | content is visible and there is exactly one worldbody. Returns the new root |
| 235 | element (a <mujoco>). No-op-equivalent for files without includes. |
| 236 | """ |
| 237 | md, txd, ad = _compiler_dirs(root) |
| 238 | visited = set() |
| 239 | new_root = ET.Element("mujoco", dict(root.attrib)) |
| 240 | for child in list(root): |
| 241 | _append_expanded(new_root, child, root_dir, root_dir, md, txd, ad, visited) |
| 242 | return new_root |
| 243 | |
| 244 | |
| 245 | def materialize_inline_meshes(root, mesh_base: Path) -> int: |
| 246 | """Rewrite ``<mesh vertex="..." face="...">`` (MuJoCo's inline-data form) |
| 247 | to ``<mesh file="...">`` by emitting an OBJ next to the rest of the |
| 248 | mesh assets. Returns the number of inline meshes materialized. |
| 249 | |
| 250 | MuJoCo accepts meshes declared inline via ``vertex`` (flat list of |
| 251 | 3-tuple floats) and ``face`` (flat list of 3-tuple ints) attributes |
| 252 | instead of an external ``file``. Unreal's importer can't follow the |
| 253 | inline form, so we synthesize a real OBJ on disk and rewrite the |
| 254 | ``<mesh>`` element to point at it. The downstream conversion phase |
| 255 | then produces a GLB companion just like any other ``file=`` mesh. |
| 256 | """ |
| 257 | materialized = 0 |
| 258 | mesh_base.mkdir(parents=True, exist_ok=True) |
| 259 | for mesh_el in list(root.iter("mesh")): |
| 260 | if mesh_el.get("file"): |
| 261 | continue |
| 262 | vertex_attr = mesh_el.get("vertex") |
| 263 | face_attr = mesh_el.get("face") |
| 264 | if not vertex_attr or not face_attr: |
| 265 | continue |
| 266 | |
| 267 | name = mesh_el.get("name") |
| 268 | if not name: |
| 269 | print(f" x Inline mesh has no name; skipping (cannot synthesize filename)") |
| 270 | continue |
| 271 | |
| 272 | try: |
| 273 | verts = _parse_floats(vertex_attr) |
| 274 | faces = [int(t) for t in face_attr.replace(",", " ").split() if t] |
| 275 | except ValueError as e: |
| 276 | print(f" x Failed to parse vertex/face for '{name}': {e}") |
| 277 | continue |
no test coverage detected