Omni.USD

Omni.USD is one of the core Kit APIs, and provides access to USD and USD-related application services such as:

  • Events/Listeners

  • Selection handling

  • Access to the Omniverse USD Audio subsystem

  • Access to the Omniverse Client Library, and handling of Omniverse Assets/URIs

  • USD Layer handling

  • A USDContext which provides convenient access to the main USDStage and it’s layers, as well as various Hydra, Renderer and Viewport related services

  • MDL (Material Definition Language)

More info

See omni.usd API Docs (part of Kit API Docs)

omni.usd API Docs

Note that we often refer to the Pixar USD Reference documentation - it’s docs describe the C++ API, not the Python API, but in most cases they are similar. For more information, It is often worthwhile to look at the USD source code - particularly unit tests and examples - for more specific guidance

Pixar USD Github

Extensions - most of Kit is written as Python-based extensions. The source code for each of these is available by clicking on the folder icon in the Extension Manager when a specific extension is selected. For the most part, these can be modified on the fly and hot-reloaded. You can also step through them with a debugger (see Kit-SDK API Docs)

omni.usd Commands

The USD-related functionality exposed in the Kit UI is partially implemented using Commands - for example most of the Viewport Interaction, Create/Edit Menus, right-click (Contextual) functionality available in the Stage and Layer widgets is command-based and using these will generated commands for most or all actions performed.

Example of Command to create a new prim on the current stage/layer (unlike USD itself, Kit will assume a certain context when working with USD Objects )

import omni.kit.commands

ret = omni.kit.commands.execute('CreatePrim', prim_type='DistantLight', attributes={'angle': 1.0, 'intensity': 3000})
print (f"return value: {ret}")

Note that some of these operations are asynchronous, so you can’t necessarily execute a command and expect the result to be available in the next line of code (but maybe it will be…depending on how quickly it executes)

# Synchronous creation of multiple prims

import omni.kit.commands

omni.usd.get_context().new_stage()

for x in range(0,50):
    ret = omni.kit.commands.execute('CreatePrim', prim_type='DistantLight', attributes={'angle': 1.0, 'intensity': 3000})

stage = omni.usd.get_context().get_stage()
num_prims = len([p for p in stage.Traverse()])
print (f"num_prims: {num_prims}")

#if you don't see 50 prims, you can add one or two of these
#await omni.kit.app.get_app().next_update_async()

..or two between each one

That may or may not work, but the safest thing to do is

# Asynchronous creation of multiple prims

import omni.kit.commands
import asyncio

async def create_many_lights():
    omni.usd.get_context().new_stage()

    for x in range(0, 50):
        ret = omni.kit.commands.execute('CreatePrim', prim_type='DistantLight', attributes={'angle': 1.0, 'intensity': 3000})

    await omni.kit.app.get_app().next_update_async()
    stage = omni.usd.get_context().get_stage()

    num_prims = len([p for p in stage.Traverse()])
    print (f"num_prims: {num_prims}")

asyncio.ensure_future(create_many_lights())

There are a wide selection of other USD related commands, which you can inspect by using the omni.kit.window.commands extension.

This extension has a very primitive search functionality you can access by pressing the “Search commands” button

Any commands invoked by running scripts, interactive use of Kit etc will be reflected in this window, and can all be copied and pasted into the script editor or your IDE and executed again.

See for example:

The first few commands you see are executed by Kit when you create a new scene.

Here’s an example which was pasted from an interactive session which creates a cone, duplicates it, moves one of the cones, then groups them under a single Xform node

import omni.kit.commands
from pxr import Gf, Usd

omni.kit.commands.execute('CreateMeshPrimWithDefaultXform',
    prim_type='Cone')

omni.kit.commands.execute('SelectPrimsCommand',
    old_selected_paths=['/World/Cone'],
    new_selected_paths=[],
    expand_in_stage=True)

omni.kit.commands.execute('SelectPrimsCommand',
    old_selected_paths=[],
    new_selected_paths=['/World/Cone'],
    expand_in_stage=True)

omni.kit.commands.execute('CopyPrims',
    paths_from=['/World/Cone'],
    duplicate_layers=False,
    combine_layers=False)

omni.kit.commands.execute('TransformPrimCommand',
    path='/World/Cone_01',
    old_transform_matrix=Gf.Matrix4d(1.0, 0.0, 0.0, 0.0,
            0.0, 1.0, 0.0, 0.0,
            0.0, 0.0, 1.0, 0.0,
            0.0, 0.0, 0.0, 1.0),
    new_transform_matrix=Gf.Matrix4d(1.0, 0.0, 0.0, 0.0,
            0.0, 1.0, 0.0, 0.0,
            0.0, 0.0, 1.0, 0.0,
            46.5615, 36.3054, -82.8669, 1.0),
    time_code=Usd.TimeCode(),
    had_transform_at_key=False)

omni.kit.commands.execute('SelectPrimsCommand',
    old_selected_paths=['/World/Cone_01'],
    new_selected_paths=[],
    expand_in_stage=True)

omni.kit.commands.execute('SelectPrimsCommand',
    old_selected_paths=[],
    new_selected_paths=['/World/defaultLight', '/World/Cone', '/World/Cone_01'],
    expand_in_stage=True)

omni.kit.commands.execute('GroupPrims',
    prim_paths=['/World/defaultLight', '/World/Cone', '/World/Cone_01'])

There are a large number of USD-related commands registered as part of various Kit extensions such as omni.usd, omni.usd.commands, omni.kit.property.transform etc….

These commands have a few things in common:

  • They operate either on existing scene state (e.g the usdContext, selection state etc.) or take a set of input arguments

  • Input arguments are generally POD type data which is easily serialisable/deserialisable and has a string representation

  • Most are undoable

  • Some have a “multiples” flavour e.g TransformPrim, TransformPrims which are similar except the multiples command often takes a list of prim paths as input rather than a single prim path, and executes the basic version of the Command multiple times

  • They are written in python and the source code for each of them can be found in the extension that registered them, including any class/method documentation

  • Some of them are “composite” commands that are a series of other commands grouped together in order e.g CreateAndBindMaterialFromLibrary

omni.usd.Context and Stages

The context provides access to the application stage and layers, file i/o and other functionality

A Kit session has a single main USD Stage associated with it, which can be accessed like so:

import omni.usd

main_stage = omni.usd.get_context().get_stage()
print (f"main_stage {type(main_stage)}, {main_stage}")

The returned stage is a pxr.Usd.Stage as defined in

Pixar USD Docs - Stage so you can perform all of the same USD API operations as you would on any USD stage, such as:

import omni.usd

main_stage = omni.usd.get_context().get_stage()
rootLayer = main_stage.GetRootLayer()
print (f"root_layer {rootLayer}")

see

User Docs - Stage

Async Operations

Kit works asynchronously for the most part, and the omni.usd API reflects this…..much of omni.usd functionality related to long-running/blocking operations like opening stages can be called asynchronously. This uses standard Python 3.6 asyncio semantics, see

Python 3.6 asyncio docs

For example here is how to open a stage using the async API

import omni.usd
import asyncio
from omni.kit.usd_docs import variant_example_usd_scene

usd_context = omni.usd.get_context()

async def open_stage():
    (result, error) = await omni.usd.get_context().open_stage_async(variant_example_usd_scene)

    #Now that we've waited for the scene to open, we should be able to get the stage
    stage = omni.usd.get_context().get_stage()

    print (f"opened stage {stage} with result {result}")


asyncio.ensure_future(open_stage())

For comparison, the following snippet will do the same, but synchronously

import omni.usd
from omni.kit.usd_docs import variant_example_usd_scene

usd_context = omni.usd.get_context()

result = omni.usd.get_context().open_stage(variant_example_usd_scene)
stage = omni.usd.get_context().get_stage()

print (f"opened stage {stage} with result {result}")

omni.usd Events

Once you want to start building USD related extensions in Kit, it can be very useful to know when certain things happen. Kit has an event-based model that allows you to to subscribe to specific events

To get a list of available stage events:

import omni.usd

print (dir(omni.usd.StageEventType))

To register a callback that gets called when a new scene is created (This might be useful if you want your UI to refresh when the new scene is created)

import carb.events
import omni.usd

context = omni.usd.get_context()


def on_stage_event(e: carb.events.IEvent):
    print ("event type", e.type)
    if e.type==int(omni.usd.StageEventType.OPENED):
        print(f"Stage Opened!")

stage_event_sub = (
    omni.usd.get_context().get_stage_event_stream().create_subscription_to_pop(on_stage_event, name="My Subscription Name")
)

print (f"stage_event_sub {stage_event_sub}")

#Creating a new stage will trigger the callback above
context.new_stage()

omni.usd Selection

Kit (via omni.usd) has native selection of USD prims, which is understood by USD-aware extensions such as:

  • Kit Viewport

  • Stage Widget

  • Layer Widgets

  • USD Property Windows

All of these Widgets/Windows will allow you to maniuplate the USD selection, and will reflect any existing selection

import omni.kit
import omni.kit.commands
import omni.usd
import asyncio


async def create_then_select_cone():
    '''
    we need to create an async function if we need to await anywhere
    '''

    result, prim_path = omni.kit.commands.execute('CreateMeshPrimWithDefaultXform', prim_type='Cone')
    print (f"Prim Path: {prim_path}")

    # If you remove this, there's no guarantee that the mesh will be created before the next line is
    # executed
    await omni.kit.app.get_app().next_update_async()

    selection = omni.usd.get_context().get_selection()

    selection.clear_selected_prim_paths()

    selection.set_selected_prim_paths([prim_path], False)

    print (f"selected prim paths are {selection.get_selected_prim_paths()}")


# This is one way to call an async function from the Kit Script Editor
asyncio.ensure_future(create_then_select_cone())

There is other selection related functionality, you can see some of it in the Edit->Select Menu

Selection Commands

  • SelectAll - Select all prims

  • SelectHierarchy - Select the children (recursively) of any selected Prims

  • SelectInvert - Select the Inverse of what’s currently selected

  • SelectLeaf - Select the Leaf nodes of any currently selected USD prims

  • SelectNone - Nullify the selection

  • SelectParent - Select the parents nodes of any currently selected USD prims

  • SelectSimilar - Select prims of the same prim type as any selected prims

  • SelectPrimsCommand Select primitives

  • SelectList - Set the selection based off a supplied list (This command requires a python list as it’s input)

omni.usd Layers

See:

User Docs - Layer Widget

the layer API allows you to get access to Layer related state e.g which layers are muted, locked etc.

This snippet shows if any of the layers used are locked

import omni.usd

layers = omni.usd.get_context().get_layers()


used = layers.get_used_sublayers()
for u in used:
    lock = layers.is_layer_locked(u)
    print (f"{u} locked? {lock}")