Skip to content

Migrate OVPhysX Articulation onto OvPhysxView (view series, part 2)#6225

Open
AntoineRichard wants to merge 16 commits into
isaac-sim:developfrom
AntoineRichard:antoiner/feat/ovphysx_articulation_view
Open

Migrate OVPhysX Articulation onto OvPhysxView (view series, part 2)#6225
AntoineRichard wants to merge 16 commits into
isaac-sim:developfrom
AntoineRichard:antoiner/feat/ovphysx_articulation_view

Conversation

@AntoineRichard

@AntoineRichard AntoineRichard commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

🔗 Stacked on top of #6224 (Part 1 — OvPhysxView)

This PR depends on #6224 and is stacked on top of it. It targets
develop, but because #6224 has not merged yet, the diff below includes the
Part 1 OvPhysxView commits as well as the Part 2 migration. Please
review #6224 first.
Once #6224 merges, this branch will be rebased onto
develop and the diff will shrink to the articulation migration only.

Description

Part 2 of the OVPhysX view-migration series. It migrates the OVPhysX
Articulation onto the new OvPhysxView binding manager introduced in
#6224 (Part 1).

The goal of the series is to dogfood OvPhysxView on the real asset classes
to surface weaknesses before the rigid-object/collection/sensor migrations, and
to consolidate the scattered create_tensor_binding / binding.read /
binding.write calls behind one managed surface.

What changed

  • Articulation._initialize_impl now builds a single
    OvPhysxView(physx, pattern, device) and populates it via try_binding_for
    (best-effort: tensor types that do not apply to these prims are skipped, the
    asset still raises if no binding could be created). All binding creation,
    caching, and the CPU/GPU device policy now live in the view.
  • ArticulationData is constructed from the view; its counts come from the
    view metadata, and both _get_binding helpers (asset + data container)
    delegate to OvPhysxView.try_binding_for.
  • Every write now goes through root_view.set_attribute(...) instead of
    calling binding.write on a binding obtained from the view — the ~70 property
    / state / target / wrench / tendon writes, including the per-step actuation
    path. This brings the view's read-only, device, dtype, and contiguity guards
    to the asset write path, which previously bypassed them.
  • Every read now goes through root_view.read_into(...). ArticulationData
    drops its bespoke _get_read_view reinterpret cache; the view now caches the
    float32 reinterpret per destination buffer (keeping the wheel's read cache
    warm) and derives the structured layout from the binding shape, so the
    transform / spatial-vector / scalar read helpers collapse to one
    implementation. CPU-only reads keep their pinned-host staging.
  • Articulation.root_view now returns the OvPhysxView instead of a raw
    dict[TensorType, TensorBinding] (breaking — see migration note below).
  • The articulation tests route through the new API
    (root_view.get_attribute(...), root_view.try_binding_for(...)).

Breaking change / migration

Articulation.root_view returns an OvPhysxView, not a dict:

  • root_view[tensor_type]root_view.try_binding_for(tensor_type)
  • tensor_type in root_view (availability) → root_view.try_binding_for(tensor_type) is not None
  • full-tensor read → root_view.get_attribute(tensor_type)

Findings surfaced (the point of the exercise)

  • CPU-only property writes work through set_attribute. mass/COM/inertia/DOF
    limits/stiffness/… are CPU-resident even on a GPU sim, and set_attribute
    refuses to stage CPU↔GPU. This is fine because the asset already pre-stages
    those to pinned CPU buffers, so the source is on the binding's native (CPU)
    device and the no-stage policy is satisfied with no change. (An initial
    assumption that the device policy would block these turned out to be wrong —
    verified by the green cuda:0 run.)
  • read_into allocates a fresh float32 reinterpret view per call, which would
    defeat the wheel binding's internal read cache that the data container caches
    by (tensor_type, ptr); hot-path reads were kept on the cached-view path.
  • Per-step writes re-resolve the binding through set_attribute (a dict lookup
    • cheap guards) rather than the previously cached binding handle; the cost is
      negligible because the pre-built float32 write-view passes through the
      reinterpret unchanged. A "bound writer" handle on the view would remove even
      that lookup — a possible Part 1 follow-up.
  • The view rejects 0-count bindings (stricter than the old loop, which could
    store a phantom binding) — a correctness improvement.

