Attribute Bindings#
Note
Python examples replace persistent bindings with reusable ovstage queries.
Persistent renderer bindings remain deprecated compatibility APIs. Refer to
skills/update-0_3-0_4-python/SKILL.md.
For repeated Python writes or maps, retain one ovstage query for the target prims and reuse it at successive ordinals. C compatibility code can retain a binding descriptor for the same purpose.
Use regular writes from Attribute Reads and Writes for one-shot edits. Use Attribute Mapping when the hot path needs zero-copy writes into ovrtx-owned buffers.
Create a Reusable Target and Write#
query = stage.query_from_path_list(path_list)
attribute = paths.intern_token("omni:xform")
matrix_dtype = ovstage.numpy_to_dldatatype(matrix.dtype, lanes=16)
matrix_tensor = ovstage.make_dltensor(matrix, dtype=matrix_dtype, shape=[1], ndim=1)
stage.write_attribute(query, attribute, ordinal=2, tensors=matrix_tensor, is_array=False).wait()
stage.advance_write_floor(2, ovstage.Scope.ALL).wait()
// The ovstage "binding" is the pair (query handle, interned attribute
// token). The query identifies the target prims; the token identifies the
// attribute. Both are reserved synchronously and reusable across many
// writes/reads until released.
path_dictionary_instance_t* pd = ovstage_get_path_dictionary(stage_);
ovx_string_t prim_path = ovx_str("/World/Plane");
ovx_primpath_list_t path_list{};
ASSERT_EQ(path_dictionary_create_path_list_from_strings(pd, &prim_path, 1, &path_list).status,
OVX_API_SUCCESS);
ovstage_query_handle_t query_handle = OVSTAGE_INVALID_QUERY_HANDLE;
ASSERT_EQ(ovstage_query_from_path_list(stage_, path_list, &query_handle), OVSTAGE_OK)
<< format_ovstage_last_error();
ovx_string_t attr_str = ovx_str("omni:xform");
ovx_token_t attr_token{};
ASSERT_EQ(path_dictionary_create_tokens_from_strings(pd, &attr_str, 1, &attr_token).status,
OVX_API_SUCCESS);
// Write through the (query, token) binding. omni:xform is a per-prim 4x4
// double matrix — shape=[1], lanes=16, with OVSTAGE_SEMANTIC_MATRIX.
// Translation lives in the last row (USD row-vector convention).
double matrix[16] = {
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
14.0, 0.0, 0.0, 1.0,
};
int64_t write_shape[1] = {1};
DLTensor write_tensor{};
write_tensor.data = matrix;
write_tensor.device = {kDLCPU, 0};
write_tensor.ndim = 1;
write_tensor.dtype = {kDLFloat, 64, 16};
write_tensor.shape = write_shape;
ovstage_write_data_t write_data{};
write_data.tensors = &write_tensor;
write_data.tensor_count = 1;
write_data.is_array = false;
write_data.semantic = OVSTAGE_SEMANTIC_MATRIX;
ovx_string_or_token_t attr_ref{};
attr_ref.token = attr_token;
ovstage_enqueue_result_t wq = ovstage_write_attribute(
stage_, query_handle, attr_ref, /*ordinal=*/2, write_data, OVSTAGE_PRIM_MODE_UPSERT);
ASSERT_EQ(wq.status, OVSTAGE_OK) << format_ovstage_last_error();
docs_wait_ovstage_no_errors(stage_, wq.op_index);
docs_ovstage_advance_write_floor(stage_, 2);
// Release the query handle (per-handle-ordered: waits for in-flight
// reads/writes to complete) and drop the path-list refcount.
ovstage_release_query(stage_, query_handle);
path_dictionary_release_path_list_reference(pd, path_list);
Async Queries and Writes#
Ovstage query and write handles can be waited explicitly for non-blocking update pipelines.
plane_filter = ovstage.Filter([ovstage.Predicate("usd-path", ovstage.FilterOp.IN, ["/World/Plane"])])
query = stage.query(filter=plane_filter)
query.wait()
attribute = paths.intern_token("omni:xform")
matrix_dtype = ovstage.numpy_to_dldatatype(matrix.dtype, lanes=16)
matrix_tensor = ovstage.make_dltensor(matrix, dtype=matrix_dtype, shape=[1], ndim=1)
write_op = stage.write_attribute(query, attribute, ordinal=2, tensors=matrix_tensor, is_array=False)
write_op.wait()
stage.advance_write_floor(2, ovstage.Scope.ALL).wait()
Array Attributes#
Use is_array=True for variable-length USD array attributes such as mesh
points.
attribute = paths.intern_token("points")
point_dtype = ovstage.numpy_to_dldatatype(points.dtype, lanes=3)
point_tensor = ovstage.make_dltensor(points, dtype=point_dtype, shape=[4], ndim=1)
stage.write_attribute(query, attribute, ordinal=2, tensors=point_tensor, is_array=True).wait()
stage.advance_write_floor(2, ovstage.Scope.ALL).wait()
Mapping Through a Query#
The same ovstage query can be reused for repeated map/unmap cycles.
query = stage.query_from_path_list(path_list)
attribute = paths.intern_token("omni:xform")
with stage.map_attribute(query, attribute, ordinal=2) as mapping:
mapping.wait()
group = mapping.fetch_next()
matrices = np.from_dlpack(group.dlpack(0)).reshape(1, 4, 4)
matrices[0, 3, 0] = 8.0
stage.advance_write_floor(2, ovstage.Scope.ALL).wait()
Lifetime Rules#
Release reusable ovstage queries when the hot path is done.
In C, keep strings and descriptor arrays alive until binding creation has completed.
OVRTX_BINDING_FLAG_OPTIMIZEis intended for frequent high-volume writes.