From 07ddf69c0be275d7f9fa9fb821fdb1c9659baee6 Mon Sep 17 00:00:00 2001 From: Mustafa H <34825877+StafaH@users.noreply.github.com> Date: Thu, 20 Aug 2026 15:20:37 -0700 Subject: [PATCH 01/15] Add Mustafa Haiderbhai to developers (#7227) ## Description Adds Mustafa Haiderbhai to the alphabetized developer list. No dependencies are required. ## Type of change - [x] Documentation update ## Testing - `uv run --frozen isaaclab -f` - `uv run --isolated --extra test -- make -C docs current-docs` ## Checklist - [x] I have read and understood the contribution guidelines - [x] I have run the pre-commit checks - [x] I have made the corresponding documentation change - [x] My changes generate no new warnings - [x] Tests are not applicable because this only updates the developer list - [x] A changelog fragment is not applicable because no source package is touched - [x] I have added my name to `CONTRIBUTORS.md` --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) 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 From 4d39d58c8d2ded88bebd3246bb587a0f7fc657ec Mon Sep 17 00:00:00 2001 From: Mustafa H <34825877+StafaH@users.noreply.github.com> Date: Thu, 20 Aug 2026 22:36:38 -0700 Subject: [PATCH 02/15] [Docs] Move migration guide (#7231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description > [!IMPORTANT] > Confirm the pull request base before submitting. Target `develop` for all > contributions. The `release/3.0.0-beta2` branch is a frozen stable landing > snapshot and is not used for ongoing maintenance. Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. Fixes # (issue) ## Type of change - Bug fix (non-breaking change which fixes an issue) - New feature (non-breaking change which adds functionality) - Breaking change (existing functionality will not work without user modification) - Documentation update ## Screenshots Please attach before and after screenshots of the change if applicable. ## Checklist - [ ] I have read and understood the [contribution guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html) - [ ] I have run the [`pre-commit` checks](https://pre-commit.com/) with `./isaaclab.sh --format` - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added a changelog fragment under `source//changelog.d/` for every touched package (do **not** edit `CHANGELOG.rst` or bump `extension.toml` — CI handles that) - [ ] I have added my name to the `CONTRIBUTORS.md` or my name already exists there --- docs/index.rst | 13 +- .../migrating_from_omniisaacgymenvs.rst | 1020 ----------------- .../source/migration/migrating_from_orbit.rst | 149 --- .../migration/migrating_to_isaaclab_3-0.rst | 7 + docs/source/refs/migration.rst | 202 ---- docs/source/refs/release_notes.rst | 13 - 6 files changed, 8 insertions(+), 1396 deletions(-) delete mode 100644 docs/source/migration/migrating_from_omniisaacgymenvs.rst delete mode 100644 docs/source/migration/migrating_from_orbit.rst delete mode 100644 docs/source/refs/migration.rst diff --git a/docs/index.rst b/docs/index.rst index f99c2daae59a..5709f6aaebb1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -92,6 +92,7 @@ Table of Contents source/setup/environments source/setup/quickstart source/refs/reference_architecture/index + source/migration/migrating_to_isaaclab_3-0 .. toctree:: @@ -157,17 +158,6 @@ Table of Contents source/policy_deployment/index -.. toctree:: - :maxdepth: 1 - :caption: Migration Guides - :titlesonly: - - source/migration/migrating_to_isaaclab_3-0 - source/migration/migrating_deformables - source/migration/migrating_from_isaacgymenvs - source/migration/migrating_from_omniisaacgymenvs - source/migration/migrating_from_orbit - .. toctree:: :maxdepth: 1 :caption: Source API @@ -182,7 +172,6 @@ Table of Contents source/refs/additional_resources source/refs/contributing source/refs/troubleshooting - source/refs/migration source/refs/issues source/refs/release_notes source/refs/changelog diff --git a/docs/source/migration/migrating_from_omniisaacgymenvs.rst b/docs/source/migration/migrating_from_omniisaacgymenvs.rst deleted file mode 100644 index e59de5a0be84..000000000000 --- a/docs/source/migration/migrating_from_omniisaacgymenvs.rst +++ /dev/null @@ -1,1020 +0,0 @@ -.. _migrating-from-omniisaacgymenvs: - -From OmniIsaacGymEnvs -===================== - -.. currentmodule:: isaaclab - - -`OmniIsaacGymEnvs`_ was a reinforcement learning framework using the Isaac Sim platform. -Features from OmniIsaacGymEnvs have been integrated into the Isaac Lab framework. -We have updated OmniIsaacGymEnvs to Isaac Sim version 4.0.0 to support the migration process -to Isaac Lab. Moving forward, OmniIsaacGymEnvs will be deprecated and future development -will continue in Isaac Lab. - -.. note:: - - The following changes are with respect to Isaac Lab 1.0 release. Please refer to the `release notes`_ for any changes - in the future releases. - -Task Config Setup -~~~~~~~~~~~~~~~~~ - -In OmniIsaacGymEnvs, task config files were defined in ``.yaml`` format. With Isaac Lab, configs are now specified -using a specialized Python class :class:`~isaaclab.utils.configclass`. The -:class:`~isaaclab.utils.configclass` module provides a wrapper on top of Python's ``dataclasses`` module. -Each environment should specify its own config class annotated by ``@configclass`` that inherits from the -:class:`~envs.DirectRLEnvCfg` class, which can include simulation parameters, environment scene parameters, -robot parameters, and task-specific parameters. - -Below is an example skeleton of a task config class: - -.. code-block:: python - - from isaaclab.envs import DirectRLEnvCfg - from isaaclab.scene import InteractiveSceneCfg - from isaaclab.sim import SimulationCfg - - @configclass - class MyEnvCfg(DirectRLEnvCfg): - # simulation - sim: SimulationCfg = SimulationCfg() - # robot - robot_cfg: ArticulationCfg = ArticulationCfg() - # scene - scene: InteractiveSceneCfg = InteractiveSceneCfg() - # env - decimation = 2 - episode_length_s = 5.0 - action_space = 1 - observation_space = 4 - state_space = 0 - # task-specific parameters - ... - -Simulation Config ------------------ - -Simulation related parameters are defined as part of the :class:`~isaaclab.sim.SimulationCfg` class, -which is a :class:`~isaaclab.utils.configclass` module that holds simulation parameters such as ``dt``, -``device``, and ``gravity``. Each task config must have a variable named ``sim`` defined that holds the type -:class:`~isaaclab.sim.SimulationCfg`. - -Simulation parameters for articulations and rigid bodies such as ``num_position_iterations``, ``num_velocity_iterations``, -``contact_offset``, ``rest_offset``, ``bounce_threshold_velocity``, ``max_depenetration_velocity`` can all -be specified on a per-actor basis in the config class for each individual articulation and rigid body. - -When running simulation on the GPU, buffers in PhysX require pre-allocation for computing and storing -information such as contacts, collisions and aggregate pairs. These buffers may need to be adjusted -depending on the complexity of the environment, the number of expected contacts and collisions, -and the number of actors in the environment. The :class:`~isaaclab.sim.PhysxCfg` class provides access -for setting the GPU buffer dimensions. - -+--------------------------------------------------+---------------------------------------------------------------+ -|| || | -|| || | -|| # OmniIsaacGymEnvs || # IsaacLab | -|| sim: || sim: SimulationCfg = SimulationCfg( | -|| || device = "cuda:0" # can be "cpu", "cuda", "cuda:" | -|| dt: 0.0083 # 1/120 s || dt=1 / 120, | -|| use_gpu_pipeline: ${eq:${...pipeline},"gpu"} || # use_gpu_pipeline is deduced from the device | -|| use_fabric: True || use_fabric=True, | -|| enable_scene_query_support: False || enable_scene_query_support=False, | -|| disable_contact_processing: False || | -|| gravity: [0.0, 0.0, -9.81] || gravity=(0.0, 0.0, -9.81), | -|| || | -|| default_physics_material: || physics_material=RigidBodyMaterialCfg( | -|| static_friction: 1.0 || static_friction=1.0, | -|| dynamic_friction: 1.0 || dynamic_friction=1.0, | -|| restitution: 0.0 || restitution=0.0 | -|| || ) | -|| physx: || physx: PhysxCfg = PhysxCfg( | -|| worker_thread_count: ${....num_threads} || # worker_thread_count is no longer needed | -|| solver_type: ${....solver_type} || solver_type=1, | -|| use_gpu: ${contains:"cuda",${....sim_device}} || # use_gpu is deduced from the device | -|| solver_position_iteration_count: 4 || max_position_iteration_count=4, | -|| solver_velocity_iteration_count: 0 || max_velocity_iteration_count=0, | -|| contact_offset: 0.02 || # moved to actor config | -|| rest_offset: 0.001 || # moved to actor config | -|| bounce_threshold_velocity: 0.2 || bounce_threshold_velocity=0.2, | -|| friction_offset_threshold: 0.04 || friction_offset_threshold=0.04, | -|| friction_correlation_distance: 0.025 || friction_correlation_distance=0.025, | -|| enable_sleeping: True || # enable_sleeping is no longer needed | -|| enable_stabilization: True || enable_stabilization=True, | -|| max_depenetration_velocity: 100.0 || # moved to RigidBodyPropertiesCfg | -|| || | -|| gpu_max_rigid_contact_count: 524288 || gpu_max_rigid_contact_count=2**23, | -|| gpu_max_rigid_patch_count: 81920 || gpu_max_rigid_patch_count=5 * 2**15, | -|| gpu_found_lost_pairs_capacity: 1024 || gpu_found_lost_pairs_capacity=2**21, | -|| gpu_found_lost_aggregate_pairs_capacity: 262144 || gpu_found_lost_aggregate_pairs_capacity=2**25, | -|| gpu_total_aggregate_pairs_capacity: 1024 || gpu_total_aggregate_pairs_capacity=2**21, | -|| gpu_heap_capacity: 67108864 || gpu_heap_capacity=2**26, | -|| gpu_temp_buffer_capacity: 16777216 || gpu_temp_buffer_capacity=2**24, | -|| gpu_max_num_partitions: 8 || gpu_max_num_partitions=8, | -|| gpu_max_soft_body_contacts: 1048576 || gpu_max_soft_body_contacts=2**20, | -|| gpu_max_particle_contacts: 1048576 || gpu_max_particle_contacts=2**20, | -|| || ) | -|| || ) | -+--------------------------------------------------+---------------------------------------------------------------+ - -Parameters such as ``add_ground_plane`` and ``add_distant_light`` are now part of the task logic when creating the scene. -Camera rendering is enabled automatically when a task uses camera sensors. - - -Scene Config ------------- - -The :class:`~isaaclab.scene.InteractiveSceneCfg` class can be used to specify parameters related to the scene, -such as the number of environments and the spacing between environments. Each task config must have a variable named -``scene`` defined that holds the type :class:`~isaaclab.scene.InteractiveSceneCfg`. - -+--------------------------------------------------------------+-------------------------------------------------------------------+ -| | | -|.. code-block:: yaml |.. code-block:: python | -| | | -| # OmniIsaacGymEnvs | # IsaacLab | -| env: | scene: InteractiveSceneCfg = InteractiveSceneCfg( | -| numEnvs: ${resolve_default:512,${...num_envs}} | num_envs=512, | -| envSpacing: 4.0 | env_spacing=4.0) | -+--------------------------------------------------------------+-------------------------------------------------------------------+ - -Task Config ------------ - -Each environment should specify its own config class that holds task specific parameters, such as the dimensions of the -observation and action buffers. Reward term scaling parameters can also be specified in the config class. - -In Isaac Lab, the ``controlFrequencyInv`` parameter has been renamed to ``decimation``, -which must be specified as a parameter in the config class. - -In addition, the maximum episode length parameter (now ``episode_length_s``) is in seconds instead of steps as it was -in OmniIsaacGymEnvs. To convert between step count to seconds, use the equation: -``episode_length_s = dt * decimation * num_steps``. - -The following parameters must be set for each environment config: - -.. code-block:: python - - decimation = 2 - episode_length_s = 5.0 - action_space = 1 - observation_space = 4 - state_space = 0 - - -RL Config Setup -~~~~~~~~~~~~~~~ - -RL config files for the rl_games library can continue to be defined in ``.yaml`` files in Isaac Lab. -Most of the content of the config file can be copied directly from OmniIsaacGymEnvs. -Note that in Isaac Lab, we do not use hydra to resolve relative paths in config files. -Please replace any relative paths such as ``${....device}`` with the actual values of the parameters. - -Additionally, the observation and action clip ranges have been moved to the RL config file. -For any ``clipObservations`` and ``clipActions`` parameters that were defined in the IsaacGymEnvs task config file, -they should be moved to the RL config file in Isaac Lab. - -+--------------------------+----------------------------+ -| | | -| IsaacGymEnvs Task Config | Isaac Lab RL Config | -+--------------------------+----------------------------+ -|.. code-block:: yaml |.. code-block:: yaml | -| | | -| # OmniIsaacGymEnvs | # IsaacLab | -| env: | params: | -| clipObservations: 5.0 | env: | -| clipActions: 1.0 | clip_observations: 5.0 | -| | clip_actions: 1.0 | -+--------------------------+----------------------------+ - -Environment Creation -~~~~~~~~~~~~~~~~~~~~ - -In OmniIsaacGymEnvs, environment creation generally happened in the ``set_up_scene()`` API, -which involved creating the initial environment, cloning the environment, filtering collisions, -adding the ground plane and lights, and creating the ``View`` classes for the actors. - -Similar functionality is performed in Isaac Lab in the ``_setup_scene()`` API. -The main difference is that the base class ``_setup_scene()`` no longer performs operations for -cloning the environment and adding ground plane and lights. Instead, these operations -should now be implemented in individual tasks' ``_setup_scene`` implementations to provide more -flexibility around the scene setup process. - -Also note that by defining an ``Articulation`` or ``RigidObject`` object, the actors will be -added to the scene by parsing the ``spawn`` parameter in the actor config and a ``View`` class -will automatically be created for the actor. This avoids the need to separately define an -``ArticulationView`` or ``RigidPrimView`` object for the actors. - - -+------------------------------------------------------------------------------+------------------------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+------------------------------------------------------------------------------+------------------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| def set_up_scene(self, scene) -> None: | def _setup_scene(self): | -| self.get_cartpole() | self.cartpole = Articulation(self.cfg.robot_cfg) | -| super().set_up_scene(scene) | # add ground plane | -| | spawn_ground_plane(prim_path="/World/ground", cfg=GroundPlaneCfg() | -| self._cartpoles = ArticulationView( | # clone, filter, and replicate | -| prim_paths_expr="/World/envs/.*/Cartpole", | # assets are built inside ReplicateSession | -| name="cartpole_view", reset_xform_properties=False | self.scene.filter_collisions(global_prim_paths=[]) | -| ) | # add articulation to scene | -| scene.add(self._cartpoles) | self.scene.articulations["cartpole"] = self.cartpole | -| | # add lights | -| | light_cfg = sim_utils.DomeLightCfg(intensity=2000.0) | -| | light_cfg.func("/World/Light", light_cfg) | -+------------------------------------------------------------------------------+------------------------------------------------------------------------+ - - -Ground Plane ------------- - -In addition to the above example, more sophisticated ground planes can be defined using the :class:`~terrains.TerrainImporterCfg` class. - -.. code-block:: python - - from isaaclab.terrains import TerrainImporterCfg - - terrain = TerrainImporterCfg( - prim_path="/World/ground", - terrain_type="plane", - collision_group=-1, - physics_material=sim_utils.RigidBodyMaterialCfg( - friction_combine_mode="multiply", - restitution_combine_mode="multiply", - static_friction=1.0, - dynamic_friction=1.0, - restitution=0.0, - ), - ) - -The terrain can then be added to the scene in ``_setup_scene(self)`` by referencing the ``TerrainImporterCfg`` object: - -.. code-block::python - - def _setup_scene(self): - ... - self.cfg.terrain.num_envs = self.scene.cfg.num_envs - self.cfg.terrain.env_spacing = self.scene.cfg.env_spacing - self._terrain = self.cfg.terrain.class_type(self.cfg.terrain) - - -Actors ------- - -In Isaac Lab, each Articulation and Rigid Body actor can have its own config class. The -:class:`~isaaclab.assets.ArticulationCfg` class can be used to define parameters for articulation actors, -including file path, simulation parameters, actuator properties, and initial states. - -.. code-block::python - - from isaaclab.actuators import ImplicitActuatorCfg - from isaaclab.assets import ArticulationCfg - - CARTPOLE_CFG = ArticulationCfg( - spawn=sim_utils.UsdFileCfg( - usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Robots/Classic/Cartpole/cartpole.usd", - rigid_props=sim_utils.RigidBodyPropertiesCfg( - rigid_body_enabled=True, - max_linear_velocity=1000.0, - max_angular_velocity=1000.0, - max_depenetration_velocity=100.0, - enable_gyroscopic_forces=True, - ), - articulation_props=sim_utils.ArticulationRootPropertiesCfg( - enabled_self_collisions=False, - solver_position_iteration_count=4, - solver_velocity_iteration_count=0, - sleep_threshold=0.005, - stabilization_threshold=0.001, - ), - ), - init_state=ArticulationCfg.InitialStateCfg( - pos=(0.0, 0.0, 2.0), joint_pos={"slider_to_cart": 0.0, "cart_to_pole": 0.0} - ), - actuators={ - "cart_actuator": ImplicitActuatorCfg( - joint_names_expr=["slider_to_cart"], - joint_effort_limit=400.0, - joint_velocity_limit=100.0, - stiffness=0.0, - damping=10.0, - ), - "pole_actuator": ImplicitActuatorCfg( - joint_names_expr=["cart_to_pole"], joint_effort_limit=400.0, joint_velocity_limit=100.0, stiffness=0.0, damping=0.0 - ), - }, - ) - -Within the :class:`~assets.ArticulationCfg`, the ``spawn`` attribute can be used to add the robot to the scene -by specifying the path to the robot file. In addition, the :class:`~isaaclab.sim.schemas.RigidBodyPropertiesCfg` -class can be used to specify simulation properties for the rigid bodies in the articulation. Similarly, the -:class:`~isaaclab.sim.schemas.ArticulationRootPropertiesCfg` class can be used to specify simulation properties -for the articulation. The joint properties are now specified as part of the ``actuators`` dictionary using -:class:`~actuators.ImplicitActuatorCfg`. Joints with the same properties can be grouped into regex expressions or -provided as a list of names or expressions. - -Actors are added to the scene by simply calling ``self.cartpole = Articulation(self.cfg.robot_cfg)``, where -``self.cfg.robot_cfg`` is an :class:`~assets.ArticulationCfg` object. Once initialized, they should also be added -to the :class:`~scene.InteractiveScene` by calling ``self.scene.articulations["cartpole"] = self.cartpole`` so that -the :class:`~scene.InteractiveScene` can traverse through actors in the scene for writing values to the simulation -and resetting. - - -Accessing States from Simulation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -APIs for accessing physics states in Isaac Lab require the creation of an :class:`~assets.Articulation` or -:class:`~assets.RigidObject` object. Multiple objects can be initialized for different articulations or rigid bodies -in the scene by defining corresponding :class:`~assets.ArticulationCfg` or :class:`~assets.RigidObjectCfg` config, -as outlined in the section above. This replaces the previously used :class:`~omni.isaac.core.articulations.ArticulationView` -and :class:`omni.isaac.core.prims.RigidPrimView` classes used in OmniIsaacGymEnvs. - -However, functionality between the classes are similar: - -+------------------------------------------------------------------+-----------------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+------------------------------------------------------------------+-----------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| dof_pos = self._cartpoles.get_joint_positions(clone=False) | self.joint_pos = self._robot.data.joint_pos | -| dof_vel = self._cartpoles.get_joint_velocities(clone=False) | self.joint_vel = self._robot.data.joint_vel | -+------------------------------------------------------------------+-----------------------------------------------------------------+ - -In Isaac Lab, :class:`~assets.Articulation` and :class:`~assets.RigidObject` classes both have a ``data`` class. -The data classes (:class:`~assets.ArticulationData` and :class:`~assets.RigidObjectData`) contain -buffers that hold the states for the articulation and rigid objects and provide -a more performant way of retrieving states from the actors. - -Apart from some renamings of APIs, setting states for actors can also be performed similarly between OmniIsaacGymEnvs and Isaac Lab. - -+---------------------------------------------------------------------------+---------------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+---------------------------------------------------------------------------+---------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| indices = env_ids.to(dtype=torch.int32) | self._robot.write_joint_state_to_sim(joint_pos, joint_vel, | -| self._cartpoles.set_joint_positions(dof_pos, indices=indices) | joint_ids, env_ids) | -| self._cartpoles.set_joint_velocities(dof_vel, indices=indices) | | -+---------------------------------------------------------------------------+---------------------------------------------------------------+ - -In Isaac Lab, ``root_pose`` and ``root_velocity`` have been combined into single buffers and no longer split between -``root_position``, ``root_orientation``, ``root_linear_velocity`` and ``root_angular_velocity``. - -.. code-block::python - - self.cartpole.write_root_pose_to_sim(default_root_state[:, :7], env_ids) - self.cartpole.write_root_velocity_to_sim(default_root_state[:, 7:], env_ids) - - -Creating a New Environment -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Each environment in Isaac Lab should be in its own directory following this structure: - -.. code-block:: none - - my_environment/ - - agents/ - - __init__.py - - rl_games_ppo_cfg.py - - __init__.py - my_env.py - -* ``my_environment`` is the root directory of the task. -* ``my_environment/agents`` is the directory containing all RL config files for the task. Isaac Lab supports multiple - RL libraries that can each have its own individual config file. -* ``my_environment/__init__.py`` is the main file that registers the environment with the Gymnasium interface. - This allows the training and inferencing scripts to find the task by its name. - The content of this file should be as follow: - - .. code-block:: python - - import gymnasium as gym - - from . import agents - from .cartpole_env import CartpoleEnv, CartpoleEnvCfg - - ## - # Register Gym environments. - ## - - gym.register( - id="Isaac-Cartpole-Direct", - entry_point="isaaclab_tasks.direct_workflow.cartpole:CartpoleEnv", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": CartpoleEnvCfg, - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml" - }, - ) - -* ``my_environment/my_env.py`` is the main python script that implements the task logic and task config class for - the environment. - - -Task Logic -~~~~~~~~~~ - -The ``post_reset`` API in OmniIsaacGymEnvs is no longer required in Isaac Lab. Everything that was previously -done in ``post_reset`` can be done in the ``__init__`` method after executing the base class's -``__init__``. At this point, simulation has already started. - -In OmniIsaacGymEnvs, due to limitations of the GPU APIs, resets could not be performed based on states of the current -step. Instead, resets have to be performed at the beginning of the next time step. -This restriction has been eliminated in Isaac Lab, and thus, tasks follow the correct workflow of applying actions, -stepping simulation, collecting states, computing dones, calculating rewards, performing resets, and finally computing -observations. This workflow is done automatically by the framework such that a ``post_physics_step`` API is not -required in the task. However, individual tasks can override the ``step()`` API to control the workflow. - -In Isaac Lab, we also separate the ``pre_physics_step`` API for processing actions from the policy with -the ``apply_action`` API, which sets the actions into the simulation. This provides more flexibility in controlling -when actions should be written to simulation when ``decimation`` is used. -The ``pre_physics_step`` method will be called once per step before stepping simulation. -The ``apply_actions`` method will be called ``decimation`` number of times for each RL step, -once before each simulation step call. - -The ordering of the calls are as follow: - -+----------------------------------+----------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+----------------------------------+----------------------------------+ -|.. code-block:: none |.. code-block:: none | -| | | -| pre_physics_step | pre_physics_step | -| |-- reset_idx() | |-- _pre_physics_step(action)| -| |-- apply_action | |-- _apply_action() | -| | | -| post_physics_step | post_physics_step | -| |-- get_observations() | |-- _get_dones() | -| |-- calculate_metrics() | |-- _get_rewards() | -| |-- is_done() | |-- _reset_idx() | -| | |-- _get_observations() | -+----------------------------------+----------------------------------+ - -With this approach, resets are performed based on actions from the current step instead of the previous step. -Observations will also be computed with the correct states after resets. - -We have also performed some renamings of APIs: - -* ``set_up_scene(self, scene)`` --> ``_setup_scene(self)`` -* ``post_reset(self)`` --> ``__init__(...)`` -* ``pre_physics_step(self, actions)`` --> ``_pre_physics_step(self, actions)`` and ``_apply_action(self)`` -* ``reset_idx(self, env_ids)`` --> ``_reset_idx(self, env_ids)`` -* ``get_observations(self)`` --> ``_get_observations(self)`` - ``_get_observations()`` should now return a dictionary ``{"policy": obs}`` -* ``calculate_metrics(self)`` --> ``_get_rewards(self)`` - ``_get_rewards()`` should now return the reward buffer -* ``is_done(self)`` --> ``_get_dones(self)`` - ``_get_dones()`` should now return 2 buffers: ``reset`` and ``time_out`` buffers - - - -Putting It All Together -~~~~~~~~~~~~~~~~~~~~~~~ - -The Cartpole environment is shown here in completion to fully show the comparison between the OmniIsaacGymEnvs -implementation and the Isaac Lab implementation. - -Task Config ------------ - -Task config in Isaac Lab can be split into the main task configuration class and individual config objects for the actors. - -+-----------------------------------------------------------------+-----------------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+-----------------------------------------------------------------+-----------------------------------------------------------------+ -|.. code-block:: yaml |.. code-block:: python | -| | | -| # used to create the object | @configclass | -| | class CartpoleEnvCfg(DirectRLEnvCfg): | -| name: Cartpole | | -| | # simulation | -| physics_engine: ${..physics_engine} | sim: SimulationCfg = SimulationCfg(dt=1 / 120) | -| | # robot | -| # if given, will override the device setting in gym. | robot_cfg: ArticulationCfg = CARTPOLE_CFG.replace( | -| env: | prim_path="/World/envs/env_.*/Robot") | -| | cart_dof_name = "slider_to_cart" | -| numEnvs: ${resolve_default:512,${...num_envs}} | pole_dof_name = "cart_to_pole" | -| envSpacing: 4.0 | # scene | -| resetDist: 3.0 | scene: InteractiveSceneCfg = InteractiveSceneCfg( | -| maxEffort: 400.0 | num_envs=4096, env_spacing=4.0, replicate_physics=True) | -| | # env | -| clipObservations: 5.0 | decimation = 2 | -| clipActions: 1.0 | episode_length_s = 5.0 | -| controlFrequencyInv: 2 # 60 Hz | action_scale = 100.0 # [N] | -| | action_space = 1 | -| sim: | observation_space = 4 | -| | state_space = 0 | -| dt: 0.0083 # 1/120 s | # reset | -| use_gpu_pipeline: ${eq:${...pipeline},"gpu"} | max_cart_pos = 3.0 | -| gravity: [0.0, 0.0, -9.81] | initial_pole_angle_range = [-0.25, 0.25] | -| add_ground_plane: True | # reward scales | -| add_distant_light: False | rew_scale_alive = 1.0 | -| use_fabric: True | rew_scale_terminated = -2.0 | -| enable_scene_query_support: False | rew_scale_pole_pos = -1.0 | -| disable_contact_processing: False | rew_scale_cart_vel = -0.01 | -| | rew_scale_pole_vel = -0.005 | -| enable_cameras: False | | -| | | -| default_physics_material: | CARTPOLE_CFG = ArticulationCfg( | -| static_friction: 1.0 | spawn=sim_utils.UsdFileCfg( | -| dynamic_friction: 1.0 | usd_path=f"{ISAACLAB_NUCLEUS_DIR}/.../cartpole.usd", | -| restitution: 0.0 | rigid_props=sim_utils.RigidBodyPropertiesCfg( | -| | rigid_body_enabled=True, | -| physx: | max_linear_velocity=1000.0, | -| worker_thread_count: ${....num_threads} | max_angular_velocity=1000.0, | -| solver_type: ${....solver_type} | max_depenetration_velocity=100.0, | -| use_gpu: ${eq:${....sim_device},"gpu"} # set to False to... | enable_gyroscopic_forces=True, | -| solver_position_iteration_count: 4 | ), | -| solver_velocity_iteration_count: 0 | articulation_props=sim_utils.ArticulationRootPropertiesCfg( | -| contact_offset: 0.02 | enabled_self_collisions=False, | -| rest_offset: 0.001 | solver_position_iteration_count=4, | -| bounce_threshold_velocity: 0.2 | solver_velocity_iteration_count=0, | -| friction_offset_threshold: 0.04 | sleep_threshold=0.005, | -| friction_correlation_distance: 0.025 | stabilization_threshold=0.001, | -| enable_sleeping: True | ), | -| enable_stabilization: True | ), | -| max_depenetration_velocity: 100.0 | init_state=ArticulationCfg.InitialStateCfg( | -| | pos=(0.0, 0.0, 2.0), | -| # GPU buffers | joint_pos={"slider_to_cart": 0.0, "cart_to_pole": 0.0} | -| gpu_max_rigid_contact_count: 524288 | ), | -| gpu_max_rigid_patch_count: 81920 | actuators={ | -| gpu_found_lost_pairs_capacity: 1024 | "cart_actuator": ImplicitActuatorCfg( | -| gpu_found_lost_aggregate_pairs_capacity: 262144 | joint_names_expr=["slider_to_cart"], | -| gpu_total_aggregate_pairs_capacity: 1024 | joint_effort_limit=400.0, | -| gpu_max_soft_body_contacts: 1048576 | joint_velocity_limit=100.0, | -| gpu_max_particle_contacts: 1048576 | stiffness=0.0, | -| gpu_heap_capacity: 67108864 | damping=10.0, | -| gpu_temp_buffer_capacity: 16777216 | ), | -| gpu_max_num_partitions: 8 | "pole_actuator": ImplicitActuatorCfg( | -| | joint_names_expr=["cart_to_pole"], | -| Cartpole: | joint_effort_limit=400.0, joint_velocity_limit=100.0, | -| override_usd_defaults: False | stiffness=0.0, damping=0.0 | -| enable_self_collisions: False | ), | -| enable_gyroscopic_forces: True | }, | -| solver_position_iteration_count: 4 | ) | -| solver_velocity_iteration_count: 0 | | -| sleep_threshold: 0.005 | | -| stabilization_threshold: 0.001 | | -| density: -1 | | -| max_depenetration_velocity: 100.0 | | -| contact_offset: 0.02 | | -| rest_offset: 0.001 | | -+-----------------------------------------------------------------+-----------------------------------------------------------------+ - - - -Task Setup ----------- - -The ``post_reset`` API in OmniIsaacGymEnvs is no longer required in Isaac Lab. -Everything that was previously done in ``post_reset`` can be done in the ``__init__`` method after -executing the base class's ``__init__``. At this point, simulation has already started. - -+-------------------------------------------------------------------------+-------------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+-------------------------------------------------------------------------+-------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| class CartpoleTask(RLTask): | class CartpoleEnv(DirectRLEnv): | -| | cfg: CartpoleEnvCfg | -| def __init__(self, name, sim_config, env, offset=None) -> None: | def __init__(self, cfg: CartpoleEnvCfg, | -| | render_mode: str | None = None, **kwargs): | -| self.update_config(sim_config) | super().__init__(cfg, render_mode, **kwargs) | -| self._max_episode_length = 500 | | -| | | -| self._num_observations = 4 | self._cart_dof_idx, _ = self.cartpole.find_joints( | -| self._num_actions = 1 | self.cfg.cart_dof_name) | -| | self._pole_dof_idx, _ = self.cartpole.find_joints( | -| RLTask.__init__(self, name, env) | self.cfg.pole_dof_name) | -| | self.action_scale=self.cfg.action_scale | -| def update_config(self, sim_config): | | -| self._sim_config = sim_config | self.joint_pos = self.cartpole.data.joint_pos | -| self._cfg = sim_config.config | self.joint_vel = self.cartpole.data.joint_vel | -| self._task_cfg = sim_config. | | -| task_config | | -| | | -| self._num_envs = self._task_cfg["env"]["numEnvs"] | | -| self._env_spacing = self._task_cfg["env"]["envSpacing"] | | -| self._cartpole_positions = torch.tensor([0.0, 0.0, 2.0]) | | -| | | -| self._reset_dist = self._task_cfg["env"]["resetDist"] | | -| self._max_push_effort = self._task_cfg["env"]["maxEffort"] | | -| | | -| | | -| def post_reset(self): | | -| self._cart_dof_idx = self._cartpoles.get_dof_index( | | -| "cartJoint") | | -| self._pole_dof_idx = self._cartpoles.get_dof_index( | | -| "poleJoint") | | -| # randomize all envs | | -| indices = torch.arange( | | -| self._cartpoles.count, dtype=torch.int64, | | -| device=self._device) | | -| self.reset_idx(indices) | | -+-------------------------------------------------------------------------+-------------------------------------------------------------+ - - - -Scene Setup ------------ - -The ``set_up_scene`` method in OmniIsaacGymEnvs has been replaced by the ``_setup_scene`` API in the task class in -Isaac Lab. Additionally, scene cloning and collision filtering have been provided as APIs for the task class to -call when necessary. Similarly, adding ground plane and lights should also be taken care of in the task class. -Adding actors to the scene has been replaced by ``self.scene.articulations["cartpole"] = self.cartpole``. - -+-----------------------------------------------------------+----------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+-----------------------------------------------------------+----------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| def set_up_scene(self, scene) -> None: | def _setup_scene(self): | -| | self.cartpole = Articulation(self.cfg.robot_cfg) | -| self.get_cartpole() | # add ground plane | -| super().set_up_scene(scene) | spawn_ground_plane(prim_path="/World/ground", | -| self._cartpoles = ArticulationView( | cfg=GroundPlaneCfg()) | -| prim_paths_expr="/World/envs/.*/Cartpole", | # clone, filter, and replicate | -| name="cartpole_view", | # assets are built inside ReplicateSession | -| reset_xform_properties=False | | -| ) | self.scene.filter_collisions( | -| scene.add(self._cartpoles) | global_prim_paths=[]) | -| return | # add articulation to scene | -| | self.scene.articulations["cartpole"] = self.cartpole | -| def get_cartpole(self): | | -| cartpole = Cartpole( | # add lights | -| prim_path=self.default_zero_env_path+"/Cartpole", | light_cfg = sim_utils.DomeLightCfg( | -| name="Cartpole", | intensity=2000.0, color=(0.75, 0.75, 0.75)) | -| translation=self._cartpole_positions | light_cfg.func("/World/Light", light_cfg) | -| ) | | -| # applies articulation settings from the | | -| # task configuration yaml file | | -| self._sim_config.apply_articulation_settings( | | -| "Cartpole", get_prim_at_path(cartpole.prim_path), | | -| self._sim_config.parse_actor_config("Cartpole") | | -| ) | | -+-----------------------------------------------------------+----------------------------------------------------------+ - - -Pre-Physics Step ----------------- - -Note that resets are no longer performed in the ``pre_physics_step`` API. In addition, the separation of the -``_pre_physics_step`` and ``_apply_action`` methods allow for more flexibility in processing the action buffer -and setting actions into simulation. - -+---------------------------------------------------+--------------------------------------------------------------+ -| OmniIsaacGymEnvs | IsaacLab | -+---------------------------------------------------+--------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| def pre_physics_step(self, actions) -> None: | def _pre_physics_step(self, | -| if not self.world.is_playing(): | actions: torch.Tensor) -> None: | -| return | self.actions = self.action_scale * actions | -| | | -| reset_env_ids = self.reset_buf.nonzero( | def _apply_action(self) -> None: | -| as_tuple=False).squeeze(-1) | self.cartpole.actuators.target_command.set_effort_index( | -| if len(reset_env_ids) > 0: | value=self.actions, joint_ids=self._cart_dof_idx) | -| self.reset_idx(reset_env_ids) | | -| | | -| actions = actions.to(self._device) | | -| | | -| forces = torch.zeros((self._cartpoles.count, | | -| self._cartpoles.num_dof), | | -| dtype=torch.float32, device=self._device) | | -| forces[:, self._cart_dof_idx] = | | -| self._max_push_effort * actions[:, 0] | | -| | | -| indices = torch.arange(self._cartpoles.count, | | -| dtype=torch.int32, device=self._device) | | -| self._cartpoles.set_joint_efforts( | | -| forces, indices=indices) | | -+---------------------------------------------------+--------------------------------------------------------------+ - - -Dones and Resets ----------------- - -In Isaac Lab, the ``dones`` are computed in the ``_get_dones()`` method and should return two variables: ``resets`` and -``time_out``. The ``_reset_idx()`` method is also called after stepping simulation instead of before, as it was done in -OmniIsaacGymEnvs. The ``progress_buf`` tensor has been renamed to ``episode_length_buf`` in Isaac Lab and the -bookkeeping is now done automatically by the framework. Task implementations no longer need to increment or -reset the ``episode_length_buf`` buffer. - -+------------------------------------------------------------------+--------------------------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+------------------------------------------------------------------+--------------------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| def is_done(self) -> None: | def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]: | -| resets = torch.where( | self.joint_pos = self.cartpole.data.joint_pos | -| torch.abs(self.cart_pos) > self._reset_dist, 1, 0) | self.joint_vel = self.cartpole.data.joint_vel | -| resets = torch.where( | | -| torch.abs(self.pole_pos) > math.pi / 2, 1, resets) | time_out = self.episode_length_buf >= self.max_episode_length - 1 | -| resets = torch.where( | out_of_bounds = torch.any(torch.abs( | -| self.progress_buf >= self._max_episode_length, 1, resets) | self.joint_pos[:, self._cart_dof_idx]) > self.cfg.max_cart_pos, | -| self.reset_buf[:] = resets | dim=1) | -| | out_of_bounds = out_of_bounds | torch.any( | -| | torch.abs(self.joint_pos[:, self._pole_dof_idx]) > math.pi / 2, | -| | dim=1) | -| | return out_of_bounds, time_out | -| | | -| def reset_idx(self, env_ids): | def _reset_idx(self, env_ids: Sequence[int] | None): | -| num_resets = len(env_ids) | if env_ids is None: | -| | env_ids = self.cartpole._ALL_INDICES | -| # randomize DOF positions | super()._reset_idx(env_ids) | -| dof_pos = torch.zeros((num_resets, self._cartpoles.num_dof), | | -| device=self._device) | joint_pos = self.cartpole.data.default_joint_pos[env_ids] | -| dof_pos[:, self._cart_dof_idx] = 1.0 * ( | joint_pos[:, self._pole_dof_idx] += sample_uniform( | -| 1.0 - 2.0 * torch.rand(num_resets, device=self._device)) | self.cfg.initial_pole_angle_range[0] * math.pi, | -| dof_pos[:, self._pole_dof_idx] = 0.125 * math.pi * ( | self.cfg.initial_pole_angle_range[1] * math.pi, | -| 1.0 - 2.0 * torch.rand(num_resets, device=self._device)) | joint_pos[:, self._pole_dof_idx].shape, | -| | joint_pos.device, | -| # randomize DOF velocities | ) | -| dof_vel = torch.zeros((num_resets, self._cartpoles.num_dof), | joint_vel = self.cartpole.data.default_joint_vel[env_ids] | -| device=self._device) | | -| dof_vel[:, self._cart_dof_idx] = 0.5 * ( | default_root_state = self.cartpole.data.default_root_state[env_ids] | -| 1.0 - 2.0 * torch.rand(num_resets, device=self._device)) | default_root_state[:, :3] += self.scene.env_origins[env_ids] | -| dof_vel[:, self._pole_dof_idx] = 0.25 * math.pi * ( | | -| 1.0 - 2.0 * torch.rand(num_resets, device=self._device)) | self.joint_pos[env_ids] = joint_pos | -| | self.joint_vel[env_ids] = joint_vel | -| # apply resets | | -| indices = env_ids.to(dtype=torch.int32) | self.cartpole.write_root_pose_to_sim( | -| self._cartpoles.set_joint_positions(dof_pos, indices=indices) | default_root_state[:, :7], env_ids) | -| self._cartpoles.set_joint_velocities(dof_vel, indices=indices) | self.cartpole.write_root_velocity_to_sim( | -| | default_root_state[:, 7:], env_ids) | -| # bookkeeping | self.cartpole.write_joint_state_to_sim( | -| self.reset_buf[env_ids] = 0 | joint_pos, joint_vel, None, env_ids) | -| self.progress_buf[env_ids] = 0 | | -| | | -| | | -+------------------------------------------------------------------+--------------------------------------------------------------------------+ - - -Rewards -------- - -In Isaac Lab, rewards are implemented in the ``_get_rewards`` API and should return the reward buffer instead of assigning -it directly to ``self.rew_buf``. Computation in the reward function can also be performed using pytorch jit -through defining functions with the ``@torch.jit.script`` annotation. - -+-------------------------------------------------------+-----------------------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+-------------------------------------------------------+-----------------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| def calculate_metrics(self) -> None: | def _get_rewards(self) -> torch.Tensor: | -| reward = (1.0 - self.pole_pos * self.pole_pos | total_reward = compute_rewards( | -| - 0.01 * torch.abs(self.cart_vel) - 0.005 | self.cfg.rew_scale_alive, | -| * torch.abs(self.pole_vel)) | self.cfg.rew_scale_terminated, | -| reward = torch.where( | self.cfg.rew_scale_pole_pos, | -| torch.abs(self.cart_pos) > self._reset_dist, | self.cfg.rew_scale_cart_vel, | -| torch.ones_like(reward) * -2.0, reward) | self.cfg.rew_scale_pole_vel, | -| reward = torch.where( | self.joint_pos[:, self._pole_dof_idx[0]], | -| torch.abs(self.pole_pos) > np.pi / 2, | self.joint_vel[:, self._pole_dof_idx[0]], | -| torch.ones_like(reward) * -2.0, reward) | self.joint_pos[:, self._cart_dof_idx[0]], | -| | self.joint_vel[:, self._cart_dof_idx[0]], | -| self.rew_buf[:] = reward | self.reset_terminated, | -| | ) | -| | return total_reward | -| | | -| | @torch.jit.script | -| | def compute_rewards( | -| | rew_scale_alive: float, | -| | rew_scale_terminated: float, | -| | rew_scale_pole_pos: float, | -| | rew_scale_cart_vel: float, | -| | rew_scale_pole_vel: float, | -| | pole_pos: torch.Tensor, | -| | pole_vel: torch.Tensor, | -| | cart_pos: torch.Tensor, | -| | cart_vel: torch.Tensor, | -| | reset_terminated: torch.Tensor, | -| | ): | -| | rew_alive = rew_scale_alive * (1.0 - reset_terminated.float()) | -| | rew_termination = rew_scale_terminated * reset_terminated.float() | -| | rew_pole_pos = rew_scale_pole_pos * torch.sum( | -| | torch.square(pole_pos), dim=-1) | -| | rew_cart_vel = rew_scale_cart_vel * torch.sum( | -| | torch.abs(cart_vel), dim=-1) | -| | rew_pole_vel = rew_scale_pole_vel * torch.sum( | -| | torch.abs(pole_vel), dim=-1) | -| | total_reward = (rew_alive + rew_termination | -| | + rew_pole_pos + rew_cart_vel + rew_pole_vel) | -| | return total_reward | -+-------------------------------------------------------+-----------------------------------------------------------------------+ - - -Observations ------------- - -In Isaac Lab, the ``_get_observations()`` API must return a dictionary with the key ``policy`` that has the observation buffer as the value. -When working with asymmetric actor-critic states, the states for the critic should have the key ``critic`` and be returned -with the observation buffer in the same dictionary. - -+------------------------------------------------------------------+-------------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+------------------------------------------------------------------+-------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: | -| | | -| def get_observations(self) -> dict: | def _get_observations(self) -> dict: | -| dof_pos = self._cartpoles.get_joint_positions(clone=False) | obs = torch.cat( | -| dof_vel = self._cartpoles.get_joint_velocities(clone=False) | ( | -| | self.joint_pos[:, self._pole_dof_idx[0]], | -| self.cart_pos = dof_pos[:, self._cart_dof_idx] | self.joint_vel[:, self._pole_dof_idx[0]], | -| self.cart_vel = dof_vel[:, self._cart_dof_idx] | self.joint_pos[:, self._cart_dof_idx[0]], | -| self.pole_pos = dof_pos[:, self._pole_dof_idx] | self.joint_vel[:, self._cart_dof_idx[0]], | -| self.pole_vel = dof_vel[:, self._pole_dof_idx] | ), | -| self.obs_buf[:, 0] = self.cart_pos | dim=-1, | -| self.obs_buf[:, 1] = self.cart_vel | ) | -| self.obs_buf[:, 2] = self.pole_pos | observations = {"policy": obs} | -| self.obs_buf[:, 3] = self.pole_vel | return observations | -| | | -| observations = {self._cartpoles.name: | | -| {"obs_buf": self.obs_buf}} | | -| return observations | | -+------------------------------------------------------------------+-------------------------------------------------------------+ - - -Domain Randomization -~~~~~~~~~~~~~~~~~~~~ - -In OmniIsaacGymEnvs, domain randomization was specified through the task ``.yaml`` config file. -In Isaac Lab, the domain randomization configuration uses the :class:`~isaaclab.utils.configclass` module -to specify a configuration class consisting of :class:`~managers.EventTermCfg` variables. - -Below is an example of a configuration class for domain randomization: - -.. code-block:: python - - @configclass - class EventCfg: - robot_physics_material = EventTerm( - func=mdp.randomize_rigid_body_material, - mode="reset", - params={ - "asset_cfg": SceneEntityCfg("robot", body_names=".*"), - "static_friction_range": (0.7, 1.3), - "dynamic_friction_range": (1.0, 1.0), - "restitution_range": (1.0, 1.0), - "num_buckets": 250, - }, - ) - robot_joint_stiffness_and_damping = EventTerm( - func=mdp.randomize_actuator_gains, - mode="reset", - params={ - "asset_cfg": SceneEntityCfg("robot", joint_names=".*"), - "stiffness_distribution_params": (0.75, 1.5), - "damping_distribution_params": (0.3, 3.0), - "operation": "scale", - "distribution": "log_uniform", - }, - ) - reset_gravity = EventTerm( - func=mdp.randomize_physics_scene_gravity, - mode="interval", - is_global_time=True, - interval_range_s=(36.0, 36.0), # time_s = num_steps * (decimation * dt) - params={ - "gravity_distribution_params": ([0.0, 0.0, 0.0], [0.0, 0.0, 0.4]), - "operation": "add", - "distribution": "gaussian", - }, - ) - -Each ``EventTerm`` object is of the :class:`~managers.EventTermCfg` class and takes in a ``func`` parameter -for specifying the function to call during randomization, a ``mode`` parameter, which can be ``startup``, -``reset`` or ``interval``. THe ``params`` dictionary should provide the necessary arguments to the -function that is specified in the ``func`` parameter. -Functions specified as ``func`` for the ``EventTerm`` can be found in the :class:`~envs.mdp.events` module. - -Note that as part of the ``"asset_cfg": SceneEntityCfg("robot", body_names=".*")`` parameter, the name of -the actor ``"robot"`` is provided, along with the body or joint names specified as a regex expression, -which will be the actors and bodies/joints that will have randomization applied. - -One difference with OmniIsaacGymEnvs is that ``interval`` randomization is now specified as seconds instead of -steps. When ``mode="interval"``, the ``interval_range_s`` parameter must also be provided, which specifies -the range of seconds for which randomization should be applied. This range will then be randomized to -determine a specific time in seconds when the next randomization will occur for the term. -To convert between steps to seconds, use the equation ``time_s = num_steps * (decimation * dt)``. - -Similar to OmniIsaacGymEnvs, randomization APIs are available for randomizing articulation properties, -such as joint stiffness and damping, joint limits, rigid body materials, fixed tendon properties, -as well as rigid body properties, such as mass and rigid body materials. Randomization of the -physics scene gravity is also supported. Note that randomization of scale is current not supported -in Isaac Lab. To randomize scale, please set up the scene in a way where each environment holds the actor -at a different scale. - -Once the ``configclass`` for the randomization terms have been set up, the class must be added -to the base config class for the task and be assigned to the variable ``events``. - -.. code-block:: python - - @configclass - class MyTaskConfig: - events: EventCfg = EventCfg() - - -Action and Observation Noise ----------------------------- - -Actions and observation noise can also be added using the :class:`~utils.configclass` module. -Action and observation noise configs must be added to the main task config using the -``action_noise_model`` and ``observation_noise_model`` variables: - -.. code-block:: python - - @configclass - class MyTaskConfig: - # at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset - action_noise_model: NoiseModelWithAdditiveBiasCfg = NoiseModelWithAdditiveBiasCfg( - noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.05, operation="add"), - bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.015, operation="abs"), - ) - # at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset - observation_noise_model: NoiseModelWithAdditiveBiasCfg = NoiseModelWithAdditiveBiasCfg( - noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.002, operation="add"), - bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.0001, operation="abs"), - ) - - -:class:`~.utils.noise.NoiseModelWithAdditiveBiasCfg` can be used to sample both uncorrelated noise -per step as well as correlated noise that is re-sampled at reset time. -The ``noise_cfg`` term specifies the Gaussian distribution that will be sampled at each -step for all environments. This noise will be added to the corresponding actions and -observations buffers at every step. -The ``bias_noise_cfg`` term specifies the Gaussian distribution for the correlated noise -that will be sampled at reset time for the environments being reset. The same noise -will be applied each step for the remaining of the episode for the environments and -resampled at the next reset. - -This replaces the following setup in OmniIsaacGymEnvs: - -.. code-block:: yaml - - domain_randomization: - randomize: True - randomization_params: - observations: - on_reset: - operation: "additive" - distribution: "gaussian" - distribution_parameters: [0, .0001] - on_interval: - frequency_interval: 1 - operation: "additive" - distribution: "gaussian" - distribution_parameters: [0, .002] - actions: - on_reset: - operation: "additive" - distribution: "gaussian" - distribution_parameters: [0, 0.015] - on_interval: - frequency_interval: 1 - operation: "additive" - distribution: "gaussian" - distribution_parameters: [0., 0.05] - - -Launching Training -~~~~~~~~~~~~~~~~~~ - -To launch a training in Isaac Lab, use the command: - -.. tab-set:: - - .. tab-item:: uv (Recommended) - - .. code-block:: bash - - uv run isaaclab train --rl_library rl_games --task=Isaac-Cartpole-Direct - - .. tab-item:: isaaclab.sh / isaaclab.bat - - .. code-block:: bash - - ./isaaclab.sh train --rl_library rl_games --task=Isaac-Cartpole-Direct - -Launching Inferencing -~~~~~~~~~~~~~~~~~~~~~ - -To launch inferencing in Isaac Lab, use the command: - -.. tab-set:: - - .. tab-item:: uv (Recommended) - - .. code-block:: bash - - uv run isaaclab play --rl_library rl_games --task=Isaac-Cartpole-Direct --num_envs=25 --checkpoint= - - - .. tab-item:: isaaclab.sh / isaaclab.bat - - .. code-block:: bash - - ./isaaclab.sh play --rl_library rl_games --task=Isaac-Cartpole-Direct --num_envs=25 --checkpoint= - - -.. _`OmniIsaacGymEnvs`: https://github.com/isaac-sim/OmniIsaacGymEnvs -.. _release notes: https://github.com/isaac-sim/IsaacLab/releases diff --git a/docs/source/migration/migrating_from_orbit.rst b/docs/source/migration/migrating_from_orbit.rst deleted file mode 100644 index a73d9c8c16fd..000000000000 --- a/docs/source/migration/migrating_from_orbit.rst +++ /dev/null @@ -1,149 +0,0 @@ -.. _migrating-from-orbit: - -From Orbit -========== - -.. currentmodule:: isaaclab - -Since `Orbit`_ was used as basis for Isaac Lab, migrating from Orbit to Isaac Lab is straightforward. -The following sections describe the changes that need to be made to your code to migrate from Orbit to Isaac Lab. - -.. note:: - - The following changes are with respect to Isaac Lab 1.0 release. Please refer to the `release notes`_ for any changes - in the future releases. - - -Renaming of the launch script -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The script ``orbit.sh`` has been renamed to ``isaaclab.sh``. - - -Updates to extensions -~~~~~~~~~~~~~~~~~~~~~ - -The extensions ``omni.isaac.orbit``, ``omni.isaac.orbit_tasks``, and ``omni.isaac.orbit_assets`` have been renamed -to ``isaaclab``, ``isaaclab_tasks``, and ``isaaclab_assets``, respectively. Thus, -the new folder structure looks like this: - -- ``source/isaaclab/isaaclab`` -- ``source/isaaclab_tasks/isaaclab_tasks`` -- ``source/isaaclab_assets/isaaclab_assets`` - -The high level imports have to be updated as well: - -+-------------------------------------+-----------------------------------+ -| Orbit | Isaac Lab | -+=====================================+===================================+ -| ``from omni.isaac.orbit...`` | ``from isaaclab...`` | -+-------------------------------------+-----------------------------------+ -| ``from omni.isaac.orbit_tasks...`` | ``from isaaclab_tasks...`` | -+-------------------------------------+-----------------------------------+ -| ``from omni.isaac.orbit_assets...`` | ``from isaaclab_assets...`` | -+-------------------------------------+-----------------------------------+ - - -Updates to class names -~~~~~~~~~~~~~~~~~~~~~~ - -In Isaac Lab, we introduced the concept of task design workflows (see :ref:`feature-workflows`). The Orbit code is using -the manager-based workflow and the environment specific class names have been updated to reflect this change: - -+------------------------+---------------------------------------------------------+ -| Orbit | Isaac Lab | -+========================+=========================================================+ -| ``BaseEnv`` | :class:`isaaclab.envs.ManagerBasedEnv` | -+------------------------+---------------------------------------------------------+ -| ``BaseEnvCfg`` | :class:`isaaclab.envs.ManagerBasedEnvCfg` | -+------------------------+---------------------------------------------------------+ -| ``RLTaskEnv`` | :class:`isaaclab.envs.ManagerBasedRLEnv` | -+------------------------+---------------------------------------------------------+ -| ``RLTaskEnvCfg`` | :class:`isaaclab.envs.ManagerBasedRLEnvCfg` | -+------------------------+---------------------------------------------------------+ -| ``RLTaskEnvWindow`` | :class:`isaaclab.envs.ui.ManagerBasedRLEnvWindow` | -+------------------------+---------------------------------------------------------+ - - -Updates to the tasks folder structure -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The tasks extension is organized into two folders: - -- ``source/isaaclab_tasks/isaaclab_tasks/core`` -- ``source/isaaclab_tasks/isaaclab_tasks/contrib`` - -The tasks from Orbit can now be found under the ``core`` folder. -This change must also be reflected in the imports for your tasks. For example, - -.. code-block:: python - - from omni.isaac.orbit_tasks.locomotion.velocity.velocity_env_cfg ... - -should now be: - -.. code-block:: python - - from isaaclab_tasks.core.velocity.velocity_env_cfg ... - - -Other Breaking changes -~~~~~~~~~~~~~~~~~~~~~~ - -Setting the device ------------------- - -The argument ``--cpu`` has been removed in favor of ``--device device_name``. Valid options for ``device_name`` are: - -- ``cpu``: Use CPU. -- ``cuda``: Use GPU with device ID ``0``. -- ``cuda:N``: Use GPU, where N is the device ID. For example, ``cuda:0``. - -The default value is ``cuda:0``. - - -Offscreen rendering -------------------- - -Offscreen rendering is selected automatically when a camera task runs without a visualizer. - - -Event term distribution configuration -------------------------------------- - -Some of the event functions in `events.py `_ -accepted a ``distribution`` parameter and a ``range`` to sample from. In an effort to support arbitrary distributions, -we have renamed the input argument ``AAA_range`` to ``AAA_distribution_params`` for these functions. -Therefore, event term configurations whose functions have a ``distribution`` argument should be updated. For example, - -.. code-block:: python - :emphasize-lines: 6 - - add_base_mass = EventTerm( - func=mdp.randomize_rigid_body_mass, - mode="startup", - params={ - "asset_cfg": SceneEntityCfg("robot", body_names="base"), - "mass_range": (-5.0, 5.0), - "operation": "add", - }, - ) - -should now be: - -.. code-block:: python - :emphasize-lines: 6 - - add_base_mass = EventTerm( - func=mdp.randomize_rigid_body_mass, - mode="startup", - params={ - "asset_cfg": SceneEntityCfg("robot", body_names="base"), - "mass_distribution_params": (-5.0, 5.0), - "operation": "add", - }, - ) - - -.. _Orbit: https://isaac-orbit.github.io/ -.. _release notes: https://github.com/isaac-sim/IsaacLab/releases diff --git a/docs/source/migration/migrating_to_isaaclab_3-0.rst b/docs/source/migration/migrating_to_isaaclab_3-0.rst index 696f5c8a27e0..a856689abea0 100644 --- a/docs/source/migration/migrating_to_isaaclab_3-0.rst +++ b/docs/source/migration/migrating_to_isaaclab_3-0.rst @@ -19,6 +19,13 @@ maintaining a consistent user-facing API. This guide covers the main breaking changes and deprecations you need to address when migrating from Isaac Lab 2.x to Isaac Lab 3.0. +.. toctree:: + :hidden: + :maxdepth: 1 + + migrating_from_isaacgymenvs + migrating_deformables + .. _actuators-solver-limit-migration: diff --git a/docs/source/refs/migration.rst b/docs/source/refs/migration.rst deleted file mode 100644 index 15691c9e6f1b..000000000000 --- a/docs/source/refs/migration.rst +++ /dev/null @@ -1,202 +0,0 @@ -.. _migration_guide: - -Migration Guide (Isaac Sim) -=========================== - -Moving from Isaac Sim 4.2 to 4.5 and later brings in a number of changes to the -APIs and Isaac Sim extensions and classes. This document outlines the changes -and how to migrate your code to the new APIs. - - -Renaming of Isaac Sim Extensions --------------------------------- - -Previously, Isaac Sim extensions have been following the convention of ``omni.isaac.*``, -such as ``omni.isaac.core``. In Isaac Sim 4.5, Isaac Sim extensions have been renamed -to use the prefix ``isaacsim``, replacing ``omni.isaac``. In addition, many extensions -have been renamed and split into multiple extensions to prepare for a more modular -framework that can be customized by users through the use of app templates. - -Notably, the following commonly used Isaac Sim extensions in Isaac Lab are renamed as follow: - -* ``omni.isaac.cloner`` --> ``isaacsim.core.cloner`` -* ``omni.isaac.core.prims`` --> ``isaacsim.core.prims`` -* ``omni.isaac.core.simulation_context`` --> ``isaacsim.core.api.simulation_context`` -* ``omni.isaac.core.utils`` --> ``isaacsim.core.utils`` -* ``omni.isaac.core.world`` --> ``isaacsim.core.api.world`` -* ``omni.isaac.kit.SimulationApp`` --> ``isaacsim.SimulationApp`` -* ``omni.isaac.ui`` --> ``isaacsim.gui.components`` - - -Renaming of the URDF and MJCF Importers ---------------------------------------- - -Starting from Isaac Sim 4.5, the URDF and MJCF importers have been renamed to be more consistent -with the other extensions in Isaac Sim. The importers are available on isaac-sim GitHub -as open source projects. - -Due to the extension name change, the Python module names have also been changed: - -* URDF Importer: :mod:`isaacsim.asset.importer.urdf` (previously :mod:`omni.importer.urdf`) -* MJCF Importer: :mod:`isaacsim.asset.importer.mjcf` (previously :mod:`omni.importer.mjcf`) - -From the Isaac Sim UI, both URDF and MJCF importers can now be accessed directly from the File > Import -menu when selecting a corresponding .urdf or .xml file in the file browser. - - -Changes in URDF Importer ------------------------- - -Isaac Sim 4.5 brings some updates to the URDF Importer, with a fresh UI to allow for better configurations -when importing robots from URDF. As a result, the Isaac Lab URDF Converter has also been updated to -reflect these changes. The :class:`UrdfConverterCfg` includes some new settings, such as :class:`PDGainsCfg` -and :class:`NaturalFrequencyGainsCfg` classes for configuring the gains of the drives. - -One breaking change to note is that the :attr:`UrdfConverterCfg.JointDriveCfg.gains` attribute must -be of class type :class:`PDGainsCfg` or :class:`NaturalFrequencyGainsCfg`. - -The stiffness of the :class:`PDGainsCfg` must be specified, as such: - -.. code::python - - joint_drive=sim_utils.UrdfConverterCfg.JointDriveCfg( - gains=sim_utils.UrdfConverterCfg.JointDriveCfg.PDGainsCfg(stiffness=None, damping=None) - ) - -The :attr:`natural_frequency` must be specified for :class:`NaturalFrequencyGainsCfg`. - - -Renaming of omni.isaac.core Classes ------------------------------------ - -Isaac Sim 4.5 introduced some naming changes to the core prim classes that are commonly -used in Isaac Lab. These affect the single and ``View`` variations of the prim classes, including -Articulation, RigidPrim, XFormPrim, and others. Single-object classes are now prefixed with -``Single``, such as ``SingleArticulation``, while tensorized View classes now have the ``View`` -suffix removed. - -The exact renaming of the classes are as follow: - -* ``Articulation`` --> ``SingleArticulation`` -* ``ArticulationView`` --> ``Articulation`` -* ``ClothPrim`` --> ``SingleClothPrim`` -* ``ClothPrimView`` --> ``ClothPrim`` -* ``DeformablePrim`` --> ``SingleDeformablePrim`` -* ``DeformablePrimView`` --> ``DeformablePrim`` -* ``GeometryPrim`` --> ``SingleGeometryPrim`` -* ``GeometryPrimView`` --> ``GeometryPrim`` -* ``ParticleSystem`` --> ``SingleParticleSystem`` -* ``ParticleSystemView`` --> ``ParticleSystem`` -* ``RigidPrim`` --> ``SingleRigidPrim`` -* ``RigidPrimView`` --> ``RigidPrim`` -* ``XFormPrim`` --> ``SingleXFormPrim`` -* ``XFormPrimView`` --> ``XFormPrim`` - - -Renaming of Isaac Lab Extensions and Folders --------------------------------------------- - -Corresponding to Isaac Sim 4.5 changes, we have also made some updates to the Isaac Lab directories and extensions. -All extensions that were previously under ``source/extensions`` are now under the ``source/`` directory directly. -The ``source/apps`` and ``source/standalone`` folders have been moved to the root directory and are now called -``apps/`` and ``scripts/``. - -Isaac Lab extensions have been renamed to: - -* ``omni.isaac.lab`` --> ``isaaclab`` -* ``omni.isaac.lab_assets`` --> ``isaaclab_assets`` -* ``omni.isaac.lab_tasks`` --> ``isaaclab_tasks`` - -In addition, we have split up the previous ``source/standalone/workflows`` directory into ``scripts/imitation_learning`` -and ``scripts/reinforcement_learning`` directories. The RSL RL, Stable-Baselines, RL_Games, SKRL, and Ray directories -are under ``scripts/reinforcement_learning``, while Robomimic and the new Isaac Lab Mimic directories are under -``scripts/imitation_learning``. - -To assist with the renaming of Isaac Lab extensions in your project, we have provided a `simple script`_ that will traverse -through the ``source`` and ``docs`` directories in your local Isaac Lab project and replace any instance of the renamed -directories and imports. **Please use the script at your own risk as it will overwrite source files directly.** - - -Restructuring of Isaac Lab Extensions -------------------------------------- - -With the introduction of ``isaaclab_mimic``, designed for supporting data generation workflows for imitation learning, -we have also split out the previous ``wrappers`` folder under ``isaaclab_tasks`` to its own module, named ``isaaclab_rl``. -This new extension will contain reinforcement learning specific wrappers for the various RL libraries supported by Isaac Lab. - -The new ``isaaclab_mimic`` extension will also replace the previous imitation learning scripts under the ``robomimic`` folder. -We have removed the old scripts for data collection and dataset preparation in favor of the new mimic workflow. For users -who prefer to use the previous scripts, they will be available in previous release branches. - -Additionally, we have also restructured the ``isaaclab_assets`` extension to be split into ``robots`` and ``sensors`` -subdirectories. This allows for clearer separation between the pre-defined configurations provided in the extension. -For any existing imports such as ``from omni.isaac.lab_assets.anymal import ANYMAL_C_CFG``, please replace it with -``from isaaclab.robots.anymal import ANYMAL_C_CFG``. - - -Lazy Exporting and Resolvable Strings --------------------------------------- - -Isaac Lab now uses **lazy exporting** throughout all packages so that importing a top-level -module (e.g. ``import isaaclab.sensors``) no longer eagerly pulls in heavyweight -dependencies such as ``pxr``, ``omni``, or ``scipy``. This is critical because Kit and the -Isaac Sim viewer do **not** tolerate imports of ``pxr``, ``omni``, or ``scipy`` before the -application is launched — doing so will cause crashes or undefined behavior. With lazy -exporting, config objects can be constructed *before* ``SimulationApp`` is launched, which -enables automatic physics-backend selection without extra launch options. - -Two key patterns support this: - -1. **Lazy exports** — Every ``__init__.py`` uses :func:`~isaaclab.utils.module.lazy_export` - together with an adjacent ``.pyi`` stub to defer submodule and symbol imports until - first access. -2. **Resolvable strings** — Config fields such as ``class_type`` store implementation - references as strings (e.g. ``"{DIR}.sensor:Sensor"``) instead of direct class imports. - The string is resolved to the actual class only after ``SimulationApp`` has been - initialized. - -For full details, examples, and the ``{DIR}`` placeholder convention, see the -:doc:`contributing` guide — in particular the -`Lazy Loading & Module Exports `__, -`Resolvable Strings `__, and -`Config + Implementation File Split `__ -sections. - -Lazy Exporting in User Code ----------------------------- - -If your own project imports Isaac Lab symbols eagerly (i.e. via normal ``from ... import`` -statements in ``__init__.py``), those imports may trigger heavyweight modules before the -simulation app is ready. This prevents automatic backend selection and may require explicit -backend configuration. - -To fix this, adopt the same lazy-exporting pattern used throughout Isaac Lab: - -1. Rename your existing ``__init__.py`` to ``__init__.pyi`` (this becomes the type stub). -2. Create a new ``__init__.py`` that calls ``lazy_export()``: - -.. code:: python - - # my_package/__init__.py - from isaaclab.utils.module import lazy_export - - lazy_export() - -3. Ensure the ``.pyi`` stub uses **relative imports** and declares ``__all__``: - -.. code:: python - - # my_package/__init__.pyi - __all__ = ["MyCfg", "MyClass"] - - from .my_cfg import MyCfg - from .my_class import MyClass - -With this in place, ``import my_package`` will not eagerly import any submodules. Symbols -are loaded on first access, giving ``SimulationApp`` time to initialize and auto-detect the -correct backend. - -For more details, refer to the :doc:`contributing` guide. - - -.. _simple script: https://gist.github.com/kellyguo11/3e8f73f739b1c013b1069ad372277a85 diff --git a/docs/source/refs/release_notes.rst b/docs/source/refs/release_notes.rst index c5714992c3d3..b5c771b6ece4 100644 --- a/docs/source/refs/release_notes.rst +++ b/docs/source/refs/release_notes.rst @@ -2006,17 +2006,4 @@ Improvements at every step call, the lazy buffers are updated only when the user queries them * Added SKRL support to more environments -Breaking Changes ----------------- - -For users coming from Orbit, this release brings certain breaking changes. Please check the migration guide for more information. - -Migration Guide ---------------- - -Please find detailed migration guides as follows: - -* :doc:`From Orbit to IsaacLab <../migration/migrating_from_orbit>` -* :doc:`From OmniIsaacGymEnvs to IsaacLab <../migration/migrating_from_omniisaacgymenvs>` - .. _simple script: https://gist.github.com/kellyguo11/3e8f73f739b1c013b1069ad372277a85 From 55719174f1ede4a0d272a070aee0f363bdb9e203 Mon Sep 17 00:00:00 2001 From: Mustafa H <34825877+StafaH@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:15:10 -0700 Subject: [PATCH 03/15] [Test] Fix OpenUSD thread limit in LEAPP tests (#7229) # Description Fix the recurring `isaaclab_rl` LEAPP export failure for `Isaac-Reach-Franka` on Newton MJWarp. The existing workaround passes `limit_cpu_threads=1` to `SimulationApp`, but the failing stack is in OpenUSD's concurrent parser and OpenUSD reads `PXR_WORK_THREAD_LIMIT` during process startup. Set that environment variable on every LEAPP child process so USD is serialized before any USD module is imported, while retaining the existing Kit-side limit. This keeps the current task and Newton backend coverage. It also adds a deterministic subprocess probe for the environment contract. Observed in unrelated PRs: - #6762: https://github.com/isaac-sim/IsaacLab/actions/runs/32343107298/job/96578439022 - #6673: https://github.com/isaac-sim/IsaacLab/actions/runs/32419190550/job/96590692454 - #7207: https://github.com/isaac-sim/IsaacLab/actions/runs/32418984574/job/96590568310 OpenUSD documents `PXR_WORK_THREAD_LIMIT=1` as single-threaded mode: https://openusd.org/dev/api/thread_limits_8h.html ## Type of change - Bug fix (non-breaking change which fixes an issue) ## Validation - `uv run --extra sb3 --extra skrl --extra rl-games --extra leapp python -m pytest source/isaaclab_rl/test/export/test_leapp_export_flow.py -k 'openusd_thread_limit or rsl_rl-Isaac-Reach-Franka' -vv` (2 passed) - `uv run isaaclab -f` - `uv run python tools/changelog/cli.py check develop` ## Checklist - [x] I have read and understood the contribution guidelines - [x] I have run the pre-commit checks with `uv run isaaclab -f` - [x] Documentation is not required for this test-only mitigation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective - [x] I have added a changelog fragment for every touched package - [x] My name is already in `CONTRIBUTORS.md` --- .../mh-fix-leapp-openusd-thread-limit.skip | 0 .../test/export/test_leapp_export_flow.py | 17 +++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 source/isaaclab_rl/changelog.d/mh-fix-leapp-openusd-thread-limit.skip 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.""" From 5616ac592e56fe6ac705a77781d64b39f929ea6d Mon Sep 17 00:00:00 2001 From: Mustafa H <34825877+StafaH@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:32:27 -0700 Subject: [PATCH 04/15] [Docs] Fix environment browser task scopes (#7207) # Description Fix the environments-page command builder and task browser: - Make the pre-trained checkpoint control actionable from Train by switching the command to Play, and disable it for Contrib and Warp. - Add a Core / Contrib / Warp task-collection switch beside the task picker and use it to partition both the picker and available-task list. - Derive Warp-compatible tasks from the same frontend compatibility path used at runtime, keep canonical task IDs, and emit `--frontend warp`. - Align the `--task` dropdown with the physics selector below it. ## Type of change - Bug fix - Documentation update ## Validation - `uv run isaaclab -f` - `uv run python -m pytest --confcutdir=tools/test tools/test/test_environ_docs.py::test_environment_browser_rows_include_concrete_core_and_contributed_selectors` - `uv run --isolated --extra test -- make -C docs current-docs` - `node --check docs/source/_static/css/environment-browser.js` - Headless Chrome interaction check for Core, Contrib, Warp, and pre-trained checkpoint command generation The full `tools/test/test_environ_docs.py` file has one unrelated existing failure in `test_physics_names_for_docs_infers_physx_from_default`; the focused environment-browser metadata test passes. ## Checklist - [x] I have read and understood the contribution guidelines - [x] I have run the pre-commit checks - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added focused coverage for the generated Warp metadata - [x] No source package was changed, so no package changelog fragment is required - [x] My name already exists in `CONTRIBUTORS.md` --- .../_static/css/environment-browser.css | 56 +++++++- .../source/_static/css/environment-browser.js | 132 +++++++++++++----- docs/source/setup/environments.rst | 11 +- tools/environ_docs.py | 31 ++++ tools/test/test_environ_docs.py | 10 +- 5 files changed, 199 insertions(+), 41 deletions(-) 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/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/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 From 094c4bd9d2dea5bec394828534778050b085a6e7 Mon Sep 17 00:00:00 2001 From: Mustafa H <34825877+StafaH@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:53:24 -0700 Subject: [PATCH 05/15] [Workflow] Add isaacsim source install uv workflow (#6762) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Add a pure uv workflow for isaacsim Easy to use: 1. Clone your isaacsim repo somewhere. 2. run `uv run isaaclab --isaacsim_source ` 3. run training with isaacsim `uv run isaaclab train --task Isaac-Cartpole physics=isaacsim_physx` ## Checklist - [ ] I have read and understood the [contribution guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html) - [ ] I have run the [`pre-commit` checks](https://pre-commit.com/) with `./isaaclab.sh --format` - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added a changelog fragment under `source//changelog.d/` for every touched package (do **not** edit `CHANGELOG.rst` or bump `extension.toml` — CI handles that) - [ ] I have added my name to the `CONTRIBUTORS.md` or my name already exists there --------- Co-authored-by: Richard Lei --- .../include/src_clone_isaaclab.rst | 10 +- docs/source/setup/installation/index.rst | 168 +++++++++++------- skills/user/install-isaac-lab/reference.md | 15 +- ...v-friendly-isaacsim-source-build.minor.rst | 8 + source/isaaclab/isaaclab/cli/__init__.py | 12 ++ source/isaaclab/isaaclab/cli/commands/misc.py | 80 +++++++++ source/isaaclab/isaaclab/cli/utils.py | 33 +++- source/isaaclab/test/cli/test_install.py | 42 +++++ .../isaaclab/test/cli/test_misc_commands.py | 56 ++++++ 9 files changed, 353 insertions(+), 71 deletions(-) create mode 100644 source/isaaclab/changelog.d/uv-friendly-isaacsim-source-build.minor.rst 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..9f89d72d4e73 100644 --- a/docs/source/setup/installation/index.rst +++ b/docs/source/setup/installation/index.rst @@ -777,98 +777,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/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/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/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/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) From 69ea1237cb5bd56a831283c38a7ed666d2798963 Mon Sep 17 00:00:00 2001 From: Antoine RICHARD Date: Fri, 21 Aug 2026 11:15:22 +0200 Subject: [PATCH 06/15] Fix OVPhysX contact force history reset (#7208) ## Summary - clear filtered force-matrix history during OVPhysX contact-sensor resets - preserve history for environments outside the reset mask - add a CPU-only Warp regression test ## Testing - `uv run --extra test python -m pytest source/isaaclab_ov/test/sensors/test_contact_sensor_kernels.py` - `uv run python tools/changelog/cli.py check develop` - `uv run isaaclab -f` --- .../ovphysx-force-matrix-history-reset.rst | 5 ++ .../sensors/contact_sensor/contact_sensor.py | 1 + .../sensors/contact_sensor/kernels.py | 8 +++ .../sensors/test_contact_sensor_kernels.py | 59 +++++++++++++++++++ 4 files changed, 73 insertions(+) create mode 100644 source/isaaclab_ov/changelog.d/ovphysx-force-matrix-history-reset.rst create mode 100644 source/isaaclab_ov/test/sensors/test_contact_sensor_kernels.py 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/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/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) From 81167f919b4bf12b38337273a2b3fd0fd4853ac5 Mon Sep 17 00:00:00 2001 From: Mustafa Haiderbhai Date: Wed, 19 Aug 2026 00:15:28 -0700 Subject: [PATCH 07/15] Clarify Isaac Lab wheel extras --- .github/workflows/license-check.yaml | 8 ++-- .github/workflows/wheel.yml | 5 ++- docs/_extensions/isaaclab_docs.py | 10 ++--- .../installation/include/pip_extras_note.rst | 6 +-- docs/source/setup/installation/index.rst | 40 +++++++++++-------- docs/source/setup/quickstart.rst | 6 +-- pyproject.toml | 12 +++--- skills/user/setup-troubleshooting/SKILL.md | 2 +- .../all-extra-without-isaacsim.major.rst | 5 +++ .../test/cli/test_uv_run_pyproject.py | 12 +++--- .../test/cli/test_wheel_builder_metadata.py | 19 ++++++--- .../misc/test_wheel_builder_smoke.py | 4 +- ...ip_install_isaaclab_all_trains_cartpole.py | 34 +++------------- ...lab_isaacsim_imports_simulation_context.py | 6 +-- ...tall_isaaclab_rl_tasks_imports_rl_tasks.py | 5 +-- uv.lock | 5 ++- 16 files changed, 87 insertions(+), 92 deletions(-) create mode 100644 source/isaaclab/changelog.d/all-extra-without-isaacsim.major.rst diff --git a/.github/workflows/license-check.yaml b/.github/workflows/license-check.yaml index ae538ad9b383..28a66019241d 100644 --- a/.github/workflows/license-check.yaml +++ b/.github/workflows/license-check.yaml @@ -61,12 +61,10 @@ 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. + # ``all`` is the curated OV, RL library, and visualizer set; it excludes Isaac Sim. + # Name Isaac Sim, ``rlinf``, and ``mimic`` explicitly to keep them scanned. 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..8a097abe6e4f 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -194,10 +194,11 @@ 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" \ --extra-index-url https://pypi.nvidia.com \ --index-strategy unsafe-best-match \ - --prerelease=allow \ - "${wheel}[all]" + "${wheel}[isaacsim]" diff --git a/docs/_extensions/isaaclab_docs.py b/docs/_extensions/isaaclab_docs.py index 51d2edfad1bc..074b2ec8b974 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,10 @@ 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) @@ -323,7 +323,7 @@ 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-torch-install", IsaacLabTorchInstall) app.add_directive("isaaclab-ovrtx-install", IsaacLabOvrtxInstall) return { diff --git a/docs/source/setup/installation/include/pip_extras_note.rst b/docs/source/setup/installation/include/pip_extras_note.rst index 2623a0180bf8..4f5cbe91a42c 100644 --- a/docs/source/setup/installation/include/pip_extras_note.rst +++ b/docs/source/setup/installation/include/pip_extras_note.rst @@ -1,5 +1,5 @@ .. 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; request ``[isaacsim]`` separately. diff --git a/docs/source/setup/installation/index.rst b/docs/source/setup/installation/index.rst index 9f89d72d4e73..fb2a4ca2e6d8 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,9 @@ 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 to the package requirement when your project needs them. Except for ``isaacsim``, use +``uv pip install "isaaclab[]"`` in a standalone environment or +``uv add "isaaclab[]"`` in a uv project. Isaac Sim has a separate command below. .. list-table:: :header-rows: 1 @@ -602,19 +602,25 @@ use ``uv pip install "isaaclab[]"``; for a uv project, use * - ``leapp`` - LEAP model export support. * - ``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, the specialized extras (``rlinf``, ``mimic``, +``teleop``, ``tetrahedralization``, ``video``, ``leapp``), and the developer ``test`` tooling +remain opt-in. -.. isaaclab-uv-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. Install the CUDA-enabled PyTorch build appropriate for your system architecture: diff --git a/docs/source/setup/quickstart.rst b/docs/source/setup/quickstart.rst index 3713595a3602..36d621db6966 100644 --- a/docs/source/setup/quickstart.rst +++ b/docs/source/setup/quickstart.rst @@ -49,9 +49,9 @@ 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 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..ae4b3daa6af8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,6 +75,9 @@ dependencies = [ # 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", + # uv ignores transitive pre-releases unless one is requested directly. The isolated + # importer requires this release candidate, so declare it here for plain wheel installs. + "tinyobjloader==2.0.0rc13", # 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 ----- @@ -193,12 +196,11 @@ 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. +# Curated OV backends, RL libraries, and visualizers in one flag. Isaac Sim and the +# specialized extras (rlinf, mimic, teleop, tetrahedralization, video, leapp) plus +# 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 diff --git a/skills/user/setup-troubleshooting/SKILL.md b/skills/user/setup-troubleshooting/SKILL.md index ad17f2cc1e7d..1c067fab9bd0 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. 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..1806217e8c7c --- /dev/null +++ b/source/isaaclab/changelog.d/all-extra-without-isaacsim.major.rst @@ -0,0 +1,5 @@ +Changed +^^^^^^^ + +* **Breaking:** Changed the ``isaaclab[all]`` extra to exclude Isaac Sim. Install + ``isaaclab[isaacsim]`` with the documented resolver overrides when Isaac Sim is required. diff --git a/source/isaaclab/test/cli/test_uv_run_pyproject.py b/source/isaaclab/test/cli/test_uv_run_pyproject.py index 198f70740c37..41de1338459e 100644 --- a/source/isaaclab/test/cli/test_uv_run_pyproject.py +++ b/source/isaaclab/test/cli/test_uv_run_pyproject.py @@ -82,19 +82,18 @@ 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. +def test_all_extra_aggregates_curated_ov_rl_and_visualizer_extras(): + """``all`` aggregates the curated OV, RL library, and visualizer extras. - 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. + Isaac Sim and specialized workflows stay opt-in by name because they need separate + installation steps, are narrowly used, or both. """ 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"} @@ -103,6 +102,7 @@ def test_all_extra_aggregates_backends_rl_libraries_and_visualizers(): # has to be classified deliberately -- into ``all`` or into this list. assert set(optional) - reachable - {"all"} == { "rlinf", + "isaacsim", "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..2338cd77648b 100644 --- a/source/isaaclab/test/cli/test_wheel_builder_metadata.py +++ b/source/isaaclab/test/cli/test_wheel_builder_metadata.py @@ -86,24 +86,31 @@ def test_wheel_builder_includes_isaacsim_extra(tmp_path): assert any(dep.startswith("isaacsim[") for dep in optional_dependencies["isaacsim"]) +def test_wheel_builder_requests_required_tinyobjloader_prerelease_directly(tmp_path): + """Plain wheel installs must opt into the isolated importer's prerelease dependency.""" + generated = _generate_wheel_pyproject(tmp_path) + + assert "tinyobjloader==2.0.0rc13" in generated["project"]["dependencies"] + + def test_wheel_builder_expands_all_extra_into_concrete_requirements(tmp_path): """``isaaclab[all]`` must ship the aggregated requirements, not a self-reference. 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. + for the curated OV backends, RL libraries, and visualizers. """ 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"): + # Sampled across what ``all`` aggregates: both OV backends, the RL libraries, + # and the visualizers. + 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"): + # Isaac Sim, specialized extras, and developer tooling stay opt-in by name. + for prefix in ("isaacsim[", "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..3df6d80cc294 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 @@ -36,8 +36,8 @@ 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]``. + fast smoke test of the built wheel, and ``all`` would pull both OV backends plus + the full curated RL and visualizer set. The dedicated ``[all]`` install test covers it. """ _wheel: str = "" 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..566a80b168cf 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.) @@ -27,7 +26,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,37 +44,15 @@ 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). + # carries both OV backends and the curated RL library and visualizer set. 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}" @@ -106,7 +83,6 @@ def test_uv_pip_install_isaaclab_all_trains_cartpole( 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..fd3905f19327 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 @@ -26,9 +26,8 @@ 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. + The extras are named individually to keep this import test small. The aggregate ``all`` + extra also excludes Isaac Sim, but adds the curated OV, RL, and visualizer dependencies. """ _wheel: str = "" diff --git a/uv.lock b/uv.lock index 686ea79ec84a..82b672cb2fcd 100644 --- a/uv.lock +++ b/uv.lock @@ -1852,6 +1852,7 @@ dependencies = [ { name = "scipy", 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 = "starlette", 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 = "tensorboard", 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')" }, { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, { name = "torch", version = "2.11.0+cu130", source = { registry = "https://download.pytorch.org/whl/cu130" }, marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, { name = "torchaudio", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, @@ -1870,7 +1871,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')" }, @@ -2010,7 +2010,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" }, @@ -2094,6 +2094,7 @@ requires-dist = [ { name = "starlette", specifier = ">=0.46.0,<0.50" }, { name = "tensorboard" }, { name = "timm", marker = "extra == 'rlinf'", specifier = ">=1.0.14" }, + { name = "tinyobjloader", 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" }, From c6d85ef0f2891f1ad9b593fe14c9e8d19aae0b1a Mon Sep 17 00:00:00 2001 From: Mustafa Haiderbhai Date: Wed, 19 Aug 2026 00:19:36 -0700 Subject: [PATCH 08/15] Trim wheel workflow comments --- .github/workflows/license-check.yaml | 2 -- pyproject.toml | 6 +----- source/isaaclab/test/cli/test_uv_run_pyproject.py | 10 +--------- .../isaaclab/test/cli/test_wheel_builder_metadata.py | 10 +--------- .../test/install_ci/misc/test_wheel_builder_smoke.py | 7 +------ ...test_uv_pip_install_isaaclab_all_trains_cartpole.py | 9 +-------- ...v_pip_install_isaaclab_rl_tasks_imports_rl_tasks.py | 7 +------ 7 files changed, 6 insertions(+), 45 deletions(-) diff --git a/.github/workflows/license-check.yaml b/.github/workflows/license-check.yaml index 28a66019241d..a803db07a97a 100644 --- a/.github/workflows/license-check.yaml +++ b/.github/workflows/license-check.yaml @@ -61,8 +61,6 @@ jobs: ACCEPT_EULA: Y ISAACSIM_ACCEPT_EULA: YES run: | - # ``all`` is the curated OV, RL library, and visualizer set; it excludes Isaac Sim. - # Name Isaac Sim, ``rlinf``, and ``mimic`` explicitly to keep them scanned. bash "$GITHUB_WORKSPACE/.github/actions/_lib/with-python-package-retries.sh" \ uv sync --extra all --extra isaacsim --extra test --extra rlinf --extra mimic # ``[tool.uv.pip] prerelease = "allow"`` lets unpinned tools float onto diff --git a/pyproject.toml b/pyproject.toml index ae4b3daa6af8..bcb32a9b982f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,8 +75,7 @@ dependencies = [ # 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", - # uv ignores transitive pre-releases unless one is requested directly. The isolated - # importer requires this release candidate, so declare it here for plain wheel installs. + # uv requires direct opt-in to transitive pre-releases. "tinyobjloader==2.0.0rc13", # 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'", @@ -196,9 +195,6 @@ rlinf = [ leapp = [ "leapp>=0.5.2", ] -# Curated OV backends, RL libraries, and visualizers in one flag. Isaac Sim and the -# specialized extras (rlinf, mimic, teleop, tetrahedralization, video, leapp) plus -# the developer ``test`` tooling stay opt-in by name. all = [ "isaaclab-dev[sb3,skrl,rl-games,rsl-rl,viser,rerun,ov]", ] diff --git a/source/isaaclab/test/cli/test_uv_run_pyproject.py b/source/isaaclab/test/cli/test_uv_run_pyproject.py index 41de1338459e..25a9ed830961 100644 --- a/source/isaaclab/test/cli/test_uv_run_pyproject.py +++ b/source/isaaclab/test/cli/test_uv_run_pyproject.py @@ -83,23 +83,15 @@ def test_uv_run_exposes_centralized_feature_extras(): def test_all_extra_aggregates_curated_ov_rl_and_visualizer_extras(): - """``all`` aggregates the curated OV, RL library, and visualizer extras. - - Isaac Sim and specialized workflows stay opt-in by name because they need separate - installation steps, are narrowly used, or both. - """ + """``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 == {"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", diff --git a/source/isaaclab/test/cli/test_wheel_builder_metadata.py b/source/isaaclab/test/cli/test_wheel_builder_metadata.py index 2338cd77648b..b850b466781f 100644 --- a/source/isaaclab/test/cli/test_wheel_builder_metadata.py +++ b/source/isaaclab/test/cli/test_wheel_builder_metadata.py @@ -94,22 +94,14 @@ def test_wheel_builder_requests_required_tinyobjloader_prerelease_directly(tmp_p def test_wheel_builder_expands_all_extra_into_concrete_requirements(tmp_path): - """``isaaclab[all]`` must ship the aggregated requirements, not a self-reference. - - 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 the curated OV backends, RL libraries, and visualizers. - """ + """``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: both OV backends, the RL libraries, - # and the visualizers. 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" - # Isaac Sim, specialized extras, and developer tooling stay opt-in by name. for prefix in ("isaacsim[", "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 3df6d80cc294..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 both OV backends plus - the full curated RL and visualizer set. The dedicated ``[all]`` install test covers it. - """ + """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 566a80b168cf..4b3cb620b171 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 @@ -47,8 +47,6 @@ def setup_class(cls): 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 both OV backends and the curated RL library and visualizer set. self.create_uv_env(isaaclab_root) result = self.run_in_uv_env( @@ -56,11 +54,7 @@ def test_uv_pip_install_isaaclab_all_trains_cartpole(self, isaaclab_root, wheel, ) 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. + # Restore the CUDA build selected for this architecture. result = self.run_in_uv_env( [ "uv", @@ -79,7 +73,6 @@ def test_uv_pip_install_isaaclab_all_trains_cartpole(self, isaaclab_root, wheel, ) 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, 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 fd3905f19327..afb4580b9a25 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,11 +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 to keep this import test small. The aggregate ``all`` - extra also excludes Isaac Sim, but adds the curated OV, RL, and visualizer dependencies. - """ + """Verify RL imports without Isaac Sim.""" _wheel: str = "" _extras: str = "[sb3,skrl,rsl-rl]" @@ -43,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 From df47129229336f1d5419fd1f90e7c91da0e6d203 Mon Sep 17 00:00:00 2001 From: Mustafa Haiderbhai Date: Wed, 19 Aug 2026 23:06:16 -0700 Subject: [PATCH 09/15] Support released Newton versions in wheel installs --- .../all-extra-without-isaacsim.major.rst | 5 +++++ .../isaaclab/actuators/newton/adapter.py | 19 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/source/isaaclab/changelog.d/all-extra-without-isaacsim.major.rst b/source/isaaclab/changelog.d/all-extra-without-isaacsim.major.rst index 1806217e8c7c..f08044d355eb 100644 --- a/source/isaaclab/changelog.d/all-extra-without-isaacsim.major.rst +++ b/source/isaaclab/changelog.d/all-extra-without-isaacsim.major.rst @@ -3,3 +3,8 @@ Changed * **Breaking:** Changed the ``isaaclab[all]`` extra to exclude Isaac Sim. Install ``isaaclab[isaacsim]`` with the documented resolver overrides when Isaac Sim is required. + +Fixed +^^^^^ + +* Fixed Newton actuator imports with the minimum Newton versions supported by the wheel. 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) From 19ed0e2fe77930a8ea67280a446336351f6a9b8e Mon Sep 17 00:00:00 2001 From: Mustafa Haiderbhai Date: Thu, 20 Aug 2026 16:02:06 -0700 Subject: [PATCH 10/15] Test all wheel extra with OV backends --- .../test/install_ci/misc/cartpole_training_smoke.py | 10 +++++++--- ...test_uv_pip_install_isaaclab_all_trains_cartpole.py | 8 ++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/source/isaaclab/test/install_ci/misc/cartpole_training_smoke.py b/source/isaaclab/test/install_ci/misc/cartpole_training_smoke.py index 469f7dc9ab9b..786769e88c9e 100644 --- a/source/isaaclab/test/install_ci/misc/cartpole_training_smoke.py +++ b/source/isaaclab/test/install_ci/misc/cartpole_training_smoke.py @@ -16,6 +16,9 @@ import subprocess from pathlib import Path +_PHYSICS_PRESET = os.environ.get("ISAACLAB_CARTPOLE_SMOKE_PHYSICS", "newton_mjwarp") +_RENDERER_PRESET = os.environ.get("ISAACLAB_CARTPOLE_SMOKE_RENDERER", "newton_renderer") + _STATE_TRAIN_CMD = [ "train", "--rl_library", @@ -24,7 +27,7 @@ "Isaac-Cartpole-Direct", "--num_envs", "16", - "presets=newton_mjwarp", + f"physics={_PHYSICS_PRESET}", "--max_iterations", "5", ] @@ -37,7 +40,8 @@ "Isaac-Cartpole-Camera-Direct", "--num_envs", "16", - "presets=newton_mjwarp,newton_renderer", + f"physics={_PHYSICS_PRESET}", + f"renderer={_RENDERER_PRESET}", "--max_iterations", "2", ] @@ -87,7 +91,7 @@ def test_render_cartpole_camera_produces_valid_observation_and_reward() -> None: from isaaclab_tasks.core.cartpole.cartpole_direct_camera_env_cfg import CartpoleCameraEnvCfg from isaaclab_tasks.utils.hydra import resolve_presets - env_cfg = resolve_presets(CartpoleCameraEnvCfg(), selected={"newton_mjwarp", "newton_renderer"}) + env_cfg = resolve_presets(CartpoleCameraEnvCfg(), selected={_PHYSICS_PRESET, _RENDERER_PRESET}) env_cfg.scene.num_envs = 2 env_cfg.frame_stack = 1 env = None 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 4b3cb620b171..1fd868ade560 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 @@ -16,8 +16,8 @@ - (aarch64 only) export LD_PRELOAD=/lib/aarch64-linux-gnu/libgomp.so.1 Tests: - 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 + physics=ovphysx --max_iterations 5; uv run isaaclab train --rl_library rsl_rl + --task Isaac-Cartpole-Camera-Direct --num_envs 16 physics=ovphysx renderer=ovrtx --max_iterations 2 -> verify state training, camera rendering, and camera training work """ @@ -76,6 +76,10 @@ def test_uv_pip_install_isaaclab_all_trains_cartpole(self, isaaclab_root, wheel, result = self.run_in_uv_env( [str(self.python), str(cartpole_smoke_script)], cwd=isaaclab_root, + env={ + "ISAACLAB_CARTPOLE_SMOKE_PHYSICS": "ovphysx", + "ISAACLAB_CARTPOLE_SMOKE_RENDERER": "ovrtx", + }, timeout=3000, ) assert result.returncode == 0, f"Cartpole smoke failed:\n{result.stdout}\n{result.stderr}" From bcb38f2769d47865e36a3036725394a2b3bf319a Mon Sep 17 00:00:00 2001 From: Mustafa Haiderbhai Date: Thu, 20 Aug 2026 18:55:02 -0700 Subject: [PATCH 11/15] Make standalone importers opt-in --- .github/workflows/wheel.yml | 5 ++++ docs/_extensions/isaaclab_docs.py | 20 +++++++++++++ docs/source/how-to/import_new_asset.rst | 3 +- .../installation/include/pip_extras_note.rst | 3 +- docs/source/setup/installation/index.rst | 30 +++++++++++++++---- docs/source/setup/quickstart.rst | 5 ++-- pyproject.toml | 17 ++++++----- skills/user/setup-troubleshooting/SKILL.md | 2 +- .../all-extra-without-isaacsim.major.rst | 2 ++ .../test/cli/test_source_package_metadata.py | 13 +++++--- .../test/cli/test_uv_run_pyproject.py | 2 ++ .../test/cli/test_wheel_builder_metadata.py | 24 ++++++++++++--- .../misc/cartpole_training_smoke.py | 10 ++----- ...ip_install_isaaclab_all_trains_cartpole.py | 18 +++++++---- ...tall_isaaclab_rl_tasks_imports_rl_tasks.py | 4 +-- uv.lock | 28 ++++++++++------- 16 files changed, 133 insertions(+), 53 deletions(-) diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index 8a097abe6e4f..16b8ee7a32ea 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -196,6 +196,11 @@ jobs: 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" \ diff --git a/docs/_extensions/isaaclab_docs.py b/docs/_extensions/isaaclab_docs.py index 074b2ec8b974..0ad216ce5085 100644 --- a/docs/_extensions/isaaclab_docs.py +++ b/docs/_extensions/isaaclab_docs.py @@ -224,6 +224,25 @@ def run(self) -> list[nodes.Node]: 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) + + class IsaacLabTorchInstall(SphinxDirective): """Render the pinned ``torch``/``torchvision`` install command for a CUDA build. @@ -324,6 +343,7 @@ def setup(app): app.add_directive("isaaclab-quickstart-install", IsaacLabQuickstartInstall) app.add_directive("isaaclab-isaacsim-install", IsaacLabIsaacSimInstall) 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/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/installation/include/pip_extras_note.rst b/docs/source/setup/installation/include/pip_extras_note.rst index 4f5cbe91a42c..e97ef912b9bc 100644 --- a/docs/source/setup/installation/include/pip_extras_note.rst +++ b/docs/source/setup/installation/include/pip_extras_note.rst @@ -2,4 +2,5 @@ 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; request ``[isaacsim]`` separately. + It does not include Isaac Sim or the standalone importers; request ``[isaacsim]`` or + ``[importers]`` separately. diff --git a/docs/source/setup/installation/index.rst b/docs/source/setup/installation/index.rst index fb2a4ca2e6d8..8029915584b6 100644 --- a/docs/source/setup/installation/index.rst +++ b/docs/source/setup/installation/index.rst @@ -574,9 +574,10 @@ or temporary work. Optional extras ~~~~~~~~~~~~~~~ -Add extras to the package requirement when your project needs them. Except for ``isaacsim``, use +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. Isaac Sim has a separate command below. +``uv add "isaaclab[]"`` in a uv project. The ``importers`` and ``isaacsim`` extras +have dedicated commands below. .. list-table:: :header-rows: 1 @@ -601,15 +602,31 @@ Add extras to the package requirement when your project needs them. Except for ` - Mesh tetrahedralization / video recording. * - ``leapp`` - LEAP model export support. + * - ``importers`` + - Standalone URDF and MJCF conversion without Isaac Sim. * - ``all`` - The curated ``ov``, ``rl-games``, ``sb3``, ``skrl``, ``rsl-rl``, ``rerun``, and ``viser`` extras. Isaac Sim is not included. * - ``test`` - Developer test and documentation tooling. -Use ``all`` for the curated list above. Isaac Sim, the specialized extras (``rlinf``, ``mimic``, -``teleop``, ``tetrahedralization``, ``video``, ``leapp``), and the developer ``test`` tooling -remain opt-in. +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. + +.. _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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -622,6 +639,9 @@ the tested overrides: 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: .. tab-set:: diff --git a/docs/source/setup/quickstart.rst b/docs/source/setup/quickstart.rst index 36d621db6966..285163920317 100644 --- a/docs/source/setup/quickstart.rst +++ b/docs/source/setup/quickstart.rst @@ -50,8 +50,9 @@ Training outputs, including checkpoints, are saved under ``logs/``. Add 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 the curated ``ov``, ``rl-games``, ``sb3``, ``skrl``, ``rsl-rl``, ``rerun``, - and ``viser`` extras. Isaac Sim and specialized extras such as ``rlinf``, ``mimic``, ``teleop``, - ``tetrahedralization``, ``video``, and ``leapp`` are not included; add them explicitly. See + 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 bcb32a9b982f..5ca4637dc197 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,12 +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", - # uv requires direct opt-in to transitive pre-releases. - "tinyobjloader==2.0.0rc13", # 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 ----- @@ -120,6 +114,13 @@ tetrahedralization = ["pytetwild[all]>=0.3.0,<0.4"] video = ["moviepy>=1.0.3,<2.0.0.dev0"] +# Standalone URDF/MJCF importers for conversion without Isaac Sim. The direct +# tinyobjloader requirement opts uv into the transitive pre-release. +importers = [ + "isaacsim-asset-isolated>=6.0,<6.1", + "tinyobjloader==2.0.0rc13", +] + test = [ "pytest", "pytest-mock", @@ -388,8 +389,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/setup-troubleshooting/SKILL.md b/skills/user/setup-troubleshooting/SKILL.md index 1c067fab9bd0..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. The `all` extra is the curated `ov`, `rl-games`, `sb3`, `skrl`, `rsl-rl`, `rerun`, and `viser` list; it excludes Isaac Sim. 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 index f08044d355eb..84998e9b74fc 100644 --- a/source/isaaclab/changelog.d/all-extra-without-isaacsim.major.rst +++ b/source/isaaclab/changelog.d/all-extra-without-isaacsim.major.rst @@ -3,6 +3,8 @@ 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 ^^^^^ 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 25a9ed830961..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", @@ -95,6 +96,7 @@ def test_all_extra_aggregates_curated_ov_rl_and_visualizer_extras(): 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 b850b466781f..7ac6515aee45 100644 --- a/source/isaaclab/test/cli/test_wheel_builder_metadata.py +++ b/source/isaaclab/test/cli/test_wheel_builder_metadata.py @@ -86,11 +86,17 @@ def test_wheel_builder_includes_isaacsim_extra(tmp_path): assert any(dep.startswith("isaacsim[") for dep in optional_dependencies["isaacsim"]) -def test_wheel_builder_requests_required_tinyobjloader_prerelease_directly(tmp_path): - """Plain wheel installs must opt into the isolated importer's prerelease dependency.""" +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"] - assert "tinyobjloader==2.0.0rc13" in generated["project"]["dependencies"] + 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): @@ -102,7 +108,17 @@ def test_wheel_builder_expands_all_extra_into_concrete_requirements(tmp_path): assert not any(dep.lower().startswith("isaaclab") for dep in all_extra) 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" - for prefix in ("isaacsim[", "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/cartpole_training_smoke.py b/source/isaaclab/test/install_ci/misc/cartpole_training_smoke.py index 786769e88c9e..469f7dc9ab9b 100644 --- a/source/isaaclab/test/install_ci/misc/cartpole_training_smoke.py +++ b/source/isaaclab/test/install_ci/misc/cartpole_training_smoke.py @@ -16,9 +16,6 @@ import subprocess from pathlib import Path -_PHYSICS_PRESET = os.environ.get("ISAACLAB_CARTPOLE_SMOKE_PHYSICS", "newton_mjwarp") -_RENDERER_PRESET = os.environ.get("ISAACLAB_CARTPOLE_SMOKE_RENDERER", "newton_renderer") - _STATE_TRAIN_CMD = [ "train", "--rl_library", @@ -27,7 +24,7 @@ "Isaac-Cartpole-Direct", "--num_envs", "16", - f"physics={_PHYSICS_PRESET}", + "presets=newton_mjwarp", "--max_iterations", "5", ] @@ -40,8 +37,7 @@ "Isaac-Cartpole-Camera-Direct", "--num_envs", "16", - f"physics={_PHYSICS_PRESET}", - f"renderer={_RENDERER_PRESET}", + "presets=newton_mjwarp,newton_renderer", "--max_iterations", "2", ] @@ -91,7 +87,7 @@ def test_render_cartpole_camera_produces_valid_observation_and_reward() -> None: from isaaclab_tasks.core.cartpole.cartpole_direct_camera_env_cfg import CartpoleCameraEnvCfg from isaaclab_tasks.utils.hydra import resolve_presets - env_cfg = resolve_presets(CartpoleCameraEnvCfg(), selected={_PHYSICS_PRESET, _RENDERER_PRESET}) + env_cfg = resolve_presets(CartpoleCameraEnvCfg(), selected={"newton_mjwarp", "newton_renderer"}) env_cfg.scene.num_envs = 2 env_cfg.frame_stack = 1 env = None 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 1fd868ade560..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 @@ -15,9 +15,11 @@ 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 - physics=ovphysx --max_iterations 5; uv run isaaclab train --rl_library rsl_rl - --task Isaac-Cartpole-Camera-Direct --num_envs 16 physics=ovphysx renderer=ovrtx --max_iterations 2 + 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 -> verify state training, camera rendering, and camera training work """ @@ -54,6 +56,14 @@ def test_uv_pip_install_isaaclab_all_trains_cartpole(self, isaaclab_root, wheel, ) assert result.returncode == 0, f"uv pip install {wheel}[all] failed:\n{result.stdout}\n{result.stderr}" + 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( [ @@ -76,10 +86,6 @@ def test_uv_pip_install_isaaclab_all_trains_cartpole(self, isaaclab_root, wheel, result = self.run_in_uv_env( [str(self.python), str(cartpole_smoke_script)], cwd=isaaclab_root, - env={ - "ISAACLAB_CARTPOLE_SMOKE_PHYSICS": "ovphysx", - "ISAACLAB_CARTPOLE_SMOKE_RENDERER": "ovrtx", - }, 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_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 afb4580b9a25..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 @@ -81,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/uv.lock b/uv.lock index 82b672cb2fcd..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')" }, @@ -1852,7 +1855,6 @@ dependencies = [ { name = "scipy", 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 = "starlette", 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 = "tensorboard", 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')" }, { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, { name = "torch", version = "2.11.0+cu130", source = { registry = "https://download.pytorch.org/whl/cu130" }, marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, { name = "torchaudio", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "(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')" }, ] @@ -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,7 +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", specifier = "==2.0.0rc13" }, + { 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" }, @@ -2116,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" @@ -2148,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')" }, @@ -2159,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')" }, @@ -2174,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')" }, @@ -2213,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')" }, @@ -2254,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')" }, From 9503cde05fe3de737208eda60b6dde7c62cb072a Mon Sep 17 00:00:00 2001 From: Mustafa Haiderbhai Date: Thu, 20 Aug 2026 19:16:50 -0700 Subject: [PATCH 12/15] Remove redundant importer dependency comment --- pyproject.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5ca4637dc197..f7fd71b6ed39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,8 +114,6 @@ tetrahedralization = ["pytetwild[all]>=0.3.0,<0.4"] video = ["moviepy>=1.0.3,<2.0.0.dev0"] -# Standalone URDF/MJCF importers for conversion without Isaac Sim. The direct -# tinyobjloader requirement opts uv into the transitive pre-release. importers = [ "isaacsim-asset-isolated>=6.0,<6.1", "tinyobjloader==2.0.0rc13", From c1c1d9c688cce255c9153149834e106b3aa98378 Mon Sep 17 00:00:00 2001 From: Mustafa Haiderbhai Date: Fri, 21 Aug 2026 01:35:12 -0700 Subject: [PATCH 13/15] Install importers in kitless Docker image --- .github/actions/_lib/compute-deps-hash/action.yml | 1 + .github/workflows/kitless-docker.yml | 1 + docker/Dockerfile.kitless | 11 +++++++++-- docker/test/test_dockerfile_nonroot.py | 9 ++++++++- 4 files changed, 19 insertions(+), 3 deletions(-) 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/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 From 48ad3ded73d203fe4f6a968870155c6210f85dd6 Mon Sep 17 00:00:00 2001 From: Richard Lei Date: Sat, 22 Aug 2026 02:10:46 +1200 Subject: [PATCH 14/15] Remove ovstage host copies (#7157) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description ovstage 0.1.1 fixes nvbug 6490020 so the OVRTX ovstage path no longer needs its per-frame host round-trip - Write transforms and points straight from their Warp GPU buffers; make_dltensor now folds a producer's trailing axes into the lane count omni:xform and points expect. - Order those writes with write_attribute(cuda_stream=...) instead of blocking the host on wp.synchronize_device, as the legacy binding path already does. - Drop the _OVSTAGE_AVAILABLE guard: ovstage is an unconditional dependency of isaaclab_ov, so the fallback was unreachable. ## Type of change - New feature (non-breaking change which adds functionality) ## Checklist - [ ] I have read and understood the [contribution guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html) - [ ] I have run the [`pre-commit` checks](https://pre-commit.com/) with `./isaaclab.sh --format` - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added a changelog fragment under `source//changelog.d/` for every touched package (do **not** edit `CHANGELOG.rst` or bump `extension.toml` — CI handles that) - [ ] I have added my name to the `CONTRIBUTORS.md` or my name already exists there --- .../ovstage-performance-improvements.rst | 11 ++ .../isaaclab_ov/physics/ovphysx_manager.py | 3 +- .../isaaclab_ov/renderers/ovrtx_renderer.py | 167 +++++++----------- source/isaaclab_ov/isaaclab_ov/stage.py | 103 +++++++++++ .../test_ovphysx_scene_data_backend.py | 8 +- .../isaaclab_ov/test/test_ovrtx_clone_plan.py | 2 +- .../test/test_ovrtx_deformable_bindings.py | 44 +++++ .../test/test_ovrtx_renderer_contract.py | 15 +- 8 files changed, 231 insertions(+), 122 deletions(-) create mode 100644 source/isaaclab_ov/changelog.d/ovstage-performance-improvements.rst create mode 100644 source/isaaclab_ov/isaaclab_ov/stage.py 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 88b4611bed2c..da51e27a19cf 100644 --- a/source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py +++ b/source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py @@ -32,22 +32,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. @@ -80,6 +70,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, @@ -134,41 +131,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. @@ -177,20 +139,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" @@ -1697,9 +1649,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: @@ -1717,6 +1666,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). @@ -1761,7 +1712,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 @@ -1870,7 +1821,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() @@ -2044,7 +1995,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() @@ -2055,10 +2006,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: @@ -2084,12 +2035,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).""" @@ -2139,7 +2096,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() @@ -2171,17 +2128,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: @@ -2200,19 +2161,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, ) @@ -2220,7 +2172,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, ) @@ -2231,7 +2183,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: @@ -2239,15 +2191,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", @@ -2255,32 +2214,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( @@ -2307,15 +2260,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: @@ -2400,6 +2353,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/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/test_ovrtx_clone_plan.py b/source/isaaclab_ov/test/test_ovrtx_clone_plan.py index a0cc5f12f834..f62294eaeaff 100644 --- a/source/isaaclab_ov/test/test_ovrtx_clone_plan.py +++ b/source/isaaclab_ov/test/test_ovrtx_clone_plan.py @@ -277,7 +277,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() From e235d612bb57fa74876ce2a81e7fce8d9f63d19b Mon Sep 17 00:00:00 2001 From: Antoine RICHARD Date: Fri, 21 Aug 2026 17:31:44 +0200 Subject: [PATCH 15/15] Fix colorized semantic segmentation normalization (#7216) # Description Fixes `mdp.image(normalize=True)` returning colorized semantic-segmentation observations as `uint8`. The normalizer now recognizes the renderer contract for this output (RGBA `uint8`) and returns the same normalized `float32` representation used for RGB-like images. Raw `int32` semantic-ID maps remain unchanged. No new dependencies. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## Screenshots Not applicable. ## Checklist - [x] I have read and understood the contribution guidelines - [x] I have run the pre-commit checks with `uv run isaaclab -f` - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] I have added a changelog fragment under `source//changelog.d/` for every touched package - [x] I have added my name to the `CONTRIBUTORS.md` or my name already exists there --- ...malize-colorized-semantic-segmentation.rst | 5 ++++ source/isaaclab/isaaclab/utils/images.py | 24 +++++++++++-------- source/isaaclab/test/utils/test_images.py | 17 +++++++++++++ 3 files changed, 36 insertions(+), 10 deletions(-) create mode 100644 source/isaaclab/changelog.d/normalize-colorized-semantic-segmentation.rst 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/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/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``."""