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)
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
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
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
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:
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}")