Skip to content

Commit 2af401f

Browse files
Add RigidObjectCollection asset for OVPhysX backend
Implements RigidObjectCollection and RigidObjectCollectionData for the OVPhysX backend, completing the rigid-body asset surface alongside RigidObject and Articulation. The collection manages N distinct rigid bodies per environment with (env, body) dual indexing. The asset creates one native fused TensorBinding per tensor type via the ovphysx 0.4.3 create_tensor_binding(prim_paths=...) API, mirroring how PhysX's RigidBodyView aggregates multiple body prims into a single flat view. Each binding spans num_instances * num_bodies prims and returns body-major flat data. Strided-view reshape helpers convert between body- major view layout and the instance-major (N, B, D) layout exposed to users -- no Warp kernels added beyond the resolve_view_ids helper, no per-body Python fan-out at runtime. Also updates the kitless guard in test_rigid_object_collection_iface so the shared interface test can run under run_ovphysx.sh. Fixes isaac-sim#5317
1 parent 3d10e74 commit 2af401f

10 files changed

Lines changed: 3791 additions & 7 deletions

File tree

source/isaaclab/test/assets/test_rigid_object_collection_iface.py

Lines changed: 107 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,42 @@
1414
The setup is a bit convoluted so that we can run these tests without requiring Isaac Sim or GPU simulation.
1515
"""
1616

17-
"""Launch Isaac Sim Simulator first."""
17+
"""Launch Isaac Sim Simulator first (when available)."""
1818

19-
from isaaclab.app import AppLauncher
20-
21-
HEADLESS = True
19+
import os
20+
import sys
21+
from unittest.mock import MagicMock
2222

23-
# launch omniverse app
24-
simulation_app = AppLauncher(headless=True).app
23+
# When running kitless (e.g., ovphysx backend via run_ovphysx.sh), AppLauncher
24+
# will try to boot Kit and hang. Skip it entirely: run_ovphysx.sh sets
25+
# LD_PRELOAD to the ovphysx libcarb.so, which is the signature of a kitless
26+
# ovphysx run. Also guard the case where neither LD_PRELOAD nor EXP_PATH is
27+
# set (bare Python, no Kit at all).
28+
_kitless = "ovphysx" in os.environ.get("LD_PRELOAD", "") or (
29+
os.environ.get("LD_PRELOAD", "") == "" and "EXP_PATH" not in os.environ
30+
)
2531

26-
from unittest.mock import MagicMock
32+
if not _kitless:
33+
from isaaclab.app import AppLauncher
34+
35+
simulation_app = AppLauncher(headless=True).app
36+
else:
37+
simulation_app = None
38+
# Stub out the Kit/Omniverse modules that are not present under
39+
# run_ovphysx.sh (pxr, carb, omni, omni.kit[.app] are real on PYTHONPATH).
40+
# ``omni`` is a real namespace package, so missing submodules also need
41+
# to be installed as attributes on it -- ``sys.modules`` alone is not
42+
# enough because attribute access on the real ``omni`` won't fall
43+
# through to ``sys.modules``.
44+
import omni as _omni
45+
46+
for _mod in ("physics", "physics.tensors", "physx", "timeline", "usd"):
47+
_stub = MagicMock()
48+
sys.modules[f"omni.{_mod}"] = _stub
49+
# Bind the leaf attribute so that ``omni.<leaf>`` resolves.
50+
setattr(_omni, _mod.split(".", 1)[0], _stub)
51+
for _mod in ("isaacsim.core", "isaacsim.core.simulation_manager"):
52+
sys.modules.setdefault(_mod, MagicMock())
2753

2854
import numpy as np
2955
import pytest
@@ -75,6 +101,23 @@
75101
except ImportError:
76102
pass
77103

104+
try:
105+
from isaaclab_ovphysx.assets.rigid_object_collection.rigid_object_collection import (
106+
RigidObjectCollection as OvPhysxRigidObjectCollection,
107+
)
108+
from isaaclab_ovphysx.assets.rigid_object_collection.rigid_object_collection_data import (
109+
RigidObjectCollectionData as OvPhysxRigidObjectCollectionData,
110+
)
111+
from isaaclab_ovphysx.test.mock_interfaces.views import MockOvPhysxBindingSet
112+
113+
# Guard against stub implementations (not yet functional).
114+
if not hasattr(OvPhysxRigidObjectCollection, "_create_buffers"):
115+
raise AttributeError("OvPhysxRigidObjectCollection is a stub; skipping ovphysx backend")
116+
117+
BACKENDS.append("ovphysx")
118+
except (ImportError, AttributeError):
119+
pass
120+
78121

79122
def create_physx_rigid_object_collection(
80123
num_instances: int = 2,
@@ -209,6 +252,61 @@ def create_newton_rigid_object_collection(
209252
return collection, mock_view
210253

211254

255+
def create_ovphysx_rigid_object_collection(
256+
num_instances: int = 2,
257+
num_bodies: int = 3,
258+
device: str = "cuda:0",
259+
):
260+
"""Create a test OVPhysX RigidObjectCollection instance with mocked tensor bindings."""
261+
body_names = [f"object_{i}" for i in range(num_bodies)]
262+
263+
collection = object.__new__(OvPhysxRigidObjectCollection)
264+
265+
rigid_objects = {f"object_{i}": RigidObjectCfg(prim_path=f"/World/Object_{i}") for i in range(num_bodies)}
266+
collection.cfg = RigidObjectCollectionCfg(rigid_objects=rigid_objects)
267+
268+
# Use articulation-mode bindings with num_joints=0 to get (N, B, ...) shaped tensors.
269+
mock_bindings = MockOvPhysxBindingSet(
270+
num_instances=num_instances,
271+
num_joints=0,
272+
num_bodies=num_bodies,
273+
body_names=body_names,
274+
asset_kind="articulation",
275+
)
276+
mock_bindings.set_random_data()
277+
278+
object.__setattr__(collection, "_device", device)
279+
object.__setattr__(collection, "_ovphysx", MagicMock())
280+
object.__setattr__(collection, "_bindings", mock_bindings.bindings)
281+
object.__setattr__(collection, "_num_instances", num_instances)
282+
object.__setattr__(collection, "_num_bodies", num_bodies)
283+
object.__setattr__(collection, "_body_names_list", body_names)
284+
285+
# Create RigidObjectCollectionData
286+
data = OvPhysxRigidObjectCollectionData(mock_bindings.bindings, num_bodies, device)
287+
data.num_instances = num_instances
288+
data.num_bodies = num_bodies
289+
data._is_primed = True
290+
object.__setattr__(collection, "_data", data)
291+
292+
# Allocate the buffers that RigidObjectCollection normally allocates in _initialize_impl.
293+
collection._create_buffers()
294+
295+
# Replace the real wrench composers with mocks for iface coverage.
296+
mock_inst_wrench = MockWrenchComposer(collection)
297+
mock_perm_wrench = MockWrenchComposer(collection)
298+
object.__setattr__(collection, "_instantaneous_wrench_composer", mock_inst_wrench)
299+
object.__setattr__(collection, "_permanent_wrench_composer", mock_perm_wrench)
300+
301+
# Prevent __del__ / _clear_callbacks from raising
302+
object.__setattr__(collection, "_initialize_handle", None)
303+
object.__setattr__(collection, "_invalidate_initialize_handle", None)
304+
object.__setattr__(collection, "_prim_deletion_handle", None)
305+
object.__setattr__(collection, "_debug_vis_handle", None)
306+
307+
return collection, mock_bindings
308+
309+
212310
def create_mock_rigid_object_collection(
213311
num_instances: int = 2,
214312
num_bodies: int = 3,
@@ -232,6 +330,8 @@ def get_rigid_object_collection(
232330
):
233331
if backend == "physx":
234332
return create_physx_rigid_object_collection(num_instances, num_bodies, device)
333+
elif backend == "ovphysx":
334+
return create_ovphysx_rigid_object_collection(num_instances, num_bodies, device)
235335
elif backend == "newton":
236336
return create_newton_rigid_object_collection(num_instances, num_bodies, device)
237337
elif backend.lower() == "mock":
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Added
2+
^^^^^
3+
4+
* Added :class:`~isaaclab_ovphysx.assets.RigidObjectCollection` and
5+
:class:`~isaaclab_ovphysx.assets.RigidObjectCollectionData` for the
6+
OVPhysX backend, completing the rigid-body asset surface alongside
7+
:class:`~isaaclab_ovphysx.assets.RigidObject` and
8+
:class:`~isaaclab_ovphysx.assets.Articulation`. Supports
9+
``(env, body)`` dual indexing and per-body property setters. Uses the
10+
ovphysx 0.4.3+ native fused multi-prim binding API
11+
(``create_tensor_binding(prim_paths=[...])``) so one binding spans all
12+
``num_instances * num_bodies`` prims per tensor type, mirroring the
13+
strided-view reshape pattern used by the PhysX collection.

source/isaaclab_ovphysx/isaaclab_ovphysx/assets/__init__.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ __all__ = [
77
"Articulation",
88
"ArticulationData",
99
"RigidObject",
10+
"RigidObjectCollection",
11+
"RigidObjectCollectionData",
1012
"RigidObjectData",
1113
]
1214

1315
from .articulation import Articulation, ArticulationData
1416
from .rigid_object import RigidObject, RigidObjectData
17+
from .rigid_object_collection import RigidObjectCollection, RigidObjectCollectionData

source/isaaclab_ovphysx/isaaclab_ovphysx/assets/kernels.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,6 +1348,31 @@ def write_joint_friction_to_buffer_index(
13481348
out_data[env_ids[i], joint_ids[j], 2] = val
13491349

13501350

1351+
@wp.kernel
1352+
def resolve_view_ids(
1353+
env_ids: wp.array(dtype=wp.int32),
1354+
body_ids: wp.array(dtype=wp.int32),
1355+
num_query_envs: wp.int32,
1356+
num_total_envs: wp.int32,
1357+
view_ids: wp.array(dtype=wp.int32),
1358+
) -> None:
1359+
"""Resolve flat view indices from environment and body index pairs.
1360+
1361+
Computes flat view indices from (env_id, body_id) pairs using body-major ordering:
1362+
``view_id = body_id * num_total_envs + env_id``. The output array is laid out in
1363+
column-major order over the (env, body) grid.
1364+
1365+
Args:
1366+
env_ids: Input environment indices. Shape is (num_query_envs,).
1367+
body_ids: Input body indices. Shape is (num_query_bodies,).
1368+
num_query_envs: Total number of queried environments.
1369+
num_total_envs: Total number of environments in the simulation.
1370+
view_ids: Output flat view indices. Shape is (num_query_bodies * num_query_envs,).
1371+
"""
1372+
i, j = wp.tid()
1373+
view_ids[j * num_query_envs + i] = body_ids[j] * num_total_envs + env_ids[i]
1374+
1375+
13511376
@wp.kernel
13521377
def write_joint_friction_to_buffer_mask(
13531378
in_data: wp.array2d(dtype=wp.float32),
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
"""Sub-module for ovphysx-backed rigid object collection assets."""
7+
8+
from isaaclab.utils.module import lazy_export
9+
10+
lazy_export()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
__all__ = [
7+
"RigidObjectCollection",
8+
"RigidObjectCollectionData",
9+
]
10+
11+
from .rigid_object_collection import RigidObjectCollection
12+
from .rigid_object_collection_data import RigidObjectCollectionData

0 commit comments

Comments
 (0)