Tensor Bindings: Read and Write Simulation Data#
This tutorial shows how to read and write simulation data through tensor bindings after you attach an ovstage-populated scene. You learn how to use path patterns to bind multiple prims in one call.
Prerequisites#
Complete the Hello World tutorial.
Use a USD scene that contains physics-enabled prims matching your binding pattern.
For the physics concepts behind the quantities these tensors expose — rigid bodies, articulations, joints and drives, deformables — and how to author them in USD, refer to the Simulation Setup pages, starting with Rigid Bodies and Articulations.
Code Language#
Python#
print("Creating tensor binding for DOF velocity targets...")
velocity_target_binding = physx.create_tensor_binding(
pattern="/World/articulation/articulationLink*",
tensor_type=TensorType.ARTICULATION_DOF_VELOCITY_TARGET,
)
print(f" DOF count: {velocity_target_binding.shape[1]}")
print("Creating tensor binding for link poses...")
link_pose_binding = physx.create_tensor_binding(
pattern="/World/articulation/articulationLink*",
tensor_type=TensorType.ARTICULATION_LINK_POSE,
)
print(f" Link count: {link_pose_binding.shape[1]}, Pose dims: {link_pose_binding.shape[2]}")
optional_pose_binding = physx.create_tensor_binding(
pattern="/World/optionalRigidBodies/*",
tensor_type=TensorType.RIGID_BODY_POSE,
)
if optional_pose_binding.count == 0:
print(" Optional rigid body pose binding is empty")
optional_pose_binding.destroy()
optional_pose_binding = None
num_dofs = velocity_target_binding.shape[1]
velocity_targets = np.zeros(velocity_target_binding.shape, dtype=np.float32)
for i in range(num_dofs):
velocity_targets[0, i] = 25.0 if i % 2 == 0 else -25.0
print("Setting DOF velocity targets (alternating +/-25 rad/s)...")
velocity_target_binding.write(velocity_targets)
print(f" Velocity targets: {velocity_targets[0, :5]}... (first 5 DOFs)")
print("\nRunning 1000 simulation steps...")
link_poses = np.zeros(link_pose_binding.shape, dtype=np.float32)
dt = 0.01
for i in range(1000):
physx.step(dt)
physx.wait_all()
if i % 100 == 0 or i == 999:
link_pose_binding.read(link_poses)
px, py, pz = link_poses[0, 0, 0:3]
qx, qy, qz, qw = link_poses[0, 0, 3:7]
roll_x_rad = math.atan2(2.0 * (qw * qx + qy * qz), 1.0 - 2.0 * (qx * qx + qy * qy))
deg_x = roll_x_rad * 180.0 / math.pi
print(
f" Step {i:4d}: pos=({px:.6f}, {py:.6f}, {pz:.6f}), "
f"quat(xyzw)=({qx:.6f}, {qy:.6f}, {qz:.6f}, {qw:.6f}), "
f"rotation_x={deg_x:.2f} deg"
)
print("\nCompleted 1000 simulation steps successfully!")
C#
Create tensor bindings, write control targets, step, and read back state:
// 3. Create tensor bindings
// 3a. DOF velocity target binding (write control targets)
ovphysx_tensor_binding_handle_t dof_target_binding = 0;
ovphysx_tensor_binding_desc_t dof_target_desc = {
.pattern = OVPHYSX_LITERAL("/World/articulation"),
.tensor_type = OVPHYSX_TENSOR_ARTICULATION_DOF_VELOCITY_TARGET_F32
};
result = ovphysx_create_tensor_binding(handle, &dof_target_desc, &dof_target_binding);
if (!check_result(result, "create DOF target binding")) {
return destroy_instance_and_shutdown(handle);
}
// 3b. Articulation link pose binding
ovphysx_tensor_binding_handle_t link_pose_binding = 0;
ovphysx_tensor_binding_desc_t link_pose_desc = {
.pattern = OVPHYSX_LITERAL("/World/articulation"),
.tensor_type = OVPHYSX_TENSOR_ARTICULATION_LINK_POSE_F32
};
result = ovphysx_create_tensor_binding(handle, &link_pose_desc, &link_pose_binding);
if (!check_result(result, "create articulation link pose binding")) {
return destroy_instance_and_shutdown(handle);
}
printf("Tensor bindings created.\n");
// 4. Query binding specs and allocate tensors
ovphysx_tensor_spec_t dof_spec, link_pose_spec;
result = ovphysx_get_tensor_binding_spec(handle, dof_target_binding, &dof_spec);
if (!check_result(result, "get_tensor_binding_spec (dof target)")) {
return destroy_instance_and_shutdown(handle);
}
result = ovphysx_get_tensor_binding_spec(handle, link_pose_binding, &link_pose_spec);
if (!check_result(result, "get_tensor_binding_spec (link pose)")) {
return destroy_instance_and_shutdown(handle);
}
printf("\nBinding specs:\n");
printf(" Articulation DOFs: shape=[%lld, %lld], ndim=%d\n",
(long long)dof_spec.shape[0], (long long)dof_spec.shape[1], dof_spec.ndim);
printf(" Articulation link poses: shape=[%lld, %lld, %lld], ndim=%d\n",
(long long)link_pose_spec.shape[0],
(long long)link_pose_spec.shape[1],
(long long)link_pose_spec.shape[2],
link_pose_spec.ndim);
// Allocate CPU tensors
const size_t dof_count = (size_t)dof_spec.shape[0];
const size_t dof_components = (size_t)dof_spec.shape[1];
const size_t link_pose_batch = (size_t)link_pose_spec.shape[0];
const size_t link_count = (size_t)link_pose_spec.shape[1];
const size_t link_pose_components = (size_t)link_pose_spec.shape[2];
TensorBuffer dof_target_tensor = make_tensor_f32_2d(dof_count, dof_components);
TensorBuffer link_pose_tensor = make_tensor_f32_3d(link_pose_batch, link_count, link_pose_components);
// 5. Set initial DOF velocity targets and simulate
printf("\n=== Setting initial DOF velocity targets ===\n");
// Initialize all targets to 0.0
float* dof_target_data = (float*)dof_target_tensor.data;
for (size_t i = 0; i < dof_count * dof_components; i++) {
dof_target_data[i] = 0.0f;
}
printf("\n=== Writing initial DOF velocity targets ===\n");
result = ovphysx_write_tensor_binding(handle, dof_target_binding, &dof_target_tensor.tensor, NULL);
if (!check_result(result, "write initial DOF targets")) {
destroy_tensor(&dof_target_tensor);
destroy_tensor(&link_pose_tensor);
return destroy_instance_and_shutdown(handle);
}
// 6. Simulation loop
const float dt = 1.0f / 60.0f;
const size_t link_index_to_print = (link_count > 0) ? (link_count - 1) : 0;
printf("Running 120 simulation steps...\n");
for (int step = 0; step < 120; ++step) {
// Update DOF targets every 50 steps
if (step % 50 == 0) {
// Alternate between positive and negative target velocities
float target_vel = ((step / 50) % 2 == 0) ? 50.0f : -50.0f;
for (size_t i = 0; i < dof_count * dof_components; ++i) {
// Alternate direction for each DOF
dof_target_data[i] = (i % 2 == 0) ? target_vel : -target_vel;
}
result = ovphysx_write_tensor_binding(handle, dof_target_binding, &dof_target_tensor.tensor, NULL);
if (!check_result(result, "write DOF targets")) {
destroy_tensor(&dof_target_tensor);
destroy_tensor(&link_pose_tensor);
return destroy_instance_and_shutdown(handle);
}
}
// Step simulation
ovphysx_enqueue_result_t step_result = ovphysx_step(handle, dt);
if (step_result.status != OVPHYSX_API_SUCCESS) {
fprintf(stderr, "ERROR in step enqueue (status=%d)\n", (int)step_result.status);
{
ovphysx_string_t err = ovphysx_get_last_error();
if (err.ptr && err.length > 0)
fprintf(stderr, " %.*s\n", (int)err.length, err.ptr);
}
destroy_tensor(&dof_target_tensor);
destroy_tensor(&link_pose_tensor);
return destroy_instance_and_shutdown(handle);
}
if (!wait_op(handle, step_result.op_index, "step")) {
destroy_tensor(&dof_target_tensor);
destroy_tensor(&link_pose_tensor);
return destroy_instance_and_shutdown(handle);
}
// Read and print state every 30 steps
if (step % 30 == 0) {
// Read articulation link poses
result = ovphysx_read_tensor_binding(handle, link_pose_binding, &link_pose_tensor.tensor);
if (!check_result(result, "read articulation link poses")) {
destroy_tensor(&dof_target_tensor);
destroy_tensor(&link_pose_tensor);
return destroy_instance_and_shutdown(handle);
}
const float* link_pose_data = (const float*)link_pose_tensor.data;
size_t articulation_index = 0;
size_t link_pose_offset = (articulation_index * link_count + link_index_to_print) * link_pose_components;
printf("Step %3d | Link %zu pos=(%.3f, %.3f, %.3f) quat=(%.3f, %.3f, %.3f, %.3f)\n",
step,
link_index_to_print,
link_pose_data[link_pose_offset + 0],
link_pose_data[link_pose_offset + 1],
link_pose_data[link_pose_offset + 2],
link_pose_data[link_pose_offset + 3],
link_pose_data[link_pose_offset + 4],
link_pose_data[link_pose_offset + 5],
link_pose_data[link_pose_offset + 6]);
}
}
// Cleanup
printf("\n=== Cleanup ===\n");
destroy_tensor(&dof_target_tensor);
destroy_tensor(&link_pose_tensor);
ovphysx_destroy_tensor_binding(handle, dof_target_binding);
ovphysx_destroy_tensor_binding(handle, link_pose_binding);
printf("=== Articulation control sample completed successfully ===\n");
For GPU tensor bindings with CUDA, see tensor_bindings_gpu_c/ in the samples
directory. GPU dynamics are enabled by default (physxScene:enableGPUDynamics
defaults to true); set it to false to opt into CPU dynamics. For maximum
performance in tensor-heavy loops, GPU dynamics alone is not enough: enable
DirectGPU TensorAPI before creating the PhysX instance with
/physics/suppressReadback=true. See
GPU Warmup and Determinism.
Empty Optional Bindings#
A tensor binding that matches zero prims is valid. This is useful when absence
is a legitimate result for the current scene, such as optional assets or broad
inspection queries. Empty bindings remain zero-count views; if topology changes
and matching prims are added or recreated, destroy the old binding and create a
new one. For optional queries, keep the default raise_if_empty=False and
check binding.count before allocating or reading tensors. Use
raise_if_empty=True only when zero matches are a configuration error for your
application.
Point-instancer limitation. TensorBindingsAPI does not expose per-instance rows for rigid bodies created by
UsdGeom.PointInstancer. With the defaultraise_if_empty=False, a rigid-body binding that targets only the point instancer has count zero; the opt-inraise_if_empty=Truemode raises as described above. Use the ovstage output read API for simulated instance readback. For control, author the point instancer’spositions,orientations,velocities, andangularVelocitiesarrays through ovstage and pass those control ordinals toupdate_from_ovstage(). Use standalone rigid-body prims when per-body tensor bindings are required.
Binding Lifetime#
Tensor bindings are views of the physics objects realized for the current stage.
Create them after loading USD and reuse them across simulation steps. A normal
step() or step_sync() does not invalidate a binding.
Do not keep cached bindings across application-owned topology changes. Before
reset(), before removing USD data that contains bound objects, or before
loading or reparsing a stage so bound objects are destroyed and recreated,
destroy cached bindings when practical. If a stale binding survives one of those
lifecycle operations, only destroy it; do not read or write through it. Create a
replacement binding after the operation completes. In reset-heavy episode code,
the reset path should clear cached bindings because that path is where the
application changes the stage.
Tensor Type Reference#
Use this table to pre-allocate tensors without probing binding.shape at runtime.
Python callers can also inspect binding.spec for the native DLPack metadata
returned by ovphysx_get_tensor_binding_spec(). Allocate buffers from
binding.shape and binding.dtype; most tensor types are float32, but types
such as TensorType.RIGID_BODY_DISABLE_SIMULATION report uint8.
Symbols:
N: rigid body count in the bindingA: articulation count in the bindingL: max link count across matched articulationsD: max DOF count across matched articulationsT: max tendon count across matched articulations (fixed or spatial, depending on type)M: generalized coordinate count —numDofsfor fixed-base,numDofs + 6for floating-base articulationsS: max collision shape count per body/link in the bindingR,C: Jacobian shape fromgetJacobianShape()— fixed-base:R=L*6, C=D; floating-base:R=(L-1)*6+6, C=D+6B: volume deformable body count in the bindingV: max simulation node count across matched volume deformablesVr: max rest node count across matched volume deformablesE: max simulation element count across matched volume deformables (tetrahedra, K=4)F: max collision element count across matched volume deformables; K =getNumNodesPerElement()(4 for tetmesh)P: deformable material count in the bindingBs: surface deformable body count in the bindingVs: max simulation node count across matched surface deformablesEs: max simulation element count across matched surface deformables (triangles, K=3)
Rigid Body State
Constant |
Shape |
Dimensionality |
Read |
Write |
Component layout |
Behavioral note |
|---|---|---|---|---|---|---|
|
|
2D |
yes |
yes |
|
World-frame rigid body transforms |
|
|
2D |
yes |
yes |
|
World-frame linear and angular velocity |
|
|
2D |
yes |
no |
|
World-frame linear and angular acceleration |
|
|
2D |
no |
yes |
|
Write-only force at center of mass (control input) |
|
|
2D |
no |
yes |
|
Write-only wrench-at-position in world frame |
Rigid Body Properties (standalone, non-articulated bodies)
Constant |
Shape |
Dimensionality |
Read |
Write |
Component layout |
Behavioral note |
|---|---|---|---|---|---|---|
|
|
1D |
yes |
yes |
mass scalar |
Scalar mass per rigid body |
|
|
1D |
yes |
no |
inverse mass scalar |
Computed from mass; read-only |
|
|
2D |
yes |
yes |
row-major 3x3 |
Inertia tensor in body frame |
|
|
2D |
yes |
no |
row-major 3x3 |
Computed from inertia; read-only |
|
|
2D |
yes |
yes |
|
COM local pose in body frame |
Rigid body property tensors in this table are CPU tensors even when the simulation is running on GPU. State tensors such as pose, velocity, acceleration, force, and wrench use the simulation device.
For Python bindings, binding.prim_paths returns row metadata only; tensor
reads and writes keep using the shapes above. Rigid-body bindings return one
rigid body prim path per row. Articulation bindings return one articulation
root prim path per A row; link names remain available through
binding.body_names.
Rigid Body Shape Properties
Constant |
Shape |
Dimensionality |
Read |
Write |
Component layout |
Behavioral note |
|---|---|---|---|---|---|---|
|
|
3D |
yes |
yes |
|
Per-shape material properties |
|
|
2D |
yes |
yes |
offset scalar per shape |
Distance at which contacts are generated |
|
|
2D |
yes |
yes |
offset scalar per shape |
Rest separation between shapes |
Shape property tensors in this table are CPU tensors even when the simulation is running on GPU.
Volume Deformable Body State
Symbols: B = volume deformable body count, V = max simulation nodes, Vr = max rest nodes, E = max simulation elements (tetrahedra, K=4), F = max collision elements (triangles, K=3).
Constant |
Shape |
Dimensionality |
Read |
Write |
Component layout |
Behavioral note |
|---|---|---|---|---|---|---|
|
|
3D |
yes |
yes |
|
Simulation mesh node positions |
|
|
3D |
yes |
yes |
|
Simulation mesh node velocities |
|
|
3D |
yes |
yes |
|
Simulation mesh kinematic targets |
|
|
3D |
yes |
no |
|
Rest mesh node positions |
|
|
3D |
yes |
no |
int32 node indices |
Tetrahedral simulation element connectivity |
|
|
3D |
yes |
no |
int32 node indices |
Collision element connectivity; K=4 for volume tetmesh |
Volume deformable body tensors require DirectGPU mode. Enable
/physics/suppressReadback=true before constructing the PhysX instance.
Surface Deformable Body State
Symbols: Bs = surface deformable body count, Vs = max simulation nodes, Vr = max rest nodes, Es = max simulation elements (triangles, K=3).
Constant |
Shape |
Dimensionality |
Read |
Write |
Component layout |
Behavioral note |
|---|---|---|---|---|---|---|
|
|
3D |
yes |
yes |
|
Simulation mesh node positions |
|
|
3D |
yes |
yes |
|
Simulation mesh node velocities |
|
|
3D |
yes |
no |
|
Rest mesh node positions |
|
|
3D |
yes |
no |
int32 node indices |
Triangular simulation element connectivity |
Surface deformable body tensors require DirectGPU mode. Enable
/physics/suppressReadback=true before constructing the PhysX instance.
Deformable Material Properties
Constant |
Shape |
Dimensionality |
Read |
Write |
Component layout |
Behavioral note |
|---|---|---|---|---|---|---|
|
|
1D |
yes |
yes |
scalar |
Dynamic friction per deformable material |
|
|
1D |
yes |
yes |
scalar |
Young’s modulus per deformable material |
|
|
1D |
yes |
yes |
scalar |
Poisson’s ratio per deformable material |
Deformable material property tensors in this table are CPU tensors even when the simulation is running on GPU.
Articulation Root State
Constant |
Shape |
Dimensionality |
Read |
Write |
Component layout |
Behavioral note |
|---|---|---|---|---|---|---|
|
|
2D |
yes |
yes |
|
Root body transform per articulation |
|
|
2D |
yes |
yes |
|
Root body velocity per articulation |
Articulation Link State
Constant |
Shape |
Dimensionality |
Read |
Write |
Component layout |
Behavioral note |
|---|---|---|---|---|---|---|
|
|
3D |
yes |
no |
|
Per-link pose; padded links are zero |
|
|
3D |
yes |
no |
|
Per-link velocity; read-only |
|
|
3D |
yes |
no |
|
Per-link linear and angular acceleration; read-only |
|
|
3D |
no |
yes |
|
Write-only per-link external wrench |
Articulation DOF State and Control
Constant |
Shape |
Dimensionality |
Read |
Write |
Component layout |
Behavioral note |
|---|---|---|---|---|---|---|
|
|
2D |
yes |
yes |
joint position scalar per DOF |
Joint-space position in articulation DOF order |
|
|
2D |
yes |
yes |
joint velocity scalar per DOF |
Joint-space velocity in articulation DOF order |
|
|
2D |
yes |
yes |
target position scalar per DOF |
Position-control targets |
|
|
2D |
yes |
yes |
target velocity scalar per DOF |
Velocity-control targets |
|
|
2D |
yes |
yes |
actuation scalar per DOF |
Readback is from staging buffer; may differ from solver-applied force |
Articulation DOF Properties
Constant |
Shape |
Dimensionality |
Read |
Write |
Component layout |
Behavioral note |
|---|---|---|---|---|---|---|
|
|
2D |
yes |
yes |
stiffness scalar per DOF |
PD position-control stiffness |
|
|
2D |
yes |
yes |
damping scalar per DOF |
PD velocity-control damping |
|
|
3D |
yes |
yes |
|
Joint position limits |
|
|
2D |
yes |
yes |
max velocity scalar per DOF |
Per-DOF velocity clamp |
|
|
2D |
yes |
yes |
max force scalar per DOF |
Per-DOF force/torque clamp |
|
|
2D |
yes |
yes |
armature scalar per DOF |
Added inertia at each DOF |
|
|
3D |
yes |
yes |
|
Friction coefficients at each DOF |
Articulation Body Properties
Constant |
Shape |
Dimensionality |
Read |
Write |
Component layout |
Behavioral note |
|---|---|---|---|---|---|---|
|
|
2D |
yes |
yes |
mass scalar per link |
Scalar mass per articulation link |
|
|
3D |
yes |
yes |
|
COM local pose in body frame per link |
|
|
3D |
yes |
yes |
row-major 3x3 |
Inertia tensor in COM frame per link |
|
|
2D |
yes |
no |
inverse mass scalar per link |
Computed from mass; read-only |
|
|
3D |
yes |
no |
row-major 3x3 |
Computed from inertia; read-only |
Articulation Shape Properties
Constant |
Shape |
Dimensionality |
Read |
Write |
Component layout |
Behavioral note |
|---|---|---|---|---|---|---|
|
|
3D |
yes |
yes |
|
Per-shape material properties per link |
|
|
2D |
yes |
yes |
offset scalar per shape |
Distance at which contacts are generated |
|
|
2D |
yes |
yes |
offset scalar per shape |
Rest separation between shapes |
Shape property tensors in this table are CPU tensors even when the simulation is running on GPU.
Articulation Dynamics Queries (read-only)
Constant |
Shape |
Dimensionality |
Read |
Write |
Component layout |
Behavioral note |
|---|---|---|---|---|---|---|
|
|
3D |
yes |
no |
row-major |
Shape from |
|
|
3D |
yes |
no |
row-major square |
Generalized mass matrix; shape from |
|
|
2D |
yes |
no |
force scalar per generalized coordinate |
Combined Coriolis and centrifugal forces |
|
|
2D |
yes |
no |
force scalar per generalized coordinate |
Gravity compensation forces |
|
|
3D |
yes |
no |
|
Incoming joint force and torque per link |
|
|
2D |
yes |
no |
scalar per DOF |
Projected joint forces |
Fixed Tendon Properties
Constant |
Shape |
Dimensionality |
Read |
Write |
Component layout |
Behavioral note |
|---|---|---|---|---|---|---|
|
|
2D |
yes |
yes |
stiffness scalar per tendon |
Requires articulation with fixed tendons |
|
|
2D |
yes |
yes |
damping scalar per tendon |
Requires articulation with fixed tendons |
|
|
2D |
yes |
yes |
limit stiffness scalar per tendon |
Requires articulation with fixed tendons |
|
|
3D |
yes |
yes |
|
Fixed tendon position limits |
|
|
2D |
yes |
yes |
rest length scalar per tendon |
Requires articulation with fixed tendons |
|
|
2D |
yes |
yes |
offset scalar per tendon |
Requires articulation with fixed tendons |
Spatial Tendon Properties
Constant |
Shape |
Dimensionality |
Read |
Write |
Component layout |
Behavioral note |
|---|---|---|---|---|---|---|
|
|
2D |
yes |
yes |
stiffness scalar per tendon |
Requires articulation with spatial tendons |
|
|
2D |
yes |
yes |
damping scalar per tendon |
Requires articulation with spatial tendons |
|
|
2D |
yes |
yes |
limit stiffness scalar per tendon |
Requires articulation with spatial tendons |
|
|
2D |
yes |
yes |
offset scalar per tendon |
Requires articulation with spatial tendons |
For canonical enum definitions and low-level semantics, see include/ovphysx/ovphysx_types.h.
Result#
After this tutorial, you can create tensor bindings, push batched simulation inputs, and read back batched results.