Stage Queries#

Note

Python examples query ovstage directly. Renderer.query_prims* and the C renderer query API are deprecated compatibility surfaces. Refer to skills/update-0_3-0_4-python/SKILL.md.

Stage queries discover prims on the runtime stage and optionally report attribute schema metadata. A typical workflow is:

  1. Query prims by type, attribute existence, or a filter combination.

  2. Inspect returned paths and attribute descriptors.

  3. Reuse the Python query handle, or the returned C prim-list handles, in later reads or writes.

Ovstage filters use predicates such as usd-prim-type and usd-path. The deprecated renderer query supports its existing AND/OR/NOT compatibility shape.

Python Queries#

query = stage.query()
query.wait()
result = query.result()
print(f"matched {result.total_prim_count} prims")
query.release().wait()
mesh_filter = ovstage.Filter([ovstage.Predicate("usd-prim-type", ovstage.FilterOp.IN, ["Mesh"])])
with stage.query(filter=mesh_filter) as meshes:
    mesh_count = meshes.result().total_prim_count
with ovstage.PathDictionary(stage) as paths:
    points = paths.intern_token("points")
    material_binding = paths.intern_token("material:binding")
    mesh_filter = ovstage.Filter([ovstage.Predicate("usd-prim-type", ovstage.FilterOp.IN, ["Mesh"])])
    with stage.query(filter=mesh_filter, attrs=[points, material_binding]) as meshes:
        result = meshes.result()
# 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 filter — matches every populated prim on the
// stage. `query()` is asynchronous; the returned handle is reserved
// synchronously and can be used as input to reads/writes immediately.
ovstage_query_handle_t query_handle = OVSTAGE_INVALID_QUERY_HANDLE;
ovstage_enqueue_result_t eq =
    ovstage_query(stage_, /*filter=*/nullptr, /*attrs=*/nullptr, 0, &query_handle);
ASSERT_EQ(eq.status, OVSTAGE_OK) << format_ovstage_last_error();
docs_wait_ovstage_no_errors(stage_, eq.op_index);

ovstage_query_result_t qr{};
ASSERT_EQ(ovstage_fetch_query_result(stage_, query_handle, OVSTAGE_TIMEOUT_INFINITE, &qr),
          OVSTAGE_OK)
    << format_ovstage_last_error();
printf("matched %zu prims\n", qr.total_prim_count);

// Always release the fetched result then the query handle when done —
// the discovered attribute list and the query itself are separate resources.
ovstage_release_query_result(stage_, &qr);
ovstage_release_query(stage_, query_handle);
// Filter prims by their populated USD type (matched against the built-in
// usd-prim-type metadata column). ovstage's filter is a conjunction of
// predicates; each predicate tests one attribute against one operator +
// a value list.
ovx_string_t mesh_value = ovx_str("Mesh");
ovx_string_t attr_name = ovx_str("usd-prim-type");
ovstage_predicate_t predicate{};
predicate.attribute.string = attr_name;
predicate.op = OVSTAGE_FILTER_OP_IN;
predicate.values = &mesh_value;
predicate.value_count = 1;

ovstage_filter_t filter{};
filter.predicates = &predicate;
filter.count = 1;

ovstage_query_handle_t query_handle = OVSTAGE_INVALID_QUERY_HANDLE;
ovstage_enqueue_result_t eq =
    ovstage_query(stage_, &filter, /*attrs=*/nullptr, 0, &query_handle);
ASSERT_EQ(eq.status, OVSTAGE_OK) << format_ovstage_last_error();
docs_wait_ovstage_no_errors(stage_, eq.op_index);

ovstage_query_result_t qr{};
ASSERT_EQ(ovstage_fetch_query_result(stage_, query_handle, OVSTAGE_TIMEOUT_INFINITE, &qr),
          OVSTAGE_OK)
    << format_ovstage_last_error();