Type of change

  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Bug fix (non-breaking change which fixes an issue)
  • This change requires a documentation update

Checklist

  • I have run the pre-commit checks with ./isaaclab.sh --format
  • I have added tests that prove my fix is effective or that my feature works (articulation tests route through the new API)
  • I have updated the changelog (source/isaaclab_ovphysx/changelog.d/)
  • I have updated the documentation accordingly
  • I have added my changes to the right changelog.d fragment(s)

Testing

Run kitless; cpu and cuda must be separate invocations (process-global device lock):

101 passed, 4 xfailed   # -k cpu
101 passed, 4 xfailed   # -k cuda

OVPhysX exposes physics attributes as a loose dict of TensorType ->
TensorBinding with no view object, unlike Newton's
selection.ArticulationView and PhysX's typed tensor views. OvPhysxView
wraps the bindings for one prim pattern behind a string-keyed
get_attribute / set_attribute surface, addressing attributes by the
lowercased TensorType enum name (e.g. "articulation_dof_stiffness").
It needs no Model/State/Control source object because the TensorType
already implies where the data lives.

Prototype per docs/superpowers/specs/2026-06-17-ovphysx-view-design.md.
Adds unit tests covering name<->enum resolution, the read-only guard,
discoverability, and get/set dispatch against a fake binding (no native
simulation required).
Reworks the view from a convenience wrapper into a layer that can back the
OVPhysX asset/data classes, per the PR review of isaac-sim#6224:

- read_into(name, dst): zero-copy fill of a caller-owned, possibly
  structured-dtype buffer (e.g. wp.transformf) via a float32 reinterpret
  view -- the mechanism the data containers use today.
- set_attribute: accepts structured-dtype sources via the same reinterpret;
  non-float32-width buffers are rejected rather than silently bit-cast.
- prim_paths + key_aliases: support the fused multi-prim binding form
  (create_tensor_binding(prim_paths=[...])) and storing a binding under a
  different TensorType key, as RigidObjectCollection needs.
- binding_for(): raw TensorBinding accessor for adoption.
- _CPU_ONLY_NAMES is now derived from tensor_types._CPU_ONLY_TYPES (no drift).
- Added joint/tendon/is_fixed_base metadata passthrough; eager construction
  raises if it creates zero bindings; get_attribute allocates a fresh buffer
  (no aliasing); nested error hierarchy; PhysX/binding Protocols.

Device policy: no implicit CPU<->GPU conversion. CPU-resident property types
are read/written on CPU; a buffer on the wrong device raises DeviceMismatch
instead of being staged. Device-less host data (numpy/list) is materialized
on the binding's native device.
The OvPhysxView addition is a significant new public surface for the OVPhysX
backend, so promote the changelog fragment from a minor to a major bump.
Reword the entry to describe the binding-manager surface (read_into, the
no-device-conversion policy) and drop the internal design-note path from the
user-facing changelog.
From the second PR review of isaac-sim#6224:

- Critical: _as_binding_view now requires a float32 scalar dtype before the
  zero-copy reinterpret. A same-byte-width wrong dtype (int32) previously passed
  the count-only guard and was bit-reinterpreted into garbage on the write path;
  sub-4-byte dtypes (float16) produced a misleading "0 elements" error. Both are
  now rejected with a clear message. Regression tests added (verified failing
  without the guard).
- _resolve enforces the str | TensorType union and raises UnknownAttribute on
  anything else, instead of letting a bogus key reach the wheel.
- _binding accesses binding.count directly (a malformed binding surfaces instead
  of being masked as a phantom no-match) and surfaces the underlying
  create_tensor_binding exception in the AttributeUnavailable message.
