diff --git a/source/isaaclab_physx/changelog.d/skip-empty-rigid-body-view.fixed.rst b/source/isaaclab_physx/changelog.d/skip-empty-rigid-body-view.fixed.rst new file mode 100644 index 000000000000..2666084c68ae --- /dev/null +++ b/source/isaaclab_physx/changelog.d/skip-empty-rigid-body-view.fixed.rst @@ -0,0 +1 @@ +* Fixed PhysX startup error logs in static-only scenes by avoiding empty scene-data rigid-body view requests. diff --git a/source/isaaclab_physx/isaaclab_physx/physics/physx_manager.py b/source/isaaclab_physx/isaaclab_physx/physics/physx_manager.py index 627013fb475e..4376a8b80dc2 100644 --- a/source/isaaclab_physx/isaaclab_physx/physics/physx_manager.py +++ b/source/isaaclab_physx/isaaclab_physx/physics/physx_manager.py @@ -32,7 +32,7 @@ import omni.timeline import omni.usd import usdrt -from pxr import Sdf, UsdGeom, UsdPhysics, UsdUtils +from pxr import Sdf, Usd, UsdGeom, UsdPhysics, UsdUtils import isaaclab.sim as sim_utils from isaaclab.physics import CallbackHandle, PhysicsEvent, PhysicsManager @@ -1025,22 +1025,25 @@ def _warmup_and_create_views(cls) -> None: # Register the complete tensor view only after PhysX has loaded the stage. cls.backend = sim.get_or_create_backend(PhysxBackendCfg(stage_id=stage_id)) cls._scene_data_backend.backend = cls.backend - view = cls.backend.simulation_view.create_rigid_body_view("/**") - # PhysX returns a wrapper with no native handle for an empty selection. - cls._scene_data_backend._rigid_body_view = view if view._backend is not None else None - if view._backend is not None: - # Wildcard bindings expose articulation aliases; publish the actual root-link paths. - native = cls.backend.simulation_view - paths = view.prim_paths - articulation_type = omni.physics.tensors.ObjectType.Articulation - roots = [path for path in paths if native.get_object_type(path) == articulation_type] - if roots: - articulations = native.create_articulation_view(roots) - links = (body_paths[0] for body_paths in articulations.link_paths) - aliases = dict(zip(articulations.prim_paths, links, strict=True)) - cls._scene_data_backend._rigid_body_view = native.create_rigid_body_view( - [aliases.get(path, path) for path in paths] - ) + # omni.physics.tensors logs errors for an empty selection, so static scenes skip the request. + prims = Usd.PrimRange.Stage(sim.stage, Usd.TraverseInstanceProxies()) + if any(prim.HasAPI(UsdPhysics.RigidBodyAPI) for prim in prims): + view = cls.backend.simulation_view.create_rigid_body_view("/**") + # PhysX returns a wrapper with no native handle for an empty selection. + cls._scene_data_backend._rigid_body_view = view if view._backend is not None else None + if view._backend is not None: + # Wildcard bindings expose articulation aliases; publish the actual root-link paths. + native = cls.backend.simulation_view + paths = view.prim_paths + articulation_type = omni.physics.tensors.ObjectType.Articulation + roots = [path for path in paths if native.get_object_type(path) == articulation_type] + if roots: + articulations = native.create_articulation_view(roots) + links = (body_paths[0] for body_paths in articulations.link_paths) + aliases = dict(zip(articulations.prim_paths, links, strict=True)) + cls._scene_data_backend._rigid_body_view = native.create_rigid_body_view( + [aliases.get(path, path) for path in paths] + ) # Final update after view creation physx.update_simulation(cls.get_physics_dt(), 0.0) diff --git a/source/isaaclab_physx/test/sim/test_simulation_context_kit.py b/source/isaaclab_physx/test/sim/test_simulation_context_kit.py index 08fed4b87380..4276ca6bdaa1 100644 --- a/source/isaaclab_physx/test/sim/test_simulation_context_kit.py +++ b/source/isaaclab_physx/test/sim/test_simulation_context_kit.py @@ -34,6 +34,34 @@ def test_setup_teardown(): SimulationContext.clear_instance() +@pytest.mark.isaacsim_ci +def test_reset_without_rigid_bodies_skips_rigid_body_view(monkeypatch): + """Resetting a static scene must not request an empty native rigid-body view.""" + create_simulation_view = omni.physics.tensors.create_simulation_view + requested_patterns = [] + + class _SimulationViewGuard: + def __init__(self, *args, **kwargs): + self._view = create_simulation_view(*args, **kwargs) + + def __getattr__(self, name): + return getattr(self._view, name) + + def create_rigid_body_view(self, pattern): + requested_patterns.append(pattern) + return self._view.create_rigid_body_view(pattern) + + monkeypatch.setattr(omni.physics.tensors, "create_simulation_view", _SimulationViewGuard) + sim = SimulationContext() + cube_cfg = sim_utils.CuboidCfg(size=(0.1, 0.1, 0.1), collision_props=sim_utils.UsdPhysicsCollisionCfg()) + cube_cfg.func("/World/Cube", cube_cfg) + + sim.reset() + + assert requested_patterns == [] + assert sim.physics_manager.get_scene_data_backend().get_rigid_body_view() is None + + @pytest.mark.isaacsim_ci def test_timeline_play_stop(monkeypatch): """Playing shares one native view; stopping releases it before the next play."""