Stage Queries#
Stage queries discover prims on the runtime stage and optionally report attribute schema metadata. A typical workflow is:
Query prims by type, attribute existence, or a filter combination.
Inspect returned paths and attribute descriptors.
Reuse the returned prim-list handles in later C reads or writes.
Filters combine as AND (require_all), OR (require_any), and NOT
(exclude). Attribute reporting can be disabled, enabled for all attributes,
or restricted to a requested name list.
Python Queries#
# Fetch every prim on the stage. With AttributeFilterMode.NONE the result
# dict maps each prim path to an empty attributes dict (lightweight).
prims = renderer.query_prims(attribute_filter_mode=ovrtx.AttributeFilterMode.NONE)
for prim_path, attributes in prims.items():
print(f"{prim_path}: {len(attributes)} attributes")
# AND filter: require prim type == "Mesh".
meshes = renderer.query_prims(
require_all=[(ovrtx.FilterKind.PRIM_TYPE, "Mesh")],
)
# Find meshes and get AttributeInfo descriptors for "points" and "material:binding".
# AttributeInfo exposes dtype, is_array, and semantic — enough to decide how
# to read the attribute next. Relationship attributes surface with
# Semantic.PATH_ID; resolve those IDs through the renderer's path dictionary.
meshes = renderer.query_prims(
require_all=[(ovrtx.FilterKind.PRIM_TYPE, "Mesh")],
attribute_filter_mode=ovrtx.AttributeFilterMode.SPECIFIC,
attribute_names=["points", "material:binding"],
)
for prim_path, attributes in meshes.items():
for name, info in attributes.items():
print(f"{prim_path}.{name}: dtype={info.dtype} array={info.is_array} semantic={info.semantic.name}")
# Match Mesh or Camera prims, then exclude Camera. The exclusion removes
# a prim that would otherwise match the OR clause.
prims = renderer.query_prims(
require_any=[
(ovrtx.FilterKind.PRIM_TYPE, "Mesh"),
(ovrtx.FilterKind.PRIM_TYPE, "Camera"),
],
exclude=[(ovrtx.FilterKind.PRIM_TYPE, "Camera")],
attribute_filter_mode=ovrtx.AttributeFilterMode.ALL,
)
C Queries#
// Issue a query with no filters and no attribute reporting.
ovrtx_query_desc_t desc{};
desc.attribute_filter.mode = OVRTX_ATTRIBUTE_FILTER_NONE;
ovrtx_query_handle_t query_handle = 0;
ovrtx_enqueue_result_t eq = ovrtx_query_prims(renderer_, &desc, &query_handle);
ASSERT_API_SUCCESS(eq.status);
ovrtx_op_wait_result_t wait_result;
ASSERT_API_SUCCESS(ovrtx_wait_op(renderer_, eq.op_index, ovrtx_timeout_infinite, &wait_result).status);
ASSERT_NO_OP_ERRORS(wait_result);
ovrtx_query_result_t qr{};
ASSERT_API_SUCCESS(ovrtx_fetch_query_results(renderer_, query_handle, ovrtx_timeout_infinite, &qr).status);
// Each group carries a prim_list_handle that can be plugged into
// ovrtx_binding_desc_t::prims_list_handle for subsequent reads/writes.
for (size_t g = 0; g < qr.group_count; ++g) {
printf("group %zu: %zu prims\n", g, qr.groups[g].prim_count);
}
// Always release the query when done — the prim list handles are owned
// by this result and become invalid after release.
ASSERT_API_SUCCESS(ovrtx_release_query_results(renderer_, query_handle).status);
// AND filter: require prim type == "Mesh".
ovx_string_t mesh_type = ovx_str("Mesh");
ovrtx_filter_t filter{};
filter.kind = OVRTX_FILTER_PRIM_TYPE;
filter.name.string = mesh_type;
ovrtx_query_desc_t desc{};
desc.require_all = &filter;
desc.require_all_count = 1;
desc.attribute_filter.mode = OVRTX_ATTRIBUTE_FILTER_NONE;
ovrtx_query_handle_t query_handle = 0;
ovrtx_enqueue_result_t eq = ovrtx_query_prims(renderer_, &desc, &query_handle);
ASSERT_API_SUCCESS(eq.status);
ovrtx_op_wait_result_t wait_result;
ASSERT_API_SUCCESS(ovrtx_wait_op(renderer_, eq.op_index, ovrtx_timeout_infinite, &wait_result).status);
ASSERT_NO_OP_ERRORS(wait_result);
ovrtx_query_result_t qr{};
ASSERT_API_SUCCESS(ovrtx_fetch_query_results(renderer_, query_handle, ovrtx_timeout_infinite, &qr).status);
// Match prims that expose a "points" attribute.
ovx_string_t points_attr = ovx_str("points");
ovrtx_filter_t filter{};
filter.kind = OVRTX_FILTER_HAS_ATTRIBUTE;
filter.name.string = points_attr;
ovrtx_query_desc_t desc{};
desc.require_all = &filter;
desc.require_all_count = 1;
desc.attribute_filter.mode = OVRTX_ATTRIBUTE_FILTER_NONE;
// Match Mesh or Camera prims, then exclude Camera. The exclusion removes
// a prim that would otherwise match the OR clause.
ovx_string_t mesh_type = ovx_str("Mesh");
ovx_string_t camera_type = ovx_str("Camera");
ovrtx_filter_t any_filters[2]{};
any_filters[0].kind = OVRTX_FILTER_PRIM_TYPE;
any_filters[0].name.string = mesh_type;
any_filters[1].kind = OVRTX_FILTER_PRIM_TYPE;
any_filters[1].name.string = camera_type;
ovrtx_filter_t exclude_filter{};
exclude_filter.kind = OVRTX_FILTER_PRIM_TYPE;
exclude_filter.name.string = camera_type;
ovrtx_query_desc_t desc{};
desc.require_any = any_filters;
desc.require_any_count = 2;
desc.exclude = &exclude_filter;
desc.exclude_count = 1;
desc.attribute_filter.mode = OVRTX_ATTRIBUTE_FILTER_ALL;
Async Queries#
Python async queries follow the same Operation / PendingFetch lifecycle
as other async APIs:
# Two-phase async: wait() → PendingFetch, then fetch() → result dict.
op = renderer.query_prims_async(
require_all=[(ovrtx.FilterKind.PRIM_TYPE, "Mesh")],
)
pending = op.wait()
meshes = pending.fetch()
Path Dictionary#
C query results use token and prim-path ids. Resolve them through the renderer’s path dictionary while the query results are still valid:
// The renderer's path dictionary converts between string paths and internal
// handles. It is valid for the lifetime of the renderer — no release is required.
path_dictionary_instance_t pd{};
ASSERT_API_SUCCESS(ovrtx_get_path_dictionary(renderer_, &pd).status);
// 1) Resolve the primpath handles in a query result's prim_list_handle to
// "/A/B/C" string paths.
ovx_primpath_list_t handle = qr.groups[0].prim_list_handle;
size_t num_paths = 0;
ASSERT_EQ(path_dictionary_get_num_paths_from_path_list(&pd, handle, &num_paths).status,
OVX_API_SUCCESS);
std::vector<ovx_primpath_t> prim_paths(num_paths);
size_t out_num = 0;
ASSERT_EQ(path_dictionary_get_paths_from_path_list(&pd, handle, 0, num_paths,
prim_paths.data(), &out_num)
.status,
OVX_API_SUCCESS);
// A primpath decomposes into a sequence of tokens; join them with '/' to get
// a full stage path.
std::vector<std::string> path_strings;
for (size_t i = 0; i < out_num; ++i) {
ovx_token_t token_buf[64];
ovx_token_t* tokens_out = nullptr;
size_t num_tokens = 0;
size_t num_processed = 0;
ASSERT_EQ(path_dictionary_get_tokens_from_paths(&pd, &prim_paths[i], 1, token_buf, 64,
&tokens_out, &num_tokens, &num_processed)
.status,
OVX_API_SUCCESS);
std::string s;
for (size_t t = 0; t < num_tokens; ++t) {
ovx_string_t tok_s{};
ASSERT_EQ(path_dictionary_get_strings_from_tokens(&pd, &tokens_out[t], 1, &tok_s)
.status,
OVX_API_SUCCESS);
s += "/";
s.append(tok_s.ptr, tok_s.length);
}
path_strings.push_back(s);
}
// 2) Round-trip: rebuild a path list from the resolved strings and verify
// it holds the same number of paths.
std::vector<ovx_string_t> str_views(path_strings.size());
for (size_t i = 0; i < path_strings.size(); ++i) {
str_views[i] = {path_strings[i].c_str(), path_strings[i].size()};
}
ovx_primpath_list_t rebuilt{};
ASSERT_EQ(path_dictionary_create_path_list_from_strings(&pd, str_views.data(),
str_views.size(), &rebuilt)
.status,
OVX_API_SUCCESS);
size_t rebuilt_num = 0;
ASSERT_EQ(path_dictionary_get_num_paths_from_path_list(&pd, rebuilt, &rebuilt_num).status,
OVX_API_SUCCESS);
EXPECT_EQ(rebuilt_num, out_num);
ASSERT_EQ(path_dictionary_destroy_path_list(&pd, rebuilt).status, OVX_API_SUCCESS);
Python query results return strings directly for prim paths and attribute names.
Troubleshooting#
Release C query results only after copying any strings, descriptors, or ids you need to keep.
AttributeFilterMode.SPECIFICwith an empty attribute-name list returns no descriptors. UseALLto dump every descriptor orNONEfor lightweight discovery.Relationship-valued attributes surface as path ids in C. Resolve them through the path dictionary before printing or storing string paths.