Population (USD → ovstage)#

The population API (ovstage_population.h) is the bridge that ingests composed USD content into the runtime stage. It is a self-contained C API with its own asynchronous model — ovstage_population_enqueue_result_t and ovstage_population_wait_op — that runs parallel to the data-plane submit/observe model.

Note

These entry points are pre-release and provisional (“Draft — API in flux”).

Loading a Scene#

ovstage_population_open_usd_from_file (and _from_string) loads a USD scene into the runtime table in one op at a chosen ordinal. As with any write, seal the ordinal with advance_write_floor before reading the populated prims back:

# 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_code=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#

Loads take a population domain bitmask (PopulationDomain / ovstage_population_domain_t: NONE, RENDERING, PHYSICS, ALL) selecting which consumer’s attribute set to populate.

Editing the USD Source#

After a scene is live you can edit the USD source and propagate the change into the runtime table at a fresh ordinal:

  • ovstage_population_add_usd_reference_from_file / _from_string — add a reference onto an existing prim.

  • ovstage_population_remove_usd_reference / ovstage_population_reset_usd — remove a reference or reset.

  • ovstage_population_apply_usd_changes — propagate pending USD edits into the table (at a new ordinal).

  • ovstage_population_apply_usd_time — apply a USD time sample.

# 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");

Python ↔ C Name Mapping#

Several Python wrappers use shorter names than their C entry points:

Python (ovstage.population)

C entry point

open_usd / open_usd_async

ovstage_population_open_usd_from_file

add_usd_reference

ovstage_population_add_usd_reference_from_file

remove_usd

ovstage_population_remove_usd_reference

update_from_usd_time

ovstage_population_apply_usd_time

The time_code parameter on open_usd* / update_from_usd_time is in seconds — it maps to the C time parameter, converted internally via the stage’s timeCodesPerSecond. math.nan (the open_usd* default) evaluates at USD’s Default time code.

Ordinal Ownership#

Population never opens or seals an ordinal for you — the application owns the ordinal lifecycle. Pass the current ordinal to each call and seal each tick with advance_write_floor. Keep ordinals monotonic (populate at 1, then writes and USD edits at higher ordinals); an operation at or below the floor is a write-floor violation.

Where to Go Next#