Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b77fa6b
Add isaaclab_ovphysx backend with full articulation interface compliance
marcodiiga Mar 4, 2026
edb081c
Wire all stub writers/setters to real ovphysx tensor bindings
marcodiiga Mar 4, 2026
13bce66
Move test USD data into repo, remove hardcoded absolute paths
marcodiiga Mar 5, 2026
bfcc5ea
Finalize ovphysx articulation backend: TensorType migration, GPU writ…
marcodiiga Mar 5, 2026
3c8cd3a
feat(ovphysx): wire humanoid task — articulation root discovery, clon…
marcodiiga Mar 6, 2026
0690af6
fix(ovphysx): avoid teardown segfault via os._exit after physx release
marcodiiga Mar 6, 2026
84594e4
clarifying comment
marcodiiga Mar 6, 2026
a5ad6b0
fix(ovphysx): clone positioning, cache invalidation, env root exclusion
marcodiiga Mar 6, 2026
1630c4c
Address PR #4852 review: ovphysx backend correctness and cleanup
marcodiiga Mar 7, 2026
f2884aa
small fixes after ov set excluded cloning environment fixes
marcodiiga Mar 10, 2026
678df15
Refactoring to make backend more format-like to the others
marcodiiga Mar 10, 2026
b953e78
Remove hardcoded packman path from run_ovphysx.sh
marcodiiga Mar 10, 2026
e4ffb37
Add ovphysx backend integration, locomotion reset optimization, and s…
marcodiiga Mar 13, 2026
20eb1f8
Apply pre-commit formatting (keep tensor_types.py column alignment)
marcodiiga Mar 16, 2026
de71ff2
Restore positions= arg to usd_replicate() in interactive_scene.py
marcodiiga Mar 16, 2026
38f6d6d
Cleanup debug code
marcodiiga Mar 16, 2026
687afdd
Clean up ovphysx_manager and locomotion_env comments
marcodiiga Mar 16, 2026
a3ccc98
Revert locomotion_env _reset_idx optimization
marcodiiga Mar 16, 2026
90c118e
linter fixes
marcodiiga Mar 16, 2026
867058a
fixed carbonite loader and a bug
marcodiiga Mar 16, 2026
4619707
locomotion fix for ovphysx
marcodiiga Mar 16, 2026
3e28a8d
reformat
marcodiiga Mar 16, 2026
4436843
Address PR #4852 review: harmonize ovphysx backend with PhysX/Newton
marcodiiga Mar 20, 2026
b87fe2f
Apply pre-commit formatting fixes
marcodiiga Mar 20, 2026
017c0ef
Fix deprecated root-state writers, tendon subset filtering, packaging…
marcodiiga Apr 2, 2026
b382fff
Add ovphysx 0.3.7 public wheel compatibility (REVERT for next release)
marcodiiga Apr 16, 2026
bcc24bc
Merge branch 'develop' into fix/malesiani/ovphysx_poc_integration_bac…
AntoineRichard Apr 16, 2026
520032c
Merge branch 'develop' into fix/malesiani/ovphysx_poc_integration_bac…
AntoineRichard Apr 17, 2026
6c5ee26
Fix ovphysx launcher and test regressions
marcodiiga Apr 17, 2026
cd2a88c
Fix ovphysx review follow-ups
marcodiiga Apr 20, 2026
434d7cc
Merge branch 'develop' into fix/malesiani/ovphysx_poc_integration_bac…
AntoineRichard Apr 20, 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
71 changes: 71 additions & 0 deletions scripts/run_ovphysx.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/bin/bash
# Run ovphysx in Kit's Python with its bundled libcarb.so preloaded.
# Use when ovphysx is installed into Kit's Python.
#
# Usage: ./scripts/run_ovphysx.sh [your_script.py or -m pytest ...]
set -e

ISAACLAB_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ISAAC_DIR="${ISAACLAB_PATH}/_isaac_sim"

# Match python.sh so Kit extensions/resources resolve during training too.
export CARB_APP_PATH="${CARB_APP_PATH:-${ISAAC_DIR}/kit}"
export ISAAC_PATH="${ISAAC_PATH:-${ISAAC_DIR}}"
export EXP_PATH="${EXP_PATH:-${ISAAC_DIR}/apps}"

