Skip to content

[OVPHYSX] Articulation rewrite (data class + asset class + kernels)#5459

Merged
kellyguo11 merged 85 commits into
isaac-sim:developfrom
AntoineRichard:antoiner/feat/ovphysx_articulation
May 16, 2026
Merged

[OVPHYSX] Articulation rewrite (data class + asset class + kernels)#5459
kellyguo11 merged 85 commits into
isaac-sim:developfrom
AntoineRichard:antoiner/feat/ovphysx_articulation

Conversation

@AntoineRichard

Copy link
Copy Markdown
Collaborator

Description

Drastic rewrite of OVPhysX Articulation and ArticulationData so they follow the same shape as the post-refactor OVPhysX RigidObject from #5426, with the API surface mirroring Newton Articulation and behavior parity with PhysX Articulation. Single-PR atomic rewrite, clean break (no deprecation aliases for OVPhysX-introduced renames; framework-inherited deprecated shims kept).

The OVPhysX articulation diverged significantly from the rest of the framework conventions. This PR brings it back in line: same docstring template, same section ordering, same naming, same internal patterns, same lifecycle.

Important

Stacked on #5426 ([OVPHYSX] RigidObject + RigidObjectData asset). Review only the 16 articulation-specific commits at the tip of this branch — every commit before that lands via #5426. Once #5426 merges to develop, this PR will rebase cleanly onto develop.

Fixes # (none — internal refactor; no associated issue)

Architectural changes

OVPhysX RigidObject is the design template. It has navigated the hybrid OVPhysX surface — Newton-style mask+index dual API + PhysX-style CPU-only bindings via pinned-host staging à la #5329 + pull-to-refresh binding.read(target):

  • Eager TimestampedBufferWarp allocation in _create_buffers (single source of truth — no _invalidate_caches / _ensure_*_buffers machinery).
  • Pinned-host CPU staging buffers for every CPU-only binding (mass, COM, inertia, all DOF properties).
  • _binding_read / _binding_write / _stage_to_pinned_cpu helpers route CPU-only types through pinned-host memory.
  • Every public property returns a ProxyArray (warp + torch dual view); raw wp.array for one-shot config buffers.
  • Counts and names (num_instances, num_bodies, num_joints, body_names, joint_names, ...) demoted from @property to plain instance attributes.
  • Dual mask+index API on every writer/setter (*_index accepts partial data; *_mask accepts full data with a wp.bool mask).
  • All write_* / set_* parameters are kwarg-only after *,. No positional. No full_data flag anywhere.
  • The deprecated _write_body_state plumbing layer is removed; deprecated state-writer shims (write_root_state_to_sim, etc.) call the public write_*_to_sim_index methods directly, mirroring RigidObject.

Articulation-specific surface mirrors Newton 1-to-1: joint-state writers, joint-property writers (CPU-only), body-property setters (multi-body shape), joint-command target setters, external-wrench setters via WrenchComposer, fixed/spatial tendon setters, deprecated state-writer shims, full actuator pipeline (compute, _apply_actuator_model, _process_actuators_cfg).

Files changed

  • source/isaaclab_ovphysx/isaaclab_ovphysx/assets/articulation/articulation.py — full rewrite (~3863 lines, matches Newton).
  • source/isaaclab_ovphysx/isaaclab_ovphysx/assets/articulation/articulation_data.py — full rewrite (~2504 lines).
  • source/isaaclab_ovphysx/isaaclab_ovphysx/assets/kernels.py — gained 6 articulation kernels migrated from the stop-gap kernels_old.py (now deleted): _compose_root_com_pose, _compute_heading, _copy_first_body, _projected_gravity, _world_vel_to_body_ang, _world_vel_to_body_lin. Plus 2 new joint-property kernels (write_joint_position_limit_to_buffer_index/mask for trailing-dim-2 limits, write_joint_friction_to_buffer_index/mask for the broadcast-coefficient pattern).
  • source/isaaclab_ovphysx/isaaclab_ovphysx/assets/kernels_old.py — deleted.
  • source/isaaclab_ovphysx/test/assets/test_articulation.py — verbatim PhysX test mirror (~210 parametrizations) with PhysX-internal root_view.X assertions adapted to the OVPhysX bindings dict and omni.physx.scripts-dependent tests xfailed; mirrors the precedent from the RigidObject test mirror in [OVPHYSX] RigidObject + RigidObjectData asset #5426.

Type of change

  • Breaking change (existing functionality will not work without user modification — OVPhysX is at 0.2.x, clean break is acceptable per semver-on-0.x; no deprecation aliases for OVPhysX-introduced renames).
  • Code modernization / refactor.

Validation

