Python Usage Examples#

# Copyright (c) 2021-2022, NVIDIA CORPORATION.  All rights reserved.
#
# NVIDIA CORPORATION and its licensors retain all intellectual property
# and proprietary rights in and to this software, related documentation
# and any modifications thereto.  Any use, reproduction, diui.scenelosure or
# distribution of this software and related documentation without an express
# license agreement from NVIDIA CORPORATION is strictly prohibited.
#

__all__ = ['SimpleWindow']

import omni.ui as ui
import carb.input
from omni.ui import scene as sc
from pxr import Gf
from omni.kit.manipulator.camera import (
    CameraManipulatorBase,
    SceneViewCameraManipulator,
    adjust_center_of_interest,
    CameraGestureBase,
    LookGesture,
    PanGesture,
    TumbleGesture,
    ZoomGesture,
)


class CustomGesture(CameraGestureBase):
    def on_mouse_move(self, mouse_moved):
        print("on custom  gesture mouse move")
        super().on_mouse_move(mouse_moved)


class CustomLookGesture(LookGesture):
    def on_mouse_move(self, mouse_moved):
        print("on custom look gesture mouse move")
        super().on_mouse_move(mouse_moved)


class CustomTumbleGesture(TumbleGesture):
    def on_mouse_move(self, mouse_moved):
        print("on custom Tumble gesture mouse move")
        super().on_mouse_move(mouse_moved)


class CustomZoomGesture(ZoomGesture):
    def on_mouse_move(self, mouse_moved):
        print("on custom zoom gesture mouse move")
        super().on_mouse_move(mouse_moved)


class CustomPanGesture(PanGesture):
    def on_mouse_move(self, mouse_moved):
        print("on custom pan gesture mouse move")
        super().on_mouse_move(mouse_moved)


CustomBindings = {
    'CustomPanGesture': 'Any MiddleButton',
    'CustomTumbleGesture': 'Alt LeftButton',
    'CustomZoomGesture': 'Alt RightButton',
    'CustomLookGesture': 'RightButton',
    "CustomGesture": "Any RightButton",

}

def build_gestures(model: sc.AbstractManipulatorModel,
                   bindings: dict = None,
                   manager: sc.GestureManager = None,
                   configure_model = None):
    def _parse_binding(binding_str: str):
        keys = binding_str.split(' ')
        button = {
            'LeftButton': 0,
            'RightButton': 1,
            'MiddleButton': 2
        }.get(keys.pop())

        modifiers = 0
        for mod_str in keys:
            mod_bit = {
                'Shift': carb.input.KEYBOARD_MODIFIER_FLAG_SHIFT,
                'Ctrl': carb.input.KEYBOARD_MODIFIER_FLAG_CONTROL,
                'Alt': carb.input.KEYBOARD_MODIFIER_FLAG_ALT,
                'Super': carb.input.KEYBOARD_MODIFIER_FLAG_SUPER,
                'Any': 0xffffffff,
            }.get(mod_str)
            if not mod_bit:
                raise RuntimeError(f'Unparseable binding: {binding_str}')
            modifiers = modifiers | mod_bit

        return (button, modifiers)

    gestures = []
    for gesture, binding in bindings.items():
        instantiator = globals().get(gesture)
        if not instantiator:
            continue
        button, modifers = _parse_binding(binding)
        gestures.append(instantiator(model, configure_model, mouse_button=button, modifiers=modifers, manager=manager))
    return gestures


class CustomCameraManipulator(CameraManipulatorBase):
    def on_build(self):
        # Need to hold a reference to this or the sc.Screen would be destroyed when out of scope
        """ Called when the manipulator is build. """
        self.__transform = sc.Transform()
        with self.__transform:
            self._screen = sc.Screen(gestures=build_gestures(self.model, self.bindings, self.manager, self._on_began))


def _flatten_matrix(matrix: Gf.Matrix4d):
    return [matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3],
            matrix[1][0], matrix[1][1], matrix[1][2], matrix[1][3],
            matrix[2][0], matrix[2][1], matrix[2][2], matrix[2][3],
            matrix[3][0], matrix[3][1], matrix[3][2], matrix[3][3]]


class SimpleGrid():
    def __init__(self, lineCount: float = 100, lineStep: float = 10, thicknes: float = 1, color: ui.color = ui.color(0.25)):
        self.__transform = ui.scene.Transform()
        with self.__transform:
            for i in range(lineCount * 2 + 1):
                ui.scene.Line(
                    ((i - lineCount) * lineStep, 0, -lineCount * lineStep),
                    ((i - lineCount) * lineStep, 0, lineCount * lineStep),
                    color=color, thickness=thicknes,
                )
                ui.scene.Line(
                    (-lineCount * lineStep, 0, (i - lineCount) * lineStep),
                    (lineCount * lineStep, 0, (i - lineCount) * lineStep),
                    color=color, thickness=thicknes,
                )