// Match prims that expose an attribute of interest (here "points").
// FILTER_OP_HAS is the schema-existence test — values must be NULL.
ovx_string_t attr_name = ovx_str("points");
ovstage_predicate_t predicate{};
predicate.attribute.string = attr_name;
predicate.op = OVSTAGE_FILTER_OP_HAS;

ovstage_filter_t filter{};
filter.predicates = &predicate;
filter.count = 1;
// Match Mesh or Camera prims, then exclude Camera. The exclusion removes
// a prim that would otherwise match the OR clause. ovrtx_query_prims is
// deprecated in 0.4 but retained for OR / NOT / ALL-attributes queries
// that ovstage's conjunction-only filter cannot yet express.
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#

Ovstage queries can be waited before reading their result and must be released when they are no longer needed:

mesh_filter = ovstage.Filter([ovstage.Predicate("usd-prim-type", ovstage.FilterOp.IN, ["Mesh"])])
meshes = stage.query(filter=mesh_filter)
meshes.wait()
result = meshes.result()
meshes.release().wait()

Path Dictionary#

C query results use token and prim-path ids. In standalone mode, resolve them through the renderer’s path dictionary. In attached mode, obtain the owner-provided dictionary with ovstage_get_path_dictionary(instance). Do not free it or assume dictionaries are shared across instances.

Path lists borrowed from ovstage results remain valid only while the producing handle owns them. Add a path-list reference before releasing the producer when the list must remain usable, and release that reference when finished.

Resolve them while the query results are still valid:

// The stage's path dictionary converts between string paths and internal
// handles. It is owned by ovstage and valid for the instance's lifetime —
// no release is required.
path_dictionary_instance_t* pd = ovstage_get_path_dictionary(stage_);
ASSERT_NE(pd, nullptr);

// 1) Enumerate the prims matched by the query. Read usd-prim-type (any
//    schema-known attribute every populated prim carries would work) and
//    pull the prim list handle off the returned group.
ovx_string_t attr_str = ovx_str("usd-prim-type");
ovx_token_t attr_read_token{};
ASSERT_EQ(path_dictionary_create_tokens_from_strings(pd, &attr_str, 1, &attr_read_token)
              .status,
          OVX_API_SUCCESS);

ovstage_ordinal_range_t range{};
range.end_ordinal = 1;
ovstage_read_handle_t read_handle = OVSTAGE_INVALID_READ_HANDLE;
ovstage_enqueue_result_t reads = ovstage_read_attributes(
    stage_, query_handle, &attr_read_token, 1, range, &read_handle);
ASSERT_EQ(reads.status, OVSTAGE_OK) << format_ovstage_last_error();
docs_wait_ovstage_no_errors(stage_, reads.op_index);

ovstage_read_group_t group{};
ASSERT_EQ(ovstage_fetch_read_next(stage_, read_handle, OVSTAGE_TIMEOUT_INFINITE, &group),
          OVSTAGE_OK);

// Pull the prim list handle from the group. Each entry is an
// ovx_primpath_t handle; decompose to tokens, then to strings.
ovx_primpath_list_t list_handle = group.prims.list;
size_t num_paths = 0;
ASSERT_EQ(path_dictionary_get_num_paths_from_path_list(pd, list_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, list_handle, 0, num_paths,
                                                    prim_paths.data(), &out_num)
              .status,
          OVX_API_SUCCESS);

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 the same count comes back.
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_release_path_list_reference(pd, rebuilt).status, OVX_API_SUCCESS);

ovstage_release_group(stage_, &group);
ovstage_release_read(stage_, read_handle);

Python uses ovstage.PathDictionary to intern attribute tokens and create path lists.

Troubleshooting#

  • Release C query results only after copying any strings, descriptors, or ids you need to keep.

  • AttributeFilterMode.SPECIFIC with an empty attribute-name list returns no descriptors. Use ALL to dump every descriptor or NONE for lightweight discovery.

  • Relationship-valued attributes surface as path ids in C. Resolve them through the path dictionary before printing or storing string paths.