nvkartik commited on
Commit
bcaecb7
·
1 Parent(s): ec16a15
Files changed (8) hide show
  1. .vscode/settings.json +3 -0
  2. README.md +286 -10
  3. __init__.py +12 -9
  4. configs/configs/config.yaml +98 -0
  5. env.py +538 -0
  6. install.sh +86 -0
  7. pyproject.toml +76 -0
  8. requirements.txt +47 -0
.vscode/settings.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "ros.distro": "humble"
3
+ }
README.md CHANGED
@@ -1,13 +1,289 @@
1
- ---
2
- title: IsaacLab Arena Envs
3
- emoji: 🤖
4
- colorFrom: blue
5
- colorTo: purple
6
- sdk: static
7
- pinned: false
8
- ---
9
-
10
  # IsaacLab Arena Environments
11
 
12
- Environment configurations for IsaacLab Arena integration with LeRobot.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
 
 
 
 
 
 
 
 
 
 
1
  # IsaacLab Arena Environments
2
 
3
+ [![Hugging Face](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-EnvHub-blue)](https://huggingface.co/nvkartik/isaaclab-arena-envs)
4
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
5
+
6
+ GPU-accelerated simulation environments for robotics learning, compatible with [LeRobot](https://github.com/huggingface/lerobot) and the [EnvHub](https://huggingface.co/docs/lerobot/envhub) ecosystem.
7
+
8
+ ## Quick Start
9
+
10
+ Load the environment directly from Hugging Face Hub:
11
+
12
+ ```python
13
+ from lerobot.envs.factory import make_env
14
+
15
+ # Load IsaacLab Arena environment
16
+ envs_dict = make_env(
17
+ "nvkartik/isaaclab-arena-envs",
18
+ n_envs=4,
19
+ trust_remote_code=True
20
+ )
21
+
22
+ # Access the environment
23
+ env = envs_dict["gr1_microwave"][0]
24
+
25
+ # Run an episode
26
+ obs, info = env.reset()
27
+ for _ in range(300):
28
+ action = env.action_space.sample()
29
+ obs, reward, terminated, truncated, info = env.step(action)
30
+ if terminated.any() or truncated.any():
31
+ break
32
+
33
+ env.close()
34
+ ```
35
+
36
+ ## Installation
37
+
38
+ ### Prerequisites
39
+
40
+ Before using this environment, you must install IsaacSim, IsaacLab, and IsaacLab-Arena:
41
+
42
+ ```bash
43
+ # 1. Install LeRobot (if not already installed)
44
+ pip install lerobot
45
+
46
+ # 2. Install Isaac Sim 5.1.0
47
+ uv pip install "isaacsim[all,extscache]==5.1.0" --extra-index-url https://pypi.nvidia.com
48
+ export ACCEPT_EULA=Y
49
+ export PRIVACY_CONSENT=Y
50
+
51
+ # 3. Install Isaac Lab 2.3.0
52
+ git clone https://github.com/isaac-sim/IsaacLab.git
53
+ cd IsaacLab
54
+ git checkout v2.3.0
55
+ ./isaaclab.sh -i
56
+ cd ..
57
+
58
+ # 4. Install IsaacLab Arena
59
+ git clone https://github.com/isaac-sim/IsaacLab-Arena.git
60
+ cd IsaacLab-Arena
61
+ uv pip install -e .
62
+ cd ..
63
+
64
+ # 5. Install additional dependencies
65
+ uv pip install qpsolvers==4.8.1 onnxruntime lightwheel-sdk vuer[all]
66
+ uv pip install numpy==1.26.0 lxml==4.9.4 packaging==23.2
67
+ ```
68
+
69
+ ### Using with EnvHub
70
+
71
+ Once the prerequisites are installed, you can load environments directly from the Hub:
72
+
73
+ ```python
74
+ from lerobot.envs.factory import make_env
75
+
76
+ envs_dict = make_env("nvkartik/isaaclab-arena-envs", n_envs=4, trust_remote_code=True)
77
+ ```
78
+
79
+ ## Available Environments
80
+
81
+ | Alias | Description | Robot | Action Dim |
82
+ |-------|-------------|-------|------------|
83
+ | `gr1_microwave` | Open a microwave door | GR1 | 36 |
84
+ | `galileo_pnp` | Pick and place task | Galileo | - |
85
+ | `g1_locomanip_pnp` | Locomotion + manipulation | G1 | - |
86
+ | `kitchen_pnp` | Kitchen pick and place | - | - |
87
+ | `press_button` | Button pressing task | - | - |
88
+
89
+ ## Configuration
90
+
91
+ Configuration is loaded from `configs/config.yaml`. Key options:
92
+
93
+ ```yaml
94
+ # Environment Selection
95
+ environment: gr1_microwave
96
+ task: "Reach out to the microwave and open it."
97
+
98
+ # Episode Configuration
99
+ episode_length: 300
100
+
101
+ # Observation Configuration
102
+ state_keys: robot_joint_pos
103
+ camera_keys: robot_pov_cam_rgb
104
+ state_dim: 54
105
+ action_dim: 36
106
+
107
+ # Simulation Settings
108
+ headless: true
109
+ enable_cameras: false
110
+ device: "cuda:0"
111
+
112
+ # Robot Configuration
113
+ embodiment: gr1_pink
114
+ object: power_drill
115
+ ```
116
+
117
+ ### Overriding Configuration
118
+
119
+ You can modify the `configs/config.yaml` file to change environment settings:
120
+
121
+ ```yaml
122
+ # Enable video recording
123
+ enable_cameras: true
124
+ headless: true
125
+
126
+ # Different robot/task
127
+ environment: galileo_pnp
128
+ embodiment: galileo
129
+ ```
130
+
131
+ ## Observation & Action Spaces
132
+
133
+ ### Observations
134
+
135
+ The environment returns dictionary observations:
136
+
137
+ ```python
138
+ obs, info = env.reset()
139
+ # obs["policy"] contains state observations (robot_joint_pos, etc.)
140
+ # obs["camera_obs"] contains camera images (when enable_cameras=True)
141
+ ```
142
+
143
+ ### Actions
144
+
145
+ Actions are continuous joint position targets:
146
+
147
+ ```python
148
+ # Default: 36-dimensional for GR1 robot
149
+ action = env.action_space.sample() # shape: (n_envs, 36)
150
+ ```
151
+
152
+ ## Video Recording
153
+
154
+ To record videos during evaluation:
155
+
156
+ 1. Enable cameras in `config.yaml`:
157
+ ```yaml
158
+ enable_cameras: true
159
+ headless: true
160
+ ```
161
+
162
+ 2. Use gymnasium's RecordVideo wrapper:
163
+ ```python
164
+ from gymnasium.wrappers import RecordVideo
165
+
166
+ env = envs_dict["gr1_microwave"][0]
167
+ env = RecordVideo(env, video_folder="./videos")
168
+ ```
169
+
170
+ ## Integration with LeRobot
171
+
172
+ ### Policy Evaluation
173
+
174
+ ```bash
175
+ python -m lerobot.scripts.eval \
176
+ --policy.path=outputs/train/my_policy/checkpoints/last/pretrained_model \
177
+ --env.type=hub \
178
+ --env.hub_id=nvkartik/isaaclab-arena-envs \
179
+ --env.trust_remote_code=true \
180
+ --eval.n_episodes=10
181
+ ```
182
+
183
+ ### Training
184
+
185
+ ```bash
186
+ python -m lerobot.scripts.train \
187
+ --policy.type=diffusion \
188
+ --env.type=hub \
189
+ --env.hub_id=nvkartik/isaaclab-arena-envs \
190
+ --env.trust_remote_code=true \
191
+ --dataset.repo_id=my_org/my_dataset
192
+ ```
193
+
194
+ ## Repository Structure
195
+
196
+ ```
197
+ isaaclab-arena-envs/
198
+ ├── env.py # Main environment definition (EnvHub entry point)
199
+ ├── configs/
200
+ │ └── config.yaml # Environment configuration
201
+ ├── requirements.txt # Python dependencies
202
+ ├── pyproject.toml # Package metadata
203
+ └── README.md # This file
204
+ ```
205
+
206
+ ## Development
207
+
208
+ ### Local Testing
209
+
210
+ ```python
211
+ # Test locally without Hub
212
+ from env import make_env
213
+
214
+ envs_dict = make_env(n_envs=2)
215
+ env = envs_dict["gr1_microwave"][0]
216
+ obs, info = env.reset()
217
+ print(f"Observation space: {env.observation_space}")
218
+ print(f"Action space: {env.action_space}")
219
+ env.close()
220
+ ```
221
+
222
+ ### Uploading to Hub
223
+
224
+ ```bash
225
+ # Install huggingface_hub
226
+ pip install huggingface_hub
227
+
228
+ # Login
229
+ huggingface-cli login
230
+
231
+ # Create and upload
232
+ huggingface-cli repo create isaaclab-arena-envs --type model
233
+ git init
234
+ git add .
235
+ git commit -m "Initial IsaacLab Arena environment"
236
+ git remote add origin https://huggingface.co/nvkartik/isaaclab-arena-envs
237
+ git push -u origin main
238
+ ```
239
+
240
+ ## Troubleshooting
241
+
242
+ ### "Module not found" errors
243
+
244
+ Ensure IsaacSim, IsaacLab, and IsaacLab-Arena are properly installed:
245
+
246
+ ```bash
247
+ python -c "import isaacsim; print('IsaacSim OK')"
248
+ python -c "import isaaclab; print('IsaacLab OK')"
249
+ python -c "import isaaclab_arena; print('IsaacLab-Arena OK')"
250
+ ```
251
+
252
+ ### CUDA/GPU issues
253
+
254
+ - Ensure you have a compatible NVIDIA GPU (RTX 2070+ recommended)
255
+ - Check CUDA installation: `nvidia-smi`
256
+ - Set device in config: `device: "cuda:0"`
257
+
258
+ ### Headless mode issues
259
+
260
+ For remote/cluster execution, ensure:
261
+ ```yaml
262
+ headless: true
263
+ enable_cameras: true # if you need video recording
264
+ ```
265
+
266
+ ## License
267
+
268
+ Apache 2.0 License. See [LICENSE](LICENSE) for details.
269
+
270
+ ## Citations
271
+
272
+ If you use this environment in your research, please cite:
273
+
274
+ ```bibtex
275
+ @software{isaaclab_arena_envs,
276
+ title = {IsaacLab Arena Environments for LeRobot},
277
+ author = {Sachdev, Kartik},
278
+ year = {2025},
279
+ url = {https://huggingface.co/nvkartik/isaaclab-arena-envs}
280
+ }
281
+ ```
282
+
283
+ ## Related Links
284
+
285
+ - [LeRobot](https://github.com/huggingface/lerobot) - Open-source robotics learning library
286
+ - [EnvHub Documentation](https://huggingface.co/docs/lerobot/envhub) - LeRobot environment hub
287
+ - [IsaacLab](https://github.com/isaac-sim/IsaacLab) - NVIDIA's robot learning framework
288
+ - [IsaacLab Arena](https://github.com/isaac-sim/IsaacLab-Arena) - Community environments for IsaacLab
289
 
__init__.py CHANGED
@@ -1,13 +1,16 @@
1
- # Copyright (c) 2025, LeRobot Project Developers.
2
- # All rights reserved.
3
- #
4
- # SPDX-License-Identifier: Apache-2.0
5
- """
6
- LeRobot Arena Hub - IsaacLab Arena environment integrations.
7
 
8
- This module provides make_env functions for various IsaacLab Arena tasks.
 
 
 
9
  """
10
 
11
- from lerobot.envs.arena_hub.envs import microwave_g1
 
 
 
12
 
13
- __all__ = ["microwave_g1"]
 
1
+ """IsaacLab Arena EnvHub Environment.
2
+
3
+ This package provides an EnvHub-compatible environment for IsaacLab Arena integration
4
+ with LeRobot. Load it from the Hugging Face Hub with:
 
 
5
 
6
+ from lerobot.envs.factory import make_env
7
+ envs_dict = make_env("nvkartik/isaaclab-arena-envs", n_envs=4, trust_remote_code=True)
8
+
9
+ See README.md for complete documentation and installation instructions.
10
  """
11
 
12
+ from .env import make_env
13
+
14
+ __all__ = ["make_env"]
15
+ __version__ = "0.1.0"
16
 
 
configs/configs/config.yaml ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # IsaacLab Arena EnvHub Configuration
2
+ #
3
+ # This file configures the IsaacLab Arena environment for EnvHub usage.
4
+ # All fields have sensible defaults - only specify what you need to override.
5
+ #
6
+ # Usage:
7
+ # from lerobot.envs.factory import make_env
8
+ # envs_dict = make_env("nvkartik/isaaclab-arena-envs", n_envs=4, trust_remote_code=True)
9
+
10
+ # ============================================================================
11
+ # Environment Selection
12
+ # ============================================================================
13
+ # Available aliases:
14
+ # - gr1_microwave: GR1 robot opening a microwave
15
+ # - galileo_pnp: Galileo pick and place task
16
+ # - g1_locomanip_pnp: G1 locomotion + manipulation pick and place
17
+ # - kitchen_pnp: Kitchen environment pick and place
18
+ # - press_button: Button pressing task
19
+ #
20
+ # Or use full module path: module.path.ClassName
21
+ environment: gr1_microwave
22
+
23
+ # Task description (used for policy conditioning, auto-generated if not provided)
24
+ task: "Reach out to the microwave and open it."
25
+
26
+ # ============================================================================
27
+ # Episode Configuration
28
+ # ============================================================================
29
+ episode_length: 300
30
+
31
+ # ============================================================================
32
+ # Observation Configuration
33
+ # ============================================================================
34
+ # State observation keys (comma-separated for multiple)
35
+ # Available keys depend on environment, common ones:
36
+ # - robot_joint_pos: Robot joint positions
37
+ # - left_eef_pos, right_eef_pos: End effector positions
38
+ state_keys: robot_joint_pos
39
+
40
+ # Camera observation keys (comma-separated for multiple)
41
+ # Available keys depend on environment, common ones:
42
+ # - robot_pov_cam_rgb: Robot's point-of-view camera
43
+ # - front_cam_rgb: Front-facing camera
44
+ camera_keys: robot_pov_cam_rgb
45
+
46
+ # Dimension configuration (must match environment)
47
+ state_dim: 54
48
+ action_dim: 36
49
+
50
+ # Camera resolution (when enable_cameras=true)
51
+ camera_height: 512
52
+ camera_width: 512
53
+
54
+ # ============================================================================
55
+ # Simulation Settings
56
+ # ============================================================================
57
+ # Run without GUI (required for remote/cluster execution)
58
+ headless: true
59
+
60
+ # Enable camera rendering (required for video recording)
61
+ # Note: Enabling cameras has performance impact
62
+ enable_cameras: false
63
+
64
+ # CUDA device for simulation
65
+ device: "cuda:0"
66
+
67
+ # Disable Fabric for debugging (slower but more compatible)
68
+ disable_fabric: false
69
+
70
+ # ============================================================================
71
+ # Robot Configuration
72
+ # ============================================================================
73
+ # Robot embodiment to use
74
+ # Available: gr1_pink, galileo, g1, etc.
75
+ embodiment: gr1_pink
76
+
77
+ # Object to interact with (environment-specific)
78
+ object: power_drill
79
+
80
+ # Enable mimic mode for teleoperation
81
+ mimic: false
82
+
83
+ # Teleoperation device (optional)
84
+ teleop_device: null
85
+
86
+ # ============================================================================
87
+ # Reproducibility & Advanced
88
+ # ============================================================================
89
+ seed: 42
90
+
91
+ # Enable Pinocchio for inverse kinematics
92
+ enable_pinocchio: true
93
+
94
+ # Video recording settings (when enable_cameras=true)
95
+ video: false
96
+ video_length: 100
97
+ video_interval: 200
98
+
env.py ADDED
@@ -0,0 +1,538 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """IsaacLab Arena EnvHub Environment.
2
+
3
+ This module provides an EnvHub-compatible environment for IsaacLab Arena integration.
4
+ Load it from the Hugging Face Hub with:
5
+
6
+ from lerobot.envs.factory import make_env
7
+ envs_dict = make_env("nvkartik/isaaclab-arena-envs", n_envs=4, trust_remote_code=True)
8
+
9
+ Or use locally during development:
10
+
11
+ from env import make_env
12
+ envs_dict = make_env(n_envs=4)
13
+
14
+ Configuration is loaded from configs/config.yaml.
15
+ See README.md for full documentation.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import argparse
21
+ import importlib
22
+ import logging
23
+ import os
24
+ from pathlib import Path
25
+ from typing import Any
26
+
27
+ import gymnasium as gym
28
+ import numpy as np
29
+ import torch
30
+ import yaml
31
+
32
+ # Module path for environment class resolution
33
+ ISAACLAB_ARENA_ENV_MODULE = os.environ.get("ISAACLAB_ARENA_ENV_MODULE", "isaaclab_arena_environments")
34
+
35
+ # Environment aliases for common configurations
36
+ ENVIRONMENT_ALIASES: dict[str, str] = {
37
+ "gr1_microwave": (
38
+ f"{ISAACLAB_ARENA_ENV_MODULE}.gr1_open_microwave_environment.Gr1OpenMicrowaveEnvironment"
39
+ ),
40
+ "galileo_pnp": (
41
+ f"{ISAACLAB_ARENA_ENV_MODULE}.galileo_pick_and_place_environment.GalileoPickAndPlaceEnvironment"
42
+ ),
43
+ "g1_locomanip_pnp": (
44
+ f"{ISAACLAB_ARENA_ENV_MODULE}"
45
+ ".galileo_g1_locomanip_pick_and_place_environment"
46
+ ".GalileoG1LocomanipPickAndPlaceEnvironment"
47
+ ),
48
+ "kitchen_pnp": (
49
+ f"{ISAACLAB_ARENA_ENV_MODULE}.kitchen_pick_and_place_environment.KitchenPickAndPlaceEnvironment"
50
+ ),
51
+ "press_button": (f"{ISAACLAB_ARENA_ENV_MODULE}.press_button_environment.PressButtonEnvironment"),
52
+ }
53
+
54
+ # Default configuration values
55
+ DEFAULT_CONFIG: dict[str, Any] = {
56
+ "environment": "gr1_microwave",
57
+ "task": None, # Auto-generated if not provided
58
+ "episode_length": 300,
59
+ "num_envs": 1,
60
+ "state_keys": "robot_joint_pos",
61
+ "camera_keys": "robot_pov_cam_rgb",
62
+ "state_dim": 54,
63
+ "action_dim": 36,
64
+ "headless": True,
65
+ "enable_cameras": False,
66
+ "device": "cuda:0",
67
+ "embodiment": "gr1_pink",
68
+ "object": "power_drill",
69
+ "seed": 42,
70
+ "enable_pinocchio": True,
71
+ "disable_fabric": False,
72
+ "mimic": False,
73
+ "teleop_device": None,
74
+ "camera_height": 512,
75
+ "camera_width": 512,
76
+ "video": False,
77
+ "video_length": 100,
78
+ "video_interval": 200,
79
+ }
80
+
81
+
82
+ def resolve_environment_alias(environment: str) -> str:
83
+ """Resolve an environment alias to its full module path."""
84
+ return ENVIRONMENT_ALIASES.get(environment, environment)
85
+
86
+
87
+ class IsaacLabVectorEnvWrapper:
88
+ """Wrapper adapting IsaacLab batched GPU env to VectorEnv interface.
89
+
90
+ IsaacLab handles vectorization internally on GPU, unlike gym's
91
+ SyncVectorEnv/AsyncVectorEnv. This provides the expected interface
92
+ for LeRobot evaluation.
93
+
94
+ Video Recording:
95
+ Supports gymnasium.wrappers.RecordVideo (IsaacLab native approach).
96
+ Requires enable_cameras=True in config when running headless.
97
+ See: isaac-sim.github.io/IsaacLab/main/source/how-to/record_video.html
98
+ """
99
+
100
+ metadata = {"render_modes": ["rgb_array"], "render_fps": 30}
101
+
102
+ def __init__(
103
+ self,
104
+ env,
105
+ episode_length: int = 500,
106
+ task: str | None = None,
107
+ render_mode: str | None = "rgb_array",
108
+ simulation_app=None,
109
+ ):
110
+ self._env = env
111
+ self._num_envs = env.num_envs
112
+ self._episode_length = episode_length
113
+ self._closed = False
114
+ self.render_mode = render_mode
115
+ self._simulation_app = simulation_app
116
+
117
+ self.observation_space = env.observation_space
118
+ self.action_space = env.action_space
119
+ self.task = task
120
+
121
+ # Use env metadata if available
122
+ if hasattr(env, "metadata") and env.metadata:
123
+ self.metadata = {**self.metadata, **env.metadata}
124
+
125
+ @property
126
+ def unwrapped(self):
127
+ return self
128
+
129
+ @property
130
+ def num_envs(self) -> int:
131
+ return self._num_envs
132
+
133
+ @property
134
+ def _max_episode_steps(self) -> int:
135
+ return self._episode_length
136
+
137
+ @property
138
+ def device(self) -> str:
139
+ return getattr(self._env, "device", "cpu")
140
+
141
+ def reset(
142
+ self,
143
+ *,
144
+ seed: int | list[int] | None = None,
145
+ options: dict[str, Any] | None = None,
146
+ ) -> tuple[dict[str, Any], dict[str, Any]]:
147
+ """Reset all environments."""
148
+ # IsaacLab expects a single seed
149
+ if isinstance(seed, (list, tuple, range)):
150
+ seed = seed[0] if len(seed) > 0 else None
151
+
152
+ obs, info = self._env.reset(seed=seed, options=options)
153
+
154
+ if "final_info" not in info:
155
+ info["final_info"] = {"is_success": np.zeros(self._num_envs, dtype=bool)}
156
+
157
+ return obs, info
158
+
159
+ def step(
160
+ self, actions: np.ndarray | torch.Tensor
161
+ ) -> tuple[dict[str, Any], np.ndarray, np.ndarray, np.ndarray, dict[str, Any]]:
162
+ """Step all environments."""
163
+ if isinstance(actions, np.ndarray):
164
+ actions = torch.from_numpy(actions).to(self._env.device)
165
+
166
+ obs, reward, terminated, truncated, info = self._env.step(actions)
167
+
168
+ # Convert to numpy for gym compatibility
169
+ reward = reward.cpu().numpy().astype(np.float32)
170
+ terminated = terminated.cpu().numpy().astype(bool)
171
+ truncated = truncated.cpu().numpy().astype(bool)
172
+
173
+ # Extract success status
174
+ is_success = self._get_success(terminated, truncated)
175
+ info["final_info"] = {"is_success": is_success}
176
+
177
+ return obs, reward, terminated, truncated, info
178
+
179
+ def _get_success(self, terminated: np.ndarray, truncated: np.ndarray) -> np.ndarray:
180
+ """Extract per-environment success status from termination manager."""
181
+ is_success = np.zeros(self._num_envs, dtype=bool)
182
+
183
+ term_manager = self._env.termination_manager
184
+ success_tensor = term_manager.get_term("success")
185
+ if isinstance(success_tensor, torch.Tensor):
186
+ is_success = success_tensor.cpu().numpy().astype(bool)
187
+ else:
188
+ is_success = np.array(success_tensor, dtype=bool)
189
+
190
+ return is_success & (terminated | truncated)
191
+
192
+ def call(self, method_name: str, *args, **kwargs) -> list[Any]:
193
+ """Call a method on the underlying environment(s)."""
194
+ if method_name == "_max_episode_steps":
195
+ return [self._episode_length] * self._num_envs
196
+ if method_name == "task":
197
+ return [self.task] * self._num_envs
198
+ if method_name == "render":
199
+ return self.render_all()
200
+
201
+ if hasattr(self._env, method_name):
202
+ attr = getattr(self._env, method_name)
203
+ result = attr(*args, **kwargs) if callable(attr) else attr
204
+ if isinstance(result, list):
205
+ return result
206
+ return [result] * self._num_envs
207
+
208
+ raise AttributeError(f"Environment has no method/attribute '{method_name}'")
209
+
210
+ def render_all(self) -> list[np.ndarray]:
211
+ """Render all environments and return list of frames.
212
+
213
+ Public method for LeRobot eval video recording.
214
+ Returns a list of RGB frames, one per environment.
215
+ """
216
+ frames = self.render()
217
+ if frames is None:
218
+ placeholder = np.zeros((480, 640, 3), dtype=np.uint8)
219
+ return [placeholder] * self._num_envs
220
+
221
+ if frames.ndim == 3: # Single frame (H, W, C)
222
+ return [frames] * self._num_envs
223
+ if frames.ndim == 4: # Batch (N, H, W, C)
224
+ return [frames[i] for i in range(min(len(frames), self._num_envs))]
225
+
226
+ placeholder = np.zeros((480, 640, 3), dtype=np.uint8)
227
+ return [placeholder] * self._num_envs
228
+
229
+ def render(self) -> np.ndarray | None:
230
+ """Render the environment (gymnasium RecordVideo compatible).
231
+
232
+ Returns rgb_array for video recording per IsaacLab native approach.
233
+ Requires enable_cameras=True in config when running headless.
234
+ """
235
+ if self.render_mode != "rgb_array":
236
+ return None
237
+
238
+ frames = self._env.render() if hasattr(self._env, "render") else None
239
+ if frames is None:
240
+ return None
241
+
242
+ if isinstance(frames, torch.Tensor):
243
+ frames = frames.cpu().numpy()
244
+
245
+ # Return first env frame for RecordVideo compatibility
246
+ if frames.ndim == 4:
247
+ return frames[0]
248
+ return frames
249
+
250
+ def close(self) -> None:
251
+ """Close the environment and release resources."""
252
+ if not self._closed:
253
+ logging.info("Closing IsaacLab Arena environment...")
254
+ self._env.close()
255
+ if self._simulation_app is not None:
256
+ self._simulation_app.app.close()
257
+ self._closed = True
258
+ logging.info("Environment closed")
259
+
260
+ @property
261
+ def envs(self) -> list[IsaacLabVectorEnvWrapper]:
262
+ """Return list of sub-environments for VectorEnv compatibility."""
263
+ return [self] * self._num_envs
264
+
265
+ def __del__(self):
266
+ self.close()
267
+
268
+
269
+ def _validate_env_config(
270
+ env,
271
+ state_keys: tuple[str, ...],
272
+ camera_keys: tuple[str, ...],
273
+ cfg_state_dim: int,
274
+ cfg_action_dim: int,
275
+ ) -> None:
276
+ """Validate observation keys and dimensions against IsaacLab managers."""
277
+ obs_manager = env.observation_manager
278
+ active_terms = obs_manager.active_terms
279
+ policy_terms = set(active_terms.get("policy", []))
280
+ camera_terms = set(active_terms.get("camera_obs", []))
281
+
282
+ # Validate keys exist
283
+ missing_state = [k for k in state_keys if k not in policy_terms]
284
+ if missing_state:
285
+ raise ValueError(f"Invalid state_keys: {missing_state}. Available: {sorted(policy_terms)}")
286
+
287
+ missing_cam = [k for k in camera_keys if k not in camera_terms]
288
+ if missing_cam:
289
+ raise ValueError(f"Invalid camera_keys: {missing_cam}. Available: {sorted(camera_terms)}")
290
+
291
+ # Validate dimensions
292
+ env_action_dim = env.action_space.shape[-1]
293
+ if cfg_action_dim != env_action_dim:
294
+ raise ValueError(f"action_dim mismatch: config={cfg_action_dim}, env={env_action_dim}")
295
+
296
+ # Compute expected state dimension
297
+ policy_dims = obs_manager.group_obs_dim.get("policy", [])
298
+ policy_names = active_terms.get("policy", [])
299
+ term_dims = dict(zip(policy_names, policy_dims, strict=False))
300
+
301
+ expected_state_dim = 0
302
+ for key in state_keys:
303
+ if key in term_dims:
304
+ shape = term_dims[key]
305
+ dim = 1
306
+ for s in shape if isinstance(shape, (tuple, list)) else [shape]:
307
+ dim *= s
308
+ expected_state_dim += dim
309
+
310
+ if cfg_state_dim != expected_state_dim:
311
+ raise ValueError(
312
+ f"state_dim mismatch: config={cfg_state_dim}, "
313
+ f"computed={expected_state_dim}. "
314
+ f"Term dims: {term_dims}"
315
+ )
316
+
317
+ logging.info(f"Validated: state_keys={state_keys}, camera_keys={camera_keys}")
318
+
319
+
320
+ def _find_config_file(config_path: str | Path | None = None) -> Path | None:
321
+ """Find the config.yaml file in standard locations.
322
+
323
+ Search order:
324
+ 1. Explicit config_path if provided
325
+ 2. configs/config.yaml relative to this module
326
+ 3. config.yaml relative to this module
327
+ 4. ISAACLAB_ARENA_CONFIG_PATH environment variable
328
+ """
329
+ if config_path is not None:
330
+ path = Path(config_path)
331
+ if path.exists():
332
+ return path
333
+
334
+ # Get the directory containing this module
335
+ module_dir = Path(__file__).parent
336
+
337
+ # Search in standard locations
338
+ search_paths = [
339
+ module_dir / "configs" / "config.yaml",
340
+ module_dir / "config.yaml",
341
+ Path("configs/config.yaml"),
342
+ Path("config.yaml"),
343
+ ]
344
+
345
+ # Also check environment variable
346
+ env_path = os.environ.get("ISAACLAB_ARENA_CONFIG_PATH")
347
+ if env_path:
348
+ search_paths.insert(0, Path(env_path))
349
+
350
+ for path in search_paths:
351
+ if path.exists():
352
+ return path
353
+
354
+ return None
355
+
356
+
357
+ def _load_config_from_yaml(config_path: str | Path | None = None) -> dict:
358
+ """Load configuration from YAML file."""
359
+ config = DEFAULT_CONFIG.copy()
360
+
361
+ yaml_path = _find_config_file(config_path)
362
+ if yaml_path is not None:
363
+ logging.info(f"Loading config from: {yaml_path}")
364
+ with open(yaml_path) as f:
365
+ yaml_config = yaml.safe_load(f) or {}
366
+ config.update(yaml_config)
367
+ else:
368
+ logging.info("No config.yaml found, using defaults")
369
+
370
+ return config
371
+
372
+
373
+ def _create_isaaclab_env(config: dict, n_envs: int) -> dict[str, dict[int, gym.vector.VectorEnv]]:
374
+ """Create IsaacLab Arena environment from configuration.
375
+
376
+ Args:
377
+ config: Configuration dictionary.
378
+ n_envs: Number of parallel environments.
379
+
380
+ Returns:
381
+ Dict mapping environment name to {task_id: VectorEnv}.
382
+ """
383
+ from isaaclab.app import AppLauncher
384
+
385
+ if config.get("enable_pinocchio", True):
386
+ import pinocchio # noqa: F401
387
+
388
+ # Override num_envs
389
+ config["num_envs"] = n_envs
390
+
391
+ # Create argparse namespace for IsaacLab
392
+ as_isaaclab_argparse = argparse.Namespace(**config)
393
+
394
+ logging.info("Launching IsaacLab simulation app...")
395
+ app_launcher = AppLauncher(as_isaaclab_argparse)
396
+
397
+ from isaaclab_arena.environments.arena_env_builder import ArenaEnvBuilder
398
+
399
+ environment = config.get("environment")
400
+ if environment is None:
401
+ raise ValueError("cfg.environment must be specified")
402
+
403
+ # Resolve alias and create environment
404
+ environment_path = resolve_environment_alias(environment)
405
+ logging.info(f"Creating environment: {environment_path}")
406
+
407
+ module_path, class_name = environment_path.rsplit(".", 1)
408
+ environment_module = importlib.import_module(module_path)
409
+ environment_class = getattr(environment_module, class_name)()
410
+
411
+ env_builder = ArenaEnvBuilder(environment_class.get_env(as_isaaclab_argparse), as_isaaclab_argparse)
412
+
413
+ # Determine render_mode
414
+ render_mode = "rgb_array" if config.get("enable_cameras", False) else None
415
+
416
+ raw_env = env_builder.make_registered()
417
+
418
+ # Set render_mode on underlying env
419
+ if render_mode and hasattr(raw_env, "render_mode"):
420
+ raw_env.render_mode = render_mode
421
+ logging.info(f"Set render_mode={render_mode} on underlying IsaacLab env")
422
+
423
+ # Validate config
424
+ state_keys = tuple(k.strip() for k in config.get("state_keys", "").split(",") if k.strip())
425
+ camera_keys = tuple(k.strip() for k in config.get("camera_keys", "").split(",") if k.strip())
426
+ _validate_env_config(
427
+ raw_env, state_keys, camera_keys,
428
+ config.get("state_dim", 54),
429
+ config.get("action_dim", 36)
430
+ )
431
+
432
+ # Get task description
433
+ task = config.get("task")
434
+ if task is None:
435
+ task = f"Complete the {environment.replace('_', ' ')} task."
436
+
437
+ # Wrap and return
438
+ wrapped_env = IsaacLabVectorEnvWrapper(
439
+ raw_env,
440
+ episode_length=config.get("episode_length", 300),
441
+ task=task,
442
+ render_mode=render_mode,
443
+ simulation_app=app_launcher,
444
+ )
445
+ logging.info(f"Created: {environment} with {wrapped_env.num_envs} envs, render_mode={render_mode}")
446
+
447
+ return {environment: {0: wrapped_env}}
448
+
449
+
450
+ def make_env(
451
+ n_envs: int = 1,
452
+ use_async_envs: bool = False,
453
+ config_path: str | Path | None = None,
454
+ ) -> dict[str, dict[int, gym.vector.VectorEnv]]:
455
+ """Create IsaacLab Arena environments (EnvHub-compatible API).
456
+
457
+ This function provides the standard EnvHub interface for loading
458
+ environments from the Hugging Face Hub. Configuration is loaded
459
+ from configs/config.yaml.
460
+
461
+ Args:
462
+ n_envs: Number of parallel environments (default: 1).
463
+ use_async_envs: Ignored for IsaacLab (GPU-based batched execution).
464
+ config_path: Optional explicit path to config.yaml file.
465
+
466
+ Returns:
467
+ Dict mapping environment name to {task_id: VectorEnv}.
468
+ Format: {suite_name: {0: wrapped_vector_env}}
469
+
470
+ Example:
471
+ >>> from env import make_env
472
+ >>> envs_dict = make_env(n_envs=4)
473
+ >>> env = envs_dict["gr1_microwave"][0]
474
+ >>> obs, info = env.reset()
475
+
476
+ Configuration:
477
+ Create a configs/config.yaml file with your settings:
478
+
479
+ ```yaml
480
+ environment: gr1_microwave
481
+ task: "Reach out to the microwave and open it."
482
+ episode_length: 300
483
+ headless: true
484
+ enable_cameras: false
485
+ ```
486
+
487
+ Note:
488
+ IsaacLab environments use GPU-based batched execution, so
489
+ `use_async_envs` is ignored. The returned wrapper provides
490
+ VectorEnv compatibility.
491
+ """
492
+ if n_envs < 1:
493
+ raise ValueError("`n_envs` must be at least 1")
494
+
495
+ # Load config from YAML
496
+ config = _load_config_from_yaml(config_path)
497
+
498
+ logging.info(
499
+ f"EnvHub make_env: environment={config.get('environment')}, n_envs={n_envs}"
500
+ )
501
+ logging.info(
502
+ f"Config: headless={config.get('headless')}, "
503
+ f"enable_cameras={config.get('enable_cameras')}"
504
+ )
505
+
506
+ return _create_isaaclab_env(config, n_envs)
507
+
508
+
509
+ # For direct script execution
510
+ if __name__ == "__main__":
511
+ import tqdm
512
+
513
+ logging.basicConfig(level=logging.INFO)
514
+
515
+ print("Creating IsaacLab Arena environment...")
516
+ envs_dict = make_env(n_envs=1)
517
+
518
+ suite_name = next(iter(envs_dict))
519
+ env = envs_dict[suite_name][0]
520
+
521
+ print(f"Environment: {suite_name}")
522
+ print(f"Num envs: {env.num_envs}")
523
+ print(f"Observation space: {env.observation_space}")
524
+ print(f"Action space: {env.action_space}")
525
+
526
+ obs, info = env.reset()
527
+ print(f"Observation keys: {obs.keys() if isinstance(obs, dict) else 'array'}")
528
+
529
+ print("Running zero-action rollout...")
530
+ for _ in tqdm.tqdm(range(100)):
531
+ with torch.inference_mode():
532
+ action_dim = env.action_space.shape[-1]
533
+ actions = torch.zeros((env.num_envs, action_dim), device=env.device)
534
+ obs, rewards, terminated, truncated, info = env.step(actions)
535
+
536
+ env.close()
537
+ print("Done!")
538
+
install.sh ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # IsaacLab Arena EnvHub Installation Script
3
+ #
4
+ # This script installs all dependencies required for IsaacLab Arena environments.
5
+ #
6
+ # Usage:
7
+ # chmod +x install.sh
8
+ # ./install.sh
9
+ #
10
+ # Prerequisites:
11
+ # - NVIDIA GPU (RTX 2070+ recommended)
12
+ # - Ubuntu 20.04+ or compatible Linux distribution
13
+ # - Python 3.10+
14
+ # - CUDA 12.x
15
+
16
+ set -e
17
+
18
+ echo "=========================================="
19
+ echo "IsaacLab Arena EnvHub Installation"
20
+ echo "=========================================="
21
+
22
+ # Check for uv or pip
23
+ if command -v uv &> /dev/null; then
24
+ PIP_CMD="uv pip"
25
+ echo "Using uv for package management"
26
+ else
27
+ PIP_CMD="pip"
28
+ echo "Using pip for package management"
29
+ fi
30
+
31
+ # Step 1: Install Isaac Sim 5.1.0
32
+ echo ""
33
+ echo "[1/5] Installing Isaac Sim 5.1.0..."
34
+ echo "Note: This requires accepting NVIDIA's EULA"
35
+ export ACCEPT_EULA=Y
36
+ export PRIVACY_CONSENT=Y
37
+ $PIP_CMD install "isaacsim[all,extscache]==5.1.0" --extra-index-url https://pypi.nvidia.com
38
+
39
+ # Step 2: Clone and install Isaac Lab 2.3.0
40
+ echo ""
41
+ echo "[2/5] Installing Isaac Lab 2.3.0..."
42
+ if [ ! -d "IsaacLab" ]; then
43
+ git clone https://github.com/isaac-sim/IsaacLab.git
44
+ fi
45
+ cd IsaacLab
46
+ git checkout v2.3.0
47
+ ./isaaclab.sh -i
48
+ cd ..
49
+
50
+ # Step 3: Clone and install IsaacLab Arena
51
+ echo ""
52
+ echo "[3/5] Installing IsaacLab Arena..."
53
+ if [ ! -d "IsaacLab-Arena" ]; then
54
+ git clone https://github.com/isaac-sim/IsaacLab-Arena.git
55
+ fi
56
+ cd IsaacLab-Arena
57
+ $PIP_CMD install -e .
58
+ cd ..
59
+
60
+ # Step 4: Install additional dependencies
61
+ echo ""
62
+ echo "[4/5] Installing additional dependencies..."
63
+ $PIP_CMD install qpsolvers==4.8.1
64
+ $PIP_CMD install onnxruntime lightwheel-sdk "vuer[all]"
65
+ $PIP_CMD install numpy==1.26.0 lxml==4.9.4 packaging==23.2
66
+
67
+ # Step 5: Install Python dependencies for env.py
68
+ echo ""
69
+ echo "[5/5] Installing EnvHub dependencies..."
70
+ $PIP_CMD install gymnasium>=0.29.0 torch>=2.0.0 pyyaml>=6.0
71
+
72
+ echo ""
73
+ echo "=========================================="
74
+ echo "Installation Complete!"
75
+ echo "=========================================="
76
+ echo ""
77
+ echo "You can now use IsaacLab Arena with LeRobot EnvHub:"
78
+ echo ""
79
+ echo " from lerobot.envs.factory import make_env"
80
+ echo " envs_dict = make_env('nvkartik/isaaclab-arena-envs', n_envs=4, trust_remote_code=True)"
81
+ echo ""
82
+ echo "Or test locally:"
83
+ echo ""
84
+ echo " python env.py"
85
+ echo ""
86
+
pyproject.toml ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "isaaclab-arena-envs"
7
+ version = "0.1.0"
8
+ description = "IsaacLab Arena environments for LeRobot EnvHub integration"
9
+ readme = "README.md"
10
+ license = "Apache-2.0"
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ { name = "Kartik Sachdev", email = "nvkartik@users.noreply.github.com" }
14
+ ]
15
+ keywords = [
16
+ "robotics",
17
+ "simulation",
18
+ "reinforcement-learning",
19
+ "isaaclab",
20
+ "lerobot",
21
+ "envhub",
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 4 - Beta",
25
+ "Intended Audience :: Developers",
26
+ "Intended Audience :: Science/Research",
27
+ "License :: OSI Approved :: Apache Software License",
28
+ "Operating System :: POSIX :: Linux",
29
+ "Programming Language :: Python :: 3",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
33
+ "Topic :: Software Development :: Libraries :: Python Modules",
34
+ ]
35
+
36
+ # Core dependencies for the env.py module
37
+ # Note: IsaacSim, IsaacLab, and IsaacLab-Arena must be installed separately
38
+ # See README.md for full installation instructions
39
+ dependencies = [
40
+ "gymnasium>=0.29.0",
41
+ "numpy>=1.26.0,<2.0",
42
+ "torch>=2.0.0",
43
+ "pyyaml>=6.0",
44
+ ]
45
+
46
+ [project.optional-dependencies]
47
+ # Development dependencies
48
+ dev = [
49
+ "pytest>=7.0",
50
+ "ruff>=0.1.0",
51
+ ]
52
+
53
+ [project.urls]
54
+ Homepage = "https://huggingface.co/nvkartik/isaaclab-arena-envs"
55
+ Repository = "https://huggingface.co/nvkartik/isaaclab-arena-envs"
56
+ Documentation = "https://huggingface.co/docs/lerobot"
57
+
58
+ [tool.hatch.build.targets.wheel]
59
+ packages = ["."]
60
+
61
+ [tool.hatch.build.targets.sdist]
62
+ include = [
63
+ "env.py",
64
+ "configs/",
65
+ "README.md",
66
+ "requirements.txt",
67
+ ]
68
+
69
+ [tool.ruff]
70
+ line-length = 110
71
+ target-version = "py310"
72
+
73
+ [tool.ruff.lint]
74
+ select = ["E", "F", "I", "W"]
75
+ ignore = ["E501"]
76
+
requirements.txt ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # IsaacLab Arena EnvHub Requirements
2
+ # ==================================
3
+ #
4
+ # This file lists dependencies for the IsaacLab Arena EnvHub environment.
5
+ #
6
+ # IMPORTANT: IsaacSim, IsaacLab, and IsaacLab-Arena must be installed separately!
7
+ # See README.md for complete installation instructions.
8
+ #
9
+ # Install these dependencies with:
10
+ # pip install -r requirements.txt
11
+
12
+ # Core Python dependencies (env.py)
13
+ gymnasium>=0.29.0
14
+ numpy>=1.26.0,<2.0
15
+ torch>=2.0.0
16
+ pyyaml>=6.0
17
+
18
+ # Required by IsaacLab-Arena
19
+ qpsolvers==4.8.1
20
+ lxml==4.9.4
21
+ packaging==23.2
22
+
23
+ # Optional: for ONNX policy inference
24
+ # onnxruntime
25
+
26
+ # Optional: for VR/teleoperation
27
+ # lightwheel-sdk
28
+ # vuer[all]
29
+
30
+ # =============================================================================
31
+ # EXTERNAL DEPENDENCIES (install separately, not via pip)
32
+ # =============================================================================
33
+ #
34
+ # 1. Isaac Sim 5.1.0:
35
+ # uv pip install "isaacsim[all,extscache]==5.1.0" --extra-index-url https://pypi.nvidia.com
36
+ # export ACCEPT_EULA=Y
37
+ # export PRIVACY_CONSENT=Y
38
+ #
39
+ # 2. Isaac Lab 2.3.0:
40
+ # git clone https://github.com/isaac-sim/IsaacLab.git
41
+ # cd IsaacLab && git checkout v2.3.0
42
+ # ./isaaclab.sh -i
43
+ #
44
+ # 3. IsaacLab Arena:
45
+ # git clone https://github.com/isaac-sim/IsaacLab-Arena.git
46
+ # cd IsaacLab-Arena && uv pip install -e .
47
+