Stage Preview Widget

The StagePreviewWidget is comprised of a few omni.ui objects which are stacked on top of one-another. So first an omni.ui.ZStack container is instantiated and using the omni.ui with syntax, the remaining objects are created in its scope:

self.__ui_container = ui.ZStack()
with self.__ui_container:

Background

First we create an omni.ui.Rectangle that will fill the entire frame with a constant color, using black as the default. We add a few additional style arguments so that the background color can be controlled easily from calling code.

# Add a background Rectangle that is black by default, but can change with a set_style 
ui.Rectangle(style_type_name_override='ViewportBackgroundColor', style={'ViewportBackgroundColor': {'background_color': 0xff000000}})

ViewportWidget

Next the omni.kit.widget.viewport.ViewportWidget is created, directly above the background Rectangle. In the stage_preview example, the ViewportWidget is actually created to always fill the parent frame by passing resolution='fill_frame', which will essentially make it so the black background never seen. If a constant resolution was requested by passing a tuple resolution=(640, 480); however, the rendered image would be locked to that resolution and causing the background rect to be seen if the Widget was wider or taller than the texture’s resolution.

self.__vp_widget = ViewportWidget(usd_context_name=usd_context_name, camera_path=camera_path, resolution=resolution, *ui_args, **ui_kw_args)

SceneView

The final omni.ui element we are going to create is an omni.ui.scene.SceneView. We are creating it primarily to host our camera-manipulators which are written for the omni.ui.scene framework, but it could also be used to insert additional drawing on top of the ViewportWidget in 3D space.

# Add the omni.ui.scene.SceneView that is going to host the camera-manipulator
self.__scene_view = sc.SceneView(aspect_ratio_policy=sc.AspectRatioPolicy.STRETCH)

# And finally add the camera-manipulator into that view
with self.__scene_view.scene:

Full code

class StagePreviewWidget:
    def __init__(self, usd_context_name: str = '', camera_path: str = None, resolution: Union[tuple, str] = None, *ui_args ,**ui_kw_args):
        """StagePreviewWidget contructor
        Args:
            usd_context_name (str): The name of a UsdContext the Viewport will be viewing.
            camera_path (str): The path of the initial camera to render to.
            resolution (x, y): The resolution of the backing texture, or 'fill_frame' to match the widget's ui-size
            *ui_args, **ui_kw_args: Additional arguments to pass to the ViewportWidget's parent frame
        """
        # Put the Viewport in a ZStack so that a background rectangle can be added underneath
        self.__ui_container = ui.ZStack()
        with self.__ui_container:
            # Add a background Rectangle that is black by default, but can change with a set_style 
            ui.Rectangle(style_type_name_override='ViewportBackgroundColor', style={'ViewportBackgroundColor': {'background_color': 0xff000000}})

            # Create the ViewportWidget, forwarding all of the arguments to this constructor
            self.__vp_widget = ViewportWidget(usd_context_name=usd_context_name, camera_path=camera_path, resolution=resolution, *ui_args, **ui_kw_args)

            # Add the omni.ui.scene.SceneView that is going to host the camera-manipulator
            self.__scene_view = sc.SceneView(aspect_ratio_policy=sc.AspectRatioPolicy.STRETCH)

            # And finally add the camera-manipulator into that view
            with self.__scene_view.scene:
                self.__camera_manip = ViewportCameraManipulator(self.viewport_api)
                model = self.__camera_manip.model

                # Let's disable any undo for these movements as we're a preview-window
                model.set_ints('disable_undo', [1])

                # We'll also let the Viewport automatically push view and projection changes into our scene-view
                self.viewport_api.add_scene_view(self.__scene_view)

    def __del__(self):
        self.destroy()

    def destroy(self):
        self.__view_change_sub = None
        if self.__camera_manip:
            self.__camera_manip.destroy()
            self.__camera_manip = None
        if self.__scene_view:
            self.__scene_view.destroy()
            self.__scene_view = None
        if self.__vp_widget:
            self.__vp_widget.destroy()
            self.__vp_widget = None
        if self.__ui_container:
            self.__ui_container.destroy()
            self.__ui_container = None

    @property
    def viewport_api(self):
        # Access to the underying ViewportAPI object to control renderer, resolution
        return self.__vp_widget.viewport_api

    @property
    def scene_view(self):
        # Access to the omni.ui.scene.SceneView
        return self.__scene_view

    def set_style(self, *args, **kwargs):
        # Give some styling access
        self.__ui_container.set_style(*args, **kwargs)