Skip to content
Snippets Groups Projects
viewport_vr_preview.py 26 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### BEGIN GPL LICENSE BLOCK #####
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation; either version 2
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software Foundation,
    #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    # ##### END GPL LICENSE BLOCK #####
    
    # <pep8 compliant>
    
    import bpy
    from bpy.types import (
        Gizmo,
        GizmoGroup,
    
        PropertyGroup,
        UIList,
        Menu,
        Panel,
        Operator,
    
    )
    from bpy.props import (
        CollectionProperty,
        IntProperty,
        BoolProperty,
    )
    from bpy.app.handlers import persistent
    
    bl_info = {
        "name": "VR Scene Inspection",
    
        "author": "Julian Eisel (Severin), Sebastian Koenig",
    
        "version": (0, 9, 0),
        "blender": (2, 90, 0),
    
        "location": "3D View > Sidebar > VR",
        "description": ("View the viewport with virtual reality glasses "
                        "(head-mounted displays)"),
        "support": "OFFICIAL",
        "warning": "This is an early, limited preview of in development "
                   "VR support for Blender.",
    
        "doc_url": "{BLENDER_MANUAL_URL}/addons/3d_view/vr_scene_inspection.html",
    
        "category": "3D View",
    }
    
    
    @persistent
    def ensure_default_vr_landmark(context: bpy.context):
        # Ensure there's a default landmark (scene camera by default).
        landmarks = bpy.context.scene.vr_landmarks
        if not landmarks:
            landmarks.add()
            landmarks[0].type = 'SCENE_CAMERA'
    
    
    def xr_landmark_active_type_update(self, context):
        wm = context.window_manager
        session_settings = wm.xr_session_settings
        landmark_active = VRLandmark.get_active_landmark(context)
    
        # Update session's base pose type to the matching type.
        if landmark_active.type == 'SCENE_CAMERA':
            session_settings.base_pose_type = 'SCENE_CAMERA'
        elif landmark_active.type == 'USER_CAMERA':
            session_settings.base_pose_type = 'OBJECT'
    
        elif landmark_active.type == 'CUSTOM':
            session_settings.base_pose_type = 'CUSTOM'
    
    
    
    def xr_landmark_active_camera_update(self, context):
        session_settings = context.window_manager.xr_session_settings
        landmark_active = VRLandmark.get_active_landmark(context)
    
        # Update the anchor object to the (new) camera of this landmark.
        session_settings.base_pose_object = landmark_active.base_pose_camera
    
    
    def xr_landmark_active_base_pose_location_update(self, context):
        session_settings = context.window_manager.xr_session_settings
        landmark_active = VRLandmark.get_active_landmark(context)
    
        session_settings.base_pose_location = landmark_active.base_pose_location
    
    
    def xr_landmark_active_base_pose_angle_update(self, context):
        session_settings = context.window_manager.xr_session_settings
        landmark_active = VRLandmark.get_active_landmark(context)
    
        session_settings.base_pose_angle = landmark_active.base_pose_angle
    
    
    def xr_landmark_type_update(self, context):
        landmark_selected = VRLandmark.get_selected_landmark(context)
        landmark_active = VRLandmark.get_active_landmark(context)
    
        # Only update session settings data if the changed landmark is actually
        # the active one.
        if landmark_active == landmark_selected:
            xr_landmark_active_type_update(self, context)
    
    
    def xr_landmark_camera_update(self, context):
        landmark_selected = VRLandmark.get_selected_landmark(context)
        landmark_active = VRLandmark.get_active_landmark(context)
    
        # Only update session settings data if the changed landmark is actually
        # the active one.
        if landmark_active == landmark_selected:
            xr_landmark_active_camera_update(self, context)
    
    
    def xr_landmark_base_pose_location_update(self, context):
        landmark_selected = VRLandmark.get_selected_landmark(context)
        landmark_active = VRLandmark.get_active_landmark(context)
    
        # Only update session settings data if the changed landmark is actually
        # the active one.
        if landmark_active == landmark_selected:
            xr_landmark_active_base_pose_location_update(self, context)
    
    
    def xr_landmark_base_pose_angle_update(self, context):
        landmark_selected = VRLandmark.get_selected_landmark(context)
        landmark_active = VRLandmark.get_active_landmark(context)
    
        # Only update session settings data if the changed landmark is actually
        # the active one.
        if landmark_active == landmark_selected:
            xr_landmark_active_base_pose_angle_update(self, context)
    
    
    def xr_landmark_camera_object_poll(self, object):
        return object.type == 'CAMERA'
    
    
    def xr_landmark_active_update(self, context):
    
        xr_landmark_active_type_update(self, context)
        xr_landmark_active_camera_update(self, context)
        xr_landmark_active_base_pose_location_update(self, context)
        xr_landmark_active_base_pose_angle_update(self, context)
    
    
            wm.xr_session_state.reset_to_base_pose(context)
    
    class VIEW3D_MT_landmark_menu(Menu):
        bl_label = "Landmark Controls"
    
        def draw(self, _context):
            layout = self.layout
    
            layout.operator("view3d.vr_landmark_from_camera")
            layout.operator("view3d.update_vr_landmark")
            layout.separator()
            layout.operator("view3d.cursor_to_vr_landmark")
    
            layout.operator("view3d.camera_to_vr_landmark")
            layout.operator("view3d.add_camera_from_vr_landmark")
    
    
    
    class VRLandmark(PropertyGroup):
    
        name: bpy.props.StringProperty(
            name="VR Landmark",
            default="Landmark"
        )
        type: bpy.props.EnumProperty(
            name="Type",
            items=[
                ('SCENE_CAMERA', "Scene Camera",
                 "Use scene's currently active camera to define the VR view base "
                 "location and rotation"),
                ('USER_CAMERA', "Custom Camera",
                 "Use an existing camera to define the VR view base location and "
                 "rotation"),
    
                ('CUSTOM', "Custom Pose",
    
                 "Allow a manually defined position and rotation to be used as "
    
                 "the VR view base pose"),
    
            ],
            default='SCENE_CAMERA',
            update=xr_landmark_type_update,
        )
        base_pose_camera: bpy.props.PointerProperty(
            name="Camera",
            type=bpy.types.Object,
            poll=xr_landmark_camera_object_poll,
            update=xr_landmark_camera_update,
        )
        base_pose_location: bpy.props.FloatVectorProperty(
            name="Base Pose Location",
            subtype='TRANSLATION',
            update=xr_landmark_base_pose_location_update,
        )
    
        base_pose_angle: bpy.props.FloatProperty(
            name="Base Pose Angle",
            subtype='ANGLE',
            update=xr_landmark_base_pose_angle_update,
        )
    
        @staticmethod
        def get_selected_landmark(context):
            scene = context.scene
            landmarks = scene.vr_landmarks
    
            return (
                None if (len(landmarks) <
                         1) else landmarks[scene.vr_landmarks_selected]
            )
    
        @staticmethod
        def get_active_landmark(context):
            scene = context.scene
            landmarks = scene.vr_landmarks
    
            return (
                None if (len(landmarks) <
                         1) else landmarks[scene.vr_landmarks_active]
            )
    
    
    
    class VIEW3D_UL_vr_landmarks(UIList):
    
        def draw_item(self, context, layout, _data, item, icon, _active_data,
                      _active_propname, index):
            landmark = item
            landmark_active_idx = context.scene.vr_landmarks_active
    
            layout.emboss = 'NONE'
    
            layout.prop(landmark, "name", text="")
    
    
            icon = (
                'RADIOBUT_ON' if (index == landmark_active_idx) else 'RADIOBUT_OFF'
            )
    
            props = layout.operator(
                "view3d.vr_landmark_activate", text="", icon=icon)
            props.index = index
    
    
    
    class VIEW3D_PT_vr_landmarks(Panel):
    
        bl_space_type = 'VIEW_3D'
        bl_region_type = 'UI'
        bl_category = "VR"
        bl_label = "Landmarks"
        bl_options = {'DEFAULT_CLOSED'}
    
        def draw(self, context):
            layout = self.layout
            scene = context.scene
            landmark_selected = VRLandmark.get_selected_landmark(context)
    
            layout.use_property_split = True
            layout.use_property_decorate = False  # No animation.
    
            row = layout.row()
    
            row.template_list("VIEW3D_UL_vr_landmarks", "", scene, "vr_landmarks",
                              scene, "vr_landmarks_selected", rows=3)
    
            col = row.column(align=True)
            col.operator("view3d.vr_landmark_add", icon='ADD', text="")
            col.operator("view3d.vr_landmark_remove", icon='REMOVE', text="")
    
            col.operator("view3d.vr_landmark_from_session", icon='PLUS', text="")
    
            col.menu("VIEW3D_MT_landmark_menu", icon='DOWNARROW_HLT', text="")
    
    
            if landmark_selected:
                layout.prop(landmark_selected, "type")
    
                if landmark_selected.type == 'USER_CAMERA':
                    layout.prop(landmark_selected, "base_pose_camera")
    
                elif landmark_selected.type == 'CUSTOM':
                    layout.prop(landmark_selected,
                                "base_pose_location", text="Location")
                    layout.prop(landmark_selected,
                                "base_pose_angle", text="Angle")
    
    class VIEW3D_PT_vr_session_view(Panel):
    
        bl_space_type = 'VIEW_3D'
        bl_region_type = 'UI'
        bl_category = "VR"
        bl_label = "View"
    
        def draw(self, context):
            layout = self.layout
            session_settings = context.window_manager.xr_session_settings
    
            layout.use_property_split = True
            layout.use_property_decorate = False  # No animation.
    
    
            col = layout.column(align=True, heading="Show")
            col.prop(session_settings, "show_floor", text="Floor")
            col.prop(session_settings, "show_annotation", text="Annotations")
    
    
            col = layout.column(align=True)
            col.prop(session_settings, "clip_start", text="Clip Start")
            col.prop(session_settings, "clip_end", text="End")
    
    
    
    class VIEW3D_PT_vr_session(Panel):
    
        bl_space_type = 'VIEW_3D'
        bl_region_type = 'UI'
        bl_category = "VR"
        bl_label = "VR Session"
    
        def draw(self, context):
            layout = self.layout
            session_settings = context.window_manager.xr_session_settings
    
    
            layout.use_property_split = True
            layout.use_property_decorate = False  # No animation.
    
    
            is_session_running = bpy.types.XrSessionState.is_running(context)
    
            # Using SNAP_FACE because it looks like a stop icon -- I shouldn't
            # have commit rights...
            toggle_info = (
                ("Start VR Session", 'PLAY') if not is_session_running else (
                    "Stop VR Session", 'SNAP_FACE')
            )
            layout.operator("wm.xr_session_toggle",
                            text=toggle_info[0], icon=toggle_info[1])
    
            layout.separator()
    
            layout.prop(session_settings, "use_positional_tracking")
    
            layout.prop(session_settings, "use_absolute_tracking")
    
    class VIEW3D_PT_vr_info(bpy.types.Panel):
        bl_space_type = 'VIEW_3D'
        bl_region_type = 'UI'
        bl_category = "VR"
        bl_label = "VR Info"
    
        @classmethod
        def poll(cls, context):
            return not bpy.app.build_options.xr_openxr
    
        def draw(self, context):
            layout = self.layout
    
            layout.label(icon='ERROR', text="Built without VR/OpenXR features")
    
    
    class VIEW3D_OT_vr_landmark_add(Operator):
    
        bl_idname = "view3d.vr_landmark_add"
        bl_label = "Add VR Landmark"
        bl_description = "Add a new VR landmark to the list and select it"
        bl_options = {'UNDO', 'REGISTER'}
    
        def execute(self, context):
            scene = context.scene
            landmarks = scene.vr_landmarks
    
            landmarks.add()
    
            # select newly created set
            scene.vr_landmarks_selected = len(landmarks) - 1
    
            return {'FINISHED'}
    
    
    
    class VIEW3D_OT_vr_landmark_from_camera(Operator):
        bl_idname = "view3d.vr_landmark_from_camera"
    
        bl_label = "Add VR Landmark from camera"
        bl_description = "Add a new VR landmark from the active camera object to the list and select it"
    
        bl_options = {'UNDO', 'REGISTER'}
    
        @classmethod
        def poll(cls, context):
    
    
            vl_objects = bpy.context.view_layer.objects
            if vl_objects.active and vl_objects.active.type == 'CAMERA':
    
            return cam_selected
    
        def execute(self, context):
            scene = context.scene
            landmarks = scene.vr_landmarks
            cam = context.view_layer.objects.active
            lm = landmarks.add()
            lm.type = 'USER_CAMERA'
            lm.base_pose_camera = cam
            lm.name = "LM_" + cam.name
    
            # select newly created set
            scene.vr_landmarks_selected = len(landmarks) - 1
    
            return {'FINISHED'}
    
    
    class VIEW3D_OT_vr_landmark_from_session(Operator):
        bl_idname = "view3d.vr_landmark_from_session"
        bl_label = "Add VR Landmark from session"
    
        bl_description = "Add VR landmark from the viewer pose of the running VR session to the list and select it"
    
        bl_options = {'UNDO', 'REGISTER'}
    
        @classmethod
        def poll(cls, context):
            return bpy.types.XrSessionState.is_running(context)
    
    
        @staticmethod
        def _calc_landmark_angle_from_viewer_rotation(rot):
            from mathutils import Vector
    
            # We want an angle around Z based on the current viewer rotation. Idea
            # is to create a vector from the viewer rotation, project that onto a
            # Z-Up plane and use the resulting vector to get an angle around Z.
    
            view_rot_vec = Vector((0, 0, 1))
            view_rot_vec.rotate(rot)
            angle_vec = view_rot_vec - view_rot_vec.project(Vector((0, 0, 1)))
    
            # We could probably use a 3D version of Vector.angle_signed() here, but
            # that's not available. So manually calculate it via a quaternion delta.
            forward_vec = Vector((0, -1, 0))
            diff = angle_vec.rotation_difference(forward_vec)
    
            return diff.angle * -diff.axis[2]
    
    
        def execute(self, context):
            scene = context.scene
            landmarks = scene.vr_landmarks
            wm = context.window_manager
    
            lm = landmarks.add()
    
            scene.vr_landmarks_selected = len(landmarks) - 1
    
    
            loc = wm.xr_session_state.viewer_pose_location
    
            rot = wm.xr_session_state.viewer_pose_rotation
            angle = self._calc_landmark_angle_from_viewer_rotation(rot)
    
    
            lm.base_pose_location = loc
    
    
            return {'FINISHED'}
    
    
    class VIEW3D_OT_update_vr_landmark(Operator):
        bl_idname = "view3d.update_vr_landmark"
    
        bl_label = "Update Custom VR Landmark"
        bl_description = "Update the selected landmark from the current viewer pose in the VR session"
    
        bl_options = {'UNDO', 'REGISTER'}
    
        @classmethod
        def poll(cls, context):
    
            selected_landmark = VRLandmark.get_selected_landmark(context)
            return bpy.types.XrSessionState.is_running(context) and selected_landmark.type == 'CUSTOM'
    
    
        def execute(self, context):
            wm = context.window_manager
    
    
            lm = VRLandmark.get_selected_landmark(context)
    
    
            loc = wm.xr_session_state.viewer_pose_location
            rot = wm.xr_session_state.viewer_pose_rotation.to_euler()
    
            lm.base_pose_location = loc
            lm.base_pose_angle = rot
    
    
            # Re-activate the landmark to trigger viewer reset and flush landmark settings to the session settings.
            xr_landmark_active_update(None, context)
    
    
            return {'FINISHED'}
    
    
    class VIEW3D_OT_vr_landmark_remove(Operator):
    
        bl_idname = "view3d.vr_landmark_remove"
        bl_label = "Remove VR Landmark"
        bl_description = "Delete the selected VR landmark from the list"
        bl_options = {'UNDO', 'REGISTER'}
    
        def execute(self, context):
            scene = context.scene
            landmarks = scene.vr_landmarks
    
            if len(landmarks) > 1:
                landmark_selected_idx = scene.vr_landmarks_selected
                landmarks.remove(landmark_selected_idx)
    
                scene.vr_landmarks_selected -= 1
    
            return {'FINISHED'}
    
    
    
    class VIEW3D_OT_cursor_to_vr_landmark(Operator):
        bl_idname = "view3d.cursor_to_vr_landmark"
        bl_label = "Cursor to VR Landmark"
    
        bl_description = "Move the 3D Cursor to the selected VR Landmark"
    
        bl_options = {'UNDO', 'REGISTER'}
    
    
        @classmethod
        def poll(cls, context):
            lm = VRLandmark.get_selected_landmark(context)
            if lm.type == 'SCENE_CAMERA':
                return context.scene.camera is not None
            elif lm.type == 'USER_CAMERA':
                return lm.base_pose_camera is not None
    
            return True
    
    
        def execute(self, context):
            scene = context.scene
    
            lm = VRLandmark.get_selected_landmark(context)
    
            if lm.type == 'SCENE_CAMERA':
                lm_pos = scene.camera.location
            elif lm.type == 'USER_CAMERA':
                lm_pos = lm.base_pose_camera.location
            else:
                lm_pos = lm.base_pose_location
            scene.cursor.location = lm_pos
    
            return{'FINISHED'}
    
    
    
    class VIEW3d_OT_add_camera_from_vr_landmark(Operator):
        bl_idname = "view3d.add_camera_from_vr_landmark"
        bl_label = "New Camera from VR Landmark"
        bl_description = "Create a new Camera from the selected VR Landmark"
    
        bl_options = {'UNDO', 'REGISTER'}
    
        def execute(self, context):
    
            scene = context.scene
            lm = VRLandmark.get_selected_landmark(context)
    
    
            cam = bpy.data.cameras.new("Camera_" + lm.name)
            new_cam = bpy.data.objects.new("Camera_" + lm.name, cam)
            scene.collection.objects.link(new_cam)
            angle = lm.base_pose_angle
            new_cam.location = lm.base_pose_location
    
            new_cam.rotation_euler = (math.pi, 0, angle)
    
    class VIEW3D_OT_camera_to_vr_landmark(Operator):
        bl_idname = "view3d.camera_to_vr_landmark"
        bl_label = "Scene Camera to VR Landmark"
        bl_description = "Position the scene camera at the selected landmark"
    
        bl_options = {'UNDO', 'REGISTER'}
    
        @classmethod
        def poll(cls, context):
            return context.scene.camera is not None
    
        def execute(self, context):
    
            scene = context.scene
            lm = VRLandmark.get_selected_landmark(context)
    
    
            cam = scene.camera
            angle = lm.base_pose_angle
            cam.location = lm.base_pose_location
    
            cam.rotation_euler = (math.pi / 2, 0, angle)
    
    
            return {'FINISHED'}
    
    
    class VIEW3D_OT_vr_landmark_activate(Operator):
    
        bl_idname = "view3d.vr_landmark_activate"
        bl_label = "Activate VR Landmark"
        bl_description = "Change to the selected VR landmark from the list"
        bl_options = {'UNDO', 'REGISTER'}
    
        index: IntProperty(
            name="Index",
            options={'HIDDEN'},
        )
    
        def execute(self, context):
            scene = context.scene
    
            if self.index >= len(scene.vr_landmarks):
                return {'CANCELLED'}
    
            scene.vr_landmarks_active = (
                self.index if self.properties.is_property_set(
                    "index") else scene.vr_landmarks_selected
            )
    
            return {'FINISHED'}
    
    
    
    class VIEW3D_PT_vr_viewport_feedback(Panel):
    
        bl_space_type = 'VIEW_3D'
        bl_region_type = 'UI'
        bl_category = "VR"
        bl_label = "Viewport Feedback"
        bl_options = {'DEFAULT_CLOSED'}
    
        def draw(self, context):
            layout = self.layout
            view3d = context.space_data
    
    
            col = layout.column(align=True)
            col.label(icon='ERROR', text="Note:")
            col.label(text="Settings here may have a significant")
            col.label(text="performance impact!")
    
            layout.separator()
    
    
            layout.prop(view3d.shading, "vr_show_virtual_camera")
    
            layout.prop(view3d.shading, "vr_show_landmarks")
    
            layout.prop(view3d, "mirror_xr_session")
    
    
    class VIEW3D_GT_vr_camera_cone(Gizmo):
        bl_idname = "VIEW_3D_GT_vr_camera_cone"
    
        aspect = 1.0, 1.0
    
        def draw(self, context):
            import bgl
    
            if not hasattr(self, "frame_shape"):
                aspect = self.aspect
    
                frame_shape_verts = (
                    (-aspect[0], -aspect[1], -1.0),
                    (aspect[0], -aspect[1], -1.0),
                    (aspect[0], aspect[1], -1.0),
                    (-aspect[0], aspect[1], -1.0),
                )
                lines_shape_verts = (
                    (0.0, 0.0, 0.0),
                    frame_shape_verts[0],
                    (0.0, 0.0, 0.0),
                    frame_shape_verts[1],
                    (0.0, 0.0, 0.0),
                    frame_shape_verts[2],
                    (0.0, 0.0, 0.0),
                    frame_shape_verts[3],
                )
    
                self.frame_shape = self.new_custom_shape(
                    'LINE_LOOP', frame_shape_verts)
                self.lines_shape = self.new_custom_shape(
                    'LINES', lines_shape_verts)
    
            # Ensure correct GL state (otherwise other gizmos might mess that up)
            bgl.glLineWidth(1)
            bgl.glEnable(bgl.GL_BLEND)
    
            self.draw_custom_shape(self.frame_shape)
            self.draw_custom_shape(self.lines_shape)
    
    
    class VIEW3D_GGT_vr_viewer_pose(GizmoGroup):
        bl_idname = "VIEW3D_GGT_vr_viewer_pose"
        bl_label = "VR Viewer Pose Indicator"
        bl_space_type = 'VIEW_3D'
        bl_region_type = 'WINDOW'
        bl_options = {'3D', 'PERSISTENT', 'SCALE', 'VR_REDRAWS'}
    
        @classmethod
        def poll(cls, context):
            view3d = context.space_data
            return (
                view3d.shading.vr_show_virtual_camera and
                bpy.types.XrSessionState.is_running(context) and
                not view3d.mirror_xr_session
            )
    
        @staticmethod
        def _get_viewer_pose_matrix(context):
            from mathutils import Matrix, Quaternion
    
            wm = context.window_manager
    
            loc = wm.xr_session_state.viewer_pose_location
            rot = wm.xr_session_state.viewer_pose_rotation
    
            rotmat = Matrix.Identity(3)
            rotmat.rotate(rot)
            rotmat.resize_4x4()
            transmat = Matrix.Translation(loc)
    
            return transmat @ rotmat
    
        def setup(self, context):
            gizmo = self.gizmos.new(VIEW3D_GT_vr_camera_cone.bl_idname)
            gizmo.aspect = 1 / 3, 1 / 4
    
            gizmo.color = gizmo.color_highlight = 0.2, 0.6, 1.0
            gizmo.alpha = 1.0
    
            self.gizmo = gizmo
    
        def draw_prepare(self, context):
            self.gizmo.matrix_basis = self._get_viewer_pose_matrix(context)
    
    
    
    class VIEW3D_GGT_vr_landmarks(GizmoGroup):
        bl_idname = "VIEW3D_GGT_vr_landmarks"
        bl_label = "VR Landmark Indicators"
    
        bl_space_type = 'VIEW_3D'
        bl_region_type = 'WINDOW'
        bl_options = {'3D', 'PERSISTENT', 'SCALE'}
    
        @classmethod
        def poll(cls, context):
            view3d = context.space_data
            return (
                view3d.shading.vr_show_landmarks
            )
    
    
            pass
    
        def draw_prepare(self, context):
    
            for g in self.gizmos:
                self.gizmos.remove(g)
    
            from math import radians
            from mathutils import Matrix, Euler
    
            scene = context.scene
            landmarks = scene.vr_landmarks
    
                if ((lm.type == 'SCENE_CAMERA' and not scene.camera) or
                        (lm.type == 'USER_CAMERA' and not lm.base_pose_camera)):
                    continue
    
    
                gizmo = self.gizmos.new(VIEW3D_GT_vr_camera_cone.bl_idname)
                gizmo.aspect = 1 / 3, 1 / 4
    
                gizmo.color = gizmo.color_highlight = 0.2, 1.0, 0.6
                gizmo.alpha = 1.0
    
                self.gizmo = gizmo
    
                if lm.type == 'SCENE_CAMERA':
    
                    cam = scene.camera
                    lm_mat = cam.matrix_world if cam else Matrix.Identity(4)
    
                elif lm.type == 'USER_CAMERA':
                    lm_mat = lm.base_pose_camera.matrix_world
                else:
                    angle = lm.base_pose_angle
                    raw_rot = Euler((radians(90.0), 0, angle))
    
                    rotmat = Matrix.Identity(3)
                    rotmat.rotate(raw_rot)
                    rotmat.resize_4x4()
    
                    transmat = Matrix.Translation(lm.base_pose_location)
    
                    lm_mat = transmat @ rotmat
    
                self.gizmo.matrix_basis = lm_mat
    
    
    
    classes = (
        VIEW3D_PT_vr_session,
        VIEW3D_PT_vr_session_view,
        VIEW3D_PT_vr_landmarks,
        VIEW3D_PT_vr_viewport_feedback,
    
        VRLandmark,
        VIEW3D_UL_vr_landmarks,
    
        VIEW3D_MT_landmark_menu,
    
    
        VIEW3D_OT_vr_landmark_add,
        VIEW3D_OT_vr_landmark_remove,
        VIEW3D_OT_vr_landmark_activate,
    
        VIEW3D_OT_vr_landmark_from_session,
    
        VIEW3d_OT_add_camera_from_vr_landmark,
        VIEW3D_OT_camera_to_vr_landmark,
    
        VIEW3D_OT_vr_landmark_from_camera,
        VIEW3D_OT_cursor_to_vr_landmark,
        VIEW3D_OT_update_vr_landmark,
    
    
        VIEW3D_GT_vr_camera_cone,
        VIEW3D_GGT_vr_viewer_pose,
    
    )
    
    
    def register():
        if not bpy.app.build_options.xr_openxr:
    
            bpy.utils.register_class(VIEW3D_PT_vr_info)
    
            return
    
        for cls in classes:
            bpy.utils.register_class(cls)
    
        bpy.types.Scene.vr_landmarks = CollectionProperty(
            name="Landmark",
            type=VRLandmark,
        )
        bpy.types.Scene.vr_landmarks_selected = IntProperty(
            name="Selected Landmark"
        )
        bpy.types.Scene.vr_landmarks_active = IntProperty(
            update=xr_landmark_active_update,
        )
        # View3DShading is the only per 3D-View struct with custom property
        # support, so "abusing" that to get a per 3D-View option.
        bpy.types.View3DShading.vr_show_virtual_camera = BoolProperty(
            name="Show VR Camera"
        )
    
        bpy.types.View3DShading.vr_show_landmarks = BoolProperty(
    
    
        bpy.app.handlers.load_post.append(ensure_default_vr_landmark)
    
    
    def unregister():
        if not bpy.app.build_options.xr_openxr:
    
            bpy.utils.unregister_class(VIEW3D_PT_vr_info)
    
            return
    
        for cls in classes:
            bpy.utils.unregister_class(cls)
    
        del bpy.types.Scene.vr_landmarks
        del bpy.types.Scene.vr_landmarks_selected
        del bpy.types.Scene.vr_landmarks_active
        del bpy.types.View3DShading.vr_show_virtual_camera
    
        del bpy.types.View3DShading.vr_show_landmarks
    
    
        bpy.app.handlers.load_post.remove(ensure_default_vr_landmark)
    
    
    if __name__ == "__main__":
        register()