Source code for unisim.backend.mjwarp.materialization
"""Cold-path scene materialization for the independent ``mjwarp`` backend."""
from __future__ import annotations
import os
from dataclasses import dataclass
from typing import Any
from unisim.scene import SceneCfg
class _TemporarySceneCleanup:
"""Own the temporary XMLs created while materializing one scene."""
def __init__(self, *paths: str) -> None:
self._paths = paths
self._cleaned = False
def cleanup(self) -> None:
if self._cleaned:
return
self._cleaned = True
for path in self._paths:
try:
os.remove(path)
except FileNotFoundError:
pass
[docs]
@dataclass(frozen=True)
class MjwarpSceneContext:
"""Cold-path scene source and cleanup ownership for one backend instance."""
source_model_file: str
diagnostic_model_file: str
cleanup_handle: Any | None = None
tracked_body_names: tuple[str, ...] = ()
[docs]
def materialize_mjwarp_scene(
scene: SceneCfg,
*,
add_body_sensors: bool = False,
) -> MjwarpSceneContext:
"""Resolve a flat/fragments scene before CUDA model upload.
Height-field terrain construction is intentionally rejected in the first
correctness profile. The rejection happens before model upload so an
unsupported owner cannot silently fall back to a different terrain path.
When ``add_body_sensors`` is set, world-frame body tracking sensors are
injected into the resolved model on the cold path (same helper as the
MuJoCo backend) so the host profile can serve body kinematics from its
per-step sensor cache without extra device transfers.
"""
if scene is None or not scene.model_file:
raise ValueError("MjwarpBackend requires SceneCfg.model_file")
if scene.terrain is not None:
raise NotImplementedError(
"mjwarp host_numpy profile does not support generated terrain or height-field "
"scanners; select a flat owner YAML or a backend with terrain support."
)
temp_paths: list[str] = []
if not scene.fragment_files:
source_model_file = str(scene.model_file)
else:
# This is intentionally in a cold-path-only module. The shared XML
# composition helper is not a sibling runtime backend dependency.
from unisim.backend.mujoco.xml import materialize_scene_fragments
source_model_file = materialize_scene_fragments(
str(scene.model_file),
fragment_files=scene.fragment_files,
)
temp_paths.append(source_model_file)
tracked_body_names: tuple[str, ...] = ()
if add_body_sensors:
from unisim.backend.mujoco.xml import inject_mujoco_tracking_sensors
source_model_file, _tracked_body_ids, valid_bnames = inject_mujoco_tracking_sensors(
source_model_file
)
temp_paths.append(source_model_file)
tracked_body_names = tuple(valid_bnames)
return MjwarpSceneContext(
source_model_file=source_model_file,
diagnostic_model_file=str(scene.model_file),
cleanup_handle=_TemporarySceneCleanup(*temp_paths) if temp_paths else None,
tracked_body_names=tracked_body_names,
)