- Added docstrings to the six metadata properties; dropped the unused
  runtime_checkable decorator.
- Tests: same-byte/sub-4-byte dtype rejection, get_attribute(out=) wrong device,
  both indices+mask forwarded, read/write through a prim_paths+key_aliases view,
  non-str/non-TensorType key, and a read-only-names-are-valid-vocabulary check.
From the API-hardening review of isaac-sim#6224. Validate at the boundary and fail loud
instead of silently corrupting, mis-binding, or no-op'ing:

- Reject non-contiguous buffers in _as_binding_view (a strided/sliced source would
  be reinterpreted as contiguous and read/write the wrong memory).
- Canonicalize the device (wp.get_device) so a "cuda" view accepts a "cuda:0"
  buffer instead of raising a spurious, unsatisfiable DeviceMismatch; falls back
  to the raw string when the device can't be resolved locally.
- Reject TensorType.INVALID via the member path too (string path already did).
- Normalize key_aliases to TensorType members so string keys are honored rather
  than silently dropped, and reject aliases that cross the CPU/GPU residency or
  read-only boundary (the device/read-only guards key on the requested type).
- Reject empty pattern/prim_paths and tensor_types-without-eager at construction.
- Eager construction with an explicit tensor_types list now surfaces a failing
  type instead of swallowing it at debug level (default sweep still skips
  inapplicable types).
- Document binding_for as an unguarded escape hatch, get_attribute's native-device
  return, and the has_attribute name-validity-vs-availability split.

Adds regression tests for each (contiguity and INVALID verified failing without
the guard).
Surfaced by dogfooding the view in the articulation migration: the assets branch
on a "binding or None" pattern for optional/absent bindings (tendon types on a
tendon-less articulation, not-yet-created bindings), which the raising binding_for
can't express. try_binding_for returns None when the attribute is valid but not
available for the view's prims, while still raising UnknownAttribute for an invalid
name (a programming error, not an availability question).
…xView

read_into now reuses the float32 reinterpret of a destination buffer
across calls (keyed by buffer id, with a pointer-staleness guard), so the
wheel's object-identity read cache stays warm even when callers hand a
structured buffer each step -- they no longer need to maintain their own
reinterpret cache. get_attribute returns a typed array for attributes with
a known structured layout (transformf for poses, spatial_vectorf for
velocities, via a hand-maintained _ATTR_DTYPE map) and flat float32
otherwise.

This lets the asset data containers drop their bespoke _get_read_view
caching and read structured buffers straight through the view.
OvPhysxView (and OvPhysxFrameView) live in isaaclab_ovphysx.sim.views,
which had no API-docs page. Add the automodule stub and wire it into the
isaaclab_ovphysx autosummary so the new binding-manager view shows up in
the rendered API reference alongside assets / cloner / physics.
Three fixes from the Part 1 review:

- get_attribute (no out): route the freshly allocated buffer through
  _as_binding_view directly instead of the id()-keyed read cache. A fresh
  buffer can never hit the cache and would leak one entry (keeping the
  buffer alive) per call in a step loop; the cache only pays off for a
  reused out/dst buffer. Add a regression test asserting the no-out path
  leaves _read_views empty.

- Raise a dedicated DtypeMismatch instead of ShapeMismatch when a buffer's
  scalar element type is not float32, so a dtype error no longer reads as a
  dimensions error. Update the affected tests and the Raises docstrings.

- Make the view Warp-native: drop the fragile __module__ string-match that
  auto-converted Torch tensors on writes. Callers bridge a Torch tensor
  with wp.from_torch(t), keeping the device policy explicit and avoiding an
  optional Torch dependency.
@AntoineRichard AntoineRichard force-pushed the antoiner/feat/ovphysx_articulation_view branch from bf1fc85 to cff7ac3 Compare June 23, 2026 08:15
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Jun 23, 2026
Documentation/comment clarifications from the isaac-sim#6224 review (no behavior change):