# Source the Python environment setup (sets PYTHONPATH, LD_LIBRARY_PATH)
# but do NOT use python.sh which sets LD_PRELOAD
source "${ISAAC_DIR}/setup_python_env.sh"

# Preload ovphysx's own libcarb.so so its Carbonite framework wins the
# SONAME race against any other libcarb.so present in the process.
_ovphysx_libcarb=""
for _sp in "${ISAAC_DIR}"/kit/python/lib/python3.*/site-packages/ovphysx/plugins/libcarb.so; do
if [ -f "${_sp}" ]; then
_ovphysx_libcarb="${_sp}"
break
fi
done
if [ -n "${_ovphysx_libcarb}" ]; then
export LD_PRELOAD="${_ovphysx_libcarb}"
else
export LD_PRELOAD=""
fi
unset _ovphysx_libcarb

# Ensure pxr (OpenUSD Python bindings) is on PYTHONPATH.
# setup_python_env.sh may not include the packman USD path after rebuilds.
# Search order: IsaacSim's bundled site-packages first, then the Python
# environment's own site-packages (covers pip-installed OpenUSD).
Comment thread
marcodiiga marked this conversation as resolved.
_pxr_found=false
for usd_dir in "${ISAAC_DIR}"/kit/python/lib/python3.*/site-packages/usd_core.libs/../.. \
"${ISAAC_DIR}"/kit/python/lib/python3.*/site-packages; do
if [ -d "${usd_dir}/pxr" ]; then
export PYTHONPATH="${usd_dir}:${PYTHONPATH}"
_pxr_found=true
break
fi
done
if [ "${_pxr_found}" = false ]; then
# Last resort: ask Python itself where pxr lives
_pxr_path=$("${ISAAC_DIR}/kit/python/bin/python3" -c "import pxr, os; print(os.path.dirname(os.path.dirname(pxr.__file__)))" 2>/dev/null)
if [ -n "${_pxr_path}" ] && [ -d "${_pxr_path}/pxr" ]; then
export PYTHONPATH="${_pxr_path}:${PYTHONPATH}"
fi
fi
unset _pxr_found _pxr_path

# Add all isaaclab source packages to PYTHONPATH so editable installs work
for pkg in isaaclab isaaclab_ovphysx isaaclab_tasks isaaclab_rl isaaclab_physx isaaclab_newton isaaclab_assets isaaclab_contrib; do
if [ -d "${ISAACLAB_PATH}/source/${pkg}" ]; then
export PYTHONPATH="${ISAACLAB_PATH}/source/${pkg}:${PYTHONPATH}"
fi
done

# Match python.sh default for Kit app resource discovery.
export RESOURCE_NAME="${RESOURCE_NAME:-IsaacSim}"

# Use the Python binary directly
PYTHON_EXE="${ISAAC_DIR}/kit/python/bin/python3"

exec "${PYTHON_EXE}" "$@"
4 changes: 2 additions & 2 deletions source/isaaclab/isaaclab/assets/asset_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,10 +359,10 @@ def _invoke(callback_name, event):
PhysicsEvent.STOP,
order=10,
)
# Optional: prim deletion (only supported by PhysX backend)
# Optional: prim deletion (only supported by Kit PhysX backend, not ovphysx)
self._prim_deletion_handle = None
physics_backend = physics_mgr_cls.__name__.lower()
if "physx" in physics_backend:
if physics_backend.startswith("physx"):
from isaaclab_physx.physics import IsaacEvents

self._prim_deletion_handle = physics_mgr_cls.register_callback(
Expand Down
20 changes: 15 additions & 5 deletions source/isaaclab/isaaclab/scene/interactive_scene.py
Comment thread
marcodiiga marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,15 @@ def __init__(self, cfg: InteractiveSceneCfg):
self.physics_backend = self.sim.physics_manager.__name__.lower()
visualizer_clone_fn = None
requested_viz_types = set(self.sim.resolve_visualizer_types())
if "physx" in self.physics_backend:
if self.physics_backend.startswith("ovphysx"):
from isaaclab_ovphysx.cloner import ovphysx_replicate

physics_clone_fn = ovphysx_replicate
elif self.physics_backend.startswith("physx"):
from isaaclab_physx.cloner import physx_replicate

physics_clone_fn = physx_replicate
elif "newton" in self.physics_backend:
elif self.physics_backend.startswith("newton"):
from isaaclab_newton.cloner import newton_physics_replicate

physics_clone_fn = newton_physics_replicate
Expand All @@ -163,6 +167,10 @@ def __init__(self, cfg: InteractiveSceneCfg):
device=self.device,
physics_clone_fn=physics_clone_fn,
visualizer_clone_fn=None,
# For ovphysx: env_1..N are created by physx.clone() in the physics
# runtime after add_usd(). USD replication of the asset hierarchy
# to env_1..N is skipped — only env_0 needs physics prims in the USD.
clone_usd=not self.physics_backend.startswith("ovphysx"),
Comment thread
marcodiiga marked this conversation as resolved.
)

