Skip to content

Commit 698d9c9

Browse files
committed
feat: scripts dispatcher + framework tests + docs
scripts/{train,play}.py: top-level dispatcher. Parses --framework {isaaclab,mjlab} (required) and --rl {rsl_rl,skrl,cusrl} (default rsl_rl), then exec's under .venvs/<framework>/bin/python before delegating to the backend-specific entry-point. Users can run from system Python without source-activating any venv. scripts/{isaaclab,mjlab}/: backend-specific entry points. - scripts/isaaclab/reinforcement_learning/{rsl_rl,skrl,cusrl}/{train,play}.py: moved from scripts/reinforcement_learning/. Log root unified to logs/isaaclab/<exp>/<run> (was logs/{rsl_rl,skrl,cusrl}/<exp>); behavior otherwise unchanged. - scripts/isaaclab/tools/: convert_urdf, convert_mjcf, list_envs, random_agent, zero_agent, train_batch, delivery, clean_trash, beyondmimic moved here. cli_trainer remains framework-neutral. - scripts/mjlab/reinforcement_learning/rsl_rl/{train,play}.py: thin wrappers over mjlab.scripts.{train,play}.main, preceded by import robot_lab.tasks so RobotLab-* tasks register into mjlab's registry. mjlab uses tyro-based CLI (--env.scene.num-envs ...). - scripts/mjlab/tools/list_envs.py: mirrors mjlab.scripts.list_envs. tests/framework/: subprocess-isolated import smoke + AST-based no-direct- imports audit (40 files scanned, 0 violations under the agreed scope). tests/integration/test_smoke.py: opt-in via -m smoke; mjlab side passes; IL side skipped pending external IsaacSim 5.1 + py3.11 compat fix. pyproject.toml (root): smoke marker + ROS-plugin block + testpaths. docs/migration-from-subclass.md: walks IL users through the (small) changes needed to keep their custom tasks working. README.md: new Frameworks section + Version Dependency mjlab column.
1 parent e1b3937 commit 698d9c9

45 files changed

Lines changed: 2142 additions & 30 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,37 +46,49 @@ The table below lists all available environments:
4646
>
4747
> Discuss in [Github Discussion](https://github.com/fan-ziqi/robot_lab/discussions) or [Discord](http://www.robotsfan.com/dc_robot_lab).
4848
49-
## Version Dependency
49+
## Frameworks
5050

51-
| robot_lab Version | Isaac Lab Version | Isaac Sim Version |
52-
|------------------ | ----------------------------- | ------------------------- |
53-
| `main` branch | `main` branch | Isaac Sim 4.5 / 5.0 / 5.1 |
54-
| `v2.3.2` | `v2.3.2` | Isaac Sim 4.5 / 5.0 / 5.1 |
55-
| `v2.2.2` | `v2.2.1` | Isaac Sim 4.5 / 5.0 |
56-
| `v2.1.1` | `v2.1.1` | Isaac Sim 4.5 |
57-
| `v1.1` | `v1.4.1` | Isaac Sim 4.2 |
51+
robot_lab tasks run on either **IsaacLab** or **mjlab**. Select the backend
52+
at runtime via the dispatcher:
5853

59-
## Installation
54+
```bash
55+
python scripts/train.py --framework isaaclab --rl rsl_rl --task RobotLab-Velocity-Rough-Unitree-A1-v0
56+
python scripts/train.py --framework mjlab --rl rsl_rl --task RobotLab-Velocity-Rough-Unitree-A1-v0
57+
```
6058

61-
- Install Isaac Lab by following the [installation guide](https://isaac-sim.github.io/IsaacLab/main/source/setup/installation/index.html). We recommend using the conda installation as it simplifies calling Python scripts from the terminal.
59+
Each backend has its own venv, provisioned by `./setup_env.sh`. The
60+
dispatcher auto-`exec`s under the right interpreter — no `source ... activate`
61+
needed. Tasks tagged `framework_required="isaaclab"` are silently skipped
62+
on the mjlab backend (and vice versa); running them with the wrong
63+
`--framework` produces a clear "task not registered" error.
6264

63-
- Clone this repository separately from the Isaac Lab installation (i.e. outside the `IsaacLab` directory):
65+
Existing IsaacLab users should read
66+
[`docs/migration-from-subclass.md`](docs/migration-from-subclass.md)
67+
for the (small) set of changes they need to make.
6468

65-
```bash
66-
git clone https://github.com/fan-ziqi/robot_lab.git
67-
```
69+
## Version Dependency
6870

69-
- Using a python interpreter that has Isaac Lab installed, install the library
71+
| robot_lab Version | Isaac Lab Version | Isaac Sim Version | mjlab Version |
72+
|------------------ | ----------------------------- | ------------------------- | ------------- |
73+
| `main` branch | `main` branch | Isaac Sim 4.5 / 5.0 / 5.1 | `>=1.4,<2` |
74+
| `v2.3.2` | `v2.3.2` | Isaac Sim 4.5 / 5.0 / 5.1 | n/a |
75+
| `v2.2.2` | `v2.2.1` | Isaac Sim 4.5 / 5.0 | n/a |
76+
| `v2.1.1` | `v2.1.1` | Isaac Sim 4.5 | n/a |
77+
| `v1.1` | `v1.4.1` | Isaac Sim 4.2 | n/a |
7078

71-
```bash
72-
python -m pip install -e source/robot_lab
73-
```
79+
## Installation
7480

75-
- Verify that the extension is correctly installed by running the following command to print all the available environments in the extension:
81+
Pick one (or both, in separate venvs):
7682

77-
```bash
78-
python scripts/tools/list_envs.py
79-
```
83+
```bash
84+
./setup_env.sh isaaclab # Python 3.11, IsaacSim+IsaacLab via PyPI
85+
./setup_env.sh mjlab # Python 3.12, mjlab via PyPI
86+
```
87+
88+
The two venvs live at `.venvs/{isaaclab,mjlab}/` and are managed by uv. You do
89+
not need to `source ... activate` — the dispatcher (`scripts/train.py`,
90+
`scripts/play.py`) auto-exec's under the right interpreter when you pass
91+
`--framework`.
8092

8193
<details>
8294

docs/migration-from-subclass.md

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# Migrating from the IsaacLab subclass style
2+
3+
This guide is for IsaacLab users who maintain custom tasks in robot_lab and
4+
want to keep them working after the dual-framework migration.
5+
6+
**TL;DR:** existing custom tasks (subclassing `LocomotionVelocityRoughEnvCfg`,
7+
overriding fields in `__post_init__`) continue to work on IsaacLab unchanged.
8+
Only two things break:
9+
10+
1. **Task IDs** dropped the `Isaac-` infix:
11+
`RobotLab-Isaac-Velocity-Rough-Unitree-A1-v0``RobotLab-Velocity-Rough-Unitree-A1-v0`.
12+
Update shell aliases / CI / wandb run names.
13+
2. **Log root** moved from `logs/{rsl_rl,skrl,cusrl}/<exp>` to
14+
`logs/isaaclab/<exp>`. Old wandb / tensorboard run links no longer
15+
resolve; new runs work normally.
16+
17+
## Quick start
18+
19+
```bash
20+
# Old:
21+
python scripts/reinforcement_learning/rsl_rl/train.py \
22+
--task RobotLab-Isaac-Velocity-Rough-Unitree-A1-v0 ...
23+
24+
# New:
25+
python scripts/train.py --framework isaaclab --rl rsl_rl \
26+
--task RobotLab-Velocity-Rough-Unitree-A1-v0 ...
27+
```
28+
29+
The dispatcher (`scripts/train.py`, `scripts/play.py`) auto-`exec`s under
30+
`.venvs/isaaclab/bin/python`, so you don't need to source-activate first.
31+
32+
## What changed and why
33+
34+
The robot_lab repo now supports both **IsaacLab** and **mjlab** as simulator
35+
backends. A new `robot_lab.framework` adapter package re-exports cfg types,
36+
sensor/event factories, MDP function aliases, and the task-registration entry
37+
point. Business code imports from `robot_lab.framework`; the framework module
38+
chooses the active backend at first import via the `ROBOT_LAB_FRAMEWORK`
39+
environment variable (set by the dispatcher).
40+
41+
For IsaacLab users, this changes very little:
42+
43+
| Aspect | Before | After |
44+
|---|---|---|
45+
| `import isaaclab.managers` etc. in your code | OK | Use `robot_lab.framework` re-exports |
46+
| `gym.register(id="RobotLab-Isaac-Velocity-...")` | OK | `register_task(task_id="RobotLab-Velocity-...", framework_required="isaaclab")` |
47+
| `LocomotionVelocityRoughEnvCfg` subclass + `__post_init__` overrides | OK | Same (kept untouched) |
48+
| `from isaaclab_rl.rsl_rl import RslRlOnPolicyRunnerCfg` in `agents/` | OK | Same — agent files still import directly; `--framework mjlab` skips them via `framework_required` |
49+
50+
If you have a custom task following the existing pattern, the only change you
51+
must make is:
52+
53+
1. Strip `Isaac-` from your task ID(s).
54+
2. Replace the `gym.register(id="...", entry_point="isaaclab.envs:ManagerBasedRLEnv", kwargs=...)`
55+
call with:
56+
57+
```python
58+
from robot_lab.framework import register_task
59+
60+
register_task(
61+
task_id="RobotLab-Velocity-Rough-MyRobot-v0",
62+
env_cfg=f"{__name__}.rough_env_cfg:MyRobotRoughEnvCfg",
63+
agent_cfg_entries={
64+
"rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:MyRobotRoughPPORunnerCfg",
65+
},
66+
framework_required="isaaclab",
67+
)
68+
```
69+
70+
That's it. Your env_cfg subclass, your `__post_init__` overrides, your
71+
agent cfgs — all stay verbatim.
72+
73+
## Why `framework_required="isaaclab"`
74+
75+
`LocomotionVelocityRoughEnvCfg` inherits the IL-style nested
76+
`@configclass` pattern. Under the mjlab backend, the class body's
77+
`ObservationGroupCfg(...)` with no `terms=` argument fails because mjlab's
78+
`ObservationGroupCfg` requires that field. So the subclass is IL-only by
79+
design until someone authors a mjlab-native env cfg from scratch (Phase 7+
80+
of the migration plan).
81+
82+
`framework_required="isaaclab"` makes `register_task` silently skip the
83+
registration when running under `--framework mjlab`. mjlab users see your
84+
task as "not registered" rather than as a class-evaluation crash.
85+
86+
## Adding a NEW task that should work on BOTH backends
87+
88+
This is the new path enabled by the migration. Use the `framework` factories:
89+
90+
```python
91+
from robot_lab.framework import (
92+
EnvCfg,
93+
SceneEntityCfg,
94+
RewardTermCfg,
95+
EventTermCfg,
96+
ObservationTermCfg,
97+
manager_container,
98+
make_observation_group,
99+
make_height_scan_sensor,
100+
make_contact_sensor,
101+
make_rough_terrain,
102+
make_sky_light,
103+
push_robot,
104+
randomize_geom_friction,
105+
register_task,
106+
mdp, # framework-routed mdp aliases
107+
)
108+
109+
110+
def my_robot_rough_env_cfg() -> EnvCfg:
111+
rewards = manager_container({
112+
"track_lin_vel_xy_exp": RewardTermCfg(
113+
func=mdp.track_lin_vel_xy_exp,
114+
weight=3.0,
115+
params={"command_name": "base_velocity", "std": 0.5},
116+
),
117+
})
118+
events = manager_container({
119+
"push_robot": push_robot(velocity_range={"x": (-0.5, 0.5)}),
120+
})
121+
# ... assemble scene, observations, actions, terminations, etc.
122+
cfg = EnvCfg(
123+
scene=...,
124+
rewards=rewards,
125+
events=events,
126+
...,
127+
)
128+
return cfg
129+
130+
131+
register_task(
132+
task_id="RobotLab-Velocity-Rough-MyRobot-v0",
133+
env_cfg=my_robot_rough_env_cfg,
134+
framework_required="any", # registers on both backends
135+
)
136+
```
137+
138+
`framework_required="any"` (the default) registers the task on both
139+
backends. The factory function returns `EnvCfg` which resolves to
140+
`isaaclab.envs.ManagerBasedRLEnvCfg` under IL and
141+
`mjlab.envs.ManagerBasedRlEnvCfg` under mjlab; same with `RewardTermCfg`,
142+
`EventTermCfg`, etc.
143+
144+
## When backend-specific helpers don't exist
145+
146+
Some MDP terms exist only on IsaacLab (e.g. `joint_deviation_l1`,
147+
`applied_torque_limits`, `undesired_contacts`, `feet_air_time_positive_biped`).
148+
149+
Two options:
150+
151+
1. **Mark the whole task IL-only:** `framework_required="isaaclab"`.
152+
2. **Branch on framework at cfg-build time:**
153+
154+
```python
155+
from robot_lab.framework import current_framework_supports
156+
157+
rewards = manager_container({
158+
"track_lin_vel_xy_exp": RewardTermCfg(
159+
func=mdp.track_lin_vel_xy_exp, weight=3.0,
160+
params={"command_name": "base_velocity", "std": 0.5},
161+
),
162+
**({
163+
"joint_deviation_l1": RewardTermCfg(...), # IL-only term
164+
} if current_framework_supports("isaaclab") else {}),
165+
})
166+
```
167+
168+
## File layout reference
169+
170+
- `robot_lab.framework` — public adapter API. Cfg types, factories, MDP aliases.
171+
- `robot_lab.framework.{isaaclab,mjlab}.*` — backend-specific implementations.
172+
- `robot_lab.assets` — robot definitions. New `assets/data/<robot>.py`
173+
carries framework-neutral constants (paths, init pose, gains).
174+
- `robot_lab.assets.builders.{isaaclab,mjlab}``build_articulation_cfg` /
175+
`build_entity_cfg` consume the neutral data and return the right cfg type.
176+
- `robot_lab.tasks.manager_based.locomotion.velocity.velocity_env_cfg`
177+
IL-style nested `@configclass` base; per-robot subclasses still inherit it.
178+
- `scripts/{train,play}.py` — top-level dispatcher.
179+
- `scripts/{isaaclab,mjlab}/...` — backend-specific entry points.
180+
181+
## Frequently surprising things
182+
183+
- `import robot_lab` works under both venvs even when nothing else loads
184+
(`tasks/__init__.py` swallows `ImportError`s during task registration).
185+
- Under the mjlab venv, `import robot_lab.assets.unitree` succeeds but the
186+
module-level `UNITREE_GO2_CFG` resolves to `None` because the IL-side
187+
cfg classes aren't importable. Use the `build_unitree_a1_cfg()`-style
188+
factory instead, which dispatches on `get_framework()`.
189+
- The mjlab CLI is **tyro**, not argparse. Pass `--env.scene.num-envs 4096`,
190+
not `--num_envs 4096`, when running under `--framework mjlab`.
191+
- Old log paths (`logs/rsl_rl/`, `logs/skrl/`, `logs/cusrl/`) are no longer
192+
written. New logs are at `logs/isaaclab/<exp>/<run>` and
193+
`logs/mjlab/<exp>/<run>`.
194+
195+
## See also
196+
197+
- README "Frameworks" section.
198+
- `robot_lab.framework` package source for the public adapter API.

scripts/__init__.py

Whitespace-only changes.

scripts/isaaclab/__init__.py

Whitespace-only changes.

scripts/isaaclab/reinforcement_learning/__init__.py

Whitespace-only changes.

scripts/isaaclab/reinforcement_learning/cusrl/__init__.py

Whitespace-only changes.

scripts/reinforcement_learning/cusrl/play.py renamed to scripts/isaaclab/reinforcement_learning/cusrl/play.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,13 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agen
123123
)
124124

125125
if args_cli.checkpoint is None:
126-
args_cli.checkpoint = os.path.join("logs", "cusrl", agent_cfg.experiment_name)
126+
args_cli.checkpoint = os.path.join("logs", "isaaclab", agent_cfg.experiment_name)
127127
trial = cusrl.Trial(args_cli.checkpoint)
128128
if trial is not None:
129129
log_dir = trial.home
130130
else:
131131
# specify directory for logging videos
132-
log_dir = os.path.join("logs", "cusrl", agent_cfg.experiment_name)
132+
log_dir = os.path.join("logs", "isaaclab", agent_cfg.experiment_name)
133133
log_dir = os.path.abspath(log_dir)
134134

135135
# create isaac environment

scripts/reinforcement_learning/cusrl/train.py renamed to scripts/isaaclab/reinforcement_learning/cusrl/train.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agen
9595
env_cfg.scene.num_envs = int(env_cfg.scene.num_envs / cusrl.utils.distributed.world_size())
9696

9797
# specify directory for logging experiments
98-
log_root_path = os.path.join("logs", "cusrl", agent_cfg.experiment_name)
98+
log_root_path = os.path.join("logs", "isaaclab", agent_cfg.experiment_name)
9999
log_root_path = os.path.abspath(log_root_path)
100100
print(f"[INFO] Logging experiment in directory: {log_root_path}")
101101
# specify directory for logging runs: {time-stamp}_{run_name}
File renamed without changes.

scripts/isaaclab/reinforcement_learning/rsl_rl/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)