- Narrow the documented contract to float32-only: attribute_names/has_attribute
  and the module docstring now state that a listed name is name-validity, not a
  dtype-support promise; non-float dtype handling awaits wheel dtype metadata.
- Mark _READ_ONLY_NAMES explicitly temporary; name the three access modes
  (read/write, read-only, write-only) and the wheel access_mode enum that should
  replace the table.
- Document key_aliases as an internal collection adapter, not general public API,
  pending descriptor metadata.
- Make the view test scope explicit: mock API mechanics here; live
  CPU-only-on-GPU / read-only+write-only / structured read_into coverage lives in
  the asset-integration tests.
Migrate the OVPhysX Articulation onto the OvPhysxView binding manager
(view migration series, part 2; rebased onto develop). _initialize_impl
builds one OvPhysxView and populates it via try_binding_for; the data
container is built from the view; both _get_binding helpers delegate to it.
All reads route through read_into (cached reinterpret, structured buffers
handled off the binding shape -- the transform/spatial/scalar read helpers
collapse to one) and all writes through set_attribute (including CPU-only
properties, pre-staged to pinned CPU). root_view now returns the OvPhysxView
(breaking). Tests route through the new API.

Verified on both devices (cpu and cuda:0: 99 passed, 4 xfailed each).
…View

The migration changed Articulation._process_tendons to read tendon counts
off self._root_view (the OvPhysxView) instead of self._bindings, but the
helpers unit test still mock-injected _bindings -- so the test broke (caught
by the isaaclab_ov CI job, which runs the full suite). Wrap the mock
binding set in a real OvPhysxView over a fake PhysX and inject _root_view
instead. test_articulation_helpers.py: 2 passed.
The articulation-helpers tendon test wrapped the mock bindings in a real
OvPhysxView over a fake PhysX to satisfy the migrated Articulation, which
defeats the purpose of the mock binding layer.