# create source prim
Expand Down Expand Up @@ -206,6 +214,7 @@ def __init__(self, cfg: InteractiveSceneCfg):
if has_scene_cfg_entities:
self.clone_environments(copy_from_source=(not self.cfg.replicate_physics))
# Collision filtering is PhysX-specific (PhysxSchema.PhysxSceneAPI)
# Intentionally matches both physx and ovphysx (both are PhysX-based)
if self.cfg.filter_collisions and "physx" in self.physics_backend:
self.filter_collisions(self._global_prim_paths)

Expand All @@ -218,6 +227,7 @@ def clone_environments(self, copy_from_source: bool = False):
may increase). Defaults to False.
"""
# PhysX-only: set env id bit count for replicated physics. Newton handles env separation in its own API.
# Intentionally matches both physx and ovphysx (both are PhysX-based)
if self.cfg.replicate_physics and "physx" in self.physics_backend:
prim = self.stage.GetPrimAtPath("/physicsScene")
prim.CreateAttribute("physxScene:envIdInBoundsBitCount", Sdf.ValueTypeNames.Int).Set(4)
Expand All @@ -235,12 +245,12 @@ def clone_environments(self, copy_from_source: bool = False):
self._default_env_origins,
)

if not copy_from_source:
# skip physx cloning, this means physx will walk and parse the stage one by one faithfully
if not copy_from_source and self.cloner_cfg.physics_clone_fn is not None:
self.cloner_cfg.physics_clone_fn(self.stage, *replicate_args, device=self.cloner_cfg.device)
if self.cloner_cfg.visualizer_clone_fn is not None:
self.cloner_cfg.visualizer_clone_fn(self.stage, *replicate_args, device=self.cloner_cfg.device)
cloner.usd_replicate(self.stage, *replicate_args)
if self.cloner_cfg.clone_usd:
cloner.usd_replicate(self.stage, *replicate_args)

def _sensor_renderer_types(self) -> list[str]:
"""Return renderer type names used by scene sensors."""
Expand Down
11 changes: 7 additions & 4 deletions source/isaaclab/isaaclab/sim/simulation_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,10 +289,13 @@ def _init_usd_physics_scene(self) -> None:
UsdGeom.SetStageMetersPerUnit(self.stage, 1.0)
UsdPhysics.SetStageKilogramsPerUnit(self.stage, 1.0)

# Find and delete any existing physics scene
for prim in self.stage.Traverse():
if prim.GetTypeName() == "PhysicsScene":
sim_utils.delete_prim(prim.GetPath().pathString, stage=self.stage)
# Find and delete any existing physics scene.
# Collect paths first to avoid iterator invalidation during deletion.
physics_scene_paths = [
prim.GetPath().pathString for prim in self.stage.Traverse() if prim.GetTypeName() == "PhysicsScene"
]
for path in physics_scene_paths:
sim_utils.delete_prim(path, stage=self.stage)

# Create a new physics scene
if self.stage.GetPrimAtPath(cfg.physics_prim_path).IsValid():
Expand Down
6 changes: 4 additions & 2 deletions source/isaaclab/isaaclab/utils/backend_utils.py
Comment thread
marcodiiga marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ def _get_backend(cls, *args, **kwargs) -> str:
from isaaclab.sim.simulation_context import SimulationContext

manager_name = SimulationContext.instance().physics_manager.__name__.lower()
if "newton" in manager_name:
if manager_name.startswith("newton"):
return "newton"
if "physx" in manager_name:
if manager_name.startswith("ovphysx"):
return "ovphysx"
if manager_name.startswith("physx"):
return "physx"
else:
raise ValueError(f"Unknown physics manager: {manager_name}")
Expand Down
122 changes: 113 additions & 9 deletions source/isaaclab/test/assets/test_articulation_iface.py
Comment thread
marcodiiga marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,25 @@
The setup is a bit convoluted so that we can run these tests without requiring Isaac Sim or GPU simulation.
"""

