Loading USD#

Note

Python examples populate an attached ovstage. The Renderer.open_usd*, reference, remove, reset, and USD-time wrappers are deprecated compatibility APIs. Refer to skills/update-0_3-0_4-python/SKILL.md. The C tabs retain the corresponding standalone APIs.

Before rendering, load USD content into the application-owned ovstage. The population layer supports three composition patterns:

  • Open a file path, URL, or inline USDA string as the root layer.

  • Compose a new inline root layer that sublayers an existing scene and authors additional prims such as cameras, RenderProducts, RenderVars, or labels.

  • Add removable referenced content under a path prefix after a root stage is already open.

Opening a Root Layer#

print(f"Opening {USD_URL}...", file=sys.stderr)
ordinal = 1
ovstage.population.open_usd(stage, USD_URL, ordinal=ordinal)
stage.advance_write_floor(ordinal, ovstage.Scope.ALL).wait()
print("USD loaded.", file=sys.stderr)
// Populate an ovstage instance from USD, then attach it to the renderer.
//
// As well as just passing a URI to an existing layer, we could pass a USDA
// string in order to compose a Stage at runtime. This can be very useful
// for dynamically creating the RenderProducts etc. that define the render
// output rather than editing the original layer to add them.
//
// A real application might want to load the USD layer and traverse it to
// find either existing RenderProducts, and/or Cameras and allow the user to
// select which one to render, and which RenderVars to output.
char const* usd_url = "https://omniverse-content-production.s3.us-west-2.amazonaws.com/Samples/Robot-OVRTX/robot-ovrtx.usda";

// The STATIC ovstage loader resolves ${executable_dir} the same way ovrtx does
// above; ovstage_setup_runtime() links the package bin beside the exe as
// "ovstage/" (next to "ovrtx/"). ovrtx_create_renderer() above already loaded
// the shared usd_ms runtime; ovstage.dll loads lazily on the first ovstage call
// and reuses that runtime.
ovx_string_t ovstage_package_root = {
    OVX_CONFIG_EXECUTABLE_DIR_TOKEN "/ovstage",
    sizeof(OVX_CONFIG_EXECUTABLE_DIR_TOKEN "/ovstage") - 1};
ovstage_config_entry_t stage_config_entries[] = {
    ovstage_config_entry_binary_package_root_path(ovstage_package_root),
};
ovstage_config_t stage_config {};
stage_config.entries = stage_config_entries;
stage_config.entry_count = sizeof(stage_config_entries) / sizeof(stage_config_entries[0]);
ovstage_api_status_t stage_init_status = ovstage_initialize(&stage_config);
if (stage_init_status != OVSTAGE_OK) {
    print_ovstage_error(nullptr, stage_init_status, "initialize");
    return cleanup(1);
}

ovstage_instance_desc_t stage_desc {};
stage_desc.name = "minimal";
ovstage_api_status_t stage_result = ovstage_create_instance(&stage_desc, &stage);
if (stage_result != OVSTAGE_OK) {
    print_ovstage_error(stage, stage_result, "create_instance");
    return cleanup(1);
}

result = ovrtx_attach_ovstage(renderer, stage);
if (check_and_print_error(result, "attach_ovstage")) {
    return cleanup(1);
}
stage_attached = true;

std::cerr << "Adding " << usd_url << " at root..." << std::endl;
ovstage_population_enqueue_result_t populate_result =
    ovstage_population_open_usd_from_file(stage,
                                          {usd_url, strlen(usd_url)},
                                          stage_ordinal,
                                          /* time = */ 0.0,
                                          OVSTAGE_POPULATION_DOMAIN_RENDERING);
if (wait_population_op(stage, populate_result, "population_open_usd_from_file") ||
    commit_ovstage_ordinal(stage, stage_ordinal)) {
    return cleanup(1);
}
std::cerr << "USD loaded." << std::endl;

Python population assigns an ordinal; advance the ovstage write floor before rendering that ordinal. C compatibility open calls are asynchronous and must be waited before using the loaded stage.

Inline Composition#

Use an inline root layer when an existing scene does not contain the render configuration, sensors, or semantic metadata the application needs. The inline root can sublayer the original scene and author additional prims without modifying the source asset.

(
    subLayers = [
        @../../data/ovrtx-test-base.usda@
    ]
)

def Camera "DocsCamera" (
    prepend apiSchemas = ["OmniSensorGenericCameraCoreAPI"]
)
{
}

def "Render"
{
    def RenderProduct "DocsCamera"
    {
        rel camera = </DocsCamera>
        rel orderedVars = [<LdrColor>]

        def RenderVar "LdrColor"
        {
            string sourceName = "LdrColor"
        }
    }
}
ovstage.population.open_usd_from_string(stage, f'''
#usda 1.0
(
    subLayers = [
        @{scene_path}@
    ]
)

def "Render" {{
    def RenderProduct "Camera" {{
        int2 resolution = (1920, 1080)
        rel camera = </Camera0>
        rel orderedVars = [<LdrColor>, <HdrColor>]

        def RenderVar "LdrColor" {{
            string sourceName = "LdrColor"
        }}

        def RenderVar "HdrColor" {{
            string sourceName = "HdrColor"
        }}
    }}
}}
''', ordinal=ordinal)
stage.advance_write_floor(ordinal, ovstage.Scope.ALL).wait()

