Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions scripts/demos/pick_and_place.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
# PhysX replication requires explicit collision filtering between environments.
if "physx" in self.scene.physics_backend:
# 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
Expand Down
6 changes: 4 additions & 2 deletions scripts/tutorials/06_deploy/anymal_c_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
# PhysX replication requires explicit collision filtering between environments.
if "physx" in self.scene.physics_backend:
# 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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Fixed
^^^^^

* 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.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
# PhysX replication requires explicit collision filtering between environments.
if "physx" in self.scene.physics_backend:
# 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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

# PhysX replication requires explicit collision filtering between environments.
if "physx" in self.scene.physics_backend:
# 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
# PhysX replication requires explicit collision filtering between environments.
if "physx" in self.scene.physics_backend:
# 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

# PhysX replication requires explicit collision filtering between environments.
if "physx" in self.scene.physics_backend:
# 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
# PhysX replication requires explicit collision filtering between environments.
if "physx" in self.scene.physics_backend:
# 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
# PhysX replication requires explicit collision filtering between environments.
if "physx" in self.scene.physics_backend:
# 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
# PhysX replication requires explicit collision filtering between environments.
if "physx" in self.scene.physics_backend:
# 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
# PhysX replication requires explicit collision filtering between environments.
if "physx" in self.scene.physics_backend:
# 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
# PhysX replication requires explicit collision filtering between environments.
if "physx" in self.scene.physics_backend:
# 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
# PhysX replication requires explicit collision filtering between environments.
if "physx" in self.scene.physics_backend:
# 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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# 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 isolation and CUDA 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, 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
from isaaclab_tasks.utils.hydra import resolve_presets

cfg = resolve_presets(CartpoleEnvCfg(), (physics_backend,))
cfg.scene.num_envs = 4
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)
cfg.initial_pole_angle_range = (0.0, 0.0)
cfg.initial_pole_velocity_range = (0.0, 0.0)

launcher_args = {
"device": device,
"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"))
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}."
)

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,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

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, "--device", device],
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} {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, args.device)
Loading