diff --git a/source/isaaclab_tasks/changelog.d/fix-direct-physx-collision-filtering.rst b/source/isaaclab_tasks/changelog.d/fix-direct-physx-collision-filtering.rst new file mode 100644 index 000000000000..6cd2b7c57354 --- /dev/null +++ b/source/isaaclab_tasks/changelog.d/fix-direct-physx-collision-filtering.rst @@ -0,0 +1,5 @@ +Fixed +^^^^^ + +* Fixed ineffective joint actuation in direct-workflow PhysX environments by filtering collisions between + replicas on all simulation devices. 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 c0a5fac0edc8..5d25326c2923 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) - # we need to explicitly filter collisions for CPU simulation - if self.device == "cpu": + # PhysX replication requires explicit collision filtering between environments. + if "physx" in self.scene.physics_backend: 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 27a7d9e9ac9c..e85b6835292f 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) - if self.device == "cpu": - # we need to explicitly filter collisions for CPU simulation + # PhysX replication requires explicit collision filtering between environments. + if "physx" in self.scene.physics_backend: 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 0e9fc3a40c85..fccda95dc46a 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) - # we need to explicitly filter collisions for CPU simulation - if self.device == "cpu": + # PhysX replication requires explicit collision filtering between environments. + if "physx" in self.scene.physics_backend: self.scene.filter_collisions(global_prim_paths=["/World/ground"]) # add articulation to scene diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/cabinet/cabinet_direct_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/cabinet/cabinet_direct_env.py index db83f6bacf32..167c40dbda55 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/cabinet/cabinet_direct_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/cabinet/cabinet_direct_env.py @@ -143,8 +143,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) - # we need to explicitly filter collisions for CPU simulation - if self.device == "cpu": + # PhysX replication requires explicit collision filtering between environments. + if "physx" in self.scene.physics_backend: self.scene.filter_collisions(global_prim_paths=[self.cfg.terrain.prim_path]) # add lights 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 e6623a444170..0585fa8b3c85 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) - if self.device == "cpu": - # we need to explicitly filter collisions for CPU simulation + # PhysX replication requires explicit collision filtering between environments. + if "physx" in self.scene.physics_backend: 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 ed125929b4dc..2378183bcf21 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) - # we need to explicitly filter collisions for CPU simulation - if self.device == "cpu": + # PhysX replication requires explicit collision filtering between environments. + if "physx" in self.scene.physics_backend: 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 a0a8630fedf6..ce78692d54fa 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env.py @@ -96,6 +96,9 @@ 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: + 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 self.scene.articulations["left_robot"] = self.left_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 5ff7a076707a..d988356171c3 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 @@ -122,8 +122,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) - # we need to explicitly filter collisions for CPU simulation - if self.device == "cpu": + # PhysX replication requires explicit collision filtering between environments. + if "physx" in self.scene.physics_backend: self.scene.filter_collisions(global_prim_paths=[self.cfg.terrain.prim_path]) # add articulation 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 7577eae85504..f0e97fded0c9 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) - # we need to explicitly filter collisions for CPU simulation - if self.device == "cpu": + # PhysX replication requires explicit collision filtering between environments. + if "physx" in self.scene.physics_backend: 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 d424b46400e5..025a5c692b3b 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,6 +57,9 @@ 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: + 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 self.scene.rigid_objects["object"] = self.object 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 a2ada7e457a1..c2c47e66491b 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,6 +106,9 @@ 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: + 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 self.scene.rigid_objects["object"] = self.object 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..4d69fa850979 --- /dev/null +++ b/source/isaaclab_tasks/test/core/test_cartpole_direct_physx_actuation.py @@ -0,0 +1,49 @@ +# 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 PhysX actuation in the direct-workflow Cartpole environment.""" + +from isaaclab.app import AppLauncher + +# launch the simulator +simulation_app = AppLauncher(headless=True).app + +"""Rest everything follows.""" + +import gymnasium as gym +import pytest +import torch + +import isaaclab_tasks # noqa: F401 +from isaaclab_tasks.core.cartpole.cartpole_direct_env_cfg import CartpoleEnvCfg +from isaaclab_tasks.utils.hydra import resolve_presets + + +@pytest.mark.isaacsim_ci +def test_cartpole_direct_physx_actuation_is_consistent_across_environments(): + """Verify that cloned PhysX carts respond consistently to a constant effort.""" + cfg = resolve_presets(CartpoleEnvCfg(), ("physx",)) + 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) + + env = gym.make("Isaac-Cartpole-Direct", cfg=cfg) + try: + 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 velocities {cart_vel.tolist()}." + torch.testing.assert_close(cart_vel, cart_vel[0].expand_as(cart_vel)) + finally: + env.close() diff --git a/source/isaaclab_tasks/test/golden_stages/cartpole/physx-isaacsim_rtx_renderer-rgb.usda b/source/isaaclab_tasks/test/golden_stages/cartpole/physx-isaacsim_rtx_renderer-rgb.usda index c25f11f6c268..b7702cae095e 100644 --- a/source/isaaclab_tasks/test/golden_stages/cartpole/physx-isaacsim_rtx_renderer-rgb.usda +++ b/source/isaaclab_tasks/test/golden_stages/cartpole/physx-isaacsim_rtx_renderer-rgb.usda @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b33d1c95f90499e865145095fa0ddd4a2c82f724307d3bc7baca3d5d0e0be70c -size 53021 +oid sha256:0601d2f4e78386786babf320601d44c0a44f58c200429fdf1cf41542dc81e2ba +size 54479 diff --git a/source/isaaclab_tasks/test/golden_stages/cartpole/physx-newton_renderer-rgb.usda b/source/isaaclab_tasks/test/golden_stages/cartpole/physx-newton_renderer-rgb.usda index c8999b3cb6ac..025bf12b256c 100644 --- a/source/isaaclab_tasks/test/golden_stages/cartpole/physx-newton_renderer-rgb.usda +++ b/source/isaaclab_tasks/test/golden_stages/cartpole/physx-newton_renderer-rgb.usda @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa2d25fb0ade1c6b763c736444963d4f42f3780c22e7019fb9631e550397c14a -size 48868 +oid sha256:9b990b3eb24d3eda2383f3ec7c64a9baabb42f342d32c37d6076b633b36d386b +size 50326 diff --git a/source/isaaclab_tasks/test/golden_stages/registered_tasks_Isaac-Cartpole-Camera-Direct/default_physics-default_renderer-stage.usda b/source/isaaclab_tasks/test/golden_stages/registered_tasks_Isaac-Cartpole-Camera-Direct/default_physics-default_renderer-stage.usda index 12046e937f2b..c931c6c1b35b 100644 --- a/source/isaaclab_tasks/test/golden_stages/registered_tasks_Isaac-Cartpole-Camera-Direct/default_physics-default_renderer-stage.usda +++ b/source/isaaclab_tasks/test/golden_stages/registered_tasks_Isaac-Cartpole-Camera-Direct/default_physics-default_renderer-stage.usda @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f149773e10ec7c172b4b13388cb7ba5e151ac96675edc7138a2ecbc5c2f8dfcd -size 53471 +oid sha256:a258c00cb0e8a5b72a4c83f327310f08a847267379b7f3c3f0618f25e99ea34d +size 54929 diff --git a/source/isaaclab_tasks/test/golden_stages/registered_tasks_Isaac-Reorient-Cube-Shadow-Camera-Direct/default_physics-default_renderer-stage.usda b/source/isaaclab_tasks/test/golden_stages/registered_tasks_Isaac-Reorient-Cube-Shadow-Camera-Direct/default_physics-default_renderer-stage.usda index 45e89f6bd878..a20fb62b3e41 100644 --- a/source/isaaclab_tasks/test/golden_stages/registered_tasks_Isaac-Reorient-Cube-Shadow-Camera-Direct/default_physics-default_renderer-stage.usda +++ b/source/isaaclab_tasks/test/golden_stages/registered_tasks_Isaac-Reorient-Cube-Shadow-Camera-Direct/default_physics-default_renderer-stage.usda @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:41c70aed3a5e6f53a27e9210ffe6d5fd6dde2ada543fe8e76805a46f4df500c3 -size 10647919 +oid sha256:191bcbccbff3085afe9b4bbc93e55dd57638b4fa94d4c89356e66cd35970d5fd +size 10649377