products = renderer.step(
    render_products={"/Render/Camera"},
    delta_time=1.0 / 60,
    ordinal=ordinal,
)
    // Compose a docs-owned render-config layer on top of a scene layer via
    // USD sublayers. The composed root gets populated into the attached
    // ovstage in one call.
    std::string scene_path = get_test_data_dir() + "/simple_camera.usda";
    std::string usda = make_sublayer_usda(scene_path, R"usda(
def "Render" {
    def RenderProduct "Camera" {
        int2 resolution = (640, 480)
        rel camera = </Camera0>
        rel orderedVars = [<LdrColor>, <HdrColor>]

        def RenderVar "LdrColor" {
            string sourceName = "LdrColor"
        }

        def RenderVar "HdrColor" {
            string sourceName = "HdrColor"
        }
    }
}
)usda");
    ovstage_population_enqueue_result_t pr = ovstage_population_open_usd_from_string(
        stage_,
        {usda.c_str(), usda.size()},
        /*ordinal=*/1,
        /*time=*/NAN,
        OVSTAGE_POPULATION_DOMAIN_RENDERING);
    ASSERT_EQ(pr.status, OVSTAGE_OK) << format_ovstage_population_last_error();
    docs_wait_ovstage_population_no_errors(stage_, pr.op_index);
    docs_ovstage_advance_write_floor(stage_, 1);

References#

Use reference APIs when a root stage is already open and you want to add content under a new path prefix, then later remove it by handle.

handle = ovstage.population.add_usd_reference(stage, str(reference_file), "/World/LoadedBase")
ovstage.population.apply_usd_changes(stage, ordinal=2)
stage.advance_write_floor(2, ovstage.Scope.ALL).wait()

ovstage.population.remove_usd(stage, handle)
ovstage.population.apply_usd_changes(stage, ordinal=3)
stage.advance_write_floor(3, ovstage.Scope.ALL).wait()
ovstage_population_usd_reference_handle_t handle =
    OVSTAGE_POPULATION_INVALID_USD_REFERENCE_HANDLE;
ovstage_population_enqueue_result_t pr = ovstage_population_add_usd_reference_from_file(
    stage_,
    {reference_path_str.c_str(), reference_path_str.size()},
    ovx_str("/World/LoadedBase"),
    &handle);
ASSERT_EQ(pr.status, OVSTAGE_OK) << format_ovstage_population_last_error();
docs_wait_ovstage_population_no_errors(stage_, pr.op_index);

pr = ovstage_population_apply_usd_changes(stage_, /*ordinal=*/2);
ASSERT_EQ(pr.status, OVSTAGE_OK) << format_ovstage_population_last_error();
docs_wait_ovstage_population_no_errors(stage_, pr.op_index);
docs_ovstage_advance_write_floor(stage_, 2);

pr = ovstage_population_remove_usd_reference(stage_, handle);
ASSERT_EQ(pr.status, OVSTAGE_OK) << format_ovstage_population_last_error();
docs_wait_ovstage_population_no_errors(stage_, pr.op_index);

pr = ovstage_population_apply_usd_changes(stage_, /*ordinal=*/3);
ASSERT_EQ(pr.status, OVSTAGE_OK) << format_ovstage_population_last_error();
docs_wait_ovstage_population_no_errors(stage_, pr.op_index);
docs_ovstage_advance_write_floor(stage_, 3);

Inline referenced content must have a defaultPrim because the reference is composed below the requested path prefix.

Time-Sampled USD#

For animated USD scenes, re-evaluate time-sampled attributes through ovstage.population.update_from_usd_time (Python) or ovstage_population_apply_usd_time (attached-mode C) and publish the changes at a new ordinal.

USD authors time samples in timecodes (frame-like units), but the runtime time APIs take seconds. Convert using the stage’s timeCodesPerSecond metadata: seconds = timecode / timeCodesPerSecond. For example, with timeCodesPerSecond = 24, a sample at timecode 48 is at 2.0 seconds. Despite its name, the usd_time parameter is in seconds, not timecodes.

This is distinct from the simulation time advanced by step(delta_time) — the two clocks are independent (refer to the stepping-and-rendering skill). step() does not move USD animation; update_from_usd_time() does not advance the simulation/sensor clock.

ovstage.population.update_from_usd_time_async(stage, ordinal=ordinal, time_code=time_seconds).wait()
stage.advance_write_floor(ordinal, ovstage.Scope.ALL).wait()
// Time-only update: reevaluate every time-sampled attribute in the
// scene at t_seconds against the stage's timeCodesPerSecond metadata
// and publish under `ordinal`. Void-return-op — wait on the op_index
// then advance the write floor.
ovstage_population_enqueue_result_t pr =
    ovstage_population_apply_usd_time(stage_, ordinal, t_seconds);
ASSERT_EQ(pr.status, OVSTAGE_OK) << format_ovstage_population_last_error();
docs_wait_ovstage_population_no_errors(stage_, pr.op_index);
docs_ovstage_advance_write_floor(stage_, ordinal);

Resetting the Stage#

reset_stage clears all USD content from the runtime stage. Python exposes reset_stage and reset_stage_async; C uses ovrtx_reset_stage(). Opening a new root layer replaces the previous root.

Authored Attribute Population#

By default, the runtime populates supported schema attributes. To read or write generic custom authored attributes, an inline root layer can set customLayerData.populateAllAuthoredAttributes = true. Use this only when needed: large assets can contain many authored properties that the application will never use.