Getting started with NVIDIA Omniverse KIT — handling selection

Michael T. Wagner
3 min readJan 15, 2022

the source code for this article can be found on github: https://github.com/mtw75/kit_customdata_view

In the previous part we’ve created an empty KIT extension. Now lets react to selection changes.

One thing I’ve found to be handy during development is to print custom attribute values of selected elements out to the console, so I’ll take that as an example use-case.

So let me do a quick introduction into custom data attributes on USD prims before describing the actual selection handling:

Working with CustomData on USD Prims

I’d like to note that a more sustainable way of putting your domain data into USD is by defining schemas, see USD Schema Documentation here:
https://graphics.pixar.com/usd/release/tut_generating_new_schema.html

The most straight forward way to attach custom data to any USD Prim works with key-value pairs — we do it to add attributes relevant to the applications in our domain.

CustomData API Documentation:
https://graphics.pixar.com/usd/release/api/class_usd_object.html#abea26b9c6a71883a2d9da4ff64952391

setting custom data:
prim.SetCustomDataByKey(“my_data_key”, my_data)
getting custom data:
my_data = prim.GetCustomDataByKey("my_data_key")
iterating over all custom data:
for key in prim.GetCustomData():
value = prim.GetCustomDataByKey(key)

Subscribing to selection events:

We subscribe for all stage events in the startup callback of the extension:

def on_startup(self, ext_id):  
self._usd_context = omni.usd.get_context()
self._selection = self._usd_context.get_selection()
self._events = self._usd_context.get_stage_event_stream()
self._stage_event_sub = self._events.create_subscription_to_pop(
self._on_stage_event,
name='my stage update'
)

It’s important to note that we have to store the subscription with “self._stage_event_sub = …” to keep subscribed when we go out of scope. (Thanks to Koen for pointing this out :-) )

Also we want to make sure that we clean up on shutdown:

def on_shutdown(self): # cleanup 
self._window = None
self._stage_event_sub = None

In the stage_event handler we check for SELECTION_CHANGED event type and call the actual selection changed method:

def _on_stage_event(self, event):
if event.type == int(omni.usd.StageEventType.SELECTION_CHANGED):
self._on_selection_changed()

Finally in the selection_changed method we print the custom data attached to the selected prims like that :

def _on_selection_changed(self):
selection = self._selection.get_selected_prim_paths()
stage = self._usd_context.get_stage()
print(f'== selection changed with {len(selection)} items')
if selection and stage:
for selected_path in selection:
print(f' item {selected_path}:')
prim = stage.GetPrimAtPath(selected_path)
for key in prim.GetCustomData():
print(f' — {key} = {prim.GetCustomDataByKey(key)}')

Events Documentation:
http://omniverse-docs-production.s3-website-us-west-1.amazonaws.com/kit-sdk/103.0/docs/api/carb/carb.events.html

Creating a GUI

Now we want to create a GUI which displays the selected prim path and lists all custom data attributes in a simple key value list.

GUI displaying custom data attributes

selected prim path:

selected prim path display

We use a SimpleStringModel to store the selected prim path and bind it to a readonly ui.StringField component to display it:

#-- create simplestringmodel with initial value "-":self._selected_primpath_model = ui.SimpleStringModel(“-”) [...]#-- create a readonly stringfield for display using this model: self._selectedPrimName = ui.StringField( 
model=self._selected_primpath_model,
read_only=True)

customdata attributes:

custom data property display

To display the attributes we create our own item model “CustomDataAttributesModel” which is derrived from ui.AbstractItemModel:

class CustomDataAttributesModel(ui.AbstractItemModel):
[...]
def set_prim(self, usd_prim ):
# we reset the model with the new custom data
self._children = []
for key in usd_prim.GetCustomData():
item = NameValueItem(key, usd_prim.GetCustomDataByKey(key))
self._children.append(item)
# emit data changed
self._item_changed(None)

For each key-value pair of the custom data we create a NameValueItem which is derrived from ui.AbstractItem:

class NameValueItem(ui.AbstractItem):
def __init__(self, text, value):
super().__init__()
self.name_model = ui.SimpleStringModel(text)
self.value_model = ui.SimpleStringModel(value)

We use a ui.TreeView component to display the list of key-value pairs as a table:

tree_view = ui.TreeView(
self._customdata_model,
root_visible=False,
header_visible=False,
columns_resizable=True,
column_widths=[ui.Fraction(0.4), ui.Fraction(0.6)],
style={'TreeView.Item': {'margin': 4}})

We use our custom_data_model to provide the data for each displayed element.

To make our TreeView aware of the window size we use the ui.Fraction(0.4), … statements in column_widths.

In order to see headers we need to create a delegate ( subclass ui.AbstractItemDelegate ) and implement the headers there, we didnt do that yet so we set header_visible to False.

Now we can see all our custom data attributes when a prim is selected :)

UI Documentation:
https://docs.omniverse.nvidia.com/py/kit/docs/api/core/omni.ui.htm

--

--

Michael T. Wagner

CTO and Co-Founder @ipolog.ai & synctwin.ai, creating clever solutions for smart factory