Extension: omni.kit.scene_view.xr_utils-1.0.2 |
Documentation Generated: Jul 18, 2026 |
Examples#
Three annotated scripts live in docs/examples/. They are runnable as-is —
paste one into the Kit script editor with the extension enabled, and the
panel appears in the active viewport. Each script is reproduced below from
its source file via literalinclude, so what you read here is exactly what
ships in the extension.
1. Basic in-scene panel#
The minimum viable example: build an omni.ui.Widget, wrap it in a
WidgetComponent, place it with a
single
SpatialSource.new_translation_source,
and hand both to a UiContainer.
The script walks through every constructor argument once and shows two
cleanup options (drop the reference, or call hide()).
import omni.ui as ui
from omni.kit.scene_view.xr import XRSceneView
from omni.kit.scene_view.xr_utils import UiContainer, WidgetComponent, UpdatePolicy, SpatialSource
from pxr import Gf
# -- 1. Define your omni.ui widget ---------------------------------------------
class InfoPanel(ui.Widget):
"""A simple label panel to show in the XR scene."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
with ui.Frame():
with ui.VStack(style={"background_color": ui.color(0.1, 0.1, 0.15, 0.9)}):
ui.Label(
"Hello XR!",
alignment=ui.Alignment.CENTER,
style={"font_size": 24, "color": ui.color(1.0, 1.0, 1.0)},
)
ui.Label(
"Kit — omni.kit.scene_view.xr_utils",
alignment=ui.Alignment.CENTER,
style={"font_size": 14, "color": ui.color(0.7, 0.9, 1.0)},
)
# -- 2. Create the WidgetComponent ---------------------------------------------
#
# Width / height are in world units (centimetres by default).
# resolution_scale * unit_to_pixel_scale controls texture quality:
# higher → sharper but more GPU memory.
panel_component = WidgetComponent(
widget_type=InfoPanel,
width=100.0, # 100 cm wide
height=20.0, # 20 cm tall
resolution_scale=4.0, # 4x supersampling on the texture
unit_to_pixel_scale=2.0, # 2 omni.ui pixels per stage unit (cm)
update_policy=UpdatePolicy.ON_MOUSE_HOVERED,
)
# -- 3. Position the panel with SpatialSource ----------------------------------
#
# SpatialSource describes where in world space the UI lives.
# new_translation_source offsets by a fixed (x, y, z) vector in stage units.
# Stack multiple sources for combined transforms — they apply left-to-right.
placement = SpatialSource.new_translation_source(Gf.Vec3f(0.0, 150.0, -80.0)) # 1.5 m up, 80 cm in front
# -- 4. Wrap everything in a UiContainer ---------------------------------------
#
# UiContainer owns both the SceneView and the manipulator hierarchy.
# Dropping this object removes the UI from the scene — keep a strong reference.
# Use show() / hide() instead of recreating; recreating leaks SceneView memory.
panel = UiContainer(
scene_view_type=XRSceneView,
initial_component=panel_component,
space_stack=placement,
)
# -- 5. Cleanup ----------------------------------------------------------------
#
# When your extension unloads, simply drop the reference:
#
# panel = None
#
# Or toggle visibility without destroying the object:
#
# panel.hide()
# panel.show()
Try modifying:
Replace
new_translation_sourcewith a list[translation, look_at_camera]to make the panel always face the camera.Raise
resolution_scaleto 8.0 to render the panel at a higher texture resolution.Swap
UpdatePolicy.ON_MOUSE_HOVEREDforUpdatePolicy.ALWAYSto redraw every frame instead of only while hovered.
Concepts exercised: WidgetComponent, the simplest case of Spatial Sources.
2. Camera-facing HUD#
A billboard panel that always faces the viewer — the standard pattern for
status readouts, FPS counters, and tooltips. The space_stack composes two
sources: a translation that positions the pivot, then a look_at_camera
source that rotates around that pivot every frame.
import omni.ui as ui
from omni.kit.scene_view.xr import XRSceneView
from omni.kit.scene_view.xr_utils import UiContainer, WidgetComponent, UpdatePolicy, SpatialSource
from pxr import Gf
# -- Widget --------------------------------------------------------------------
class StatusHUD(ui.Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
with ui.Frame():
with ui.VStack(
style={
"background_color": ui.color(0.05, 0.05, 0.1, 0.85),
"border_radius": 8,
}
):
ui.Label(
"Status: Connected",
alignment=ui.Alignment.CENTER,
style={"font_size": 18, "color": ui.color(0.2, 1.0, 0.4)},
)
ui.Spacer(height=4)
ui.Label(
"FPS: 90",
alignment=ui.Alignment.CENTER,
style={"font_size": 14, "color": ui.color(0.8, 0.8, 0.8)},
)
# -- Component -----------------------------------------------------------------
hud_component = WidgetComponent(
widget_type=StatusHUD,
width=80.0,
height=20.0,
resolution_scale=5.0,
unit_to_pixel_scale=2.0,
update_policy=UpdatePolicy.ALWAYS, # update every frame so status stays fresh
)
# -- Spatial stack -------------------------------------------------------------
#
# The sources are applied in list order.
# Translating first places the pivot, then LookAtCamera rotates around it.
space_stack = [
SpatialSource.new_translation_source(Gf.Vec3f(0.0, 170.0, -60.0)),
SpatialSource.new_look_at_camera_source(),
]
# -- Container -----------------------------------------------------------------
status_hud = UiContainer(
scene_view_type=XRSceneView,
initial_component=hud_component,
space_stack=space_stack,
)
Try modifying:
Reorder the
space_stackto putlook_at_camerafirst. The result is visually identical here, because the look-at source contributes rotation only and there is nothing before the translation to rotate around — see Example 3 for a case where the order does matter.Drop
UpdatePolicy.ALWAYStoON_MOUSE_HOVERED. The HUD will stop refreshing until you point at it.
Concepts exercised: composing a two-source space_stack, billboard
behaviour via
SpatialSource.new_look_at_camera_source.
3. Prim-anchored label#
Attach a label to a USD prim’s world transform via
SpatialSource.new_prim_path_source,
then lift the label 30 cm above the prim’s origin and have it face the
viewer. This example shows the most subtle composition rule: the prim-path
source overrides preceding contributions, so it must come first in the
stack, with relative offsets afterwards.
import omni.ui as ui
from omni.kit.scene_view.xr import XRSceneView
from omni.kit.scene_view.xr_utils import UiContainer, WidgetComponent, UpdatePolicy, SpatialSource
from pxr import Gf
ASSET_PRIM_PATH = "/World/MyMachine" # the prim this UI should follow
# -- Widget --------------------------------------------------------------------
class ObjectLabel(ui.Widget):
def __init__(self, prim_path: str, **kwargs):
super().__init__(**kwargs)
name = prim_path.split("/")[-1]
with ui.Frame():
with ui.HStack(
style={
"background_color": ui.color(0.1, 0.1, 0.2, 0.88),
"padding": 6,
"border_radius": 4,
}
):
ui.Label(
f"+ {name}",
alignment=ui.Alignment.CENTER,
style={"font_size": 20, "color": ui.color(1.0, 0.85, 0.4)},
)
# -- Component -----------------------------------------------------------------
label_component = WidgetComponent(
widget_type=ObjectLabel,
width=75.0,
height=10.0,
resolution_scale=4.0,
unit_to_pixel_scale=2.0,
update_policy=UpdatePolicy.ON_MOUSE_HOVERED,
widget_kwargs={"prim_path": ASSET_PRIM_PATH},
)
# -- Spatial stack -------------------------------------------------------------
#
# PrimPathSpace anchors the pivot to the prim's world transform.
# The translation afterwards lifts the label above the object's origin.
space_stack = [
SpatialSource.new_prim_path_source(ASSET_PRIM_PATH),
SpatialSource.new_translation_source(Gf.Vec3f(0.0, 30.0, 0.0)), # 30 cm above prim origin
SpatialSource.new_look_at_camera_source(), # always face the viewer
]
# -- Container -----------------------------------------------------------------
object_label = UiContainer(
scene_view_type=XRSceneView,
initial_component=label_component,
space_stack=space_stack,
)
# To update the label text dynamically (e.g. on a stage event), call:
#
# label_component.scene_widget.invalidate()
#
# This triggers a rebuild of the widget's omni.ui tree.
Try modifying:
Change
ASSET_PRIM_PATHto point at a prim in your stage. The label picks up the new prim on the next frame.Swap the order of the prim-path and translation sources. The label will end up at the world origin offset, ignoring the prim — see the override semantics described in Spatial Sources.
Reach into
label_component.widgetto drive the label text from a stage event, then calllabel_component.scene_widget.invalidate()to apply the change.
Concepts exercised: prim anchoring, three-source composition, the
widget_kwargs pattern for passing constructor data into a wrapped widget.
See also#
Spatial Sources — the composition rules used in examples 2 and 3.
WidgetComponent — the sizing and update-policy concepts behind every example’s component.
Migration — if you have existing code using the deprecated
SceneWidgetManipulator, this is where the example patterns slot in.