Migrating off Tensor Bindings#
The tensor-binding API is deprecated since ovphysx 0.6. It is superseded by the session read/write API —
ovphysx_read/ovphysx_writein C,PhysX.read/PhysX.writein Python. Bindings keep working through the deprecation window; new code should use the session API.
This guide maps each tensor-binding capability to its session read/write equivalent. For the full data contract — which attributes exist on each object type, their semantics, units, format and device — refer to the read/write reference; this page is the migration path, not the contract.
What changes across the board#
These differences apply to every capability below; the capability map only repeats a note where a row diverges from them.
A binding becomes a reusable query plus per-call sessions.
query(object_type)returns a selector you can reuse withread/write. Close each session and release the query separately withovphysx_release_queryin C orQuery.close().wait()in Python when finished. Closing a session does not release a caller-owned query. Python’sread_sync/write_syncconveniences create an internal query per call and release it with their session.One packed
TensorTypebecomes an(object type, attribute)pair. A binding’s single packed column splits into per-attribute columns. The Data attribute map below lists the split for the common columns; for example:RIGID_BODY_POSE(N, 7)=>position(N, 3)+orientation(N, 4)RIGID_BODY_VELOCITY(N, 6)=>linearVelocity(N, 3)+angularVelocity(N, 3)
Angular units are per-axis, not per column. An
ARTICULATION_JOINTjoint row holds one value per unlocked reduced-coordinate DOF axis, so the unit ofjointPosition,jointVelocity,jointPositionTarget, andjointVelocityTargetfollows the axis kind, not the column: an angular axis (revolute, or a D6 rotational DOF) is always in degrees, and a prismatic axis stays in the stage’s base length unit (no conversion). The joint’s axis kind fixes this — authoring aJointStateAPIdoes not change it. A binding value on an angular axis must be scaled to degrees, but scaling a prismatic axis by 57.3× is a bug — so convert by the axis kind you authored in USD (no per-row attribute reports it back), not by blanket-converting the column. Refer to ovstage integration for the full per-axis rule. Everything else, including the body / link / rootangularVelocitycolumns, is the engine’s native radians with no conversion — scaling those by 57.3× is likewise a bug.A subset is a prim selection, not a device index/mask.
binding.write(tensor, indices=indices)orbinding.write(tensor, mask=mask)addressed rows by device position. The session API narrows a query by prim identity instead — Pythonquery(object_type, prim_paths=..., pattern=...), Covphysx_query_selection. Selection is host-resolved by prim, so there is no GPU-resident row mask capturable inside a CUDA graph. Pythonprim_pathstakes interned prim handles, not the binding’s path strings. Groups return rows in engine order, not input-selection or binding order; resolve each group’s prim list before mapping values to rows. See Selecting a subset of prims.Writability is queryable at runtime. With bindings, a
TensorType’s read/write capability was a fixed flag. In the session API, which(object type, attribute)pairs are writable is checked at write time:write_sync()raisesTaskError(aRuntimeError) for an unwritable attribute, and a defaultwrite()reports it when its operation is waited. The Writable data reference lists the writable set.Session calls are asynchronous — wait before you use the result. A binding’s
read()/write()handed you data (or applied it) immediately. A sessionquery,read,write,fetch_next,commitorcloseinstead returns a receipt you wait on before its output is valid; in Python the receipt is anOperationyou.wait(). Theread_sync/write_syncvariants do the waiting for you and return a session with its groups already fetched. Fetching past the last group is not an error: the wait succeeds with a null group, which marks the end. A write session can carry several attributes; each fetched group’sattribute_indexnames which one it holds. The calls run inline today, but GPU work still needs its own CUDA event/stream ordering.
Capability map#
Each row maps a tensor-binding API to its session read/write equivalent; the cross-cutting changes above are not repeated here.
Tensor-binding API |
Session read/write equivalent |
|---|---|
|
|
|
|
|
|
|
a selection query ( |
|
resolve each fetched group’s prim-list handle through the query’s path dictionary: |
|
each fetched group’s |
|
read the read-only |
|
read |
|
resolve the prim paths of fetched |
|
resolve the prim paths of fetched |
|
|
|
write the |
|
release the reusable query with |
Data attribute map#
This table maps each tensor-binding TensorType to its session object type and attributes:
Tensor binding |
Session object type |
Session attribute(s) |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This is the common subset. The full attribute vocabulary is the OVPHYSX_ATTR_* macros in
ovphysx_attributes.h (include it alongside ovphysx.h, which does not include it); the macros are
C-only, and a Python caller passes the plain attribute-name string directly, e.g. "position". Which (object type, attribute) pairs are writable is queryable at runtime
through ovphysx_writability (no public Python binding) and listed in the
Writable data reference; the full set of readable columns is in
Readable data.
Using the session API#
A minimal write on DirectGPU (suppressReadback): zero every rigid body’s linear velocity by filling its mapped device
tensor in place. Complete a warmup first because DirectGPU writes require a first simulation step:
import warp as wp
from ovphysx.types import SimObjectType
# `physx` has an attached DirectGPU scene; its mapped tensors are Warp arrays on the GPU.
physx.warmup()
physx.wait_all()
with physx.write_sync(SimObjectType.RIGID_BODY, "linearVelocity") as session:
for group in session.groups:
if not group.tensors:
continue
for tensor in group.tensors:
tensor.zero_() # fill in place on the tensor's native (CUDA) device
device = group.tensors[0].device
with wp.ScopedDevice(device):
session.commit_sync(group, cuda_stream=int(wp.get_stream(device).cuda_stream or 1))
physx.step_sync(1.0 / 60.0)
For the full query → read/write flow — opening a query, fetching its groups, and filling the mapped
buffers on their native device — follow The session model
and Session results; the
ovstage integration guide walks a complete closed-loop read. Runnable
samples: tests/python_samples/session_write.py (CPU write) and tests/python_samples/output_read.py
/ tests/c_samples/output_read_c/ (reads), tests/c_samples/vehicle_read_write_cpp/ (C++ write).
These are source-checkout paths relative to ovphysx/; see the
tensor-bindings tutorial for their installed wheel and SDK locations.