Population (USD → ovstage)#
Population transfers selected, composed USD data into the
runtime stage. Use ovstage.population to load a
scene, apply USD edits, or sample animated values at an ordinal.
Population has its own operation queue and waits, separate from direct
attribute reads and writes. See Asynchronous Operations.
Note
These entry points are pre-release and provisional (“Draft — API in flux”).
Supply an ordinal above the applicable write floors and wait for population to complete. Apply any direct writes that override populated values afterward, using a newer ordinal. Once all updates are complete, advance the write floor and wait for completion before reading or rendering the results. See Choosing Ordinals for sequencing and Ordinal Admission Policies for policy configuration.
Loading a Scene#
Use ovstage.population.open_usd() for a file or
ovstage.population.open_usd_from_string() for USDA text. Both block
until population completes; their _async variants return an operation.
Advance the watermark and wait before reading the values.
The following examples load a scene at a chosen ordinal:
# Load a USD file and populate the runtime table in one op, at the
# caller-owned ordinal 1, then seal that ordinal so reads can see it.
# The application owns the ordinal lifecycle; population never opens
# its own.
population.open_usd(
stage, str(SCENE), ordinal=1, time=0.0, domains=PopulationDomain.RENDERING
)
stage.advance_write_floor(ordinal=1).wait()
// One population op loads the USD file into the runtime table at ordinal 1;
// sealing that ordinal makes it readable. An explicit time code keeps later
// USD-change synchronization on the same supported timeline sample;
// RENDERING selects meshes/lights/cameras.
ovstage_population_enqueue_result_t popEnq =
ovstage_population_open_usd_from_file(stage, ovx_string_t{ scenePath.c_str(), scenePath.size() },
/*ordinal*/ 1, /*time*/ 0.0, OVSTAGE_POPULATION_DOMAIN_RENDERING);
waitPop(stage, popEnq, "open_usd");
ovstage_write_floor_desc_t floor1{};
floor1.ordinal = 1;
floor1.scope = OVSTAGE_SCOPE_ALL;
ovstage_enqueue_result_t enq = ovstage_advance_write_floor(stage, &floor1);
waitOp(stage, enq, "advance_write_floor");
Population Domains#
A population domain is a predefined selection of USD prims and properties
needed by a consuming library. Pass PopulationDomain values through the
domains argument when loading USD:
Domain |
What it selects |
|---|---|
|
Scene data for rendering, including meshes, lights, materials, and cameras. |
|
Scene data for physics, including bodies, colliders, joints, materials, and the geometry and hierarchy they need. |
|
Both |
|
No predefined selection. Use explicit selectors to choose the prims and properties to populate. |
Combine domains with bitwise OR, for example
PopulationDomain.RENDERING | PopulationDomain.PHYSICS. Selecting a domain
loads data; it does not start a renderer or run a physics simulation. Explicit
selectors add to the domain’s selection, as described below.
Physics Selection#
PHYSICS selects prims with supported physics types or applied APIs, including
scenes, rigid bodies, colliders, joints, articulations, and physics materials.
It also includes supporting prims needed to interpret that data:
Data |
What population includes |
|---|---|
Physics properties |
Attributes and relationships whose names are declared by the supported physics schemas. Unrelated geometry and display properties inherited from base schemas are excluded unless a separate rule includes them. |
Shape geometry |
Selected geometry properties for colliders and other supported physics roles, such as mesh points and topology or primitive dimensions. A plain render mesh is not selected just because it has geometry. |
Relationship targets and subsets |
Supporting geometry named by physics relationships, and |
Materials |
Physics material properties and bindings, including prims that author
|
Point instancers |
|
Hierarchy and metadata |
Ancestors and transform data, as described in
Ancestors and Transforms. Stage metadata includes
|
Selected attributes can use schema defaults without an authored value. Some physics defaults are computed instead: for example, an unauthored scene gravity uses the stage’s units and up axis. Population also resolves physics material bindings and supported physics collections for consumers.
Applying an arbitrary custom API does not make its properties part of the physics domain. Add explicit selectors for application attributes. Those selectors extend the physics selection; they do not restrict it.
The examples below use scene.usda from the
population example. It contains a rigid-body Cube
at /World/Body below an Xform at /World. Load its physics data first.
The later snippets use the same stage and paths inside the Python
context managers; in C, keep stage and dict until the end of the example.
scene names the adjacent scene.usda file:
from pathlib import Path
import ovstage
from ovstage import OrdinalRange, PopulationDomain, population
scene = str(Path(__file__).with_name("scene.usda"))
with ovstage.Stage("example.population") as stage, ovstage.PathDictionary(stage) as paths:
# Select physics data, including Body's rigid-body API and its ancestors.
population.open_usd(stage, scene, ordinal=1, domains=PopulationDomain.PHYSICS)
stage.advance_write_floor(ordinal=1).wait()
# Continue the following examples inside these context managers.
waitPop() checks population completion; waitOp() waits for runtime
operations and releases them. check() and checkOvx() stop on errors.
const std::string scene = argc > 1 ? argv[1] : "scene.usda";
const ovx_string_t scenePath{scene.c_str(), scene.size()};
ovstage_instance_desc_t instanceDesc{};
instanceDesc.name = "example.population";
ovstage_instance_t* stage = nullptr;
check(nullptr, ovstage_create_instance(&instanceDesc, &stage), "create_instance");
auto* dict = getPathDictionary(stage);
// Select physics data and wait for population before making it readable.
waitPop(stage, ovstage_population_open_usd_from_file(
stage, scenePath, 1, OVSTAGE_POPULATION_DEFAULT_TIME,
OVSTAGE_POPULATION_DOMAIN_PHYSICS), "open_usd");
ovstage_write_floor_desc_t watermark{};
watermark.scope = OVSTAGE_SCOPE_ALL;
watermark.ordinal = 1;
waitOp(stage, ovstage_advance_write_floor(stage, &watermark), "advance watermark");
// Keep stage and dict alive until all examples finish.
Rendering Selection#
RENDERING builds the scene data needed by the renderer. It handles supported
geometry, lights, cameras, materials, volumes, and render settings, together
with their transforms, hierarchy, and instances. It does not copy every USD
prim and property unchanged.
Property selection depends on the kind of prim:
Data |
What population includes |
|---|---|
Geometry, lights, and cameras |
The attributes handled for that prim type. Values can come from authored opinions, schema defaults, or rendering-specific defaults. Some runtime attributes are derived, such as combined transforms. |
Applied APIs |
Properties of the APIs supported by the relevant rendering handler. Registering and applying an arbitrary API does not guarantee that all its properties are populated. |
Primvars |
Supported primvars are collected separately. They are not restricted to a fixed list of schema attributes. |
Materials and shaders |
Material networks, shader inputs and outputs, and their connections use material-specific population. These properties need not be declared in a registered USD schema. |
|
Their supported rendering properties and an additional pass over supported authored attributes. |
Other custom attributes |
Generally omitted unless explicitly selected or the extra pass described below is enabled. |
Schema-defined properties can therefore be present even when they are absent from the USDA text. Conversely, an authored custom property can be absent from the runtime stage. Attribute presence is not evidence that the value was authored.
For application data, set a population description’s domains to
PopulationDomain.RENDERING and provide explicit selectors as described
below. The selectors add properties; they do not narrow the rendering
domain’s selection.
A boolean populateAllAuthoredAttributes entry in the root or session layer’s
customLayerData, set to true, enables an extra pass over authored attributes on prims
already populated. The hosting application can also enable this behavior.
The extra pass skips attributes
already present or without values, xformOp* and primvars:* attributes,
shader prims, instance proxies, and unsupported types, including asset arrays,
string arrays, and time codes.
Use selectors when you need to name exactly which additional properties to copy.
Selecting Prims and Properties#
Use ovstage.population.Desc for more control. It contains domains
and a list of ovstage.population.Selector objects. The _with_desc
functions accept several descriptions; they combine the domains with OR and
append the selectors in order.
Each selector pairs a prim condition with a property condition. It selects which prims to populate and which of their properties to include. If several selectors match, their property selections are combined. An omitted property condition selects no properties. Selectors can also request prim and property metadata.
Build conditions with ovstage.population.PrimPredicate and
ovstage.population.PropertyPredicate:
Multiple values in one condition mean “match any.” For example,
has_type("Cube", "Sphere")matches either type.has_property()selects a prim by a condition on its properties.
Unsupported kinds, cycles, or graphs exceeding the depth or node limits fail at submission with a diagnostic. In C, predicate nodes and string arrays are borrowed and must remain valid through the call. Python builders retain them.
For the remaining examples, select /World/Body and name the properties to
populate. The description also requests metadata, explained below. Populate
these values at ordinal 2, after the physics-domain load at ordinal 1:
# Select only /World/Body. is_under_path("/World") also includes its descendants.
body_condition = population.PrimPredicate.has_path("/World/Body")
# Request the values used below, plus metadata on those properties.
selector = population.Selector(
prim_predicate=body_condition,
property_predicate=population.PropertyPredicate.has_name(
"size", "temperature", "state", "points", "emptyName", "emptyArray", "extent"
),
prim_metadata_paths=["customData:label"],
property_metadata_paths=["customData:unit"],
)
desc = population.Desc(
domains=PopulationDomain.NONE,
selectors=[selector],
stage_metadata_paths=["timeCodesPerSecond"],
)
population.open_usd_with_desc(
stage, scene, ordinal=2, time=population.DEFAULT_TIME, descs=[desc]
)
stage.advance_write_floor(ordinal=2).wait()
const ovx_string_t bodyPath[] = {literal_to_ovx_string("/World/Body")};
// Match this exact prim; is_under_path would select a subtree instead.
auto bodyCondition = ovstage_population_prim_predicate_has_path(bodyPath, 1);
const ovx_string_t primMetadata[] = {literal_to_ovx_string("customData:label")};
const ovx_string_t propertyMetadata[] = {literal_to_ovx_string("customData:unit")};
ovstage_population_selector_t selector{};
selector.prim_predicate = bodyCondition;
const ovx_string_t properties[] = {
literal_to_ovx_string("size"), literal_to_ovx_string("temperature"), literal_to_ovx_string("state"),
literal_to_ovx_string("points"), literal_to_ovx_string("emptyName"),
literal_to_ovx_string("emptyArray"), literal_to_ovx_string("extent")};
selector.property_predicate = ovstage_population_property_predicate_has_name(properties, 7);
selector.prim_metadata_paths = primMetadata;
selector.prim_metadata_path_count = 1;
selector.property_metadata_paths = propertyMetadata;
selector.property_metadata_path_count = 1;
const ovx_string_t stageMetadata[] = {literal_to_ovx_string("timeCodesPerSecond")};
ovstage_population_desc_t desc{};
desc.domains = OVSTAGE_POPULATION_DOMAIN_NONE;
desc.selectors = &selector;
desc.selector_count = 1;
desc.stage_metadata_paths = stageMetadata;
desc.stage_metadata_path_count = 1;
// Population copies the description and the referenced arrays during submission.
waitPop(stage, ovstage_population_open_usd_from_file_with_desc(
stage, scenePath, 2, OVSTAGE_POPULATION_DEFAULT_TIME, &desc, 1), "open with description");
watermark.ordinal = 2;
waitOp(stage, ovstage_advance_write_floor(stage, &watermark), "advance watermark");
See ovstage.population.PrimPredicateKind and
ovstage.population.PropertyPredicateKind for the supported conditions.
Which Properties Become Runtime Attributes?#
Selecting a prim does not select all its properties. Domains choose the
properties they need; explicit selectors add the properties their conditions
match. Schema membership is not required for an explicit selection: the example
above includes the custom temperature attribute by name.
For explicit selectors and the PHYSICS domain, population reads the selected
properties through USD:
A selected attribute is populated if USD resolves a value and ovstage supports its value type. The value can come from an authored default, a time sample, or a registered schema’s default. It need not be authored in the scene file.
A schema-declared attribute with no resolved value is skipped. Merely declaring or selecting an attribute does not create a runtime value.
A selected relationship is populated only if it has authored targets. An explicitly authored empty target list is included as an empty array.
Schema defaults require the schema definitions to be registered with the USD
runtime used by population. For additional schema families, call
ovstage.population.register_usd_schemas() before any code in the process
first reads USD schema definitions, including population, export, or another USD
consumer. Registration makes those definitions available; it does not select
their properties for population.
Domains can also produce derived data or supply domain-specific defaults. See Rendering Selection for the rendering domain’s selection and conversion rules.
The example scene declares /World/Body as a Cube but never authors size.
The selector above explicitly requests both size and the custom
temperature attribute. Reading them returns the Cube schema’s default size
of 2 and the authored temperature of 20:
with paths.create_path_list_from_strings(["/World/Body"]) as selected:
with stage.query_from_path_list(selected) as query:
with stage.read_attributes(query, [paths.intern_token("size")], OrdinalRange.latest(2)) as read:
read.wait()
for group in read.groups():
with group:
print(float(group.array(0).ravel()[0]))
# 2.0
with paths.create_path_list_from_strings(["/World/Body"]) as selected:
with stage.query_from_path_list(selected) as query:
with stage.read_attributes(query, [paths.intern_token("temperature")], OrdinalRange.latest(2)) as read:
read.wait()
for group in read.groups():
with group:
print(float(group.array(0).ravel()[0]))
# 20.0
{
const ovx_string_t selected[] = {literal_to_ovx_string("/World/Body")};
ovx_primpath_list_t pathList{};
checkOvx(dict, path_dictionary_create_path_list_from_strings(dict, selected, 1, &pathList), "paths");
ovstage_query_handle_t query{};
check(stage, ovstage_query_from_path_list(stage, pathList, &query), "query");
const ovx_string_t name = literal_to_ovx_string("size");
ovx_token_t attribute{};
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, &name, 1, &attribute), "attribute");
ovstage_ordinal_range_t range{};
range.end_ordinal = 2;
ovstage_read_handle_t read{};
waitOp(stage, ovstage_read_attributes(stage, query, &attribute, 1, range, &read), "read");
ovstage_read_group_t group{};
check(stage, ovstage_fetch_read_next(stage, read, OVSTAGE_TIMEOUT_INFINITE, &group), "fetch");
const DLTensor& tensor = group.data.tensors[0];
const auto* values = reinterpret_cast<const double*>(
static_cast<const uint8_t*>(tensor.data) + tensor.byte_offset);
std::printf("%.1f\n", static_cast<double>(values[0]));
// 2.0
// Release the borrowed data before releasing the read and query.
ovstage_release_group(stage, &group);
waitOp(stage, ovstage_release_read(stage, read), "release read");
waitOp(stage, ovstage_release_query(stage, query), "release query");
path_dictionary_release_path_list_reference(dict, pathList);
}
{
const ovx_string_t selected[] = {literal_to_ovx_string("/World/Body")};
ovx_primpath_list_t pathList{};
checkOvx(dict, path_dictionary_create_path_list_from_strings(dict, selected, 1, &pathList), "paths");
ovstage_query_handle_t query{};
check(stage, ovstage_query_from_path_list(stage, pathList, &query), "query");
const ovx_string_t name = literal_to_ovx_string("temperature");
ovx_token_t attribute{};
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, &name, 1, &attribute), "attribute");
ovstage_ordinal_range_t range{};
range.end_ordinal = 2;
ovstage_read_handle_t read{};
waitOp(stage, ovstage_read_attributes(stage, query, &attribute, 1, range, &read), "read");
ovstage_read_group_t group{};
check(stage, ovstage_fetch_read_next(stage, read, OVSTAGE_TIMEOUT_INFINITE, &group), "fetch");
const DLTensor& tensor = group.data.tensors[0];
const auto* values = reinterpret_cast<const float*>(
static_cast<const uint8_t*>(tensor.data) + tensor.byte_offset);
std::printf("%.1f\n", static_cast<double>(values[0]));
// 20.0
// Release the borrowed data before releasing the read and query.
ovstage_release_group(stage, &group);
waitOp(stage, ovstage_release_read(stage, read), "release read");
waitOp(stage, ovstage_release_query(stage, query), "release query");
path_dictionary_release_path_list_reference(dict, pathList);
}
USD Value Types#
The USD value type determines the runtime type. For schema attributes, this is the type declared by the schema:
Numeric values retain their scalar type and bit width. DLPack lanes record the component count.
Arrays use the element’s dtype with
is_array=True. Other values use one fixed-size value per prim.Strings and paths become 64-bit identifiers resolved through the Path Dictionary.
Numeric values authored in a different type are converted to the declared type when possible; see Values Authored in Another Type Than Declared.
The tables describe storage types and canonical write dtypes. Returned reads and maps expand component widths into trailing dimensions with scalar lanes; see DLPack Tensor Exchange.
The tables use {code, bits, lanes} for ovstage.DLDataType and
ovstage.AttributeSemantic names for semantics.
Implicit Conversions#
Population converts some USD values for the runtime stage:
Strings and paths: store identifiers from the Path Dictionary. Exceptions store text bytes; see Properties That Do Not Follow Their USD Type.
Assets: store identifiers for the authored path and the path USD resolved. Generic attribute population does not resolve the asset again; rendering material inputs have the UDIM exception described below.
Empty strings: use identifier 0 when stored as identifiers. See Empty Values.
USD roles: become attribute semantics. For example,
point3fuses three floats withPOINTsemantic.Schema-specific properties: can use a different type or semantic. See Properties That Do Not Follow Their USD Type.
Numeric type mismatches: convert to the schema’s declared type when possible. See Values Authored in Another Type Than Declared.
Transforms: combine USD transform operations into
omni:xform. See Transforms.
Export uses the stored values, including these conversions.
Numeric scalars#
USD type |
dtype |
|
semantic |
|---|---|---|---|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
USD roles change the semantic, not the storage type. For example, point3f
uses {float, 32, 3} with POINT. The corresponding roles are NORMAL
for normal3f, VECTOR for vector3f, COLOR for color3f, and
TEXTURE_COORDINATE for texCoord2f and texCoord3f.
frame4d uses the same storage as matrix4d but has semantic FRAME
instead of MATRIX.
The scene authors float temperature = 20 on /World/Body. After selecting
that property and populating at ordinal 2, read its value:
with paths.create_path_list_from_strings(["/World/Body"]) as selected:
with stage.query_from_path_list(selected) as query:
with stage.read_attributes(query, [paths.intern_token("temperature")], OrdinalRange.latest(2)) as read:
read.wait()
for group in read.groups():
with group:
print(float(group.array(0).ravel()[0]))
# 20.0
{
const ovx_string_t selected[] = {literal_to_ovx_string("/World/Body")};
ovx_primpath_list_t pathList{};
checkOvx(dict, path_dictionary_create_path_list_from_strings(dict, selected, 1, &pathList), "paths");
ovstage_query_handle_t query{};
check(stage, ovstage_query_from_path_list(stage, pathList, &query), "query");
const ovx_string_t name = literal_to_ovx_string("temperature");
ovx_token_t attribute{};
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, &name, 1, &attribute), "attribute");
ovstage_ordinal_range_t range{};
range.end_ordinal = 2;
ovstage_read_handle_t read{};
waitOp(stage, ovstage_read_attributes(stage, query, &attribute, 1, range, &read), "read");
ovstage_read_group_t group{};
check(stage, ovstage_fetch_read_next(stage, read, OVSTAGE_TIMEOUT_INFINITE, &group), "fetch");
const DLTensor& tensor = group.data.tensors[0];
const auto* values = reinterpret_cast<const float*>(
static_cast<const uint8_t*>(tensor.data) + tensor.byte_offset);
std::printf("%.1f\n", static_cast<double>(values[0]));
// 20.0
// Release the borrowed data before releasing the read and query.
ovstage_release_group(stage, &group);
waitOp(stage, ovstage_release_read(stage, read), "release read");
waitOp(stage, ovstage_release_query(stage, query), "release query");
path_dictionary_release_path_list_reference(dict, pathList);
}
String-like scalars#
USD type |
dtype |
|
semantic |
|---|---|---|---|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
An asset stores an (authored, resolved) pair of identifiers. The resolved
value is whatever USD supplied to generic attribute population; that path does
not resolve the asset again. Rendering material inputs containing <UDIM> are
an exception: rendering derives a resolved pattern from a tile that resolves.
If no tile resolves, the resolved half is empty, even if USD supplied an anchored
pattern; the authored half is retained. A string has semantic NONE, so
runtime semantic alone cannot distinguish it from uint64. Consult the USD
type when that distinction matters.
The authored token state = "ready" becomes an identifier. Resolve it through
the path dictionary while the read group is alive:
with paths.create_path_list_from_strings(["/World/Body"]) as selected:
with stage.query_from_path_list(selected) as query:
with stage.read_attributes(query, [paths.intern_token("state")], OrdinalRange.latest(2)) as read:
read.wait()
for group in read.groups():
with group:
token = int(group.array(0).ravel()[0])
print(paths.token_to_string(token))
# ready
{
const ovx_string_t selected[] = {literal_to_ovx_string("/World/Body")};
ovx_primpath_list_t pathList{};
checkOvx(dict, path_dictionary_create_path_list_from_strings(dict, selected, 1, &pathList), "paths");
ovstage_query_handle_t query{};
check(stage, ovstage_query_from_path_list(stage, pathList, &query), "query");
const ovx_string_t name = literal_to_ovx_string("state");
ovx_token_t attribute{};
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, &name, 1, &attribute), "attribute");
ovstage_ordinal_range_t range{};
range.end_ordinal = 2;
ovstage_read_handle_t read{};
waitOp(stage, ovstage_read_attributes(stage, query, &attribute, 1, range, &read), "read");
ovstage_read_group_t group{};
check(stage, ovstage_fetch_read_next(stage, read, OVSTAGE_TIMEOUT_INFINITE, &group), "fetch");
const DLTensor& tensor = group.data.tensors[0];
const auto* values = reinterpret_cast<const uint64_t*>(
static_cast<const uint8_t*>(tensor.data) + tensor.byte_offset);
ovx_token_t token = values[0];
ovx_string_t text{};
checkOvx(dict, path_dictionary_get_strings_from_tokens(dict, &token, 1, &text), "resolve token");
std::printf("%.*s\n", static_cast<int>(text.length), text.ptr);
// ready
// Release the borrowed data before releasing the read and query.
ovstage_release_group(stage, &group);
waitOp(stage, ovstage_release_read(stage, read), "release read");
waitOp(stage, ovstage_release_query(stage, query), "release query");
path_dictionary_release_path_list_reference(dict, pathList);
}
Arrays#
A USD array populates with is_array=True and its element’s dtype and semantic:
float3[] is {float, 32, 3}, and matrix4d[] is {float, 64, 16} with
MATRIX.
token[], string[], and pathExpression[] store one identifier per
element. asset[] stores one pair per element. Their tensor element counts
therefore match the USD array lengths.
Relationship targets use path identifiers with {uint, 64, 1},
RELATIONSHIP_PATH_ID, and is_array=True. An authored relationship with
no targets is present as an empty value, rather than absent.
The scene authors two points in point3f[] points. Read the array from the
one-prim query and view its three components per point:
with paths.create_path_list_from_strings(["/World/Body"]) as selected:
with stage.query_from_path_list(selected) as query:
with stage.read_attributes(query, [paths.intern_token("points")], OrdinalRange.latest(2)) as read:
read.wait()
for group in read.groups():
with group:
print(group.array(0).reshape(-1, 3).tolist())
# [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]
{
const ovx_string_t selected[] = {literal_to_ovx_string("/World/Body")};
ovx_primpath_list_t pathList{};
checkOvx(dict, path_dictionary_create_path_list_from_strings(dict, selected, 1, &pathList), "paths");
ovstage_query_handle_t query{};
check(stage, ovstage_query_from_path_list(stage, pathList, &query), "query");
const ovx_string_t name = literal_to_ovx_string("points");
ovx_token_t attribute{};
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, &name, 1, &attribute), "attribute");
ovstage_ordinal_range_t range{};
range.end_ordinal = 2;
ovstage_read_handle_t read{};
waitOp(stage, ovstage_read_attributes(stage, query, &attribute, 1, range, &read), "read");
ovstage_read_group_t group{};
check(stage, ovstage_fetch_read_next(stage, read, OVSTAGE_TIMEOUT_INFINITE, &group), "fetch");
const DLTensor& tensor = group.data.tensors[0];
// The tensor has shape (2, 3): two points with scalar float32 lanes.
std::printf("points: %lld, lanes: %u\n", static_cast<long long>(tensor.shape[0]), tensor.dtype.lanes);
// points: 2, lanes: 1
// Release the borrowed data before releasing the read and query.
ovstage_release_group(stage, &group);
waitOp(stage, ovstage_release_read(stage, read), "release read");
waitOp(stage, ovstage_release_query(stage, query), "release query");
path_dictionary_release_path_list_reference(dict, pathList);
}
Empty Values#
Where the interned representation applies, empty tokens, strings, path
expressions, and either half of an asset pair use identifier 0. A text-backed
exception such as UsdRenderVar.sourceName uses a zero-length STRING value. Empty array elements keep their position. An authored empty value
is distinct from an absent property.
opaque has no value to store and is skipped without a diagnostic.
The authored empty token emptyName is present with identifier 0:
with paths.create_path_list_from_strings(["/World/Body"]) as selected:
with stage.query_from_path_list(selected) as query:
with stage.read_attributes(query, [paths.intern_token("emptyName")], OrdinalRange.latest(2)) as read:
read.wait()
for group in read.groups():
with group:
print(int(group.array(0).ravel()[0]))
# 0
{
const ovx_string_t selected[] = {literal_to_ovx_string("/World/Body")};
ovx_primpath_list_t pathList{};
checkOvx(dict, path_dictionary_create_path_list_from_strings(dict, selected, 1, &pathList), "paths");
ovstage_query_handle_t query{};
check(stage, ovstage_query_from_path_list(stage, pathList, &query), "query");
const ovx_string_t name = literal_to_ovx_string("emptyName");
ovx_token_t attribute{};
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, &name, 1, &attribute), "attribute");
ovstage_ordinal_range_t range{};
range.end_ordinal = 2;
ovstage_read_handle_t read{};
waitOp(stage, ovstage_read_attributes(stage, query, &attribute, 1, range, &read), "read");
ovstage_read_group_t group{};
check(stage, ovstage_fetch_read_next(stage, read, OVSTAGE_TIMEOUT_INFINITE, &group), "fetch");
const DLTensor& tensor = group.data.tensors[0];
const auto* values = reinterpret_cast<const uint64_t*>(
static_cast<const uint8_t*>(tensor.data) + tensor.byte_offset);
std::printf("%llu\n", static_cast<unsigned long long>(values[0]));
// 0
// Release the borrowed data before releasing the read and query.
ovstage_release_group(stage, &group);
waitOp(stage, ovstage_release_read(stage, read), "release read");
waitOp(stage, ovstage_release_query(stage, query), "release query");
path_dictionary_release_path_list_reference(dict, pathList);
}
An authored float[] emptyArray = [] also produces a read group. Its tensor
has zero elements; an absent property would produce no group:
with paths.create_path_list_from_strings(["/World/Body"]) as selected:
with stage.query_from_path_list(selected) as query:
with stage.read_attributes(query, [paths.intern_token("emptyArray")], OrdinalRange.latest(2)) as read:
read.wait()
for group in read.groups():
with group:
print(group.array(0).tolist())
# []
{
const ovx_string_t selected[] = {literal_to_ovx_string("/World/Body")};
ovx_primpath_list_t pathList{};
checkOvx(dict, path_dictionary_create_path_list_from_strings(dict, selected, 1, &pathList), "paths");
ovstage_query_handle_t query{};
check(stage, ovstage_query_from_path_list(stage, pathList, &query), "query");
const ovx_string_t name = literal_to_ovx_string("emptyArray");
ovx_token_t attribute{};
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, &name, 1, &attribute), "attribute");
ovstage_ordinal_range_t range{};
range.end_ordinal = 2;
ovstage_read_handle_t read{};
waitOp(stage, ovstage_read_attributes(stage, query, &attribute, 1, range, &read), "read");
ovstage_read_group_t group{};
check(stage, ovstage_fetch_read_next(stage, read, OVSTAGE_TIMEOUT_INFINITE, &group), "fetch");
const DLTensor& tensor = group.data.tensors[0];
// The group is present even though its array has no elements.
std::printf("elements: %lld\n", static_cast<long long>(tensor.shape[0]));
// elements: 0
// Release the borrowed data before releasing the read and query.
ovstage_release_group(stage, &group);
waitOp(stage, ovstage_release_read(stage, read), "release read");
waitOp(stage, ovstage_release_query(stage, query), "release query");
path_dictionary_release_path_list_reference(dict, pathList);
}
Properties That Do Not Follow Their USD Type#
The following schema properties use fixed runtime types so that population domains agree when sharing a stage:
Property (where it applies) |
USD type |
populated as |
instead of |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Scalar |
|
|
interned |
On |
|
|
interned |
Scalar |
|
|
interned |
Scalar |
|
|
interned |
|
|
|
|
|
|
|
|
extent and text stored as bytes change dtype or shape. The other exceptions
change only the semantic. Each rule applies only to the schemas or prim types
listed above. string[] attributes and primvars still store identifiers.
These schema-specific rules do not apply to arbitrary properties with the same
name. When rendering populates a non-schema color3f, point3f, normal3f,
vector3f, or texCoord3f property, its authored role is preserved through
value edits. This does not depend on the property’s custom flag. Schema
properties retain their required rendering representation; for example, a light’s
inputs:color still uses VECTOR even if authored with custom = true.
If a value cannot use the required representation (for example, an authored
token where a rule requires string bytes), population skips the value and
logs a warning. The attribute keeps its required type and its last written value;
this conversion mismatch does not fail the population operation.
Stage Metadata#
Population writes requested stage metadata to / as attributes named
usd-metadata:<path>. Domains may request fields; descriptions request them
through stage_metadata_paths. Unrequested fields are not populated.
A metadata path names a field and optional dictionary keys separated by ::
timeCodesPerSecondselects one field.customLayerData:myTool:versionselects a nested value.customLayerDataselects all values in that dictionary, each stored under its own full path.
Missing paths produce no value. Leading, trailing, or repeated : characters
create empty components and are rejected.
The description above requests timeCodesPerSecond. Read the field from
the root path. This scene uses 24 time codes per second:
with paths.create_path_list_from_strings(["/"]) as selected:
with stage.query_from_path_list(selected) as query:
with stage.read_attributes(query, [paths.intern_token("usd-metadata:timeCodesPerSecond")], OrdinalRange.latest(2)) as read:
read.wait()
for group in read.groups():
with group:
print(float(group.array(0).ravel()[0]))
# 24.0
{
const ovx_string_t selected[] = {literal_to_ovx_string("/")};
ovx_primpath_list_t pathList{};
checkOvx(dict, path_dictionary_create_path_list_from_strings(dict, selected, 1, &pathList), "paths");
ovstage_query_handle_t query{};
check(stage, ovstage_query_from_path_list(stage, pathList, &query), "query");
const ovx_string_t name = literal_to_ovx_string("usd-metadata:timeCodesPerSecond");
ovx_token_t attribute{};
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, &name, 1, &attribute), "attribute");
ovstage_ordinal_range_t range{};
range.end_ordinal = 2;
ovstage_read_handle_t read{};
waitOp(stage, ovstage_read_attributes(stage, query, &attribute, 1, range, &read), "read");
ovstage_read_group_t group{};
check(stage, ovstage_fetch_read_next(stage, read, OVSTAGE_TIMEOUT_INFINITE, &group), "fetch");
const DLTensor& tensor = group.data.tensors[0];
const auto* values = reinterpret_cast<const double*>(
static_cast<const uint8_t*>(tensor.data) + tensor.byte_offset);
std::printf("%.1f\n", values[0]);
// 24.0
// Release the borrowed data before releasing the read and query.
ovstage_release_group(stage, &group);
waitOp(stage, ovstage_release_read(stage, read), "release read");
waitOp(stage, ovstage_release_query(stage, query), "release query");
path_dictionary_release_path_list_reference(dict, pathList);
}
Prim and Property Metadata#
Selectors use prim_metadata_paths and property_metadata_paths to request
metadata for selected prims and properties. They use the same path syntax:
Prim metadata becomes
usd-metadata:<path>.Property metadata becomes
<property>:usd-metadata:<path>.
has_metadata() matches the presence of any requested path, including a value
of False. It can be used in prim or property conditions. A dictionary field
matches when any value beneath it resolves, including schema-provided metadata.
Name a specific key, such as customData:myTool:export, when that is what you
need to select.
The selector above requests the prim label and each selected property’s
customData:unit. Read the populated temperature unit:
with paths.create_path_list_from_strings(["/World/Body"]) as selected:
with stage.query_from_path_list(selected) as query:
with stage.read_attributes(query, [paths.intern_token("temperature:usd-metadata:customData:unit")], OrdinalRange.latest(2)) as read:
read.wait()
for group in read.groups():
with group:
print(paths.token_to_string(int(group.array(0).ravel()[0])))
# C
{
const ovx_string_t selected[] = {literal_to_ovx_string("/World/Body")};
ovx_primpath_list_t pathList{};
checkOvx(dict, path_dictionary_create_path_list_from_strings(dict, selected, 1, &pathList), "paths");
ovstage_query_handle_t query{};
check(stage, ovstage_query_from_path_list(stage, pathList, &query), "query");
const ovx_string_t name = literal_to_ovx_string("temperature:usd-metadata:customData:unit");
ovx_token_t attribute{};
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, &name, 1, &attribute), "attribute");
ovstage_ordinal_range_t range{};
range.end_ordinal = 2;
ovstage_read_handle_t read{};
waitOp(stage, ovstage_read_attributes(stage, query, &attribute, 1, range, &read), "read");
ovstage_read_group_t group{};
check(stage, ovstage_fetch_read_next(stage, read, OVSTAGE_TIMEOUT_INFINITE, &group), "fetch");
const DLTensor& tensor = group.data.tensors[0];
const auto* values = reinterpret_cast<const uint64_t*>(
static_cast<const uint8_t*>(tensor.data) + tensor.byte_offset);
ovx_token_t token = values[0];
ovx_string_t text{};
checkOvx(dict, path_dictionary_get_strings_from_tokens(dict, &token, 1, &text), "resolve metadata");
std::printf("%.*s\n", static_cast<int>(text.length), text.ptr);
// C
// Release the borrowed data before releasing the read and query.
ovstage_release_group(stage, &group);
waitOp(stage, ovstage_release_read(stage, read), "release read");
waitOp(stage, ovstage_release_query(stage, query), "release query");
path_dictionary_release_path_list_reference(dict, pathList);
}
The same selector requested customData:label on the prim itself. Read that
value without the property-name prefix:
with paths.create_path_list_from_strings(["/World/Body"]) as selected:
with stage.query_from_path_list(selected) as query:
with stage.read_attributes(query, [paths.intern_token("usd-metadata:customData:label")], OrdinalRange.latest(2)) as read:
read.wait()
for group in read.groups():
with group:
print(paths.token_to_string(int(group.array(0).ravel()[0])))
# robot
{
const ovx_string_t selected[] = {literal_to_ovx_string("/World/Body")};
ovx_primpath_list_t pathList{};
checkOvx(dict, path_dictionary_create_path_list_from_strings(dict, selected, 1, &pathList), "paths");
ovstage_query_handle_t query{};
check(stage, ovstage_query_from_path_list(stage, pathList, &query), "query");
const ovx_string_t name = literal_to_ovx_string("usd-metadata:customData:label");
ovx_token_t attribute{};
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, &name, 1, &attribute), "attribute");
ovstage_ordinal_range_t range{};
range.end_ordinal = 2;
ovstage_read_handle_t read{};
waitOp(stage, ovstage_read_attributes(stage, query, &attribute, 1, range, &read), "read");
ovstage_read_group_t group{};
check(stage, ovstage_fetch_read_next(stage, read, OVSTAGE_TIMEOUT_INFINITE, &group), "fetch");
const DLTensor& tensor = group.data.tensors[0];
const auto* values = reinterpret_cast<const uint64_t*>(
static_cast<const uint8_t*>(tensor.data) + tensor.byte_offset);
ovx_token_t token = values[0];
ovx_string_t text{};
checkOvx(dict, path_dictionary_get_strings_from_tokens(dict, &token, 1, &text), "resolve metadata");
std::printf("%.*s\n", static_cast<int>(text.length), text.ptr);
// robot
// Release the borrowed data before releasing the read and query.
ovstage_release_group(stage, &group);
waitOp(stage, ovstage_release_read(stage, read), "release read");
waitOp(stage, ovstage_release_query(stage, query), "release query");
path_dictionary_release_path_list_reference(dict, pathList);
}
Reserved Prim Attributes#
An ordinary populated prim with a USD type name carries usd-prim-type. A typeless prim
is still populated but has no usd-prim-type; there is no synthetic type name.
These metadata rules also apply when a selector requests no properties.
Outside RENDERING, usd-schemas contains the applied API names, or an empty
set when none apply. HAS therefore matches even an empty set; use
CONTAINS to select a particular API. RENDERING also adds the schemas and
tags described below, including on geometry without authored APIs; it can omit
usd-schemas when neither applied APIs nor rendering tags are present.
A multiple-apply schema records both its instance name and bare name. For
example, CollectionAPI:parts also adds CollectionAPI. Typed export skips
that accompanying bare name rather than treating it as an additional API instance.
For ordinary prims populated through selectors or PHYSICS, these names
come from USD’s type token and composed applied-API list. Use those identifiers,
rather than C++ or Python class names:
USD declaration |
Runtime name |
|---|---|
|
|
|
|
|
|
For example, use Cube, not UsdGeomCube or UsdGeom.Cube, as the type
name in a population predicate. The applied-API list is composed by USD, so it
can include APIs supplied by schema definitions as well as those written in
an apiSchemas statement. Do not rely on its runtime array order.
usd-ancestor-types lists the USD types the prim derives from, excluding
its own type. These are schema ancestors, not parent prims: a Mesh includes
PointBased, Gprim, Boundable, Xformable, Imageable,
Typed, SchemaBase, and TfType::_Root.
Use CONTAINS to select prims by a base type. Typeless and unknown types
have no ancestor set in non-rendering population, even if fallbackPrimTypes
provides a fallback. Live type edits update or remove the set with the prim type.
Outside authored PointInstancer descendants, instance roots use the following
metadata in every population domain, with the RENDERING-only exception below:
usd-prim-typeisXformInstance, orScopeInstancewhen it is aScope, whatever its authored type.usd-schemasalso carriesGeomInstanceAPI, andGeomInstanceRootAPIwhen the instance root is outside a prototype. No USD schema applies these names.usd-ancestor-typesisXformablealone.
With RENDERING alone, a Scope and a non-Scope instance sharing a prototype
may both receive the instance type selected from the first instance encountered.
PHYSICS and selector population classify each root separately, including when
combined with RENDERING. Consumers must therefore not assume identical
usd-prim-type values across these modes for such a shared prototype.
When PHYSICS or a selector populates an instanceable prim authored beneath
a PointInstancer, it keeps its authored type and applied schemas, including
alongside RENDERING. At that authored path it receives neither instance tag
nor the rendering-derived tags listed below. It still links its prototype and
remains discoverable through the Instancing Queries queries. Without RENDERING,
it also receives its normal USD ancestor set. With RENDERING selected,
population does not synthesize usd-ancestor-types at these authored paths;
use their type or APIs rather than relying on an ancestor set. Rendering uses
relocated prototype geometry, whose metadata is a separate representation.
An instance root’s authored properties are populated as for any other prim. A root
populated by PHYSICS or a selector also carries its composed authored type
as usd-metadata:typeName, even when the root itself matches no selector.
Every prim the PHYSICS domain populates also carries usd-metadata:typeName: its
composed USD type name, as authored, including a type no loaded schema declares.
This includes structural ancestors populated to reach physics content. On an
instance root it is the authored type, not XformInstance or ScopeInstance. Live
edits update it, and a prim that loses its type name loses the attribute. A prim with no
type name carries none. A desc selector publishes the same attribute when it selects the
typeName metadata path.
Wherever the PHYSICS domain or a selector publishes a PointInstancer’s
prototypes relationship, population also publishes its authored targets, in authored
order, as prototypes:usd-targets: the list protoIndices indexes, whichever domains are
selected. Live edits update it, including an edit that clears the targets.
When the RENDERING domain is selected, the following hold for every prim it populates,
including prims a selector or the PHYSICS domain also populates:
A
PointInstancer’sprototypeslists the geometryRENDERINGrenders for its prototypes, at paths beneath the instancer, not the authored relationship’s targets.protoIndicesdoes not index that list. The authored targets are inprototypes:usd-targetswhenPHYSICSor a selector also publishes the instancer’sprototypes. Itsextentis the bounds of those prototypes, not the authoredextent.usd-schemasincludesMaterialBindingAPIon supported geometry andDomeLight, even without an authored material binding. This is a recognized USD API and typed export applies it. Instance roots are excluded; material-binding subsets are included only under a non-instanceMesh.usd-schemasalso carries rendering tags that are not applied USD APIs:PrimvarsAPIon a prim with a primvar that has a value, other than a string array, or with an authorednormalsattribute.MeshSubdivisionon aMeshwhose subdivision scheme, boundary or face-varying interpolation, or triangle subdivision rule differs from its default, or that has creases or corners, and on everyTetMesh.SkelBindingAPI_Geomon aMeshoutside a prototype, or on curves, with aprimvars:skel:jointIndicesattribute.SkelBindingAPI_Skelon a non-instanceSkeleton.GeomPrototypeAPIon supported geometry inside a USD prototype, excluding the prototype root and instance roots.Light-attribute tags such as
LightVisibleInPrimaryRayAPI,LightGhostFactorAPI,LightDisableFogInteractionAPI,LightIsProjectorAPI,LightProjectorDirectionTextureAPI,LightEnableCausticsAPI,LightUsdLuxVersionAPI,DomeLightFiniteAPI, andLightShapingForwardAxisAPI, when their rendering attributes apply.
These tags are preserved when RENDERING is combined with selectors or
PHYSICS. They are not added at authored paths beneath a PointInstancer.
Rendering population supplies the ancestor set when selected, including alongside
selectors, except at the authored PointInstancer descendant paths described
above. Its other current exceptions are:
Typeless prims carry
Imageable.Instance proxies carry only
Xformable(instance-root rules are above).GeomSubsetand USD prototype prims carry no ancestor types.Unknown types resolved to geometry through
fallbackPrimTypescarry that fallback’s ancestors. With rendering alone the prim type is also the fallback; a selector can retain the source type name instead.LODSelector, which has no USD schema, carriesImageable.
Writing usd-prim-type does not derive a new ancestor set. If the new type
already appears in usd-ancestor-types, it is removed from that set because a
prim’s own type is excluded. When changing a type directly, also write its
matching ancestor set. Both this set and usd-schemas are replaced as a whole
by a write. Typed export does
not author usd-ancestor-types. Custom properties named after USD types or
API schemas, such as Boundable on a Mesh, are unsupported.
USD scene-graph instance roots use XformInstance or ScopeInstance as
described above. With RENDERING population (including ALL), their
populated contents can also use types such as MeshInstance and
CubeInstance: a Mesh beneath an instance root can become MeshInstance. You author ordinary USD prims
with references and instanceable = true; population generates these runtime
types. You do not author MeshInstance or XformInstance in USD.
A PointInstancer does not generate one such prim per point. Instanceable
prims authored beneath it keep their authored metadata as described above;
rendering’s relocated prototype geometry is a separate representation.
See Instancing Queries for scene-graph instancing examples and queries.
Consequently, a runtime usd-prim-type value can differ from the source USD
type. Population predicates inspect the source USD prim; runtime queries
inspect the populated stage.
Read the populated type of /World/Body and resolve its token:
with paths.create_path_list_from_strings(["/World/Body"]) as selected:
with stage.query_from_path_list(selected) as query:
with stage.read_attributes(query, [paths.intern_token("usd-prim-type")], OrdinalRange.latest(2)) as read:
read.wait()
for group in read.groups():
with group:
print(paths.token_to_string(int(group.array(0).ravel()[0])))
# Cube
{
const ovx_string_t selected[] = {literal_to_ovx_string("/World/Body")};
ovx_primpath_list_t pathList{};
checkOvx(dict, path_dictionary_create_path_list_from_strings(dict, selected, 1, &pathList), "paths");
ovstage_query_handle_t query{};
check(stage, ovstage_query_from_path_list(stage, pathList, &query), "query");
const ovx_string_t name = literal_to_ovx_string("usd-prim-type");
ovx_token_t attribute{};
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, &name, 1, &attribute), "attribute");
ovstage_ordinal_range_t range{};
range.end_ordinal = 2;
ovstage_read_handle_t read{};
waitOp(stage, ovstage_read_attributes(stage, query, &attribute, 1, range, &read), "read");
ovstage_read_group_t group{};
check(stage, ovstage_fetch_read_next(stage, read, OVSTAGE_TIMEOUT_INFINITE, &group), "fetch");
const DLTensor& tensor = group.data.tensors[0];
const auto* values = reinterpret_cast<const uint64_t*>(
static_cast<const uint8_t*>(tensor.data) + tensor.byte_offset);
ovx_token_t token = values[0];
ovx_string_t text{};
checkOvx(dict, path_dictionary_get_strings_from_tokens(dict, &token, 1, &text), "resolve type");
std::printf("%.*s\n", static_cast<int>(text.length), text.ptr);
// Cube
// Release the borrowed data before releasing the read and query.
ovstage_release_group(stage, &group);
waitOp(stage, ovstage_release_read(stage, read), "release read");
waitOp(stage, ovstage_release_query(stage, query), "release query");
path_dictionary_release_path_list_reference(dict, pathList);
}
Read usd-schemas to find the applied APIs. This scene applies
PhysicsRigidBodyAPI to /World/Body:
with paths.create_path_list_from_strings(["/World/Body"]) as selected:
with stage.query_from_path_list(selected) as query:
with stage.read_attributes(query, [paths.intern_token("usd-schemas")], OrdinalRange.latest(2)) as read:
read.wait()
for group in read.groups():
with group:
print([paths.token_to_string(int(token)) for token in group.array(0).ravel()])
# ['PhysicsRigidBodyAPI']
{
const ovx_string_t selected[] = {literal_to_ovx_string("/World/Body")};
ovx_primpath_list_t pathList{};
checkOvx(dict, path_dictionary_create_path_list_from_strings(dict, selected, 1, &pathList), "paths");
ovstage_query_handle_t query{};
check(stage, ovstage_query_from_path_list(stage, pathList, &query), "query");
const ovx_string_t name = literal_to_ovx_string("usd-schemas");
ovx_token_t attribute{};
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, &name, 1, &attribute), "attribute");
ovstage_ordinal_range_t range{};
range.end_ordinal = 2;
ovstage_read_handle_t read{};
waitOp(stage, ovstage_read_attributes(stage, query, &attribute, 1, range, &read), "read");
ovstage_read_group_t group{};
check(stage, ovstage_fetch_read_next(stage, read, OVSTAGE_TIMEOUT_INFINITE, &group), "fetch");
const DLTensor& tensor = group.data.tensors[0];
const auto* values = reinterpret_cast<const uint64_t*>(
static_cast<const uint8_t*>(tensor.data) + tensor.byte_offset);
// One token per applied API schema, stored in a USD array.
for (int64_t i = 0; i < tensor.shape[0]; ++i)
{
ovx_token_t token = values[i];
ovx_string_t text{};
checkOvx(dict, path_dictionary_get_strings_from_tokens(dict, &token, 1, &text), "resolve type");
std::printf("%.*s\n", static_cast<int>(text.length), text.ptr);
// PhysicsRigidBodyAPI
}
// Release the borrowed data before releasing the read and query.
ovstage_release_group(stage, &group);
waitOp(stage, ovstage_release_read(stage, read), "release read");
waitOp(stage, ovstage_release_query(stage, query), "release query");
path_dictionary_release_path_list_reference(dict, pathList);
}
Runtime-Owned Prims#
Rendering population keeps bookkeeping prims alongside the scene. They are not
USD content and remain after reset_usd() or removal of a reference. Unfiltered
queries count them too. Other population domains do not create these prims.
To enumerate scene content, filter by its root path or expected types; see Queries. Their paths, types, and count are implementation details. Do not write to them.
Path Conditions#
has_path() and is_under_path() accept absolute prim paths, including
/. Property, target, and variant-selection paths fail at submission. Neither
condition matches prims inside a native instance.
For an exact match, use has_path() as the selector’s prim condition:
# Select only /World/Body. is_under_path("/World") also includes its descendants.
body_condition = population.PrimPredicate.has_path("/World/Body")
const ovx_string_t bodyPath[] = {literal_to_ovx_string("/World/Body")};
// Match this exact prim; is_under_path would select a subtree instead.
auto bodyCondition = ovstage_population_prim_predicate_has_path(bodyPath, 1);
To select Cube prims below /World, combine a subtree condition with a type
condition. This example also includes the PHYSICS domain: the selector adds
size to the domain’s selection. It runs at ordinal 7, after the time-update
examples below have used ordinals 3 through 6:
# Add size for Cube prims below /World to the predefined physics selection.
subtree_selector = population.Selector(
prim_predicate=population.PrimPredicate.and_(
population.PrimPredicate.is_under_path("/World"),
population.PrimPredicate.has_type("Cube"),
),
property_predicate=population.PropertyPredicate.has_name("size"),
)
population.open_usd_with_desc(
stage, scene, ordinal=7, time=population.DEFAULT_TIME,
descs=[population.Desc(domains=PopulationDomain.PHYSICS, selectors=[subtree_selector])],
)
stage.advance_write_floor(ordinal=7).wait()
// Add size for Cube prims below /World to the predefined physics selection.
const ovx_string_t roots[] = {literal_to_ovx_string("/World")};
const ovx_string_t types[] = {literal_to_ovx_string("Cube")};
const ovx_string_t subtreeProperties[] = {literal_to_ovx_string("size")};
const ovstage_population_prim_predicate_t conditions[] = {
ovstage_population_prim_predicate_is_under_path(roots, 1),
ovstage_population_prim_predicate_has_type(types, 1)};
ovstage_population_selector_t subtreeSelector{};
subtreeSelector.prim_predicate = ovstage_population_prim_predicate_and(conditions, 2);
subtreeSelector.property_predicate = ovstage_population_property_predicate_has_name(subtreeProperties, 1);
ovstage_population_desc_t subtreeDesc{};
subtreeDesc.domains = OVSTAGE_POPULATION_DOMAIN_PHYSICS;
subtreeDesc.selectors = &subtreeSelector;
subtreeDesc.selector_count = 1;
waitPop(stage, ovstage_population_open_usd_from_file_with_desc(
stage, scenePath, 7, OVSTAGE_POPULATION_DEFAULT_TIME, &subtreeDesc, 1), "populate subtree");
watermark.ordinal = 7;
waitOp(stage, ovstage_advance_write_floor(stage, &watermark), "advance watermark");
Ancestors and Transforms#
The PHYSICS domain and explicit selectors also populate ancestors of selected
prims so consumers can follow the hierarchy. An ancestor that no selector matches
gets the reserved attributes above and, if USD-xformable, transform data.
It does not receive other attributes, relationships, or metadata, except
usd-metadata:typeName on ancestors populated by PHYSICS and on instance
roots populated by PHYSICS or a selector.
For each USD-xformable prim selected this way, including ancestors, population writes:
omni:xform— composed local USD transform, stored as a row-major 4×4float64matrix with 16 lanes and semanticMATRIX.omni:resetXformStack— a boolean indicating whether the prim ignores its parent’s transform.
When RENDERING is not selected, population also creates
omni:fabric:worldMatrix if absent, initially using the local matrix. Later
world-transform values come from ovstage.Stage.compute_hierarchy();
population does not overwrite them. A prim that stops being xformable loses this
attribute. When RENDERING is selected, that domain manages it instead.
Treat the provisionally named world-matrix attribute as read-only output. Hierarchy computation updates existing rows but does not create them. Non-xformable prims receive none of these derived attributes, and being xformable does not by itself select a prim for population. See Transforms.
These names belong to population. A USD property named omni:xform,
omni:resetXformStack, or omni:fabric:worldMatrix is never
published, even when a selector names or matches it, on any prim.
Selecting /World/Body also populates the transform of its ancestor
/World. Its authored translation is 10 along X:
with paths.create_path_list_from_strings(["/World"]) as selected:
with stage.query_from_path_list(selected) as query:
with stage.read_attributes(query, [paths.intern_token("omni:xform")], OrdinalRange.latest(2)) as read:
read.wait()
for group in read.groups():
with group:
print(group.array(0).reshape(4, 4)[3, :3].tolist())
# [10.0, 0.0, 0.0]
{
const ovx_string_t selected[] = {literal_to_ovx_string("/World")};
ovx_primpath_list_t pathList{};
checkOvx(dict, path_dictionary_create_path_list_from_strings(dict, selected, 1, &pathList), "paths");
ovstage_query_handle_t query{};
check(stage, ovstage_query_from_path_list(stage, pathList, &query), "query");
const ovx_string_t name = literal_to_ovx_string("omni:xform");
ovx_token_t attribute{};
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, &name, 1, &attribute), "attribute");
ovstage_ordinal_range_t range{};
range.end_ordinal = 2;
ovstage_read_handle_t read{};
waitOp(stage, ovstage_read_attributes(stage, query, &attribute, 1, range, &read), "read");
ovstage_read_group_t group{};
check(stage, ovstage_fetch_read_next(stage, read, OVSTAGE_TIMEOUT_INFINITE, &group), "fetch");
const DLTensor& tensor = group.data.tensors[0];
const auto* values = reinterpret_cast<const double*>(
static_cast<const uint8_t*>(tensor.data) + tensor.byte_offset);
// Row-major matrices store translation in the last row.
std::printf("World translation: %.1f %.1f %.1f\n", values[12], values[13], values[14]);
// World translation: 10.0 0.0 0.0
// Release the borrowed data before releasing the read and query.
ovstage_release_group(stage, &group);
waitOp(stage, ovstage_release_read(stage, read), "release read");
waitOp(stage, ovstage_release_query(stage, query), "release query");
path_dictionary_release_path_list_reference(dict, pathList);
}
Rebuilding from the Current USD Source#
population.repopulate_usd_with_desc()
rewrites selected attributes from the current USD source at the supplied
ordinal. Use it to replace runtime edits with USD values. Pass time
in seconds, or ovstage.population.DEFAULT_TIME for default opinions.
Use population.repopulate_usd_with_desc_async()
for asynchronous execution.
Repopulation keeps unselected prims and attributes, including runtime-created data. Delete them explicitly if they must be removed. The supplied descriptions also set the population scope for later updates.
The USD stage, root and session layers, edits, composition state, edit target, and reference handles stay valid.
For incremental updates, use
population.apply_usd_changes() or
population.update_from_usd_time(). These
rewrite changed or time-sampled USD values, so they can leave runtime edits in
place on other attributes.
Editing the USD Source#
Reference edits change the USD source first. Apply them to the runtime stage at a new ordinal:
population.add_usd_reference()oradd_usd_reference_from_string()adds a reference. An absolute-root target adds a sublayer; a prim target adds a reference at that prim.population.remove_usd()removes a reference by its returned handle.population.reset_usd()clears the USD source.population.apply_usd_changes()transfers pending edits to the runtime stage.
The reference target decides what the next apply republishes. A not-yet-existing prim path republishes only the new prim and the content beneath it. An existing prim path republishes that prim and every prim beneath it, including prims the reference does not add or change; the absolute root republishes the whole stage. Every selected attribute of a republished prim is written at the apply ordinal even when its value is unchanged, so a read of that ordinal range reports it as changed, and the written value replaces any value written directly into the runtime stage since. To add content without republishing existing prims, target a not-yet-existing prim path.
Note
Clearing all USD values of a populated application attribute, with no fallback
remaining, removes its runtime column on the next apply_usd_changes.
With RENDERING enabled, renderer-retained columns are exceptions: schema
attributes, primvars, properties of shader and node-graph prims, and authored
properties of RenderSettings, RenderProduct, and RenderVar prims.
These examples update the USD source, apply its changes, and read the result:
# Reference a cube onto a new /World/EditCube in the USD source, then
# propagate the change into the runtime table with apply_usd_changes
# at a fresh ordinal (above the animation's floor). The runtime stage
# now sees a prim that existed only in USD.
usd_ordinal = 2 + FRAMES
population.add_usd_reference_from_string(stage, EDIT_CUBE_USDA, "/World/EditCube")
population.apply_usd_changes(stage, ordinal=usd_ordinal)
stage.advance_write_floor(ordinal=usd_ordinal).wait()
expanded_paths = paths.create_path_list_from_strings(
["/World", "/World/Plane", "/World/Torus", "/World/EditCube"])
expanded_query = stage.query_from_path_list(expanded_paths)
print("after USD edit, prim types:",
", ".join(_prim_types(stage, paths, expanded_query, prim_type, usd_ordinal)))
// Reference a cube onto a new /World/EditCube in the USD source, then
// propagate the edit into the runtime table with apply_usd_changes at a
// fresh ordinal above the animation's floor. add-reference carries no
// ordinal; apply_usd_changes does.
const ovstage_ordinal_t usdOrdinal = static_cast<ovstage_ordinal_t>(2 + kFrames);
const ovx_string_t editLayer = kEditCubeUsda;
// The optional out-handle is only needed for a later remove_usd_reference;
// this reference lives for the stage's lifetime, so pass NULL (as the
// Python sibling does by discarding the returned handle).
popEnq = ovstage_population_add_usd_reference_from_string(stage, editLayer,
literal_to_ovx_string("/World/EditCube"), nullptr);
waitPop(stage, popEnq, "add_usd_reference");
popEnq = ovstage_population_apply_usd_changes(stage, usdOrdinal);
waitPop(stage, popEnq, "apply_usd_changes");
ovstage_write_floor_desc_t usdFloor{};
usdFloor.ordinal = usdOrdinal;
usdFloor.scope = OVSTAGE_SCOPE_ALL;
enq = ovstage_advance_write_floor(stage, &usdFloor);
waitOp(stage, enq, "advance_write_floor");
const ovx_string_t expanded[] = { literal_to_ovx_string("/World"), literal_to_ovx_string("/World/Plane"),
literal_to_ovx_string("/World/Torus"),
literal_to_ovx_string("/World/EditCube") };
ovx_primpath_list_t expandedPaths = OVX_INVALID_PRIMPATH_LIST;
ovxResult = path_dictionary_create_path_list_from_strings(dict, expanded, 4, &expandedPaths);
checkOvx(dict, ovxResult, "create-path-list");
ovstage_query_handle_t expandedQuery = OVSTAGE_INVALID_QUERY_HANDLE;
status = ovstage_query_from_path_list(stage, expandedPaths, &expandedQuery);
check(stage, status, "query_from_path_list");
const std::vector<std::string> editedTypes = readPrimTypes(stage, dict, expandedQuery, primType, usdOrdinal);
// Example plumbing: print the type names on one line; the Cube is the propagated edit.
std::printf("after USD edit, prim types:");
for (const std::string& name : editedTypes)
std::printf(" %s", name.c_str());
std::printf("\n");
Advancing Time#
Use ovstage.population.update_from_usd_time() to sample animated USD
values at a new time and write them at a new ordinal. It uses the domains and
selectors from the opening call, including selectors with domains=NONE.
Edits to time samples are picked up without a separate apply_usd_changes().
The time argument is in seconds, converted using the stage’s
timeCodesPerSecond. Pass population.DEFAULT_TIME for USD’s default time
code; it is also the default for the basic open functions. The _with_desc
functions require an explicit time.
DEFAULT_TIME is NaN, so test it with population.is_default_time() rather
than ==. In C, use OVSTAGE_POPULATION_DEFAULT_TIME and
ovstage_population_is_default_time().
Reapplying the same time rewrites in-scope time-sampled properties in every domain, including at default time. It is not a no-op.
Use apply_usd_changes() for structural edits and changes to properties that
are not time samples. The RENDERING domain currently propagates these during
time updates too; other domains do not. Do not rely on that difference.
The example temperature has samples of 20 at USD time code 0 and 30 at time code 24. Sample one second at ordinal 3, then read 30:
# One second is USD time code 24 in this scene. The ordinal is independent.
population.update_from_usd_time(stage, time=1.0, ordinal=3)
stage.advance_write_floor(ordinal=3).wait()
with paths.create_path_list_from_strings(["/World/Body"]) as selected:
with stage.query_from_path_list(selected) as query:
with stage.read_attributes(query, [paths.intern_token("temperature")], OrdinalRange.latest(3)) as read:
read.wait()
for group in read.groups():
with group:
print(float(group.array(0).ravel()[0]))
# 30.0
// Seconds are converted using timeCodesPerSecond=24, independently of ordinal 3.
waitPop(stage, ovstage_population_apply_usd_time(stage, 3, 1.0), "sample one second");
watermark.ordinal = 3;
waitOp(stage, ovstage_advance_write_floor(stage, &watermark), "advance watermark");
{
const ovx_string_t selected[] = {literal_to_ovx_string("/World/Body")};
ovx_primpath_list_t pathList{};
checkOvx(dict, path_dictionary_create_path_list_from_strings(dict, selected, 1, &pathList), "paths");
ovstage_query_handle_t query{};
check(stage, ovstage_query_from_path_list(stage, pathList, &query), "query");
const ovx_string_t name = literal_to_ovx_string("temperature");
ovx_token_t attribute{};
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, &name, 1, &attribute), "attribute");
ovstage_ordinal_range_t range{};
range.end_ordinal = 3;
ovstage_read_handle_t read{};
waitOp(stage, ovstage_read_attributes(stage, query, &attribute, 1, range, &read), "read");
ovstage_read_group_t group{};
check(stage, ovstage_fetch_read_next(stage, read, OVSTAGE_TIMEOUT_INFINITE, &group), "fetch");
const DLTensor& tensor = group.data.tensors[0];
const auto* values = reinterpret_cast<const float*>(
static_cast<const uint8_t*>(tensor.data) + tensor.byte_offset);
std::printf("%.1f\n", static_cast<double>(values[0]));
// 30.0
// Release the borrowed data before releasing the read and query.
ovstage_release_group(stage, &group);
waitOp(stage, ovstage_release_read(stage, read), "release read");
waitOp(stage, ovstage_release_query(stage, query), "release query");
path_dictionary_release_path_list_reference(dict, pathList);
}
Python ↔ C Name Mapping#
Several Python wrappers use shorter names than their C entry points:
Python ( |
C entry point |
|---|---|
Choosing Ordinals#
The application assigns ordinals to population operations and seals completed
updates with advance_write_floor. Population never chooses, increments, or
seals an ordinal for you. A tick may use several ordinals; seal through the final
one once its updates complete. An operation at or below an applicable floor is
a write-floor violation under every admission policy.
Python requires an explicit ordinal for open_usd (file/URL),
open_usd_from_string, apply_usd_changes, and their _async variants.
Pass it positionally or as ordinal=.... The former
implicit value of 1 has been removed; calls that omit it raise TypeError.
The _with_desc and update_from_usd_time helpers already require an
explicit ordinal.
A newly created instance already has a write floor of 0, so the first population must use an ordinal of at least 1 — 0 is never a usable write ordinal, even though it is a perfectly valid ordinal to read at. An application whose frame counter starts at 0 should offset it rather than pass 0 through.
Use increasing ordinals for successive updates to the same data. For example,
open at ordinal 1, propagate later USD edits with apply_usd_changes at 2,
and update time-sampled properties with update_from_usd_time at 3. Wait for
each operation before starting the next, then seal through ordinal 3. Use higher
values when other writers or floors have already advanced the stage.
If you apply direct overrides after population, the same sequence applies: populate at 1, wait, write the overrides at 2, wait, then seal through 2. See Ordinal Admission Policies for the policy options and their configuration.
After using ordinals 1 through 3, start the next updates at 4 even though the frame counter starts at 0:
# Population has already used ordinals 1, 2, and 3 above.
for frame in range(3):
ordinal = 4 + frame
population.update_from_usd_time(stage, time=frame / 24.0, ordinal=ordinal)
stage.advance_write_floor(ordinal=ordinal).wait()
# Consumers can now read through this ordinal before the next update.
// Start after the three ordinals already used in this example.
for (int frame = 0; frame < 3; ++frame)
{
const ovstage_ordinal_t ordinal = 4 + frame;
waitPop(stage, ovstage_population_apply_usd_time(stage, ordinal, frame / 24.0), "sample frame");
watermark.ordinal = ordinal;
waitOp(stage, ovstage_advance_write_floor(stage, &watermark), "advance watermark");
// Consumers can now read through this ordinal before the next update.
}
Repopulating a Stage#
Opening a new USD source adds or updates its data without removing values left by the previous source. Prims missing from the new scene remain, as do old attributes that the new scene no longer publishes.
To replace previously populated content:
Call
population.reset_usd(stage)to clear the USD source.Call
population.apply_usd_changes(stage, ordinal=n)to remove its populated content. Prims created only by direct application writes remain.Call
population.open_usd(stage, path, ordinal=n + 1)to load the replacement.
Wait for each step before starting the next. The blocking Python functions do
this; with asynchronous calls or C, observe each operation’s completion.
The following examples clear and reload a small Cube scene. CUBE_USDA in
Python and kCubeUsda in C hold the scene text. Their test helpers count prims
under /World to check removal:
# reset_usd clears the USD source; apply_usd_changes propagates the cleared
# state into the stage. The stage stays usable afterwards — repopulating from
# USD still works.
population.reset_usd(stage)
population.apply_usd_changes(stage, ordinal=2)
assert _prefix_count(stage, "/World") == 0 # cleared before repopulating
population.open_usd_from_string(stage, CUBE_USDA, ordinal=3, domains=PopulationDomain.RENDERING)
// reset_usd clears the USD source; apply_usd_changes propagates the cleared
// state. The stage stays usable afterwards — repopulating from USD still works.
ASSERT_TRUE(waitPopOk(stage_, ovstage_population_reset_usd(stage_), "reset_usd"));
ASSERT_TRUE(waitPopOk(stage_, ovstage_population_apply_usd_changes(stage_, 2), "apply_usd_changes"));
EXPECT_EQ(prefixCount("/World"), 0u); // cleared before repopulating
ASSERT_TRUE(waitPopOk(stage_,
ovstage_population_open_usd_from_string(stage_, str(kCubeUsda), 3, NAN, OVSTAGE_POPULATION_DOMAIN_RENDERING),
"reopen"));
To clear the whole stage, including application-created prims, use an unfiltered
stage.query(), wait, then call stage.delete_attributes(query, [], n).
An empty attribute list deletes the selected prims. Wait for deletion and release
the query before opening the new source. In C, the same operations are
ovstage_query() with no filter and ovstage_delete_attributes() with no
attribute names, followed by ovstage_release_query().
Use a fresh ordinal for deletion and another for population. Wait for deletion to complete before starting population.
Failure and the Ordinal#
A submission failure means the operation never ran. It leaves previously
populated content unchanged, so the same ordinal can be retried if it still
satisfies current watermarks, admission policy, and stage state. In C, this is
the enqueue result’s status.
A failure while waiting may follow partial writes or removal of previous
content. Retry population at a higher ordinal. Python raises OvstageError;
in C, inspect ovstage_population_get_last_error() after the failed
ovstage_population_wait_op(). The status code alone does not show how much
work completed.
This example deliberately opens a missing file, distinguishes rejection from failure during execution, and retries with the valid scene:
ordinal = 1
try:
operation = population.open_usd_async(
stage, scene + ".missing", ordinal=ordinal, domains=PopulationDomain.PHYSICS
)
except ovstage.OvstageError:
# Rejected at submission: no writes ran, so this ordinal can be reused.
retry_ordinal = ordinal
else:
try:
operation.wait()
except ovstage.OvstageError:
# Accepted but failed while running: allow for partial writes.
retry_ordinal = ordinal + 1
else:
raise RuntimeError("Expected the missing file to fail")
# Fix the input and retry at the chosen ordinal.
population.open_usd(stage, scene, ordinal=retry_ordinal, domains=PopulationDomain.PHYSICS)
stage.advance_write_floor(ordinal=retry_ordinal).wait()
const std::string missing = scene + ".missing";
const ovstage_ordinal_t ordinal = 1;
ovstage_ordinal_t retryOrdinal = ordinal;
const auto operation = ovstage_population_open_usd_from_file(
stage, {missing.c_str(), missing.size()}, ordinal,
OVSTAGE_POPULATION_DEFAULT_TIME, OVSTAGE_POPULATION_DOMAIN_PHYSICS);
if (operation.status == OVSTAGE_OK)
{
ovstage_population_op_wait_result_t result{};
const auto status = ovstage_population_wait_op(
stage, operation.op_index, OVSTAGE_TIMEOUT_INFINITE, &result);
// A successful wait can still report a failed operation.
if (status == OVSTAGE_OK && result.error_op_id_count == 0)
{
std::fprintf(stderr, "Expected the missing-file load to fail\n");
ovstage_destroy_instance(stage);
return EXIT_FAILURE;
}
retryOrdinal = ordinal + 1; // accepted: allow for partial writes
}
// Submission rejection leaves retryOrdinal unchanged: no writes ran.
// Read the diagnostic before the retry replaces it.
const ovx_string_t error = ovstage_population_get_last_error();
std::fprintf(stderr, "Population failed: %.*s\n", static_cast<int>(error.length), error.ptr);
// Population failed: <diagnostic for the missing file>
waitPop(stage, ovstage_population_open_usd_from_file(
stage, scenePath, retryOrdinal, OVSTAGE_POPULATION_DEFAULT_TIME,
OVSTAGE_POPULATION_DOMAIN_PHYSICS), "retry with valid file");
watermark.ordinal = retryOrdinal;
waitOp(stage, ovstage_advance_write_floor(stage, &watermark), "advance watermark");
Further Reading#
Runtime Loop — the full populate → read → update → read loop.
Exporting ovstage to USD — author selected runtime state back into a USD layer.
Reading Attributes — reading
usd-prim-typeand other populated metadata back.Instancing Queries — discovering instance and prototype roots in populated content.