Three layers, run on GPU and CPU separately (the wheel's process-global device-mode lock makes a single invocation lock to one device):

  1. Real-backend porttest_articulation.py (verbatim PhysX mirror). ./scripts/run_ovphysx.sh -m pytest <path> -k 'cuda:0' and ... -k 'cpu'. Expected end state: each pass shows <X> passed, <Y> xfailed, 0 failed. Every xfail carries a reason pointing at the wheel-gaps spec.
  2. Cross-backend interfacesource/isaaclab/test/assets/test_articulation_iface.py will gain an ovphysx backend, mirroring the rigid-object iface treatment from [OVPHYSX] RigidObject + RigidObjectData asset #5426.
  3. API consistency audit — per-method side-by-side checklist comparing Newton, RigidObject (post-refactor), and the rewritten Articulation; verifies method name, kwarg-only signature, parameter order, return type, docstring template, section-header placement.

Status

Active triage — not yet ready for review.

  • ✅ Implementation complete (all writers, setters, properties, lifecycle, actuator pipeline).
  • ✅ Initial GPU root-cause bug fixed: _read_transform_binding now routes BODY_COM_POSE through _binding_read so the wheel's CPU-only-binding device check is satisfied on a GPU sim.
  • ✅ Verbatim PhysX-internals assertions (root_view.max_dofs == shared_metatype.dof_count, link_paths[0] round-trip) adapted to the OVPhysX bindings dict — they now check binding.shape[1] == num_joints / num_bodies for each per-DOF / per-link binding.
  • 🔄 In-flight: tendon-init device-routing bug. _read_initial_properties reads FIXED_TENDON_* / SPATIAL_TENDON_* via numpy assuming CPU residency, but the wheel exposes them as GPU-resident (consistent with PhysX's set_fixed_tendon_properties not cloning to CPU). Plan is to remove tendon types from _CPU_ONLY_TYPES and read them directly into the sim-device buffer.
  • ⏳ Pending: cross-backend test_articulation_iface.py extension, API consistency audit, CHANGELOG + version bump (0.2.x → 0.3.0).

Checklist

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with ./isaaclab.sh --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works (the verbatim PhysX test mirror is the contract; bug-fixing in progress)
  • I have updated the changelog and the corresponding version in the extension's config/extension.toml file (deferred to final-pass commit)
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

Add nine RIGID_BODY_* aliases to isaaclab_ovphysx.tensor_types covering
the rigid-actor root pose/velocity/acceleration, wrench application, and
mass/inertia/COM properties. Each alias carries a shape/units docstring
that Sphinx autoattribute can pick up.

Extend _CPU_ONLY_TYPES with the five CPU-routed rigid-body variants so
the existing GPU/CPU dispatch in _to_flat_f32 routes them correctly.

Direct imports (no shim) intentionally couple this package to a future
ovphysx wheel that exposes the matching TensorType enum values; see
docs/superpowers/specs/2026-04-27-ovphysx-rigid-body-tensortypes-gap.md.

Issue: isaac-sim#5316
Move kernels reused across multiple asset types from
isaaclab_ovphysx.assets.articulation.kernels into a new
isaaclab_ovphysx.assets.kernels module: _body_wrench_to_world,
_scatter_rows_partial, _copy_first_body, _compose_root_com_pose,
_projected_gravity, _compute_heading, _world_vel_to_body_lin,
_world_vel_to_body_ang.

Articulation-only kernels (joint-limit setup, FD joint acceleration,
multi-body COM compose) stay in articulation/kernels.py. Articulation
modules update their imports accordingly. No behavior change.

This refactor unblocks the upcoming RigidObject implementation, which
needs the same kernels for frame conversions and wrench packing.

Issue: isaac-sim#5316
Generalize the mock binding factory to produce a rigid-object-shaped
binding set (RIGID_BODY_* keys only, num_joints=0, num_bodies=1, no
tendons) when called with asset_kind='rigid_object'. Default behavior
is unchanged: existing articulation callers do not need updates.

Make set_random_data tolerant of the smaller binding set so it works
for both asset kinds.

Issue: isaac-sim#5316
Add the rigid_object sub-package and the RigidObjectData class skeleton
with constructor, count properties, update/invalidate hooks, and
_process_cfg that fills _default_root_pose / _default_root_velocity from
cfg.init_state.

Add the backend-specific test file with a hasattr-based wheel gate
(importorskip-then-attr on the module would AttributeError rather than
skip when RIGID_BODY_* enums are absent on the wheel) and a basic-counts
smoke test.

Issue: isaac-sim#5316
Address review blockers on commit 65084f7:

1. _process_cfg now mirrors the articulation pattern (wp.zeros to
   pre-allocate, then wp.copy from wp.from_numpy with the typed dtype
   directly — matching articulation._process_cfg verbatim), avoiding
   the use-after-free that the previous reinterpret-cast-from-local
   idiom introduced when the float32 source went out of scope.

2. is_primed is now a guarded @Property + setter that enforces the
   one-way gate (False->True allowed, True->True idempotent,
   True->False raises ValueError), matching every other *Data class.

3. Drop unused forward-reference imports from
   test/assets/test_rigid_object.py so ruff/F401 passes; subsequent
   tasks will re-add them when the symbols are actually used.

Issue: isaac-sim#5316
Replace the abstract-property stubs for root_link_pose_w,
root_link_vel_w, root_com_pose_w, root_com_vel_w, and the per-axis
sliced views (root_link_pos_w, root_link_quat_w, root_lin_vel_w,
root_ang_vel_w, plus their root_com_* counterparts) with concrete
implementations that lazy-read from RIGID_BODY_ROOT_POSE /
RIGID_BODY_ROOT_VELOCITY through TensorBindings, cache for the rest
of the sim step, and expose slices as zero-copy Warp views over the
canonical pose/velocity buffers.

Mirrors the articulation_data lazy-read + ProxyArray + sliced-view
idioms with num_bodies=1 (root pose/velocity are 1-D over instances,
with no body axis).

Issue: isaac-sim#5316
The per-property TimestampedBuffer.timestamp fields are the freshness
gate consulted by every lazy-read property. _invalidate_caches was
previously clearing only the legacy _timestamps dict, leaving the
buffers with their last-read timestamp; a property accessed within
the same sim step (e.g. immediately after RigidObject.reset and
before update(dt) advances _sim_time) would serve pre-reset data.

Reset every allocated buffer's timestamp to -1.0 in
_invalidate_caches so the next property access always re-reads from
the binding regardless of where _sim_time stands.

Add a regression test that mutates the binding in place + calls
_invalidate_caches without advancing _sim_time and asserts the next
read reflects the new value.

Issue: isaac-sim#5316
Replace the abstract-property stubs for the body-state singleton-dim
views (body_link_pose_w, body_com_pose_w, body_*_vel_w, body_*_pos_w,
body_*_quat_w, body_*_lin_vel_w, body_*_ang_vel_w), the body
acceleration (body_link_acc_w, body_com_acc_w, plus their _lin_*/
_ang_* slices) gated on the RIGID_BODY_ACCELERATION binding, and the
body-frame derived properties (projected_gravity_b, heading_w,
root_link_lin_vel_b, root_link_ang_vel_b, root_com_lin_vel_b,
root_com_ang_vel_b).

Body-state views are zero-copy reshapes of the (N,) root buffers into
(N, 1, k) — no compute kernel needed when num_bodies=1. Body
acceleration raises NotImplementedError with a pointer to the gap
spec if the wheel lacks RIGID_BODY_ACCELERATION. Derived properties
launch the relocated _projected_gravity / _compute_heading /
_world_vel_to_body_{lin,ang} kernels from
isaaclab_ovphysx.assets.kernels.

Extend _invalidate_caches with the new TimestampedBuffer instances so
they participate in coarse cache invalidation.

Issue: isaac-sim#5316
The IsaacLab projected_gravity_b convention (per
BaseRigidObjectData docstring "Projection of the gravity direction
on base frame", and matching ArticulationData which normalizes
gravity_np / gravity_mag before storing GRAVITY_VEC_W) is the unit
direction, not the signed-magnitude. The Task 6 test assertion
expected -9.81 (signed magnitude); the kernel correctly produces
-1.0 (unit z-component of the gravity direction).

Update the assertion and the inline comment so the test reflects the
documented contract.

Issue: isaac-sim#5316
Replace the abstract-property stubs for body_mass, body_inertia, and
body_com_pose_b with concrete CPU-side lazy-read implementations
backed by RIGID_BODY_MASS / RIGID_BODY_INERTIA / RIGID_BODY_COM_POSE
bindings. Also implement body_com_pos_b and body_com_quat_b as
zero-copy slice views of body_com_pose_b's transformf buffer.

Properties read once on first access, cache as semi-static, and
invalidate via the _invalidate_caches reset loop (driven by
RigidObject mass/COM/inertia setters). Mirrors the articulation_data
pattern adapted for num_bodies = 1.

Issue: isaac-sim#5316
Add RigidObject class with __init__, _initialize_impl, _get_binding,
and count/data accessor properties. Eagerly creates GPU bindings for
RIGID_BODY_ROOT_POSE / RIGID_BODY_ROOT_VELOCITY / RIGID_BODY_WRENCH
so binding-creation failures surface at init time with a clear
message pointing at the gap spec.

_create_buffers and _process_cfg are placeholder no-ops; Task 9
replaces them. Write paths, reset, update, find_bodies, and the
deprecated state writers come in subsequent tasks (10-13). Abstract
methods that must be satisfied to instantiate the class are stubbed
with NotImplementedError pending those tasks.

Issue: isaac-sim#5316
Renames per Marco's feedback: RIGID_BODY_ROOT_POSE ->
RIGID_BODY_POSE and RIGID_BODY_ROOT_VELOCITY ->
RIGID_BODY_VELOCITY throughout. "Root" is articulation
vocabulary; a standalone rigid body IS the body.

Shape correction: RIGID_BODY_MASS and RIGID_BODY_INV_MASS
ship as (N,) not (N, 1). MockOvPhysxBindingSet allocates
(N,) for both; rigid_object_data.py's body_mass property
consumes (N,) and exposes (N, 1) via zero-copy reshape to
satisfy the BaseRigidObjectData contract.

Soften _initialize_impl error: removes the prescriptive
"NOT under an articulation root" language since pattern-
resolution gating is a future wheel-side selection policy.

Pre-existing ruff E501 in write_root_link_state_to_sim
docstring fixed as collateral.
Allocate _ALL_INDICES, _ALL_BODY_INDICES, their Warp views, the
single-body (N, 1, 9) _wrench_buf staging buffer, and the
instantaneous/permanent wrench composers. _process_cfg delegates to
RigidObjectData._process_cfg.

Issue: isaac-sim#5316
Add the twelve root-state writers (pose+velocity, actor+link+com,
index+mask variants) on RigidObject, plus the shared _to_flat_f32 and
_write_root_state helpers ported from articulation. Frame-conversion
variants launch the relocated assets/kernels.py kernels before the
binding write. Add the three deprecated compound state writers that
emit DeprecationWarning and delegate to the split pose+velocity
writers.

Add _compose_root_link_pose_from_com kernel to assets/kernels.py
to support COM->link pose inversion on the write path:
  link_pose = com_pose_w * inverse(com_pose_b)

Issue: isaac-sim#5316
Add set_masses_{index,mask}, set_coms_{index,mask},
set_inertias_{index,mask}. Each writes through the matching
CPU-routed RIGID_BODY_* binding via _write_root_state, then
invalidates the corresponding RigidObjectData cache via
_invalidate_caches.

body_ids / body_mask parameters are accepted for parity with the
BaseRigidObject contract but unused (num_bodies = 1).

Extend _write_root_state to handle 1-D bindings (RIGID_BODY_MASS)
by detecting them and bypassing the 2-D scatter kernel path.

Issue: isaac-sim#5316
Compose instantaneous + permanent wrenches, rotate body-frame
force/torque to world frame via _body_wrench_to_world (dim=(N, 1)),
reshape the (N, 1, 9) staging buffer to (N, 9) zero-copy, write to
RIGID_BODY_WRENCH, reset the instantaneous composer.

Issue: isaac-sim#5316
Replace stubs for reset, update, and find_bodies. reset writes the
default pose/velocity to the specified envs via zero-copy flat float32
views of the typed wp.transformf/wp.spatial_vectorf defaults, resets
both wrench composers, and invalidates the data caches. update
delegates to RigidObjectData.update. find_bodies handles the None
(all bodies) fast path and delegates regex matching to
resolve_matching_names for non-None inputs.

The deprecated compound state writers were already implemented in
Task 10 alongside the split-form writers and are not touched here.

Issue: isaac-sim#5316
Add the rigid_object/__init__.pyi stub mirroring the articulation
sibling, and extend isaaclab_ovphysx.assets/__init__.pyi to include
RigidObject and RigidObjectData. Public imports now resolve via
``from isaaclab_ovphysx.assets import RigidObject, RigidObjectData``.

Issue: isaac-sim#5316
Add the import-guarded BACKENDS.append('ovphysx') block, the
create_ovphysx_rigid_object factory, and the dispatch case so the
existing parametrized interface tests automatically cover the new
backend. Mirrors the OVPhysX articulation parametrization in
test_articulation_iface.py.

Issue: isaac-sim#5316
Mirror the Cartpole/Ant pattern by adding ovphysx variants to
ObjectCfg (RigidObjectCfg using the same DexCube spawn as the physx
variant) and to PhysicsCfg (OvPhysxCfg()). Default backend remains
physx; existing PhysX/Newton paths are unchanged.

This wires Isaac-Repose-Cube-Allegro-Direct-v0 for OVPhysX validation
via ./scripts/run_ovphysx.sh source/isaaclab_tasks/isaaclab_tasks/
direct/allegro_hand/allegro_hand_env.py --num_envs 4 --headless once
the wheel ships.

Issue: isaac-sim#5316
Add the 0.2.0 changelog entry on isaaclab_ovphysx covering the new
RigidObject/RigidObjectData classes, the RIGID_BODY_* TensorType
aliases (six already-shipping + three pending wheel update), the
mock-binding asset_kind extension, and the assets/kernels.py kernel
relocation. Bump the matching extension.toml version.

Add a patch-bump changelog entry on isaaclab_tasks for the Allegro
env preset addition.

Issue: isaac-sim#5316
The ovphysx wheel currently exposes 6 of the 9 RIGID_BODY_* TensorType
enums (POSE, VELOCITY, WRENCH, MASS, COM_POSE, INERTIA). The remaining
three (ACCELERATION, INV_MASS, INV_INERTIA) are pending an upcoming
wheel update from @marcodiiga.

Make the IsaacLab side wheel-update-agnostic:

* Guard tensor_types.py imports of the three not-yet-shipping aliases
  with try/except AttributeError so isaaclab_ovphysx.tensor_types
  imports cleanly on today's wheel. _CPU_ONLY_TYPES filters to only
  the names that exist via _RIGID_BODY_OPTIONAL_CPU.
* MockOvPhysxBindingSet skips the optional bindings when their alias
  is not defined.
* RigidObjectData.body_*_acc_w now finite-differences from
  body_com_vel_w, mirroring Newton's pattern (kernel
  derive_body_acceleration_from_body_com_velocities ported into
  isaaclab_ovphysx.assets.kernels). Removes the
  NotImplementedError-on-missing-binding fallback.
* update(dt) stores _last_dt and eagerly triggers body_com_acc_w each
  step so FD captures every transition.

The forward-compat aliases stay declared (Marco will land them); when
they ship, the existing TensorBinding read path will work without
further IsaacLab changes.

Issue: isaac-sim#5316
WrenchComposer.__init__ calls hasattr(asset.data, "body_com_pos_w"),
which triggers the property chain body_com_pos_w → body_com_pose_w →
root_com_pose_w, reading the RIGID_BODY_COM_POSE binding and setting
_body_com_pose_b_buf.timestamp = _sim_time = 0.0.

Any subsequent mutation of the binding (e.g. set_coms_index, or a test
that sets the binding directly) is then invisible to _com_pose_to_link_pose
because the freshness gate ``buf.timestamp >= _sim_time`` treats 0.0 ≥ 0.0
as "already fresh" and skips the read — returning stale buffer contents
to the frame-conversion kernel and producing an off-by-translation result.

Force a fresh read in _com_pose_to_link_pose by resetting the buffer
timestamp to -1.0 immediately before calling _read_transform_binding.
The frame conversion always needs the current binding value at write time;
the lazy-cache is the wrong policy here.

Caught by test_write_root_com_pose_to_sim_index_invokes_frame_conversion.

Issue: isaac-sim#5316
When running via ./scripts/run_ovphysx.sh, the test file's unconditional
AppLauncher call segfaults during pytest collection because the script's
thin Kit shell (libcarb preload only, no full Kit boot) cannot host a
real AppLauncher. Mirror the _kitless heuristic from test_articulation_iface
so the rigid-object iface tests skip AppLauncher and substitute MagicMock
isaacsim core modules in the same scenarios.

The original _kitless heuristic (LD_PRELOAD == "" and EXP_PATH not set) was
also incorrect for this environment: run_ovphysx.sh sets LD_PRELOAD to the
ovphysx libcarb.so path, not an empty string. Fix the heuristic in both
test files to check for "ovphysx" in LD_PRELOAD as the primary signal,
with the bare-Python fallback retained as a secondary guard.

Also extend the kitless sys.modules stub list to cover omni.physics.tensors
and related Kit modules that physx_manager.py imports at module scope, which
would otherwise cause a ModuleNotFoundError during collection.

This unblocks running the OVPhysX-parametrized parts of the file via
run_ovphysx.sh. PhysX/Newton/Mock paths are unaffected.

Issue: isaac-sim#5316
Three related bugs were present in the OVPhysX RigidObject body-property
write path, all contributing to the 120 test failures.

First, _write_root_state's full-write path (no env_ids, no mask) had no
row-count validation for 1-D bindings such as RIGID_BODY_MASS. Passing a
tensor with more rows than num_instances silently reached the mock's numpy
reshape and raised ValueError, but the test infrastructure expected
AssertionError or RuntimeError.  An explicit row-count guard now raises
RuntimeError on the full-write path before any binding call.

Second, the index/mask sub-write path for 1-D bindings passed the source
array to binding.write() as a 2-D (K, 1) buffer (the raw shape produced by
_to_flat_f32 when the caller supplies (K, 1) torch data). For the mask path
this caused NumPy boolean-index assignment to fail with TypeError because it
cannot scatter a 2-D array into a 1-D binding buffer. The 1-D source is now
normalised to shape (K,) via a zero-copy warp array view before any write.

Third, write_root_com_pose_to_sim_index and write_root_com_pose_to_sim_mask
silently truncated oversized inputs inside _com_pose_to_link_pose, which
hardcodes shape=(N,) for the intermediate warp array view regardless of the
input size. This masked shape errors on full writes. Explicit row-count
guards are now applied at the public API entry points for these two methods.

Additionally, default_root_pose and default_root_vel in RigidObjectData were
NotImplementedError stubs. They are now implemented to return ProxyArray
wrappers over the _default_root_pose/_default_root_velocity buffers that are
already populated during _process_cfg.

Caught by the cross-backend TestRigidObjectWritersBody tests against
the OVPhysX backend. After the fix all 372 ovphysx-parametrized cases
pass.

Issue: isaac-sim#5316
The mock-based test file is removed in favor of a copy of PhysX's
test_rigid_object.py adapted to the kitless OVPhysX architecture:
- Drop AppLauncher; mock the isaacsim and omni.* modules instead so
  the file imports under run_ovphysx.sh without launching Kit.
- Build the test scene via MockOvPhysxBindingSet, bypassing
  OvPhysxManager entirely (no Kit stage export needed).
- Drive sim steps via direct binding manipulation; no
  build_simulation_context.
- Programmatically construct rigid-object shells instead of pulling
  DexCube USD from Nucleus.

Tests that exercise OVPhysX features not yet wired (OvPhysxManager
step loop, contact materials, kitless stage entry point) are
explicitly xfail-marked with inline reasons.

Result: 67 passed, 73 xfailed, 0 failed.

See docs/superpowers/specs/2026-04-28-ovphysx-rigid-object-test-gaps.md
(worktree-only, gitignored) for the consolidated gap list for Marco.
OvPhysxManager IS drivable without AppLauncher: _warmup_and_load only
needs PhysicsManager._sim.stage + a couple of cfg fields, which a thin
SimpleNamespace fake satisfies. Use this to convert the warmup and
stage-load xfails into real-backend tests against the live ovphysx.PhysX
instance.

Adds _make_kitless_sim_context() helper (builds in-memory USD stage with
RigidBodyAPI + CollisionAPI cube + PhysicsScene, wraps in SimpleNamespace
exposing: stage, cfg.physics, cfg.device, cfg.physics_prim_path,
cfg.enable_scene_query_support, cfg.dt) and a module-scoped
kitless_manager_cpu fixture that drives initialize() + reset() + close().

Converts test_warmup_attach_stage_not_called_for_cpu (1 xfail) to three
passing real-backend tests:
- test_warmup_and_load_cpu: lifecycle assertions
- test_warmup_gpu_not_called_for_cpu: CPU path skips warmup_gpu
- test_stage_load_cpu: stage export + usd_handle type check

Adds test_warmup_and_load_gpu as xfail pending a GPU CI runner.

Result: 70 passed, 73 xfailed (was 67 passed, 73 xfailed).

Update the test-gaps doc to reflect the closed gap (no wheel change
required for this category).

Issue: isaac-sim#5316
Drop the kitless mocks, the SimpleNamespace sim-context fake, and the
MockOvPhysxBindingSet shell-injection helpers. The standard
SimulationContext + UsdFileCfg(ISAAC_NUCLEUS_DIR/...) pattern works
under ./scripts/run_ovphysx.sh without AppLauncher — omni.client
resolves Nucleus URLs from Kit's Python directly, and SimulationContext
runs kitless via has_kit() returning False.

Tests now exercise real RigidObject + RigidObjectData + OvPhysxManager
against live ovphysx.PhysX bindings, using the same Nucleus assets
the Cartpole/Newton tests use.

Material-properties tests stay xfailed pending a wheel-side
RIGID_BODY_MATERIAL TensorType. GPU tests now pass (NVIDIA RTX 5000 Ada
verified).

Production bug surfaced: RigidObject._initialize_impl uses hasattr() on
TensorBinding.body_names which propagates RuntimeError (not
AttributeError), blocking all RigidObject lifecycle tests against the
real backend. Fix: wrap in try/except (AttributeError, RuntimeError).
Tracking: issue isaac-sim#5316.

Issue: isaac-sim#5316
The previous ``hasattr(root_pose, "body_names")`` gate only catches
AttributeError, but the real ovphysx TensorBinding raises TypeError
on the body_names property for non-articulation tensor types such as
RIGID_BODY_POSE: "Articulation metadata … is not available for
tensor type 'RIGID_BODY_POSE'." Replace with try/except that catches
both AttributeError and TypeError; fall back to ["base_link"].

Also fix self._device derivation: ``hasattr(self._ovphysx, "device")``
always returns False for the real PhysX object (no .device property),
so the device silently fell back to "cuda:0" even when the simulation
runs on CPU, causing a device mismatch in TensorBinding.read(). Use
OvPhysxManager.get_device() which mirrors SimulationContext.cfg.device.

Un-xfail 68 of the 70 tests tagged _INIT_IMPL_BUG in test_rigid_object.py
— they now run cleanly against the live ovphysx CPU backend (59 passed).
The two remaining xfails use a tightened reason (_FORCE_BALANCE_GAP):
test_external_force_on_single_body drifts ~0.57 m instead of < 0.1 m,
a physics-accuracy gap under investigation.

Issue: isaac-sim#5316
Rewrites test_external_force_on_single_body to mirror Newton's reference
implementation: 5 outer iterations with a full pose/velocity reset between
each, 5 inner sim steps per iteration, force applied to every 2nd cube
(indices 0::2), and alternating global/local frame each outer iteration.

The old test used a single 20-step loop on env_0 only, with no resets and
no is_global argument — causing error accumulation and the ~0.57 m drift.

Because RigidObjectData._bindings has no RIGID_BODY_MASS entry at init
time, body_mass.torch returns zeros; the USD-stage MassAPI value is read
instead.  The per-block drift is ~6 mm vs ~35 mm free-fall, well within
the atol=1e-2 guard, so the xfail decorator is removed.
…ysx_articulation

# Conflicts:
#	source/isaaclab_ovphysx/isaaclab_ovphysx/assets/articulation/articulation.py
#	source/isaaclab_ovphysx/isaaclab_ovphysx/assets/articulation/articulation_data.py
#	source/isaaclab_ovphysx/isaaclab_ovphysx/assets/articulation/kernels.py
#	source/isaaclab_ovphysx/isaaclab_ovphysx/assets/kernels.py
#	source/isaaclab_ovphysx/isaaclab_ovphysx/tensor_types.py
Adopt the RigidObjectData / PhysX pattern: derive instance / body /
joint / tendon counts from the binding metadata
(``next(iter(bindings.values()))``), and assign name lists as
class-level attributes filled in by ``Articulation._initialize_impl``
after construction. The constructor signature collapses from twelve
arguments to ``(bindings, device)``.

Drop the ``binding_getter`` callable. Bindings the data container reads
(LINK_POSE, LINK_VELOCITY, LINK_ACCELERATION, LINK_INCOMING_JOINT_FORCE,
ROOT_VELOCITY, and the fixed / spatial tendon set when counts > 0) are
eagerly created in ``_initialize_impl`` instead of looked up via a
callback, so ``ArticulationData._get_binding`` reduces to a dict lookup.

Guard the forward-kinematics call in ``body_link_pose_w`` against a
missing ``OvPhysxManager`` instance, so the mocked cross-backend iface
tests no longer trip ``WrenchComposer``'s ``hasattr`` probe.

Drop ``test_articulation_data.py`` (covered by real-backend tests) and
the two ``_data`` tendon-name assertions in
``test_articulation_helpers.py`` (propagation now lives in
``_initialize_impl``, not ``_process_tendons``).

Articulation tests: 101 real-backend + 2 helpers + 1080 iface = 1183
passed.
The isaaclab_ov CI job's glob collects all ``test/`` files under
``source/isaaclab_ov*``, including the isaaclab_ovphysx tests. The
sibling ovphysx test files already guard their isaaclab_ovphysx imports
with ``pytest.importorskip("ovphysx.types", reason="ovphysx wheel not
installed")``; only ``test_articulation.py`` went straight to top-level
``from isaaclab_ovphysx.assets import Articulation``, which raised
``ModuleNotFoundError`` at collection time and failed the entire
isaaclab_ov job in the CI image (where the ovphysx wheel is not
installed).

Add the same ``importorskip`` guard so the file is skipped cleanly in
that environment, matching the established pattern from
test_articulation_helpers.py and the rigid-object fix in isaac-sim#5426.

Also add a ``.skip`` changelog fragment for the isaaclab core package
covering the iface test factory tweak in the prior commit.
The recent OVPhysX preset hookup for the Allegro Repose direct task adds
``from isaaclab_ovphysx.physics import OvPhysxCfg`` at module top in
``source/isaaclab_tasks/.../allegro_hand/allegro_hand_env_cfg.py``.

When sphinx walks ``isaaclab_mimic.datagen`` for autodoc, the chain
``generation.py`` → ``isaaclab_tasks.utils.parse_cfg`` →
``isaaclab_tasks.__init__.import_packages`` recursively imports every
task package's ``__init__.py``. ``isaaclab_ovphysx/tensor_types.py``
does ``from ovphysx.types import TensorType`` at module load; the
``ovphysx`` wheel is not in ``docs/requirements.txt``, so the import
raises ``ModuleNotFoundError`` and sphinx reports the failure at the
outermost autodoc'd module:

    WARNING: Failed to import isaaclab_mimic.datagen.
    Possible hints:
    * AttributeError: module 'isaaclab_mimic' has no attribute 'datagen'
    * TypeError: unsupported operand type(s) for *: 'float' and 'pi'

(the ``float * pi`` hint is the assemble_trocar ``np.pi`` mock issue
surfacing as a sphinx fallback; not the primary cause.)

Add ``ovphysx`` to ``autodoc_mock_imports`` so sphinx mocks the wheel
during doc build, mirroring how ``omni`` / ``carb`` / ``isaacsim`` are
handled. No source-side change needed.
AntoineRichard added a commit to AntoineRichard/IsaacLab that referenced this pull request May 15, 2026
Verification agents flagged five issues against the previous two
commits and one issue-isaac-sim#876 gap that the first pass missed.

1. Newton using-kamino.rst literalinclude path was one level short
   (../../../../../ → ../../../../../../). The previous depth resolved
   to a non-existent /docs/source/isaaclab_tasks/... and would have
   broken the sphinx build.

2. PhysX supported-features.rst listed Ray Caster, Visuo-tactile, and
   Camera as PhysX-specific sensors. Ray Caster and Camera are
   implemented in isaaclab core (backend-agnostic); Visuo-tactile lives
   in isaaclab_contrib. Reframe the section to separate PhysX-implemented
   sensors from backend-agnostic ones. Drop the unverifiable
   "path-traced" qualifier on the RTX renderer claim.

3. OvPhysX stub referenced only PR isaac-sim#5426 and PR isaac-sim#5459. The actual
   in-flight set spans six PRs: isaac-sim#5426 (merged), isaac-sim#5459, isaac-sim#5422, isaac-sim#5421,
   isaac-sim#5570, isaac-sim#5589. List them all with merge state, and reword "primary
   covered surfaces" to reflect that most are still open PRs.

4. backends/index.rst feature matrix said OvPhysX sensor coverage was
   "Partial" — actually only RigidObject is merged. Replace the
   matrix rows with concrete "In-flight (PR #...)" / "Not yet" cells.

5. Issue isaac-sim#876 asked to "review the limitations list and update it." The
   previous pass only reworded the intro. Refresh the task list against
   develop's actual newton_mjwarp coverage, add Shadow Hand / Shadow
   Hand Over / cabinet / dexsuite / rough-terrain locomotion, and
   replace the rigid bullet list with a discovery recipe so the list
   stops bit-rotting.
@kellyguo11 kellyguo11 moved this from In review to Ready to merge in Isaac Lab May 16, 2026
@kellyguo11 kellyguo11 merged commit 6e3482d into isaac-sim:develop May 16, 2026
93 of 100 checks passed
@github-project-automation github-project-automation Bot moved this from Ready to merge to Done in Isaac Lab May 16, 2026
@isaaclab-review-bot isaaclab-review-bot Bot mentioned this pull request May 16, 2026
7 tasks
AntoineRichard added a commit that referenced this pull request May 18, 2026
# Description

Adds `OvPhysxSceneDataProvider`, the OVPhysX-backend implementation of
`BaseSceneDataProvider`. Enables Newton-based visualizers (Rerun, Viser,
native Newton viewport) to render OVPhysX simulations. Also fixes the
`SceneDataProvider` factory dispatch (and adds a regression test) so the
substring check `"physx" in "ovphysxmanager"` no longer routes ovphysx
to PhysX.

Fixes #5327

> [!IMPORTANT]
> **Stacked on #5459** (`[OVPHYSX] Articulation rewrite`). The base of
this PR is `develop`, so the diff includes every commit from #5459 —
review only the 16 SDP-specific commits on top (`git log
antoiner/feat/ovphysx_articulation..antoiner/feat/ovphysx_scene_data_provider`).
Once #5459 merges, this PR will rebase cleanly.

## Architectural notes

Mirrors `PhysxSceneDataProvider` end-to-end: build a Newton model from
the scene's `ClonePlan` at init (gated on
`SceneDataRequirement.requires_newton_model`), then on every `update()`
read body poses through ovphysx `TensorBinding`s and write them into the
Newton state's `body_q` via a single Warp kernel.

The interesting OVPhysX-specific surface:

- **One-pattern-per-binding**: the ovphysx wheel's
`PhysX.create_tensor_binding` accepts a single glob-style pattern per
binding (not a list, unlike PhysX's `RigidBodyView`). We bucket Newton
body paths by their env-wildcard form (`/World/envs/env_*/Robot/cart`,
`/World/envs/env_*/Robot/pole`, ...), one pose+velocity binding per
distinct relative path. Cartpole produces 2 bindings; Allegro hand ~17 —
each covering all envs.
- **`RIGID_BODY_POSE` covers everything**: confirmed via standalone
probe + wheel docs that the matcher accepts any prim with
`UsdPhysics.RigidBodyAPI`, including articulation links. No separate
`LINK_POSE` plumbing is needed. Paths flow through a `RigidBodyAPI`
filter (mirroring `PhysxSceneDataProvider._setup_rigid_body_view` line
~281) so joints and articulation-root xforms are excluded from binding
patterns.
- **Wheel-0.4 `binding.read(dst)` API**: pre-allocated warp destination
buffers, so per-step reads are allocation-free.
- **View-order reorder**: each binding's `prim_paths` is matched against
the Newton `body_label` ordering to build a per-binding reorder tensor
with `-1` sentinels for rows owned by other bindings. The pose merge
respects partial coverage across bindings, with `FrameView` fallback for
anything that lacks `RigidBodyAPI` (cameras, decorative xforms).
- **Factory fix**: `SceneDataProvider._get_backend` now checks
`"ovphysx"` explicitly before `"physx"` so the substring overlap doesn't
route the wrong backend.

## Files changed (16 commits since `antoiner/feat/ovphysx_articulation`)

- `source/isaaclab/isaaclab/physics/scene_data_provider.py` — factory
dispatch fix (+9/-1).
- `source/isaaclab/test/sim/test_scene_data_provider_factory.py` — new
parametrized regression test (44 lines).
-
`source/isaaclab_ovphysx/isaaclab_ovphysx/scene_data_providers/__init__.py`,
`__init__.pyi` — package scaffolding.
-
`source/isaaclab_ovphysx/isaaclab_ovphysx/scene_data_providers/ovphysx_scene_data_provider.py`
— the provider (~760 lines).
-
`source/isaaclab_ovphysx/test/scene_data_providers/test_ovphysx_scene_data_provider.py`
— 18 stub-based unit tests covering factory dispatch, metadata, env
discovery, Newton-model build, binding setup, pose-merge orchestration,
transform/velocity getters, view-order reorder, camera-transform stage
walk.
- `docs/source/api/index.rst` +
`docs/source/api/lab_ovphysx/isaaclab_ovphysx.scene_data_providers.rst`
— first `isaaclab_ovphysx` section in the API reference.
-
`source/isaaclab/changelog.d/antoiner-feat-ovphysx-scene-data-provider.rst`
— patch fragment for the factory fix.
-
`source/isaaclab_ovphysx/changelog.d/antoiner-feat-ovphysx-scene-data-provider.minor.rst`
— minor fragment for the new provider.

## Type of change

- New feature (non-breaking change which adds functionality).
- Bug fix (factory dispatch).

## Validation

1. **Unit tests** — `./isaaclab.sh -p -m pytest
source/isaaclab/test/sim/test_scene_data_provider_factory.py
source/isaaclab_ovphysx/test/scene_data_providers/` → 23 passed.
2. **End-to-end** — `./isaaclab.sh -p
scripts/reinforcement_learning/rsl_rl/train.py
--task=Isaac-Cartpole-Direct-v0 --visualizer newton --num_envs 4096
presets=ovphysx` reaches the simulation loop; visualizer renders the
cartpole scene.
3. **Pre-commit** — `./isaaclab.sh -f` clean.
4. **Pattern probe** — standalone reproduction at
`/tmp/probe_rigid_body_pose_on_articulation.py` confirmed
`RIGID_BODY_POSE` accepts articulation-link patterns. Reproducible
against the wheel's bundled minimal articulation USD.

## Known follow-ups (not in this PR)

- Extract `_build_newton_model_from_clone_plan` into a shared helper
used by both PhysX and OVPhysX providers (currently a verbatim copy).
- `FrameView` factory could grow an explicit `"ovphysx"` registration
entry (today's dispatch falls through to the `"physx"` branch, which
works because `FabricFrameView` reads from the shared USD stage).
- Pattern length cap: if a scene's distinct-body-path count exceeds the
wheel's per-pattern matcher limit (unknown), chunk into multiple
bindings indexed by buffer-row offset. Hasn't bitten yet.
- A future `test_ovphysx_scene_data_provider_visualizer_contract.py`
mirroring `test_physx_scene_data_provider_visualizer_contract.py`.

## Checklist

- [x] I have read and understood the [contribution
guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html)
- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my feature works
- [x] I have added a changelog fragment under
`source/<pkg>/changelog.d/` for every touched package
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there
AntoineRichard added a commit to AntoineRichard/IsaacLab that referenced this pull request May 18, 2026
Drop AppLauncher and the PhysX-style setup_simulation fixture in favour
of the kitless _ovphysx_sim_context helper (matches test_rigid_object.py
shipped in isaac-sim#5459) and the autouse device-lock fixture for ovphysx<=0.3.7.

Also drop the disable_contact_processing parametrize axis: toggling the
global Carbonite '/physics/disableContactProcessing' flag requires the
settings manager which is not available in kitless mode.  test_no_contact_reporting
now exercises the same semantic via a no-filter cfg instead.

The three v2-deferred tests (track_friction_forces / track_contact_points)
keep their pytest.mark.skip but their bodies are fully kitless so they
can be un-skipped once the ovphysx wheel ships the missing per-sensor
read APIs (see docs/superpowers/specs/2026-04-27-ovphysx-contact-api-gaps.md).

Run via:
    ./scripts/run_ovphysx.sh -m pytest source/isaaclab_ovphysx/test/sensors/test_contact_sensor.py -v
matthewtrepte pushed a commit to matthewtrepte/IsaacLab that referenced this pull request May 18, 2026
…saac-sim#5459)

# Description

Drastic rewrite of OVPhysX `Articulation` and `ArticulationData` so they
follow the same shape as the post-refactor OVPhysX `RigidObject` from
isaac-sim#5426, with the API surface mirroring `Newton Articulation` and behavior
parity with `PhysX Articulation`. Single-PR atomic rewrite, clean break
(no deprecation aliases for OVPhysX-introduced renames;
framework-inherited deprecated shims kept).

The OVPhysX articulation diverged significantly from the rest of the
framework conventions. This PR brings it back in line: same docstring
template, same section ordering, same naming, same internal patterns,
same lifecycle.

> [!IMPORTANT]
> **Stacked on isaac-sim#5426** (`[OVPHYSX] RigidObject + RigidObjectData
asset`). Review only the 16 articulation-specific commits at the tip of
this branch — every commit before that lands via isaac-sim#5426. Once isaac-sim#5426
merges to `develop`, this PR will rebase cleanly onto `develop`.

Fixes # (none — internal refactor; no associated issue)

## Architectural changes

**OVPhysX RigidObject is the design template.** It has navigated the
hybrid OVPhysX surface — Newton-style mask+index dual API + PhysX-style
CPU-only bindings via pinned-host staging à la isaac-sim#5329 + pull-to-refresh
`binding.read(target)`:

- Eager `TimestampedBufferWarp` allocation in `_create_buffers` (single
source of truth — no `_invalidate_caches` / `_ensure_*_buffers`
machinery).
- Pinned-host CPU staging buffers for every CPU-only binding (mass, COM,
inertia, all DOF properties).
- `_binding_read` / `_binding_write` / `_stage_to_pinned_cpu` helpers
route CPU-only types through pinned-host memory.
- Every public property returns a `ProxyArray` (warp + torch dual view);
raw `wp.array` for one-shot config buffers.
- Counts and names (`num_instances`, `num_bodies`, `num_joints`,
`body_names`, `joint_names`, ...) demoted from `@property` to plain
instance attributes.
- Dual mask+index API on every writer/setter (`*_index` accepts partial
data; `*_mask` accepts full data with a `wp.bool` mask).
- All `write_*` / `set_*` parameters are kwarg-only after `*,`. No
positional. **No `full_data` flag anywhere.**
- The deprecated `_write_body_state` plumbing layer is removed;
deprecated state-writer shims (`write_root_state_to_sim`, etc.) call the
public `write_*_to_sim_index` methods directly, mirroring RigidObject.

**Articulation-specific surface** mirrors Newton 1-to-1: joint-state
writers, joint-property writers (CPU-only), body-property setters
(multi-body shape), joint-command target setters, external-wrench
setters via `WrenchComposer`, fixed/spatial tendon setters, deprecated
state-writer shims, full actuator pipeline (`compute`,
`_apply_actuator_model`, `_process_actuators_cfg`).

## Files changed

-
`source/isaaclab_ovphysx/isaaclab_ovphysx/assets/articulation/articulation.py`
— full rewrite (~3863 lines, matches Newton).
-
`source/isaaclab_ovphysx/isaaclab_ovphysx/assets/articulation/articulation_data.py`
— full rewrite (~2504 lines).
- `source/isaaclab_ovphysx/isaaclab_ovphysx/assets/kernels.py` — gained
6 articulation kernels migrated from the stop-gap `kernels_old.py` (now
deleted): `_compose_root_com_pose`, `_compute_heading`,
`_copy_first_body`, `_projected_gravity`, `_world_vel_to_body_ang`,
`_world_vel_to_body_lin`. Plus 2 new joint-property kernels
(`write_joint_position_limit_to_buffer_index/mask` for trailing-dim-2
limits, `write_joint_friction_to_buffer_index/mask` for the
broadcast-coefficient pattern).
- `source/isaaclab_ovphysx/isaaclab_ovphysx/assets/kernels_old.py` —
deleted.
- `source/isaaclab_ovphysx/test/assets/test_articulation.py` — verbatim
PhysX test mirror (~210 parametrizations) with PhysX-internal
`root_view.X` assertions adapted to the OVPhysX bindings dict and
`omni.physx.scripts`-dependent tests xfailed; mirrors the precedent from
the RigidObject test mirror in isaac-sim#5426.

## Type of change

- Breaking change (existing functionality will not work without user
modification — OVPhysX is at `0.2.x`, clean break is acceptable per
semver-on-0.x; no deprecation aliases for OVPhysX-introduced renames).
- Code modernization / refactor.

## Validation

Three layers, run on **GPU and CPU separately** (the wheel's
process-global device-mode lock makes a single invocation lock to one
device):

1. **Real-backend port** — `test_articulation.py` (verbatim PhysX
mirror). `./scripts/run_ovphysx.sh -m pytest <path> -k 'cuda:0'` and
`... -k 'cpu'`. Expected end state: each pass shows `<X> passed, <Y>
xfailed, 0 failed`. Every xfail carries a `reason` pointing at the
wheel-gaps spec.
2. **Cross-backend interface** —
`source/isaaclab/test/assets/test_articulation_iface.py` will gain an
`ovphysx` backend, mirroring the rigid-object iface treatment from
isaac-sim#5426.
3. **API consistency audit** — per-method side-by-side checklist
comparing Newton, RigidObject (post-refactor), and the rewritten
Articulation; verifies method name, kwarg-only signature, parameter
order, return type, docstring template, section-header placement.

## Status

Active triage — not yet ready for review.

- ✅ Implementation complete (all writers, setters, properties,
lifecycle, actuator pipeline).
- ✅ Initial GPU root-cause bug fixed: `_read_transform_binding` now
routes `BODY_COM_POSE` through `_binding_read` so the wheel's
CPU-only-binding device check is satisfied on a GPU sim.
- ✅ Verbatim PhysX-internals assertions (`root_view.max_dofs ==
shared_metatype.dof_count`, `link_paths[0]` round-trip) adapted to the
OVPhysX bindings dict — they now check `binding.shape[1] == num_joints /
num_bodies` for each per-DOF / per-link binding.
- 🔄 In-flight: tendon-init device-routing bug.
`_read_initial_properties` reads `FIXED_TENDON_*` / `SPATIAL_TENDON_*`
via numpy assuming CPU residency, but the wheel exposes them as
GPU-resident (consistent with PhysX's `set_fixed_tendon_properties` not
cloning to CPU). Plan is to remove tendon types from `_CPU_ONLY_TYPES`
and read them directly into the sim-device buffer.
- ⏳ Pending: cross-backend `test_articulation_iface.py` extension, API
consistency audit, CHANGELOG + version bump (`0.2.x → 0.3.0`).

## Checklist

- [x] I have read and understood the [contribution
guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html)
- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works (the verbatim PhysX test mirror is the contract;
bug-fixing in progress)
- [ ] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file (deferred to final-pass commit)
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there

---------

Signed-off-by: Kelly Guo <kellyg@nvidia.com>
Co-authored-by: Kelly Guo <kellyg@nvidia.com>
matthewtrepte pushed a commit to matthewtrepte/IsaacLab that referenced this pull request May 18, 2026
# Description

Adds `OvPhysxSceneDataProvider`, the OVPhysX-backend implementation of
`BaseSceneDataProvider`. Enables Newton-based visualizers (Rerun, Viser,
native Newton viewport) to render OVPhysX simulations. Also fixes the
`SceneDataProvider` factory dispatch (and adds a regression test) so the
substring check `"physx" in "ovphysxmanager"` no longer routes ovphysx
to PhysX.

Fixes isaac-sim#5327

> [!IMPORTANT]
> **Stacked on isaac-sim#5459** (`[OVPHYSX] Articulation rewrite`). The base of
this PR is `develop`, so the diff includes every commit from isaac-sim#5459 —
review only the 16 SDP-specific commits on top (`git log
antoiner/feat/ovphysx_articulation..antoiner/feat/ovphysx_scene_data_provider`).
Once isaac-sim#5459 merges, this PR will rebase cleanly.

## Architectural notes

Mirrors `PhysxSceneDataProvider` end-to-end: build a Newton model from
the scene's `ClonePlan` at init (gated on
`SceneDataRequirement.requires_newton_model`), then on every `update()`
read body poses through ovphysx `TensorBinding`s and write them into the
Newton state's `body_q` via a single Warp kernel.

The interesting OVPhysX-specific surface:

- **One-pattern-per-binding**: the ovphysx wheel's
`PhysX.create_tensor_binding` accepts a single glob-style pattern per
binding (not a list, unlike PhysX's `RigidBodyView`). We bucket Newton
body paths by their env-wildcard form (`/World/envs/env_*/Robot/cart`,
`/World/envs/env_*/Robot/pole`, ...), one pose+velocity binding per
distinct relative path. Cartpole produces 2 bindings; Allegro hand ~17 —
each covering all envs.
- **`RIGID_BODY_POSE` covers everything**: confirmed via standalone
probe + wheel docs that the matcher accepts any prim with
`UsdPhysics.RigidBodyAPI`, including articulation links. No separate
`LINK_POSE` plumbing is needed. Paths flow through a `RigidBodyAPI`
filter (mirroring `PhysxSceneDataProvider._setup_rigid_body_view` line
~281) so joints and articulation-root xforms are excluded from binding
patterns.
- **Wheel-0.4 `binding.read(dst)` API**: pre-allocated warp destination
buffers, so per-step reads are allocation-free.
- **View-order reorder**: each binding's `prim_paths` is matched against
the Newton `body_label` ordering to build a per-binding reorder tensor
with `-1` sentinels for rows owned by other bindings. The pose merge
respects partial coverage across bindings, with `FrameView` fallback for
anything that lacks `RigidBodyAPI` (cameras, decorative xforms).
- **Factory fix**: `SceneDataProvider._get_backend` now checks
`"ovphysx"` explicitly before `"physx"` so the substring overlap doesn't
route the wrong backend.

## Files changed (16 commits since `antoiner/feat/ovphysx_articulation`)

- `source/isaaclab/isaaclab/physics/scene_data_provider.py` — factory
dispatch fix (+9/-1).
- `source/isaaclab/test/sim/test_scene_data_provider_factory.py` — new
parametrized regression test (44 lines).
-
`source/isaaclab_ovphysx/isaaclab_ovphysx/scene_data_providers/__init__.py`,
`__init__.pyi` — package scaffolding.
-
`source/isaaclab_ovphysx/isaaclab_ovphysx/scene_data_providers/ovphysx_scene_data_provider.py`
— the provider (~760 lines).
-
`source/isaaclab_ovphysx/test/scene_data_providers/test_ovphysx_scene_data_provider.py`
— 18 stub-based unit tests covering factory dispatch, metadata, env
discovery, Newton-model build, binding setup, pose-merge orchestration,
transform/velocity getters, view-order reorder, camera-transform stage
walk.
- `docs/source/api/index.rst` +
`docs/source/api/lab_ovphysx/isaaclab_ovphysx.scene_data_providers.rst`
— first `isaaclab_ovphysx` section in the API reference.
-
`source/isaaclab/changelog.d/antoiner-feat-ovphysx-scene-data-provider.rst`
— patch fragment for the factory fix.
-
`source/isaaclab_ovphysx/changelog.d/antoiner-feat-ovphysx-scene-data-provider.minor.rst`
— minor fragment for the new provider.

## Type of change

- New feature (non-breaking change which adds functionality).
- Bug fix (factory dispatch).

## Validation

1. **Unit tests** — `./isaaclab.sh -p -m pytest
source/isaaclab/test/sim/test_scene_data_provider_factory.py
source/isaaclab_ovphysx/test/scene_data_providers/` → 23 passed.
2. **End-to-end** — `./isaaclab.sh -p
scripts/reinforcement_learning/rsl_rl/train.py
--task=Isaac-Cartpole-Direct-v0 --visualizer newton --num_envs 4096
presets=ovphysx` reaches the simulation loop; visualizer renders the
cartpole scene.
3. **Pre-commit** — `./isaaclab.sh -f` clean.
4. **Pattern probe** — standalone reproduction at
`/tmp/probe_rigid_body_pose_on_articulation.py` confirmed
`RIGID_BODY_POSE` accepts articulation-link patterns. Reproducible
against the wheel's bundled minimal articulation USD.

## Known follow-ups (not in this PR)

- Extract `_build_newton_model_from_clone_plan` into a shared helper
used by both PhysX and OVPhysX providers (currently a verbatim copy).
- `FrameView` factory could grow an explicit `"ovphysx"` registration
entry (today's dispatch falls through to the `"physx"` branch, which
works because `FabricFrameView` reads from the shared USD stage).
- Pattern length cap: if a scene's distinct-body-path count exceeds the
wheel's per-pattern matcher limit (unknown), chunk into multiple
bindings indexed by buffer-row offset. Hasn't bitten yet.
- A future `test_ovphysx_scene_data_provider_visualizer_contract.py`
mirroring `test_physx_scene_data_provider_visualizer_contract.py`.

## Checklist

- [x] I have read and understood the [contribution
guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html)
- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my feature works
- [x] I have added a changelog fragment under
`source/<pkg>/changelog.d/` for every touched package
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there
AntoineRichard added a commit to AntoineRichard/IsaacLab that referenced this pull request May 19, 2026
Drop AppLauncher and the PhysX-style setup_simulation fixture in favour
of the kitless _ovphysx_sim_context helper (matches test_rigid_object.py
shipped in isaac-sim#5459) and the autouse device-lock fixture for ovphysx<=0.3.7.

Also drop the disable_contact_processing parametrize axis: toggling the
global Carbonite '/physics/disableContactProcessing' flag requires the
settings manager which is not available in kitless mode.  test_no_contact_reporting
now exercises the same semantic via a no-filter cfg instead.

The three v2-deferred tests (track_friction_forces / track_contact_points)
keep their pytest.mark.skip but their bodies are fully kitless so they
can be un-skipped once the ovphysx wheel ships the missing per-sensor
read APIs (see docs/superpowers/specs/2026-04-27-ovphysx-contact-api-gaps.md).

Run via:
    ./scripts/run_ovphysx.sh -m pytest source/isaaclab_ovphysx/test/sensors/test_contact_sensor.py -v
AntoineRichard added a commit that referenced this pull request May 19, 2026
# Description

Implements `RigidObjectCollection` and `RigidObjectCollectionData` for
the OVPhysX backend, completing the rigid-body asset surface alongside
`RigidObject` (#5426) and `Articulation` (#5459). 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=[glob_0, …,
glob_{B-1}])` 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
`(body_0_env_0, body_0_env_1, …, body_1_env_0, …)`. The data class and
asset writers use strided-view reshape helpers
(`_reshape_view_to_data_2d/_3d` and `reshape_data_to_view_2d/_3d`,
ported from the PhysX collection) to convert between body-major view
layout and the instance-major `(N, B, D)` layout exposed to users — no
Warp kernels added, no per-body Python fan-out at runtime.

Fixes #5317

**Stacked on:**
- #5459 — OVPhysX `Articulation`
- #5426 — OVPhysX `RigidObject`

**Carries a one-commit cherry-pick of #5545's `ovphysx_manager.py`
portion** (required for ovphysx 0.4 `active_cuda_gpus` API). Drop the
cherry-pick once #5545 lands.

## Type of change

- New feature (non-breaking change which adds functionality)

## Screenshots

N/A — backend infrastructure, no visible behaviour change.

## Checklist

- [x] I have read and understood the [contribution
guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html)
- [x] I have run the [pre-commit checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] I have updated the changelog (fragment file under
`source/isaaclab_ovphysx/changelog.d/`)
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there

## Test plan

Tested in a Docker container built from `docker/Dockerfile.base`
(IsaacSim `6.0.0-dev2`) with the ovphysx 0.4.3 wheel installed:

- `./scripts/run_ovphysx.sh -m pytest
source/isaaclab/test/assets/test_rigid_object_collection_iface.py -k
ovphysx` → **636 passed**
- `./scripts/run_ovphysx.sh -m pytest
source/isaaclab_ovphysx/test/assets/test_rigid_object_collection.py` →
**72 passed, 76 skipped (device-mode lock — a second invocation with `-k
'cpu'` covers CPU), 4 xfailed** (material-properties gap shared with
`RigidObject` until the wheel exposes `RIGID_BODY_MATERIAL`)
- `./isaaclab.sh -f` → clean
AntoineRichard added a commit that referenced this pull request May 19, 2026
…5637)

# Description

Restructures the physics-backend documentation to give each backend a
first-class home and to add the cross-backend orientation pages asked
for in
[isaac-sim/IsaacLab-Internal#876](isaac-sim/IsaacLab-Internal#876).

**Structure**

```
docs/source/overview/core-concepts/physical-backends/
├── index.rst                 ← user-facing hub + feature-support matrix
├── solver-comparison.rst     ← cross-backend behavioural differences
├── physx/
│   ├── index.rst
│   ├── installation.rst
│   ├── configuration.rst     ← PhysxCfg tuning knobs
│   └── supported-features.rst
├── newton/
│   ├── index.rst
│   ├── installation.rst
│   ├── supported-features.rst   (was experimental-features/.../limitations-and-known-bugs.rst)
│   ├── mjwarp-solver.rst        (was experimental-features/.../solver-transitioning.rst)
│   └── kamino-solver.rst        (was experimental-features/.../using-kamino.rst)
└── ovphysx/
    └── index.rst             ← stub flagged as highly experimental; tracking issue #5634
```

**Highlights**

- The Newton subdir moves wholesale out of `experimental-features/` via
`git mv`; framing changes from "experimental feature branch" to "beta
backend." The Experimental Features toctree now contains only
`bleeding-edge`.
- Per-solver Newton pages (`mjwarp-solver`, `kamino-solver`) replace the
old `solver-transitioning` / `using-kamino` files, matching #876's
"sub-sections for the Newton solvers" ask.
- New PhysX page set written from `isaaclab_physx.physics.PhysxCfg`,
mirroring the Newton structure.
- OvPhysX stub references the full in-flight PR set (#5421, #5422,
#5426, #5459, #5570, #5589) and is gated by a follow-up issue (#5634)
for expansion after those land.
- `solver-comparison.rst` covers friction, contact pipeline,
restitution, stabilization, convergence, articulation coordinates,
substepping, and GPU buffers across PhysX TGS, Newton MJWarp, and Newton
Kamino. Each cell points at the concrete config attribute that controls
the behavior, with a porting checklist at the end.
- Newton's `supported-features.rst` list refreshed against `develop`'s
actual `newton_mjwarp` coverage (adds Shadow Hand, Shadow Hand Over,
cabinet, dexsuite, rough-terrain locomotion). Replaces the bit-rotting
bullet list with a discovery recipe (`grep -rln newton_mjwarp
source/isaaclab_tasks/`).
- Cross-references updated in `docs/index.rst`,
`core-concepts/index.rst`, `multi_backend_architecture.rst`,
`features/visualization.rst`, and
`overview/reinforcement-learning/rl_existing_scripts.rst`.

Fixes isaac-sim/IsaacLab-Internal#876

## Type of change

- Documentation update

## Screenshots

N/A — docs-only restructure. New page tree visible from the
table-of-contents in `core-concepts/`.

## Checklist

- [x] I have read and understood the [contribution
guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html)
- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works *(N/A — docs-only)*
- [ ] I have added a changelog fragment under
`source/<pkg>/changelog.d/` for every touched package *(N/A — docs-only;
matches precedent of #5512 and 4aeb4d6 which shipped without
fragments)*
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there
AntoineRichard added a commit that referenced this pull request May 19, 2026
# Description

Implements `ContactSensor`, `ContactSensorCfg`, and `ContactSensorData`
for the `isaaclab_ovphysx` backend, mirroring the existing PhysX
implementation.

The contact sensor reports normal contact forces in the world frame
using the ovphysx 0.3.7 `ContactBinding` API
(`PhysX.create_contact_binding` / `read_net_forces` /
`read_force_matrix`). Optional pose tracking is wired through a
`RIGID_BODY_POSE` `TensorBinding`.

Validation environment: `Isaac-Velocity-Flat-Anymal-C-Direct-v0`
(Anymal-C foot-contact tracking for locomotion).

**v1 scope (this PR):**
- Net contact forces + history
- Force matrix (filtered partner forces) + history
- Pose tracking (`track_pose`)
- Air/contact time tracking + `compute_first_contact` /
`compute_first_air`
- Reset semantics + native handle teardown on simulation stop

**Deferred (raise `NotImplementedError` if cfg flag enabled):**
- `track_contact_points`
- `track_friction_forces`

These features are blocked on tensor-friendly per-sensor read APIs in
ovphysx (`ContactBinding.read_contact_points` / `read_friction_forces`).
A maintainer-facing spec listing the missing APIs is attached at
`docs/superpowers/specs/2026-04-27-ovphysx-contact-api-gaps.md`. The
data properties still return `None` per the `BaseContactSensorData`
contract; the implementation only raises if the cfg flag is set.

Fixes #5325
Parent issue: #5315

**Stacked on:**
- #5459 (`[OVPHYSX] Articulation rewrite`) — this branch is rebased on
top of `antoiner/feat/ovphysx_articulation`. Reviewers can focus on the
last 13 commits (everything from `Scaffold isaaclab_ovphysx sensors
sub-package` onward); commits before that are inherited from #5459. Will
be re-rebased on `develop` once #5459 merges.

**Dependencies:**
- Requires #4852 (merged into `develop` 2026-04-20).
- ovphysx `RigidObject` lands as part of #5459 — no separate gate.

## Type of change

- New feature (non-breaking change which adds functionality)

## Screenshots

N/A — backend feature, no UI surface.

## Checklist

- [x] I have read and understood the [contribution
guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html)
- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation (changelog
fragment at
`source/isaaclab_ovphysx/changelog.d/antoiner-feat-ovphysx_contactsensor.minor.rst`,
docstrings, gap spec for the maintainer of ovphysx)
- [x] My changes generate no new warnings
- [x] I have added tests that prove my feature works (9 tests adapted
from the PhysX backend; 3 skipped for the deferred features so they're
trivially un-skip-able once ovphysx ships the missing APIs)
- [x] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file (changelog fragment, minor tier
— version bump compiled by the nightly CI workflow per the upstream
convention)
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there

## Notes for the reviewer

- The Warp kernels (`sensors/contact_sensor/kernels.py` and the shared
`sensors/kernels.py`) are physics-engine-agnostic and ported verbatim
from the PhysX backend — only module docstrings differ.
- `ContactSensorCfg` and `ContactSensorData` are also near-verbatim
mirrors; the only edits are the backend-specific `class_type` pointer
and the shared-kernels import path.
- The ovphysx-specific glue lives in `_initialize_impl` (USD prim
discovery + regex→fnmatch glob conversion + `create_contact_binding` +
optional `RIGID_BODY_POSE` binding), `_create_buffers` (pre-allocated
DLPack read buffers), `_update_buffers_impl` (per-step reads), and
`_invalidate_initialize_callback` (native handle release).
- **Tests not yet executed.** They are currently authored in the
Isaac-Sim/`AppLauncher` style (mirroring
`isaaclab_physx/test/sensors/test_contact_sensor.py`); they need to be
re-adapted to the kitless `./scripts/run_ovphysx.sh -m pytest` flow and
the `_ovphysx_sim_context` helper used by the articulation/rigid-object
suites in #5459. Will land as a follow-up commit on this branch once
that adaptation is reviewed.
@AntoineRichard AntoineRichard mentioned this pull request May 20, 2026
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation infrastructure isaac-lab Related to Isaac Lab team

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants