feat: dual-framework support (IsaacLab + mjlab)#118
Closed
fan-ziqi wants to merge 3 commits into
Closed
Conversation
This is the foundation of dual-backend (IsaacLab + mjlab) support.
robot_lab.framework: new package that re-exports cfg types, MDP function
aliases, sensor/event/scene/contact factories, RL cfg shim, app launcher,
and register_task on both backends. Business code imports only from this
namespace; the active backend is selected at first import via the
ROBOT_LAB_FRAMEWORK environment variable.
assets/builders: backend-specific helpers that consume neutral robot data
(URDF/MJCF paths, init pose, actuator wiring) and return either an
isaaclab ArticulationCfg or a mjlab EntityCfg. Per-vendor robot factories
dispatch on get_framework() to pick the right builder.
assets/data: framework-neutral robot constants (Unitree A1 used as the
template; pattern can be replicated per-robot as needed).
pyproject.toml: PEP 621 metadata + two mutually exclusive optional
dependencies, [isaaclab] and [mjlab]. The IsaacSim wheel is routed
through the nvidia index. There is intentionally no [all] extra: IsaacSim
forces Python 3.11 while mjlab is native to 3.12.
setup_env.sh (repo root): provisions per-framework venvs at fixed paths
(.venvs/{isaaclab,mjlab}/) with fixed Python versions. Idempotent. The
IsaacLab side also writes a .pth file so the wheel-bundled isaaclab_rl /
isaaclab_tasks / isaaclab_assets subpackages are importable without
git-cloning IsaacLab.
robot_lab.__init__ + tasks.__init__: defensive try/ImportError around isaaclab_tasks-dependent imports so the package is importable under the mjlab venv (where Isaac Sim is absent). IsaacLab behavior is unchanged. assets/<vendor>.py: 10 vendor files now wrap IL-only top-level constants (ArticulationCfg, DCMotorCfg, etc.) in try/ImportError. Constants resolve to None on mjlab; tasks attempting to use them fail at task-instantiation time rather than crashing module import. assets/unitree.py: build_unitree_a1_cfg() factory dispatches on get_framework() and calls the matching builder. velocity_env_cfg.py: imports come from robot_lab.framework. IL-specific runtime types (sim_utils, ArticulationCfg, sensor cfgs, terrain helpers) still import from isaaclab.* but wrapped in try/except. configclass falls back to a no-op identity on mjlab. velocity/mdp/*.py: rewards.py, events.py, observations.py, commands.py swap isaaclab.envs/managers imports for robot_lab.framework re-exports. Articulation / RigidObject / ContactSensor / RayCaster type imports moved to TYPE_CHECKING blocks. Math helpers fall back to mjlab.utils.lab_api. Task __init__.py files (24 velocity quadruped/wheeled/humanoid/others + beyondmimic): gym.register replaced with framework.register_task. Task IDs unified to RobotLab-Velocity-... (Isaac- infix dropped). framework_required ="isaaclab" on every task: existing env_cfg classes inherit IL-style nested @configclass that don't survive class-body evaluation under mjlab. Authoring mjlab-native env cfgs is a follow-up. env_cfg passed as "module:Class" string so the cfg module loads lazily — under mjlab the framework_required gate short-circuits before any IL-only imports fire.
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.
Owner
Author
|
Closing — the implementation took the wrong shape. Reopening fresh with a proper IL-named framework layer instead of try/except scaffolding around isaaclab imports. See follow-up PR. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds dual-backend support: every existing IsaacLab task continues to work, and the project gains a parallel mjlab path through a new adapter layer at
robot_lab.framework. Backend is selected at runtime via--framework {isaaclab,mjlab}to a top-level dispatcher; users provision per-framework venvs via./setup_env.sh.What's in this PR
3 commits:
feat: dual-framework adapter layer + venv automationrobot_lab.frameworkpackage: cfg-type aliases, MDP function aliases, sensor / event / scene / contact factories, RL cfg shim, app launcher,register_task,manager_container,make_observation_group— all with parallel implementations on both backends.assets/builders/{isaaclab,mjlab}.py+assets/data/<robot>.pyfor framework-neutral robot data + per-backend cfg construction.setup_env.sh: provisions.venvs/{isaaclab,mjlab}with fixed Python versions; idempotent. The IsaacLab side drops a.pthso the wheel-bundledisaaclab_rl/_tasks/_assetssubpackages become importable without a separategit clone IsaacLab.pyproject.toml: PEP 621 metadata + mutually exclusive[isaaclab]/[mjlab]extras (no[all]: IsaacSim forces py3.11, mjlab is native to py3.12).refactor: migrate business code to robot_lab.framework adapterrobot_lab/__init__.py+tasks/__init__.py: defensivetry/ImportErrorsoimport robot_labworks under both venvs, even pre-SimApp.assets/<vendor>.py(10 files): wrap IL-only top-level cfg constants intry/ImportErrorso the modules import cleanly under mjlab.assets/unitree.py::build_unitree_a1_cfg(): factory dispatching onget_framework().velocity_env_cfg.py+mdp/{rewards,events,observations,commands}.py: imports come fromrobot_lab.framework. Math helpers fall back to mjlab'slab_api. Type-only imports moved intoTYPE_CHECKINGblocks.__init__.pyfiles + beyondmimic G1: switched fromgym.registertorobot_lab.framework.register_task. Task IDs unified toRobotLab-Velocity-...-v0(Isaac- infix dropped). Every task isframework_required="isaaclab"for now: existing env_cfg subclasses inherit IL-style nested@configclassand don't survive class-body evaluation under mjlab. Authoring mjlab-native env cfgs is a follow-up beyond this PR.feat: scripts dispatcher + framework tests + docsscripts/{train,play}.py: top-level dispatcher with auto-execinto the right venv.scripts/isaaclab/...: existing scripts moved here. Log root unified tologs/isaaclab/<exp>/<run>(waslogs/{rsl_rl,skrl,cusrl}/).scripts/mjlab/reinforcement_learning/rsl_rl/{train,play}.py: thin wrappers overmjlab.scripts.{train,play}. mjlab CLI stays tyro-native (--env.scene.num-envs ...).tests/framework/: detect / spec / container / subprocess-isolated imports / AST-based no-direct-imports audit.docs/migration-from-subclass.md: walks IL users through the small set of changes.README.md: new "Frameworks" section +mjlabcolumn in Version Dependency.Migration impact for existing IsaacLab users
Two breaking changes:
Isaac-infix:RobotLab-Isaac-Velocity-Rough-Unitree-A1-v0→RobotLab-Velocity-Rough-Unitree-A1-v0. Update shell aliases / CI / wandb run names.logs/{rsl_rl,skrl,cusrl}/<exp>→logs/isaaclab/<exp>. Old run links no longer resolve; new runs work normally.Custom env_cfg subclasses,
__post_init__overrides, and agent cfgs all keep working unchanged. Seedocs/migration-from-subclass.mdfor details.Verification
pytest tests/framework/— 23/23 pass under.venvs/mjlab(subprocess-isolated import smoke + AST audit + container/detect/spec unit tests).pytest tests/framework/test_no_framework_imports.py— 40 files scanned, 0 violations of the "business code does not import isaaclab/mjlab directly" invariant.pytest -m smoke tests/integration/— mjlab list-tasks pass; IL side currently skipped on this dev host pending an external IsaacSim 5.1 + Python 3.11 SimApp bootstrap fix (verified to be external — a fresh venv with the same code triggers the same kit-bootstrap error).import robot_lab,import robot_lab.framework, andimport robot_lab.tasks.INFO[robot_lab.framework] task ... requires framework='isaaclab'; skipping under current framework.log lines.Out of scope (follow-ups)
.pthapproach works but is wheel-internal).Why some things look the way they do
framework_required="isaaclab"on every task is intentional, not laziness. mjlab'sObservationGroupCfgrequires aterms=argument that IsaacLab's doesn't take. The existing nested-@configclassLocomotionVelocityRoughEnvCfgcannot evaluate under mjlab. The skip-with-info pattern is the cleanest way to keep IL behavior identical while leaving the door open for mjlab-native cfgs.configclassis intentionally NOT exposed byrobot_lab.framework. It's a dataclass-like decorator on IsaacLab and a no-op identity on mjlab; exposing it would be a footgun (silent semantic divergence). Internal code usesfrom isaaclab.utils import configclassdirectly.[all]extra was deliberately omitted.IsaacSimforces Python 3.11; mjlab is native to 3.12. Combining them in one venv requires picking 3.11 and degrading mjlab. Two-venv discipline is the project's design choice.