class SimpleOrigin():
    def __init__(self, length: float = 5, thickness: float = 4):
        origin = (0, 0, 0)
        with ui.scene.Transform():
            ui.scene.Line(origin, (length, 0, 0), color=ui.color.red, thickness=thickness)
            ui.scene.Line(origin, (0, length, 0), color=ui.color.green, thickness=thickness)
            ui.scene.Line(origin, (0, 0, length), color=ui.color.blue, thickness=thickness)


# Create a few scenes with different camera-maniupulators (a general ui.scene manip and one that allows ortho-tumble )
class SimpleScene:
    def __init__(self, custom: bool = False, ortho: bool = False, *args, **kwargs):
        self.__scene_view = ui.scene.SceneView(*args, **kwargs)
        if ortho:
            view = [-1.0, -2.719262146893781e-32, -1.224646799147353e-16, 0.0, -1.2246467991473532e-16, 2.220446049250312e-16, 0.9999999999999998, 0.0, 0.0, 0.9999999999999998, -2.220446049250312e-16, 0.0, 0, 0, -2000, 1.0]
            projection = [0.0022443845736905463, 0.0, 0.0, 0.0, 0.0, 0.004, 0.0, 0.0, 0.0, 0.0, -2.000002000002e-06, 0.0, -0.0, -0.0, -1.000002000002, 1.0]
        else:
            view = [0.7071067811865476, -0.40557978767263897, 0.5792279653395693, 0.0, -2.775557561562892e-17, 0.8191520442889919, 0.5735764363510462, 0.0, -0.7071067811865477, -0.4055797876726389, 0.5792279653395692, 0.0, 6.838973831690966e-14, -3.996234471857009, -866.0161835150924, 1.0000000000000002]
            projection = [4.7602203407949375, 0.0, 0.0, 0.0, 0.0, 8.483787309173106, 0.0, 0.0, 0.0, 0.0, -1.000002000002, -1.0, 0.0, 0.0, -2.000002000002, 0.0]

        view = Gf.Matrix4d(*view)
        center_of_interest = [0, 0, -view.Transform((0, 0, 0)).GetLength()]
        with self.__scene_view.scene:
            self.items = [SimpleGrid(), ui.scene.Arc(100, axis=1, wireframe=True), SimpleOrigin()]

            if custom:
                # Also could use 'self.items.append(CameraManipulatorBase())' if don't want custom gestures
                self.items.append(CustomCameraManipulator(bindings=CustomBindings))
            else:
                self.items.append(SceneViewCameraManipulator(center_of_interest))

        # Push the start values into the CameraManipulator
        self.setup_camera_model(self.items[-1].model, view, projection, center_of_interest, ortho)

    def setup_camera_model(self, cam_model, view, projection, center_of_interest, ortho):
        cam_model.set_floats('transform', _flatten_matrix(view.GetInverse()))
        cam_model.set_floats('projection', projection)
        cam_model.set_floats('center_of_interest', [0, 0, -view.Transform((0, 0, 0)).GetLength()])
        if ortho:
            cam_model.set_ints('orthographic', [ortho])

        # Setup up the subscription to the CameraModel so changes here get pushed to SceneView
        self.model_changed_sub = cam_model.subscribe_item_changed_fn(self.model_changed)
        # And pusht the view and projection into the SceneView.model
        cam_model._item_changed(cam_model.get_item('transform'))
        cam_model._item_changed(cam_model.get_item('projection'))

    def model_changed(self, model, item):
        if item == model.get_item('transform'):
            transform = Gf.Matrix4d(*model.get_as_floats(item))
            # Signal that this this is the final change block, adjust our center-of-interest then
            interaction_ended = model.get_as_ints('interaction_ended')
            if interaction_ended and interaction_ended[0]:
                transform = Gf.Matrix4d(*model.get_as_floats(item))
                # Adjust the center-of-interest if requested (zoom out in perspective does this)
                initial_transform = Gf.Matrix4d(*model.get_as_floats('initial_transform'))
                coi_start, coi_end = adjust_center_of_interest(model, initial_transform, transform)
                if coi_end:
                    model.set_floats('center_of_interest', [coi_end[0], coi_end[1], coi_end[2]])

            # Push the start values into the SceneView
            self.__scene_view.model.set_floats('view', _flatten_matrix(transform.GetInverse()))
        elif item == model.get_item('projection'):
            self.__scene_view.model.set_floats('projection', model.get_as_floats('projection'))


class SimpleWindow(ui.Window):
    def __init__(self, name: str, custom=False, ortho: bool = False):
        super().__init__(f'{name} Camera Manipulator', width=640, height=480 + 20, flags=ui.WINDOW_FLAGS_NO_SCROLLBAR)
        self.frame.set_style({"Window": {"background_color": 0xff000000}})
        with self.frame:
            self.__scene_view = SimpleScene(custom, ortho)

    def destroy(self):
        if self.__scene_view:
            self.__scene_view.destroy()
        super().destroy()


persp_win = SimpleWindow('Perspective')
ortho_win = SimpleWindow('Orthographic', ortho=True)
ortho_custom_win = SimpleWindow('Custom  Orthographic', custom=True, ortho=True)