"""Launch Isaac Sim Simulator first."""
"""Launch Isaac Sim Simulator first (when available)."""

from isaaclab.app import AppLauncher
import os
import sys
from unittest.mock import MagicMock

HEADLESS = True
# When running kitless (e.g., ovphysx backend via run_ovphysx.sh), AppLauncher
# will try to boot Kit and hang. Skip it entirely when LD_PRELOAD is cleared
# (the signature of run_ovphysx.sh) or when EXP_PATH is not set.
_kitless = os.environ.get("LD_PRELOAD", "") == "" and "EXP_PATH" not in os.environ

# launch omniverse app
simulation_app = AppLauncher(headless=True).app
if not _kitless:
from isaaclab.app import AppLauncher

from unittest.mock import MagicMock
simulation_app = AppLauncher(headless=True).app
else:
simulation_app = None
for _mod in ("isaacsim.core", "isaacsim.core.simulation_manager"):
sys.modules.setdefault(_mod, MagicMock())

import numpy as np
import pytest
Expand All @@ -32,9 +41,10 @@
from isaaclab.assets.articulation.articulation_cfg import ArticulationCfg
from isaaclab.test.mock_interfaces.utils import MockWrenchComposer

# Mock SimulationManager.get_physics_sim_view() to return a mock object with gravity
# This is needed because the Data classes call SimulationManager.get_physics_sim_view().get_gravity()
# but there's no actual physics scene when running unit tests
# Mock SimulationManager.get_physics_sim_view() to return a mock object with gravity.
# This is needed because the PhysX Data classes call
# SimulationManager.get_physics_sim_view().get_gravity() but there's no actual
# physics scene when running unit tests.
_mock_physics_sim_view = MagicMock()
_mock_physics_sim_view.get_gravity.return_value = (0.0, 0.0, -9.81)

Expand Down Expand Up @@ -66,6 +76,15 @@
except ImportError:
pass

try:
from isaaclab_ovphysx.assets.articulation.articulation import Articulation as OvPhysxArticulation
from isaaclab_ovphysx.assets.articulation.articulation_data import ArticulationData as OvPhysxArticulationData
from isaaclab_ovphysx.test.mock_interfaces.views import MockOvPhysxBindingSet

BACKENDS.append("ovphysx")
except ImportError:
pass


