Exporting ovstage to USD#
The population export API (ovstage_population_export.h) authors a selected
slice of ovstage state into USD. Use it to bake simulation results, produce a
sparse overlay, or persist application-selected properties without passing
OpenUSD C++ objects across the public ABI.
Note
Export is a snapshot-authoring operation, not a live synchronization system. USD-to-ovstage population is intentionally lossy, so export cannot reconstruct every opinion from the original USD source.
Destination Ownership and Persistence#
The public C ABI addresses USD storage with strings so no OpenUSD object crosses the boundary. Choose the destination form from the required lifetime:
One snapshot, one durable file:
ovstage_population_export_to_usd_fileand Pythonpopulation.export_to_usd_filecreate an empty private destination, export once, save, and close. For populated USD hierarchies, Pythonpopulation.export_typed_hierarchy_to_usd_filesupplies the common typed export policies automatically. Existing stored content is not read; a successful save replaces it. This workflow requires nopxrPython bindings.Several exports, one durable file: create an
ovstage_population_export_destination_handle_tor PythonExportDestination. Exports accumulate in memory until an explicit*_destination_save/destination.save().EMPTYstarts without reading storage;OPEN_EXISTINGopens and preserves an existing layer stack and authors its root layer. Closing or context-manager exit never saves.
Every destination is opened and owned by ovstage. There is no route that authors into a USD layer or stage owned by the calling application: OpenUSD does not promise that a layer or stage identifier means the same thing in two USD runtimes, so such a route would only work when the caller happens to share ovstage’s USD build. Hand off through storage instead — export to a file, then open or reload that file in the host’s own USD runtime.
Reusable destinations are bound to their source ovstage instance, which must
outlive them. Only one active destination may own a canonical identifier in the
process. A successful save writes the complete current destination and leaves it
open for later export/save calls, but OpenUSD does not promise portable atomic
replacement; a failed save may have modified storage. A failed export leaves
the reusable destination’s in-memory state unchanged.
Dropping a live Python ExportDestination queues a best-effort no-save release
and emits ResourceWarning; use with or call close() explicitly instead.
Export a Typed USD Hierarchy#
When the runtime stage was populated from USD, use the typed-hierarchy helper to export an ordinary subtree without rebuilding its USD representation as rule tables. This example preserves the Sphere and Cube types, each prim’s own applied Physics API schema, and the supported attributes declared by those schemas:
scene = """#usda 1.0
def Xform "World"
{
def Xform "Props"
{
def Sphere "Ball" (prepend apiSchemas = ["PhysicsCollisionAPI"])
{
double radius = 1.5
bool physics:collisionEnabled = true
}
def Cube "Crate" (prepend apiSchemas = ["PhysicsRigidBodyAPI"])
{
double size = 2
bool physics:rigidBodyEnabled = true
}
}
}
"""
population.open_usd_from_string(
stage,
scene,
ordinal=1,
time_code=math.nan,
domains=PopulationDomain.ALL,
)
stage.advance_write_floor(1).wait()
output = tmp_path / "typed-hierarchy.usda"
report = population.export_typed_hierarchy_to_usd_file(
stage,
str(output),
"/World/Props",
1,
)
The helper selects root_path and its descendants, authors typed DEF
prims from usd-prim-type, applies each prim’s recognized usd-schemas, and
projects supported schema-declared attributes under their source names. It also
handles recognized omni:xform values. Application-owned custom attributes
remain excluded unless their namespaces are named with
include_custom_namespaces=[...].
Export Runtime-Created Data with Rules#
Runtime-created columns may not carry enough USD representation metadata for the
typed helper. This example writes local transforms, seals the ordinal, then
exports and saves the recognized omni:xform column. The descriptor defaults
produce sparse OVER prims and exact USD xform ops:
prim_paths = ["/World/Red", "/World/Green", "/World/Blue"]
translations = [(0.0, 0.0, 0.0), (2.0, 0.0, 0.0), (4.0, 0.0, 0.0)]
with PathDictionary(stage) as paths:
path_list = paths.create_path_list_from_strings(prim_paths)
try:
with stage.query_from_path_list(path_list) as query:
stage.write_attribute(
query,
"omni:xform",
ordinal=1,
tensors=_translation_matrices(translations),
is_array=False,
semantic=AttributeSemantic.MATRIX,
).wait()
finally:
paths.destroy_path_list(path_list)
stage.advance_write_floor(ordinal=1).wait()
# The one-shot API creates an empty destination, exports, saves, and closes.
report = population.export_to_usd_file(
stage,
str(destination),
ordinal=1,
attribute_rules=["omni:xform"],
)
The destination is replaced by a layer containing this export. This is an overlay, not a standalone typed scene. Use the typed-hierarchy helper when the source carries USD type and schema metadata; use explicit rules when runtime columns must be mapped to a different USD representation.
Reusable Destination#
Use a reusable destination when several selected exports must accumulate before
one save, or when existing composed content must be preserved. Saving is always
explicit; leaving the context without save() discards unsaved changes:
with population.ExportDestination.create(stage, str(destination)) as usd:
usd.export(
ordinal=1,
prim_predicate=population.PrimPredicate.is_under_path("/World/Red"),
attribute_rules=[{"source_attribute_name": "temperature", "usd_type_name": "float"}],
)
usd.export(
ordinal=1,
prim_predicate=population.PrimPredicate.is_under_path("/World/Blue"),
attribute_rules=[{"source_attribute_name": "temperature", "usd_type_name": "float"}],
)
usd.save()
Copy Custom Attributes Without Renaming#
An empty destination name already means “reuse the source name.” A glob rule can therefore publish an application-owned namespace without listing every column. Custom type inference is explicit because arbitrary runtime storage does not otherwise imply a USD value type:
report = population.export_to_usd_file(
stage,
str(destination),
ordinal=1,
attribute_rules=[
{
"source_attribute_name": "userProperties:*",
"source_attribute_name_match": population.OVSTAGE_POPULATION_EXPORT_NAME_MATCH_GLOB,
"flags": (
population.OVSTAGE_POPULATION_EXPORT_RULE_INFER_CUSTOM_TYPE
| population.OVSTAGE_POPULATION_EXPORT_RULE_REQUIRED
),
}
],
)
Map Runtime Data to a USD Schema#
Explicit source and destination names matter when the runtime model differs from
the USD schema. Here a solver-owned radius column is mapped onto Sphere.radius.
DEF mode reads the source usd-prim-type metadata to create a durable
Sphere, while the rule states the destination attribute’s USD type explicitly:
report = population.export_to_usd_file(
stage,
str(destination),
ordinal=1,
layer_mode=population.OVSTAGE_POPULATION_EXPORT_LAYER_MODE_DEF,
attribute_rules=[
{
"source_attribute_name": "solver:collisionRadius",
"destination_attribute_name": "radius",
"usd_type_name": "double",
"flags": population.OVSTAGE_POPULATION_EXPORT_RULE_REQUIRED,
}
],
)
Export a Typed Mesh Subtree#
The same helper handles a common geometry subtree without an attribute allowlist or blanket API-schema rules. The tested source contains two Meshes with geometry and different Physics APIs; the call is only the root selection:
report = population.export_typed_hierarchy_to_usd_file(
stage,
str(destination),
"/World/SimulatedMeshes",
ordinal=1,
)
The exporter derives each Mesh type and preserves the APIs recorded on that
specific prim: the static Mesh remains collision-only while the dynamic Mesh
also receives PhysicsRigidBodyAPI. Token-valued properties such as
orientation and subdivisionScheme export when their runtime values are
pre-interned token-ID rows and the selected schema or an explicit rule supplies
the token type. Relationships and connections still require property-edge
rules; positive connection export remains unavailable.
Export Only Changed Properties#
After a baseline export, a later file can contain only ordinary properties
dirtied in the open interval (since_ordinal, ordinal]. The result is an
overlay: whoever consumes it must compose it over the baseline export:
report = population.export_to_usd_file(
stage,
str(changed_destination),
ordinal=2,
selection=population.OVSTAGE_POPULATION_EXPORT_SELECTION_CHANGED_PROPERTIES_SINCE_ORDINAL,
since_ordinal=1,
attribute_rules=[rule],
)
Metadata-Driven C Descriptor#
The C API expresses the same metadata-driven policy through the public
ovstage_population_export_desc_init_typed_hierarchy convenience initializer.
It initializes a snapshot descriptor, selects the root and descendants, chooses
DEF mode, applies recorded schemas, and selects schema-declared projection.
The caller-owned root ovx_string_t object and the character bytes referenced
by that view remain borrowed by the descriptor through a synchronous call or
successful async enqueue; do not destroy or change either sooner.
constexpr char source[] = R"usd(#usda 1.0
def Xform "World"
{
def Xform "Props"
{
def Sphere "Ball" (prepend apiSchemas = ["PhysicsCollisionAPI"])
{
double radius = 1.5
bool physics:collisionEnabled = true
}
def Cube "Crate" (prepend apiSchemas = ["PhysicsRigidBodyAPI"])
{
double size = 2
bool physics:rigidBodyEnabled = true
}
}
}
)usd";
ASSERT_TRUE(waitPopulation(runtime.get(),
ovstage_population_open_usd_from_string(
runtime.get(), str(source), 1, NAN, OVSTAGE_POPULATION_DOMAIN_ALL),
"open_usd_from_string"));
ovstage_write_floor_desc_t floor{};
floor.ordinal = 1;
floor.scope = OVSTAGE_SCOPE_ALL;
ASSERT_TRUE(waitOp(runtime.get(), ovstage_advance_write_floor(runtime.get(), &floor), "advance_write_floor"));
const ovx_string_t root = str("/World/Props");
ovstage_population_export_desc_t desc{};
ASSERT_EQ(ovstage_population_export_desc_init_typed_hierarchy(&desc, 1, &root), OVSTAGE_OK);
ovstage_population_export_report_t report{};
ASSERT_EQ(ovstage_population_export_to_usd_file(
runtime.get(), str(destination.identifier()), &desc, &report),
OVSTAGE_OK);
Shared population predicates select source prims and properties. Rule tables are destination-authoring instructions for renaming, custom data, and other explicit mappings. Schema-declared projection can author supported properties without a matching attribute rule.
Descriptor Model#
Initialize every descriptor with
ovstage_population_export_desc_init_snapshot or
ovstage_population_export_desc_init. A snapshot descriptor records the
current committed ordinal expected by the caller. struct_size identifies
this header revision; it is not an automatic compatibility mode for older
descriptor layouts. Export rejects stale and future ordinals rather than
silently reading a different snapshot.
Selection#
EXPLICIT_PREDICATE exports the current committed state selected by the
predicates and rules. CHANGED_PROPERTIES_SINCE_ORDINAL further intersects
property authoring with the open interval (since_ordinal, ordinal]. It is
useful for incremental overlays, but it does not make the output a complete
standalone description: selected values are authored as current USD default
opinions, never as intermediate time samples.
prim_predicate and property_predicate use the same immutable predicate
graphs as generic USD-to-ovstage population. Compose path, concrete type,
applied-schema, property-name, namespace, and property-kind leaves with
AND, OR, and NOT. Predicates select source candidates; rule-local
path and name matching routes selected data to destination opinions.
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 OVSTAGE_POPULATION_EXPORT_RULE_REQUIRED when failure to match, convert,
or author a rule must fail the operation. Optional rules instead contribute to
the report’s skipped or unsupported counters. Type inference for custom
attributes is opt-in through RULE_INFER_CUSTOM_TYPE; an explicit USD type is
usually clearer at a public boundary.
Layer and Transform Policy#
LAYER_MODE_OVER is the conservative default for sparse overlays.
LAYER_MODE_DEF creates durable definitions and uses a prim rule or readable
usd-prim-type metadata to choose type names.
SOURCE_API_SCHEMAS_APPLY_RECORDED applies recognized effective schemas from
usd-schemas on each selected prim. PROJECTION_SCHEMA_DECLARED then
authors selected runtime attributes declared by the concrete type or those
applied schemas. Explicit rules still take precedence and remain necessary for
renaming, custom namespaces, relationships, connections, and metadata.
The default transform policy converts recognized local transforms into USD
xform ops when the value has an exact supported decomposition.
TRANSFORM_USD_MATRIX_OP authors an explicit matrix op, while
TRANSFORM_NONE omits recognized transform columns. Unsupported transform
states are reported rather than silently approximated.
Reports and Failures#
The report summarizes examined, exported, skipped, and unsupported items. Its
source_states_unavailable counter records a rejected stale or future source
ordinal.
Use required rules and the returned ovstage_api_status_t for correctness
requirements. In Python, a failing blocking export or terminal async wait raises
OvstageError.
Asynchronous Convenience API#
The async functions deep-copy the descriptor graph and enqueue work on the source instance’s population FIFO. The one-shot file operation does not complete until the file has been saved:
operation = population.export_to_usd_file_async(
stage,
str(destination),
ordinal=1,
attribute_rules=["omni:xform"],
)
report = operation.wait()
const ovstage_population_enqueue_result_t enqueue =
ovstage_population_export_enqueue_to_usd_file(
runtime.get(), str(destination.identifier()), &desc);
ASSERT_EQ(enqueue.status, OVSTAGE_OK);
ovstage_population_export_report_t report{};
ASSERT_EQ(ovstage_population_export_wait_op(
runtime.get(), enqueue.op_index, OVSTAGE_TIMEOUT_INFINITE, &report),
OVSTAGE_OK);
Every accepted async operation must receive an export-specific terminal wait. A timeout does not consume the result, so wait again. Reusable-destination create, export, save, and destroy operations share the source instance’s population FIFO; they may be enqueued in order before waiting. The create call reserves a handle synchronously, but only its terminal wait proves that opening succeeded. Do not mix synchronous calls with pending async work on the same destination. An export-specific wait observes only its export/destination operation. Use the generic population wait separately to observe and drain unrelated population operations (including their failures).
Limitations#
Export authors the runtime representation available at one committed ordinal. It is not a reversible copy of the USD file that originally populated ovstage. The following limitations are important when choosing rules and interpreting the result.
Export reads one snapshot, not its history#
Export reads the values present at ordinal. since_ordinal limits which
dirty properties are selected, but it does not provide every value that existed
between the two ordinals. For example, if radius changed from 1 to 2 and then
to 3, an incremental export authors the current value 3, not two historical edits.
Historical payload or topology addressing is not available.
Applied API schemas require an explicit policy#
The low-level descriptor defaults to explicit API-schema rules. Set
SOURCE_API_SCHEMAS_APPLY_RECORDED, or use the Python typed-hierarchy helper,
to preserve each selected prim’s recognized usd-schemas. Unrecognized or
malformed schema metadata follows unknown_metadata_policy. Explicit
API-schema rules remain necessary when the destination schema assignment should
differ from the source.
Some USD property representations cannot be exported#
Pre-interned token-ID scalar and array rows can be authored as USD token and
token-array values when a selected schema or explicit rule supplies the token
type. For example, a Mesh’s token-valued subdivisionScheme exports through a
matching attribute rule. Attribute connection targets, such as a shader
inputs:roughness connection, still cannot be authored by a positive connection
rule, and a value plus its connection cannot be round-tripped together.
Relationship and material-binding target lists are supported when the source column carries the documented relationship-path semantic. Always choose relationship, connection, or material binding explicitly; a zero-initialized property rule is invalid. Optional unsupported rules increase the report’s skipped or unsupported counters. Mark a rule required when this must instead fail the export.
Changed-property mode is precise for values, not prim structure#
Changed-property selection can identify supported dirty attribute values. It
cannot always distinguish a changed prim spec or topology from an ordinary value
change. For example, changing only /World/Ball.radius can correctly produce a
sparse radius opinion, while a broad changed prim rule may also re-author the
/World/Ball prim spec even though its type and hierarchy did not change. Use
changed attribute rules for value overlays; do not rely on changed prim rules as
an exact structural layer diff.
Reusable OPEN_EXISTING destinations preserve
unrelated destination opinions, but export is not a general USD composition
copier or source-provenance filter. One-shot file export intentionally starts
empty and replaces the stored destination.
Where to Go Next#
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 Submit/Observe Model — 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.