diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c654214e7..625f0e1f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -215,6 +215,12 @@ jobs: with: path: src/unilab/assets/robots key: ${{ runner.os }}-robot-assets-v1-${{ hashFiles('src/unilab/assets/hub.py') }} + - name: Keep git-tracked assets at the checked-out commit + # The robots cache persists HF-downloaded meshes across runs, but it + # also snapshots git-tracked scene XMLs and would silently roll them + # back to the cached revision. Restore tracked files from the index; + # untracked downloads stay untouched. + run: git restore --worktree -- src/unilab/assets/robots - name: Pull robot assets run: uv run --no-sync unilab-pull-assets --robot all - name: Test with coverage diff --git a/docs/sphinx/source/en/5-reference/5-support_matrix.md b/docs/sphinx/source/en/5-reference/5-support_matrix.md index a9307537e..8fe789240 100644 --- a/docs/sphinx/source/en/5-reference/5-support_matrix.md +++ b/docs/sphinx/source/en/5-reference/5-support_matrix.md @@ -24,7 +24,9 @@ in English. Do not infer support beyond the evidence grade shown below. - `mujoco`: `--render-mode auto` exports `play_video.mp4`; `--render-mode viser` serves the rollout in a browser-based viser viewer. - `motrix`: `--render-mode auto` opens an interactive renderer window; it does - not record a video and is not bound by `play_steps`. + not record a video and is not bound by `play_steps`. `--render-mode viser` + routes to the browser-based viser viewer with per-env MuJoCo playback + models driven by physics-state snapshots. - `mjwarp`: supports explicit, finite-step `record` by default, rendered offline through the task owner's MuJoCo visual model; `--render-mode interactive` routes to the MuJoCo interactive viewer (mjwarp runs the physics while diff --git a/docs/sphinx/source/zh_CN/5-reference/5-support_matrix.md b/docs/sphinx/source/zh_CN/5-reference/5-support_matrix.md index ffd1018ad..a00cc9ac8 100644 --- a/docs/sphinx/source/zh_CN/5-reference/5-support_matrix.md +++ b/docs/sphinx/source/zh_CN/5-reference/5-support_matrix.md @@ -20,7 +20,7 @@ - `mujoco`: `--render-mode auto` 会导出 `play_video.mp4`;`--render-mode viser` 通过基于浏览器的 viser viewer 展示回放 -- `motrix`: `--render-mode auto` 会打开交互式 renderer 窗口,不录制视频,不受 `play_steps` 限制 +- `motrix`: `--render-mode auto` 会打开交互式 renderer 窗口,不录制视频,不受 `play_steps` 限制;`--render-mode viser` 路由到浏览器 viser viewer(物理快照驱动按 env 的 MuJoCo playback model) - `mjwarp`: 默认仅支持显式、有限步数的 `record`,通过 task owner 的 MuJoCo visual model 离线录制;`--render-mode interactive` 路由到 MuJoCo 交互 viewer(mjwarp 跑物理、MuJoCo 渲染 env[0],强制单 env);`--render-mode viser` 路由到浏览器 viser viewer(按 env 使用 MuJoCo playback model);不支持 `auto` 或 native renderer - `isaacsim`: `auto` 在有 display 时选择 Kit viewer,否则选择 headless RGB camera;当前真实主机仍有 RTX renderer 初始化 blocker,支持等级保持 `Configured` - `--render-mode record`: MuJoCo、mjwarp、Motrix 和 IsaacSim 都只录制视频 diff --git a/pyproject.rocm.toml b/pyproject.rocm.toml index 0128cabcb..9eaffbf0f 100644 --- a/pyproject.rocm.toml +++ b/pyproject.rocm.toml @@ -23,7 +23,7 @@ classifiers = [ requires-python = ">=3.10,<3.14" dependencies = [ "numpy", - "unisim-core>=1.7.3", + "unisim-core>=1.7.8", "torch==2.11.0", "triton-rocm==3.6.0 ; sys_platform == 'linux' and platform_machine == 'x86_64'", "gymnasium", @@ -69,7 +69,7 @@ mujoco = [ "mujoco~=3.11.0", "mjbatch-uni~=0.2.1", ] -motrix = ["unisim-core[motrix]>=1.7.3"] +motrix = ["unisim-core[motrix]>=1.7.8"] uni_rl = ["unilab-rl==1.3.2"] [dependency-groups] diff --git a/pyproject.toml b/pyproject.toml index f05644878..45e59e5ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ classifiers = [ requires-python = ">=3.10,<3.14" dependencies = [ "numpy", - "unisim-core>=1.7.5", + "unisim-core>=1.7.8", "numba>=0.67", "prettytable>=3.10", # A range (not an exact pin) lets ROCm users substitute a ROCm torch @@ -108,13 +108,13 @@ newton = [ "pyglet>=2.1.6,<3", "imgui-bundle>=1.92.0", ] -motrix = ["unisim-core[motrix]>=1.7.5"] +motrix = ["unisim-core[motrix]>=1.7.8"] genesis = ["genesis-world==1.3.3"] uni_rl = ["unilab-rl==1.3.2"] # Wheels are CPython 3.12/3.13 Linux x86_64 only; the marker keeps uv lock # resolvable for the other required-environments. superdex = [ - "unisim-core[superdex]>=1.7.5 ; python_version >= '3.12' and sys_platform == 'linux' and platform_machine == 'x86_64'", + "unisim-core[superdex]>=1.7.8 ; python_version >= '3.12' and sys_platform == 'linux' and platform_machine == 'x86_64'", ] [dependency-groups] diff --git a/src/unilab/assets/robots/stewart/scene.xml b/src/unilab/assets/robots/stewart/scene.xml index a3a8b7f41..9e5b1e699 100644 --- a/src/unilab/assets/robots/stewart/scene.xml +++ b/src/unilab/assets/robots/stewart/scene.xml @@ -62,8 +62,8 @@ - - + + @@ -82,8 +82,8 @@ - - + + @@ -102,8 +102,8 @@ - - + + @@ -122,8 +122,8 @@ - - + + @@ -142,8 +142,8 @@ - - + + @@ -162,8 +162,8 @@ - - + + diff --git a/src/unilab/cli.py b/src/unilab/cli.py index a7d156e53..abf9b1ec3 100644 --- a/src/unilab/cli.py +++ b/src/unilab/cli.py @@ -40,7 +40,7 @@ # playback contract the viser viewer renders from (they expose native # renderers instead). Supporting viser there requires upstream unisim # capability work, so these sims fail closed with an actionable message. -VISER_UNSUPPORTED_SIMS = frozenset({"motrix", "genesis", "isaacgym", "isaacsim"}) +VISER_UNSUPPORTED_SIMS = frozenset({"genesis", "isaacgym", "isaacsim"}) RESERVED_OVERRIDE_KEYS = { "algo", "task", @@ -229,7 +229,11 @@ def _needs_motrix_renderer(mode: str, sim: str, overrides: Sequence[str]) -> boo if sim != "motrix": return False play_render_mode = _override_value(overrides, "training.play_render_mode") - if play_render_mode is not None and play_render_mode.strip().lower() in {"none", "record"}: + if play_render_mode is not None and play_render_mode.strip().lower() in { + "none", + "record", + "viser", + }: return False if mode == "eval": return True diff --git a/tests/base/test_backend_pre_step_control.py b/tests/base/test_backend_pre_step_control.py index c53ea20bb..e7637ca70 100644 --- a/tests/base/test_backend_pre_step_control.py +++ b/tests/base/test_backend_pre_step_control.py @@ -299,6 +299,8 @@ def _fake_motrix_backend(pre_step_control_fn=None): actuator_ctrls=np.zeros((1, 2), dtype=np.float32), sensor_value=0.0, ) + backend._sim_dt = 0.002 + backend._time_view = np.zeros((1,), dtype=np.float32) backend._refresh_link_pose_cache = lambda: None return backend diff --git a/tests/base/test_motrix_backend_options.py b/tests/base/test_motrix_backend_options.py index 61111aafa..934e7db06 100644 --- a/tests/base/test_motrix_backend_options.py +++ b/tests/base/test_motrix_backend_options.py @@ -247,6 +247,16 @@ def _install_fake_motrix(monkeypatch, tmp_path): "_materialize_motrix_scene_with_sensor_names", lambda **kwargs: (fake_model, ()), ) + # These tests exercise override caches and terrain plumbing with a fake + # native model; unisim-core 1.7.6 validates the whole-MJCF joint order + # against the real source file at construction (covered upstream), which + # has no meaning for the fake model and only reads "source.xml" from disk. + monkeypatch.setattr( + mod.MotrixBackend, + "_validate_non_portable_joint_order", + lambda self, model_file: None, + raising=False, + ) return mod, fake_model diff --git a/tests/test_cli.py b/tests/test_cli.py index b87389149..473b7c5aa 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -785,18 +785,74 @@ def test_macos_mujoco_viser_eval_uses_current_python( assert command[0] == sys.executable -def test_eval_viser_render_mode_rejected_for_motrix( +def test_eval_viser_render_mode_routes_to_play_viser_for_motrix( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: _make_minimal_checkout(tmp_path) + (tmp_path / "scripts" / "play_viser.py").write_text("", encoding="utf-8") _pretend_motrix_is_installed(monkeypatch) + # The viser viewer renders MuJoCo playback models, so the CLI also + # requires the mujoco package regardless of the physics backend. + monkeypatch.setattr( + cli, + "find_spec", + lambda name: ModuleSpec(name, loader=None) if name in {"motrixsim", "mujoco"} else None, + ) + + command = cli.build_command( + mode="eval", + algo="ppo", + task="go2_joystick_flat", + sim="motrix", + overrides=[], + load_run="-1", + render_mode="viser", + root=tmp_path, + ) + + assert command[1] == str(tmp_path / "scripts" / "play_viser.py") + assert "--sim" in command and "motrix" in command + + +def test_train_viser_render_mode_routes_to_train_script_for_motrix( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _make_minimal_checkout(tmp_path) + _pretend_motrix_is_installed(monkeypatch) + monkeypatch.setattr( + cli, + "find_spec", + lambda name: ModuleSpec(name, loader=None) if name in {"motrixsim", "mujoco"} else None, + ) + + command = cli.build_command( + mode="train", + algo="ppo", + task="go2_joystick_flat", + sim="motrix", + overrides=[], + render_mode="viser", + root=tmp_path, + ) + + assert command[1] == str(tmp_path / "scripts" / "train_rsl_rl.py") + assert "training.play_render_mode=viser" in command + + +def test_eval_viser_render_mode_rejected_for_genesis( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + owner_dir = tmp_path / "conf" / "ppo" / "task" / "go2_joystick_flat" + owner_dir.mkdir(parents=True) + (owner_dir / "genesis.yaml").write_text("training:\n sim_backend: genesis\n", encoding="utf-8") + _pretend_genesis_runtime(monkeypatch, available=True) with pytest.raises(SystemExit, match="viser"): cli.build_command( mode="eval", algo="ppo", task="go2_joystick_flat", - sim="motrix", + sim="genesis", overrides=[], load_run="-1", render_mode="viser", @@ -804,18 +860,20 @@ def test_eval_viser_render_mode_rejected_for_motrix( ) -def test_train_viser_render_mode_rejected_for_motrix( +def test_train_viser_render_mode_rejected_for_genesis( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: - _make_minimal_checkout(tmp_path) - _pretend_motrix_is_installed(monkeypatch) + owner_dir = tmp_path / "conf" / "ppo" / "task" / "go2_joystick_flat" + owner_dir.mkdir(parents=True) + (owner_dir / "genesis.yaml").write_text("training:\n sim_backend: genesis\n", encoding="utf-8") + _pretend_genesis_runtime(monkeypatch, available=True) with pytest.raises(SystemExit, match="viser"): cli.build_command( mode="train", algo="ppo", task="go2_joystick_flat", - sim="motrix", + sim="genesis", overrides=[], render_mode="viser", root=tmp_path, diff --git a/uv.lock b/uv.lock index abee3ce21..276a7ed37 100644 --- a/uv.lock +++ b/uv.lock @@ -5238,9 +5238,9 @@ requires-dist = [ { name = "trimesh", specifier = ">=3.21.7" }, { name = "typing-extensions" }, { name = "unilab-rl", marker = "extra == 'uni-rl'", specifier = "==1.3.2" }, - { name = "unisim-core", specifier = ">=1.7.5" }, - { name = "unisim-core", extras = ["motrix"], marker = "extra == 'motrix'", specifier = ">=1.7.5" }, - { name = "unisim-core", extras = ["superdex"], marker = "python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'superdex'", specifier = ">=1.7.5" }, + { name = "unisim-core", specifier = ">=1.7.8" }, + { name = "unisim-core", extras = ["motrix"], marker = "extra == 'motrix'", specifier = ">=1.7.8" }, + { name = "unisim-core", extras = ["superdex"], marker = "python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'superdex'", specifier = ">=1.7.8" }, { name = "viser", specifier = ">=1.0.26" }, { name = "wandb" }, { name = "warp-lang", marker = "extra == 'mjwarp'", specifier = "==1.16.0" }, @@ -5284,13 +5284,13 @@ wheels = [ [[package]] name = "unisim-core" -version = "1.7.5" +version = "1.7.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ea/2c/8255a60c0260a4dc3b41aa60d9910b417a9ae11a1ef3de2b682252680390/unisim_core-1.7.5.tar.gz", hash = "sha256:7613702bf757a6253691bbcd5ab7ae38bf2ce2b8466cf5e6b0947c59466fbc11", size = 526190, upload-time = "2026-09-23T12:31:03.655Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/7f/9b13d12cf57250b704b9e43f0caba5646db0479124aa880393cff1c54273/unisim_core-1.7.8.tar.gz", hash = "sha256:d550e7052c85762778aa7e365fac748099b8b749a12fc6216947fd4ba98a3add", size = 532853, upload-time = "2026-09-24T10:02:44.662Z" } [package.optional-dependencies] motrix = [