USD Plugin Paths and Schema Registration#

USD schemas describe prim types, attribute types, and fallback values. ovrtx ships schema plugins for renderer settings, sensors, lens distortion, particle fields, and other capabilities. An application that creates USD through its own OpenUSD installation must make the required schema definitions available before it first uses the schema registry.

There are two separate consumers: your external OpenUSD (for example Python’s usd-core) and the USD runtime used by ovstage population. Registering with one does not register with the other.

Choose the Registration Path#

Application workflow

Required action

Register with External OpenUSD

Author with your own OpenUSD installation. Enumerate ovrtx’s directories, select the required families, and register them with that installation.

Register for the Bundled USD Runtime

Populate an ovstage scene. Publish ovrtx’s schema paths before initializing ovstage, or initialize the renderer first.

Register with External OpenUSD#

ovrtx.usd_plugin_paths() returns schema directories without changing plugin-path environment variables. C uses ovrtx_get_usd_plugin_path_count() and ovrtx_get_usd_plugin_paths().

Enumeration does not check whether each directory exists. Select the families your application needs, register them with its OpenUSD installation, and verify that their schema definitions are available.

The following Python code registers the physical-lighting family. The C tab shows the equivalent directory enumeration for a caller integrating its own OpenUSD installation:

# Select only the schema family this application needs.
paths = ovrtx.usd_plugin_paths()
schema_path = next(p for p in paths if Path(p).name == "physical_lighting")
assert (Path(schema_path) / "plugInfo.json").is_file(), "Schema plugin descriptor missing"
# Register before creating a stage or using Usd.SchemaRegistry.
Plug.Registry().RegisterPlugins(schema_path)
// Returned strings belong to ovrtx and remain valid for this process.
const size_t count = ovrtx_get_usd_plugin_path_count(&config);
std::vector<ovx_string_t> paths(count);
const size_t total = ovrtx_get_usd_plugin_paths(&config, paths.data(), paths.size());
if (total != count || count == 0) return 1;
for (auto path : paths) std::cout << std::string_view(path.ptr, path.length) << "\n";

C returns the total directory count even when the supplied array is smaller. Each ovx_string_t contains a length and an ovrtx-owned pointer; do not free the strings or assume null termination. They remain valid for the process lifetime.

Warning

Register external plugins before the first Usd.SchemaRegistry lookup or stage open. Late registration does not rebuild cached schema definitions. Set PXR_PLUGINPATH_NAME before importing pxr when using the environment alternative. Run ordering checks in new processes.

The example selects only physical_lighting, the family it uses, and is tested with usd-core==26.3. This does not establish compatibility with every OpenUSD version or binary plugin.

Warning

Different plugins can provide the same schema alias. For example, recent external OpenUSD versions already define particle schemas also supplied by ovrtx’s usd_particle_field family. OpenUSD may select a provider without reporting a conflict. A successful render or matching fallback value does not identify the selected provider. Check the plugin and its resource directory as well as the schema’s fallback values.

Selecting only physical_lighting avoids that overlap in this example. It is not a general rule to discard OV schemas whenever OpenUSD provides the same alias; the provider must meet your application’s compatibility needs.

Verify a Schema#

After registration, explicitly apply PhysicalLightIlluminantAPI to a RectLight. These equivalent USDA and Python examples use the existing ovrtx schema:

def "World"
{
    def RectLight "Light" (
        prepend apiSchemas = ["PhysicalLightIlluminantAPI"]
    )
    {
    }
}
light = stage.DefinePrim("/World/Light", "RectLight")
assert light.ApplyAPI("PhysicalLightIlluminantAPI")

The Python check verifies that physical:illuminant is absent before applying this schema, then reads its token fallback white without authoring a value. It also checks that the selected schema comes from physicalLighting in the directory returned by ovrtx. Writing a custom attribute would not prove that registration worked.

The C tab checks that each enumerated directory contains a plugin descriptor. That catches a nonexistent package root before publishing paths. Resolving the schema and its fallback with an external C++ OpenUSD installation also requires that installation’s headers and libraries; ovrtx’s C API does not expose its schema registry.

# A fallback alone does not identify which plugin supplied the schema.
schema_type = Usd.SchemaRegistry.GetTypeFromSchemaTypeName("PhysicalLightIlluminantAPI")
assert schema_type, "Schema type not found"
provider = Plug.Registry().GetPluginForType(schema_type)
assert provider and provider.name == "physicalLighting", "Unexpected schema provider"
assert Path(provider.resourcePath).samefile(schema_path), (
    f"Unexpected schema resource path: {provider.resourcePath} (expected {schema_path})"
)

stage = Usd.Stage.CreateInMemory()
light = stage.DefinePrim("/World/Light", "RectLight")
assert not light.GetAttribute("physical:illuminant"), "Schema must be applied explicitly"
assert light.ApplyAPI("PhysicalLightIlluminantAPI")

# Read a schema fallback without authoring a value ourselves.
illuminant = light.GetAttribute("physical:illuminant")
assert illuminant.GetTypeName() == Sdf.ValueTypeNames.Token
assert illuminant.Get() == "white"
assert not illuminant.HasAuthoredValueOpinion()
// Enumeration and registration do not check the filesystem.
for (auto path : paths) {
    const auto descriptor = std::filesystem::path(std::string(path.ptr, path.length)) / "plugInfo.json";
    std::error_code error;
    if (!std::filesystem::is_regular_file(descriptor, error)) {
        std::cerr << "Schema plugin descriptor missing: " << descriptor.string() << "\n";
        return 1;
    }
}

Register for the Bundled USD Runtime#

Call ovrtx.register_schema_paths() before initializing ovstage in a stage-first application. This publishes ovrtx’s paths for the bundled USD runtime. It does not register them with a separate external OpenUSD instance.

If ovstage is already loaded, the function also forwards the paths directly to it. Registration must still happen before the first operation that reads USD schema definitions, including population and schema-aware export. Registering before ovstage initialization avoids that timing dependency.

Call the registration function before creating ovstage. The C tab uses the same configuration as path enumeration:

# Publish paths before initializing ovstage in a stage-first application.
ovrtx.register_schema_paths()
// Publish paths before the bundled USD runtime first reads its registry.
const ovrtx_result_t result = ovrtx_register_schema_paths(&config);
if (result.status != OVRTX_API_SUCCESS) {
    std::cerr << "Could not register ovrtx schema paths.\n";
    return 1;
}

If the application initializes the renderer before ovstage, renderer initialization publishes these paths automatically. External OpenUSD still needs the separate registration described above.

Enumeration and registration share a first-call-wins package-root selection. A later request for another root warns and keeps the pinned root. Enumeration therefore has this process-state effect even though it does not change environment variables.

The following Python test uses two existing package roots, root_a and root_b, each containing a copy of the installed schema resources. In a fresh process, requesting B after A still returns and publishes A’s paths. The test also checks the warning on stderr and runs with registration as the first call:

# The first call selects A for this process.
os.environ['OMNI_USD_PLUGINS_BASE_PATH'] = str(root_a)
paths_a = ovrtx.usd_plugin_paths()
assert paths_a and all(Path(p).is_relative_to(root_a) for p in paths_a)
assert all((Path(p) / 'plugInfo.json').is_file() for p in paths_a)

# B also contains the schemas, but changing the root cannot replace A.
assert all((root_b / Path(p).relative_to(root_a) / 'plugInfo.json').is_file()
           for p in paths_a)
os.environ['OMNI_USD_PLUGINS_BASE_PATH'] = str(root_b)
assert ovrtx.usd_plugin_paths() == paths_a  # Warns and keeps A.
ovrtx.register_schema_paths()              # Also warns and publishes A.
assert ovrtx.usd_plugin_paths() == paths_a

Warning

ovrtx_register_schema_paths() always returns OVRTX_API_SUCCESS, including for a root mismatch. Its return value does not prove that the paths exist or that the USD runtime can resolve the schemas. Verify the required schema definitions before using them.

Importing ovrtx registers paths automatically only when OVRTX_PXR_SCHEMA_AUTO_REGISTER=1. The opt-in also publishes paths to the upstream PXR_PLUGINPATH_NAME. Keep it unset when you need to select paths yourself. The bundled runtime uses its own versioned plugin-path environment key; prefer the public functions over hard-coding that key.

Further Reading#