Exporting ovstage to USD#
Export writes selected runtime stage values into USD. Use it to save simulation results or create a layer containing selected updates. It does not reconstruct the original USD layers or continuously synchronize them.
The snippets come from the USD export example.
Its runtime-created /World/Ball has a transform, temperature, radius, and
recorded Sphere type. The examples export at ordinal 1 after advancing the
watermark to 1; the changed-property example then writes at ordinal 2.
Python’s directory is a temporary output directory. C uses prefix to
name output files. The typed-hierarchy and mesh snippets use a separate
USD-populated stage.
Export uses runtime values at the requested ordinal, including writes made after population. It does not reread values from the USD source.
Population conversions carry through
to export. For example, a schema-declared int[] populated from uint[]
exports as int[]. A double converted to float retains only float
precision. Values omitted during population can be exported only if they were
added to the runtime stage another way.
Choosing a Destination#
Save once:
population.export_to_usd_file()creates an empty destination, exports, saves, and closes it. A successful save replaces any existing stored content. For populated USD subtrees, preferpopulation.export_typed_hierarchy_to_usd_file(). Neither needspxrPython bindings.Accumulate several exports: use
population.ExportDestinationand calldestination.save()explicitly.ExportDestinationMode.EMPTYstarts empty;OPEN_EXISTINGpreserves an existing layer stack and authors its root layer. Closing or leaving awithblock does not save.Edit a host-owned USD object:
population.export_to_usd_layer()authors the specified layer.population.export_to_usd_stage()opens the root-layer identifier and uses the default edit target; it does not preserve a host’s custom edit target. The host must save the result.
Python’s in-memory wrappers pass identifiers to the native API, not pxr
objects. A layer or stage from an unrelated USD build, including standalone
usd-core, is not compatible just because its identifier matches.
For a single saved layer, export the transform and inspect the report:
import math
from pathlib import Path
from tempfile import TemporaryDirectory
import numpy as np
from ovstage import AttributeSemantic, OvstageError, PathDictionary, PopulationDomain, Stage, population
# A one-shot export creates, saves, and closes an overlay layer.
report = population.export_to_usd_file(
stage, str(directory / "transforms.usda"), ordinal=1,
attribute_rules=["omni:xform"],
)
print("Transforms exported:", report["transforms_exported"])
# Transforms exported: 1
check() and checkOvx() stop on errors; waitOp() waits for runtime
operations and releases them. createStage() creates the source instance.
ovstage_population_export_attribute_rule_t transformRule{};
transformRule.source_attribute_name = literal_to_ovx_string("omni:xform");
ovstage_population_export_desc_t transformDesc{};
check(stage, ovstage_population_export_desc_init_snapshot(&transformDesc, 1), "init export");
transformDesc.attribute_rules = &transformRule;
transformDesc.attribute_rule_count = 1;
const std::string transformFile = prefix + "transforms.usda";
ovstage_population_export_report_t report{};
// One-shot export creates, saves, and closes the overlay layer.
check(stage, ovstage_population_export_to_usd_file(stage,
{transformFile.c_str(), transformFile.size()}, &transformDesc, &report), "export transforms");
std::printf("Transforms exported: %zu\n", report.transforms_exported);
// Transforms exported: 1
Destination Lifetime#
Keep the source stage alive until its reusable destinations are closed. Only
one active destination in the process can own a canonical identifier. Use
with or close(); dropping a live destination attempts cleanup without
saving and emits ResourceWarning.
A reusable destination remains open after save(). A failed export leaves
its prior in-memory state unchanged. A failed save may already have changed
storage; portable atomic replacement is not guaranteed.
Direct layer/stage-identifier exports have different rules. Synchronous calls edit in place, bypass the population/export queue, and provide no rollback; serialize access yourself. During an asynchronous export, the host must not read or write the destination USD object until the operation completes.
Create and close a destination explicitly when you need to control its lifetime. This example discards its export by closing without saving:
usd = population.ExportDestination.create(stage, str(directory / "discarded.usda"))
try:
usd.export(ordinal=1, attribute_rules=["omni:xform"])
# No save: discard this export when the destination closes.
finally:
usd.close() # Close before destroying the source stage.
const std::string discardedFile = prefix + "discarded.usda";
ovstage_population_export_destination_handle_t discarded{};
check(stage, ovstage_population_export_destination_create(stage,
{discardedFile.c_str(), discardedFile.size()}, OVSTAGE_POPULATION_EXPORT_DESTINATION_EMPTY,
&discarded), "create destination");
check(stage, ovstage_population_export_to_destination(stage, discarded, &transformDesc, nullptr), "export");
// No save: destroy discards this export. Do this before destroying the source stage.
check(stage, ovstage_population_export_destination_destroy(stage, discarded), "close destination");
Export a Typed USD Hierarchy#
For a populated subtree, the typed-hierarchy helper uses
recorded USD types and schemas. These examples preserve Sphere and Cube types,
each prim’s applied Physics API schema, and supported schema-declared attributes.
The source scene contains /World/Props/Ball (Sphere with
PhysicsCollisionAPI), /World/Props/Crate (Cube with
PhysicsRigidBodyAPI), and prims outside that subtree. Here destination
is the output file path, and C calls the populated stage runtime. Export
only the subtree:
report = population.export_typed_hierarchy_to_usd_file(
stage,
str(destination),
"/World/Props",
ordinal=1,
)
text = destination.read_text(encoding="utf-8")
assert 'def Sphere "Ball"' in text
assert 'def Cube "Crate"' in text
assert "PhysicsCollisionAPI" in text
assert "PhysicsRigidBodyAPI" in text
assert "OutsideSelection" not in text
const ovx_string_t root = literal_to_ovx_string("/World/Props");
ovstage_population_export_desc_t exportDesc{};
check(runtime,
ovstage_population_export_desc_init_typed_hierarchy(&exportDesc, 1, &root),
"export_desc_init_typed_hierarchy");
ovstage_population_export_report_t report{};
const ovx_string_t destinationId{ destination, std::strlen(destination) };
check(runtime,
ovstage_population_export_to_usd_file(runtime, destinationId, &exportDesc, &report),
"export_to_usd_file");
std::ifstream output(destination, std::ios::binary);
const std::string text((std::istreambuf_iterator<char>(output)), std::istreambuf_iterator<char>());
if (text.find("def Sphere \"Ball\"") == std::string::npos ||
text.find("def Cube \"Crate\"") == std::string::npos ||
text.find("OutsideSelection") != std::string::npos)
{
std::fprintf(stderr, "the exported USD file does not contain the selected typed hierarchy\n");
std::exit(EXIT_FAILURE);
}
The helper selects the root and descendants, creates DEF prims using
usd-prim-type, applies recognized usd-schemas, and exports supported
schema-declared attributes under their existing names. It also handles
omni:xform. For an instance root with synthetic XformInstance or
ScopeInstance type, it recovers the authored USD type and schema-declared
attributes from usd-metadata:typeName when present (PHYSICS or selector
population). A root populated only by RENDERING lacks that metadata and
exports as a typeless prim when its runtime type is synthetic.
The synthetic GeomInstanceAPI
and GeomInstanceRootAPI tags are not applied as USD schemas. This exports
the root’s selected values without recreating its instance reference.
RENDERING adds MaterialBindingAPI on supported geometry and DomeLight,
even without an authored material binding. Typed export applies this recognized API along
with recorded Physics APIs, so exported apiSchemas can contain more names
than were authored on the source USD prims.
Custom attributes require include_custom_namespaces=[...].
In C, keep the initializer’s root ovx_string_t and its character buffer valid
through the synchronous export or successful asynchronous submission. The
asynchronous call copies the descriptor and its referenced data.
Export Runtime-Created Data with Rules#
Data created by direct writes may lack USD type and schema metadata. Use explicit rules to choose its USD representation. First write the example’s transform and scalar attributes, then advance the watermark. The later runtime-data snippets run inside the same Python context managers:
with Stage("example.usd-export.runtime") as stage, PathDictionary(stage) as paths:
with paths.create_path_list_from_strings(["/World/Ball"]) as selected:
with stage.query_from_path_list(selected) as query:
matrix = np.eye(4, dtype=np.float64)[None, :, :]
matrix[0, 3, :3] = [1, 2, 3]
stage.write_attribute(query, "omni:xform", ordinal=1, tensors=matrix,
is_array=False, semantic=AttributeSemantic.MATRIX).wait()
stage.write_attribute(query, "userProperties:temperature", ordinal=1,
tensors=np.array([20], dtype=np.float32), is_array=False).wait()
stage.write_attribute(query, "solver:collisionRadius", ordinal=1,
tensors=np.array([2.5], dtype=np.float64), is_array=False).wait()
# Record the USD type so DEF exports can create a Sphere.
stage.write_attribute(query, "usd-prim-type", ordinal=1,
tensors=np.array([paths.intern_token("Sphere")], dtype=np.uint64),
is_array=False).wait()
stage.advance_write_floor(ordinal=1).wait()
# Run the later snippets inside these same context managers.
ovstage_instance_t* stage = createStage("example.usd-export.runtime");
auto* dict = getPathDictionary(stage);
const ovx_string_t selected[] = {literal_to_ovx_string("/World/Ball")};
ovx_primpath_list_t paths{};
checkOvx(dict, path_dictionary_create_path_list_from_strings(dict, selected, 1, &paths), "paths");
ovstage_query_handle_t query{};
check(stage, ovstage_query_from_path_list(stage, paths, &query), "query");
const ovx_string_t names[] = {literal_to_ovx_string("omni:xform"),
literal_to_ovx_string("userProperties:temperature"), literal_to_ovx_string("solver:collisionRadius"),
literal_to_ovx_string("usd-prim-type"), literal_to_ovx_string("Sphere")};
ovx_token_t tokens[5]{};
checkOvx(dict, path_dictionary_create_tokens_from_strings(dict, names, 5, tokens), "tokens");
int64_t shape[] = {1};
double matrix[] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 2, 3, 1};
float temperature = 20;
double radius = 2.5;
uint64_t sphereType = tokens[4];
DLTensor tensors[4]{};
void* values[] = {matrix, &temperature, &radius, &sphereType};
const DLDataType types[] = {{kDLFloat, 64, 16}, {kDLFloat, 32, 1},
{kDLFloat, 64, 1}, {kDLUInt, 64, 1}};
// Four fixed-size attributes on one prim. Record the type for later DEF exports.
for (int i = 0; i < 4; ++i)
{
tensors[i].data = values[i];
tensors[i].device = {kDLCPU, 0};
tensors[i].ndim = 1;
tensors[i].shape = shape;
tensors[i].dtype = types[i];
auto write = writeData(&tensors[i], i == 0 ? OVSTAGE_SEMANTIC_MATRIX : OVSTAGE_SEMANTIC_NONE);
waitOp(stage, ovstage_write_attribute(stage, query, {tokens[i], {}}, 1, write,
OVSTAGE_PRIM_MODE_UPSERT), "write");
}
ovstage_write_floor_desc_t watermark{};
watermark.ordinal = 1;
watermark.scope = OVSTAGE_SCOPE_ALL;
waitOp(stage, ovstage_advance_write_floor(stage, &watermark), "advance watermark");
// Keep this source stage, query, and dictionary alive for the remaining examples.
Export the transform as USD transform operations on an OVER prim:
# A one-shot export creates, saves, and closes an overlay layer.
report = population.export_to_usd_file(
stage, str(directory / "transforms.usda"), ordinal=1,
attribute_rules=["omni:xform"],
)
print("Transforms exported:", report["transforms_exported"])
# Transforms exported: 1
ovstage_population_export_attribute_rule_t transformRule{};
transformRule.source_attribute_name = literal_to_ovx_string("omni:xform");
ovstage_population_export_desc_t transformDesc{};
check(stage, ovstage_population_export_desc_init_snapshot(&transformDesc, 1), "init export");
transformDesc.attribute_rules = &transformRule;
transformDesc.attribute_rule_count = 1;
const std::string transformFile = prefix + "transforms.usda";
ovstage_population_export_report_t report{};
// One-shot export creates, saves, and closes the overlay layer.
check(stage, ovstage_population_export_to_usd_file(stage,
{transformFile.c_str(), transformFile.size()}, &transformDesc, &report), "export transforms");
std::printf("Transforms exported: %zu\n", report.transforms_exported);
// Transforms exported: 1
This creates an overlay layer, not a standalone typed scene. The recorded
Sphere type is used later when an export requests DEF prims.
Reusable Destination#
Accumulate a transform and a temperature in the same destination, then save
both together. Closing without save() discards unsaved changes:
with population.ExportDestination.create(stage, str(directory / "combined.usda")) as usd:
usd.export(ordinal=1, attribute_rules=["omni:xform"])
usd.export(ordinal=1, attribute_rules=[{
"source_attribute_name": "userProperties:temperature",
"usd_type_name": "float",
}])
# Save both exports together. The destination remains open until block exit.
usd.save()
const std::string combinedFile = prefix + "combined.usda";
ovstage_population_export_destination_handle_t usd{};
check(stage, ovstage_population_export_destination_create(stage,
{combinedFile.c_str(), combinedFile.size()}, OVSTAGE_POPULATION_EXPORT_DESTINATION_EMPTY,
&usd), "create destination");
check(stage, ovstage_population_export_to_destination(stage, usd, &transformDesc, nullptr), "export transform");
ovstage_population_export_attribute_rule_t temperatureRule{};
temperatureRule.source_attribute_name = literal_to_ovx_string("userProperties:temperature");
temperatureRule.usd_type_name = literal_to_ovx_string("float");
ovstage_population_export_desc_t temperatureDesc{};
check(stage, ovstage_population_export_desc_init_snapshot(&temperatureDesc, 1), "init export");
temperatureDesc.attribute_rules = &temperatureRule;
temperatureDesc.attribute_rule_count = 1;
check(stage, ovstage_population_export_to_destination(stage, usd, &temperatureDesc, nullptr), "export temperature");
// Persist both exports, then release the destination. Save itself leaves it open.
check(stage, ovstage_population_export_destination_save(stage, usd), "save");
check(stage, ovstage_population_export_destination_destroy(stage, usd), "close destination");
Copy Custom Attributes Without Renaming#
An empty destination name reuses the source name. This glob rule copies
userProperties:* without listing each attribute and enables type inference
for custom data. The example saves the baseline temperature of 20:
population.export_to_usd_file(
stage, str(directory / "custom.usda"), ordinal=1,
attribute_rules=[{
"source_attribute_name": "userProperties:*",
"source_attribute_name_match": population.OVSTAGE_POPULATION_EXPORT_NAME_MATCH_GLOB,
# Infer the USD type and fail rather than silently skip this data.
"flags": (
population.OVSTAGE_POPULATION_EXPORT_RULE_INFER_CUSTOM_TYPE
| population.OVSTAGE_POPULATION_EXPORT_RULE_REQUIRED
),
}],
)
ovstage_population_export_attribute_rule_t customRule{};
customRule.source_attribute_name = literal_to_ovx_string("userProperties:*");
customRule.source_attribute_name_match = OVSTAGE_POPULATION_EXPORT_NAME_MATCH_GLOB;
// An empty destination name preserves each matched source name.
customRule.flags = OVSTAGE_POPULATION_EXPORT_RULE_INFER_CUSTOM_TYPE | OVSTAGE_POPULATION_EXPORT_RULE_REQUIRED;
ovstage_population_export_desc_t customDesc{};
check(stage, ovstage_population_export_desc_init_snapshot(&customDesc, 1), "init export");
customDesc.attribute_rules = &customRule;
customDesc.attribute_rule_count = 1;
const std::string customFile = prefix + "custom.usda";
check(stage, ovstage_population_export_to_usd_file(stage,
{customFile.c_str(), customFile.size()}, &customDesc, nullptr), "export custom attributes");
Map Runtime Data to a USD Schema#
Map solver:collisionRadius to Sphere.radius. The rule names the USD type;
DEF mode uses the recorded usd-prim-type to create the Sphere:
radius_rule = {
"source_attribute_name": "solver:collisionRadius",
"destination_attribute_name": "radius",
"usd_type_name": "double",
"flags": population.OVSTAGE_POPULATION_EXPORT_RULE_REQUIRED,
}
population.export_to_usd_file(
stage, str(directory / "sphere.usda"), ordinal=1,
layer_mode=population.OVSTAGE_POPULATION_EXPORT_LAYER_MODE_DEF,
attribute_rules=[radius_rule],
)
# The recorded usd-prim-type creates a Sphere with radius 2.5.
ovstage_population_export_attribute_rule_t radiusRule{};
radiusRule.source_attribute_name = literal_to_ovx_string("solver:collisionRadius");
radiusRule.destination_attribute_name = literal_to_ovx_string("radius");
radiusRule.usd_type_name = literal_to_ovx_string("double");
radiusRule.flags = OVSTAGE_POPULATION_EXPORT_RULE_REQUIRED;
ovstage_population_export_desc_t radiusDesc{};
check(stage, ovstage_population_export_desc_init_snapshot(&radiusDesc, 1), "init export");
radiusDesc.layer_mode = OVSTAGE_POPULATION_EXPORT_LAYER_MODE_DEF;
radiusDesc.attribute_rules = &radiusRule;
radiusDesc.attribute_rule_count = 1;
const std::string sphereFile = prefix + "sphere.usda";
check(stage, ovstage_population_export_to_usd_file(stage,
{sphereFile.c_str(), sphereFile.size()}, &radiusDesc, nullptr), "export Sphere radius");
// The recorded usd-prim-type creates a Sphere with radius 2.5.
Value Representation#
Booleans: scalar and array values accept both {bool, 8, 1} and
{uint, 8, 1}. Use {bool, 8, 1} for boolean metadata: a single zero byte
with {uint, 8, 1} is interpreted as an empty string, not False.
Quaternions: ovstage.AttributeSemantic.QUATERNION uses four floating-point
components in (x, y, z, w) order, with the real component last. The 16-, 32-,
and 64-bit forms export as quath, quatf, and quatd, including arrays.
This applies to explicit export types, schema attributes, and inferred types.
An explicit quaternion rule with ovstage.AttributeSemantic.NONE instead
uses (w, x, y, z). If you reordered components to work around older exports,
remove that workaround when using QUATERNION.
Empty arrays: selected token, asset, and numeric tuple arrays keep their USD array type and empty value. Values matching schema defaults may still be omitted. In C, an empty array may have a null data pointer; a nonempty array needs valid storage.
Root selection: selecting / includes the whole stage. The root itself
supplies only explicitly selected layer metadata; it does not export as a prim
or supply prim attributes, transforms, API schemas, or prim metadata. Layer
metadata must match both the description’s prim predicate and the rule’s paths.
Export a Typed Mesh Subtree#
The typed helper also exports mesh geometry. This example selects a subtree
containing Floor and Dynamic, two triangle meshes with different Physics
API schemas. Both have points, face counts, and face indices in the source USD.
Python writes into temp_dir, its temporary output directory. C adds a suffix
to destination, the hierarchy output path:
mesh_report = population.export_typed_hierarchy_to_usd_file(
stage, str(Path(temp_dir) / "meshes.usda"), "/World/SimulatedMeshes", ordinal=1,
)
print("Mesh APIs exported:", mesh_report["api_schemas_applied"])
# Mesh APIs exported: 5
const ovx_string_t meshRoot = literal_to_ovx_string("/World/SimulatedMeshes");
ovstage_population_export_desc_t meshDesc{};
check(runtime, ovstage_population_export_desc_init_typed_hierarchy(&meshDesc, 1, &meshRoot), "init mesh export");
const std::string meshFile = std::string(destination) + ".meshes.usda";
ovstage_population_export_report_t meshReport{};
check(runtime, ovstage_population_export_to_usd_file(runtime,
{meshFile.c_str(), meshFile.size()}, &meshDesc, &meshReport), "export meshes");
std::printf("Mesh APIs exported: %zu\n", meshReport.api_schemas_applied);
// Mesh APIs exported: 5
Each mesh keeps its own recorded schemas: the static mesh has collision only,
and the dynamic mesh also has PhysicsRigidBodyAPI. Token and token-array
values require interned token identifiers and a schema or rule that supplies the USD token or token[] type. Mesh examples
include orientation and subdivisionScheme.
A UTF-8 byte attribute with AttributeSemantic.STRING exports as one USD
string under a string rule or custom type inference. Every byte is part of
the value: do not append a C-style NUL terminator. USD cannot store embedded
NUL bytes, so such a value is unsupported (an error for a REQUIRED rule).
Without a semantic, the existing byte-string path still accepts one trailing
NUL as a terminator.
Typed export resolves token ID 0 as the empty token and skips it when it matches
the schema default, unless EXPORT_DEFAULT_VALUE requests that value.
Distant-light purpose retains authored or inherited non-default values.
Relationships and connections need explicit property rules. Single-target connections written directly to ovstage can be exported. Population does not yet create those columns from USD connections, and multiple connection targets cannot yet be represented without losing information.
Export Only Changed Properties#
After exporting a baseline, select properties changed in
(since_ordinal, ordinal]. The lower bound is excluded and the upper bound
included. Keep the baseline in the layer stack when consuming the update layer.
The custom-attribute example saved temperature 20 at ordinal 1. Change it to 24.5 at ordinal 2, then export only that change:
# custom.usda already holds the baseline temperature 20 at ordinal 1.
with paths.create_path_list_from_strings(["/World/Ball"]) as selected:
with stage.query_from_path_list(selected) as query:
stage.write_attribute(query, "userProperties:temperature", ordinal=2,
tensors=np.array([24.5], dtype=np.float32), is_array=False).wait()
stage.advance_write_floor(ordinal=2).wait()
changed = population.export_to_usd_file(
stage, str(directory / "changed.usda"), ordinal=2, since_ordinal=1,
selection=population.OVSTAGE_POPULATION_EXPORT_SELECTION_CHANGED_PROPERTIES_SINCE_ORDINAL,
attribute_rules=[{
"source_attribute_name": "userProperties:temperature",
"usd_type_name": "float",
}],
)
print("Changed attributes exported:", changed["attributes_exported"])
# Changed attributes exported: 1
// custom.usda already holds the baseline temperature 20 at ordinal 1.
// tensors[1] points to temperature; tokens[1] names userProperties:temperature.
// query still selects the single prim /World/Ball.
temperature = 24.5f;
waitOp(stage, ovstage_write_attribute(stage, query, {tokens[1], {}}, 2,
writeData(&tensors[1]), OVSTAGE_PRIM_MODE_UPSERT), "update temperature");
watermark.ordinal = 2;
waitOp(stage, ovstage_advance_write_floor(stage, &watermark), "advance watermark");
auto changedDesc = temperatureDesc;
changedDesc.ordinal = 2;
changedDesc.since_ordinal = 1;
changedDesc.selection = OVSTAGE_POPULATION_EXPORT_SELECTION_CHANGED_PROPERTIES_SINCE_ORDINAL;
const std::string changedFile = prefix + "changed.usda";
ovstage_population_export_report_t changed{};
check(stage, ovstage_population_export_to_usd_file(stage,
{changedFile.c_str(), changedFile.size()}, &changedDesc, &changed), "export changes");
std::printf("Changed attributes exported: %zu\n", changed.attributes_exported);
// Changed attributes exported: 1
Export Options#
Pass the current global watermark as the export’s source
ordinal; stale or future ordinals are rejected. Export builds
stage-wide snapshot context at that ordinal before applying its selection.
An unrelated attribute with a recorded change above the watermark can therefore
make even a narrowly selected export fail with INVALID_ARGUMENT. Wait for
pending writers and seal their changes before exporting. A newer ordinal cannot
bypass this requirement until the global watermark reaches it; historical
payload versions are not available.
In C, initialize descriptors with
ovstage_population_export_desc_init_snapshot() or
ovstage_population_export_desc_init(). struct_size identifies the header
layout; it does not make older layouts compatible automatically. Python wrappers
initialize the descriptor for you.
Selection#
selection normally uses EXPLICIT_PREDICATE to select current values by
conditions and rules. CHANGED_PROPERTIES_SINCE_ORDINAL also restricts ordinary
properties to changes in (since_ordinal, ordinal]. It writes current USD
default values, not intermediate time samples or a complete standalone scene.
prim_predicate and property_predicate use the builders described in
Population (USD → ovstage), but export supports a subset. Prim conditions can match
paths, subtrees, types, and schemas. Property conditions can match names,
namespaces, schema declarations, and attribute/relationship kinds. Combine
supported conditions with and_(), or_(), and not_(). Conditions that
require information absent from the runtime stage, such as is_authored(),
are rejected with NOT_SUPPORTED.
Rules determine how selected data is written to USD; schema-declared export
can include supported properties without individual attribute rules.
Export only userProperties:temperature on /World/Ball. The predicate
selects the property; the rule supplies its USD representation:
population.export_to_usd_file(
stage, str(directory / "selected.usda"), ordinal=1,
prim_predicate=population.PrimPredicate.has_path("/World/Ball"),
property_predicate=population.PropertyPredicate.in_namespace("userProperties"),
attribute_rules=[{
"source_attribute_name": "userProperties:temperature",
"usd_type_name": "float",
}],
)
auto selectedDesc = temperatureDesc; // Explicit rule: temperature becomes USD float.
const ovx_string_t selectedPath[] = {literal_to_ovx_string("/World/Ball")};
const ovx_string_t namespaces[] = {literal_to_ovx_string("userProperties")};
selectedDesc.prim_predicate = ovstage_population_prim_predicate_has_path(selectedPath, 1);
selectedDesc.property_predicate = ovstage_population_property_predicate_in_namespace(namespaces, 1);
const std::string selectedFile = prefix + "selected.usda";
check(stage, ovstage_population_export_to_usd_file(stage,
{selectedFile.c_str(), selectedFile.size()}, &selectedDesc, nullptr), "export selected data");
Rule Tables#
Rule |
Authors |
Important controls |
|---|---|---|
Prim |
Prim specs and optional type names |
Exact/prefix path, |
API schema |
Single- or multiple-apply API schemas |
Schema name, instance name, required flag |
Attribute |
Values and recognized transforms |
Exact/glob source name, rename, explicit/schema/inferred USD type |
Property edge |
Relationships, material bindings, or connections |
Explicit property kind and destination value type for connections |
Metadata |
Prim metadata, layer metadata/custom data, or references |
Explicit destination kind and optional key rename |
Set population.OVSTAGE_POPULATION_EXPORT_RULE_REQUIRED when a rule that
cannot match, convert, or author must fail the export. Optional rules increase
the skipped or unsupported counters instead. Custom attribute type inference
requires population.OVSTAGE_POPULATION_EXPORT_RULE_INFER_CUSTOM_TYPE;
otherwise supply an explicit USD type.
For example, this required attribute rule renames the solver radius and gives
it the USD double type. The mapping example above uses this rule:
radius_rule = {
"source_attribute_name": "solver:collisionRadius",
"destination_attribute_name": "radius",
"usd_type_name": "double",
"flags": population.OVSTAGE_POPULATION_EXPORT_RULE_REQUIRED,
}
ovstage_population_export_attribute_rule_t radiusRule{};
radiusRule.source_attribute_name = literal_to_ovx_string("solver:collisionRadius");
radiusRule.destination_attribute_name = literal_to_ovx_string("radius");
radiusRule.usd_type_name = literal_to_ovx_string("double");
radiusRule.flags = OVSTAGE_POPULATION_EXPORT_RULE_REQUIRED;
Layer and Transform Options#
layer_mode defaults to OVER for overlay layers. DEF creates
prim definitions, choosing types from prim rules or usd-prim-type.
Use SourceApiSchemaPolicy.APPLY_RECORDED to apply each prim’s recognized
usd-schemas and ExportProjection.SCHEMA_DECLARED to export attributes
declared by those schemas or the concrete type. Explicit rules take precedence
and remain necessary for renaming, custom namespaces, relationships, connections,
and metadata. Use explicit schema rules when the destination should have different
schemas. unknown_metadata_policy controls unrecognized or malformed metadata.
The default transform_policy converts supported local transforms into USD
transform operations when exact decomposition is possible. USD_MATRIX_OP
writes a matrix operation; NONE omits recognized transform attributes.
Unsupported states are reported rather than approximated.
Where no public Python enum is provided, the option names above abbreviate
constants in ovstage.population: for example,
OVSTAGE_POPULATION_EXPORT_LAYER_MODE_DEF and
OVSTAGE_POPULATION_EXPORT_TRANSFORM_USD_MATRIX_OP. See
Python API Reference for full names.
To preserve the local transform as a matrix operation on a typed prim, export
omni:xform with DEF and USD_MATRIX_OP:
population.export_to_usd_file(
stage, str(directory / "matrix.usda"), ordinal=1,
layer_mode=population.OVSTAGE_POPULATION_EXPORT_LAYER_MODE_DEF,
transform_policy=population.OVSTAGE_POPULATION_EXPORT_TRANSFORM_USD_MATRIX_OP,
attribute_rules=["omni:xform"],
)
# Creates a Sphere with a matrix xform operation instead of decomposed operations.
auto matrixDesc = transformDesc; // Selects omni:xform at ordinal 1.
matrixDesc.layer_mode = OVSTAGE_POPULATION_EXPORT_LAYER_MODE_DEF;
matrixDesc.transform_policy = OVSTAGE_POPULATION_EXPORT_TRANSFORM_USD_MATRIX_OP;
const std::string matrixFile = prefix + "matrix.usda";
check(stage, ovstage_population_export_to_usd_file(stage,
{matrixFile.c_str(), matrixFile.size()}, &matrixDesc, nullptr), "export matrix");
// Creates a Sphere with a matrix xform operation instead of decomposed operations.
Reports and Failures#
Reports count examined, exported, skipped, and unsupported items.
source_states_unavailable counts a rejected stale or future source ordinal.
Stage-wide snapshot reads can also fail while unsealed changes are recorded,
including outside the export selection; see Export Options above.
Use required rules for values that must export successfully. Python raises
ovstage.OvstageError on a failed blocking call or completed asynchronous
operation; C callers check the returned status.
Inspect the earlier transform export’s report, then try a stale ordinal to see failure handling. The current watermark is 1, so ordinal 0 is rejected:
print("Attributes exported:", report["attributes_exported"])
# Attributes exported: 1
try:
# The current watermark is 1; ordinal 0 cannot select an older snapshot.
population.export_to_usd_file(
stage, str(directory / "stale.usda"), ordinal=0,
attribute_rules=["omni:xform"],
)
except OvstageError:
print("Stale export rejected")
# Stale export rejected
else:
raise RuntimeError("Expected the stale ordinal to fail")
// report and transformDesc come from the earlier transform export.
std::printf("Attributes exported: %zu\n", report.attributes_exported);
// Attributes exported: 1
auto staleDesc = transformDesc;
staleDesc.ordinal = 0; // Current watermark is 1: this cannot select an older snapshot.
const std::string staleFile = prefix + "stale.usda";
ovstage_population_export_report_t failureReport{};
const auto status = ovstage_population_export_to_usd_file(stage,
{staleFile.c_str(), staleFile.size()}, &staleDesc, &failureReport);
if (status == OVSTAGE_OK || failureReport.source_states_unavailable == 0)
std::exit(EXIT_FAILURE);
std::printf("Stale export rejected\n");
// Stale export rejected
Asynchronous Convenience API#
The _async functions copy the descriptor and its referenced data, then
queue work on the source stage’s population queue. A file export completes only
after saving. Both examples export omni:xform at ordinal 1 and wait for the
export report:
operation = population.export_to_usd_file_async(
stage, str(directory / "async.usda"), ordinal=1,
attribute_rules=["omni:xform"],
)
# This export-specific wait includes saving the file and returns its report.
async_report = operation.wait()
print("Async transforms exported:", async_report["transforms_exported"])
# Async transforms exported: 1
const std::string asyncFile = prefix + "async.usda";
const auto operation = ovstage_population_export_enqueue_to_usd_file(stage,
{asyncFile.c_str(), asyncFile.size()}, &transformDesc);
check(stage, operation.status, "enqueue export");
ovstage_population_export_report_t asyncReport{};
// Use the export-specific wait; completion includes saving the file.
check(stage, ovstage_population_export_wait_op(stage, operation.op_index,
OVSTAGE_TIMEOUT_INFINITE, &asyncReport), "wait export");
std::printf("Async transforms exported: %zu\n", asyncReport.transforms_exported);
// Async transforms exported: 1
Wait for every accepted asynchronous export or destination operation using its export-specific wait. A timeout leaves it pending; wait again. Creation, export, save, and destruction share the population queue and can be submitted in order before waiting. Creation reserves a handle immediately, but successful completion is required before treating the destination as open.
Do not mix synchronous access with pending asynchronous work on the same destination. An export wait reports only that export or destination operation. Use the population wait separately to observe unrelated population operations and their failures.
Limitations#
Current values only. If
radiuschanges from 1 to 2 to 3, a changed-property export writes 3. It does not write both historical edits or reconstruct earlier scene structure.Schema policy is explicit. The low-level default uses explicit schema rules. Use
SourceApiSchemaPolicy.APPLY_RECORDEDor the typed helper to keep recognized recorded schemas.Property kinds are explicit. Relationship and material-binding rules require the relationship-path semantic. A single-path column (
is_array=False) exports as a relationship with that one target. Connection rules require a single target and the connection semantic. A zero-initialized property rule is invalid. Optional unsupported rules are counted; required ones fail.Connections have limits. A direct-write single-target connection can be exported alongside a value on the same attribute. Population does not create connection columns from USD, and multiple targets have no lossless runtime form.
Changed properties are not a structural diff. A changed
radiuscan produce just that value, but a broad changed-prim rule may also author the prim definition even if its type and hierarchy did not change. Use attribute rules for value overlays.Layer composition is not copied.
OPEN_EXISTINGand in-memory export preserve unrelated destination opinions, but do not reconstruct source layers or select values by their layer of origin. One-shot file export starts empty and replaces stored content.
Further Reading#
Population (USD → ovstage) — load and update USD in the opposite direction and compose shared prim/property predicates.
Writing Attributes — author the runtime columns selected for export.
Queries — inspect the runtime state that predicates select.
Asynchronous Operations — operation ownership and waiting.
String Handling — C string-view lifetime and ownership.
Set Up a C/C++ Project and Set Up a Python Project — build, link, and package setup for the public C and Python surfaces.
Error Handling and Diagnostics — status and error-string handling.