openral-sim 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. openral_sim/__init__.py +67 -0
  2. openral_sim/_assets.py +322 -0
  3. openral_sim/_deps.py +1943 -0
  4. openral_sim/_quantization.py +819 -0
  5. openral_sim/_sidecar_common.py +295 -0
  6. openral_sim/_video.py +334 -0
  7. openral_sim/_website_video.py +221 -0
  8. openral_sim/backends/__init__.py +64 -0
  9. openral_sim/backends/aloha.py +215 -0
  10. openral_sim/backends/depth_camera.py +320 -0
  11. openral_sim/backends/isaac_sim.py +669 -0
  12. openral_sim/backends/libero.py +434 -0
  13. openral_sim/backends/maniskill3.py +496 -0
  14. openral_sim/backends/metaworld.py +163 -0
  15. openral_sim/backends/openarm_robosuite/__init__.py +28 -0
  16. openral_sim/backends/openarm_robosuite/_assets.py +554 -0
  17. openral_sim/backends/openarm_robosuite/env.py +907 -0
  18. openral_sim/backends/pusht.py +182 -0
  19. openral_sim/backends/rlbench.py +347 -0
  20. openral_sim/backends/robocasa.py +1579 -0
  21. openral_sim/backends/robotwin.py +522 -0
  22. openral_sim/backends/simpler_env.py +453 -0
  23. openral_sim/backends/so100_robosuite/__init__.py +37 -0
  24. openral_sim/backends/so100_robosuite/_assets.py +620 -0
  25. openral_sim/backends/so100_robosuite/env.py +299 -0
  26. openral_sim/backends/so100_robosuite/model.py +193 -0
  27. openral_sim/backends/so100_robosuite/policy.py +188 -0
  28. openral_sim/backends/so101_box/__init__.py +21 -0
  29. openral_sim/backends/so101_box/_assets.py +603 -0
  30. openral_sim/backends/so101_box/env.py +633 -0
  31. openral_sim/backends/tabletop_push/__init__.py +13 -0
  32. openral_sim/backends/tabletop_push/_assets.py +420 -0
  33. openral_sim/backends/tabletop_push/env.py +553 -0
  34. openral_sim/backends/vlabench.py +206 -0
  35. openral_sim/benchmark.py +841 -0
  36. openral_sim/cli.py +876 -0
  37. openral_sim/factory.py +77 -0
  38. openral_sim/policies/__init__.py +34 -0
  39. openral_sim/policies/_policy_loading.py +132 -0
  40. openral_sim/policies/_processors.py +69 -0
  41. openral_sim/policies/_video_capture.py +119 -0
  42. openral_sim/policies/act.py +596 -0
  43. openral_sim/policies/diffusion.py +241 -0
  44. openral_sim/policies/gr00t.py +481 -0
  45. openral_sim/policies/mock.py +216 -0
  46. openral_sim/policies/molmoact2.py +762 -0
  47. openral_sim/policies/openvla.py +847 -0
  48. openral_sim/policies/pi05.py +951 -0
  49. openral_sim/policies/rlbench_3dda.py +235 -0
  50. openral_sim/policies/rldx.py +1945 -0
  51. openral_sim/policies/robots.py +125 -0
  52. openral_sim/policies/smolvla.py +638 -0
  53. openral_sim/policies/xvla.py +266 -0
  54. openral_sim/policy.py +67 -0
  55. openral_sim/policy_deps.py +249 -0
  56. openral_sim/py.typed +0 -0
  57. openral_sim/registry.py +135 -0
  58. openral_sim/rollout.py +231 -0
  59. openral_sim/sidecar.py +448 -0
  60. openral_sim/sim_runner.py +1335 -0
  61. openral_sim-0.1.0.dist-info/METADATA +33 -0
  62. openral_sim-0.1.0.dist-info/RECORD +63 -0
  63. openral_sim-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,67 @@
1
+ r"""openral sim runner — swappable (robot x scene x task x VLA) for rSkill validation.
2
+
3
+ Typical usage::
4
+
5
+ from openral_sim import SimRunner
6
+
7
+ # ``SimRunner`` consumes the runtime-composed ``SimEnvironment``
8
+ # (scene + task + VLA). The on-disk YAML is a ``SimScene`` (scene +
9
+ # task, no VLA); the CLI composes the ``SimEnvironment`` from a
10
+ # ``SimScene`` + an rSkill manifest. See ``openral sim run`` and
11
+ # :func:`openral_sim.cli._load_or_build_env` for the canonical
12
+ # compose path.
13
+ env_cfg: SimEnvironment = ... # composed by the CLI
14
+ runner = SimRunner(env_cfg)
15
+ runner.activate()
16
+ runner.run(max_ticks=env_cfg.n_episodes * ((env_cfg.task.max_steps or 1000) + 1))
17
+ for episode in runner.episode_results:
18
+ print(episode.success, episode.steps, episode.mean_step_latency_ms)
19
+ runner.deactivate()
20
+
21
+ CLI::
22
+
23
+ openral sim run --config scenes/benchmark/libero_spatial.yaml \
24
+ --rskill smolvla-libero
25
+ openral sim run --robot franka_panda --scene libero_spatial \
26
+ --task libero_spatial/0 \
27
+ --rskill smolvla-libero
28
+ openral benchmark run --suite libero_spatial \
29
+ --rskill smolvla-libero
30
+
31
+ The sim package itself depends only on ``openral-core``,
32
+ ``openral-runner``, and ``openral-rskill``. Physics backends
33
+ (LIBERO, MetaWorld, ...) are imported lazily by the registered adapters
34
+ so installing this package never pulls heavyweight ML deps.
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ # Trigger built-in policy + backend registration (LIBERO, MetaWorld, mock,
40
+ # smolvla, …). Importing the two subpackages runs their `_register_*`
41
+ # side effects.
42
+ from openral_sim._video import save_episode_mp4
43
+ from openral_sim.backends import _register_backends as _register_backends
44
+ from openral_sim.benchmark import default_output_path, run_benchmark
45
+ from openral_sim.factory import make_env, make_policy
46
+ from openral_sim.policies import _register_policies as _register_policies
47
+ from openral_sim.policy import PolicyAdapter
48
+ from openral_sim.registry import POLICIES, ROBOTS, SCENES
49
+ from openral_sim.rollout import EpisodeResult, SimRollout
50
+ from openral_sim.sim_runner import SimRunner
51
+
52
+ __all__ = [
53
+ "POLICIES",
54
+ "ROBOTS",
55
+ "SCENES",
56
+ "EpisodeResult",
57
+ "PolicyAdapter",
58
+ "SimRollout",
59
+ "SimRunner",
60
+ "default_output_path",
61
+ "make_env",
62
+ "make_policy",
63
+ "run_benchmark",
64
+ "save_episode_mp4",
65
+ ]
66
+
67
+ __version__ = "0.1.0"
openral_sim/_assets.py ADDED
@@ -0,0 +1,322 @@
1
+ """Lazy asset-fetch helpers for sim backends with large CC-BY downloads.
2
+
3
+ Today the only consumer is the RoboCasa adapter; the module
4
+ is named generically so a future MuJoCo backend that ships its own
5
+ ~GB asset bundle can reuse the same readiness-sentinel + license-banner
6
+ pattern without re-inventing it.
7
+
8
+ Conventions
9
+ -----------
10
+ - Asset caches live under ``$OPENRAL_CACHE_HOME`` (defaults to
11
+ ``~/.cache/openral/``), mirroring the rSkill cache convention in
12
+ ``openral_rskill.loader``.
13
+ - Each backend gets its own subdirectory (``<cache_home>/robocasa/``).
14
+ - A readiness sentinel file (``.openral-ready``) marks "assets
15
+ fully unpacked"; the helper short-circuits on subsequent runs.
16
+ - A Rich license banner surfaces the upstream license + URL + target
17
+ path + size *before* asking the user to confirm.
18
+ - An env-var bypass (``OPENRAL_ALLOW_ROBOCASA_ASSETS=1`` for
19
+ RoboCasa) skips the prompt for CI.
20
+
21
+ Refusal raises :class:`ROSConfigError` with the manual-fetch command so
22
+ users can authorise the download out-of-band.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import os
28
+ import subprocess
29
+ import sys
30
+ from pathlib import Path
31
+
32
+ import typer
33
+ from openral_core.exceptions import ROSConfigError
34
+ from rich.console import Console
35
+ from rich.panel import Panel
36
+
37
+ # ── Cache layout ──────────────────────────────────────────────────────────────
38
+
39
+
40
+ _DEFAULT_CACHE_HOME = Path.home() / ".cache" / "openral"
41
+ _READY_SENTINEL = ".openral-ready"
42
+
43
+ _ROBOCASA_ALLOW_ENV = "OPENRAL_ALLOW_ROBOCASA_ASSETS"
44
+ _ROBOCASA_ASSETS_SIZE_GB = 11 # upstream README says ~10-11 GB depending on version
45
+
46
+
47
+ def _cache_home() -> Path:
48
+ """Resolve the openral cache root from env or its default."""
49
+ raw = os.environ.get("OPENRAL_CACHE_HOME")
50
+ return Path(raw) if raw else _DEFAULT_CACHE_HOME
51
+
52
+
53
+ def _display_robocasa_license_banner(target: Path) -> None:
54
+ """Print a Rich license panel before the typer.confirm() prompt.
55
+
56
+ Separate function so unit tests can monkeypatch the prompt without
57
+ suppressing the user-visible banner (the banner is a UX
58
+ requirement, not implementation detail).
59
+ """
60
+ Console().print(
61
+ Panel.fit(
62
+ (
63
+ "[bold]RoboCasa kitchen assets[/bold]\n"
64
+ "License: [bold]CC-BY-4.0[/bold] "
65
+ "(https://creativecommons.org/licenses/by/4.0/)\n"
66
+ f"Target: {target}\n"
67
+ f"Approx. ~{_ROBOCASA_ASSETS_SIZE_GB} GB on disk\n"
68
+ "\n"
69
+ "By continuing you accept the upstream RoboCasa CC-BY-4.0\n"
70
+ "license for the kitchen asset pack. Derivative artefacts\n"
71
+ "(videos, traces) must carry attribution. To skip this\n"
72
+ f"prompt in CI export {_ROBOCASA_ALLOW_ENV}=1."
73
+ ),
74
+ title="RoboCasa asset download (first-use)",
75
+ border_style="yellow",
76
+ )
77
+ )
78
+
79
+
80
+ def _detect_robocasa_variant() -> str:
81
+ """Return ``"kitchen"`` or ``"gr1_tabletop"`` based on the installed robocasa.
82
+
83
+ The two python packages share the name ``robocasa`` so a host
84
+ installs ONE or the OTHER. We pick the variant by which download
85
+ script the package ships:
86
+
87
+ * Kitchen (github.com/robocasa/robocasa) — ``download_kitchen_assets``.
88
+ * GR1 tabletop fork (github.com/robocasa/robocasa-gr1-tabletop-tasks)
89
+ — ``download_tabletop_assets`` (plus the kitchen one as a sibling
90
+ since the fork is a soft fork of robocasa).
91
+
92
+ Detection is purely filesystem-based so we don't trigger robocasa's
93
+ own import-time version assertions. If neither script is present
94
+ we default to ``"kitchen"`` -- the historical behaviour -- and the
95
+ subsequent subprocess call will surface a typed ImportError.
96
+ """
97
+ try:
98
+ import robocasa # type: ignore[import-not-found,import-untyped,unused-ignore]
99
+ except ImportError:
100
+ return "kitchen"
101
+ scripts = Path(robocasa.__file__).parent / "scripts"
102
+ if (scripts / "download_tabletop_assets.py").is_file():
103
+ return "gr1_tabletop"
104
+ return "kitchen"
105
+
106
+
107
+ def ensure_robocasa_assets() -> Path: # noqa: PLR0915 # reason: orchestrates two variants (kitchen + gr1) each with a short-circuit + a download subprocess path; splitting hurts readability more than the length does
108
+ """Make sure the RoboCasa assets are on disk; download if needed.
109
+
110
+ Handles both robocasa variants -- the upstream kitchen package and
111
+ the GR1 tabletop fork -- by detecting which is installed and
112
+ invoking the matching download script. The version asserts are
113
+ relaxed in the editable clone at provision time
114
+ (``_deps._relax_robocasa_version_asserts_step``), so no version
115
+ spoof is needed here.
116
+
117
+ The first call triggers a Rich license banner + ``typer.confirm()``
118
+ prompt unless ``OPENRAL_ALLOW_ROBOCASA_ASSETS=1`` is set. On
119
+ confirm we run the variant's download script and touch the
120
+ readiness sentinel. Subsequent calls are silent.
121
+
122
+ Returns:
123
+ Path to the ``<cache_home>/robocasa/`` directory.
124
+
125
+ Raises:
126
+ ROSConfigError: When the user refuses the prompt OR when the
127
+ upstream downloader fails (with the exact subprocess
128
+ stderr embedded so the user can debug).
129
+ """
130
+ # Variant-scoped sentinel. A previous shared `<cache>/robocasa/.openral-ready`
131
+ # was incorrect: the kitchen and GR1 variants ship different `models/assets/`
132
+ # trees and live in different `import robocasa` paths, so a sentinel
133
+ # touched by the GR1 short-circuit (line ~152) silently masked a
134
+ # missing kitchen asset bundle on the next swap to the kitchen
135
+ # backend — and vice versa. Per-variant cache dirs decouple them.
136
+ variant = _detect_robocasa_variant()
137
+ target = _cache_home() / f"robocasa_{variant}"
138
+ sentinel = target / _READY_SENTINEL
139
+ if sentinel.is_file():
140
+ return target
141
+
142
+ # GR1 fork: the user runs the upstream
143
+ # `python robocasa/scripts/download_tabletop_assets.py -y` once at
144
+ # install time per the fork's README (because that script does a
145
+ # sibling import `from download_groot_assets import …` that only
146
+ # resolves when run from inside the scripts/ directory -- it does
147
+ # NOT survive `python -m` invocation). Detect already-downloaded
148
+ # assets in the editable install's models/assets/objects/ and
149
+ # short-circuit with a sentinel touch so subsequent runs are silent.
150
+ if variant == "gr1_tabletop":
151
+ try:
152
+ import robocasa # type: ignore[import-not-found,import-untyped,unused-ignore]
153
+
154
+ assert robocasa.__file__ is not None
155
+ robocasa_dir = Path(robocasa.__file__).parent
156
+ objects_dir = robocasa_dir / "models" / "assets" / "objects"
157
+ required_registries = ("objaverse", "sketchfab", "lightwheel")
158
+ if objects_dir.is_dir() and all(
159
+ (objects_dir / registry).is_dir() for registry in required_registries
160
+ ):
161
+ target.mkdir(parents=True, exist_ok=True)
162
+ sentinel.touch()
163
+ return target
164
+ except ImportError:
165
+ # fall through; the deps path will reinstall robocasa and
166
+ # this function gets called again on the next env build.
167
+ raise ROSConfigError(
168
+ "RoboCasa GR1 tabletop adapter requires the editable "
169
+ "fork clone; openral_sim._deps.ensure_backend_deps "
170
+ "('robocasa_gr1') installs it. Re-run the same command "
171
+ "and accept the deps-install prompt."
172
+ ) from None
173
+
174
+ # Drive the upstream downloader as a subprocess with the
175
+ # script's parent dir as cwd (the upstream script does a
176
+ # sibling import `from download_groot_assets import …` that
177
+ # only resolves when invoked from inside scripts/ -- it does
178
+ # NOT survive `python -m`). Bypass the prompt with the same
179
+ # env-var that gates the kitchen path.
180
+ bypass_gr1 = (
181
+ os.environ.get(_ROBOCASA_ALLOW_ENV) == "1"
182
+ or os.environ.get("OPENRAL_AUTO_INSTALL_DEPS") == "1"
183
+ )
184
+ if not bypass_gr1:
185
+ _display_robocasa_license_banner(target)
186
+ if not typer.confirm("Download RoboCasa GR1 tabletop assets now?", default=False):
187
+ raise ROSConfigError(
188
+ "RoboCasa GR1 tabletop assets not downloaded. Either "
189
+ f"set {_ROBOCASA_ALLOW_ENV}=1 (or "
190
+ "OPENRAL_AUTO_INSTALL_DEPS=1) and re-run, or fetch "
191
+ "them manually:\n"
192
+ f" cd {robocasa_dir.parent} && uv run python "
193
+ "robocasa/scripts/download_tabletop_assets.py -y"
194
+ )
195
+
196
+ scripts_dir = robocasa_dir / "scripts"
197
+ try:
198
+ subprocess.run(
199
+ [sys.executable, "download_tabletop_assets.py", "-y"],
200
+ cwd=str(scripts_dir),
201
+ check=True,
202
+ )
203
+ except (subprocess.CalledProcessError, FileNotFoundError) as exc:
204
+ raise ROSConfigError(
205
+ f"RoboCasa GR1 asset download failed: {exc}. Re-run "
206
+ f"manually:\n cd {robocasa_dir.parent} && uv run python "
207
+ "robocasa/scripts/download_tabletop_assets.py -y"
208
+ ) from exc
209
+
210
+ target.mkdir(parents=True, exist_ok=True)
211
+ sentinel.touch()
212
+ return target
213
+
214
+ # Kitchen variant: short-circuit ONLY when every downloaded bundle is
215
+ # on disk. The editable install path (`_deps._robocasa_kitchen_plan`
216
+ # → clone + `uv pip install -e`) ships the static skeleton
217
+ # (``arenas/empty_kitchen_arena.xml``, ``box_links/...json``) from the
218
+ # cloned source, but the heavyweight content (~11 GB) ships
219
+ # separately via ``robocasa.scripts.download_kitchen_assets``'s Box
220
+ # URLs and lands under the dirs enumerated in that script's
221
+ # ``DOWNLOAD_ASSET_REGISTRY``. Per-task object sampling in
222
+ # ``kitchen_object_utils.sample_kitchen_object`` reads the default
223
+ # ``obj_registries=("objaverse", "lightwheel")``, and the AI-gen
224
+ # category set is also referenced by many tasks — so a partial
225
+ # install (e.g. only ``objects/lightwheel/``) reads "ready" but later
226
+ # divides by zero candidates in the sampler and surfaces as
227
+ # ``ValueError: probabilities contain NaN`` at first ``env.reset()``.
228
+ # Require every non-source download target to be non-empty.
229
+ try:
230
+ import robocasa # type: ignore[import-not-found,import-untyped,unused-ignore]
231
+
232
+ assert robocasa.__file__ is not None
233
+ robocasa_dir = Path(robocasa.__file__).parent
234
+ kitchen_arena = robocasa_dir / "models" / "assets" / "arenas" / "empty_kitchen_arena.xml"
235
+ box_links_assets = (
236
+ robocasa_dir / "models" / "assets" / "box_links" / "box_links_assets.json"
237
+ )
238
+ # Downloaded bundles, matching DOWNLOAD_ASSET_REGISTRY keys in
239
+ # robocasa.scripts.download_kitchen_assets. Each must be a
240
+ # non-empty directory.
241
+ assets_root = robocasa_dir / "models" / "assets"
242
+ downloaded_bundles = (
243
+ assets_root / "textures",
244
+ assets_root / "generative_textures",
245
+ assets_root / "fixtures",
246
+ assets_root / "objects" / "objaverse",
247
+ assets_root / "objects" / "aigen_objs",
248
+ assets_root / "objects" / "lightwheel",
249
+ )
250
+
251
+ def _populated(p: Path) -> bool:
252
+ return p.is_dir() and any(p.iterdir())
253
+
254
+ if (
255
+ kitchen_arena.is_file()
256
+ and box_links_assets.is_file()
257
+ and all(_populated(p) for p in downloaded_bundles)
258
+ ):
259
+ target.mkdir(parents=True, exist_ok=True)
260
+ sentinel.touch()
261
+ return target
262
+ except ImportError:
263
+ pass
264
+
265
+ # ``OPENRAL_AUTO_INSTALL_DEPS=1`` (used by ``openral deploy sim``
266
+ # for CI-style runs) implies acceptance of the RoboCasa asset
267
+ # license + download. The license banner still prints to stderr
268
+ # so the operator has a record of what was downloaded.
269
+ bypass = (
270
+ os.environ.get(_ROBOCASA_ALLOW_ENV) == "1"
271
+ or os.environ.get("OPENRAL_AUTO_INSTALL_DEPS") == "1"
272
+ )
273
+ if not bypass:
274
+ _display_robocasa_license_banner(target)
275
+ if not typer.confirm("Download RoboCasa assets now?", default=False):
276
+ script = (
277
+ "robocasa.scripts.download_tabletop_assets"
278
+ if variant == "gr1_tabletop"
279
+ else "robocasa.scripts.download_kitchen_assets"
280
+ )
281
+ raise ROSConfigError(
282
+ "RoboCasa assets not downloaded. Either set "
283
+ f"{_ROBOCASA_ALLOW_ENV}=1 (or OPENRAL_AUTO_INSTALL_DEPS=1) "
284
+ f"and re-run, or fetch them manually with: "
285
+ f"`uv run python -m {script}`"
286
+ )
287
+
288
+ target.mkdir(parents=True, exist_ok=True)
289
+ # The upstream download script prompts on stdin when called
290
+ # directly, so we drive it as a subprocess with stdin confirmation
291
+ # pre-baked. The script writes assets to its site-package-internal
292
+ # `models/assets/` directory (not `target`); we touch the sentinel
293
+ # under `target` afterwards as a readiness marker.
294
+ #
295
+ # robocasa's download scripts ``import robocasa``, which used to hard-assert
296
+ # exact mujoco/numpy/robosuite micro versions at import. The install plan now
297
+ # relaxes those asserts in the editable clone at provision time
298
+ # (``_deps._relax_robocasa_version_asserts_step``), so the download runs on
299
+ # the workspace's real versions with NO version spoof -- a plain ``runpy``
300
+ # shim suffices. Only the procedural ``download_*_assets`` target differs by
301
+ # variant (kitchen vs the GR1 tabletop fork).
302
+ if variant == "gr1_tabletop":
303
+ script_hint = "robocasa.scripts.download_tabletop_assets"
304
+ else:
305
+ script_hint = "robocasa.scripts.download_kitchen_assets"
306
+ shim = f'import runpy; runpy.run_module("{script_hint}", run_name="__main__")'
307
+
308
+ try:
309
+ subprocess.run(
310
+ [sys.executable, "-c", shim],
311
+ input="y\n",
312
+ text=True,
313
+ check=True,
314
+ )
315
+ except (subprocess.CalledProcessError, FileNotFoundError) as exc:
316
+ raise ROSConfigError(
317
+ f"RoboCasa asset download failed: {exc}. Run "
318
+ f"`uv run python -m {script_hint}` manually and re-run the sim."
319
+ ) from exc
320
+
321
+ sentinel.touch()
322
+ return target