From b7f0fb6a0674655da91f80919fbc272a508a25f5 Mon Sep 17 00:00:00 2001 From: Kelly Guo Date: Thu, 6 Aug 2026 13:04:51 -0700 Subject: [PATCH 1/3] Limit direct filtering to Isaac Sim PhysX OVPhysX assigns native environment IDs during GPU replication, so manually authoring USD collision groups is redundant. Keep explicit collision filtering for the Isaac Sim PhysX replication path and cover both backends with a Cartpole actuation regression. --- scripts/demos/pick_and_place.py | 4 +- scripts/tutorials/06_deploy/anymal_c_env.py | 4 +- .../limit-direct-physx-filtering.rst | 5 + .../contrib/anymal_c_direct/anymal_c_env.py | 4 +- .../contrib/factory/factory_env.py | 4 +- .../contrib/humanoid_amp/humanoid_amp_env.py | 4 +- .../cartpole/cartpole_direct_camera_env.py | 4 +- .../core/cartpole/cartpole_direct_env.py | 4 +- .../core/handover/handover_env.py | 4 +- .../core/locomotion/locomotion_direct_env.py | 4 +- .../core/pendulum/pendulum_env.py | 4 +- .../shadow_hand_direct_camera_env.py | 4 +- .../core/reorient/reorient_direct_env.py | 4 +- .../test_cartpole_direct_physx_actuation.py | 97 +++++++++++++++++++ 14 files changed, 126 insertions(+), 24 deletions(-) create mode 100644 source/isaaclab_tasks/changelog.d/limit-direct-physx-filtering.rst create mode 100644 source/isaaclab_tasks/test/core/test_cartpole_direct_physx_actuation.py diff --git a/scripts/demos/pick_and_place.py b/scripts/demos/pick_and_place.py index e23ee1a2450c..a3fa3239a2c8 100644 --- a/scripts/demos/pick_and_place.py +++ b/scripts/demos/pick_and_place.py @@ -230,8 +230,8 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # PhysX replication requires explicit collision filtering between environments. - if "physx" in self.scene.physics_backend: + # Isaac Sim PhysX replication requires explicit collision filtering between environments. + if self.scene.physics_backend == "physxmanager": self.scene.filter_collisions(global_prim_paths=["/World/ground"]) # add articulation to scene self.scene.articulations["pick_and_place"] = self.pick_and_place diff --git a/scripts/tutorials/06_deploy/anymal_c_env.py b/scripts/tutorials/06_deploy/anymal_c_env.py index c3999cb5b8b5..20216db5cc4a 100644 --- a/scripts/tutorials/06_deploy/anymal_c_env.py +++ b/scripts/tutorials/06_deploy/anymal_c_env.py @@ -68,8 +68,8 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # PhysX replication requires explicit collision filtering between environments. - if "physx" in self.scene.physics_backend: + # Isaac Sim PhysX replication requires explicit collision filtering between environments. + if self.scene.physics_backend == "physxmanager": self.scene.filter_collisions(global_prim_paths=[self.cfg.terrain.prim_path]) light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75)) light_cfg.func("/World/Light", light_cfg) diff --git a/source/isaaclab_tasks/changelog.d/limit-direct-physx-filtering.rst b/source/isaaclab_tasks/changelog.d/limit-direct-physx-filtering.rst new file mode 100644 index 000000000000..d35a2961b2fe --- /dev/null +++ b/source/isaaclab_tasks/changelog.d/limit-direct-physx-filtering.rst @@ -0,0 +1,5 @@ +Fixed +^^^^^ + +* Limited explicit collision filtering in manually cloned direct environments to Isaac Sim PhysX so + OvPhysX uses its native environment-ID isolation. diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/anymal_c_direct/anymal_c_env.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/anymal_c_direct/anymal_c_env.py index 5d25326c2923..26eceada9b86 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/anymal_c_direct/anymal_c_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/anymal_c_direct/anymal_c_env.py @@ -77,8 +77,8 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # PhysX replication requires explicit collision filtering between environments. - if "physx" in self.scene.physics_backend: + # Isaac Sim PhysX replication requires explicit collision filtering between environments. + if self.scene.physics_backend == "physxmanager": self.scene.filter_collisions(global_prim_paths=[self.cfg.terrain.prim_path]) # add lights light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75)) diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/factory/factory_env.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/factory/factory_env.py index e85b6835292f..415ba05daa10 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/factory/factory_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/factory/factory_env.py @@ -103,8 +103,8 @@ def _setup_scene(self): plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # PhysX replication requires explicit collision filtering between environments. - if "physx" in self.scene.physics_backend: + # Isaac Sim PhysX replication requires explicit collision filtering between environments. + if self.scene.physics_backend == "physxmanager": self.scene.filter_collisions() self.scene.articulations["robot"] = self._robot diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/humanoid_amp/humanoid_amp_env.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/humanoid_amp/humanoid_amp_env.py index fccda95dc46a..5d91a9442680 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/humanoid_amp/humanoid_amp_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/humanoid_amp/humanoid_amp_env.py @@ -69,8 +69,8 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # PhysX replication requires explicit collision filtering between environments. - if "physx" in self.scene.physics_backend: + # Isaac Sim PhysX replication requires explicit collision filtering between environments. + if self.scene.physics_backend == "physxmanager": self.scene.filter_collisions(global_prim_paths=["/World/ground"]) # add articulation to scene diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_camera_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_camera_env.py index 0585fa8b3c85..1d1c9fcaee3e 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_camera_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_camera_env.py @@ -56,8 +56,8 @@ def _setup_scene(self): plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # PhysX replication requires explicit collision filtering between environments. - if "physx" in self.scene.physics_backend: + # Isaac Sim PhysX replication requires explicit collision filtering between environments. + if self.scene.physics_backend == "physxmanager": self.scene.filter_collisions(global_prim_paths=[]) # add articulation and sensors to scene diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_env.py index 2378183bcf21..1b08f4754405 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_env.py @@ -43,8 +43,8 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # PhysX replication requires explicit collision filtering between environments. - if "physx" in self.scene.physics_backend: + # Isaac Sim PhysX replication requires explicit collision filtering between environments. + if self.scene.physics_backend == "physxmanager": self.scene.filter_collisions(global_prim_paths=[]) # add articulation to scene diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env.py index ce78692d54fa..5be743ce7041 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env.py @@ -96,8 +96,8 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # PhysX replication requires explicit collision filtering between environments. - if "physx" in self.scene.physics_backend: + # Isaac Sim PhysX replication requires explicit collision filtering between environments. + if self.scene.physics_backend == "physxmanager": self.scene.filter_collisions(global_prim_paths=["/World/ground"]) # add articulation to scene - we must register to scene to randomize with EventManager self.scene.articulations["right_robot"] = self.right_hand diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/locomotion/locomotion_direct_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/locomotion/locomotion_direct_env.py index 2416a95df9ab..e24a754d3157 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/locomotion/locomotion_direct_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/locomotion/locomotion_direct_env.py @@ -69,8 +69,8 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # PhysX replication requires explicit collision filtering between environments. - if "physx" in self.scene.physics_backend: + # Isaac Sim PhysX replication requires explicit collision filtering between environments. + if self.scene.physics_backend == "physxmanager": self.scene.filter_collisions(global_prim_paths=[self.cfg.terrain.prim_path]) # add articulation and the feet wrench sensor to scene self.scene.articulations["robot"] = self.robot diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/pendulum/pendulum_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/pendulum/pendulum_env.py index f0e97fded0c9..1bebd18ab572 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/pendulum/pendulum_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/pendulum/pendulum_env.py @@ -47,8 +47,8 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # PhysX replication requires explicit collision filtering between environments. - if "physx" in self.scene.physics_backend: + # Isaac Sim PhysX replication requires explicit collision filtering between environments. + if self.scene.physics_backend == "physxmanager": self.scene.filter_collisions(global_prim_paths=[]) # add articulation to scene self.scene.articulations["robot"] = self.robot diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env.py index 025a5c692b3b..60781862c1da 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env.py @@ -57,8 +57,8 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # PhysX replication requires explicit collision filtering between environments. - if "physx" in self.scene.physics_backend: + # Isaac Sim PhysX replication requires explicit collision filtering between environments. + if self.scene.physics_backend == "physxmanager": self.scene.filter_collisions(global_prim_paths=[]) # add articulation to scene - we must register to scene to randomize with EventManager self.scene.articulations["robot"] = self.hand diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_direct_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_direct_env.py index c2c47e66491b..a0971b7d330f 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_direct_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_direct_env.py @@ -106,8 +106,8 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # PhysX replication requires explicit collision filtering between environments. - if "physx" in self.scene.physics_backend: + # Isaac Sim PhysX replication requires explicit collision filtering between environments. + if self.scene.physics_backend == "physxmanager": self.scene.filter_collisions(global_prim_paths=["/World/ground"]) # add articulation to scene - we must register to scene to randomize with EventManager self.scene.articulations["robot"] = self.hand diff --git a/source/isaaclab_tasks/test/core/test_cartpole_direct_physx_actuation.py b/source/isaaclab_tasks/test/core/test_cartpole_direct_physx_actuation.py new file mode 100644 index 000000000000..041b1bfea6b2 --- /dev/null +++ b/source/isaaclab_tasks/test/core/test_cartpole_direct_physx_actuation.py @@ -0,0 +1,97 @@ +# 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 + +"""Test direct-workflow Cartpole actuation with both PhysX backends.""" + +from __future__ import annotations + +import argparse +import importlib.util +import subprocess +import sys + +import pytest + + +def _run_cartpole_diagnostic(physics_backend: str) -> None: + """Run the Cartpole actuation diagnostic in an isolated simulator process.""" + from isaaclab.app import launch_simulation + + from isaaclab_tasks.core.cartpole.cartpole_direct_env_cfg import CartpoleEnvCfg + from isaaclab_tasks.utils.hydra import resolve_presets + + cfg = resolve_presets(CartpoleEnvCfg(), (physics_backend,)) + cfg.scene.num_envs = 4 + cfg.sim.device = "cuda:0" + cfg.seed = 0 + cfg.initial_cart_position_range = (0.0, 0.0) + cfg.initial_cart_velocity_range = (0.0, 0.0) + cfg.initial_pole_angle_range = (0.0, 0.0) + cfg.initial_pole_velocity_range = (0.0, 0.0) + + launcher_args = { + "device": "cuda:0", + "headless": True, + "visualizer": None, + "visualizer_explicit": True, + } + with launch_simulation(cfg, launcher_args): + import gymnasium as gym + import torch + + import isaaclab_tasks # noqa: F401, PLC0415 + + env = gym.make("Isaac-Cartpole-Direct", cfg=cfg) + try: + has_collision_groups = bool(env.unwrapped.scene.stage.GetPrimAtPath("/World/collisions")) + assert has_collision_groups == (physics_backend == "isaacsim_physx"), ( + f"Unexpected collision-group state for {physics_backend}: {has_collision_groups}." + ) + + env.reset() + actions = torch.ones(env.action_space.shape, device=env.unwrapped.device) + with torch.inference_mode(): + for _ in range(20): + env.step(actions) + + cart_idx = env.unwrapped._cart_dof_idx[0] + cart_vel = env.unwrapped.cartpole.data.joint_vel.torch[:, cart_idx] + assert torch.all(cart_vel > 1.0), f"Expected every cart to accelerate, got {cart_vel.tolist()}." + torch.testing.assert_close(cart_vel, cart_vel[0].expand_as(cart_vel)) + print(f"CARTPOLE_DIAGNOSTIC_PASSED {physics_backend}") + finally: + env.close() + + +@pytest.mark.isaacsim_ci +@pytest.mark.integration +@pytest.mark.parametrize("physics_backend", ("isaacsim_physx", "ovphysx")) +def test_cartpole_direct_physx_actuation_is_consistent_across_environments(physics_backend: str): + """Verify consistent Cartpole actuation and backend-specific collision isolation.""" + if physics_backend == "isaacsim_physx": + from isaaclab.app import AppLauncher + + if not AppLauncher.is_available(): + pytest.skip("Isaac Sim is not installed") + if physics_backend == "ovphysx" and importlib.util.find_spec("ovphysx") is None: + pytest.skip("OVPhysX is not installed") + + result = subprocess.run( + [sys.executable, __file__, "--physics_backend", physics_backend], + capture_output=True, + text=True, + timeout=180, + ) + output = result.stdout + result.stderr + assert result.returncode == 0, f"Cartpole diagnostic failed:\n{output}" + assert "Traceback (most recent call last):" not in output, f"Cartpole diagnostic failed:\n{output}" + assert f"CARTPOLE_DIAGNOSTIC_PASSED {physics_backend}" in output, f"Cartpole diagnostic failed:\n{output}" + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--physics_backend", choices=("isaacsim_physx", "ovphysx"), required=True) + args = parser.parse_args() + _run_cartpole_diagnostic(args.physics_backend) From 5339d88aa131be9cf4b66b7e59f3daddde8507a7 Mon Sep 17 00:00:00 2001 From: Kelly Guo Date: Thu, 6 Aug 2026 21:54:15 -0700 Subject: [PATCH 2/3] Handle OvPhysX CPU collision filtering OvPhysX environment-ID isolation requires GPU dynamics and GPU broadphase. Keep USD collision filtering on CPU and native isolation on CUDA. Cover each PhysX backend/device pair with the Cartpole regression test. --- scripts/demos/pick_and_place.py | 6 +- scripts/tutorials/06_deploy/anymal_c_env.py | 6 +- .../limit-direct-physx-filtering.rst | 4 +- .../contrib/anymal_c_direct/anymal_c_env.py | 6 +- .../contrib/factory/factory_env.py | 6 +- .../contrib/humanoid_amp/humanoid_amp_env.py | 6 +- .../cartpole/cartpole_direct_camera_env.py | 6 +- .../core/cartpole/cartpole_direct_env.py | 6 +- .../core/handover/handover_env.py | 6 +- .../core/locomotion/locomotion_direct_env.py | 6 +- .../core/pendulum/pendulum_env.py | 6 +- .../shadow_hand_direct_camera_env.py | 6 +- .../core/reorient/reorient_direct_env.py | 6 +- .../test_cartpole_direct_physx_actuation.py | 61 +++++++++++-------- 14 files changed, 87 insertions(+), 50 deletions(-) diff --git a/scripts/demos/pick_and_place.py b/scripts/demos/pick_and_place.py index a3fa3239a2c8..7d7cb8ed9710 100644 --- a/scripts/demos/pick_and_place.py +++ b/scripts/demos/pick_and_place.py @@ -230,8 +230,10 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # Isaac Sim PhysX replication requires explicit collision filtering between environments. - if self.scene.physics_backend == "physxmanager": + # Isaac Sim PhysX always needs USD filtering; OvPhysX needs it on CPU. + if self.scene.physics_backend == "physxmanager" or ( + self.scene.physics_backend == "ovphysxmanager" and self.device == "cpu" + ): self.scene.filter_collisions(global_prim_paths=["/World/ground"]) # add articulation to scene self.scene.articulations["pick_and_place"] = self.pick_and_place diff --git a/scripts/tutorials/06_deploy/anymal_c_env.py b/scripts/tutorials/06_deploy/anymal_c_env.py index 20216db5cc4a..b7775617f01b 100644 --- a/scripts/tutorials/06_deploy/anymal_c_env.py +++ b/scripts/tutorials/06_deploy/anymal_c_env.py @@ -68,8 +68,10 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # Isaac Sim PhysX replication requires explicit collision filtering between environments. - if self.scene.physics_backend == "physxmanager": + # Isaac Sim PhysX always needs USD filtering; OvPhysX needs it on CPU. + if self.scene.physics_backend == "physxmanager" or ( + self.scene.physics_backend == "ovphysxmanager" and self.device == "cpu" + ): self.scene.filter_collisions(global_prim_paths=[self.cfg.terrain.prim_path]) light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75)) light_cfg.func("/World/Light", light_cfg) diff --git a/source/isaaclab_tasks/changelog.d/limit-direct-physx-filtering.rst b/source/isaaclab_tasks/changelog.d/limit-direct-physx-filtering.rst index d35a2961b2fe..2ce3b2fda94b 100644 --- a/source/isaaclab_tasks/changelog.d/limit-direct-physx-filtering.rst +++ b/source/isaaclab_tasks/changelog.d/limit-direct-physx-filtering.rst @@ -1,5 +1,5 @@ Fixed ^^^^^ -* Limited explicit collision filtering in manually cloned direct environments to Isaac Sim PhysX so - OvPhysX uses its native environment-ID isolation. +* Limited explicit collision filtering in manually cloned direct environments to Isaac Sim PhysX and + CPU OvPhysX so GPU OvPhysX uses its native environment-ID isolation. diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/anymal_c_direct/anymal_c_env.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/anymal_c_direct/anymal_c_env.py index 26eceada9b86..a3f392eee2b4 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/anymal_c_direct/anymal_c_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/anymal_c_direct/anymal_c_env.py @@ -77,8 +77,10 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # Isaac Sim PhysX replication requires explicit collision filtering between environments. - if self.scene.physics_backend == "physxmanager": + # Isaac Sim PhysX always needs USD filtering; OvPhysX needs it on CPU. + if self.scene.physics_backend == "physxmanager" or ( + self.scene.physics_backend == "ovphysxmanager" and self.device == "cpu" + ): self.scene.filter_collisions(global_prim_paths=[self.cfg.terrain.prim_path]) # add lights light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75)) diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/factory/factory_env.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/factory/factory_env.py index 415ba05daa10..a9d379f71a06 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/factory/factory_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/factory/factory_env.py @@ -103,8 +103,10 @@ def _setup_scene(self): plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # Isaac Sim PhysX replication requires explicit collision filtering between environments. - if self.scene.physics_backend == "physxmanager": + # Isaac Sim PhysX always needs USD filtering; OvPhysX needs it on CPU. + if self.scene.physics_backend == "physxmanager" or ( + self.scene.physics_backend == "ovphysxmanager" and self.device == "cpu" + ): self.scene.filter_collisions() self.scene.articulations["robot"] = self._robot diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/humanoid_amp/humanoid_amp_env.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/humanoid_amp/humanoid_amp_env.py index 5d91a9442680..b545be718946 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/humanoid_amp/humanoid_amp_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/humanoid_amp/humanoid_amp_env.py @@ -69,8 +69,10 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # Isaac Sim PhysX replication requires explicit collision filtering between environments. - if self.scene.physics_backend == "physxmanager": + # Isaac Sim PhysX always needs USD filtering; OvPhysX needs it on CPU. + if self.scene.physics_backend == "physxmanager" or ( + self.scene.physics_backend == "ovphysxmanager" and self.device == "cpu" + ): self.scene.filter_collisions(global_prim_paths=["/World/ground"]) # add articulation to scene diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_camera_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_camera_env.py index 1d1c9fcaee3e..a327e39edbf6 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_camera_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_camera_env.py @@ -56,8 +56,10 @@ def _setup_scene(self): plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # Isaac Sim PhysX replication requires explicit collision filtering between environments. - if self.scene.physics_backend == "physxmanager": + # Isaac Sim PhysX always needs USD filtering; OvPhysX needs it on CPU. + if self.scene.physics_backend == "physxmanager" or ( + self.scene.physics_backend == "ovphysxmanager" and self.device == "cpu" + ): self.scene.filter_collisions(global_prim_paths=[]) # add articulation and sensors to scene diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_env.py index 1b08f4754405..579e67d7e474 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_env.py @@ -43,8 +43,10 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # Isaac Sim PhysX replication requires explicit collision filtering between environments. - if self.scene.physics_backend == "physxmanager": + # Isaac Sim PhysX always needs USD filtering; OvPhysX needs it on CPU. + if self.scene.physics_backend == "physxmanager" or ( + self.scene.physics_backend == "ovphysxmanager" and self.device == "cpu" + ): self.scene.filter_collisions(global_prim_paths=[]) # add articulation to scene diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env.py index 5be743ce7041..f3e274103ec9 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env.py @@ -96,8 +96,10 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # Isaac Sim PhysX replication requires explicit collision filtering between environments. - if self.scene.physics_backend == "physxmanager": + # Isaac Sim PhysX always needs USD filtering; OvPhysX needs it on CPU. + if self.scene.physics_backend == "physxmanager" or ( + self.scene.physics_backend == "ovphysxmanager" and self.device == "cpu" + ): self.scene.filter_collisions(global_prim_paths=["/World/ground"]) # add articulation to scene - we must register to scene to randomize with EventManager self.scene.articulations["right_robot"] = self.right_hand diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/locomotion/locomotion_direct_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/locomotion/locomotion_direct_env.py index e24a754d3157..f308468588fb 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/locomotion/locomotion_direct_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/locomotion/locomotion_direct_env.py @@ -69,8 +69,10 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # Isaac Sim PhysX replication requires explicit collision filtering between environments. - if self.scene.physics_backend == "physxmanager": + # Isaac Sim PhysX always needs USD filtering; OvPhysX needs it on CPU. + if self.scene.physics_backend == "physxmanager" or ( + self.scene.physics_backend == "ovphysxmanager" and self.device == "cpu" + ): self.scene.filter_collisions(global_prim_paths=[self.cfg.terrain.prim_path]) # add articulation and the feet wrench sensor to scene self.scene.articulations["robot"] = self.robot diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/pendulum/pendulum_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/pendulum/pendulum_env.py index 1bebd18ab572..cd64a48809b5 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/pendulum/pendulum_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/pendulum/pendulum_env.py @@ -47,8 +47,10 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # Isaac Sim PhysX replication requires explicit collision filtering between environments. - if self.scene.physics_backend == "physxmanager": + # Isaac Sim PhysX always needs USD filtering; OvPhysX needs it on CPU. + if self.scene.physics_backend == "physxmanager" or ( + self.scene.physics_backend == "ovphysxmanager" and self.device == "cpu" + ): self.scene.filter_collisions(global_prim_paths=[]) # add articulation to scene self.scene.articulations["robot"] = self.robot diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env.py index 60781862c1da..c4b83a175c63 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env.py @@ -57,8 +57,10 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # Isaac Sim PhysX replication requires explicit collision filtering between environments. - if self.scene.physics_backend == "physxmanager": + # Isaac Sim PhysX always needs USD filtering; OvPhysX needs it on CPU. + if self.scene.physics_backend == "physxmanager" or ( + self.scene.physics_backend == "ovphysxmanager" and self.device == "cpu" + ): self.scene.filter_collisions(global_prim_paths=[]) # add articulation to scene - we must register to scene to randomize with EventManager self.scene.articulations["robot"] = self.hand diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_direct_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_direct_env.py index a0971b7d330f..520029ee8ec1 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_direct_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_direct_env.py @@ -106,8 +106,10 @@ def _setup_scene(self): pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0] plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos) cloner.replicate(plan, stage=self.scene.stage) - # Isaac Sim PhysX replication requires explicit collision filtering between environments. - if self.scene.physics_backend == "physxmanager": + # Isaac Sim PhysX always needs USD filtering; OvPhysX needs it on CPU. + if self.scene.physics_backend == "physxmanager" or ( + self.scene.physics_backend == "ovphysxmanager" and self.device == "cpu" + ): self.scene.filter_collisions(global_prim_paths=["/World/ground"]) # add articulation to scene - we must register to scene to randomize with EventManager self.scene.articulations["robot"] = self.hand diff --git a/source/isaaclab_tasks/test/core/test_cartpole_direct_physx_actuation.py b/source/isaaclab_tasks/test/core/test_cartpole_direct_physx_actuation.py index 041b1bfea6b2..138ec53b4e12 100644 --- a/source/isaaclab_tasks/test/core/test_cartpole_direct_physx_actuation.py +++ b/source/isaaclab_tasks/test/core/test_cartpole_direct_physx_actuation.py @@ -3,7 +3,7 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Test direct-workflow Cartpole actuation with both PhysX backends.""" +"""Test direct-workflow Cartpole isolation and CUDA actuation with both PhysX backends.""" from __future__ import annotations @@ -15,8 +15,8 @@ import pytest -def _run_cartpole_diagnostic(physics_backend: str) -> None: - """Run the Cartpole actuation diagnostic in an isolated simulator process.""" +def _run_cartpole_diagnostic(physics_backend: str, device: str) -> None: + """Run the Cartpole diagnostic in an isolated simulator process.""" from isaaclab.app import launch_simulation from isaaclab_tasks.core.cartpole.cartpole_direct_env_cfg import CartpoleEnvCfg @@ -24,7 +24,7 @@ def _run_cartpole_diagnostic(physics_backend: str) -> None: cfg = resolve_presets(CartpoleEnvCfg(), (physics_backend,)) cfg.scene.num_envs = 4 - cfg.sim.device = "cuda:0" + cfg.sim.device = device cfg.seed = 0 cfg.initial_cart_position_range = (0.0, 0.0) cfg.initial_cart_velocity_range = (0.0, 0.0) @@ -32,7 +32,7 @@ def _run_cartpole_diagnostic(physics_backend: str) -> None: cfg.initial_pole_velocity_range = (0.0, 0.0) launcher_args = { - "device": "cuda:0", + "device": device, "headless": True, "visualizer": None, "visualizer_explicit": True, @@ -46,30 +46,40 @@ def _run_cartpole_diagnostic(physics_backend: str) -> None: env = gym.make("Isaac-Cartpole-Direct", cfg=cfg) try: has_collision_groups = bool(env.unwrapped.scene.stage.GetPrimAtPath("/World/collisions")) - assert has_collision_groups == (physics_backend == "isaacsim_physx"), ( - f"Unexpected collision-group state for {physics_backend}: {has_collision_groups}." + expected_collision_groups = physics_backend == "isaacsim_physx" or device == "cpu" + assert has_collision_groups == expected_collision_groups, ( + f"Unexpected collision-group state for {physics_backend} on {device}: {has_collision_groups}." ) - env.reset() - actions = torch.ones(env.action_space.shape, device=env.unwrapped.device) - with torch.inference_mode(): - for _ in range(20): - env.step(actions) - - cart_idx = env.unwrapped._cart_dof_idx[0] - cart_vel = env.unwrapped.cartpole.data.joint_vel.torch[:, cart_idx] - assert torch.all(cart_vel > 1.0), f"Expected every cart to accelerate, got {cart_vel.tolist()}." - torch.testing.assert_close(cart_vel, cart_vel[0].expand_as(cart_vel)) - print(f"CARTPOLE_DIAGNOSTIC_PASSED {physics_backend}") + if device == "cuda:0": + env.reset() + actions = torch.ones(env.action_space.shape, device=env.unwrapped.device) + with torch.inference_mode(): + for _ in range(20): + env.step(actions) + + cart_idx = env.unwrapped._cart_dof_idx[0] + cart_vel = env.unwrapped.cartpole.data.joint_vel.torch[:, cart_idx] + assert torch.all(cart_vel > 1.0), f"Expected every cart to accelerate, got {cart_vel.tolist()}." + torch.testing.assert_close(cart_vel, cart_vel[0].expand_as(cart_vel)) + print(f"CARTPOLE_DIAGNOSTIC_PASSED {physics_backend} {device}") finally: env.close() @pytest.mark.isaacsim_ci @pytest.mark.integration -@pytest.mark.parametrize("physics_backend", ("isaacsim_physx", "ovphysx")) -def test_cartpole_direct_physx_actuation_is_consistent_across_environments(physics_backend: str): - """Verify consistent Cartpole actuation and backend-specific collision isolation.""" +@pytest.mark.parametrize( + "physics_backend,device", + ( + pytest.param("isaacsim_physx", "cuda:0", id="isaacsim-physx-cuda"), + pytest.param("isaacsim_physx", "cpu", id="isaacsim-physx-cpu"), + pytest.param("ovphysx", "cuda:0", id="ovphysx-cuda"), + pytest.param("ovphysx", "cpu", id="ovphysx-cpu"), + ), +) +def test_cartpole_direct_physx_isolation_matches_backend_device(physics_backend: str, device: str): + """Verify backend-specific collision isolation and consistent CUDA actuation.""" if physics_backend == "isaacsim_physx": from isaaclab.app import AppLauncher @@ -79,7 +89,7 @@ def test_cartpole_direct_physx_actuation_is_consistent_across_environments(physi pytest.skip("OVPhysX is not installed") result = subprocess.run( - [sys.executable, __file__, "--physics_backend", physics_backend], + [sys.executable, __file__, "--physics_backend", physics_backend, "--device", device], capture_output=True, text=True, timeout=180, @@ -87,11 +97,14 @@ def test_cartpole_direct_physx_actuation_is_consistent_across_environments(physi output = result.stdout + result.stderr assert result.returncode == 0, f"Cartpole diagnostic failed:\n{output}" assert "Traceback (most recent call last):" not in output, f"Cartpole diagnostic failed:\n{output}" - assert f"CARTPOLE_DIAGNOSTIC_PASSED {physics_backend}" in output, f"Cartpole diagnostic failed:\n{output}" + assert f"CARTPOLE_DIAGNOSTIC_PASSED {physics_backend} {device}" in output, ( + f"Cartpole diagnostic failed:\n{output}" + ) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--physics_backend", choices=("isaacsim_physx", "ovphysx"), required=True) + parser.add_argument("--device", choices=("cpu", "cuda:0"), required=True) args = parser.parse_args() - _run_cartpole_diagnostic(args.physics_backend) + _run_cartpole_diagnostic(args.physics_backend, args.device) From 0c26b9b90b13bd93ca67b9b7165507f57bd83765 Mon Sep 17 00:00:00 2001 From: Kelly Guo Date: Thu, 6 Aug 2026 22:29:55 -0700 Subject: [PATCH 3/3] Apply Ruff formatting --- .../test/core/test_cartpole_direct_physx_actuation.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/source/isaaclab_tasks/test/core/test_cartpole_direct_physx_actuation.py b/source/isaaclab_tasks/test/core/test_cartpole_direct_physx_actuation.py index 138ec53b4e12..429e74fee99b 100644 --- a/source/isaaclab_tasks/test/core/test_cartpole_direct_physx_actuation.py +++ b/source/isaaclab_tasks/test/core/test_cartpole_direct_physx_actuation.py @@ -97,9 +97,7 @@ def test_cartpole_direct_physx_isolation_matches_backend_device(physics_backend: output = result.stdout + result.stderr assert result.returncode == 0, f"Cartpole diagnostic failed:\n{output}" assert "Traceback (most recent call last):" not in output, f"Cartpole diagnostic failed:\n{output}" - assert f"CARTPOLE_DIAGNOSTIC_PASSED {physics_backend} {device}" in output, ( - f"Cartpole diagnostic failed:\n{output}" - ) + assert f"CARTPOLE_DIAGNOSTIC_PASSED {physics_backend} {device}" in output, f"Cartpole diagnostic failed:\n{output}" if __name__ == "__main__":