Instructions to use lukasskellijs/env_assembly_bench with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LeRobot
How to use lukasskellijs/env_assembly_bench with LeRobot:
- Notebooks
- Google Colab
- Kaggle
| """Assembly bench EnvHub entry — LeRobot ``make_env`` for Isaac Lab Arena. | |
| Docs: https://huggingface.co/docs/lerobot/en/envhub_isaaclab_arena | |
| Variant selection (Arena ``--task``) is *not* ``cfg.task`` — that field is the | |
| natural-language prompt on ``IsaaclabArenaEnv``. Pass the variant via kwargs: | |
| --env.kwargs='{"variant": "peg_round_M1_loose", "reward": "none"}' | |
| """ | |
| from __future__ import annotations | |
| import importlib.util | |
| import logging | |
| import os | |
| import shutil | |
| import sys | |
| from pathlib import Path | |
| from typing import Any | |
| import gymnasium as gym | |
| import yaml | |
| from huggingface_hub import hf_hub_download, snapshot_download | |
| # Hub id for sibling downloads + full-repo snapshot (assets + package). | |
| HUB_REPO_ID = os.environ.get("ASSEMBLY_BENCH_HUB", "lukasskellijs/env_assembly_bench") | |
| EXAMPLE_ENVS = "example_envs.yaml" | |
| # Default proprio / cameras match assembly_bench/contract.json (DROID 8-D). | |
| _DEFAULT_STATE_KEYS = "joint_pos,gripper_pos,eef_pos,eef_quat" | |
| _DEFAULT_CAMERA_KEYS = "front_cam_rgb,wrist_camera_rgb" | |
| _DEFAULT_STATE_DIM = 15 # 7+1+3+4 | |
| _DEFAULT_ACTION_DIM = 8 | |
| _DEFAULT_VARIANT = "peg_round_M1_loose" | |
| _DEFAULT_EMBODIMENT = "droid_abs_joint_pos_softmimic" | |
| _MICROWAVE_PROMPT = "Reach out to the microwave and open it." # IsaaclabArenaEnv default | |
| _REPO_ROOT: str | None = None | |
| def _materialize_hub_tree(root: Path) -> Path: | |
| """Copy a HF snapshot to a real directory (dereference blob symlinks). | |
| Hub stores files as symlinks into ``blobs/<hash>`` (no ``.usd`` suffix). USD / | |
| PhysX open the resolved path and then hang or fail on the extension-less blob. | |
| ``symlinks=False`` copies content under the original names. | |
| """ | |
| sample = next((root / "assembly_bench" / "assets").rglob("*.usd"), None) | |
| if sample is None or not sample.is_symlink(): | |
| return root | |
| cache = Path( | |
| os.environ.get( | |
| "ASSEMBLY_BENCH_MATERIALIZE_DIR", | |
| Path.home() / ".cache" / "env_assembly_bench" / "materialized", | |
| ) | |
| ) | |
| dest = cache / root.name | |
| marker = dest / ".materialized" | |
| if marker.is_file(): | |
| return dest | |
| logging.info("Materializing Hub snapshot (deref blob symlinks) → %s", dest) | |
| if dest.exists(): | |
| shutil.rmtree(dest) | |
| shutil.copytree(root, dest, symlinks=False) | |
| marker.write_text("ok\n", encoding="utf-8") | |
| return dest | |
| def _ensure_repo_root() -> str: | |
| """Put this EnvHub checkout/snapshot on sys.path so ``assembly_bench`` imports.""" | |
| global _REPO_ROOT | |
| if _REPO_ROOT is not None: | |
| return _REPO_ROOT | |
| # absolute() keeps the Hub snapshot path; resolve() follows blob symlinks. | |
| here = Path(__file__).absolute().parent | |
| if (here / "assembly_bench" / "environments" / "assembly" / "assembly.py").is_file(): | |
| root = here | |
| else: | |
| # Loaded as a lone hub file — pull the full repo (USDs + package). | |
| root = Path(snapshot_download(repo_id=HUB_REPO_ID)) | |
| root = _materialize_hub_tree(root) | |
| _REPO_ROOT = str(root) | |
| # Materialized tree must win over a still-on-path HF snapshot (blob symlinks). | |
| if _REPO_ROOT in sys.path: | |
| sys.path.remove(_REPO_ROOT) | |
| sys.path.insert(0, _REPO_ROOT) | |
| for name in list(sys.modules): | |
| if name == "assembly_bench" or name.startswith("assembly_bench."): | |
| del sys.modules[name] | |
| return _REPO_ROOT | |
| def _download_hub_file(filename: str) -> str: | |
| root = _ensure_repo_root() | |
| local = Path(root) / filename | |
| if local.is_file(): | |
| return str(local) | |
| return hf_hub_download(repo_id=HUB_REPO_ID, filename=filename) | |
| def _download_and_import(filename: str): | |
| """Load a sibling module from the hub snapshot (absolute imports).""" | |
| local_path = _download_hub_file(filename) | |
| module_dir = os.path.dirname(local_path) | |
| if module_dir not in sys.path: | |
| sys.path.insert(0, module_dir) | |
| module_name = filename.replace(".py", "") | |
| with open(local_path) as f: | |
| content = f.read() | |
| content = content.replace("from .errors import", "from errors import") | |
| content = content.replace("from .isaaclab_env_wrapper import", "from isaaclab_env_wrapper import") | |
| spec = importlib.util.spec_from_file_location(module_name, local_path) | |
| module = importlib.util.module_from_spec(spec) | |
| sys.modules[module_name] = module | |
| exec(compile(content, local_path, "exec"), module.__dict__) # noqa: S102 | |
| return module | |
| try: | |
| from .errors import IsaacLabArenaCameraKeyError, IsaacLabArenaStateKeyError | |
| from .isaaclab_env_wrapper import IsaacLabEnvWrapper, cleanup_isaaclab | |
| except ImportError: | |
| _errors = _download_and_import("errors.py") | |
| _wrapper = _download_and_import("isaaclab_env_wrapper.py") | |
| IsaacLabEnvWrapper = _wrapper.IsaacLabEnvWrapper | |
| cleanup_isaaclab = _wrapper.cleanup_isaaclab | |
| IsaacLabArenaCameraKeyError = _errors.IsaacLabArenaCameraKeyError | |
| IsaacLabArenaStateKeyError = _errors.IsaacLabArenaStateKeyError | |
| def validate_config( | |
| env, | |
| state_keys: tuple[str, ...], | |
| camera_keys: tuple[str, ...], | |
| cfg_state_dim: int, | |
| cfg_action_dim: int, | |
| ) -> None: | |
| """Check observation keys and dims against the live Isaac Lab managers.""" | |
| obs_manager = env.observation_manager | |
| active_terms = obs_manager.active_terms | |
| policy_terms = set(active_terms.get("policy", [])) | |
| camera_terms = set(active_terms.get("camera_obs", [])) | |
| missing_state = [k for k in state_keys if k not in policy_terms] | |
| if missing_state: | |
| raise IsaacLabArenaStateKeyError(missing_state, policy_terms) | |
| missing_cam = [k for k in camera_keys if k not in camera_terms] | |
| if missing_cam: | |
| raise IsaacLabArenaCameraKeyError(missing_cam, camera_terms) | |
| env_action_dim = env.action_space.shape[-1] | |
| if cfg_action_dim != env_action_dim: | |
| raise ValueError(f"action_dim mismatch: config={cfg_action_dim}, env={env_action_dim}") | |
| policy_dims = obs_manager.group_obs_dim.get("policy", []) | |
| policy_names = active_terms.get("policy", []) | |
| term_dims = dict(zip(policy_names, policy_dims, strict=False)) | |
| expected_state_dim = 0 | |
| for key in state_keys: | |
| if key in term_dims: | |
| shape = term_dims[key] | |
| dim = 1 | |
| for s in shape if isinstance(shape, (tuple, list)) else [shape]: | |
| dim *= s | |
| expected_state_dim += dim | |
| if cfg_state_dim != expected_state_dim: | |
| raise ValueError( | |
| f"state_dim mismatch: config={cfg_state_dim}, " | |
| f"computed={expected_state_dim}. " | |
| f"Term dims: {term_dims}" | |
| ) | |
| logging.info(f"Validated: state_keys={state_keys}, camera_keys={camera_keys}") | |
| def resolve_environment_alias(environment: str) -> str: | |
| """Map ``--env.environment`` alias → dotted class path.""" | |
| with open(_download_hub_file(EXAMPLE_ENVS)) as f: | |
| envs_mapping = yaml.safe_load(f) | |
| return f"{envs_mapping['repo']['base_module']}.{envs_mapping['repo']['envs'][environment]}" | |
| def _cfg_get(cfg: Any, name: str, default: Any = None) -> Any: | |
| if cfg is None: | |
| return default | |
| return getattr(cfg, name, default) | |
| def _create_isaaclab_env(config: dict, n_envs: int) -> dict[str, dict[int, gym.vector.VectorEnv]]: | |
| """Boot Kit, build AssemblyBench via Arena, wrap for EnvHub.""" | |
| _ensure_repo_root() | |
| from isaaclab.app import AppLauncher | |
| if config.get("enable_pinocchio", False): | |
| import pinocchio # noqa: F401 | |
| config = {**config, "num_envs": n_envs} | |
| # Only Kit-relevant fields — extra Arena/EnvHub keys confuse nothing, but a | |
| # minimal Namespace keeps AppLauncher resolution predictable. | |
| logging.info("Launching IsaacLab simulation app...") | |
| app_launcher = AppLauncher( | |
| headless=bool(config.get("headless", True)), | |
| enable_cameras=bool(config.get("enable_cameras", True)), | |
| device=config.get("device") or "cuda:0", | |
| ) | |
| # Package imports need the hub root on path (assets resolve from scene.py). | |
| from assembly_bench.environments.assembly.assembly import make_assembly_env | |
| from assembly_bench.environments.assembly.variants import VARIANTS | |
| variant = config.get("variant") or _DEFAULT_VARIANT | |
| if variant not in VARIANTS: | |
| raise ValueError(f"Unknown variant {variant!r}. Choices: {sorted(VARIANTS)}") | |
| # Confirm the EnvHub alias maps to this package (discoverability / typos). | |
| resolve_environment_alias(config.get("environment") or "assembly_bench") | |
| # Shared factory: Arena CLI defaults + reward/hdr wiring. | |
| raw_env = make_assembly_env( | |
| task=variant, | |
| num_envs=n_envs, | |
| embodiment=config.get("embodiment") or _DEFAULT_EMBODIMENT, | |
| hdr=config.get("hdr") or "asm_machine_shop", | |
| light_intensity=float(config.get("light_intensity") or 1500.0), | |
| reward=config.get("reward") or "none", | |
| ) | |
| # Peel Gymnasium wrappers (OrderEnforcing, …) — managers live on the Isaac env. | |
| isaac_env = raw_env.unwrapped if hasattr(raw_env, "unwrapped") else raw_env | |
| # make_assembly_env already registers with render_mode="rgb_array"; Gymnasium's | |
| # OrderEnforcing wrapper exposes render_mode as a read-only property, so don't | |
| # assign on raw_env. IsaacLabEnvWrapper owns the EnvHub-facing value. | |
| render_mode = "rgb_array" if config.get("enable_cameras", False) else None | |
| state_keys = tuple(k.strip() for k in (config.get("state_keys") or "").split(",") if k.strip()) | |
| camera_keys = tuple(k.strip() for k in (config.get("camera_keys") or "").split(",") if k.strip()) | |
| try: | |
| validate_config( | |
| isaac_env, | |
| state_keys, | |
| camera_keys, | |
| config.get("state_dim", _DEFAULT_STATE_DIM), | |
| config.get("action_dim", _DEFAULT_ACTION_DIM), | |
| ) | |
| except (IsaacLabArenaCameraKeyError, IsaacLabArenaStateKeyError, ValueError): | |
| cleanup_isaaclab(raw_env, app_launcher) | |
| raise | |
| except Exception as e: | |
| logging.error(f"Validation failed with unexpected error: {type(e).__name__}: {e}") | |
| cleanup_isaaclab(raw_env, app_launcher) | |
| raise | |
| # NL prompt: cfg.task unless still the Arena microwave default → use variant text. | |
| task_prompt = config.get("task") | |
| if not task_prompt or task_prompt == _MICROWAVE_PROMPT: | |
| task_prompt = VARIANTS[variant].instruction | |
| # 15 Hz control; fall back to the variant's authored duration (seconds). | |
| episode_length = config.get("episode_length") | |
| if episode_length in (None, 300): | |
| episode_length = int(VARIANTS[variant].episode_length_s * 15) | |
| environment = config.get("environment") or "assembly_bench" | |
| wrapped_env = IsaacLabEnvWrapper( | |
| isaac_env, | |
| episode_length=int(episode_length), | |
| task=task_prompt, | |
| render_mode=render_mode, | |
| simulation_app=app_launcher, | |
| ) | |
| # Keep the Gymnasium outer wrapper alive: its __del__/close() would otherwise | |
| # tear down the Isaac env as soon as this frame returns (exit 0 mid-smoke). | |
| wrapped_env._gym_root = raw_env | |
| # OrderEnforcing.close() would close the Isaac env on GC; neutralize it. | |
| raw_env.close = lambda *a, **k: None # type: ignore[method-assign] | |
| logging.info( | |
| f"Created: {environment} variant={variant} n_envs={wrapped_env.num_envs} " | |
| f"render_mode={render_mode}" | |
| ) | |
| return {environment: {0: wrapped_env}} | |
| def make_env( | |
| n_envs: int = 1, | |
| use_async_envs: bool = False, # noqa: ARG001 — GPU batching; AsyncVectorEnv unused | |
| cfg: Any | None = None, | |
| ) -> dict[str, dict[int, gym.vector.VectorEnv]]: | |
| """EnvHub entry point. ``cfg`` is typically ``IsaaclabArenaEnv`` (or SimpleNamespace).""" | |
| if n_envs < 1: | |
| raise ValueError("`n_envs` must be at least 1") | |
| environment = _cfg_get(cfg, "environment", "assembly_bench") | |
| if environment is None: | |
| raise ValueError("No 'environment' specified (expected 'assembly_bench').") | |
| # IsaaclabArenaEnv.__post_init__ promotes kwargs (variant/reward/hdr) onto cfg. | |
| variant = _cfg_get(cfg, "variant", _DEFAULT_VARIANT) | |
| reward = _cfg_get(cfg, "reward", "none") | |
| hdr = _cfg_get(cfg, "hdr", "asm_machine_shop") | |
| light_intensity = _cfg_get(cfg, "light_intensity", 1500.0) | |
| config = { | |
| "environment": environment, | |
| "embodiment": _cfg_get(cfg, "embodiment", _DEFAULT_EMBODIMENT), | |
| "object": _cfg_get(cfg, "object", None), | |
| "mimic": _cfg_get(cfg, "mimic", False), | |
| "teleop_device": _cfg_get(cfg, "teleop_device", None), | |
| "seed": _cfg_get(cfg, "seed", 42), | |
| "device": _cfg_get(cfg, "device", "cuda:0"), | |
| "disable_fabric": _cfg_get(cfg, "disable_fabric", False), | |
| "enable_cameras": _cfg_get(cfg, "enable_cameras", True), | |
| "headless": _cfg_get(cfg, "headless", True), | |
| "enable_pinocchio": _cfg_get(cfg, "enable_pinocchio", False), | |
| "episode_length": _cfg_get(cfg, "episode_length", None), | |
| "state_dim": _cfg_get(cfg, "state_dim", _DEFAULT_STATE_DIM), | |
| "action_dim": _cfg_get(cfg, "action_dim", _DEFAULT_ACTION_DIM), | |
| "camera_height": _cfg_get(cfg, "camera_height", 720), | |
| "camera_width": _cfg_get(cfg, "camera_width", 1280), | |
| "video": _cfg_get(cfg, "video", False), | |
| "video_length": _cfg_get(cfg, "video_length", 100), | |
| "video_interval": _cfg_get(cfg, "video_interval", 200), | |
| "state_keys": _cfg_get(cfg, "state_keys", _DEFAULT_STATE_KEYS), | |
| "camera_keys": _cfg_get(cfg, "camera_keys", _DEFAULT_CAMERA_KEYS) or "", | |
| "task": _cfg_get(cfg, "task", None), | |
| "variant": variant, | |
| "reward": reward, | |
| "hdr": hdr, | |
| "light_intensity": light_intensity, | |
| } | |
| logging.info( | |
| f"EnvHub make_env: environment={environment}, variant={variant}, n_envs={n_envs}, " | |
| f"headless={config['headless']}, enable_cameras={config['enable_cameras']}" | |
| ) | |
| return _create_isaaclab_env(config, n_envs) | |