Add a MockOvPhysxView that mirrors the consumed OvPhysxView surface
(binding_for/try_binding_for, get_attribute/read_into/set_attribute, the
discoverability helpers, and the metadata passthrough) over the mock
bindings, exposed via MockOvPhysxBindingSet.view. Inject that as the
articulation's _root_view so the test stays within the mock framework.
@AntoineRichard AntoineRichard force-pushed the antoiner/feat/ovphysx_articulation_view branch from cff7ac3 to 6268f9a Compare June 23, 2026 10:01
AntoineRichard added a commit that referenced this pull request Jun 25, 2026
…ndings (view series, part 1) (#6224)

> **📦 Part 1 of a 5-part series** adding an OvPhysX binding view and
migrating the assets onto it:
> 1. **This PR** — add `OvPhysxView` (the API) + tests.
> 2. Migrate the OvPhysX **Articulation** to `root_view`.
> 3. Migrate the **RigidObject**.
> 4. Migrate the **RigidObjectCollection** (uses the fused
`prim_paths`+`key_aliases` path).
> 5. Migrate the **sensors** (pose tracking; contact forces use a
separate `ContactBinding` API and may not fully adopt the view).
>
> Parts 2–5 each depend only on this PR and can be reviewed
independently.

---

# Description

Adds **`OvPhysxView`**, a string-keyed binding-management layer over the
OVPhysX tensor bindings, in `isaaclab_ovphysx`.

**Why.** OVPhysX is the odd backend out: it exposes physics attributes
as a loose `dict[TensorType, TensorBinding]` with **no view object**,
whereas Newton has `selection.ArticulationView` and PhysX has typed
tensor views. Today callers must hold the right `TensorType` enum
member, manage a destination buffer, and create bindings against a USD
glob — and the `create_tensor_binding` calls are scattered across the
asset classes behind a private `_get_binding`. `OvPhysxView` gives
OVPhysX a single, discoverable surface as pleasant as Newton's selection
API, and one owner of binding creation/caching that the asset/data
classes can delegate to. It is the narrow, independently-useful slice
carved out of the (shelved) cross-backend `get_property`/`set_property`
effort; the full rationale and decision log live in
`docs/superpowers/specs/2026-06-17-ovphysx-view-design.md`.

**What it provides**
- String-keyed access by lowercased `TensorType` name (auto-derived from
the wheel enum — no hand-maintained table); a `TensorType` member is
also accepted.
- `get_attribute(name, out=)` — fresh-allocates a **typed** array
(`wp.transformf` for poses, `wp.spatial_vectorf` for velocities, flat
`float32` otherwise; via a maintained `_ATTR_DTYPE` map), no aliasing;
**`read_into(name, dst)`** — zero-copy fill of a caller-owned, possibly
structured-dtype buffer (`wp.transformf`, …) via a `float32` reinterpret
view that is **cached per destination buffer** so the wheel's
object-identity read cache stays warm even when callers hand a
structured buffer each step; `set_attribute(name, values,
indices=/mask=)` (structured sources reinterpreted); and a raw
`binding_for(name)` accessor for adoption.
- The fused multi-prim form (`prim_paths=[...]` + `key_aliases`) so
`RigidObjectCollection` can use it.
- Discoverability (`attribute_names`, `available_attributes`,
`has_attribute`), metadata passthrough, and a nested error hierarchy
(`UnknownAttribute` / `ReadOnlyAttribute` / `AttributeUnavailable` /
`ShapeMismatch` / `DeviceMismatch`).

**Device policy:** no implicit CPU↔GPU conversion — CPU-resident
property types are read/written on CPU, and a buffer on the wrong device
raises `DeviceMismatch` rather than being staged.

**Scope / status:** this PR adds the view plus 48 mock-based unit tests
(no live sim). The asset adoption that replaces
`_get_binding`/`_binding_read`/`_binding_write` lives in the follow-up
PRs (Articulation #6225, RigidObject #6226), which dogfood the view
against a live sim on CPU and GPU and have fed fixes back here (e.g.
`try_binding_for`, the read-view cache, typed `get_attribute`).

Fixes # — N/A (internal design tracking; surviving slice of the shelved
cross-backend view effort).

## Type of change

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

## Screenshots

N/A — no user-facing visual 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`
- [ ] I have made corresponding changes to the documentation <!-- design
note updated separately
(docs/superpowers/specs/2026-06-17-ovphysx-view-design.md); no rendered
API-doc change in this PR yet -->
- [x] My changes generate no new warnings
- [x] I have added tests that prove my feature works
(`test/sim/test_ovphysx_view.py`, 48 tests)
- [x] I have added a changelog fragment under
`source/<pkg>/changelog.d/` for every touched package (do **not** edit
`CHANGELOG.rst` or bump `extension.toml` — CI handles that)
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there
@AntoineRichard AntoineRichard marked this pull request as ready for review June 25, 2026 11:17
@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR migrates the OVPhysX Articulation and ArticulationData classes from a raw dict[TensorType, TensorBinding] to the OvPhysxView binding manager introduced in Part 1 (#6224). Every read and write — roughly 70 property/state/target/tendon paths — now flows through root_view.read_into / root_view.set_attribute, bringing the view's device, dtype, and contiguity guards to all write paths.

  • Articulation: _bindings dict replaced by _root_view: OvPhysxView; bindings created eagerly via try_binding_for during _initialize_impl; all write calls migrated from binding.write(...) to root_view.set_attribute(...); root_view property now returns OvPhysxView (breaking change, documented in changelog and migration note).
  • ArticulationData: Accepts OvPhysxView instead of raw bindings dict; drops the bespoke _get_read_view reinterpret cache and three specialized read helpers, replacing them with a single unified _read_binding_into_buf; CPU-only staging path unchanged.
  • Mock and tests: MockOvPhysxView added to mirror the consumed OvPhysxView surface for kitless unit tests; all initialization and binding-shape invariant checks in test_articulation.py updated to use the new API.

Confidence Score: 4/5

The production code paths are mechanically correct and well-guarded; the real-backend test suite passes cleanly. The only open items are in the new MockOvPhysxView test double, where a structured-dtype mismatch in read_into and a StopIteration edge case on empty bindings are latent gaps that do not affect current tests but will surface when mock-based tests exercise transform or velocity reads.

The core migration is thorough and consistent — every write and read path has been updated, the per-step guards are semantically equivalent to the old binding-existence checks, and the CPU staging path is preserved intact. The three retained binding instance attributes are unused after init but harmless. The mock surface gaps are real but confined to test infrastructure and do not affect simulation correctness.

mock_ovphysx_bindings.py — MockOvPhysxView.read_into and _resolve have latent correctness gaps that will surface when mock-based tests exercise transform/velocity reads.

Important Files Changed

Filename Overview
source/isaaclab_ovphysx/isaaclab_ovphysx/assets/articulation/articulation.py Core migration: replaces _bindings dict with OvPhysxView; all ~70 property/state writes now route through set_attribute and all reads through read_into/_binding_read. Correctness logic is sound and well-guarded; minor cleanup gap with retained _effort_binding / _pos_target_binding / _vel_target_binding attributes.
source/isaaclab_ovphysx/isaaclab_ovphysx/assets/articulation/articulation_data.py Migrated to accept OvPhysxView instead of raw bindings dict; removed _get_read_view reinterpret cache and three specialized read helpers, replacing them with a single _read_binding_into_buf aliased to all three names. CPU staging path for structured dtypes now derived from staging.shape rather than binding.shape directly — equivalent and correct.
source/isaaclab_ovphysx/isaaclab_ovphysx/test/mock_interfaces/views/mock_ovphysx_bindings.py New MockOvPhysxView mirrors the OvPhysxView surface for unit tests; two latent issues: _resolve throws StopIteration on empty bindings, and read_into passes raw structured-dtype arrays to MockTensorBinding.read which will fail in future mock-based tests that read transform/velocity bindings.
source/isaaclab_ovphysx/test/assets/test_articulation.py Correctly migrated to use root_view.try_binding_for / root_view.get_attribute API instead of dict indexing; removed unused numpy import; all binding-shape invariant checks updated throughout.
source/isaaclab_ovphysx/test/assets/test_articulation_helpers.py Single-line change: injects bindings.view (MockOvPhysxView) instead of bindings.bindings (raw dict) into _root_view; correctly adapts the helper shell for the migrated Articulation API.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Sim as Simulation Step
    participant Art as Articulation
    participant View as OvPhysxView
    participant Bind as TensorBinding
    participant Data as ArticulationData

    Note over Art,View: _initialize_impl
    Art->>View: OvPhysxView(physx, pattern, device)
    loop eager_types
        Art->>View: try_binding_for(tt)
        View->>Bind: create_tensor_binding(pattern, tt)
        View-->>Art: binding (cached)
    end
    Art->>Data: ArticulationData(root_view, device)

    Note over Art,Bind: write_data_to_sim (per-step)
    Sim->>Art: write_data_to_sim()
    Art->>View: set_attribute(DOF_ACTUATION_FORCE, effort_view)
    View->>Bind: binding.write(values, indices, mask)
    Art->>View: set_attribute(DOF_POSITION_TARGET, pos_view)
    View->>Bind: binding.write(values, indices, mask)

    Note over Data,Bind: lazy state read (property access)
    Sim->>Data: joint_pos (property)
    Data->>Data: _read_binding_into_buf(DOF_POSITION, buf)
    Data->>View: read_into(DOF_POSITION, buf.data)
    View->>Bind: binding.read(float32_view)
    Data-->>Sim: cached tensor
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Sim as Simulation Step
    participant Art as Articulation
    participant View as OvPhysxView
    participant Bind as TensorBinding
    participant Data as ArticulationData

    Note over Art,View: _initialize_impl
    Art->>View: OvPhysxView(physx, pattern, device)
    loop eager_types
        Art->>View: try_binding_for(tt)
        View->>Bind: create_tensor_binding(pattern, tt)
        View-->>Art: binding (cached)
    end
    Art->>Data: ArticulationData(root_view, device)

    Note over Art,Bind: write_data_to_sim (per-step)
    Sim->>Art: write_data_to_sim()
    Art->>View: set_attribute(DOF_ACTUATION_FORCE, effort_view)
    View->>Bind: binding.write(values, indices, mask)
    Art->>View: set_attribute(DOF_POSITION_TARGET, pos_view)
    View->>Bind: binding.write(values, indices, mask)

    Note over Data,Bind: lazy state read (property access)
    Sim->>Data: joint_pos (property)
    Data->>Data: _read_binding_into_buf(DOF_POSITION, buf)
    Data->>View: read_into(DOF_POSITION, buf.data)
    View->>Bind: binding.read(float32_view)
    Data-->>Sim: cached tensor
Loading

Comments Outside Diff (3)

  1. source/isaaclab_ovphysx/isaaclab_ovphysx/assets/articulation/articulation.py, line 3671-3697 (link)

    P2 Binding references retained post-migration but no longer used for writes

    self._effort_binding, self._pos_target_binding, and self._vel_target_binding are each stored as instance attributes during _initialize_impl but are now used only to derive the write-view shape. All three writes go through root_view.set_attribute using the per-step guards on the corresponding *_write_view. The stored binding references keep live objects alive on self without serving a runtime purpose — using local variables during init and not assigning them to self would complete the cleanup the PR sets out to do.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

  2. source/isaaclab_ovphysx/isaaclab_ovphysx/test/mock_interfaces/views/mock_ovphysx_bindings.py, line 1333-1342 (link)

    P2 _resolve throws StopIteration on empty bindings dict

    next(iter(self._bindings)) raises StopIteration (not KeyError or a readable error) when _bindings is empty and any string name is passed to a method that calls _resolve. In practice this cannot occur because MockOvPhysxView is always constructed via MockOvPhysxBindingSet.view with a non-empty dict, but if a caller constructs a bare MockOvPhysxView({}) for an edge-case test, the failure will be cryptic. Guarding with if not self._bindings: raise KeyError(...) before the next(iter(...)) call would give a clearer diagnostic.

  3. source/isaaclab_ovphysx/isaaclab_ovphysx/test/mock_interfaces/views/mock_ovphysx_bindings.py, line 1361-1363 (link)

    P2 read_into passes structured dtypes directly to MockTensorBinding.read, causing a shape/dtype mismatch for transform and spatial-vector bindings

    MockTensorBinding.read does wp.copy(tensor, tmp) where tmp is always a flat float32 array of shape binding.shape (e.g. (N, 7)) and tensor is whatever dst the caller passes. The production _binding_read path now sends the raw wp.transformf buffer (shape (N, 1)) directly to read_into; the old _read_transform_binding always passed a pre-built float32 reinterpret view of matching shape. Warp rejects a wp.copy between arrays whose element counts differ even when the total byte size matches, so any future mock-based test that reads a transform or spatial-vector binding (e.g. ROOT_POSE, LINK_POSE, ROOT_VELOCITY) through _binding_read will fail. The real view handles the reinterpret internally, so the real-backend tests pass, but the gap is latent in the mock surface.

Reviews (1): Last reviewed commit: "Merge branch 'develop' into antoiner/fea..." | Re-trigger Greptile

Clear self._root_view in _invalidate_initialize_callback so a stale/destroyed
binding cached by the view is not held across a simulation stop; _initialize_impl
rebuilds a fresh OvPhysxView on the next play. Teardown hygiene matching the
joint-wrench sensor.
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 isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant