diff --git a/.github/actions/_lib/compute-deps-hash/action.yml b/.github/actions/_lib/compute-deps-hash/action.yml index 658db170130a..5a912d9c4261 100644 --- a/.github/actions/_lib/compute-deps-hash/action.yml +++ b/.github/actions/_lib/compute-deps-hash/action.yml @@ -54,6 +54,7 @@ runs: isaaclab.sh environment.yml source/isaaclab/isaaclab/cli + tools/wheel_builder/uv-overrides.txt # Pins the CI pytest deps layered onto the image after build, so a # change to that list must invalidate the deps cache. .github/actions/docker-build/action.yml diff --git a/.github/workflows/kitless-docker.yml b/.github/workflows/kitless-docker.yml index 03cb4a8bb8ff..4a347b78d173 100644 --- a/.github/workflows/kitless-docker.yml +++ b/.github/workflows/kitless-docker.yml @@ -59,6 +59,7 @@ jobs: ^source/isaaclab/isaaclab/test/fixtures/ :: Isolated asset fixture implementation ^source/isaaclab_(physx|newton|ov)/isaaclab_.*/test/fixtures/ :: Backend fixture implementation ^source/isaaclab/test/benchmark/test_asset_suite_runtime_semantics\.py$ :: Kit-less pytest subset + ^tools/wheel_builder/uv-overrides\.txt$ :: Importer dependency overrides ^\.github/workflows/kitless-docker\.yml$ :: This workflow file ^\.github/actions/detect-changes/ :: Change-detection action ^\.github/actions/_lib/compute-deps-hash/ :: Dependency-cache identity diff --git a/.github/workflows/license-check.yaml b/.github/workflows/license-check.yaml index ae538ad9b383..a803db07a97a 100644 --- a/.github/workflows/license-check.yaml +++ b/.github/workflows/license-check.yaml @@ -61,12 +61,8 @@ jobs: ACCEPT_EULA: Y ISAACSIM_ACCEPT_EULA: YES run: | - # ``all`` covers every backend (Isaac Sim included), RL library, and visualizer. - # ``rlinf`` and ``mimic`` are outside ``all``, so name them to keep them scanned. - # No extras conflict, so this is a single resolution -- Isaac Sim no longer needs - # an imperative install after the sync. bash "$GITHUB_WORKSPACE/.github/actions/_lib/with-python-package-retries.sh" \ - uv sync --extra all --extra test --extra rlinf --extra mimic + uv sync --extra all --extra isaacsim --extra test --extra rlinf --extra mimic # ``[tool.uv.pip] prerelease = "allow"`` lets unpinned tools float onto # prereleases. pip-licenses 6.0.0a1 reports an empty License where 5.x reports # ``UNKNOWN``, which license-exceptions.json keys on, and joins multi-license diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index 5fd8830e24a1..16b8ee7a32ea 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -194,10 +194,16 @@ jobs: exit 1 fi + bash "$GITHUB_WORKSPACE/.github/actions/_lib/with-python-package-retries.sh" uv pip install --dry-run "${wheel}[all]" + + bash "$GITHUB_WORKSPACE/.github/actions/_lib/with-python-package-retries.sh" uv pip install \ + --dry-run \ + --overrides "$overrides" \ + "${wheel}[importers]" + bash "$GITHUB_WORKSPACE/.github/actions/_lib/with-python-package-retries.sh" uv pip install \ --dry-run \ --overrides "$overrides" \ --extra-index-url https://pypi.nvidia.com \ --index-strategy unsafe-best-match \ - --prerelease=allow \ - "${wheel}[all]" + "${wheel}[isaacsim]" diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 8c70e227e188..37fc9e02fcd9 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -37,6 +37,7 @@ Guidelines for modifications: * Mayank Mittal * Mike Yan Michelis * Mikhail Yurasov +* Mustafa Haiderbhai * Nikita Rudin * Octi (Zhengyu) Zhang * Ossama Ahmed diff --git a/docker/Dockerfile.kitless b/docker/Dockerfile.kitless index 5ecfac3020c0..c7c69d3459fd 100644 --- a/docker/Dockerfile.kitless +++ b/docker/Dockerfile.kitless @@ -46,17 +46,24 @@ WORKDIR ${ISAACLAB_PATH} COPY pyproject.toml uv.lock VERSION LICENSE LICENSE-mimic README.md ./ COPY source/ source/ COPY isaaclab.sh ./ +COPY tools/wheel_builder/uv-overrides.txt tools/wheel_builder/uv-overrides.txt # Same entry point as Dockerfile.base. The selectors are explicit because a bare # --install excludes `ov` and `visualizer`; both take `[all]` here. The standalone -# importers are core dependencies. The venv pins python3.12 to match the runtime -# stage's libpython3.12, and isaaclab.sh resolves VIRTUAL_ENV first. +# importers are explicit wheel extras, so install them with the resolver overrides +# that preserve Isaac Lab's dependency versions. The venv pins python3.12 to match +# the runtime stage's libpython3.12, and isaaclab.sh resolves VIRTUAL_ENV first. RUN uv venv --python /usr/bin/python3.12 --seed --no-managed-python "${VIRTUAL_ENV}" \ && chmod +x "${ISAACLAB_PATH}/isaaclab.sh" \ && "${ISAACLAB_PATH}/isaaclab.sh" --install newton,rl[all],ov[all],visualizer[all] \ + && uv pip install \ + --overrides "${ISAACLAB_PATH}/tools/wheel_builder/uv-overrides.txt" \ + "isaacsim-asset-isolated>=6.0,<6.1" \ + "tinyobjloader==2.0.0rc13" \ && python -c "import importlib.metadata as m; \ names = {d.metadata['Name'].lower() for d in m.distributions()}; \ assert 'isaacsim' not in names; \ + assert 'isaacsim-asset-isolated' in names; \ assert 'ovphysx' in names; \ assert 'ovrtx' in names; \ assert 'viser' in names; \ diff --git a/docker/test/test_dockerfile_nonroot.py b/docker/test/test_dockerfile_nonroot.py index e019ecdb624b..4188416c3107 100644 --- a/docker/test/test_dockerfile_nonroot.py +++ b/docker/test/test_dockerfile_nonroot.py @@ -8,6 +8,7 @@ from pathlib import Path import pytest +import tomllib REPO_ROOT = Path(__file__).resolve().parents[2] DOCKER_DIR = REPO_ROOT / "docker" @@ -110,8 +111,10 @@ def test_ros2_dockerfile_restores_non_root_runtime_user(): def test_kitless_dockerfile_installs_newton_rl_ov_and_visualizers_without_isaac_sim(): - """The kit-less image installs Newton, both OV runtimes, every Newton viewer, and the RL frameworks.""" + """The kit-less image installs its runtime features and importers without the full Isaac Sim runtime.""" dockerfile_text = (DOCKER_DIR / "Dockerfile.kitless").read_text(encoding="utf-8") + with (REPO_ROOT / "pyproject.toml").open("rb") as file: + importer_requirements = tomllib.load(file)["project"]["optional-dependencies"]["importers"] assert ( "FROM ghcr.io/astral-sh/uv:0.9.25@sha256:13e233d08517abdafac4ead26c16d881cd77504a2c40c38c905cf3a0d70131a6 AS uv" @@ -119,8 +122,12 @@ def test_kitless_dockerfile_installs_newton_rl_ov_and_visualizers_without_isaac_ ) # Installed through the same entry point as Dockerfile.base/Dockerfile.curobo. assert '"${ISAACLAB_PATH}/isaaclab.sh" --install newton,rl[all],ov[all],visualizer[all]' in dockerfile_text + assert "COPY tools/wheel_builder/uv-overrides.txt tools/wheel_builder/uv-overrides.txt" in dockerfile_text + assert '--overrides "${ISAACLAB_PATH}/tools/wheel_builder/uv-overrides.txt"' in dockerfile_text + assert all(f'"{requirement}"' in dockerfile_text for requirement in importer_requirements) assert "COPY isaaclab.sh ./" in dockerfile_text assert "'isaacsim' not in names" in dockerfile_text + assert "'isaacsim-asset-isolated' in names" in dockerfile_text assert "'ovphysx' in names" in dockerfile_text assert "'ovrtx' in names" in dockerfile_text assert "'viser' in names" in dockerfile_text diff --git a/docs/_extensions/isaaclab_docs.py b/docs/_extensions/isaaclab_docs.py index 51d2edfad1bc..0ad216ce5085 100644 --- a/docs/_extensions/isaaclab_docs.py +++ b/docs/_extensions/isaaclab_docs.py @@ -203,8 +203,8 @@ def run(self) -> list[nodes.Node]: return _parse_rst(self, content) -class IsaacLabUvWheelInstall(SphinxDirective): - """Render the uv wheel installation command for the current documentation version.""" +class IsaacLabUvIsaacSimWheelInstall(SphinxDirective): + """Render the Isaac Lab wheel command for the current Isaac Sim version.""" has_content = False @@ -216,10 +216,29 @@ def run(self) -> list[nodes.Node]: content = f"""\ .. code-block:: bash - uv pip install "isaaclab[all]" \\ + uv pip install "isaaclab[isaacsim]" \\ --overrides "{overrides_url}" \\ --extra-index-url https://pypi.nvidia.com \\ - --index-strategy unsafe-best-match --prerelease=allow + --index-strategy unsafe-best-match +""" + return _parse_rst(self, content) + + +class IsaacLabUvImportersWheelInstall(SphinxDirective): + """Render the Isaac Lab standalone importer command with resolver overrides.""" + + has_content = False + + def run(self) -> list[nodes.Node]: + branch = _source_branch(self.config) + overrides_url = ( + f"https://raw.githubusercontent.com/isaac-sim/IsaacLab/{branch}/tools/wheel_builder/uv-overrides.txt" + ) + content = f"""\ +.. code-block:: bash + + uv pip install "isaaclab[importers]" \\ + --overrides "{overrides_url}" """ return _parse_rst(self, content) @@ -323,7 +342,8 @@ def setup(app): app.add_directive("isaaclab-kitless-install-snippet", IsaacLabKitlessInstallSnippet) app.add_directive("isaaclab-quickstart-install", IsaacLabQuickstartInstall) app.add_directive("isaaclab-isaacsim-install", IsaacLabIsaacSimInstall) - app.add_directive("isaaclab-uv-wheel-install", IsaacLabUvWheelInstall) + app.add_directive("isaaclab-uv-isaacsim-wheel-install", IsaacLabUvIsaacSimWheelInstall) + app.add_directive("isaaclab-uv-importers-wheel-install", IsaacLabUvImportersWheelInstall) app.add_directive("isaaclab-torch-install", IsaacLabTorchInstall) app.add_directive("isaaclab-ovrtx-install", IsaacLabOvrtxInstall) return { diff --git a/docs/source/_static/css/environment-browser.css b/docs/source/_static/css/environment-browser.css index 0136fd3501ff..47e73ba9f107 100644 --- a/docs/source/_static/css/environment-browser.css +++ b/docs/source/_static/css/environment-browser.css @@ -53,7 +53,8 @@ html[data-theme="dark"] .environment-browser { white-space: nowrap; } -.environment-mode-switch { +.environment-mode-switch, +.environment-scope-switch { display: inline-flex; flex: 0 0 auto; overflow: hidden; @@ -61,7 +62,8 @@ html[data-theme="dark"] .environment-browser { border-radius: 6px; } -.environment-mode-switch button { +.environment-mode-switch button, +.environment-scope-switch button { min-height: 2.35rem; padding: 0.35rem 0.7rem; border: 0; @@ -72,15 +74,24 @@ html[data-theme="dark"] .environment-browser { font-weight: 600; } -.environment-mode-switch button:last-child { +.environment-mode-switch button:last-child, +.environment-scope-switch button:last-child { border-right: 0; } -.environment-mode-switch button.is-active { +.environment-mode-switch button.is-active, +.environment-scope-switch button.is-active { color: #1f3300; background: #76b900; } +.environment-mode-switch button:disabled, +.environment-scope-switch button:disabled { + color: var(--environment-muted); + cursor: not-allowed; + opacity: 0.65; +} + .environment-inline-field, .environment-selector, .environment-checkpoint-toggle { @@ -102,6 +113,14 @@ html[data-theme="dark"] .environment-browser { flex: 1 1 20rem; } +.environment-task-field > span { + min-width: 4.75rem; +} + +.environment-scope-switch { + margin-left: auto; +} + .environment-inline-field select, .environment-selector select, .environment-task-filter select, @@ -167,8 +186,7 @@ html[data-theme="dark"] .environment-browser { .environment-checkpoint-toggle { justify-content: center; - justify-self: start; - width: calc(100% - 0.35rem); + width: 100%; min-height: 2.4rem; padding: 0.35rem 0.6rem; border: 1px solid var(--environment-border); @@ -187,6 +205,32 @@ html[data-theme="dark"] .environment-browser { accent-color: var(--pst-color-primary); } +.environment-checkpoint-toggle:has(input:disabled) { + color: var(--environment-muted); + background: color-mix(in srgb, var(--pst-color-background) 55%, transparent); + cursor: not-allowed; + opacity: 0.65; +} + +.environment-non-rl-note { + display: flex; + align-items: baseline; + gap: 0.5rem; + margin: 1rem 0 0; + padding: 0.65rem 0.8rem; + border-left: 3px solid var(--pst-color-info); + color: var(--pst-color-text-base); + background: color-mix(in srgb, var(--pst-color-info) 8%, transparent); +} + +.environment-non-rl-note[hidden] { + display: none; +} + +.environment-non-rl-note i { + color: var(--pst-color-info); +} + .environment-command-output { display: grid; grid-template-columns: minmax(0, 1fr) auto; diff --git a/docs/source/_static/css/environment-browser.js b/docs/source/_static/css/environment-browser.js index fd6b9329fe12..4c1c1ec20657 100644 --- a/docs/source/_static/css/environment-browser.js +++ b/docs/source/_static/css/environment-browser.js @@ -9,18 +9,18 @@ "use strict"; const initializeEnvironmentBrowser = () => { - // Generated from the core and contributed rows in source/overview/environments.rst. + // Generated from the task rows in source/overview/environments.rst. // START-AUTO-GENERATED: environment-browser-task-rows const taskRows = [ - ["Isaac-Ant-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/classic/ant.jpg"], - ["Isaac-Ant", "rl_games,rsl_rl,skrl,sb3", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/classic/ant.jpg"], - ["Isaac-Cartpole-Direct", "rl_games,rsl_rl,skrl,sb3", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/classic/cartpole.jpg"], - ["Isaac-Cartpole", "rl_games,rsl_rl,skrl,sb3", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/classic/cartpole.jpg"], + ["Isaac-Ant-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/classic/ant.jpg", true], + ["Isaac-Ant", "rl_games,rsl_rl,skrl,sb3", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/classic/ant.jpg", true], + ["Isaac-Cartpole-Direct", "rl_games,rsl_rl,skrl,sb3", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/classic/cartpole.jpg", true], + ["Isaac-Cartpole", "rl_games,rsl_rl,skrl,sb3", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/classic/cartpole.jpg", true], ["Isaac-Cartpole-Camera-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "isaacsim_rtx,newton_renderer,ovrtx", "albedo,depth,rgb,semantic_segmentation,simple_shading_constant_diffuse,simple_shading_diffuse_mdl,simple_shading_full_mdl", {}, "tasks/classic/cartpole.jpg"], ["Isaac-Cartpole-Camera", "rl_games,rsl_rl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "isaacsim_rtx,newton_renderer,ovrtx", "albedo,depth,resnet18,rgb,semantic_segmentation,simple_shading_constant_diffuse,simple_shading_diffuse_mdl,simple_shading_full_mdl,theia_tiny", {"rl_games_cfg_entry_point": ["albedo", "depth", "rgb", "semantic_segmentation", "simple_shading_constant_diffuse", "simple_shading_diffuse_mdl", "simple_shading_full_mdl"], "rl_games_feature_cfg_entry_point": ["resnet18", "theia_tiny"], "rsl_rl_cfg_entry_point": ["albedo", "depth", "rgb", "semantic_segmentation", "simple_shading_constant_diffuse", "simple_shading_diffuse_mdl", "simple_shading_full_mdl"], "rsl_rl_feature_cfg_entry_point": ["resnet18", "theia_tiny"]}, "tasks/classic/cartpole.jpg"], ["Isaac-Fourbar-Pole-Swingup", "rsl_rl", "newton_kamino", "", "", {}, "tasks/classic/fourbar_pole.jpg"], - ["Isaac-Humanoid-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", {}, "tasks/classic/humanoid.jpg"], - ["Isaac-Humanoid", "rl_games,rsl_rl,skrl,sb3", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", {}, "tasks/classic/humanoid.jpg"], + ["Isaac-Humanoid-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", {}, "tasks/classic/humanoid.jpg", true], + ["Isaac-Humanoid", "rl_games,rsl_rl,skrl,sb3", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", {}, "tasks/classic/humanoid.jpg", true], ["Isaac-Lift-Cable-Franka", "rsl_rl", "newton_mjwarp_vbd_proxy", "", "ik,joint", {}, "tasks/manipulation/franka_lift_cable.jpg"], ["Isaac-Lift-Cable-Franka-Camera", "rsl_rl", "newton_mjwarp_vbd_proxy", "isaacsim_rtx,newton_renderer,ovrtx", "ik,joint", {}, "tasks/manipulation/franka_lift_cable.jpg"], ["Isaac-Lift-Cloth-Franka", "rsl_rl", "isaacsim_physx,newton_mjwarp_vbd_proxy", "", "ik,joint", {}, "tasks/manipulation/franka_lift_cloth.jpg"], @@ -33,10 +33,10 @@ ["Isaac-Open-Drawer-Franka-Direct", "rl_games,rsl_rl,skrl", "", "", "", {}, "tasks/manipulation/franka_open_drawer.jpg"], ["Isaac-Open-Drawer-Franka", "rl_games,rsl_rl,skrl", "", "", "", {}, "tasks/manipulation/franka_open_drawer.jpg"], ["Isaac-Pendulum-MARL-Direct", "rl_games,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", ""], - ["Isaac-Reach-Franka", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "diffik,diffik_abs,joint_pos,newton_ik", {}, "tasks/manipulation/franka_reach.jpg"], + ["Isaac-Reach-Franka", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "diffik,diffik_abs,joint_pos,newton_ik", {}, "tasks/manipulation/franka_reach.jpg", true], ["Isaac-Reach-Franka-OSC", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "diffik_abs", {}, "tasks/manipulation/franka_reach.jpg"], - ["Isaac-Reach-UR10", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", {}, "tasks/manipulation/ur10_reach.jpg"], - ["Isaac-Reorient-Cube-Allegro-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", {}, "tasks/manipulation/allegro_cube.jpg"], + ["Isaac-Reach-UR10", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", {}, "tasks/manipulation/ur10_reach.jpg", true], + ["Isaac-Reorient-Cube-Allegro-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", {}, "tasks/manipulation/allegro_cube.jpg", true], ["Isaac-Reorient-Cube-Allegro", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "randomized,reset_only", {}, "tasks/manipulation/allegro_cube.jpg"], ["Isaac-Reorient-Cube-Shadow-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", {}, "tasks/manipulation/shadow_cube.jpg"], ["Isaac-Reorient-Cube-Shadow", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "asymmetric,randomized"], @@ -47,11 +47,11 @@ ["Isaac-Reorient-KukaAllegro-Camera", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "isaacsim_rtx,newton_renderer,ovrtx", "albedo128,albedo256,albedo64,cube,depth128,depth256,depth64,duo_camera,raycaster_depth128,raycaster_depth256,raycaster_depth64,rgb128,rgb256,rgb64,semantic_segmentation128,semantic_segmentation256,semantic_segmentation64,shapes,simple_shading_constant_diffuse128,simple_shading_constant_diffuse256,simple_shading_constant_diffuse64,simple_shading_diffuse_mdl128,simple_shading_diffuse_mdl256,simple_shading_diffuse_mdl64,simple_shading_full_mdl128,simple_shading_full_mdl256,simple_shading_full_mdl64,single_camera", {}, "tasks/manipulation/kuka_allegro_reorient.jpg"], ["Isaac-Shadow-Handover-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", {}, "tasks/manipulation/shadow_hand_over.jpg"], ["Isaac-Shadow-Handover", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "randomized"], - ["Isaac-Velocity-Flat-AnymalD", "rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/anymal_d_flat.jpg"], - ["Isaac-Velocity-Flat-Cassie", "rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", ""], - ["Isaac-Velocity-Flat-G1", "rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/g1_flat.jpg"], - ["Isaac-Velocity-Flat-H1", "rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/h1_flat.jpg"], - ["Isaac-Velocity-Flat-UnitreeGo2", "rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/go2_flat.jpg"], + ["Isaac-Velocity-Flat-AnymalD", "rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/anymal_d_flat.jpg", true], + ["Isaac-Velocity-Flat-Cassie", "rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "", true], + ["Isaac-Velocity-Flat-G1", "rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/g1_flat.jpg", true], + ["Isaac-Velocity-Flat-H1", "rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/h1_flat.jpg", true], + ["Isaac-Velocity-Flat-UnitreeGo2", "rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/go2_flat.jpg", true], ["Isaac-Velocity-Rough-AnymalD", "rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/anymal_d_rough.jpg"], ["Isaac-Velocity-Rough-Cassie", "rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/anymal_d_rough.jpg"], ["Isaac-Velocity-Rough-G1", "rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/g1_rough.jpg"], @@ -135,13 +135,13 @@ ["IsaacContrib-TrackPositionNoObstacles-ARL-Robot-1", "rl_games,rsl_rl,skrl", "", "", "", {}, "tasks/drone_arl/arl_robot_1_track_position_state_based.jpg"], ["IsaacContrib-Tracking-LocoManip-Digit", "rsl_rl", "isaacsim_physx", "", "", {}, "tasks/locomotion/agility_digit_loco_manip.jpg"], ["IsaacContrib-UR10-Particle-Push", "rsl_rl", "", "", "", {}, "tasks/manipulation/ur10_particle_push.jpg"], - ["IsaacContrib-Velocity-Flat-AnymalB", "rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/anymal_b_flat.jpg"], + ["IsaacContrib-Velocity-Flat-AnymalB", "rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/anymal_b_flat.jpg", true], ["IsaacContrib-Velocity-Flat-AnymalC-Direct", "rl_games,rsl_rl,skrl", "", "", "", {}, "tasks/locomotion/anymal_c_flat.jpg"], - ["IsaacContrib-Velocity-Flat-AnymalC", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/anymal_c_flat.jpg"], + ["IsaacContrib-Velocity-Flat-AnymalC", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/anymal_c_flat.jpg", true], ["IsaacContrib-Velocity-Flat-Digit", "rsl_rl", "isaacsim_physx", "", "", {}, "tasks/locomotion/agility_digit_flat.jpg"], ["IsaacContrib-Velocity-Flat-Spot", "rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp", "", "", {}, "tasks/locomotion/spot_flat.jpg"], - ["IsaacContrib-Velocity-Flat-UnitreeA1", "rsl_rl,skrl,sb3", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/a1_flat.jpg"], - ["IsaacContrib-Velocity-Flat-UnitreeGo1", "rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/go1_flat.jpg"], + ["IsaacContrib-Velocity-Flat-UnitreeA1", "rsl_rl,skrl,sb3", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/a1_flat.jpg", true], + ["IsaacContrib-Velocity-Flat-UnitreeGo1", "rsl_rl,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/go1_flat.jpg", true], ["IsaacContrib-Velocity-Rough-AnymalB", "rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/anymal_b_rough.jpg"], ["IsaacContrib-Velocity-Rough-AnymalC-Direct", "rl_games,rsl_rl,skrl", "", "", "", {}, "tasks/locomotion/anymal_c_rough.jpg"], ["IsaacContrib-Velocity-Rough-AnymalC", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", {}, "tasks/locomotion/anymal_c_rough.jpg"], @@ -152,7 +152,9 @@ // END-AUTO-GENERATED: environment-browser-task-rows const splitValues = (value) => value ? value.split(",") : []; - const tasks = taskRows.map(([task, rl, physics, renderer, presets, agentPresetCompatibility = {}, previewImage = ""]) => ({ + const tasks = taskRows.map(([ + task, rl, physics, renderer, presets, agentPresetCompatibility = {}, previewImage = "", supportsWarpFrontend = false, + ]) => ({ task, scope: task.startsWith("IsaacContrib-") ? "contrib" : "core", rl: splitValues(rl), @@ -161,6 +163,7 @@ presets: splitValues(presets), agentPresetCompatibility, previewImage, + supportsWarpFrontend, })); const builder = document.querySelector("[data-environment-browser]"); @@ -175,9 +178,11 @@ [...builder.querySelectorAll("[data-environment-field]")].map((field) => [field.dataset.environmentField, field]) ); const commandOutput = builder.querySelector("[data-command-output]"); + const nonRlNote = builder.querySelector("[data-non-rl-note]"); const copyButton = builder.querySelector("[data-copy-command]"); const copyStatus = builder.querySelector("[data-copy-status]"); const modeButtons = [...builder.querySelectorAll("[data-command-mode]")]; + const scopeButtons = [...builder.querySelectorAll("[data-task-scope]")]; const taskList = taskBrowser.querySelector("[data-task-list]"); const taskSearch = taskBrowser.querySelector("[data-task-search]"); const taskCategory = taskBrowser.querySelector("[data-task-category]"); @@ -185,6 +190,7 @@ const taskEmpty = taskBrowser.querySelector("[data-task-empty]"); const state = { mode: "train", + scope: "core", task: "Isaac-Cartpole", benchmarkWorkload: "runtime", }; @@ -211,6 +217,10 @@ const selectedTask = () => tasks.find((task) => task.task === state.task) || tasks[0]; + const tasksForScope = (scope = state.scope) => tasks.filter((task) => ( + scope === "warp" ? task.supportsWarpFrontend : task.scope === scope + )); + const previewImageFor = (task) => { if (task.previewImage) { return task.previewImage; @@ -263,14 +273,23 @@ const updateTaskControls = () => { const task = selectedTask(); populateSelect(fields.rl, task.rl, [fields.rl.value, "rsl_rl", "rl_games", "skrl", "sb3"]); - populateSelect(fields.physics, task.physics, [fields.physics.value, "newton_mjwarp", "isaacsim_physx", "ovphysx", "newton_kamino"]); + const physics = state.scope === "warp" ? ["newton_mjwarp"] : task.physics; + populateSelect(fields.physics, physics, [fields.physics.value, "newton_mjwarp", "isaacsim_physx", "ovphysx", "newton_kamino"]); const preferredRenderer = fields.physics.value.startsWith("newton") ? "newton_renderer" : "isaacsim_rtx"; populateSelect(fields.renderer, task.renderer, [fields.renderer.value, preferredRenderer, "ovrtx"]); populateSelect(fields.presets, task.presets, [fields.presets.value, "joint", "ik", "rgb", "cube", "single_camera"]); }; const updateModeControls = () => { - const supportsPretrainedCheckpoint = state.mode === "play"; + const supportsRl = selectedTask().rl.length > 0; + for (const modeButton of modeButtons) { + modeButton.disabled = !supportsRl; + const isActive = supportsRl && modeButton.dataset.commandMode === state.mode; + modeButton.classList.toggle("is-active", isActive); + modeButton.setAttribute("aria-pressed", String(isActive)); + } + nonRlNote.hidden = supportsRl; + const supportsPretrainedCheckpoint = supportsRl && state.scope === "core"; fields.checkpoint.disabled = !supportsPretrainedCheckpoint; if (!supportsPretrainedCheckpoint) { fields.checkpoint.checked = false; @@ -297,19 +316,20 @@ for (const extra of extras) { parts.push("--extra", extra); } - parts.push("isaaclab", state.mode); - if (fields.rl.value) { + const task = selectedTask(); + const supportsRl = task.rl.length > 0; + parts.push("isaaclab", supportsRl ? state.mode : "zero_agent"); + if (supportsRl && fields.rl.value) { parts.push("--rl_library", fields.rl.value); } parts.push("--task", state.task); - const task = selectedTask(); const selectedAgent = Object.entries(task.agentPresetCompatibility).find(([agent, presets]) => ( agent.startsWith(`${fields.rl.value}_`) && presets.includes(fields.presets.value) ))?.[0]; if (selectedAgent && selectedAgent !== `${fields.rl.value}_cfg_entry_point`) { parts.push("--agent", selectedAgent); } - if (state.task.includes("-Warp")) { + if (state.scope === "warp") { parts.push("--frontend", "warp"); } for (const selector of ["physics", "renderer", "presets"]) { @@ -317,7 +337,7 @@ parts.push(`${selector}=${fields[selector].value}`); } } - if (fields.checkpoint.checked) { + if (supportsRl && fields.checkpoint.checked) { parts.push("--checkpoint", "pretrained"); } return parts.join(" "); @@ -347,8 +367,11 @@ previewImage.hidden = false; } preview.querySelector("[data-preview-task]").textContent = state.task; - preview.querySelector("[data-preview-mode]").textContent = state.mode === "train" ? "Train" : "Play"; - preview.querySelector("[data-preview-rl]").textContent = fields.rl.value || "Default"; + const supportsRl = selectedTask().rl.length > 0; + preview.querySelector("[data-preview-mode]").textContent = supportsRl + ? (state.mode === "train" ? "Train" : "Play") + : "Zero agent"; + preview.querySelector("[data-preview-rl]").textContent = supportsRl ? fields.rl.value : "Not supported"; preview.querySelector("[data-preview-physics]").textContent = fields.physics.value || "Default"; preview.querySelector("[data-preview-renderer]").textContent = fields.renderer.value || "Default"; preview.querySelector("[data-preview-presets]").textContent = fields.presets.value || "Default"; @@ -369,6 +392,7 @@ const updateSelection = () => { fields.task.value = state.task; updateTaskControls(); + updateModeControls(); commandOutput.textContent = currentCommand(); updatePreview(); for (const row of taskList.querySelectorAll("[data-task-name]")) { @@ -381,10 +405,10 @@ const renderTasks = () => { const query = taskSearch.value.trim().toLowerCase(); const category = taskCategory.value; - const visibleTasks = tasks.filter((task) => { + const visibleTasks = tasksForScope().filter((task) => { const matchesQuery = task.task.toLowerCase().includes(query); const matchesCategory = category === "all" - || (category === "contrib" ? task.scope === "contrib" : task.scope === "core" && categoryFor(task.task) === category); + || categoryFor(task.task) === category; return matchesQuery && matchesCategory; }); taskList.replaceChildren(...visibleTasks.map((task) => { @@ -399,7 +423,10 @@ button.querySelector(".environment-task-name").textContent = task.task; const meta = button.querySelector(".environment-task-meta"); const workflow = task.task.includes("Direct") ? "Direct" : "Manager based"; - meta.replaceChildren(...[workflow, `${task.rl.length} RL ${task.rl.length === 1 ? "library" : "libraries"}`].map((label) => { + const rlSupport = task.rl.length + ? `${task.rl.length} RL ${task.rl.length === 1 ? "library" : "libraries"}` + : "RL not supported"; + meta.replaceChildren(...[workflow, rlSupport].map((label) => { const badge = document.createElement("span"); badge.textContent = label; return badge; @@ -416,7 +443,7 @@ }; const initializeTasks = () => { - fields.task.replaceChildren(...tasks.map((task) => new Option(task.task, task.task))); + fields.task.replaceChildren(...tasksForScope().map((task) => new Option(task.task, task.task))); fields.task.value = state.task; renderTasks(); updateSelection(); @@ -645,6 +672,9 @@ for (const button of modeButtons) { button.addEventListener("click", () => { state.mode = button.dataset.commandMode; + if (state.mode === "train") { + fields.checkpoint.checked = false; + } for (const modeButton of modeButtons) { const isActive = modeButton === button; modeButton.classList.toggle("is-active", isActive); @@ -655,6 +685,42 @@ updatePreview(); }); } + for (const button of scopeButtons) { + button.disabled = tasksForScope(button.dataset.taskScope).length === 0; + button.addEventListener("click", () => { + const scope = button.dataset.taskScope; + const scopedTasks = tasksForScope(scope); + if (scopedTasks.length === 0) { + return; + } + state.scope = scope; + for (const scopeButton of scopeButtons) { + const isActive = scopeButton === button; + scopeButton.classList.toggle("is-active", isActive); + scopeButton.setAttribute("aria-pressed", String(isActive)); + } + if (!scopedTasks.some((task) => task.task === state.task)) { + state.task = scopedTasks[0].task; + } + fields.task.replaceChildren(...scopedTasks.map((task) => new Option(task.task, task.task))); + updateModeControls(); + renderTasks(); + updateSelection(); + }); + } + fields.checkpoint.addEventListener("change", () => { + if (!fields.checkpoint.checked || state.mode === "play") { + return; + } + state.mode = "play"; + for (const modeButton of modeButtons) { + const isActive = modeButton.dataset.commandMode === "play"; + modeButton.classList.toggle("is-active", isActive); + modeButton.setAttribute("aria-pressed", String(isActive)); + } + commandOutput.textContent = currentCommand(); + updatePreview(); + }); for (const button of benchmarks?.querySelectorAll("[data-benchmark-workload]") || []) { button.addEventListener("click", () => { state.benchmarkWorkload = button.dataset.benchmarkWorkload; diff --git a/docs/source/how-to/import_new_asset.rst b/docs/source/how-to/import_new_asset.rst index 111497dade1a..bcfa92ec12ac 100644 --- a/docs/source/how-to/import_new_asset.rst +++ b/docs/source/how-to/import_new_asset.rst @@ -44,7 +44,8 @@ Standalone URDF/MJCF importers ------------------------------ The URDF and MJCF converter scripts run without Isaac Sim. The standalone -``isaacsim-asset-isolated`` wheel is a base dependency, so no extra install step is needed. +importers are optional; install them with the ``isaaclab[importers]`` command in +:ref:`installation-importers-extra` before running these scripts. Optionally pass ``--viz newton`` (or ``rerun`` / ``viser``) to preview the converted asset in a kit-less Isaac Lab visualizer: diff --git a/docs/source/setup/environments.rst b/docs/source/setup/environments.rst index 8dab8c2b331e..1ca42117394c 100644 --- a/docs/source/setup/environments.rst +++ b/docs/source/setup/environments.rst @@ -31,6 +31,11 @@ Command Builder --task +
+ + + +
+
@@ -135,7 +145,6 @@ Available Tasks - diff --git a/docs/source/setup/installation/include/pip_extras_note.rst b/docs/source/setup/installation/include/pip_extras_note.rst index 2623a0180bf8..e97ef912b9bc 100644 --- a/docs/source/setup/installation/include/pip_extras_note.rst +++ b/docs/source/setup/installation/include/pip_extras_note.rst @@ -1,5 +1,6 @@ .. note:: - The ``isaaclab`` pip wheel bundles all Isaac Lab extensions. Install with - ``[all]`` for the full workflow: it carries Isaac Sim, both OV backends, every RL - library, and every visualizer. + The ``isaaclab`` pip wheel bundles all Isaac Lab extensions. The ``[all]`` extra is the + curated ``ov``, ``rl-games``, ``sb3``, ``skrl``, ``rsl-rl``, ``rerun``, and ``viser`` list. + It does not include Isaac Sim or the standalone importers; request ``[isaacsim]`` or + ``[importers]`` separately. diff --git a/docs/source/setup/installation/include/src_clone_isaaclab.rst b/docs/source/setup/installation/include/src_clone_isaaclab.rst index ef8a0e6a25bb..794ad2be60c1 100644 --- a/docs/source/setup/installation/include/src_clone_isaaclab.rst +++ b/docs/source/setup/installation/include/src_clone_isaaclab.rst @@ -24,7 +24,7 @@ We provide helper executables at the repository root — ``./isaaclab.sh`` (Linu ./isaaclab.sh --help - usage: isaaclab.sh [-h] [-i [INSTALL]] [-f] [-p ...] [-s ...] [-t ...] [-o ...] [-v] [-d] [-n ...] [-c [CONDA]] [-u [UV]] + usage: isaaclab.sh [-h] [-i [INSTALL]] [-f] [-p ...] [-s ...] [-t ...] [-o ...] [-v] [-d] [-n ...] [-c [CONDA]] [-u [UV]] [--isaacsim_source PATH] Isaac Lab CLI @@ -55,6 +55,9 @@ We provide helper executables at the repository root — ``./isaaclab.sh`` (Linu -c [CONDA], --conda [CONDA] Create a new conda environment for Isaac Lab. Default name is 'env_isaaclab'. -u [UV], --uv [UV] Create a new uv environment for Isaac Lab. Default name is 'env_isaaclab'. + --isaacsim_source PATH + Incrementally build the Isaac Sim source checkout at PATH and link its live release + tree as '_isaac_sim'. Python commands keep using the active uv environment. .. tab-item:: :icon:`fa-brands fa-windows` Windows :sync: windows @@ -63,7 +66,7 @@ We provide helper executables at the repository root — ``./isaaclab.sh`` (Linu isaaclab.bat --help - usage: isaaclab.bat [-h] [-i [INSTALL]] [-f] [-p ...] [-s ...] [-t ...] [-o ...] [-v] [-d] [-n ...] [-c [CONDA]] [-u [UV]] + usage: isaaclab.bat [-h] [-i [INSTALL]] [-f] [-p ...] [-s ...] [-t ...] [-o ...] [-v] [-d] [-n ...] [-c [CONDA]] [-u [UV]] [--isaacsim_source PATH] Isaac Lab CLI @@ -94,3 +97,6 @@ We provide helper executables at the repository root — ``./isaaclab.sh`` (Linu -c [CONDA], --conda [CONDA] Create a new conda environment for Isaac Lab. Default name is 'env_isaaclab'. -u [UV], --uv [UV] Create a new uv environment for Isaac Lab. Default name is 'env_isaaclab'. + --isaacsim_source PATH + Incrementally build the Isaac Sim source checkout at PATH and link its live release + tree as '_isaac_sim'. Python commands keep using the active uv environment. diff --git a/docs/source/setup/installation/index.rst b/docs/source/setup/installation/index.rst index 0249906c4c71..8029915584b6 100644 --- a/docs/source/setup/installation/index.rst +++ b/docs/source/setup/installation/index.rst @@ -225,15 +225,15 @@ Install ``uv``, clone Isaac Lab, and start a workflow: option includes the selected optional integration in the command's environment. Place it before ``isaaclab``; for example, ``--extra ov`` installs both ovphysx and ovrtx backends. Pass a comma-separated list or repeat ``--extra``. No extras conflict, so -any combination resolves into one environment. The ``--extra all`` shortcut installs a -curated set of backends, RL libraries, and visualizers. It does not include the specialized -extras ``rlinf``, ``mimic``, ``teleop``, ``tetrahedralization``, ``video``, and ``leapp``; -request them by name: +any combination resolves into one environment. The ``--extra all`` shortcut installs the +curated ``ov``, ``rl-games``, ``sb3``, ``skrl``, ``rsl-rl``, ``rerun``, and ``viser`` extras. +It does not include Isaac Sim or the specialized ``rlinf``, ``mimic``, ``teleop``, +``tetrahedralization``, ``video``, and ``leapp`` extras; request them by name: .. code-block:: bash uv run --extra all isaaclab train --rl_library rsl_rl \ - --task Isaac-Cartpole-Direct physics=isaacsim_physx + --task Isaac-Cartpole-Direct physics=ovphysx See :ref:`installation-optional-extras` for the available extras. @@ -574,9 +574,10 @@ or temporary work. Optional extras ~~~~~~~~~~~~~~~ -Add extras to the package requirement when your project needs them. For a standalone environment, -use ``uv pip install "isaaclab[]"``; for a uv project, use -``uv add "isaaclab[]"``. +Add extras only when your project needs them. Most extras work with +``uv pip install "isaaclab[]"`` in a standalone environment or +``uv add "isaaclab[]"`` in a uv project. The ``importers`` and ``isaacsim`` extras +have dedicated commands below. .. list-table:: :header-rows: 1 @@ -601,20 +602,45 @@ use ``uv pip install "isaaclab[]"``; for a uv project, use - Mesh tetrahedralization / video recording. * - ``leapp`` - LEAP model export support. + * - ``importers`` + - Standalone URDF and MJCF conversion without Isaac Sim. * - ``all`` - - A curated set of backends, RL libraries, and visualizers: ``isaacsim``, ``ov``, ``rl-games``, - ``sb3``, ``skrl``, ``rsl-rl``, ``rerun``, and ``viser``. + - The curated ``ov``, ``rl-games``, ``sb3``, ``skrl``, ``rsl-rl``, ``rerun``, and ``viser`` + extras. Isaac Sim is not included. * - ``test`` - Developer test and documentation tooling. -Extras can be combined freely: none of them conflict, so any set of extras -- including -the Isaac Sim and OV backend stacks together -- resolves into a single environment. -Use ``all`` to install the curated set of backends, RL libraries, and visualizers listed -above with one flag. The specialized extras (``rlinf``, ``mimic``, ``teleop``, -``tetrahedralization``, ``video``, ``leapp``) and the developer ``test`` tooling are not -part of ``all``; request them by name. +Use ``all`` for the curated list above. Isaac Sim, standalone importers, specialized extras +(``rlinf``, ``mimic``, ``teleop``, ``tetrahedralization``, ``video``, ``leapp``), and the +developer ``test`` tooling remain opt-in. -.. isaaclab-uv-wheel-install:: +.. _installation-importers-extra: + +Installing the ``importers`` extra +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Install this extra to convert URDF and MJCF files without Isaac Sim. + +.. warning:: + + Use the full command below. Without the overrides, the importer extra can downgrade packages + used by the base Isaac Lab install. The overrides keep Isaac Lab's tested versions. + +.. isaaclab-uv-importers-wheel-install:: + +Installing the ``isaacsim`` extra +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Isaac Sim 6.0 pins dependencies that conflict with Isaac Lab. Install the ``isaacsim`` extra with +the tested overrides: + +.. isaaclab-uv-isaacsim-wheel-install:: + +Add other extras inside the brackets when needed; for example, use +``isaaclab[isaacsim,all]`` to include the curated ``all`` list. + +Installing CUDA-enabled PyTorch +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Install the CUDA-enabled PyTorch build appropriate for your system architecture: @@ -777,98 +803,132 @@ On Windows, enable `long-path support `__ before building. +Choose how to connect the Isaac Sim source build to Isaac Lab: + .. tab-set:: - :sync-group: installation-platform + :sync-group: isaacsim-source-installation-method - .. tab-item:: :icon:`fa-brands fa-linux` Linux (x86_64) - :sync: linux-x86_64 + .. tab-item:: uv (Recommended) + :sync: uv - .. code-block:: bash + Clone Isaac Sim next to the Isaac Lab checkout. From the Isaac Lab root, run the source-build + command. It incrementally builds Isaac Sim and links the live release tree as ``_isaac_sim``: - git clone https://github.com/isaac-sim/IsaacSim.git - cd IsaacSim - ./build.sh - export ISAACSIM_PATH="${PWD}/_build/linux-x86_64/release" - export ISAACSIM_PYTHON_EXE="${ISAACSIM_PATH}/python.sh" - ${ISAACSIM_PATH}/isaac-sim.sh - ${ISAACSIM_PYTHON_EXE} -c "print('Isaac Sim configuration is now complete.')" - ${ISAACSIM_PYTHON_EXE} ${ISAACSIM_PATH}/standalone_examples/api/isaacsim.core.experimental.api/add_cubes.py + .. code-block:: text - .. tab-item:: :icon:`fa-brands fa-linux` Linux (aarch64) - :sync: linux-aarch64 + git clone https://github.com/isaac-sim/IsaacSim.git ../IsaacSim + uv run isaaclab --isaacsim_source ../IsaacSim - .. code-block:: bash + Isaac Lab runs the active ``uv`` environment through Isaac Sim's generated Python launcher. + This loads Kit and extensions directly from the source build without creating wheels or + changing ``pyproject.toml`` and ``uv.lock``. Run Isaac Lab against the source build with: - git clone https://github.com/isaac-sim/IsaacSim.git - cd IsaacSim - ./build.sh - export ISAACSIM_PATH="${PWD}/_build/linux-aarch64/release" - export ISAACSIM_PYTHON_EXE="${ISAACSIM_PATH}/python.sh" - ${ISAACSIM_PATH}/isaac-sim.sh - ${ISAACSIM_PYTHON_EXE} -c "print('Isaac Sim configuration is now complete.')" - ${ISAACSIM_PYTHON_EXE} ${ISAACSIM_PATH}/standalone_examples/api/isaacsim.core.experimental.api/add_cubes.py + .. code-block:: text - .. tab-item:: :icon:`fa-brands fa-windows` Windows (x86_64) - :sync: windows-x86_64 + uv run isaaclab train --rl_library rsl_rl --task Isaac-Cartpole-Direct physics=isaacsim_physx - .. code-block:: batch + After changing Isaac Sim source, run the same ``--isaacsim_source`` command again. The native + build is incremental, and the link continues to expose the updated build immediately; no + wheel packaging or dependency resolution step is required. - git clone https://github.com/isaac-sim/IsaacSim.git - cd IsaacSim - build.bat - set ISAACSIM_PATH="%cd%\_build\windows-x86_64\release" - set ISAACSIM_PYTHON_EXE="%ISAACSIM_PATH:"=%\python.bat" - %ISAACSIM_PATH%\isaac-sim.bat - %ISAACSIM_PYTHON_EXE% -c "print('Isaac Sim configuration is now complete.')" - %ISAACSIM_PYTHON_EXE% %ISAACSIM_PATH%\standalone_examples\api\isaacsim.core.experimental.api\add_cubes.py + .. tab-item:: isaaclab.sh / isaaclab.bat + :sync: isaaclab-script -Return to the workspace containing the ``IsaacSim`` checkout, then clone Isaac Lab, link it to the -source build, install, and verify: + Build and verify Isaac Sim for your platform: -.. code-block:: text + .. tab-set:: + :sync-group: installation-platform - cd .. + .. tab-item:: :icon:`fa-brands fa-linux` Linux (x86_64) + :sync: linux-x86_64 -.. isaaclab-clone-commands:: + .. code-block:: bash -.. tab-set:: - :sync-group: installation-platform + git clone https://github.com/isaac-sim/IsaacSim.git + cd IsaacSim + ./build.sh + export ISAACSIM_PATH="${PWD}/_build/linux-x86_64/release" + export ISAACSIM_PYTHON_EXE="${ISAACSIM_PATH}/python.sh" + ${ISAACSIM_PATH}/isaac-sim.sh + ${ISAACSIM_PYTHON_EXE} -c "print('Isaac Sim configuration is now complete.')" + ${ISAACSIM_PYTHON_EXE} ${ISAACSIM_PATH}/standalone_examples/api/isaacsim.core.experimental.api/add_cubes.py - .. tab-item:: :icon:`fa-brands fa-linux` Linux (x86_64) - :sync: linux-x86_64 + .. tab-item:: :icon:`fa-brands fa-linux` Linux (aarch64) + :sync: linux-aarch64 - .. code-block:: bash + .. code-block:: bash - cd IsaacLab - ln -s ${ISAACSIM_PATH} _isaac_sim - sudo apt install cmake build-essential - ./isaaclab.sh -i - ./isaaclab.sh -p scripts/tutorials/00_sim/create_empty.py --viz kit + git clone https://github.com/isaac-sim/IsaacSim.git + cd IsaacSim + ./build.sh + export ISAACSIM_PATH="${PWD}/_build/linux-aarch64/release" + export ISAACSIM_PYTHON_EXE="${ISAACSIM_PATH}/python.sh" + ${ISAACSIM_PATH}/isaac-sim.sh + ${ISAACSIM_PYTHON_EXE} -c "print('Isaac Sim configuration is now complete.')" + ${ISAACSIM_PYTHON_EXE} ${ISAACSIM_PATH}/standalone_examples/api/isaacsim.core.experimental.api/add_cubes.py - .. tab-item:: :icon:`fa-brands fa-linux` Linux (aarch64) - :sync: linux-aarch64 + .. tab-item:: :icon:`fa-brands fa-windows` Windows (x86_64) + :sync: windows-x86_64 - .. code-block:: bash + .. code-block:: batch - cd IsaacLab - ln -s ${ISAACSIM_PATH} _isaac_sim - sudo apt install cmake build-essential python3.12-dev libgl1-mesa-dev libx11-dev \ - libxcursor-dev libxi-dev libxinerama-dev libxrandr-dev - ./isaaclab.sh -i - ./isaaclab.sh -p scripts/tutorials/00_sim/create_empty.py --viz kit + git clone https://github.com/isaac-sim/IsaacSim.git + cd IsaacSim + build.bat + set ISAACSIM_PATH="%cd%\_build\windows-x86_64\release" + set ISAACSIM_PYTHON_EXE="%ISAACSIM_PATH:"=%\python.bat" + %ISAACSIM_PATH%\isaac-sim.bat + %ISAACSIM_PYTHON_EXE% -c "print('Isaac Sim configuration is now complete.')" + %ISAACSIM_PYTHON_EXE% %ISAACSIM_PATH%\standalone_examples\api\isaacsim.core.experimental.api\add_cubes.py - .. tab-item:: :icon:`fa-brands fa-windows` Windows (x86_64) - :sync: windows-x86_64 + Return to the workspace containing the ``IsaacSim`` checkout, then clone Isaac Lab: - .. code-block:: batch + .. code-block:: text - cd IsaacLab - mklink /D _isaac_sim %ISAACSIM_PATH% - isaaclab.bat -i - isaaclab.bat -p scripts\tutorials\00_sim\create_empty.py --viz kit + cd .. + + .. isaaclab-clone-commands:: + + Link Isaac Lab to the source build, install, and verify: + + .. tab-set:: + :sync-group: installation-platform + + .. tab-item:: :icon:`fa-brands fa-linux` Linux (x86_64) + :sync: linux-x86_64 + + .. code-block:: bash + + cd IsaacLab + ln -s ${ISAACSIM_PATH} _isaac_sim + sudo apt install cmake build-essential + ./isaaclab.sh -i + ./isaaclab.sh -p scripts/tutorials/00_sim/create_empty.py --viz kit + + .. tab-item:: :icon:`fa-brands fa-linux` Linux (aarch64) + :sync: linux-aarch64 + + .. code-block:: bash + + cd IsaacLab + ln -s ${ISAACSIM_PATH} _isaac_sim + sudo apt install cmake build-essential python3.12-dev libgl1-mesa-dev libx11-dev \ + libxcursor-dev libxi-dev libxinerama-dev libxrandr-dev + ./isaaclab.sh -i + ./isaaclab.sh -p scripts/tutorials/00_sim/create_empty.py --viz kit + + .. tab-item:: :icon:`fa-brands fa-windows` Windows (x86_64) + :sync: windows-x86_64 + + .. code-block:: batch + + cd IsaacLab + mklink /D _isaac_sim %ISAACSIM_PATH% + isaaclab.bat -i + isaaclab.bat -p scripts\tutorials\00_sim\create_empty.py --viz kit -The tutorial command should open a black simulator viewport. Use the binary-installation -troubleshooting links above if the source build does not launch. + The tutorial command should open a black simulator viewport. Use the binary-installation + troubleshooting links above if the source build does not launch. .. _installation-method-container: diff --git a/docs/source/setup/quickstart.rst b/docs/source/setup/quickstart.rst index 3713595a3602..285163920317 100644 --- a/docs/source/setup/quickstart.rst +++ b/docs/source/setup/quickstart.rst @@ -49,9 +49,10 @@ Training outputs, including checkpoints, are saved under ``logs/``. Add Extras make optional capabilities available; task selectors choose which capabilities the task uses For example, ``--extra ovphysx`` makes the OV PhysX integration available, while ``physics=ovphysx`` selects it for the task. You can combine extras as needed. The ``--extra all`` - shortcut installs a curated set of backends, RL libraries, and visualizers. - Specialized extras such as ``rlinf``, ``mimic``, ``teleop``, ``tetrahedralization``, ``video``, - and ``leapp`` are not included; add them explicitly when needed. See + shortcut installs the curated ``ov``, ``rl-games``, ``sb3``, ``skrl``, ``rsl-rl``, ``rerun``, + and ``viser`` extras. Isaac Sim, standalone importers, and specialized extras such as ``rlinf``, + ``mimic``, ``teleop``, ``tetrahedralization``, ``video``, and ``leapp`` are not included; add + them explicitly. See :ref:`installation-optional-extras` for the complete list. Choose an RL library diff --git a/pyproject.toml b/pyproject.toml index 0ee6fb31762c..f7fd71b6ed39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,10 +71,6 @@ dependencies = [ # one environment overwrite each other's files, and removing either then breaks ``pxr``. # usd-exchange 2.3.0 vendors USD 25.5, matching the Isaac Sim 6.0 wheel stack. "usd-exchange==2.3.0", - # Standalone URDF/MJCF importers, so conversion works without Isaac Sim. They install - # alongside it without displacing it: Kit serves ``isaacsim.asset`` from its extension - # roots when the runtime is present, and the standalone packages serve it otherwise. - "isaacsim-asset-isolated>=6.0,<6.1", # avoid broken hf-xet pre-release cached on NVIDIA Artifactory "hf-xet>=1.4.1,<2.0.0 ; platform_machine == 'x86_64' or platform_machine == 'AMD64' or platform_machine == 'aarch64'", # ----- tasks ----- @@ -118,6 +114,11 @@ tetrahedralization = ["pytetwild[all]>=0.3.0,<0.4"] video = ["moviepy>=1.0.3,<2.0.0.dev0"] +importers = [ + "isaacsim-asset-isolated>=6.0,<6.1", + "tinyobjloader==2.0.0rc13", +] + test = [ "pytest", "pytest-mock", @@ -193,12 +194,8 @@ rlinf = [ leapp = [ "leapp>=0.5.2", ] -# Every backend, RL library, and visualizer in one flag. No extra is forked in -# [tool.uv].conflicts, so any combination resolves into a single environment. The -# specialized extras (rlinf, mimic, teleop, tetrahedralization, video, leapp) -# and the developer ``test`` tooling stay opt-in by name. all = [ - "isaaclab-dev[sb3,skrl,rl-games,rsl-rl,viser,rerun,isaacsim,ov]", + "isaaclab-dev[sb3,skrl,rl-games,rsl-rl,viser,rerun,ov]", ] # Single source of truth for externally-pinned versions, read by docs/conf.py, the @@ -390,8 +387,8 @@ environments = [ "sys_platform == 'win32' and platform_machine == 'AMD64'", ] # Isaac Lab owns the Newton, MuJoCo, and torch versions. Overrides replace requirements -# for every requester, unlike constraints, which only intersect. The ``isaacsim`` extra -# pins older Newton, MuJoCo, and torch versions, so these overrides prevent downgrades. +# for every requester, unlike constraints, which only intersect. The ``isaacsim`` and +# ``importers`` extras pin older versions of these packages, so the overrides prevent downgrades. # Torch routes through [tool.uv.sources]. Values mirror [tool.isaaclab.versions] where applicable. override-dependencies = [ "numpy>=2", diff --git a/skills/user/install-isaac-lab/reference.md b/skills/user/install-isaac-lab/reference.md index 0501cea5f9ac..d58ee01b010b 100644 --- a/skills/user/install-isaac-lab/reference.md +++ b/skills/user/install-isaac-lab/reference.md @@ -59,12 +59,25 @@ Read `docs/source/setup/installation/index.rst` "System requirements" from the c Run the docs-defined minimal verification command after every install, before larger tests. The command varies by route: -- Automatic uv (`installation-method-uv`), legacy installer (`installation-legacy-installer`), managed Python env (`installation-method-python-env`), Isaac Lab wheel (`installation-method-wheel`), and Isaac Sim source build (`installation-method-source`) verify Isaac Lab via the tutorial script documented in the section's included verification snippet: +- Automatic uv (`installation-method-uv`), legacy installer (`installation-legacy-installer`), managed Python env (`installation-method-python-env`), and Isaac Lab wheel (`installation-method-wheel`) verify Isaac Lab via the tutorial script documented in the section's included verification snippet: ```bash uv run python scripts/tutorials/00_sim/create_empty.py --viz kit ``` +- Isaac Sim source build (`installation-method-source`) runs the same script against the locally built Isaac Sim wheels: + +```bash +uv run --extra isaacsim-local python scripts/tutorials/00_sim/create_empty.py --viz kit +``` + + This only uses the local build when `pyproject.toml` carries both edits that + `uv run isaaclab --isaacsim_source ` writes: `find-links = ["_isaac_sim_wheels"]` under + `[tool.uv]`, and an `isaacsim-local` extra pinning the exact version from + `_isaac_sim_wheels/isaacsim-*.whl`. Without the pin, uv resolves the published wheels from + `pypi.nvidia.com` instead, because source builds carry pre-release local versions that sort below + the release. + - Downloaded Isaac Sim package (`installation-method-binary`) uses the bundled-Python verification documented in the section (launch via `${ISAACSIM_PATH}/isaac-sim.sh`, then run the tutorial script from the checkout). - Docker (`installation-method-container`) runs the same tutorial verification inside the container as documented in `docs/source/features/docker_cloud.rst`. diff --git a/skills/user/setup-troubleshooting/SKILL.md b/skills/user/setup-troubleshooting/SKILL.md index ad17f2cc1e7d..5c78c73590e5 100644 --- a/skills/user/setup-troubleshooting/SKILL.md +++ b/skills/user/setup-troubleshooting/SKILL.md @@ -20,7 +20,7 @@ Do not duplicate installation or troubleshooting docs in this skill. The officia 1. Identify the install mode: automatic uv, legacy installer script, managed Python environment, Python package, downloaded Isaac Sim package, source build, Docker, cloud, or backend-specific setup. For a new full-feature Isaac Sim setup, prefer the automatic uv installation guide. 2. Identify OS, Python environment, GPU/driver context, Isaac Sim source, and target backend. 3. Read the matching installation guide and troubleshooting reference before prescribing commands. -4. From the Isaac Lab checkout, use documented uv commands such as `uv run python`, `uv run isaaclab train`, and `uv run isaaclab play` for Python, verification, and RL entry points. XR teleoperation entry points are `uv run --extra teleop isaaclab teleop run|record|replay`; `teleop` cannot be combined with the `mimic` or `all` extras in one command. +4. From the Isaac Lab checkout, use documented uv commands such as `uv run python`, `uv run isaaclab train`, and `uv run isaaclab play` for Python, verification, and RL entry points. The `all` extra is the curated `ov`, `rl-games`, `sb3`, `skrl`, `rsl-rl`, `rerun`, and `viser` list; it excludes Isaac Sim and the standalone URDF/MJCF importers. Install the `importers` extra with the documented resolver overrides. XR teleoperation entry points are `uv run --extra teleop isaaclab teleop run|record|replay`; `teleop` cannot be combined with the `mimic` or `all` extras in one command. 5. Use suffixless task names in verification and training commands. 6. Ask for the smallest relevant error output when the failure mode is unclear. 7. Prefer a minimal verification command before running examples, training, or rendering workflows. diff --git a/source/isaaclab/changelog.d/all-extra-without-isaacsim.major.rst b/source/isaaclab/changelog.d/all-extra-without-isaacsim.major.rst new file mode 100644 index 000000000000..84998e9b74fc --- /dev/null +++ b/source/isaaclab/changelog.d/all-extra-without-isaacsim.major.rst @@ -0,0 +1,12 @@ +Changed +^^^^^^^ + +* **Breaking:** Changed the ``isaaclab[all]`` extra to exclude Isaac Sim. Install + ``isaaclab[isaacsim]`` with the documented resolver overrides when Isaac Sim is required. +* **Breaking:** Moved the standalone URDF/MJCF importers from the base wheel to the + ``isaaclab[importers]`` extra. Use the documented override command when installing it. + +Fixed +^^^^^ + +* Fixed Newton actuator imports with the minimum Newton versions supported by the wheel. diff --git a/source/isaaclab/changelog.d/normalize-colorized-semantic-segmentation.rst b/source/isaaclab/changelog.d/normalize-colorized-semantic-segmentation.rst new file mode 100644 index 000000000000..6e374ee6bd4c --- /dev/null +++ b/source/isaaclab/changelog.d/normalize-colorized-semantic-segmentation.rst @@ -0,0 +1,5 @@ +Fixed +^^^^^ + +* Fixed :func:`isaaclab.envs.mdp.image` so colorized semantic-segmentation observations are + converted to normalized ``float32`` tensors when ``normalize=True``. diff --git a/source/isaaclab/changelog.d/uv-friendly-isaacsim-source-build.minor.rst b/source/isaaclab/changelog.d/uv-friendly-isaacsim-source-build.minor.rst new file mode 100644 index 000000000000..8a6fc94e69a7 --- /dev/null +++ b/source/isaaclab/changelog.d/uv-friendly-isaacsim-source-build.minor.rst @@ -0,0 +1,8 @@ +Added +^^^^^ + +* Added the ``--isaacsim_source`` CLI option, which incrementally builds Isaac Sim from a source checkout, + links its live release tree into the repository as ``_isaac_sim``, and runs Python commands with + the active environment through Isaac Sim's generated launcher. This avoided rebuilding and + installing Python wheels after every incremental native build and left ``pyproject.toml`` and + ``uv.lock`` unchanged. diff --git a/source/isaaclab/isaaclab/actuators/newton/adapter.py b/source/isaaclab/isaaclab/actuators/newton/adapter.py index 7498334c01ac..9dc50f833ae2 100644 --- a/source/isaaclab/isaaclab/actuators/newton/adapter.py +++ b/source/isaaclab/isaaclab/actuators/newton/adapter.py @@ -475,7 +475,24 @@ def __init__(self, num_envs: int, num_joints: int, device: str): get_actuator_parameter = ArticulationView.get_actuator_parameter set_actuator_parameter = ArticulationView.set_actuator_parameter _get_actuator_dof_mapping = ArticulationView._get_actuator_dof_mapping - _resolve_world_mask = ArticulationView._resolve_world_mask + + def _resolve_world_mask(self, mask: Sequence[bool] | wp.array | None) -> wp.array: + """Normalize a world mask independently of the installed Newton version.""" + if mask is None: + return self.full_mask + if isinstance(mask, wp.array): + if mask.dtype is not wp.bool: + raise ValueError(f"Expected Boolean mask, got dtype {mask.dtype}") + if mask.shape != (self.world_count,): + raise ValueError(f"Expected mask shape ({self.world_count},), got {mask.shape}") + if mask.device != self.device: + raise ValueError(f"Expected mask on device {self.device}, got {mask.device}") + return mask + + try: + return wp.array(mask, dtype=wp.bool, shape=(self.world_count,), device=self.device, copy=False) + except Exception as error: + raise ValueError(f"Expected Boolean mask with shape ({self.world_count},)") from error @dataclass(frozen=True) diff --git a/source/isaaclab/isaaclab/cli/__init__.py b/source/isaaclab/isaaclab/cli/__init__.py index 56549fdadc88..3f90fc5d3554 100644 --- a/source/isaaclab/isaaclab/cli/__init__.py +++ b/source/isaaclab/isaaclab/cli/__init__.py @@ -17,6 +17,7 @@ ) from .commands.misc import ( command_build_docs, + command_build_isaacsim, command_new, command_run_docker, command_run_isaacsim, @@ -262,6 +263,14 @@ def cli() -> None: const="env_isaaclab", help="Create a new uv environment for Isaac Lab. Default name is 'env_isaaclab'.", ) + parser.add_argument( + "--isaacsim_source", + metavar="PATH", + help=( + "Incrementally build the Isaac Sim source checkout at PATH and link its live release\n" + "tree as '_isaac_sim'. Python commands keep using the active uv environment." + ), + ) args = parser.parse_args() @@ -277,6 +286,9 @@ def cli() -> None: elif args.uv: command_setup_uv(args.uv) + elif args.isaacsim_source: + command_build_isaacsim(args.isaacsim_source) + elif args.vscode: command_vscode_settings() diff --git a/source/isaaclab/isaaclab/cli/commands/misc.py b/source/isaaclab/isaaclab/cli/commands/misc.py index e1bff31762ab..44e684413c14 100644 --- a/source/isaaclab/isaaclab/cli/commands/misc.py +++ b/source/isaaclab/isaaclab/cli/commands/misc.py @@ -5,7 +5,10 @@ """Misc commands""" +import platform import shutil +import sys +from pathlib import Path from ..utils import ( ISAACLAB_ROOT, @@ -118,6 +121,83 @@ def command_build_docs() -> None: print_info(f"Open with: xdg-open {index_path}") +def command_build_isaacsim(source_path: str) -> None: + """Build Isaac Sim from source and make it usable through ``uv`` (--isaacsim_source). + + Runs Isaac Sim's incremental build and links its release tree into Isaac Lab as ``_isaac_sim``. + Python commands launched through the Isaac Lab CLI use the active environment's interpreter + through Isaac Sim's ``python.sh`` or ``python.bat`` wrapper, so they load the live build without + packaging or installing it as wheels. + + Args: + source_path: Path to an Isaac Sim source checkout. + """ + isaacsim_root = Path(source_path).expanduser().resolve() + build_script = isaacsim_root / ("build.bat" if is_windows() else "build.sh") + + if not build_script.is_file(): + print_error(f"'{isaacsim_root}' is not an Isaac Sim source checkout ({build_script.name} not found).") + print_info("Clone it first with: git clone https://github.com/isaac-sim/IsaacSim.git") + raise SystemExit(1) + + print_info("Incrementally building Isaac Sim from source. This may take a while...") + run_command([str(build_script)], cwd=isaacsim_root) + + release_dir = _resolve_isaacsim_release_dir(isaacsim_root) + python_launcher = release_dir / ("python.bat" if is_windows() else "python.sh") + if not python_launcher.is_file(): + print_error(f"The Isaac Sim build did not produce {python_launcher}.") + raise SystemExit(1) + + link_path = ISAACLAB_ROOT / "_isaac_sim" + if link_path.is_symlink() or link_path.exists(): + if link_path.is_symlink(): + link_path.unlink() + else: + print_error(f"{link_path} exists and is not a symbolic link. Remove it and re-run.") + raise SystemExit(1) + try: + link_path.symlink_to(release_dir, target_is_directory=True) + except OSError as error: + print_error(f"Could not link {link_path} to {release_dir}: {error}") + if is_windows(): + print_info("Enable Windows Developer Mode or run from an elevated terminal, then retry.") + raise SystemExit(1) from error + print_info(f"Linked {link_path} -> {release_dir}") + _repoint_source_build_prebundles() + + print_info("Isaac Sim is ready. Python commands now use the live source build through '_isaac_sim'.") + print_info("Run Isaac Lab against it with:") + print_info(" uv run isaaclab train --rl_library rsl_rl --task Isaac-Cartpole-Direct physics=isaacsim_physx") + + +def _resolve_isaacsim_release_dir(isaacsim_root: Path) -> Path: + """Resolve the platform-specific Isaac Sim release directory.""" + machine = platform.machine().lower() + targets = { + ("linux", "amd64"): "linux-x86_64", + ("linux", "x86_64"): "linux-x86_64", + ("linux", "aarch64"): "linux-aarch64", + ("linux", "arm64"): "linux-aarch64", + ("win32", "amd64"): "windows-x86_64", + ("win32", "x86_64"): "windows-x86_64", + } + target = targets.get((sys.platform, machine)) + if target is None: + print_error(f"Isaac Sim source builds are not supported on platform '{sys.platform}' with machine '{machine}'.") + raise SystemExit(1) + return isaacsim_root / "_build" / target / "release" + + +def _repoint_source_build_prebundles() -> None: + """Keep Isaac Sim's prebundled packages from shadowing the active environment.""" + # ``install`` imports ``command_vscode_settings`` from this module, so defer this import until + # both command modules are initialized. Reuse the same protection as the legacy installer. + from .install import _repoint_prebundle_packages + + _repoint_prebundle_packages() + + def command_run_docker(args: list[str]) -> None: """Run the docker container helper script (docker/container.py). diff --git a/source/isaaclab/isaaclab/cli/utils.py b/source/isaaclab/isaaclab/cli/utils.py index ced6877c3b10..11f15432c05b 100644 --- a/source/isaaclab/isaaclab/cli/utils.py +++ b/source/isaaclab/isaaclab/cli/utils.py @@ -6,6 +6,7 @@ import os import platform import shutil +import site import subprocess import sys import time @@ -597,7 +598,37 @@ def run_python_command( [subprocess.CompletedProcess] Result returned by ``subprocess.run``. """ - cmd = [extract_python_exe()] + python_exe = extract_python_exe() + cmd = [python_exe] + + # A source build linked at ``_isaac_sim`` must load its live Kit and extension paths, but the + # dependencies managed by uv should still come from the active environment. Isaac Sim's Python + # launcher supports exactly this combination through its ``PYTHONEXE`` override. The shell + # wrappers already configure ``ISAAC_PATH`` before starting this CLI, so only direct invocations + # such as ``uv run isaaclab train`` need to delegate through the launcher here. + command_env = os.environ if env is None else env + configured_isaac_path = command_env.get("ISAAC_PATH") + local_sim = DEFAULT_ISAAC_SIM_PATH + python_launcher = local_sim / ("python.bat" if is_windows() else "python.sh") + isaac_env_active = ( + configured_isaac_path is not None and Path(configured_isaac_path).resolve() == local_sim.resolve() + ) + if local_sim.is_dir() and python_launcher.is_file() and not isaac_env_active: + env = dict(command_env) + env["PYTHONEXE"] = python_exe + source_paths = [ + local_sim / "python_packages", + local_sim / "exts" / "isaacsim.simulation_app", + local_sim / "kit" / "kernel" / "py", + local_sim / "kit" / "plugins" / "bindings-python", + Path(site.getsitepackages()[0]), + ] + existing_pythonpath = env.get("PYTHONPATH") + python_paths = [str(path) for path in source_paths if path.is_dir()] + if existing_pythonpath: + python_paths.append(existing_pythonpath) + env["PYTHONPATH"] = os.pathsep.join(python_paths) + cmd = [str(python_launcher)] if is_module: cmd.append("-m") diff --git a/source/isaaclab/isaaclab/utils/images.py b/source/isaaclab/isaaclab/utils/images.py index 480245ed6540..8917e9d915e4 100644 --- a/source/isaaclab/isaaclab/utils/images.py +++ b/source/isaaclab/isaaclab/utils/images.py @@ -52,11 +52,13 @@ def normalize_camera_image( Dispatch (in order of check): - - :func:`is_rgb_like` and ``images.dtype == torch.uint8`` and contiguous 4D: routes to the + - :func:`is_rgb_like` or colorized ``"semantic_segmentation"`` (``uint8``) and contiguous + 4D: routes to the fused Warp kernel via :func:`~isaaclab.utils.warp.ops.normalize_image_uint8`. ``out`` and ``channel_dim`` are forwarded so callers can reuse a pre-allocated float32 buffer and select the image layout. - - :func:`is_rgb_like` and any other dtype/shape: pure-PyTorch ``(x.float() / 255.0) - mean`` + - :func:`is_rgb_like` or colorized ``"semantic_segmentation"`` and any other dtype/shape: + pure-PyTorch ``(x.float() / 255.0) - mean`` with the same math. ``out`` is ignored on this branch; ``channel_dim`` selects the spatial reduction axes. - :func:`is_depth_like`: in-place ``images[images == inf] = 0``. ``images`` is returned as-is. @@ -65,21 +67,23 @@ def normalize_camera_image( Args: images: The camera-observation tensor. Shape and dtype vary by ``data_type``; the - RGB-like Warp fast path requires 4D contiguous uint8 with the channel axis at - position ``channel_dim``. + RGB-like and colorized semantic-segmentation Warp fast paths require 4D contiguous uint8 + with the channel axis at position ``channel_dim``. data_type: The camera data-type string. Drives the dispatch. out: Optional pre-allocated float32 output for the RGB-like Warp fast path. Reused across steps to eliminate per-step allocation. Ignored on the PyTorch fallback and on non-RGB branches. Defaults to None. - channel_dim: Position of the channel axis for the RGB-like branches. ``-1`` (BHWC, - default) or ``-3`` / ``1`` (BCHW). Ignored on non-RGB branches. + channel_dim: Position of the channel axis for the RGB-like and colorized semantic- + segmentation branches. ``-1`` (BHWC, default) or ``-3`` / ``1`` (BCHW). Ignored on + other branches. Returns: - The normalized tensor. For RGB-like input this is a fresh (or pre-allocated) float32 - tensor; for depth-like input it is ``images`` itself (mutated in place); for - normals-like input it is a new tensor; for anything else, ``images`` unchanged. + The normalized tensor. For RGB-like and colorized semantic-segmentation input this is a + fresh (or pre-allocated) float32 tensor; for depth-like input it is ``images`` itself + (mutated in place); for normals-like input it is a new tensor; for anything else, + ``images`` unchanged. """ - if is_rgb_like(data_type): + if is_rgb_like(data_type) or (data_type == "semantic_segmentation" and images.dtype == torch.uint8): if images.dtype == torch.uint8 and images.ndim == 4 and images.is_contiguous(): return normalize_image_uint8(images, channel_dim=channel_dim, out=out) # PyTorch fallback for callers that pre-floated or pass a strided view. diff --git a/source/isaaclab/test/cli/test_install.py b/source/isaaclab/test/cli/test_install.py index e2ddb89738b1..9f6e44fb43e3 100644 --- a/source/isaaclab/test/cli/test_install.py +++ b/source/isaaclab/test/cli/test_install.py @@ -19,6 +19,7 @@ extract_python_exe, get_pip_command, run_command, + run_python_command, ) pytestmark = pytest.mark.unit @@ -61,6 +62,47 @@ def test_run_command_retries_a_failed_process(): sleep.assert_called_once_with(3.0) +def test_run_python_command_uses_live_isaac_sim_with_active_python(tmp_path): + """Direct uv launches must combine the live source build with the active Python.""" + local_sim = tmp_path / "_isaac_sim" + local_sim.mkdir() + python_launcher = local_sim / "python.sh" + python_launcher.touch() + active_python = str(tmp_path / ".venv" / "bin" / "python") + + with ( + mock.patch("isaaclab.cli.utils.DEFAULT_ISAAC_SIM_PATH", local_sim), + mock.patch("isaaclab.cli.utils.extract_python_exe", return_value=active_python), + mock.patch("isaaclab.cli.utils.run_command") as run, + mock.patch.dict(os.environ, {}, clear=True), + ): + run_python_command("train.py", ["--task", "Cartpole"]) + + command = run.call_args.args[0] + assert command[0] == str(python_launcher) + assert Path(command[1]).name == "train.py" + assert command[2:] == ["--task", "Cartpole"] + assert run.call_args.kwargs["env"]["PYTHONEXE"] == active_python + + +def test_run_python_command_does_not_wrap_an_active_isaac_sim_environment(tmp_path): + """The legacy wrapper path must not source the same Isaac Sim environment twice.""" + local_sim = tmp_path / "_isaac_sim" + local_sim.mkdir() + (local_sim / "python.sh").touch() + active_python = str(tmp_path / ".venv" / "bin" / "python") + + with ( + mock.patch("isaaclab.cli.utils.DEFAULT_ISAAC_SIM_PATH", local_sim), + mock.patch("isaaclab.cli.utils.extract_python_exe", return_value=active_python), + mock.patch("isaaclab.cli.utils.run_command") as run, + mock.patch.dict(os.environ, {"ISAAC_PATH": str(local_sim)}, clear=True), + ): + run_python_command("script.py", []) + + assert run.call_args.args[0] == [active_python, "script.py"] + + # --------------------------------------------------------------------------- # get_pip_command # --------------------------------------------------------------------------- diff --git a/source/isaaclab/test/cli/test_misc_commands.py b/source/isaaclab/test/cli/test_misc_commands.py index 330cb8d1674d..abcb8b040108 100644 --- a/source/isaaclab/test/cli/test_misc_commands.py +++ b/source/isaaclab/test/cli/test_misc_commands.py @@ -64,3 +64,59 @@ def test_build_docs_explains_how_to_install_uv(): mock.call("uv could not be found. Please install uv and try again."), mock.call("https://docs.astral.sh/uv/getting-started/installation/"), ] + + +def test_build_isaacsim_links_incremental_build_without_packaging(tmp_path): + """The source workflow must link the live build without creating Python wheels.""" + isaacsim_root = tmp_path / "IsaacSim" + build_script = isaacsim_root / "build.sh" + build_script.parent.mkdir() + build_script.touch() + release_dir = isaacsim_root / "_build" / "linux-x86_64" / "release" + release_dir.mkdir(parents=True) + (release_dir / "python.sh").touch() + + workspace = tmp_path / "IsaacLab" + workspace.mkdir() + + with ( + mock.patch.object(misc, "ISAACLAB_ROOT", workspace), + mock.patch.object(misc, "run_command") as run_command, + mock.patch.object(misc, "_repoint_source_build_prebundles") as repoint_prebundles, + mock.patch.object(misc.sys, "platform", "linux"), + mock.patch.object(misc.platform, "machine", return_value="x86_64"), + ): + misc.command_build_isaacsim(str(isaacsim_root)) + + run_command.assert_called_once_with([str(build_script)], cwd=isaacsim_root) + repoint_prebundles.assert_called_once_with() + assert (workspace / "_isaac_sim").resolve() == release_dir + + +@pytest.mark.parametrize( + ("sys_platform", "machine", "target"), + [ + ("linux", "x86_64", "linux-x86_64"), + ("linux", "aarch64", "linux-aarch64"), + ("win32", "AMD64", "windows-x86_64"), + ], +) +def test_build_isaacsim_resolves_release_directory(tmp_path, sys_platform, machine, target): + """The source workflow must select the current platform's live release tree.""" + with ( + mock.patch.object(misc.sys, "platform", sys_platform), + mock.patch.object(misc.platform, "machine", return_value=machine), + ): + result = misc._resolve_isaacsim_release_dir(tmp_path) + + assert result == tmp_path / "_build" / target / "release" + + +def test_build_isaacsim_rejects_unsupported_platform(tmp_path): + """The source workflow must reject platforms Isaac Sim cannot build.""" + with ( + mock.patch.object(misc.sys, "platform", "darwin"), + mock.patch.object(misc.platform, "machine", return_value="arm64"), + pytest.raises(SystemExit, match="1"), + ): + misc._resolve_isaacsim_release_dir(tmp_path) diff --git a/source/isaaclab/test/cli/test_source_package_metadata.py b/source/isaaclab/test/cli/test_source_package_metadata.py index 29c6844730c4..722b4063f416 100644 --- a/source/isaaclab/test/cli/test_source_package_metadata.py +++ b/source/isaaclab/test/cli/test_source_package_metadata.py @@ -59,10 +59,15 @@ def test_resolved_environment_has_no_second_usd_provider(): assert "usd-exchange" in locked -def test_standalone_importers_ship_as_base_dependencies(): - """The standalone URDF/MJCF importers install by default, so conversion works without Isaac Sim.""" +def test_standalone_importers_are_opt_in(): + """Standalone URDF/MJCF importers must not constrain the base environment.""" with (_repo_root() / "pyproject.toml").open("rb") as f: pyproject = tomllib.load(f) - assert "isaacsim-asset-isolated>=6.0,<6.1" in pyproject["project"]["dependencies"] - assert "importers" not in pyproject["project"]["optional-dependencies"] + project = pyproject["project"] + assert "isaacsim-asset-isolated>=6.0,<6.1" not in project["dependencies"] + assert "tinyobjloader==2.0.0rc13" not in project["dependencies"] + assert project["optional-dependencies"]["importers"] == [ + "isaacsim-asset-isolated>=6.0,<6.1", + "tinyobjloader==2.0.0rc13", + ] diff --git a/source/isaaclab/test/cli/test_uv_run_pyproject.py b/source/isaaclab/test/cli/test_uv_run_pyproject.py index 198f70740c37..b860d84c3c04 100644 --- a/source/isaaclab/test/cli/test_uv_run_pyproject.py +++ b/source/isaaclab/test/cli/test_uv_run_pyproject.py @@ -59,6 +59,7 @@ def test_uv_run_exposes_centralized_feature_extras(): "ovrtx", "mimic", "teleop", + "importers", "rlinf", "tetrahedralization", "all", @@ -82,27 +83,20 @@ def test_uv_run_exposes_centralized_feature_extras(): assert any(dep.startswith("ovstage") for dep in optional_dependencies["ovrtx"]) -def test_all_extra_aggregates_backends_rl_libraries_and_visualizers(): - """``all`` is the single flag for every backend, RL library, and visualizer. - - Nothing is forked in ``[tool.uv].conflicts``, so Isaac Sim and both OV backends fit in - one environment alongside every RL library and visualizer. The specialized workflows stay - opt-in by name -- they are large, narrowly used, or both. - """ +def test_all_extra_aggregates_curated_ov_rl_and_visualizer_extras(): + """``all`` aggregates only the curated OV, RL, and visualizer extras.""" optional = _root_pyproject()["project"]["optional-dependencies"] - # ``all`` is a single self-reference listing the extras it aggregates. assert len(optional["all"]) == 1 aggregated = set(re.fullmatch(r"isaaclab-dev\[(.+)\]", optional["all"][0]).group(1).split(",")) - assert aggregated == {"isaacsim", "ov", "sb3", "skrl", "rl-games", "rsl-rl", "viser", "rerun"} + assert aggregated == {"ov", "sb3", "skrl", "rl-games", "rsl-rl", "viser", "rerun"} - # ``ov`` pulls both OV backends, so naming it covers ``ovphysx`` and ``ovrtx`` too. reachable = aggregated | {"ovphysx", "ovrtx"} - # Everything else is requested by name. A newly added extra lands in this diff and - # has to be classified deliberately -- into ``all`` or into this list. assert set(optional) - reachable - {"all"} == { "rlinf", + "isaacsim", + "importers", "mimic", "teleop", "tetrahedralization", diff --git a/source/isaaclab/test/cli/test_wheel_builder_metadata.py b/source/isaaclab/test/cli/test_wheel_builder_metadata.py index ceeaac018bdd..7ac6515aee45 100644 --- a/source/isaaclab/test/cli/test_wheel_builder_metadata.py +++ b/source/isaaclab/test/cli/test_wheel_builder_metadata.py @@ -86,24 +86,39 @@ def test_wheel_builder_includes_isaacsim_extra(tmp_path): assert any(dep.startswith("isaacsim[") for dep in optional_dependencies["isaacsim"]) -def test_wheel_builder_expands_all_extra_into_concrete_requirements(tmp_path): - """``isaaclab[all]`` must ship the aggregated requirements, not a self-reference. +def test_wheel_builder_keeps_standalone_importers_explicit(tmp_path): + """The wheel must expose standalone importers only through their explicit extra.""" + generated = _generate_wheel_pyproject(tmp_path) + project = generated["project"] - At the root, ``all`` is the self-reference ``isaaclab-dev[...]``. The generator - inlines it, so the published wheel carries the concrete third-party requirements - for every backend, RL library, and visualizer. - """ + assert "isaacsim-asset-isolated>=6.0,<6.1" not in project["dependencies"] + assert "tinyobjloader==2.0.0rc13" not in project["dependencies"] + assert project["optional-dependencies"]["importers"] == [ + "isaacsim-asset-isolated>=6.0,<6.1", + "tinyobjloader==2.0.0rc13", + ] + + +def test_wheel_builder_expands_all_extra_into_concrete_requirements(tmp_path): + """``isaaclab[all]`` must contain concrete curated requirements.""" generated = _generate_wheel_pyproject(tmp_path) optional_dependencies = generated["project"]["optional-dependencies"] all_extra = optional_dependencies["all"] assert not any(dep.lower().startswith("isaaclab") for dep in all_extra) - # Sampled across what ``all`` aggregates: Isaac Sim, both OV backends, the RL - # libraries, and the visualizers. - for prefix in ("isaacsim[", "ovphysx", "ovrtx", "ovstage", "stable-baselines3", "skrl", "viser", "rerun-sdk"): + for prefix in ("ovphysx", "ovrtx", "ovstage", "stable-baselines3", "skrl", "viser", "rerun-sdk"): assert any(dep.startswith(prefix) for dep in all_extra), f"'{prefix}' missing from the 'all' extra" - # The specialized extras and the developer tooling stay opt-in by name. - for prefix in ("ray", "robomimic", "isaacteleop", "pytetwild", "moviepy", "leapp", "pytest"): + for prefix in ( + "isaacsim[", + "isaacsim-asset-isolated", + "ray", + "robomimic", + "isaacteleop", + "pytetwild", + "moviepy", + "leapp", + "pytest", + ): assert not any(dep.startswith(prefix) for dep in all_extra), f"'{prefix}' must not be in the 'all' extra" diff --git a/source/isaaclab/test/install_ci/misc/test_wheel_builder_smoke.py b/source/isaaclab/test/install_ci/misc/test_wheel_builder_smoke.py index fe4699682742..a32dc83a117b 100644 --- a/source/isaaclab/test/install_ci/misc/test_wheel_builder_smoke.py +++ b/source/isaaclab/test/install_ci/misc/test_wheel_builder_smoke.py @@ -33,12 +33,7 @@ @pytest.mark.smoke class Test_Wheel_Builder_Smoke(UV_Mixin): - """Test building the isaaclab wheel and installing it in a uv environment. - - The extras are named individually rather than using the aggregate ``all``: this is a - fast smoke test of the built wheel, and ``all`` would pull Isaac Sim and both OV - backends in. ``test_uv_pip_install_isaaclab_all_trains_cartpole`` covers ``[all]``. - """ + """Test building and installing the Isaac Lab wheel with selected RL extras.""" _wheel: str = "" _extras: str = "[sb3,skrl,rsl-rl]" diff --git a/source/isaaclab/test/install_ci/uv_pip/test_uv_pip_install_isaaclab_all_trains_cartpole.py b/source/isaaclab/test/install_ci/uv_pip/test_uv_pip_install_isaaclab_all_trains_cartpole.py index 986e329a5f20..3defd6fca820 100644 --- a/source/isaaclab/test/install_ci/uv_pip/test_uv_pip_install_isaaclab_all_trains_cartpole.py +++ b/source/isaaclab/test/install_ci/uv_pip/test_uv_pip_install_isaaclab_all_trains_cartpole.py @@ -7,8 +7,7 @@ Setup: - (wheel supplied by runner: tools/run_install_ci.py --build-wheel or --wheel ) - ./isaaclab.sh -u - - uv pip install [all] --overrides uv_pip/uv-overrides.txt - --extra-index-url https://pypi.nvidia.com --index-strategy unsafe-best-match --prerelease=allow + - uv --no-config pip install [all] - uv pip install --reinstall-package torch --reinstall-package torchvision torch== torchvision== --index-url (versions read from [tool.isaaclab.versions] in the root pyproject.) @@ -16,6 +15,8 @@ Reinstall AFTER the wheel install: unsafe-best-match re-resolves torch from PyPI to CPU.) - (aarch64 only) export LD_PRELOAD=/lib/aarch64-linux-gnu/libgomp.so.1 Tests: + - python -c "import importlib.metadata as m; assert m.version('newton') == '1.5.0'" + -> verify the wheel resolves Newton 1.5 - uv run isaaclab train --rl_library rsl_rl --task Isaac-Cartpole-Direct --num_envs 16 presets=newton_mjwarp --max_iterations 5; uv run isaaclab train --rl_library rsl_rl --task Isaac-Cartpole-Camera-Direct --num_envs 16 presets=newton_mjwarp,newton_renderer --max_iterations 2 @@ -27,7 +28,7 @@ import shutil import pytest -from utils import UV_Mixin, aarch64_isaacsim_env, cuda_torch_index_url, pinned_torch_specs +from utils import UV_Mixin, cuda_torch_index_url, pinned_torch_specs @pytest.mark.install_path_uv_pip @@ -45,45 +46,25 @@ def setup_class(cls): @pytest.mark.slow @pytest.mark.gpu @pytest.mark.timeout(4800) - def test_uv_pip_install_isaaclab_all_trains_cartpole( - self, isaaclab_root, wheel, uv_overrides, cartpole_smoke_script - ): + def test_uv_pip_install_isaaclab_all_trains_cartpole(self, isaaclab_root, wheel, cartpole_smoke_script): """Install the runner-supplied wheel with ``[all]`` via ``uv pip``, then train.""" try: - # 1. Create the uv env and install the wheel with the aggregate [all] extra, which - # carries Isaac Sim, both OV backends, every RL library, and every visualizer. - # This mirrors the documented wheel install (isaaclab-uv-wheel-install directive). self.create_uv_env(isaaclab_root) - # uv pip install "isaaclab[all]" --extra-index-url https://pypi.nvidia.com - # --index-strategy unsafe-best-match --prerelease=allow - # NOTE: --index-strategy unsafe-best-match re-resolves torch from PyPI (CPU build), - # overriding any pre-installed CUDA torch. So install isaaclab FIRST, then - # force-reinstall the CUDA torch from cu128/cu130 below. result = self.run_in_uv_env( - [ - "uv", - "pip", - "install", - f"{wheel}[all]", - "--overrides", - str(uv_overrides), - "--extra-index-url", - "https://pypi.nvidia.com", - "--index-strategy", - "unsafe-best-match", - "--prerelease=allow", - ], - cwd=isaaclab_root, - timeout=1800, + ["uv", "--no-config", "pip", "install", f"{wheel}[all]"], cwd=isaaclab_root, timeout=1800 ) assert result.returncode == 0, f"uv pip install {wheel}[all] failed:\n{result.stdout}\n{result.stderr}" - # 2. uv pip install --reinstall-package torch --reinstall-package torchvision - # torch== torchvision== --index-url - # (versions from [tool.isaaclab.versions]; cu128 on x86_64, cu130 on aarch64, - # e.g. GB10 / DGX Spark with CUDA capability 12.x). - # --reinstall-package forces uv to swap the CPU torch installed above with the CUDA build. + result = self.run_in_uv_env( + ["python", "-c", "import importlib.metadata as m; assert m.version('newton') == '1.5.0'"], + cwd=isaaclab_root, + ) + assert result.returncode == 0, ( + f"isaaclab[all] did not resolve Newton 1.5:\n{result.stdout}\n{result.stderr}" + ) + + # Restore the CUDA build selected for this architecture. result = self.run_in_uv_env( [ "uv", @@ -102,11 +83,9 @@ def test_uv_pip_install_isaaclab_all_trains_cartpole( ) assert result.returncode == 0, f"uv pip install CUDA torch failed:\n{result.stdout}\n{result.stderr}" - # 3. Run the shared state and camera Cartpole smoke in the installed environment. result = self.run_in_uv_env( [str(self.python), str(cartpole_smoke_script)], cwd=isaaclab_root, - env=aarch64_isaacsim_env(), timeout=3000, ) assert result.returncode == 0, f"Cartpole smoke failed:\n{result.stdout}\n{result.stderr}" diff --git a/source/isaaclab/test/install_ci/uv_pip/test_uv_pip_install_isaaclab_isaacsim_imports_simulation_context.py b/source/isaaclab/test/install_ci/uv_pip/test_uv_pip_install_isaaclab_isaacsim_imports_simulation_context.py index eb034a69fd50..aeb9893f2987 100644 --- a/source/isaaclab/test/install_ci/uv_pip/test_uv_pip_install_isaaclab_isaacsim_imports_simulation_context.py +++ b/source/isaaclab/test/install_ci/uv_pip/test_uv_pip_install_isaaclab_isaacsim_imports_simulation_context.py @@ -7,8 +7,8 @@ Setup: - (wheel supplied by runner: tools/run_install_ci.py --build-wheel or --wheel ) - ./isaaclab.sh -u - - uv pip install [isaacsim] --overrides uv_pip/uv-overrides.txt - --extra-index-url https://pypi.nvidia.com --index-strategy unsafe-best-match --prerelease=allow + - uv --no-config pip install [isaacsim] --overrides uv_pip/uv-overrides.txt + --extra-index-url https://pypi.nvidia.com --index-strategy unsafe-best-match - uv pip install --reinstall-package torch --reinstall-package torchvision torch== torchvision== --index-url (versions read from [tool.isaaclab.versions] in the root pyproject.) @@ -56,6 +56,7 @@ def _install_wheel(self, isaaclab_root, wheel, uv_overrides): result = self.run_in_uv_env( [ "uv", + "--no-config", "pip", "install", f"{cls._wheel}[isaacsim]", @@ -65,7 +66,6 @@ def _install_wheel(self, isaaclab_root, wheel, uv_overrides): "https://pypi.nvidia.com", "--index-strategy", "unsafe-best-match", - "--prerelease=allow", ], cwd=isaaclab_root, timeout=1800, diff --git a/source/isaaclab/test/install_ci/uv_pip/test_uv_pip_install_isaaclab_rl_tasks_imports_rl_tasks.py b/source/isaaclab/test/install_ci/uv_pip/test_uv_pip_install_isaaclab_rl_tasks_imports_rl_tasks.py index af8fd4a7a8b0..91cf3f812ede 100644 --- a/source/isaaclab/test/install_ci/uv_pip/test_uv_pip_install_isaaclab_rl_tasks_imports_rl_tasks.py +++ b/source/isaaclab/test/install_ci/uv_pip/test_uv_pip_install_isaaclab_rl_tasks_imports_rl_tasks.py @@ -24,12 +24,7 @@ @pytest.mark.install_path_uv_pip class Test_Uv_Pip_Install_Isaaclab_Rl_Tasks_Imports_Rl_Tasks(UV_Mixin): - """``uv pip install [sb3,skrl,rsl-rl]``: verify RL imports without Isaac Sim. - - The extras are named individually on purpose. ``test_install_rl_tasks_omits_isaacsim`` - asserts Isaac Sim is absent, and the aggregate ``all`` extra carries it -- switching to - ``[all]`` would make that assertion fail. - """ + """Verify RL imports without Isaac Sim.""" _wheel: str = "" _extras: str = "[sb3,skrl,rsl-rl]" @@ -44,7 +39,6 @@ def _install_wheel(self, isaaclab_root, wheel): cls = self.__class__ cls._wheel = str(wheel) - # Create the uv env and install the RL extras (no isaacsim, no NVIDIA flags). self.create_uv_env(isaaclab_root) cls.env_path = self.env_path cls.python = self.python @@ -87,9 +81,7 @@ def test_install_rl_tasks_makes_isaaclab_tasks_importable(self): def test_install_rl_tasks_omits_isaacsim(self): """The Isaac Sim runtime is absent after installing the RL extras (isaacsim extra not requested). - ``import isaacsim`` is not the check: the standalone importers ship in the base - dependencies and contribute an ``isaacsim.asset`` portion, so the namespace package - resolves without the runtime. Ask the distribution instead. + Ask the distribution directly so this remains independent of namespace-package behavior. """ result = self.run_in_uv_env( ["python", "-c", "import importlib.metadata as m; m.version('isaacsim')"], diff --git a/source/isaaclab/test/utils/test_images.py b/source/isaaclab/test/utils/test_images.py index 26dc770b0118..9a7c045e6479 100644 --- a/source/isaaclab/test/utils/test_images.py +++ b/source/isaaclab/test/utils/test_images.py @@ -146,6 +146,23 @@ def test_bchw_float_input_takes_pytorch_fallback(self, device): torch.testing.assert_close(out, expected) +class TestNormalizeCameraImageColorizedSegmentation: + """Colorized segmentation dispatch.""" + + def test_colorized_semantic_segmentation_is_normalized(self, device): + """RGBA uint8 semantic segmentation produces a float32 normalized image.""" + from isaaclab.utils.images import normalize_camera_image + + torch.manual_seed(0) + src = torch.randint(0, 255, (2, 8, 8, 4), dtype=torch.uint8, device=device) + out = normalize_camera_image(src, "semantic_segmentation") + + expected = src.float() / 255.0 + expected = expected - torch.mean(expected, dim=(1, 2), keepdim=True) + torch.testing.assert_close(out, expected, atol=1e-5, rtol=1e-5) + assert out.dtype == torch.float32 + + class TestNormalizeCameraImageDepth: """Depth-like dispatch: in-place ``inf -> 0``.""" diff --git a/source/isaaclab_ov/changelog.d/ovphysx-force-matrix-history-reset.rst b/source/isaaclab_ov/changelog.d/ovphysx-force-matrix-history-reset.rst new file mode 100644 index 000000000000..919df66444e1 --- /dev/null +++ b/source/isaaclab_ov/changelog.d/ovphysx-force-matrix-history-reset.rst @@ -0,0 +1,5 @@ +Fixed +^^^^^ + +* Cleared ``ContactSensorData.force_matrix_w_history`` when resetting an + OVPhysX contact sensor. diff --git a/source/isaaclab_ov/changelog.d/ovstage-performance-improvements.rst b/source/isaaclab_ov/changelog.d/ovstage-performance-improvements.rst new file mode 100644 index 000000000000..ef4c016bd89b --- /dev/null +++ b/source/isaaclab_ov/changelog.d/ovstage-performance-improvements.rst @@ -0,0 +1,11 @@ +Changed +^^^^^^^ + +* Changed the OVRTX ovstage path to write object transforms, camera transforms and deformable or + particle points straight from their Warp GPU buffers as CUDA DLTensors, removing the per-frame + host copies that ``ovstage 0.1.0`` required. +* Changed those writes to be ordered by handing ovstage the producing Warp stream + (``write_attribute(cuda_stream=...)``), replacing the device-wide ``wp.synchronize_device`` with + stream-scoped producer ordering, and matching the legacy OVRTX binding path. The write is still + awaited, so the calling thread can block; the gain is the removed host copy and the narrower + synchronization scope, not a nonblocking handoff. diff --git a/source/isaaclab_ov/isaaclab_ov/physics/ovphysx_manager.py b/source/isaaclab_ov/isaaclab_ov/physics/ovphysx_manager.py index d818ab8fffbc..03c953559883 100644 --- a/source/isaaclab_ov/isaaclab_ov/physics/ovphysx_manager.py +++ b/source/isaaclab_ov/isaaclab_ov/physics/ovphysx_manager.py @@ -37,6 +37,7 @@ from isaaclab_ov._clone import CloneTransform, clone_transforms_from_positions from isaaclab_ov._runtime import import_ovphysx +from isaaclab_ov.stage import create_ovstage if TYPE_CHECKING: from isaaclab.sim.simulation_context import SimulationContext @@ -596,7 +597,7 @@ def _attach_ovstage(cls, stage_usda: str) -> None: """Populate an OVStage from USDA text and attach it to the runtime.""" import ovstage # noqa: PLC0415 - stage = ovstage.Stage("isaaclab") + stage = create_ovstage("isaaclab") try: ovstage.population.open_usd_from_string( stage, diff --git a/source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py b/source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py index eb006605d3e0..b4ae36de5acc 100644 --- a/source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py +++ b/source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py @@ -33,22 +33,12 @@ logger = logging.getLogger(__name__) import numpy as np +import ovstage import torch import warp as wp import isaaclab.utils.warp # noqa: F401 # initializes Warp runtime -# ovstage is optional: when present the renderer uses the split-ownership model -# (ovstage owns scene data, ovrtx owns rendering). When absent it falls back to -# the legacy renderer-owned scene APIs (deprecated in ovrtx 0.4). -_OVSTAGE_AVAILABLE = False -try: - import ovstage - - _OVSTAGE_AVAILABLE = True -except ModuleNotFoundError: - pass - # The ovrtx C library links to its own version of the USD libraries. Having # the pxr Python package available can cause the C library to load an # incompatible version of libusd, potentially leading to undefined behavior. @@ -81,6 +71,13 @@ from isaaclab.sim import SimulationContext from isaaclab.utils.warp.warp_math import convert_camera_frame_orientation_convention_wp +from isaaclab_ov.stage import ( + create_ovstage, + points_tensor_from_warp, + xform_tensor_from_numpy, + xform_tensor_from_warp, +) + from .ovrtx_annotator_utils import ( build_instance_id_to_labels_and_semantics, build_semantic_id_to_labels, @@ -137,41 +134,6 @@ _DISABLE_LINUX_CUDA_CPU_SYNC_ENV = "ISAAC_LAB_OVRTX_DISABLE_LINUX_CUDA_CPU_SYNC" -if _OVSTAGE_AVAILABLE: - # DLDataType for a 4×4 double matrix (omni:xform column). ovstage stores omni:xform - # as one 16-lane float64 element per prim; wp.mat44d maps to the same layout via __dlpack__. - _OVSTAGE_XFORM_DTYPE = ovstage.DLDataType(code=ovstage.DLDataTypeCode.kDLFloat, bits=64, lanes=16) - - def _xform_tensor_from_numpy(xforms: np.ndarray) -> Any: - """Wrap a ``(N, 4, 4)`` float64 array as a 16-lane DLTensor for ``omni:xform`` writes. - - Args: - xforms: Array of shape ``(N, 4, 4)`` with dtype ``float64``. - - Returns: - A :class:`ovstage.DLTensor` with shape ``[N]`` and ``lanes=16``. - """ - flat = np.ascontiguousarray(xforms, dtype=np.float64).reshape(-1) - return ovstage.make_dltensor(flat, dtype=_OVSTAGE_XFORM_DTYPE, shape=[xforms.shape[0]]) - - # DLDataType for a float32 3-vector (``points`` column). ovstage stores ``point3f[] points`` - # as one 3-lane float32 element per vertex; a warp ``vec3f`` array exports as ``(N, 3)`` lanes=1 - # via DLPack, so a lanes=3 override on a host numpy array is required to match the column. - _OVSTAGE_POINT_DTYPE = ovstage.DLDataType(code=ovstage.DLDataTypeCode.kDLFloat, bits=32, lanes=3) - - def _points_tensor_from_numpy(points: np.ndarray) -> Any: - """Wrap an ``(N, 3)`` float32 array as a 3-lane DLTensor for ``points`` writes. - - Args: - points: Array of shape ``(N, 3)`` with dtype ``float32``. - - Returns: - A :class:`ovstage.DLTensor` with shape ``[N]`` and ``lanes=3``. - """ - flat = np.ascontiguousarray(points, dtype=np.float32).reshape(-1) - return ovstage.make_dltensor(flat, dtype=_OVSTAGE_POINT_DTYPE, shape=[points.shape[0]]) - - def ovrtx_use_ovstage_enabled() -> bool: """Return whether the ovstage scene-ownership path should be used. @@ -180,20 +142,10 @@ def ovrtx_use_ovstage_enabled() -> bool: Raises: ValueError: If the environment variable is set to anything other than ``0`` or ``1``. - RuntimeError: If the environment variable is ``1`` but ovstage is not importable. Falling - back to the legacy path here would silently ignore an explicit request and make the - renderer look like it had honoured it. """ value = os.environ.get(_USE_OVSTAGE_ENV, "0").strip() if value not in {"0", "1"}: raise ValueError(f"Invalid value for environment variable `{_USE_OVSTAGE_ENV}`: {value}. Expected 0 or 1.") - if value == "1" and not _OVSTAGE_AVAILABLE: - raise RuntimeError( - f"`{_USE_OVSTAGE_ENV}=1` requests the ovstage scene-ownership path, but the 'ovstage' " - "package is not installed. Run your command with: uv run --extra ovrtx " - "(or, manually: python -m pip install --extra-index-url https://pypi.nvidia.com " - "'ovstage>=0.1.0,<0.2.0')." - ) return value == "1" @@ -1721,9 +1673,6 @@ def close(self) -> None: # :meth:`_render_ovstage` already bars all writes at ordinals <= N, so accumulate the # ``Operation`` objects and ``stage.release_op(op.op_id)`` after it. They must outlive the # barrier — an ``Operation`` is its buffer's only keepalive. Saves caller-side blocking only. - # - Direct zero-copy warp DLpack writes are rejected in ovstage 0.1.0 because - # ``omni:xform``/``points`` are lanes=16/3 while warp's DLPack export is always lanes=1. - # ovstage 0.1.1 will address this. # --------------------------------------------------------------------------- def _init_fields_ovstage(self) -> None: @@ -1741,6 +1690,8 @@ def _init_fields_ovstage(self) -> None: self._particle_paths_list = None self._cable_points_query = None self._cable_paths_list = None + # DLTensor descriptors aliasing ``_cable_point_slices``; rebuilt only when cables rebind. + self._cable_point_tensors: list = [] def _initialize_from_spec_ovstage(self, spec: CameraRenderSpec) -> None: """Initialize the OVRTX renderer with internal environment cloning (ovstage path). @@ -1785,7 +1736,7 @@ def _initialize_from_spec_ovstage(self, spec: CameraRenderSpec) -> None: logger.info("Loading USD into OvRTX via ovstage...") self._ovstage_exit_stack = contextlib.ExitStack() - self._stage = self._ovstage_exit_stack.enter_context(ovstage.Stage("isaaclab.ovrtx")) + self._stage = self._ovstage_exit_stack.enter_context(create_ovstage("isaaclab.ovrtx")) self._stage_paths = self._ovstage_exit_stack.enter_context(ovstage.PathDictionary(self._stage)) # Ordinal 0 is the empty/unwritten state in ovstage; the first write must use >= 1. self._current_ordinal += 1 @@ -1891,7 +1842,7 @@ def _clone_sources_ovstage(self): env_query, "omni:xform", ordinal=self._current_ordinal, - tensors=_xform_tensor_from_numpy(env_root_xforms), + tensors=xform_tensor_from_numpy(env_root_xforms), is_array=False, semantic=ovstage.AttributeSemantic.MATRIX, ).wait() @@ -2065,7 +2016,7 @@ def _setup_deformable_bindings_ovstage(self, num_envs: int) -> None: self._deformable_points_query, "omni:xform", ordinal=self._current_ordinal, - tensors=_xform_tensor_from_numpy(identity_xforms), + tensors=xform_tensor_from_numpy(identity_xforms), is_array=False, semantic=ovstage.AttributeSemantic.MATRIX, ).wait() @@ -2076,10 +2027,10 @@ def _setup_deformable_bindings_ovstage(self, num_envs: int) -> None: def _setup_cable_bindings_ovstage(self) -> None: """Setup ovstage ``points`` bindings for Newton cables (``UsdGeom.BasisCurves``). - Mirrors :meth:`_setup_cable_bindings_legacy`, except that the per-frame write goes through a - host copy: ovstage 0.1.0's ``make_dltensor`` accepts the lanes=3 dtype override only on - numpy arrays, so a warp ``vec3f`` slice is rejected against the ``points`` column. The - endpoint kernel still runs on device; only the handover is host-side. + Mirrors :meth:`_setup_cable_bindings_legacy`: the endpoint kernel writes device memory and + the per-frame handover is zero-copy. The per-curve slices and their DLTensor descriptors are + built once here rather than per frame, because the layout is fixed for the lifetime of the + binding — only the contents of ``_cable_points`` change each step. """ discovered = self._discover_cable_segment_bindings() if discovered is None: @@ -2105,12 +2056,18 @@ def _setup_cable_bindings_ovstage(self) -> None: self._cable_points_query, "omni:xform", ordinal=self._current_ordinal, - tensors=_xform_tensor_from_numpy(identity_xforms), + tensors=xform_tensor_from_numpy(identity_xforms), is_array=False, semantic=ovstage.AttributeSemantic.MATRIX, ).wait() self._allocate_cable_device_buffers(flat_shape_ids, offsets, counts) + # The descriptors alias these slices, so both must outlive every write that uses them. + self._cable_point_slices = [ + self._cable_points[offset + curve : offset + curve + segment_count + 1] + for curve, (offset, segment_count) in enumerate(zip(offsets, counts, strict=True)) + ] + self._cable_point_tensors = [points_tensor_from_warp(points) for points in self._cable_point_slices] def _setup_particle_bindings_ovstage(self) -> None: """Setup OVRTX bindings for Newton particle clouds (ovstage path).""" @@ -2160,7 +2117,7 @@ def _setup_particle_bindings_ovstage(self) -> None: self._particle_points_query, "omni:xform", ordinal=self._current_ordinal, - tensors=_xform_tensor_from_numpy(identity_xforms), + tensors=xform_tensor_from_numpy(identity_xforms), is_array=False, semantic=ovstage.AttributeSemantic.MATRIX, ).wait() @@ -2192,17 +2149,21 @@ def _update_transforms_ovstage(self) -> None: inputs=[object_transforms, self._object_newton_indices, body_q, self._object_scales], device=self._device, ) - # Synchronize then copy to CPU numpy: ovstage's make_dltensor only accepts the lanes=16 - # dtype override on numpy arrays, not DLPack producers. wp.mat44d exports as (N,4,4) lanes=1 - # via DLPack, which conflicts with the lanes=16 omni:xform column created at population time. - wp.synchronize_device(self._device) + # The tensor is handed over zero-copy, so ovstage reads ``object_transforms`` in place and + # must not do so until the kernel above has landed. Passing the producing Warp stream as + # ``cuda_stream`` gives producer ordering: ovstage drains the work already queued on that + # stream before it touches the tensor. That replaces the device-wide + # ``wp.synchronize_device()`` with stream-scoped ordering and removes the host copy; it is + # not a nonblocking handoff, and the ``.wait()`` below can still block the calling thread. + # A GPU-side wait would need the event-based API instead. self._stage.write_attribute( self._object_xform_query, "omni:xform", ordinal=self._current_ordinal, - tensors=_xform_tensor_from_numpy(object_transforms.numpy().reshape(-1, 4, 4)), + tensors=xform_tensor_from_warp(object_transforms), is_array=False, semantic=ovstage.AttributeSemantic.MATRIX, + cuda_stream=wp.get_stream(self._device).cuda_stream, ).wait() def _update_geometries_ovstage(self) -> None: @@ -2221,19 +2182,10 @@ def _update_geometries_ovstage(self) -> None: if particle_q is None: raise RuntimeError("Newton state has no particle_q but particle geometry queries exist") - # ovstage write_attribute needs one DLPack tensor per prim, not one flat ``particle_q`` - # plus offsets. Synchronize then copy to CPU numpy once (shared by both queries below): - # the ``points`` column is ``point3f[]`` (lanes=3), and ovstage's make_dltensor only - # accepts the lanes=3 dtype override on numpy arrays, not DLPack producers. A warp - # ``vec3f`` slice exports as ``(N, 3)`` lanes=1, which is rejected as a type mismatch - # against the lanes=3 column. - wp.synchronize_device(self._device) - particle_np = particle_q.numpy() - if self._deformable_points_query is not None: self._write_particle_q_slices_ovstage( self._deformable_points_query, - particle_np, + particle_q, self._deformable_particle_offsets, self._deformable_particle_counts, ) @@ -2241,7 +2193,7 @@ def _update_geometries_ovstage(self) -> None: if self._particle_points_query is not None: self._write_particle_q_slices_ovstage( self._particle_points_query, - particle_np, + particle_q, self._particle_visual_offsets, self._particle_visual_counts, ) @@ -2252,7 +2204,7 @@ def _update_geometries_ovstage(self) -> None: def _write_particle_q_slices_ovstage( self, query: Any, - particle_np: np.ndarray, + particle_q: wp.array, particle_offsets: list[int], particle_counts: list[int], ) -> None: @@ -2260,15 +2212,22 @@ def _write_particle_q_slices_ovstage( Args: query: ovstage query selecting the prims whose ``points`` attribute is written. - particle_np: Host copy of Newton particle positions [m], shape ``[total_particles, 3]``. - particle_offsets: Start index of each prim's slice into Newton's ``particle_q``. + particle_q: Flat world-space particle positions [m], shape ``[total_particles]``, + dtype ``wp.vec3f``. Slices are passed zero-copy as CUDA DLTensors. + particle_offsets: Start index of each prim's slice into :paramref:`particle_q`. particle_counts: Number of particles in each prim's slice. """ particle_slices = [ - _points_tensor_from_numpy(particle_np[particle_offset : particle_offset + particle_count]) + points_tensor_from_warp(particle_q[particle_offset : particle_offset + particle_count]) for particle_offset, particle_count in zip(particle_offsets, particle_counts, strict=True) ] + # The slices alias ``particle_q`` and are handed over zero-copy, so ovstage must not read + # them until the Warp kernels that wrote ``particle_q`` have finished. Passing the producing + # Warp stream as ``cuda_stream`` gives producer ordering: ovstage drains the work already + # queued on that stream before it touches the slices. That replaces the device-wide + # ``wp.synchronize_device()`` with stream-scoped ordering and removes the host copy; it is + # not a nonblocking handoff, and the ``.wait()`` below can still block the calling thread. self._stage.write_attribute( query, "points", @@ -2276,32 +2235,26 @@ def _write_particle_q_slices_ovstage( tensors=particle_slices, is_array=True, semantic=ovstage.AttributeSemantic.POINT, + cuda_stream=wp.get_stream(self._device).cuda_stream, ).wait() def _write_cable_points_ovstage(self) -> None: """Recompute world-space cable curve points on device and write them through ovstage.""" self._compute_cable_points_world() - # ovstage write_attribute needs one DLPack tensor per prim, not one flat ``particle_q`` - # plus offsets. Synchronize then copy to CPU numpy once (shared by both queries below): - # the ``points`` column is ``point3f[]`` (lanes=3), and ovstage's make_dltensor only - # accepts the lanes=3 dtype override on numpy arrays, not DLPack producers. A warp - # ``vec3f`` slice exports as ``(N, 3)`` lanes=1, which is rejected as a type mismatch - # against the lanes=3 column. - points_np = self._cable_points.numpy() - cable_slices = [] - point_offset = 0 - for segment_count in self._cable_segment_counts: - cable_slices.append(_points_tensor_from_numpy(points_np[point_offset : point_offset + segment_count + 1])) - point_offset += segment_count + 1 - + # The cached descriptors alias ``_cable_points`` and are handed over zero-copy, so ovstage + # must not read them until the kernel above has landed. Passing the producing Warp stream as + # ``cuda_stream`` gives producer ordering: ovstage drains the work already queued on that + # stream before it touches the slices. That keeps the handover off the host; it is not a + # nonblocking handoff, and the ``.wait()`` below can still block the calling thread. self._stage.write_attribute( self._cable_points_query, "points", ordinal=self._current_ordinal, - tensors=cable_slices, + tensors=self._cable_point_tensors, is_array=True, semantic=ovstage.AttributeSemantic.POINT, + cuda_stream=wp.get_stream(self._device).cuda_stream, ).wait() def _update_camera_ovstage( @@ -2328,15 +2281,15 @@ def _update_camera_ovstage( device=self._device, ) if self._camera_xform_query is not None: - # Synchronize then copy to CPU numpy: same lanes=16 constraint as object transforms above. - wp.synchronize_device(self._device) + # Stream-ordered zero-copy handoff, as for the object transforms above. self._stage.write_attribute( self._camera_xform_query, "omni:xform", ordinal=self._current_ordinal, - tensors=_xform_tensor_from_numpy(camera_transforms.numpy().reshape(-1, 4, 4)), + tensors=xform_tensor_from_warp(camera_transforms), is_array=False, semantic=ovstage.AttributeSemantic.MATRIX, + cuda_stream=wp.get_stream(self._device).cuda_stream, ).wait() def _render_ovstage(self, render_data: OVRTXRenderData) -> None: @@ -2430,6 +2383,10 @@ def _safe_destroy_path_list(path_list, name: str) -> None: self._particle_visual_counts = [] self._cable_segment_counts = [] self._cable_max_points = 0 + # Descriptors alias ``_cable_points``; drop them before the buffer so no cached + # DLTensor can outlive the device memory it points at. + self._cable_point_tensors = [] + self._cable_point_slices = [] self._cable_points = None self._cable_shape_ids = None self._cable_offsets = None diff --git a/source/isaaclab_ov/isaaclab_ov/sensors/contact_sensor/contact_sensor.py b/source/isaaclab_ov/isaaclab_ov/sensors/contact_sensor/contact_sensor.py index 757fe06a1681..9a92a878e134 100644 --- a/source/isaaclab_ov/isaaclab_ov/sensors/contact_sensor/contact_sensor.py +++ b/source/isaaclab_ov/isaaclab_ov/sensors/contact_sensor/contact_sensor.py @@ -409,6 +409,7 @@ def reset(self, env_ids: Sequence[int] | None = None, env_mask: wp.array | None self._data._net_forces_w, self._data._net_forces_w_history, self._data._force_matrix_w, + self._data._force_matrix_w_history, ], outputs=[ self._data._current_air_time, diff --git a/source/isaaclab_ov/isaaclab_ov/sensors/contact_sensor/kernels.py b/source/isaaclab_ov/isaaclab_ov/sensors/contact_sensor/kernels.py index 437355dc470d..a4de413a82f5 100644 --- a/source/isaaclab_ov/isaaclab_ov/sensors/contact_sensor/kernels.py +++ b/source/isaaclab_ov/isaaclab_ov/sensors/contact_sensor/kernels.py @@ -97,6 +97,7 @@ def reset_contact_sensor_kernel( net_forces_w: wp.array2d(dtype=wp.vec3f), net_forces_w_history: wp.array3d(dtype=wp.vec3f), force_matrix_w: wp.array3d(dtype=wp.vec3f), + force_matrix_w_history: wp.array4d(dtype=wp.vec3f), # outputs current_air_time: wp.array2d(dtype=wp.float32), last_air_time: wp.array2d(dtype=wp.float32), @@ -116,6 +117,8 @@ def reset_contact_sensor_kernel( net_forces_w: Net forces array. Shape is (num_envs, num_sensors). net_forces_w_history: Net forces history array. Shape is (num_envs, history_length, num_sensors). force_matrix_w: Force matrix array. Shape is (num_envs, num_sensors, num_filter_objects). + force_matrix_w_history: Force matrix history array. Shape is + (num_envs, history_length, num_sensors, num_filter_objects). current_air_time: Current air time array. Shape is (num_envs, num_sensors). last_air_time: Last air time array. Shape is (num_envs, num_sensors). current_contact_time: Current contact time array. Shape is (num_envs, num_sensors). @@ -142,6 +145,11 @@ def reset_contact_sensor_kernel( for f in range(num_filter_objects): force_matrix_w[env, sensor, f] = wp.vec3f(0.0) + if force_matrix_w_history: + for i in range(history_length): + for f in range(num_filter_objects): + force_matrix_w_history[env, i, sensor, f] = wp.vec3f(0.0) + # Reset air/contact time tracking if current_air_time: current_air_time[env, sensor] = 0.0 diff --git a/source/isaaclab_ov/isaaclab_ov/stage.py b/source/isaaclab_ov/isaaclab_ov/stage.py new file mode 100644 index 000000000000..e054e7f1476e --- /dev/null +++ b/source/isaaclab_ov/isaaclab_ov/stage.py @@ -0,0 +1,103 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Shared helpers for creating ovstage stages and describing their attribute columns. + +``ovstage`` is a hard dependency of ``isaaclab_ov``, so it is imported unconditionally here. +""" + +from __future__ import annotations + +import numpy as np +import ovstage +import warp as wp + +# DLDataType for a 4x4 double matrix (``omni:xform`` column). ovstage stores omni:xform as one +# 16-lane float64 element per prim; wp.mat44d maps to the same layout via __dlpack__. +OVSTAGE_XFORM_DTYPE = ovstage.DLDataType(code=ovstage.DLDataTypeCode.kDLFloat, bits=64, lanes=16) + +# DLDataType for a float32 3-vector (``points`` column). ovstage stores ``point3f[] points`` as one +# 3-lane float32 element per vertex. +OVSTAGE_POINT_DTYPE = ovstage.DLDataType(code=ovstage.DLDataTypeCode.kDLFloat, bits=32, lanes=3) + + +def create_ovstage(name: str) -> ovstage.Stage: + """Create an ovstage stage using Isaac Lab's process-wide stage configuration. + + ovstage's hierarchy computation model drives its automatic world-transform updates. It is + process-scoped rather than per-stage: ovstage applies it when the first process reference is + acquired and raises if a later stage asks for a conflicting model while another stage is live. + Every Isaac Lab stage is therefore created through this helper so the whole process agrees on + one model. + + :attr:`~ovstage.HierarchyComputationModel.CPU_INCREMENTAL` is requested explicitly rather than + left implicit, so the model in force is visible at the call site. + :attr:`~ovstage.HierarchyComputationModel.GPU_INCREMENTAL` is currently not working - objects + are out-of-place. Needs investigation + + Args: + name: Instance name used for ovstage diagnostics. + + Returns: + The created :class:`ovstage.Stage`. + """ + config = ovstage.StageConfig( + runtime_default_hierarchy_computation_model=ovstage.HierarchyComputationModel.CPU_INCREMENTAL + ) + return ovstage.Stage(name, config=config) + + +def xform_tensor_from_numpy(xforms: np.ndarray) -> ovstage.DLTensor: + """Wrap a ``(N, 4, 4)`` float64 host array as a 16-lane DLTensor for ``omni:xform`` writes. + + Args: + xforms: Array of shape ``(N, 4, 4)`` with dtype ``float64``. + + Returns: + A :class:`ovstage.DLTensor` with shape ``[N]`` and ``lanes=16``. + """ + flat = np.ascontiguousarray(xforms, dtype=np.float64).reshape(-1) + return ovstage.make_dltensor(flat, dtype=OVSTAGE_XFORM_DTYPE, shape=[xforms.shape[0]]) + + +def xform_tensor_from_warp(xforms: wp.array) -> ovstage.DLTensor: + """Describe a warp ``mat44d`` array as a 16-lane DLTensor for ``omni:xform`` writes. + + The array is consumed zero-copy through DLPack: a warp ``mat44d`` exports as ``(N, 4, 4)`` + ``lanes=1``, and ovstage folds the trailing matrix axes into the ``lanes=16`` the column + expects. A device array therefore reaches ovstage without a host round-trip. + + The caller owns the data: the returned tensor must stay alive until the consuming write + completes, and that write must be ordered against the kernels that produced + :paramref:`xforms` — pass their Warp stream as ``write_attribute(cuda_stream=...)``. + + Args: + xforms: Warp array of shape ``[N]`` and dtype :class:`warp.mat44d`. + + Returns: + A :class:`ovstage.DLTensor` with shape ``[N]`` and ``lanes=16``. + """ + return ovstage.make_dltensor(xforms, dtype=OVSTAGE_XFORM_DTYPE) + + +def points_tensor_from_warp(points: wp.array) -> ovstage.DLTensor: + """Describe a warp ``vec3f`` array as a 3-lane DLTensor for ``points`` writes. + + The array is consumed zero-copy through DLPack: a warp ``vec3f`` exports as ``(N, 3)`` + ``lanes=1``, and ovstage folds the trailing component axis into the ``lanes=3`` the + ``point3f[]`` column expects. A device array therefore reaches ovstage without a host + round-trip. + + The caller owns the data: the returned tensor must stay alive until the consuming write + completes, and that write must be ordered against the kernels that produced + :paramref:`points` — pass their Warp stream as ``write_attribute(cuda_stream=...)``. + + Args: + points: Warp array of shape ``[N]`` and dtype :class:`warp.vec3f`. + + Returns: + A :class:`ovstage.DLTensor` with shape ``[N]`` and ``lanes=3``. + """ + return ovstage.make_dltensor(points, dtype=OVSTAGE_POINT_DTYPE) diff --git a/source/isaaclab_ov/test/physics/test_ovphysx_scene_data_backend.py b/source/isaaclab_ov/test/physics/test_ovphysx_scene_data_backend.py index a8e32ad9f61a..bad5af8750ce 100644 --- a/source/isaaclab_ov/test/physics/test_ovphysx_scene_data_backend.py +++ b/source/isaaclab_ov/test/physics/test_ovphysx_scene_data_backend.py @@ -455,6 +455,7 @@ def test_manager_logs_when_serialized_stage_has_no_envs(caplog): def test_manager_attaches_and_releases_owned_ovstage(monkeypatch): """The manager owns OVStage from population through PhysX release.""" + import isaaclab_ov.physics.ovphysx_manager as om_mod from isaaclab_ov.physics import OvPhysxManager events = [] @@ -491,7 +492,6 @@ def release(self): events.append(("release",)) fake_ovstage = ModuleType("ovstage") - fake_ovstage.Stage = FakeStage fake_ovstage.PopulationDomain = SimpleNamespace(ALL="all") fake_ovstage.population = SimpleNamespace( open_usd_from_string=lambda stage, usda, ordinal, domains: events.append( @@ -499,6 +499,9 @@ def release(self): ) ) monkeypatch.setitem(sys.modules, "ovstage", fake_ovstage) + # The manager builds its stage through the shared helper so every stage in the process gets + # the same ovstage configuration; that is the seam to fake, not ``ovstage.Stage``. + monkeypatch.setattr(om_mod, "create_ovstage", FakeStage) previous_physx = OvPhysxManager._physx previous_ovstage = getattr(OvPhysxManager, "_ovstage", None) @@ -535,6 +538,7 @@ def release(self): def test_manager_destroys_ovstage_when_population_fails(monkeypatch): """A failed in-memory population does not leak its OVStage allocation.""" + import isaaclab_ov.physics.ovphysx_manager as om_mod from isaaclab_ov.physics import OvPhysxManager destroyed = [] @@ -550,10 +554,10 @@ def fail_population(*args, **kwargs): raise RuntimeError("population failed") fake_ovstage = ModuleType("ovstage") - fake_ovstage.Stage = FakeStage fake_ovstage.PopulationDomain = SimpleNamespace(ALL="all") fake_ovstage.population = SimpleNamespace(open_usd_from_string=fail_population) monkeypatch.setitem(sys.modules, "ovstage", fake_ovstage) + monkeypatch.setattr(om_mod, "create_ovstage", FakeStage) previous_ovstage = getattr(OvPhysxManager, "_ovstage", None) OvPhysxManager._ovstage = None diff --git a/source/isaaclab_ov/test/sensors/test_contact_sensor_kernels.py b/source/isaaclab_ov/test/sensors/test_contact_sensor_kernels.py new file mode 100644 index 000000000000..43090133ca33 --- /dev/null +++ b/source/isaaclab_ov/test/sensors/test_contact_sensor_kernels.py @@ -0,0 +1,59 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Unit tests for OvPhysx contact-sensor Warp kernels.""" + +import numpy as np +import warp as wp +from isaaclab_ov.sensors.contact_sensor.kernels import reset_contact_sensor_kernel + + +def test_reset_contact_sensor_kernel_clears_selected_force_matrix_history(): + """Reset clears filtered-force history only for selected environments.""" + num_envs = 2 + num_sensors = 1 + history_length = 2 + num_filter_shapes = 1 + device = "cpu" + env_mask = wp.array([True, False], dtype=wp.bool, device=device) + + net_forces_w = wp.zeros((num_envs, num_sensors), dtype=wp.vec3f, device=device) + net_forces_w_history = wp.zeros((num_envs, history_length, num_sensors), dtype=wp.vec3f, device=device) + force_matrix_w = wp.zeros((num_envs, num_sensors, num_filter_shapes), dtype=wp.vec3f, device=device) + force_matrix_w_history = wp.array( + np.ones((num_envs, history_length, num_sensors, num_filter_shapes, 3), dtype=np.float32), + dtype=wp.vec3f, + device=device, + ) + current_air_time = wp.zeros((num_envs, num_sensors), dtype=wp.float32, device=device) + last_air_time = wp.zeros((num_envs, num_sensors), dtype=wp.float32, device=device) + current_contact_time = wp.zeros((num_envs, num_sensors), dtype=wp.float32, device=device) + last_contact_time = wp.zeros((num_envs, num_sensors), dtype=wp.float32, device=device) + + wp.launch( + reset_contact_sensor_kernel, + dim=(num_envs, num_sensors), + inputs=[ + history_length, + num_filter_shapes, + env_mask, + net_forces_w, + net_forces_w_history, + force_matrix_w, + force_matrix_w_history, + ], + outputs=[ + current_air_time, + last_air_time, + current_contact_time, + last_contact_time, + None, + None, + ], + device=device, + ) + + np.testing.assert_array_equal(force_matrix_w_history.numpy()[0], 0.0) + np.testing.assert_array_equal(force_matrix_w_history.numpy()[1], 1.0) diff --git a/source/isaaclab_ov/test/test_ovrtx_clone_plan.py b/source/isaaclab_ov/test/test_ovrtx_clone_plan.py index e167ba979d9e..dc1256b06065 100644 --- a/source/isaaclab_ov/test/test_ovrtx_clone_plan.py +++ b/source/isaaclab_ov/test/test_ovrtx_clone_plan.py @@ -276,7 +276,7 @@ def _record_xforms(value: np.ndarray) -> str: xforms.append(value.copy()) return "root_xforms" - monkeypatch.setattr("isaaclab_ov.renderers.ovrtx_renderer._xform_tensor_from_numpy", _record_xforms) + monkeypatch.setattr("isaaclab_ov.renderers.ovrtx_renderer.xform_tensor_from_numpy", _record_xforms) renderer._clone_sources_ovstage() diff --git a/source/isaaclab_ov/test/test_ovrtx_deformable_bindings.py b/source/isaaclab_ov/test/test_ovrtx_deformable_bindings.py index 75ee17a08a8c..88d9f233485e 100644 --- a/source/isaaclab_ov/test/test_ovrtx_deformable_bindings.py +++ b/source/isaaclab_ov/test/test_ovrtx_deformable_bindings.py @@ -27,6 +27,9 @@ if not _MISSING_MODULES: import isaaclab_ov.renderers.ovrtx_renderer as ovrtx_renderer_module # noqa: E402 + + # ovstage is an unconditional dependency of isaaclab_ov, so it is importable here. + import ovstage # noqa: E402 from isaaclab_newton.physics import NewtonManager # noqa: E402 from isaaclab_ov.renderers import OVRTXRendererCfg # noqa: E402 from isaaclab_ov.renderers.ovrtx_renderer import OVRTXRenderer # noqa: E402 @@ -546,3 +549,44 @@ def _capture_launch(*args, **kwargs): # only guard against that -- the downgrade does not raise, it just renders from a stale copy. assert renderer._cable_points_binding.write_kwargs["data_access"] is DataAccess.ASYNC assert renderer._cable_points_binding.write_kwargs["cuda_stream"] == 1234 + + +@pytest.mark.skipif(not wp.get_cuda_device_count(), reason="requires a CUDA device") +def test_write_particle_q_slices_ovstage_passes_device_slices_zero_copy(): + """The ovstage points write hands ``particle_q`` slices to ovstage as CUDA DLTensors, without a host copy.""" + renderer, _backend = _make_renderer_without_backend(device="cuda:0") + particle_q = wp.array( + [ + wp.vec3f(-1.0, -1.0, -1.0), + wp.vec3f(1.0, 2.0, 3.0), + wp.vec3f(4.0, 5.0, 6.0), + wp.vec3f(7.0, 8.0, 9.0), + ], + dtype=wp.vec3f, + device="cuda:0", + ) + writes: list[dict] = [] + + def _write(query, attribute, **kwargs): + writes.append({"query": query, "attribute": attribute, **kwargs}) + return SimpleNamespace(wait=lambda: None) + + renderer._stage = SimpleNamespace(write_attribute=_write) + renderer._current_ordinal = 7 + + renderer._write_particle_q_slices_ovstage("points_query", particle_q, [1], [3]) + + assert len(writes) == 1 + assert writes[0]["attribute"] == "points" + assert writes[0]["is_array"] is True + # The slices alias ``particle_q``, so ovstage is handed the producing Warp stream to order its + # read against, rather than the caller blocking the host on a device synchronize. + assert writes[0]["cuda_stream"] == wp.get_stream("cuda:0").cuda_stream + tensors = writes[0]["tensors"] + assert len(tensors) == 1 + # A zero-copy device view: the descriptor points straight at the slice's own CUDA buffer with + # the trailing component axis folded into point3f's three lanes. + assert tensors[0].device.device_type.value == ovstage.DLDeviceType.kDLCUDA + assert tensors[0].data == particle_q[1:4].ptr + assert tensors[0].shape_tuple == (3,) + assert tensors[0].dtype.lanes == 3 diff --git a/source/isaaclab_ov/test/test_ovrtx_renderer_contract.py b/source/isaaclab_ov/test/test_ovrtx_renderer_contract.py index e4aa92214740..9585105d41ee 100644 --- a/source/isaaclab_ov/test/test_ovrtx_renderer_contract.py +++ b/source/isaaclab_ov/test/test_ovrtx_renderer_contract.py @@ -370,26 +370,15 @@ def test_ovrtx_use_ovstage_defaults_to_disabled(monkeypatch): assert ovrtx_use_ovstage_enabled() is False -def test_ovrtx_use_ovstage_enabled_when_requested_and_available(monkeypatch): - """Setting the variable to 1 selects the ovstage path when ovstage is importable.""" +def test_ovrtx_use_ovstage_enabled_when_requested(monkeypatch): + """Setting the variable to 1 selects the ovstage path.""" monkeypatch.setenv("ISAAC_LAB_OVRTX_USE_OVSTAGE", "1") - monkeypatch.setattr(ovrtx_renderer_module, "_OVSTAGE_AVAILABLE", True) assert ovrtx_use_ovstage_enabled() is True -def test_ovrtx_use_ovstage_raises_when_requested_but_unavailable(monkeypatch): - """An explicit opt-in must fail loudly rather than silently falling back to the legacy path.""" - monkeypatch.setenv("ISAAC_LAB_OVRTX_USE_OVSTAGE", "1") - monkeypatch.setattr(ovrtx_renderer_module, "_OVSTAGE_AVAILABLE", False) - - with pytest.raises(RuntimeError, match="uv run --extra ovrtx"): - ovrtx_use_ovstage_enabled() - - def test_ovrtx_use_ovstage_rejects_non_boolean_values(monkeypatch): """Values other than 0/1 are a configuration error, not a silent disable.""" monkeypatch.setenv("ISAAC_LAB_OVRTX_USE_OVSTAGE", "true") - monkeypatch.setattr(ovrtx_renderer_module, "_OVSTAGE_AVAILABLE", True) with pytest.raises(ValueError, match="Expected 0 or 1"): ovrtx_use_ovstage_enabled() diff --git a/source/isaaclab_rl/changelog.d/mh-fix-leapp-openusd-thread-limit.skip b/source/isaaclab_rl/changelog.d/mh-fix-leapp-openusd-thread-limit.skip new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/source/isaaclab_rl/test/export/test_leapp_export_flow.py b/source/isaaclab_rl/test/export/test_leapp_export_flow.py index 8615d7738bfc..854382cc8a7c 100644 --- a/source/isaaclab_rl/test/export/test_leapp_export_flow.py +++ b/source/isaaclab_rl/test/export/test_leapp_export_flow.py @@ -11,6 +11,7 @@ from __future__ import annotations +import os import subprocess import sys import tempfile @@ -27,8 +28,10 @@ _SUBPROCESS_TIMEOUT = 600 _CHECKPOINT_BATCH_TIMEOUT = 1200 _OUTPUT_TAIL_CHARS = 5000 -# TODO: Remove once usd-core>=26.5 is the minimum. Earlier OpenUSD releases -# can corrupt the heap while parsing the Newton Franka payload concurrently. +# TODO: Remove once usd-core>=26.5 is the minimum. Earlier OpenUSD releases can +# corrupt the heap while parsing the Newton Franka payload concurrently. OpenUSD +# reads PXR_WORK_THREAD_LIMIT during process startup, before AppLauncher can apply +# its matching SimulationApp limit. _LEAPP_TEST_CPU_THREAD_LIMIT = 1 @@ -132,6 +135,7 @@ def _run_checked( list(cmd), cwd=_REPO_ROOT, capture_output=True, + env={**os.environ, "PXR_WORK_THREAD_LIMIT": str(_LEAPP_TEST_CPU_THREAD_LIMIT)}, text=True, timeout=timeout, ) @@ -266,6 +270,15 @@ def test_initialized_checkpoints(initialized_checkpoints: Path): assert not missing, f"Missing initialized checkpoints for: {', '.join(missing)}" +def test_openusd_thread_limit_is_set_before_subprocess_startup(): + """Assert LEAPP subprocesses start with OpenUSD concurrency disabled.""" + result = _run_checked( + [sys.executable, "-c", "import os; print(os.environ['PXR_WORK_THREAD_LIMIT'])"], + label="OpenUSD thread-limit probe", + ) + assert result.stdout.strip() == str(_LEAPP_TEST_CPU_THREAD_LIMIT) + + @pytest.mark.parametrize(("backend", "task_name"), _export_cases()) def test_leapp_export_flow(backend: ExportFlowBackend, task_name: str, initialized_checkpoints: Path): """Export one backend/task pair using the shared initialized checkpoint.""" diff --git a/tools/environ_docs.py b/tools/environ_docs.py index 69403e9916d2..c3f9edd9a038 100644 --- a/tools/environ_docs.py +++ b/tools/environ_docs.py @@ -92,6 +92,30 @@ class EnvironmentDocRow: rl_libraries: dict[str, list[str]] presets: dict[PresetTarget, list[str]] | None agent_preset_compatibility: dict[str, tuple[str, ...]] = field(default_factory=dict) + supports_warp_frontend: bool = False + + +def _supports_warp_frontend(task_name: str, workflow: str, presets: dict[PresetTarget, list[str]] | None) -> bool: + """Return whether a task can run through ``--frontend warp``.""" + if presets is None or "newton_mjwarp" not in presets.get(PresetTarget.PHYSICS, []): + return False + + try: + from isaaclab_experimental.envs.frontend import FrontendIncompatibleError, WarpFrontend + + from isaaclab_tasks.utils.hydra import resolve_presets + from isaaclab_tasks.utils.parse_cfg import load_cfg_from_registry + + cfg = load_cfg_from_registry(task_name, "env_cfg_entry_point") + cfg = resolve_presets(cfg, selected=("newton_mjwarp",)) + if workflow == "Direct": + try: + return WarpFrontend._resolve_direct_warp_class(task_name, cfg) is not None + except FrontendIncompatibleError: + return False + return WarpFrontend.check_compatibility(cfg) is None + except (ImportError, gym.error.Error): + return False def is_training_task(task_id: str) -> bool: @@ -526,6 +550,7 @@ def collect_environment_doc_rows( for agent, presets in spec.kwargs.get("agent_preset_compatibility", {}).items() if agent in spec.kwargs }, + supports_warp_frontend=_supports_warp_frontend(spec.id, workflow, preset_map), ) ) @@ -632,6 +657,12 @@ def render_environment_browser_task_rows( rendered_values += f", {json.dumps(row.agent_preset_compatibility, sort_keys=True)}" if preview_image: rendered_values += f", {json.dumps(preview_image)}" + if row.supports_warp_frontend: + if not row.agent_preset_compatibility and not preview_image: + rendered_values += ", {}" + if not preview_image: + rendered_values += ', ""' + rendered_values += ", true" lines.append(f" [{rendered_values}],") lines.append(" ];") return "\n".join(lines) diff --git a/tools/test/test_environ_docs.py b/tools/test/test_environ_docs.py index cd3b7e846e51..df41aec9a860 100644 --- a/tools/test/test_environ_docs.py +++ b/tools/test/test_environ_docs.py @@ -382,6 +382,7 @@ def test_environment_browser_rows_include_concrete_core_and_contributed_selector "rsl_rl_cfg_entry_point": ("rgb",), "rsl_rl_feature_cfg_entry_point": ("resnet18", "theia_tiny"), }, + supports_warp_frontend=True, ), EnvironmentDocRow( task_name="IsaacContrib-Cartpole", @@ -391,7 +392,13 @@ def test_environment_browser_rows_include_concrete_core_and_contributed_selector ), ] rows.reverse() - rendered = render_environment_browser_task_rows(rows, {"IsaacContrib-Cartpole": "tasks/classic/cartpole.jpg"}) + rendered = render_environment_browser_task_rows( + rows, + { + "Isaac-Cartpole": "tasks/classic/cartpole.jpg", + "IsaacContrib-Cartpole": "tasks/classic/cartpole.jpg", + }, + ) original = ( f" {ENVIRONMENT_BROWSER_TASKS_START_MARKER}\n" " const taskRows = [];\n" @@ -410,6 +417,7 @@ def test_environment_browser_rows_include_concrete_core_and_contributed_selector assert '"IsaacContrib-Cartpole"' in updated assert '"ovphysx"' in updated assert '"tasks/classic/cartpole.jpg"' in updated + assert '"tasks/classic/cartpole.jpg", true' in updated assert updated.index('"Isaac-Cartpole"') < updated.index('"IsaacContrib-Cartpole"') assert "const preserved = true;" in updated diff --git a/uv.lock b/uv.lock index 686ea79ec84a..a70b4d0e001c 100644 --- a/uv.lock +++ b/uv.lock @@ -1764,7 +1764,7 @@ wheels = [ [[package]] name = "isaaclab" -version = "16.2.3" +version = "16.4.0" source = { editable = "source/isaaclab" } [[package]] @@ -1774,12 +1774,16 @@ source = { editable = "source/isaaclab_assets" } dependencies = [ { name = "isaaclab", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, { name = "isaaclab-contrib", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "isaaclab-newton", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "isaaclab-physx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, ] [package.metadata] requires-dist = [ { name = "isaaclab", editable = "source/isaaclab" }, { name = "isaaclab-contrib", editable = "source/isaaclab_contrib" }, + { name = "isaaclab-newton", editable = "source/isaaclab_newton" }, + { name = "isaaclab-physx", editable = "source/isaaclab_physx" }, ] [[package]] @@ -1823,7 +1827,6 @@ dependencies = [ { name = "isaaclab-tasks", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, { name = "isaaclab-tasks-experimental", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, { name = "isaaclab-visualizers", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, - { name = "isaacsim-asset-isolated", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, { name = "lazy-loader", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, { name = "matplotlib", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, { name = "meshio", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, @@ -1870,7 +1873,6 @@ dependencies = [ all = [ { name = "aiohttp", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, { name = "gym", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, - { name = "isaacsim", extra = ["all", "extscache"], marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, { name = "onnxscript", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, { name = "ovphysx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, { name = "ovrtx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, @@ -1886,6 +1888,10 @@ all = [ { name = "tqdm", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, { name = "viser", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, ] +importers = [ + { name = "isaacsim-asset-isolated", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "tinyobjloader", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] isaacsim = [ { name = "isaacsim", extra = ["all", "extscache"], marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, ] @@ -2010,7 +2016,7 @@ requires-dist = [ { name = "isaaclab", editable = "source/isaaclab" }, { name = "isaaclab-assets", editable = "source/isaaclab_assets" }, { name = "isaaclab-contrib", editable = "source/isaaclab_contrib" }, - { name = "isaaclab-dev", extras = ["sb3", "skrl", "rl-games", "rsl-rl", "viser", "rerun", "isaacsim", "ov"], marker = "extra == 'all'" }, + { name = "isaaclab-dev", extras = ["sb3", "skrl", "rl-games", "rsl-rl", "viser", "rerun", "ov"], marker = "extra == 'all'" }, { name = "isaaclab-experimental", editable = "source/isaaclab_experimental" }, { name = "isaaclab-mimic", marker = "extra == 'mimic'", editable = "source/isaaclab_mimic" }, { name = "isaaclab-mimic", marker = "extra == 'teleop'", editable = "source/isaaclab_mimic" }, @@ -2025,7 +2031,7 @@ requires-dist = [ { name = "isaaclab-visualizers", editable = "source/isaaclab_visualizers" }, { name = "isaacsim", extras = ["all", "extscache"], marker = "extra == 'isaacsim'", specifier = "==6.0.1.0" }, { name = "isaacsim", extras = ["all", "extscache"], marker = "extra == 'teleop'", specifier = "==6.0.1.0" }, - { name = "isaacsim-asset-isolated", specifier = ">=6.0,<6.1" }, + { name = "isaacsim-asset-isolated", marker = "extra == 'importers'", specifier = ">=6.0,<6.1" }, { name = "isaacteleop", extras = ["retargeters", "ui", "cloudxr"], marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'teleop'", specifier = "~=1.4.0" }, { name = "junitparser", marker = "extra == 'test'" }, { name = "lazy-loader", specifier = ">=0.4" }, @@ -2094,6 +2100,7 @@ requires-dist = [ { name = "starlette", specifier = ">=0.46.0,<0.50" }, { name = "tensorboard" }, { name = "timm", marker = "extra == 'rlinf'", specifier = ">=1.0.14" }, + { name = "tinyobjloader", marker = "extra == 'importers'", specifier = "==2.0.0rc13" }, { name = "torch", marker = "(platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'linux' and sys_platform != 'win32')", specifier = ">=2.11" }, { name = "torch", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'", specifier = ">=2.11", index = "https://download.pytorch.org/whl/cu130" }, { name = "torch", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'", specifier = ">=2.11", index = "https://download.pytorch.org/whl/cu128" }, @@ -2115,7 +2122,7 @@ requires-dist = [ { name = "viser", marker = "extra == 'viser'", specifier = ">=1.0.16" }, { name = "warp-lang", specifier = "==1.16.0" }, ] -provides-extras = ["tetrahedralization", "video", "test", "sb3", "skrl", "rl-games", "rsl-rl", "viser", "rerun", "isaacsim", "ov", "ovphysx", "ovrtx", "mimic", "teleop", "rlinf", "leapp", "all"] +provides-extras = ["tetrahedralization", "video", "importers", "test", "sb3", "skrl", "rl-games", "rsl-rl", "viser", "rerun", "isaacsim", "ov", "ovphysx", "ovrtx", "mimic", "teleop", "rlinf", "leapp", "all"] [[package]] name = "isaaclab-experimental" @@ -2147,7 +2154,7 @@ requires-dist = [ [[package]] name = "isaaclab-newton" -version = "5.2.0" +version = "5.3.0" source = { editable = "source/isaaclab_newton" } dependencies = [ { name = "isaaclab", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, @@ -2158,7 +2165,7 @@ requires-dist = [{ name = "isaaclab", editable = "source/isaaclab" }] [[package]] name = "isaaclab-ov" -version = "2.0.4" +version = "2.1.0" source = { editable = "source/isaaclab_ov" } dependencies = [ { name = "isaaclab", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, @@ -2173,7 +2180,7 @@ requires-dist = [ [[package]] name = "isaaclab-physx" -version = "5.0.1" +version = "5.1.0" source = { editable = "source/isaaclab_physx" } dependencies = [ { name = "isaaclab", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, @@ -2212,7 +2219,7 @@ requires-dist = [ [[package]] name = "isaaclab-tasks" -version = "16.5.0" +version = "17.0.0" source = { editable = "source/isaaclab_tasks" } dependencies = [ { name = "isaaclab", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, @@ -2253,7 +2260,7 @@ requires-dist = [{ name = "isaaclab", editable = "source/isaaclab" }] [[package]] name = "isaaclab-visualizers" -version = "1.6.0" +version = "1.7.0" source = { editable = "source/isaaclab_visualizers" } dependencies = [ { name = "isaaclab", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },