Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
bbba067
Scaffold isaaclab_ovphysx sensors sub-package
AntoineRichard Apr 28, 2026
172e2b6
Add shared warp sensor kernels for ovphysx backend
AntoineRichard Apr 28, 2026
8d0f547
Port contact-sensor warp kernels to ovphysx backend
AntoineRichard Apr 28, 2026
3ac6cb7
Add ovphysx ContactSensorCfg
AntoineRichard Apr 28, 2026
6b5645c
Add ovphysx ContactSensorData mirroring PhysX
AntoineRichard Apr 28, 2026
f3b8bf1
Add ovphysx ContactSensor skeleton
AntoineRichard Apr 28, 2026
2bd46bb
Implement ovphysx ContactSensor _initialize_impl and _create_buffers
AntoineRichard Apr 28, 2026
53ee53e
Implement ovphysx ContactSensor _update_buffers_impl
AntoineRichard Apr 28, 2026
8db62db
Implement ovphysx ContactSensor reset, compute_first_*, invalidation
AntoineRichard Apr 28, 2026
e0f63be
Replace ovphysx ContactSensor test stubs with ported PhysX tests
AntoineRichard Apr 28, 2026
d28b529
Fix ruff SIM105 lint errors in contact sensor cleanup
AntoineRichard Apr 28, 2026
02e334d
Convert ContactSensor changelog to fragment file
AntoineRichard May 6, 2026
d62d8c8
Rewrite ContactSensor tests for kitless ovphysx flow
AntoineRichard May 6, 2026
12ba6b8
Apply pre-commit auto-formatting to test file
AntoineRichard May 6, 2026
6b1d3b0
Fix SensorBase backend dispatch matching OvPhysxManager
AntoineRichard May 6, 2026
0d621da
Generalize physx-vs-ovphysx dispatch in base classes
AntoineRichard May 6, 2026
38e0fcd
Drop PhysxContactReportAPI requirement in ovphysx ContactSensor
AntoineRichard May 6, 2026
6919a21
Revert "Drop PhysxContactReportAPI requirement in ovphysx ContactSensor"
AntoineRichard May 6, 2026
7ee5897
Auto-register PhysxSchema USD plugin in OvPhysxManager
AntoineRichard May 6, 2026
b1a1e59
Fix PhysicsManager.clear_callbacks ID-recycle bug
AntoineRichard May 11, 2026
9bfc8d2
Wire OvPhysX preset into AnymalD Flat + multi-env sensor count
AntoineRichard May 12, 2026
05c4053
Fix ContactSensor flat-buffer indexing to match ovphysx pattern-major…
AntoineRichard May 12, 2026
8e9783d
Add regression test for multi-body sensor flat-buffer ordering
AntoineRichard May 12, 2026
2578b48
Align OVPhysX contact sensor with develop's configclass import path
AntoineRichard May 18, 2026
f495912
Align OVPhysX ContactSensor parity with PhysX/Newton
AntoineRichard May 18, 2026
e6fc1e0
Skip OVPhysX contact-sensor tests when the wheel is unavailable
AntoineRichard May 19, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Fixed
^^^^^

* Fixed three places where ``OvPhysxManager`` was misclassified as the
PhysX backend by a substring/schema match:

- :meth:`~isaaclab.sensors.SensorBase._register_callbacks` matched
``"physx" in physics_mgr_cls.__name__.lower()`` to gate the PhysX
``IsaacEvents.PRIM_DELETION`` import — the substring also matches
``"OvPhysxManager"``, so the ``isaaclab_physx`` import fired in
kitless OVPhysX mode and raised
:exc:`ModuleNotFoundError` because ``omni.physics.tensors`` is not
loaded. Switched to an exact ``physics_mgr_cls.__name__ ==
"PhysxManager"`` match.
- :meth:`~isaaclab.assets.AssetBase.set_debug_vis` had the same
substring check guarding an ``import omni.kit.app`` call, which
would fire for OVPhysX-backed assets and break under
``./scripts/run_ovphysx.sh``. Switched to an exact
``"PhysxManager"`` match.
- :meth:`~isaaclab.physics.SceneDataProvider._get_backend` used
``"physx" in manager_name`` to dispatch the backend factory; this
silently routed ``OvPhysxManager`` to the PhysX scene-data
provider. Switched to exact ``"PhysxManager"`` /
``"NewtonManager"`` matches and an explicit ``ValueError`` for
unknown managers.
* Made
:attr:`~isaaclab.scene.InteractiveScene.physics_scene_path` accept a
bare :class:`pxr.UsdPhysics.Scene` prim as a fallback when no prim
with ``PhysxSceneAPI`` applied is on the stage. Kitless OVPhysX
does not load the ``omni.physx`` schema, so the auto-created scene
prim only carries the stock USD type. PhysX-backed flows continue
to prefer the ``PhysxSceneAPI`` prim.
31 changes: 27 additions & 4 deletions source/isaaclab/isaaclab/envs/mdp/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,12 +385,35 @@ def __init__(self, cfg: EventTermCfg, env: ManagerBasedEnv):
f" with type: '{type(self.asset)}'."
)

