Queries#
A query selects the prims that are affected by a read, write, or map operation.
First create a query by supplying explicit paths or finding prims that match
conditions. Pass that query to stage.read_attributes(),
stage.write_attribute(), or stage.map_attribute() to operate on those
prims.
You can call query.result() after a query.wait() to
inspect the selection, such as its prim count.
Release the query when it is no longer needed.
Python’s with statement handles this for you.
Creating a Query From a List of Paths#
Use this when you already know the paths of the prims you want to read or write.
Store those paths in a list through the
path dictionary, then pass the list to
stage.query_from_path_list(). This creates a query containing the supplied
paths without checking whether the prims exist.
These snippets create a path list and a query for use in later operations:
# Build an interned prim-path list and open a query over those prims.
# The query is a context manager: block exit releases its handle, and
# Stage teardown requires all handles released first -- the try/finally
# releases the path list even when an error interrupts the flow.
prim_paths = paths.create_path_list_from_strings(["/World/A", "/World/B", "/World/C"])
try:
with stage.query_from_path_list(prim_paths) as query:
// Build an immutable prim-path list from strings and open a query over it.
const ovx_string_t paths[] = { literal_to_ovx_string("/World/A"), literal_to_ovx_string("/World/B"),
literal_to_ovx_string("/World/C") };
ovx_primpath_list_t pathList = OVX_INVALID_PRIMPATH_LIST;
ovxResult = path_dictionary_create_path_list_from_strings(dict, paths, 3, &pathList);
checkOvx(dict, ovxResult, "create-path-list");
ovstage_query_handle_t query = OVSTAGE_INVALID_QUERY_HANDLE;
status = ovstage_query_from_path_list(stage, pathList, &query);
check(stage, status, "query_from_path_list");
Creating a Query by Filtering Prims#
Use a filter when you want ovstage to find prims that meet conditions, such as
having an attribute or belonging to a particular prim type. Pass a
ovstage.Filter to ovstage.Stage.query(). ovstage checks prims
in the runtime stage and includes those that match in
the resulting query.
Omitting the filter selects all prims.
Each ovstage.Predicate describes one condition: an attribute to check,
a ovstage.FilterOp operation, and any values needed by that operation.
All predicates in a filter must match. Built-in metadata attributes let you
check a prim’s type (usd-prim-type), path (usd-path), or parent
(usd-parent).
Filtered queries find prims in the current scene state. They do not reconstruct an earlier scene state from an ordinal.
Example: Find Mesh Prims in a Subtree#
Combine a type match with a path prefix. These snippets select Mesh prims
below /World/Group. The trailing slash excludes the group itself and avoids
matching names such as /World/GroupExtra:
from ovstage import Filter, FilterOp, Predicate
# Separate predicates must all match.
mesh_filter = Filter([
Predicate("usd-prim-type", FilterOp.IN, ["Mesh"]),
Predicate("usd-path", FilterOp.PREFIX, ["/World/Group/"]),
])
with stage.query(filter=mesh_filter) as query:
query.wait()
mesh_count = query.result().total_prim_count
print("Meshes below /World/Group:", mesh_count)
# Meshes below /World/Group: 2
The waitOp() helper waits, checks errors, and
releases operation tracking; check() checks synchronous status codes.
// Match Mesh prims below /World/Group; the trailing slash excludes the root.
const ovx_string_t type = literal_to_ovx_string("Mesh");
const ovx_string_t prefix = literal_to_ovx_string("/World/Group/");
ovstage_predicate_t predicates[2]{};
predicates[0].attribute.string = literal_to_ovx_string("usd-prim-type");
predicates[0].op = OVSTAGE_FILTER_OP_IN;
predicates[0].values = &type;
predicates[0].value_count = 1;
predicates[1].attribute.string = literal_to_ovx_string("usd-path");
predicates[1].op = OVSTAGE_FILTER_OP_PREFIX;
predicates[1].values = &prefix;
predicates[1].value_count = 1;
ovstage_filter_t filter{};
filter.predicates = predicates;
filter.count = 2;
// Wait, inspect the match count, and release the result and query.
ovstage_query_handle_t query = OVSTAGE_INVALID_QUERY_HANDLE;
waitOp(stage, ovstage_query(stage, &filter, nullptr, 0, &query), "query");
ovstage_query_result_t result{};
check(stage, ovstage_fetch_query_result(stage, query, OVSTAGE_TIMEOUT_INFINITE, &result),
"fetch_query_result");
std::printf("Meshes below /World/Group: %llu\n", static_cast<unsigned long long>(result.total_prim_count));
// Meshes below /World/Group: 2
check(stage, ovstage_release_query_result(stage, &result), "release_query_result");
waitOp(stage, ovstage_release_query(stage, query), "release_query");
Common Patterns#
Checking Whether a Prim Exists#
Use FilterOp.IN on usd-path to match one exact path, then check whether
query.result().total_prim_count is nonzero. Do not use
query_from_path_list() as an existence check: a path-list query can include
paths that do not exist yet.
These snippets check whether /World/Anchor exists:
# Match the exact path, not a prefix that could include other prims.
prim_filter = Filter([Predicate("usd-path", FilterOp.IN, ["/World/Anchor"])])
with stage.query(filter=prim_filter) as query:
query.wait()
prim_exists = query.result().total_prim_count != 0
print("/World/Anchor exists:", prim_exists)
# /World/Anchor exists: True
// Match the exact path, not a prefix that could include other prims.
const ovx_string_t path = literal_to_ovx_string("/World/Anchor");
ovstage_predicate_t predicate{};
predicate.attribute.string = literal_to_ovx_string("usd-path");
predicate.op = OVSTAGE_FILTER_OP_IN;
predicate.values = &path;
predicate.value_count = 1;
ovstage_filter_t filter{};
filter.predicates = &predicate;
filter.count = 1;
// Wait for the query before inspecting its result.
ovstage_query_handle_t query = OVSTAGE_INVALID_QUERY_HANDLE;
waitOp(stage, ovstage_query(stage, &filter, nullptr, 0, &query), "query prim existence");
ovstage_query_result_t result{};
check(stage, ovstage_fetch_query_result(stage, query, OVSTAGE_TIMEOUT_INFINITE, &result),
"fetch_query_result");
const bool primExists = result.total_prim_count != 0;
std::printf("/World/Anchor exists: %s\n", primExists ? "True" : "False");
// /World/Anchor exists: True
// The result and query handle have separate lifetimes; release both.
check(stage, ovstage_release_query_result(stage, &result), "release_query_result");
waitOp(stage, ovstage_release_query(stage, query), "release_query");
Checking Whether an Attribute Exists on a Prim#
Combine the exact path condition with FilterOp.HAS for the attribute.
Both conditions must match. HAS checks presence, not the attribute’s value;
a value of zero or an empty array still counts as present.
These snippets check whether /World/Anchor has example:count. The result
is false if either the prim or the attribute is absent:
# Both conditions must match: this path AND presence of this attribute.
attribute_filter = Filter([
Predicate("usd-path", FilterOp.IN, ["/World/Anchor"]),
Predicate("example:count", FilterOp.HAS),
])
with stage.query(filter=attribute_filter) as query:
query.wait()
attribute_exists = query.result().total_prim_count != 0
print("/World/Anchor has example:count:", attribute_exists)
# /World/Anchor has example:count: True
// Both conditions must match: this path AND presence of this attribute.
const ovx_string_t path = literal_to_ovx_string("/World/Anchor");
ovstage_predicate_t predicates[2]{};
predicates[0].attribute.string = literal_to_ovx_string("usd-path");
predicates[0].op = OVSTAGE_FILTER_OP_IN;
predicates[0].values = &path;
predicates[0].value_count = 1;
predicates[1].attribute.string = literal_to_ovx_string("example:count");
predicates[1].op = OVSTAGE_FILTER_OP_HAS; // No comparison value is needed.
ovstage_filter_t filter{};
filter.predicates = predicates;
filter.count = 2;
ovstage_query_handle_t query = OVSTAGE_INVALID_QUERY_HANDLE;
waitOp(stage, ovstage_query(stage, &filter, nullptr, 0, &query), "query attribute existence");
ovstage_query_result_t result{};
check(stage, ovstage_fetch_query_result(stage, query, OVSTAGE_TIMEOUT_INFINITE, &result),
"fetch_query_result");
const bool attributeExists = result.total_prim_count != 0;
std::printf("/World/Anchor has example:count: %s\n", attributeExists ? "True" : "False");
// /World/Anchor has example:count: True
// Release the result and query even when no prim matched.
check(stage, ovstage_release_query_result(stage, &result), "release_query_result");
waitOp(stage, ovstage_release_query(stage, query), "release_query");
Both checks inspect current scene state and do not require a watermark advance. Presence does not guarantee that a value is ready to read; reads still follow the watermark rules. A later operation can change the scene after the query completes.
Selecting Children#
Filter usd-parent by /World/Group to select prims immediately below
that prim, such as /World/Group/Left. This excludes deeper descendants such
as /World/Group/Left/Child. To include all descendants, use FilterOp.PREFIX
on usd-path with /World/Group/.
These snippets count the direct children of /World/Group:
# Match the parent exactly; grandchildren have a different parent.
children_filter = Filter([
Predicate("usd-parent", FilterOp.IN, ["/World/Group"]),
])
with stage.query(filter=children_filter) as query:
query.wait()
child_count = query.result().total_prim_count
print("Direct children of /World/Group:", child_count)
# Direct children of /World/Group: 2
// Match the parent exactly; grandchildren have a different parent.
const ovx_string_t parent = literal_to_ovx_string("/World/Group");
ovstage_predicate_t predicate{};
predicate.attribute.string = literal_to_ovx_string("usd-parent");
predicate.op = OVSTAGE_FILTER_OP_IN;
predicate.values = &parent;
predicate.value_count = 1;
ovstage_filter_t filter{};
filter.predicates = &predicate;
filter.count = 1;
// Wait, inspect the match count, and release the result and query.
ovstage_query_handle_t query = OVSTAGE_INVALID_QUERY_HANDLE;
waitOp(stage, ovstage_query(stage, &filter, nullptr, 0, &query), "query");
ovstage_query_result_t result{};
check(stage, ovstage_fetch_query_result(stage, query, OVSTAGE_TIMEOUT_INFINITE, &result),
"fetch_query_result");
std::printf("Direct children of /World/Group: %llu\n", static_cast<unsigned long long>(result.total_prim_count));
// Direct children of /World/Group: 2
check(stage, ovstage_release_query_result(stage, &result), "release_query_result");
waitOp(stage, ovstage_release_query(stage, query), "release_query");
Requiring Several Attributes#
Use one HAS predicate for each required attribute. These snippets select
prims with both points and faceVertexCounts. Neither predicate checks
the attribute’s type, length, or value. An empty array still counts as present:
# Require both attributes, without comparing their values.
geometry_filter = Filter([
Predicate("points", FilterOp.HAS),
Predicate("faceVertexCounts", FilterOp.HAS),
])
with stage.query(filter=geometry_filter) as query:
query.wait()
geometry_count = query.result().total_prim_count
print("Prims with points and faceVertexCounts:", geometry_count)
# Prims with points and faceVertexCounts: 2
// Separate HAS predicates require both attributes, regardless of their values.
ovstage_predicate_t predicates[2]{};
predicates[0].attribute.string = literal_to_ovx_string("points");
predicates[0].op = OVSTAGE_FILTER_OP_HAS;
predicates[1].attribute.string = literal_to_ovx_string("faceVertexCounts");
predicates[1].op = OVSTAGE_FILTER_OP_HAS;
ovstage_filter_t filter{};
filter.predicates = predicates;
filter.count = 2;
// Wait, inspect the match count, and release the result and query.
ovstage_query_handle_t query = OVSTAGE_INVALID_QUERY_HANDLE;
waitOp(stage, ovstage_query(stage, &filter, nullptr, 0, &query), "query");
ovstage_query_result_t result{};
check(stage, ovstage_fetch_query_result(stage, query, OVSTAGE_TIMEOUT_INFINITE, &result),
"fetch_query_result");
std::printf("Prims with points and faceVertexCounts: %llu\n", static_cast<unsigned long long>(result.total_prim_count));
// Prims with points and faceVertexCounts: 2
check(stage, ovstage_release_query_result(stage, &result), "release_query_result");
waitOp(stage, ovstage_release_query(stage, query), "release_query");
Listing the Matched Paths#
Read usd-path for the selected prims, then resolve the returned path
identifiers through the Path Dictionary. A returned group can include a
data index map, which tells you which tensor row holds each prim’s path
identifier. The snippets below handle this mapping when present, fetch every
group, and release each group after use.
These snippets list the direct children of /World/Group. Python uses the
stage’s PathDictionary as paths and imports OrdinalRange from
ovstage. C uses the stage dictionary as dict; its resolvePath() helper
joins the path’s component tokens into a string. Both examples sort the resulting
strings for display; query order is not a path-sort guarantee.
The snapshot read below uses end ordinal 2 from the example’s update cycle. usd-path
is derived from current structure and is not watermark-gated; the ordinal does
not select historical paths:
# Query the direct children, then read their current path identifiers.
children_filter = Filter([
Predicate("usd-parent", FilterOp.IN, ["/World/Group"]),
])
path_attribute = paths.intern_token("usd-path")
matched_paths = []
with stage.query(filter=children_filter) as query:
query.wait()
with stage.read_attributes(query, [path_attribute], OrdinalRange.latest(2)) as read:
read.wait()
for group in read.groups():
with group: # Release every group after resolving its identifiers.
path_ids = group.array(0)
for i in range(group.prim_count):
row = group.data_row_index(i) if group.has_data_index_map else i
matched_paths.append(paths.path_to_string(int(path_ids[row])))
print("Matched paths:", " ".join(sorted(matched_paths)))
# Matched paths: /World/Group/Left /World/Group/Right
// Query the direct children of /World/Group.
const ovx_string_t parent = literal_to_ovx_string("/World/Group");
ovstage_predicate_t predicate{};
predicate.attribute.string = literal_to_ovx_string("usd-parent");
predicate.op = OVSTAGE_FILTER_OP_IN;
predicate.values = &parent;
predicate.value_count = 1;
ovstage_filter_t filter{};
filter.predicates = &predicate;
filter.count = 1;
ovstage_query_handle_t query = OVSTAGE_INVALID_QUERY_HANDLE;
waitOp(stage, ovstage_query(stage, &filter, nullptr, 0, &query), "query paths");
// usd-path contains prim-path identifiers resolved through the stage dictionary.
const ovx_string_t name = literal_to_ovx_string("usd-path");
ovx_token_t pathAttribute = OVX_INVALID_TOKEN;
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, &name, 1, &pathAttribute),
"intern usd-path");
ovstage_ordinal_range_t range{};
range.end_ordinal = 2; // No start ordinal: read current paths.
ovstage_read_handle_t read = OVSTAGE_INVALID_READ_HANDLE;
waitOp(stage, ovstage_read_attributes(stage, query, &pathAttribute, 1, range, &read), "read paths");
std::vector<std::string> matchedPaths;
for (;;)
{
ovstage_read_group_t group{};
const ovstage_api_status_t fetched =
ovstage_fetch_read_next(stage, read, OVSTAGE_TIMEOUT_INFINITE, &group);
if (fetched == OVSTAGE_ERROR_END_OF_ITERATION)
break;
check(stage, fetched, "fetch_read_next");
const DLTensor& tensor = group.data.tensors[0];
const auto* pathIds = reinterpret_cast<const uint64_t*>(
static_cast<const uint8_t*>(tensor.data) + tensor.byte_offset);
for (uint32_t i = 0; i < group.prims.count; ++i)
{
const uint32_t row = group.data.index_map ? group.data.index_map[i] : i;
matchedPaths.push_back(resolvePath(dict, pathIds[row]));
}
check(stage, ovstage_release_group(stage, &group), "release_group");
}
waitOp(stage, ovstage_release_read(stage, read), "release_read");
waitOp(stage, ovstage_release_query(stage, query), "release_query");
std::sort(matchedPaths.begin(), matchedPaths.end());
std::printf("Matched paths:");
for (const std::string& path : matchedPaths)
std::printf(" %s", path.c_str());
std::printf("\n");
// Matched paths: /World/Group/Left /World/Group/Right
Selecting Prims With an Applied API Schema#
Use FilterOp.CONTAINS on usd-schemas to select prims with an applied
API schema. These snippets find prims with ShadowAPI, which is applied to
/World/Group/Right in the example scene:
from ovstage import Filter, FilterOp, Predicate
# Select prims carrying this applied API schema.
schema_filter = Filter([Predicate("usd-schemas", FilterOp.CONTAINS, ["ShadowAPI"])])
with stage.query(filter=schema_filter) as query:
query.wait()
schema_count = query.result().total_prim_count
print("Prims with ShadowAPI:", schema_count)
# Prims with ShadowAPI: 1
// Select prims carrying this applied API schema.
const ovx_string_t schema = literal_to_ovx_string("ShadowAPI");
ovstage_predicate_t predicate{};
predicate.attribute.string = literal_to_ovx_string("usd-schemas");
predicate.op = OVSTAGE_FILTER_OP_CONTAINS;
predicate.values = &schema;
predicate.value_count = 1;
ovstage_filter_t filter{};
filter.predicates = &predicate;
filter.count = 1;
// Wait for the query before inspecting its result.
ovstage_query_handle_t query = OVSTAGE_INVALID_QUERY_HANDLE;
waitOp(stage, ovstage_query(stage, &filter, nullptr, 0, &query), "query applied schema");
ovstage_query_result_t result{};
check(stage, ovstage_fetch_query_result(stage, query, OVSTAGE_TIMEOUT_INFINITE, &result),
"fetch_query_result");
std::printf("Prims with ShadowAPI: %llu\n",
static_cast<unsigned long long>(result.total_prim_count));
// Prims with ShadowAPI: 1
// The result and query handle have separate lifetimes; release both.
check(stage, ovstage_release_query_result(stage, &result), "release_query_result");
waitOp(stage, ovstage_release_query(stage, query), "release_query");
Listing the Applied API Schemas on a Prim#
Select the prim by its exact path, then read usd-schemas. It returns an
array of token identifiers; resolve each through the Path Dictionary.
The names describe schemas present in the runtime stage, including any added
during population.
These snippets list the schemas on /World/Group/Right. Python uses the
stage dictionary as paths and imports OrdinalRange from ovstage.
The C helper tokenName() resolves a token through that dictionary.
The snapshot uses end ordinal 2, after the example has advanced the
watermark. A later recorded schema change makes this snapshot
unavailable; use UINT64_MAX ((1 << 64) - 1 in Python) for current values:
# Select one prim, then read its array of applied-schema token identifiers.
prim_filter = Filter([Predicate("usd-path", FilterOp.IN, ["/World/Group/Right"])])
schema_attribute = paths.intern_token("usd-schemas")
schema_names = []
with stage.query(filter=prim_filter) as query:
query.wait()
with stage.read_attributes(query, [schema_attribute], OrdinalRange.latest(2)) as read:
read.wait()
for group in read.groups():
with group:
# Array attributes return a separate tensor for each prim.
for i in range(group.prim_count):
for token in group.array(i):
schema_names.append(paths.token_to_string(int(token)))
print("APIs on /World/Group/Right:", " ".join(sorted(schema_names)))
# APIs on /World/Group/Right: MaterialBindingAPI MeshSubdivision ShadowAPI
// Select the prim whose applied schemas we want to inspect.
const ovx_string_t primPath = literal_to_ovx_string("/World/Group/Right");
ovstage_predicate_t predicate{};
predicate.attribute.string = literal_to_ovx_string("usd-path");
predicate.op = OVSTAGE_FILTER_OP_IN;
predicate.values = &primPath;
predicate.value_count = 1;
ovstage_filter_t filter{};
filter.predicates = &predicate;
filter.count = 1;
ovstage_query_handle_t query = OVSTAGE_INVALID_QUERY_HANDLE;
waitOp(stage, ovstage_query(stage, &filter, nullptr, 0, &query), "query prim schemas");
// usd-schemas contains an array of token identifiers for each prim.
const ovx_string_t name = literal_to_ovx_string("usd-schemas");
ovx_token_t schemaAttribute = OVX_INVALID_TOKEN;
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, &name, 1, &schemaAttribute),
"intern usd-schemas");
ovstage_ordinal_range_t range{};
range.end_ordinal = 2; // Snapshot at 2 (no start); use UINT64_MAX for current schemas.
ovstage_read_handle_t read = OVSTAGE_INVALID_READ_HANDLE;
waitOp(stage, ovstage_read_attributes(stage, query, &schemaAttribute, 1, range, &read), "read schemas");
std::vector<std::string> schemaNames;
for (;;)
{
ovstage_read_group_t group{};
const ovstage_api_status_t fetched =
ovstage_fetch_read_next(stage, read, OVSTAGE_TIMEOUT_INFINITE, &group);
if (fetched == OVSTAGE_ERROR_END_OF_ITERATION)
break;
check(stage, fetched, "fetch_read_next");
// Array attributes return a separate tensor for each prim.
for (uint32_t i = 0; i < group.data.tensor_count; ++i)
{
const DLTensor& tensor = group.data.tensors[i];
const auto* tokens = reinterpret_cast<const ovx_token_t*>(
static_cast<const uint8_t*>(tensor.data) + tensor.byte_offset);
for (int64_t j = 0; j < tensor.shape[0]; ++j)
schemaNames.push_back(tokenName(dict, tokens[j]));
}
check(stage, ovstage_release_group(stage, &group), "release_group");
}
waitOp(stage, ovstage_release_read(stage, read), "release_read");
waitOp(stage, ovstage_release_query(stage, query), "release_query");
std::sort(schemaNames.begin(), schemaNames.end());
std::printf("APIs on /World/Group/Right:");
for (const std::string& schemaName : schemaNames)
std::printf(" %s", schemaName.c_str());
std::printf("\n");
// APIs on /World/Group/Right: MaterialBindingAPI MeshSubdivision ShadowAPI
Supported Predicates#
The current implementation supports these combinations. The operator names are
members of FilterOp in Python.
Attribute |
Operator |
Matches |
|---|---|---|
Any supported attribute |
|
Prims where the attribute is present; no value comparison. |
|
|
Any of the exact paths listed. Use several values to check a set of candidate paths. |
|
|
Paths starting with a listed byte prefix; see the trailing-slash rule below. |
|
|
Direct children of any listed parent. |
|
|
Prims with any listed child path. |
|
|
Prims with any listed type, such as |
|
|
Prims with any listed applied API schema, such as |
|
|
Prims with any listed ancestor schema type, such as |
Any versus all: values within one IN predicate match any listed value.
Separate predicates must all match. For example, one usd-prim-type predicate
with ["Mesh", "Cube"] matches either type. Two separate type predicates
requiring Mesh and Cube cannot both match a prim.
For applied schemas and ancestor types, one CONTAINS predicate with several
names matches any;
use separate predicates to require all of them.
Other combinations fail with ErrorCode.NOT_SUPPORTED. In particular, the
current query implementation cannot compare an ordinary attribute’s value,
such as mass > 10. Read the values and filter them in your application.
usd-active is unsupported even with HAS.
query.result().attributes lists discovered attribute tokens. Resolve them
with the path dictionary. The attrs= argument to Stage.query() limits
that discovery; it does not require the attributes to exist. Use HAS for
that requirement.
Built-in Metadata Attributes#
These reserved attributes describe the current scene. All except usd-active
can be read and used in filters:
Attribute |
Contents |
|---|---|
|
The prim’s path. |
|
The prim’s type name. |
|
Applied API schemas. |
|
The types the prim’s type derives from, such as |
|
The parent prim. |
|
The child prims. |
|
Whether the prim is active. Not supported — a live prim is always
active, so predicates naming it return |
A query that names no path restriction spans every prim on the stage. With the
RENDERING domain populated, that currently includes a few prims the runtime
maintains for its own bookkeeping (see Runtime-Owned Prims, a
temporary limitation of that domain). A HAS predicate on usd-prim-type
matches those too, so total_prim_count on such a query is not the number of prims
in the loaded scene. Scope to the scene with a usd-path PREFIX, or match
usd-prim-type IN the types you expect, when the count matters.
usd-path PREFIX Convention#
FilterOp.PREFIX compares the opening bytes of the path string. A value without a
trailing / can match unrelated paths that share the same opening bytes — for
example, PREFIX "/World" also matches /Worldwide. Append a trailing
/ to scope to the subtree under that path: PREFIX "/World/" matches only
descendants of /World; it does not match /World itself.
Further Reading#
Reading Attributes — read columns over a query.
Writing Attributes — write columns over a query.
Path Dictionary — build the path list a query is opened from.