def create_physx_articulation(
num_instances: int = 2,
Expand Down Expand Up @@ -169,6 +188,87 @@ def create_physx_articulation(
return articulation, mock_view


def create_ovphysx_articulation(
num_instances: int = 2,
num_joints: int = 6,
num_bodies: int = 7,
num_fixed_tendons: int = 0,
num_spatial_tendons: int = 0,
device: str = "cuda:0",
):
"""Create a test OvPhysX Articulation instance with mocked tensor bindings."""
joint_names = [f"joint_{i}" for i in range(num_joints)]
body_names = [f"body_{i}" for i in range(num_bodies)]

articulation = object.__new__(OvPhysxArticulation)

articulation.cfg = ArticulationCfg(
prim_path="/World/Robot",
soft_joint_pos_limit_factor=1.0,
actuators={},
)

# Create mock binding set
mock_bindings = MockOvPhysxBindingSet(
num_instances=num_instances,
num_joints=num_joints,
num_bodies=num_bodies,
is_fixed_base=False,
joint_names=joint_names,
Comment thread
marcodiiga marked this conversation as resolved.
body_names=body_names,
num_fixed_tendons=num_fixed_tendons,
num_spatial_tendons=num_spatial_tendons,
)
mock_bindings.set_random_data()

fixed_tendon_names = [f"fixed_tendon_{i}" for i in range(num_fixed_tendons)]
spatial_tendon_names = [f"spatial_tendon_{i}" for i in range(num_spatial_tendons)]

object.__setattr__(articulation, "_device", device)
object.__setattr__(articulation, "_ovphysx", MagicMock())
object.__setattr__(articulation, "_bindings", mock_bindings.bindings)
object.__setattr__(articulation, "_num_instances", num_instances)
object.__setattr__(articulation, "_num_joints", num_joints)
object.__setattr__(articulation, "_num_bodies", num_bodies)
object.__setattr__(articulation, "_is_fixed_base", False)
object.__setattr__(articulation, "_joint_names", joint_names)
object.__setattr__(articulation, "_body_names", body_names)
object.__setattr__(articulation, "_fixed_tendon_names", fixed_tendon_names)
object.__setattr__(articulation, "_spatial_tendon_names", spatial_tendon_names)
object.__setattr__(articulation, "_num_fixed_tendons", num_fixed_tendons)
object.__setattr__(articulation, "_num_spatial_tendons", num_spatial_tendons)

# Create ArticulationData
data = OvPhysxArticulationData(mock_bindings.bindings, device)
data._num_instances = num_instances
data._num_joints = num_joints
data._num_bodies = num_bodies
data._num_fixed_tendons = num_fixed_tendons
data._num_spatial_tendons = num_spatial_tendons
data._is_fixed_base = False
data.body_names = body_names
data.joint_names = joint_names
data.fixed_tendon_names = fixed_tendon_names
data.spatial_tendon_names = spatial_tendon_names
data._create_buffers()
object.__setattr__(articulation, "_data", data)

# Wrench composers
mock_inst_wrench = MockWrenchComposer(articulation)
mock_perm_wrench = MockWrenchComposer(articulation)
object.__setattr__(articulation, "_instantaneous_wrench_composer", mock_inst_wrench)
object.__setattr__(articulation, "_permanent_wrench_composer", mock_perm_wrench)

# Prevent __del__ / _clear_callbacks from raising
object.__setattr__(articulation, "_initialize_handle", None)
object.__setattr__(articulation, "_invalidate_initialize_handle", None)
object.__setattr__(articulation, "_prim_deletion_handle", None)
object.__setattr__(articulation, "_debug_vis_handle", None)
object.__setattr__(articulation, "actuators", {})

return articulation, mock_bindings


def create_newton_articulation(
num_instances: int = 2,
num_joints: int = 6,
Expand Down Expand Up @@ -326,6 +426,10 @@ def get_articulation(
return create_physx_articulation(
num_instances, num_joints, num_bodies, num_fixed_tendons, num_spatial_tendons, device
)
elif backend == "ovphysx":
return create_ovphysx_articulation(
num_instances, num_joints, num_bodies, num_fixed_tendons, num_spatial_tendons, device
)
elif backend == "newton":
return create_newton_articulation(num_instances, num_joints, num_bodies, device)
elif backend.lower() == "mock":
Expand Down
1 change: 1 addition & 0 deletions source/isaaclab/test/scene/test_interactive_scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ def _usd_replicate(stage, *args, **kwargs):
device="cpu",
physics_clone_fn=_physics_clone_fn,
visualizer_clone_fn=_visualizer_clone_fn,
clone_usd=True,
)
monkeypatch.setattr("isaaclab.scene.interactive_scene.cloner.usd_replicate", _usd_replicate)

Expand Down
21 changes: 21 additions & 0 deletions source/isaaclab_ovphysx/config/extension.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]

# Note: Semantic Versioning is used: https://semver.org/
version = "0.1.0"

# Description
title = "OvPhysX simulation interfaces for IsaacLab core package"
description = "Extension providing IsaacLab with ovphysx/TensorBindingsAPI specific abstractions."
readme = "docs/README.md"
repository = "https://github.com/isaac-sim/IsaacLab"
category = "robotics"
keywords = ["robotics", "simulation", "ovphysx", "tensorbindingsapi"]

[dependencies]
"isaaclab" = {}

[core]
reloadable = false

[[python.module]]
name = "isaaclab_ovphysx"
10 changes: 10 additions & 0 deletions source/isaaclab_ovphysx/docs/CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Changelog
---------

0.1.0 (2026-04-20)
~~~~~~~~~~~~~~~~~~

Added
^^^^^

* Initial release of the ``isaaclab_ovphysx`` extension.
Loading
Loading