# detect physics backend and instantiate the appropriate implementation
manager_name = env.sim.physics_manager.__name__.lower()
if "newton" in manager_name:
# detect physics backend and instantiate the appropriate implementation.
# Exact name matches: ``"physx" in name.lower()`` would also catch
# ``OvPhysxManager`` and route it to the PhysX impl, which assumes a
# ``root_view`` with ``.link_paths`` — OVPhysX's per-tensor-type
# bindings dict does not satisfy that contract.
manager_name = env.sim.physics_manager.__name__
if manager_name == "NewtonManager":
self._impl = _RandomizeRigidBodyMaterialNewton(cfg, env, self.asset, self.asset_cfg)
else:
elif manager_name == "PhysxManager":
self._impl = _RandomizeRigidBodyMaterialPhysx(cfg, env, self.asset, self.asset_cfg)
elif manager_name == "OvPhysxManager":
# No OVPhysX implementation yet — wheel-side
# ``RIGID_BODY_MATERIAL`` tensor binding is missing; randomization
# would require per-body view creation that ovphysx does not yet
# expose. Run with material randomization disabled (warns once).
import logging # noqa: PLC0415

logging.getLogger(__name__).warning(
"randomize_rigid_body_material is a no-op on the OVPhysX backend "
"(wheel-side gap — see docs/superpowers/specs/2026-04-27-ovphysx-contact-api-gaps.md)."
)

class _Noop:
def __call__(self, *args, **kwargs):
pass

self._impl = _Noop()
else:
raise ValueError(f"Unsupported physics manager for randomize_rigid_body_material: {manager_name!r}")

def __call__(
self,
Expand Down
16 changes: 14 additions & 2 deletions source/isaaclab/isaaclab/physics/physics_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,23 @@ def dispatch_event(cls, event: PhysicsEvent, payload: Any = None) -> None:

@classmethod
def clear_callbacks(cls) -> None:
"""Remove all registered callbacks."""
"""Remove all registered callbacks.

Do NOT reset ``_callback_id`` — handle IDs must remain monotonically
unique across the lifetime of the process. Resetting the counter
would let a future :meth:`register_callback` hand out an ID that an
old, still-alive :class:`CallbackHandle` (e.g. on a sensor that has
not been garbage-collected yet) holds, so when the old object
eventually finalizes its ``__del__`` would deregister the new
callback. This bit ovphysx's kitless multi-context tests where two
``InteractiveScene``s are created in sequence: the first scene's
sensor would post-GC deregister the second scene's
``_initialize_callback`` by ID collision, leaving the second sensor
forever uninitialized.
"""
for cid in list(cls._callbacks.keys()):
cls.deregister_callback(cid)
cls._callbacks.clear()
cls._callback_id = 0

@classmethod
def _wrap_weak_ref(cls, callback: Callable) -> Callable:
Expand Down
10 changes: 10 additions & 0 deletions source/isaaclab/isaaclab/scene/interactive_scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,11 +454,21 @@ def __str__(self) -> str:
def physics_scene_path(self) -> str:
"""The path to the USD Physics Scene."""
if self._physics_scene_path is None:
# Prefer a prim with PhysxSceneAPI applied (Isaac Sim flow). Fall
# back to any UsdPhysics.Scene prim (kitless OvPhysX flow does not
# load the omni.physx schema, so the auto-created scene only
# carries the stock USD type without PhysxSceneAPI).
fallback_path: str | None = None
for prim in self.stage.Traverse():
if "PhysxSceneAPI" in prim.GetAppliedSchemas():
self._physics_scene_path = prim.GetPrimPath().pathString
logger.info(f"Physics scene prim path: {self._physics_scene_path}")
break
if fallback_path is None and prim.GetTypeName() == "PhysicsScene":
fallback_path = prim.GetPrimPath().pathString
if self._physics_scene_path is None and fallback_path is not None:
self._physics_scene_path = fallback_path
logger.info(f"Physics scene prim path (no PhysxSceneAPI): {self._physics_scene_path}")
if self._physics_scene_path is None:
raise RuntimeError("No physics scene found! Please make sure one exists.")
return self._physics_scene_path
Expand Down
4 changes: 3 additions & 1 deletion source/isaaclab/isaaclab/sensors/sensor_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,9 @@ def _invoke(callback_name, event):
PhysicsEvent.STOP,
order=10,
)
# Optional: prim deletion (only supported by PhysX backend)
# Optional: prim deletion (only supported by PhysX backend; the substring
# check would also match ``OvPhysxManager``, which does not expose
# ``IsaacEvents``, so use an exact class-name match).
self._prim_deletion_handle = None
if physics_mgr_cls.__name__ == "PhysxManager":
from isaaclab_physx.physics import IsaacEvents # noqa: PLC0415
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
Added
^^^^^

* Added :class:`~isaaclab_ovphysx.sensors.ContactSensor`,
:class:`~isaaclab_ovphysx.sensors.ContactSensorCfg`, and
:class:`~isaaclab_ovphysx.sensors.ContactSensorData` for the OVPhysX
backend, satisfying the
:class:`~isaaclab.sensors.contact_sensor.BaseContactSensor` and
:class:`~isaaclab.sensors.contact_sensor.BaseContactSensorData`
contracts. Wires net contact forces and the per-partner force matrix
through the OVPhysX :class:`ovphysx.api.ContactBinding` API
(``read_net_forces`` / ``read_force_matrix``); optional pose tracking
reads through a ``RIGID_BODY_POSE`` :class:`ovphysx.api.TensorBinding`.
Air/contact time tracking,
:meth:`~isaaclab_ovphysx.sensors.ContactSensor.compute_first_contact`,
:meth:`~isaaclab_ovphysx.sensors.ContactSensor.compute_first_air`,
history buffers, and reset semantics mirror the PhysX backend.
* Added the shared
:mod:`isaaclab_ovphysx.sensors.kernels` module with
:func:`~isaaclab_ovphysx.sensors.kernels.concat_pos_and_quat_to_pose_kernel`
and the 1D variant for reuse across future OVPhysX sensors.

Changed
^^^^^^^

* Changed the existing
``source/isaaclab_ovphysx/test/sensors/check_contact_sensor.py``
stubs to real tests adapted from the PhysX
:mod:`isaaclab_physx.test.sensors.test_contact_sensor` suite. The
three tests that exercise ``track_contact_points`` or
``track_friction_forces`` are decorated with
:func:`pytest.mark.skip` until the OVPhysX wheel ships
tensor-friendly per-sensor reads (see
``docs/superpowers/specs/2026-04-27-ovphysx-contact-api-gaps.md``);
the test bodies are preserved so the decorator can be removed in a
follow-up.

Removed
^^^^^^^

* **Breaking:** Removed the five
``source/isaaclab_ovphysx/test/sensors/check_contact_sensor.py``
``pytest.skip("Contact sensor not yet supported by ovphysx
backend.")`` placeholders in favour of the real test suite above.
No public migration is required; the placeholder names did not
appear in any external API.
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,36 @@ def register_clone(
"""
cls._pending_clones.append((source, targets, parent_positions or []))

_physx_schemas_registered: ClassVar[bool] = False

@classmethod
def _ensure_physx_schemas_registered(cls) -> None:
"""Register the ``PhysxSchema`` USD plugin shipped with the ovphysx wheel.

In Kit-based runs ``omni.physx`` registers the schema; in kitless
runs it must be registered manually before the wheel can match
``PhysxContactReportAPI`` and friends on the stage. The wheel
bundles the plugin under ``ovphysx/plugins/usd/PhysxSchema``. This
method is idempotent — :meth:`pxr.Plug.Registry.RegisterPlugins`
is a no-op once the plugin is registered.
"""
if cls._physx_schemas_registered:
return
try:
import os # noqa: PLC0415

import ovphysx # noqa: PLC0415

from pxr import Plug # noqa: PLC0415
except Exception:
return
plugin_root = os.path.join(os.path.dirname(ovphysx.__file__), "plugins", "usd")
for sub in ("PhysxSchema/resources", "PhysxSchemaAddition/resources"):
path = os.path.join(plugin_root, sub)
if os.path.isdir(path):
Plug.Registry().RegisterPlugins(path)
cls._physx_schemas_registered = True

@classmethod
def initialize(cls, sim_context: SimulationContext) -> None:
"""Initialize the physics manager with simulation context.
Expand All @@ -262,6 +292,7 @@ def initialize(cls, sim_context: SimulationContext) -> None:
instance is bound to.
"""
super().initialize(sim_context)
cls._ensure_physx_schemas_registered()
cls._warmup_done = False
cls._usd_handle = None
cls._stage_path = None
Expand Down
10 changes: 10 additions & 0 deletions source/isaaclab_ovphysx/isaaclab_ovphysx/sensors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# 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

"""Sub-package for ovphysx-backed sensors."""

from isaaclab.utils.module import lazy_export

lazy_export()
12 changes: 12 additions & 0 deletions source/isaaclab_ovphysx/isaaclab_ovphysx/sensors/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# 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

__all__ = [
"ContactSensor",
"ContactSensorCfg",
"ContactSensorData",
]

from .contact_sensor import ContactSensor, ContactSensorCfg, ContactSensorData
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# 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

"""OVPhysX-backed contact sensor."""

from isaaclab.utils.module import lazy_export

lazy_export()
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# 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

__all__ = [
"ContactSensor",
"ContactSensorCfg",
"ContactSensorData",
]

from .contact_sensor import ContactSensor
from .contact_sensor_cfg import ContactSensorCfg
from .contact_sensor_data import ContactSensorData
Loading
Loading