Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Kim <pk15950@gmail.com>2021-10-26 07:45:00 +0300
committerPeter Kim <pk15950@gmail.com>2021-10-26 07:45:16 +0300
commit823910c50d1c4814c4aa1962a31b91e4dc1c183b (patch)
tree58c449c9808dcfb782ae03ea8926eaf515e98d6a
parentc49c16c38d4bce29a1e0518d5fad5d90f42ab5e7 (diff)
VR: Default Actions, Controller Visualization
This updates the VR Scene Inspection add-on with functionality for loading and using default controller actions, including controller poses and haptics, and visualizing VR controllers both in-headset and in the regular 3D viewport. In other words, users can finally view their VR controllers and use them to navigate their scene in VR. Controller actions (enabled by default) are available as an option in the "VR Session" panel. For controller bindings that require OpenXR extensions (Reverb G2, Vive Cosmos, Huawei), there is a new "Action Maps" panel where users can toggle these bindings. Bindings that require extensions are disabled by default since not all OpenXR runtimes may support them, which will lead to an error during action creation at session start. There is also an option in the "Action Maps" panel to use a gamepad (Xbox Controller) instead of motion controllers for VR actions/viewport navigation. In addition to default actions, this update adds new options for VR controller visualization. For in-headset (VR) visualization, controller visibility as well as style (dark/light, ray/no ray) can be set via the "View" panel. For visualization in the regular 3D viewport, there is a new option in the "Viewport Feedback" panel to draw controllers as gizmos, similar to the existing option for the VR camera (headset). Finally, this update also changes the VR Landmark "Custom Camera" type to "Custom Object", so users can specify any object (not just cameras) as a base pose reference, and adds a base scale option for custom object and custom pose-type landmarks. Reviewed By: Severin Differential Revision: https://developer.blender.org/D11271
-rw-r--r--viewport_vr_preview.py840
-rw-r--r--viewport_vr_preview/__init__.py68
-rw-r--r--viewport_vr_preview/action_map.py172
-rw-r--r--viewport_vr_preview/action_map_io.py348
-rw-r--r--viewport_vr_preview/configs/default.py406
-rw-r--r--viewport_vr_preview/defaults.py1518
-rw-r--r--viewport_vr_preview/gui.py277
-rw-r--r--viewport_vr_preview/operators.py521
-rw-r--r--viewport_vr_preview/properties.py244
-rw-r--r--viewport_vr_preview/versioning.py54
10 files changed, 3608 insertions, 840 deletions
diff --git a/viewport_vr_preview.py b/viewport_vr_preview.py
deleted file mode 100644
index 07f34756..00000000
--- a/viewport_vr_preview.py
+++ /dev/null
@@ -1,840 +0,0 @@
-# ##### 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):
- wm = context.window_manager
-
- 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)
-
- if wm.xr_session_state:
- 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):
- cam_selected = False
-
- vl_objects = bpy.context.view_layer.objects
- if vl_objects.active and vl_objects.active.type == 'CAMERA':
- cam_selected = True
- 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()
- lm.type = 'CUSTOM'
- 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
- lm.base_pose_angle = angle
-
- 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):
- import math
-
- 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)
-
- return {'FINISHED'}
-
-
-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):
- import math
-
- 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
- )
-
- def setup(self, context):
- pass
-
- def draw_prepare(self, context):
- # first delete the old gizmos
- 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
-
- for lm in 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,
- VIEW3D_GGT_vr_landmarks,
-)
-
-
-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(
- name="Show Landmarks"
- )
-
- 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()
diff --git a/viewport_vr_preview/__init__.py b/viewport_vr_preview/__init__.py
new file mode 100644
index 00000000..352470ca
--- /dev/null
+++ b/viewport_vr_preview/__init__.py
@@ -0,0 +1,68 @@
+# ##### 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>
+
+bl_info = {
+ "name": "VR Scene Inspection",
+ "author": "Julian Eisel (Severin), Sebastian Koenig, Peter Kim (muxed-reality)",
+ "version": (0, 10, 0),
+ "blender": (3, 0, 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",
+}
+
+
+if "bpy" in locals():
+ import importlib
+ importlib.reload(action_map)
+ importlib.reload(gui)
+ importlib.reload(operators)
+ importlib.reload(properties)
+else:
+ from . import action_map, gui, operators, properties
+
+import bpy
+
+
+def register():
+ if not bpy.app.build_options.xr_openxr:
+ bpy.utils.register_class(gui.VIEW3D_PT_vr_info)
+ return
+
+ action_map.register()
+ gui.register()
+ operators.register()
+ properties.register()
+
+
+def unregister():
+ if not bpy.app.build_options.xr_openxr:
+ bpy.utils.unregister_class(gui.VIEW3D_PT_vr_info)
+ return
+
+ action_map.unregister()
+ gui.unregister()
+ operators.unregister()
+ properties.unregister()
diff --git a/viewport_vr_preview/action_map.py b/viewport_vr_preview/action_map.py
new file mode 100644
index 00000000..49c3f7dd
--- /dev/null
+++ b/viewport_vr_preview/action_map.py
@@ -0,0 +1,172 @@
+# ##### 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>
+
+if "bpy" in locals():
+ import importlib
+ importlib.reload(defaults)
+else:
+ from . import action_map_io, defaults
+
+import bpy
+from bpy.app.handlers import persistent
+from bpy_extras.io_utils import ExportHelper, ImportHelper
+import importlib.util
+import os.path
+
+
+def vr_actionset_active_update(context):
+ session_state = context.window_manager.xr_session_state
+ if not session_state or len(session_state.actionmaps) < 1:
+ return
+
+ scene = context.scene
+
+ if scene.vr_actions_use_gamepad and session_state.actionmaps.find(session_state, defaults.VRDefaultActionmaps.GAMEPAD.value):
+ session_state.active_action_set_set(context, defaults.VRDefaultActionmaps.GAMEPAD.value)
+ else:
+ # Use first action map.
+ session_state.active_action_set_set(context, session_state.actionmaps[0].name)
+
+
+def vr_actions_use_gamepad_update(self, context):
+ vr_actionset_active_update(context)
+
+
+@persistent
+def vr_create_actions(context: bpy.context):
+ context = bpy.context
+ session_state = context.window_manager.xr_session_state
+ if not session_state:
+ return
+
+ # Check if actions are enabled.
+ scene = context.scene
+ if not scene.vr_actions_enable:
+ return
+
+ # Ensure default action maps.
+ if not defaults.vr_ensure_default_actionmaps(session_state):
+ return
+
+ for am in session_state.actionmaps:
+ if len(am.actionmap_items) < 1:
+ continue
+
+ ok = session_state.action_set_create(context, am)
+ if not ok:
+ return
+
+ controller_grip_name = ""
+ controller_aim_name = ""
+
+ for ami in am.actionmap_items:
+ if len(ami.bindings) < 1:
+ continue
+
+ ok = session_state.action_create(context, am, ami)
+ if not ok:
+ return
+
+ if ami.type == 'POSE':
+ if ami.pose_is_controller_grip:
+ controller_grip_name = ami.name
+ if ami.pose_is_controller_aim:
+ controller_aim_name = ami.name
+
+ for amb in ami.bindings:
+ # Check for bindings that require OpenXR extensions.
+ if amb.name == defaults.VRDefaultActionbindings.REVERB_G2.value:
+ if not scene.vr_actions_enable_reverb_g2:
+ continue
+ elif amb.name == defaults.VRDefaultActionbindings.COSMOS.value:
+ if not scene.vr_actions_enable_cosmos:
+ continue
+ elif amb.name == defaults.VRDefaultActionbindings.HUAWEI.value:
+ if not scene.vr_actions_enable_huawei:
+ continue
+
+ ok = session_state.action_binding_create(context, am, ami, amb)
+ if not ok:
+ return
+
+ # Set controller pose actions.
+ if controller_grip_name and controller_aim_name:
+ session_state.controller_pose_actions_set(context, am.name, controller_grip_name, controller_aim_name)
+
+ # Set active action set.
+ vr_actionset_active_update(context)
+
+
+def vr_load_actionmaps(session_state, filepath):
+ if not os.path.exists(filepath):
+ return False
+
+ spec = importlib.util.spec_from_file_location(os.path.basename(filepath), filepath)
+ file = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(file)
+
+ action_map_io.actionconfig_init_from_data(session_state, file.actionconfig_data, file.actionconfig_version)
+
+ return True
+
+
+def vr_save_actionmaps(session_state, filepath, sort=False):
+ action_map_io.actionconfig_export_as_data(session_state, filepath, sort=sort)
+
+ print("Saved XR actionmaps: " + filepath)
+
+ return True
+
+
+def register():
+ bpy.types.Scene.vr_actions_enable = bpy.props.BoolProperty(
+ name="Use Controller Actions",
+ description="Enable default VR controller actions, including controller poses and haptics",
+ default=True,
+ )
+ bpy.types.Scene.vr_actions_use_gamepad = bpy.props.BoolProperty(
+ description="Use input from gamepad (Microsoft Xbox Controller) instead of motion controllers",
+ default=False,
+ update=vr_actions_use_gamepad_update,
+ )
+ bpy.types.Scene.vr_actions_enable_cosmos = bpy.props.BoolProperty(
+ description="Enable bindings for the HTC Vive Cosmos controllers. Note that this may not be supported by all OpenXR runtimes",
+ default=False,
+ )
+ bpy.types.Scene.vr_actions_enable_huawei = bpy.props.BoolProperty(
+ description="Enable bindings for the Huawei controllers. Note that this may not be supported by all OpenXR runtimes",
+ default=False,
+ )
+ bpy.types.Scene.vr_actions_enable_reverb_g2 = bpy.props.BoolProperty(
+ description="Enable bindings for the HP Reverb G2 controllers. Note that this may not be supported by all OpenXR runtimes",
+ default=False,
+ )
+
+ bpy.app.handlers.xr_session_start_pre.append(vr_create_actions)
+
+
+def unregister():
+ del bpy.types.Scene.vr_actions_enable
+ del bpy.types.Scene.vr_actions_use_gamepad
+ del bpy.types.Scene.vr_actions_enable_cosmos
+ del bpy.types.Scene.vr_actions_enable_huawei
+ del bpy.types.Scene.vr_actions_enable_reverb_g2
+
+ bpy.app.handlers.xr_session_start_pre.remove(vr_create_actions)
diff --git a/viewport_vr_preview/action_map_io.py b/viewport_vr_preview/action_map_io.py
new file mode 100644
index 00000000..c57543a0
--- /dev/null
+++ b/viewport_vr_preview/action_map_io.py
@@ -0,0 +1,348 @@
+# ##### 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>
+
+# -----------------------------------------------------------------------------
+# Export Functions
+
+__all__ = (
+ "actionconfig_export_as_data",
+ "actionconfig_import_from_data",
+ "actionconfig_init_from_data",
+ "actionmap_init_from_data",
+ "actionmap_item_init_from_data",
+)
+
+
+def indent(levels):
+ return levels * " "
+
+
+def round_float_32(f):
+ from struct import pack, unpack
+ return unpack("f", pack("f", f))[0]
+
+
+def repr_f32(f):
+ f_round = round_float_32(f)
+ f_str = repr(f)
+ f_str_frac = f_str.partition(".")[2]
+ if not f_str_frac:
+ return f_str
+ for i in range(1, len(f_str_frac)):
+ f_test = round(f, i)
+ f_test_round = round_float_32(f_test)
+ if f_test_round == f_round:
+ return "%.*f" % (i, f_test)
+ return f_str
+
+def ami_args_as_data(ami):
+ s = [
+ f"\"type\": '{ami.type}'",
+ f"\"user_path0\": '{ami.user_path0}'",
+ f"\"user_path1\": '{ami.user_path1}'",
+ ]
+
+ if ami.type == 'FLOAT' or ami.type == 'VECTOR2D':
+ s.append(f"\"op\": '{ami.op}'")
+ s.append(f"\"op_mode\": '{ami.op_mode}'")
+ s.append(f"\"bimanual\": '{ami.bimanual}'")
+ s.append(f"\"haptic_name\": '{ami.haptic_name}'")
+ s.append(f"\"haptic_match_user_paths\": '{ami.haptic_match_user_paths}'")
+ s.append(f"\"haptic_duration\": '{ami.haptic_duration}'")
+ s.append(f"\"haptic_frequency\": '{ami.haptic_frequency}'")
+ s.append(f"\"haptic_amplitude\": '{ami.haptic_amplitude}'")
+ s.append(f"\"haptic_mode\": '{ami.haptic_mode}'")
+ elif ami.type == 'POSE':
+ s.append(f"\"pose_is_controller_grip\": '{ami.pose_is_controller_grip}'")
+ s.append(f"\"pose_is_controller_aim\": '{ami.pose_is_controller_aim}'")
+
+
+ return "{" + ", ".join(s) + "}"
+
+
+def ami_data_from_args(ami, args):
+ ami.type = args["type"]
+ ami.user_path0 = args["user_path0"]
+ ami.user_path1 = args["user_path1"]
+
+ if ami.type == 'FLOAT' or ami.type == 'VECTOR2D':
+ ami.op = args["op"]
+ ami.op_mode = args["op_mode"]
+ ami.bimanual = True if (args["bimanual"] == 'True') else False
+ ami.haptic_name = args["haptic_name"]
+ ami.haptic_match_user_paths = True if (args["haptic_match_user_paths"] == 'True') else False
+ ami.haptic_duration = float(args["haptic_duration"])
+ ami.haptic_frequency = float(args["haptic_frequency"])
+ ami.haptic_amplitude = float(args["haptic_amplitude"])
+ ami.haptic_mode = args["haptic_mode"]
+ elif ami.type == 'POSE':
+ ami.pose_is_controller_grip = True if (args["pose_is_controller_grip"] == 'True') else False
+ ami.pose_is_controller_aim = True if (args["pose_is_controller_aim"] == 'True') else False
+
+
+def _ami_properties_to_lines_recursive(level, properties, lines):
+ from bpy.types import OperatorProperties
+
+ def string_value(value):
+ if isinstance(value, (str, bool, int, set)):
+ return repr(value)
+ elif isinstance(value, float):
+ return repr_f32(value)
+ elif getattr(value, '__len__', False):
+ return repr(tuple(value))
+ raise Exception(f"Export action configuration: can't write {value!r}")
+
+ for pname in properties.bl_rna.properties.keys():
+ if pname != "rna_type":
+ value = getattr(properties, pname)
+ if isinstance(value, OperatorProperties):
+ lines_test = []
+ _ami_properties_to_lines_recursive(level + 2, value, lines_test)
+ if lines_test:
+ lines.append(f"(")
+ lines.append(f"\"{pname}\",\n")
+ lines.append(f"{indent(level + 3)}" "[")
+ lines.extend(lines_test)
+ lines.append("],\n")
+ lines.append(f"{indent(level + 3)}" "),\n" f"{indent(level + 2)}")
+ del lines_test
+ elif properties.is_property_set(pname):
+ value = string_value(value)
+ lines.append((f"(\"{pname}\", {value:s}),\n" f"{indent(level + 2)}"))
+
+
+def _ami_properties_to_lines(level, ami_props, lines):
+ if ami_props is None:
+ return
+
+ lines_test = [f"\"op_properties\":\n" f"{indent(level + 1)}" "["]
+ _ami_properties_to_lines_recursive(level, ami_props, lines_test)
+ if len(lines_test) > 1:
+ lines_test.append("],\n")
+ lines.extend(lines_test)
+
+
+def _ami_attrs_or_none(level, ami):
+ lines = []
+ _ami_properties_to_lines(level + 1, ami.op_properties, lines)
+ if not lines:
+ return None
+ return "".join(lines)
+
+
+def amb_args_as_data(amb, type):
+ s = [
+ f"\"profile\": '{amb.profile}'",
+ f"\"component_path0\": '{amb.component_path0}'",
+ f"\"component_path1\": '{amb.component_path1}'",
+ ]
+
+ if type == 'FLOAT' or type == 'VECTOR2D':
+ s.append(f"\"threshold\": '{amb.threshold}'")
+ if type == 'FLOAT':
+ s.append(f"\"axis_region\": '{amb.axis0_region}'")
+ else: # type == 'VECTOR2D':
+ s.append(f"\"axis0_region\": '{amb.axis0_region}'")
+ s.append(f"\"axis1_region\": '{amb.axis1_region}'")
+ elif type == 'POSE':
+ s.append(f"\"pose_location\": '{amb.pose_location.x, amb.pose_location.y, amb.pose_location.z}'")
+ s.append(f"\"pose_rotation\": '{amb.pose_rotation.x, amb.pose_rotation.y, amb.pose_rotation.z}'")
+
+ return "{" + ", ".join(s) + "}"
+
+
+def amb_data_from_args(amb, args, type):
+ amb.profile = args["profile"]
+ amb.component_path0 = args["component_path0"]
+ amb.component_path1 = args["component_path1"]
+
+ if type == 'FLOAT' or type == 'VECTOR2D':
+ amb.threshold = float(args["threshold"])
+ if type == 'FLOAT':
+ amb.axis0_region = args["axis_region"]
+ else: # type == 'VECTOR2D':
+ amb.axis0_region = args["axis0_region"]
+ amb.axis1_region = args["axis1_region"]
+ elif type == 'POSE':
+ l = args["pose_location"].strip(')(').split(', ')
+ amb.pose_location.x = float(l[0])
+ amb.pose_location.y = float(l[1])
+ amb.pose_location.z = float(l[2])
+ l = args["pose_rotation"].strip(')(').split(', ')
+ amb.pose_rotation.x = float(l[0])
+ amb.pose_rotation.y = float(l[1])
+ amb.pose_rotation.z = float(l[2])
+
+
+def actionconfig_export_as_data(session_state, filepath, *, sort=False):
+ export_actionmaps = []
+
+ for am in session_state.actionmaps:
+ export_actionmaps.append(am)
+
+ if sort:
+ export_actionmaps.sort(key=lambda k: k.name)
+
+ with open(filepath, "w", encoding="utf-8") as fh:
+ fw = fh.write
+
+ # Use the file version since it includes the sub-version
+ # which we can bump multiple times between releases.
+ from bpy.app import version_file
+ fw(f"actionconfig_version = {version_file!r}\n")
+ del version_file
+
+ fw("actionconfig_data = \\\n[")
+
+ for am in export_actionmaps:
+ fw("(")
+ fw(f"\"{am.name:s}\",\n")
+
+ fw(f"{indent(2)}" "{")
+ fw(f"\"items\":\n")
+ fw(f"{indent(3)}[")
+ for ami in am.actionmap_items:
+ fw(f"(")
+ fw(f"\"{ami.name:s}\"")
+ ami_args = ami_args_as_data(ami)
+ ami_data = _ami_attrs_or_none(4, ami)
+ if ami_data is None:
+ fw(f", ")
+ else:
+ fw(",\n" f"{indent(5)}")
+
+ fw(ami_args)
+ if ami_data is None:
+ fw(", None,\n")
+ else:
+ fw(",\n")
+ fw(f"{indent(5)}" "{")
+ fw(ami_data)
+ fw(f"{indent(6)}")
+ fw("}," f"{indent(5)}")
+ fw("\n")
+
+ fw(f"{indent(5)}" "{")
+ fw(f"\"bindings\":\n")
+ fw(f"{indent(6)}[")
+ for amb in ami.bindings:
+ fw(f"(")
+ fw(f"\"{amb.name:s}\"")
+ fw(f", ")
+ amb_args = amb_args_as_data(amb, ami.type)
+ fw(amb_args)
+ fw("),\n" f"{indent(7)}")
+ fw("],\n" f"{indent(6)}")
+ fw("},\n" f"{indent(5)}")
+ fw("),\n" f"{indent(4)}")
+
+ fw("],\n" f"{indent(3)}")
+ fw("},\n" f"{indent(2)}")
+ fw("),\n" f"{indent(1)}")
+
+ fw("]\n")
+ fw("\n\n")
+ fw("if __name__ == \"__main__\":\n")
+
+ # We could remove this in the future, as loading new action-maps in older Blender versions
+ # makes less and less sense as Blender changes.
+ fw(" # Only add keywords that are supported.\n")
+ fw(" from bpy.app import version as blender_version\n")
+ fw(" keywords = {}\n")
+ fw(" if blender_version >= (3, 0, 0):\n")
+ fw(" keywords[\"actionconfig_version\"] = actionconfig_version\n")
+
+ fw(" import os\n")
+ fw(" from viewport_vr_preview.io import actionconfig_import_from_data\n")
+ fw(" actionconfig_import_from_data(\n")
+ fw(" os.path.splitext(os.path.basename(__file__))[0],\n")
+ fw(" actionconfig_data,\n")
+ fw(" **keywords,\n")
+ fw(" )\n")
+
+
+# -----------------------------------------------------------------------------
+# Import Functions
+
+def _ami_props_setattr(ami_name, ami_props, attr, value):
+ if type(value) is list:
+ ami_subprop = getattr(ami_props, attr)
+ for subattr, subvalue in value:
+ _ami_props_setattr(ami_subprop, subattr, subvalue)
+ return
+
+ try:
+ setattr(ami_props, attr, value)
+ except AttributeError:
+ print(f"Warning: property '{attr}' not found in action map item '{ami_name}'")
+ except Exception as ex:
+ print(f"Warning: {ex!r}")
+
+
+def actionmap_item_init_from_data(ami, ami_bindings):
+ new_fn = getattr(ami.bindings, "new")
+ for (amb_name, amb_args) in ami_bindings:
+ amb = new_fn(amb_name, True)
+ amb_data_from_args(amb, amb_args, ami.type)
+
+
+def actionmap_init_from_data(am, am_items):
+ new_fn = getattr(am.actionmap_items, "new")
+ for (ami_name, ami_args, ami_data, ami_content) in am_items:
+ ami = new_fn(ami_name, True)
+ ami_data_from_args(ami, ami_args)
+ if ami_data is not None:
+ ami_props_data = ami_data.get("op_properties", None)
+ if ami_props_data is not None:
+ ami_props = ami.op_properties
+ assert type(ami_props_data) is list
+ for attr, value in ami_props_data:
+ _ami_props_setattr(ami_name, ami_props, attr, value)
+ ami_bindings = ami_content["bindings"]
+ assert type(ami_bindings) is list
+ actionmap_item_init_from_data(ami, ami_bindings)
+
+
+def actionconfig_init_from_data(session_state, actionconfig_data, actionconfig_version):
+ # Load data in the format defined above.
+ #
+ # Runs at load time, keep this fast!
+ if actionconfig_version is not None:
+ from .versioning import actionconfig_update
+ actionconfig_data = actionconfig_update(actionconfig_data, actionconfig_version)
+
+ for (am_name, am_content) in actionconfig_data:
+ am = session_state.actionmaps.new(session_state, am_name, True)
+ am_items = am_content["items"]
+ # Check here instead of inside 'actionmap_init_from_data'
+ # because we want to allow both tuple & list types in that case.
+ #
+ # For full action maps, ensure these are always lists to allow for extending them
+ # in a generic way that doesn't have to check for the type each time.
+ assert type(am_items) is list
+ actionmap_init_from_data(am, am_items)
+
+
+def actionconfig_import_from_data(session_state, actionconfig_data, *, actionconfig_version=(0, 0, 0)):
+ # Load data in the format defined above.
+ #
+ # Runs at load time, keep this fast!
+ import bpy
+ actionconfig_init_from_data(session_state, actionconfig_data, actionconfig_version)
diff --git a/viewport_vr_preview/configs/default.py b/viewport_vr_preview/configs/default.py
new file mode 100644
index 00000000..efb130c1
--- /dev/null
+++ b/viewport_vr_preview/configs/default.py
@@ -0,0 +1,406 @@
+actionconfig_version = (3, 0, 39)
+actionconfig_data = \
+[("blender_default",
+ {"items":
+ [("controller_grip", {"type": 'POSE', "user_path0": '/user/hand/left', "user_path1": '/user/hand/right', "pose_is_controller_grip": 'True', "pose_is_controller_aim": 'False'}, None,
+ {"bindings":
+ [("cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_path0": '/input/grip/pose', "component_path1": '/input/grip/pose', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}),
+ ("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_path0": '/input/grip/pose', "component_path1": '/input/grip/pose', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}),
+ ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_path0": '/input/grip/pose', "component_path1": '/input/grip/pose', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}),
+ ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_path0": '/input/grip/pose', "component_path1": '/input/grip/pose', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}),
+ ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_path0": '/input/grip/pose', "component_path1": '/input/grip/pose', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}),
+ ("simple", {"profile": '/interaction_profiles/khr/simple_controller', "component_path0": '/input/grip/pose', "component_path1": '/input/grip/pose', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}),
+ ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_path0": '/input/grip/pose', "component_path1": '/input/grip/pose', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}),
+ ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_path0": '/input/grip/pose', "component_path1": '/input/grip/pose', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}),
+ ],
+ },
+ ),
+ ("controller_aim", {"type": 'POSE', "user_path0": '/user/hand/left', "user_path1": '/user/hand/right', "pose_is_controller_grip": 'False', "pose_is_controller_aim": 'True'}, None,
+ {"bindings":
+ [("cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_path0": '/input/aim/pose', "component_path1": '/input/aim/pose', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}),
+ ("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_path0": '/input/aim/pose', "component_path1": '/input/aim/pose', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}),
+ ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_path0": '/input/aim/pose', "component_path1": '/input/aim/pose', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}),
+ ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_path0": '/input/aim/pose', "component_path1": '/input/aim/pose', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}),
+ ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_path0": '/input/aim/pose', "component_path1": '/input/aim/pose', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}),
+ ("simple", {"profile": '/interaction_profiles/khr/simple_controller', "component_path0": '/input/aim/pose', "component_path1": '/input/aim/pose', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}),
+ ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_path0": '/input/aim/pose', "component_path1": '/input/aim/pose', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}),
+ ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_path0": '/input/aim/pose', "component_path1": '/input/aim/pose', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}),
+ ],
+ },
+ ),
+ ("teleport", {"type": 'FLOAT', "user_path0": '/user/hand/left', "user_path1": '/user/hand/right', "op": 'wm.xr_navigation_teleport', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("interpolation", 0.9),
+ ("color", (0.0, 1.0, 1.0, 1.0)),
+ ],
+ },
+ {"bindings":
+ [("cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_path0": '/input/trigger/value', "component_path1": '/input/trigger/value', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_path0": '/input/trigger/value', "component_path1": '/input/trigger/value', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_path0": '/input/trigger/value', "component_path1": '/input/trigger/value', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_path0": '/input/trigger/value', "component_path1": '/input/trigger/value', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_path0": '/input/trigger/value', "component_path1": '/input/trigger/value', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("simple", {"profile": '/interaction_profiles/khr/simple_controller', "component_path0": '/input/select/click', "component_path1": '/input/select/click', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_path0": '/input/trigger/value', "component_path1": '/input/trigger/value', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_path0": '/input/trigger/value', "component_path1": '/input/trigger/value', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ],
+ },
+ ),
+ ("nav_grab", {"type": 'FLOAT', "user_path0": '/user/hand/left', "user_path1": '/user/hand/right', "op": 'wm.xr_navigation_grab', "op_mode": 'MODAL', "bimanual": 'True', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("lock_rotation", True),
+ ],
+ },
+ {"bindings":
+ [("cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_path0": '/input/squeeze/click', "component_path1": '/input/squeeze/click', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_path0": '/input/trackpad/click', "component_path1": '/input/trackpad/click', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_path0": '/input/squeeze/value', "component_path1": '/input/squeeze/value', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_path0": '/input/squeeze/value', "component_path1": '/input/squeeze/value', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_path0": '/input/squeeze/value', "component_path1": '/input/squeeze/value', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("simple", {"profile": '/interaction_profiles/khr/simple_controller', "component_path0": '/input/menu/click', "component_path1": '/input/menu/click', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_path0": '/input/squeeze/click', "component_path1": '/input/squeeze/click', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_path0": '/input/squeeze/click', "component_path1": '/input/squeeze/click', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ],
+ },
+ ),
+ ("fly_forward", {"type": 'FLOAT', "user_path0": '/user/hand/left', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("mode", 'VIEWER_FORWARD'),
+ ("lock_location_z", True),
+ ],
+ },
+ {"bindings":
+ [("cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_path0": '/input/trackpad/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_path0": '/input/trackpad/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ],
+ },
+ ),
+ ("fly_back", {"type": 'FLOAT', "user_path0": '/user/hand/left', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("mode", 'VIEWER_BACK'),
+ ("lock_location_z", True),
+ ],
+ },
+ {"bindings":
+ [("cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_path0": '/input/trackpad/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_path0": '/input/trackpad/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ],
+ },
+ ),
+ ("fly_left", {"type": 'FLOAT', "user_path0": '/user/hand/left', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("mode", 'VIEWER_LEFT'),
+ ("lock_location_z", True),
+ ],
+ },
+ {"bindings":
+ [("cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_path0": '/input/trackpad/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_path0": '/input/trackpad/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ],
+ },
+ ),
+ ("fly_right", {"type": 'FLOAT', "user_path0": '/user/hand/left', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("mode", 'VIEWER_RIGHT'),
+ ("lock_location_z", True),
+ ],
+ },
+ {"bindings":
+ [("cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_path0": '/input/trackpad/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_path0": '/input/trackpad/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ],
+ },
+ ),
+ ("fly_up", {"type": 'FLOAT', "user_path0": '/user/hand/right', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("mode", 'UP'),
+ ("speed_min", 0.014),
+ ("speed_max", 0.042),
+ ],
+ },
+ {"bindings":
+ [("cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_path0": '/input/trackpad/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_path0": '/input/trackpad/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ],
+ },
+ ),
+ ("fly_down", {"type": 'FLOAT', "user_path0": '/user/hand/right', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("mode", 'DOWN'),
+ ("speed_min", 0.014),
+ ("speed_max", 0.042),
+ ],
+ },
+ {"bindings":
+ [("cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_path0": '/input/trackpad/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_path0": '/input/trackpad/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_path0": '/input/thumbstick/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ],
+ },
+ ),
+ ("fly_turnleft", {"type": 'FLOAT', "user_path0": '/user/hand/right', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("mode", 'TURNLEFT'),
+ ("speed_min", 0.01),
+ ("speed_max", 0.03),
+ ],
+ },
+ {"bindings":
+ [("cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_path0": '/input/trackpad/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_path0": '/input/trackpad/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ],
+ },
+ ),
+ ("fly_turnright", {"type": 'FLOAT', "user_path0": '/user/hand/right', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("mode", 'TURNRIGHT'),
+ ("speed_min", 0.01),
+ ("speed_max", 0.03),
+ ],
+ },
+ {"bindings":
+ [("cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_path0": '/input/trackpad/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_path0": '/input/trackpad/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_path0": '/input/thumbstick/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ],
+ },
+ ),
+ ("nav_reset", {"type": 'FLOAT', "user_path0": '/user/hand/left', "user_path1": '/user/hand/right', "op": 'wm.xr_navigation_reset', "op_mode": 'PRESS', "bimanual": 'False', "haptic_name": 'haptic', "haptic_match_user_paths": 'True', "haptic_duration": '0.30000001192092896', "haptic_frequency": '3000.0', "haptic_amplitude": '0.5', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("location", False),
+ ("rotation", False),
+ ("scale", True),
+ ],
+ },
+ {"bindings":
+ [("cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_path0": '/input/x/click', "component_path1": '/input/a/click', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_path0": '/input/back/click', "component_path1": '/input/back/click', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_path0": '/input/a/click', "component_path1": '/input/a/click', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_path0": '/input/x/click', "component_path1": '/input/a/click', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_path0": '/input/x/click', "component_path1": '/input/a/click', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_path0": '/input/menu/click', "component_path1": '/input/menu/click', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_path0": '/input/menu/click', "component_path1": '/input/menu/click', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ],
+ },
+ ),
+ ("haptic", {"type": 'VIBRATION', "user_path0": '/user/hand/left', "user_path1": '/user/hand/right'}, None,
+ {"bindings":
+ [("cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_path0": '/output/haptic', "component_path1": '/output/haptic'}),
+ ("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_path0": '/output/haptic', "component_path1": '/output/haptic'}),
+ ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_path0": '/output/haptic', "component_path1": '/output/haptic'}),
+ ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_path0": '/output/haptic', "component_path1": '/output/haptic'}),
+ ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_path0": '/output/haptic', "component_path1": '/output/haptic'}),
+ ("simple", {"profile": '/interaction_profiles/khr/simple_controller', "component_path0": '/output/haptic', "component_path1": '/output/haptic'}),
+ ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_path0": '/output/haptic', "component_path1": '/output/haptic'}),
+ ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_path0": '/output/haptic', "component_path1": '/output/haptic'}),
+ ],
+ },
+ ),
+ ],
+ },
+ ),
+ ("blender_default_gamepad",
+ {"items":
+ [("teleport", {"type": 'FLOAT', "user_path0": '/user/gamepad', "user_path1": '', "op": 'wm.xr_navigation_teleport', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("interpolation", 0.9),
+ ("from_viewer", True),
+ ("color", (0.0, 1.0, 1.0, 1.0)),
+ ],
+ },
+ {"bindings":
+ [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_path0": '/input/trigger_right/value', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ],
+ },
+ ),
+ ("fly", {"type": 'FLOAT', "user_path0": '/user/gamepad', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'}, None,
+ {"bindings":
+ [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_path0": '/input/trigger_left/value', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ],
+ },
+ ),
+ ("fly_forward", {"type": 'FLOAT', "user_path0": '/user/gamepad', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("mode", 'VIEWER_FORWARD'),
+ ("lock_location_z", True),
+ ],
+ },
+ {"bindings":
+ [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_path0": '/input/thumbstick_left/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ],
+ },
+ ),
+ ("fly_back", {"type": 'FLOAT', "user_path0": '/user/gamepad', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("mode", 'VIEWER_BACK'),
+ ("lock_location_z", True),
+ ],
+ },
+ {"bindings":
+ [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_path0": '/input/thumbstick_left/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ],
+ },
+ ),
+ ("fly_left", {"type": 'FLOAT', "user_path0": '/user/gamepad', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("mode", 'VIEWER_LEFT'),
+ ("lock_location_z", True),
+ ],
+ },
+ {"bindings":
+ [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_path0": '/input/thumbstick_left/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ],
+ },
+ ),
+ ("fly_right", {"type": 'FLOAT', "user_path0": '/user/gamepad', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("mode", 'VIEWER_RIGHT'),
+ ("lock_location_z", True),
+ ],
+ },
+ {"bindings":
+ [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_path0": '/input/thumbstick_left/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ],
+ },
+ ),
+ ("fly_up", {"type": 'FLOAT', "user_path0": '/user/gamepad', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("mode", 'UP'),
+ ("speed_min", 0.014),
+ ("speed_max", 0.042),
+ ],
+ },
+ {"bindings":
+ [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_path0": '/input/thumbstick_right/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ],
+ },
+ ),
+ ("fly_down", {"type": 'FLOAT', "user_path0": '/user/gamepad', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("mode", 'DOWN'),
+ ("speed_min", 0.014),
+ ("speed_max", 0.042),
+ ],
+ },
+ {"bindings":
+ [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_path0": '/input/thumbstick_right/y', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ],
+ },
+ ),
+ ("fly_turnleft", {"type": 'FLOAT', "user_path0": '/user/gamepad', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("mode", 'TURNLEFT'),
+ ("speed_min", 0.01),
+ ("speed_max", 0.03),
+ ],
+ },
+ {"bindings":
+ [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_path0": '/input/thumbstick_right/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
+ ],
+ },
+ ),
+ ("fly_turnright", {"type": 'FLOAT', "user_path0": '/user/gamepad', "user_path1": '', "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("mode", 'TURNRIGHT'),
+ ("speed_min", 0.01),
+ ("speed_max", 0.03),
+ ],
+ },
+ {"bindings":
+ [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_path0": '/input/thumbstick_right/x', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
+ ],
+ },
+ ),
+ ("nav_reset", {"type": 'FLOAT', "user_path0": '/user/gamepad', "user_path1": '', "op": 'wm.xr_navigation_reset', "op_mode": 'PRESS', "bimanual": 'False', "haptic_name": 'haptic_right', "haptic_match_user_paths": 'True', "haptic_duration": '0.30000001192092896', "haptic_frequency": '3000.0', "haptic_amplitude": '0.5', "haptic_mode": 'PRESS'},
+ {"op_properties":
+ [("location", False),
+ ("rotation", False),
+ ("scale", True),
+ ],
+ },
+ {"bindings":
+ [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_path0": '/input/a/click', "component_path1": '', "threshold": '0.30000001192092896', "axis_region": 'ANY'}),
+ ],
+ },
+ ),
+ ("haptic_left", {"type": 'VIBRATION', "user_path0": '/user/gamepad', "user_path1": ''}, None,
+ {"bindings":
+ [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_path0": '/output/haptic_left', "component_path1": ''}),
+ ],
+ },
+ ),
+ ("haptic_right", {"type": 'VIBRATION', "user_path0": '/user/gamepad', "user_path1": ''}, None,
+ {"bindings":
+ [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_path0": '/output/haptic_right', "component_path1": ''}),
+ ],
+ },
+ ),
+ ("haptic_lefttrigger", {"type": 'VIBRATION', "user_path0": '/user/gamepad', "user_path1": ''}, None,
+ {"bindings":
+ [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_path0": '/output/haptic_left_trigger', "component_path1": ''}),
+ ],
+ },
+ ),
+ ("haptic_righttrigger", {"type": 'VIBRATION', "user_path0": '/user/gamepad', "user_path1": ''}, None,
+ {"bindings":
+ [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_path0": '/output/haptic_right_trigger', "component_path1": ''}),
+ ],
+ },
+ ),
+ ],
+ },
+ ),
+ ]
+
+
+if __name__ == "__main__":
+ # Only add keywords that are supported.
+ from bpy.app import version as blender_version
+ keywords = {}
+ if blender_version >= (3, 0, 0):
+ keywords["actionconfig_version"] = actionconfig_version
+ import os
+ from viewport_vr_preview.io import actionconfig_import_from_data
+ actionconfig_import_from_data(
+ os.path.splitext(os.path.basename(__file__))[0],
+ actionconfig_data,
+ **keywords,
+ )
diff --git a/viewport_vr_preview/defaults.py b/viewport_vr_preview/defaults.py
new file mode 100644
index 00000000..205e39d2
--- /dev/null
+++ b/viewport_vr_preview/defaults.py
@@ -0,0 +1,1518 @@
+# ##### 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>
+
+if "bpy" in locals():
+ import importlib
+ importlib.reload(action_map)
+else:
+ from . import action_map
+
+import bpy
+from bpy.app.handlers import persistent
+from enum import Enum
+import math
+import os.path
+
+
+# Default action maps.
+class VRDefaultActionmaps(Enum):
+ DEFAULT = "blender_default"
+ GAMEPAD = "blender_default_gamepad"
+
+
+# Default actions.
+class VRDefaultActions(Enum):
+ CONTROLLER_GRIP = "controller_grip"
+ CONTROLLER_AIM = "controller_aim"
+ TELEPORT = "teleport"
+ NAV_GRAB = "nav_grab"
+ FLY = "fly"
+ FLY_FORWARD = "fly_forward"
+ FLY_BACK = "fly_back"
+ FLY_LEFT = "fly_left"
+ FLY_RIGHT = "fly_right"
+ FLY_UP = "fly_up"
+ FLY_DOWN = "fly_down"
+ FLY_TURNLEFT = "fly_turnleft"
+ FLY_TURNRIGHT = "fly_turnright"
+ NAV_RESET = "nav_reset"
+ HAPTIC = "haptic"
+ HAPTIC_LEFT = "haptic_left"
+ HAPTIC_RIGHT = "haptic_right"
+ HAPTIC_LEFTTRIGGER = "haptic_lefttrigger"
+ HAPTIC_RIGHTTRIGGER = "haptic_righttrigger"
+
+
+# Default action bindings.
+class VRDefaultActionbindings(Enum):
+ COSMOS = "cosmos"
+ GAMEPAD = "gamepad"
+ HUAWEI = "huawei"
+ INDEX = "index"
+ OCULUS = "oculus"
+ REVERB_G2 = "reverb_g2"
+ SIMPLE = "simple"
+ VIVE = "vive"
+ WMR = "wmr"
+
+
+class VRDefaultActionprofiles(Enum):
+ COSMOS = "/interaction_profiles/htc/vive_cosmos_controller"
+ GAMEPAD = "/interaction_profiles/microsoft/xbox_controller"
+ HUAWEI = "/interaction_profiles/huawei/controller"
+ INDEX = "/interaction_profiles/valve/index_controller"
+ OCULUS = "/interaction_profiles/oculus/touch_controller"
+ REVERB_G2 = "/interaction_profiles/hp/mixed_reality_controller"
+ SIMPLE = "/interaction_profiles/khr/simple_controller"
+ VIVE = "/interaction_profiles/htc/vive_controller"
+ WMR = "/interaction_profiles/microsoft/motion_controller"
+
+
+def vr_defaults_actionmap_add(session_state, name):
+ am = session_state.actionmaps.new(session_state, name, True)
+
+ return am
+
+
+def vr_defaults_action_add(am,
+ name,
+ user_path0,
+ user_path1,
+ op,
+ op_mode,
+ bimanual,
+ haptic_name,
+ haptic_match_user_paths,
+ haptic_duration,
+ haptic_frequency,
+ haptic_amplitude,
+ haptic_mode):
+
+
+ ami = am.actionmap_items.new(name, True)
+ if ami:
+ ami.type = 'FLOAT'
+ ami.user_path0 = user_path0
+ ami.user_path1 = user_path1
+ ami.op = op
+ ami.op_mode = op_mode
+ ami.bimanual = bimanual
+ ami.haptic_name = haptic_name
+ ami.haptic_match_user_paths = haptic_match_user_paths
+ ami.haptic_duration = haptic_duration
+ ami.haptic_frequency = haptic_frequency
+ ami.haptic_amplitude = haptic_amplitude
+ ami.haptic_mode = haptic_mode
+
+ return ami
+
+
+def vr_defaults_pose_action_add(am,
+ name,
+ user_path0,
+ user_path1,
+ is_controller_grip,
+ is_controller_aim):
+ ami = am.actionmap_items.new(name, True)
+ if ami:
+ ami.type = 'POSE'
+ ami.user_path0 = user_path0
+ ami.user_path1 = user_path1
+ ami.pose_is_controller_grip = is_controller_grip
+ ami.pose_is_controller_aim = is_controller_aim
+
+ return ami
+
+
+def vr_defaults_haptic_action_add(am,
+ name,
+ user_path0,
+ user_path1):
+ ami = am.actionmap_items.new(name, True)
+ if ami:
+ ami.type = 'VIBRATION'
+ ami.user_path0 = user_path0
+ ami.user_path1 = user_path1
+
+ return ami
+
+
+def vr_defaults_actionbinding_add(ami,
+ name,
+ profile,
+ component_path0,
+ component_path1,
+ threshold,
+ axis0_region,
+ axis1_region):
+ amb = ami.bindings.new(name, True)
+ if amb:
+ amb.profile = profile
+ amb.component_path0 = component_path0
+ amb.component_path1 = component_path1
+ amb.threshold = threshold
+ amb.axis0_region = axis0_region
+ amb.axis1_region = axis1_region
+
+ return amb
+
+
+def vr_defaults_pose_actionbinding_add(ami,
+ name,
+ profile,
+ component_path0,
+ component_path1,
+ location,
+ rotation):
+ amb = ami.bindings.new(name, True)
+ if amb:
+ amb.profile = profile
+ amb.component_path0 = component_path0
+ amb.component_path1 = component_path1
+ amb.pose_location = location
+ amb.pose_rotation = rotation
+
+ return amb
+
+
+def vr_defaults_haptic_actionbinding_add(ami,
+ name,
+ profile,
+ component_path0,
+ component_path1):
+ amb = ami.bindings.new(name, True)
+ if amb:
+ amb.profile = profile
+ amb.component_path0 = component_path0
+ amb.component_path1 = component_path1
+
+
+ return amb
+
+
+def vr_defaults_create_default(session_state):
+ am = vr_defaults_actionmap_add(session_state,
+ VRDefaultActionmaps.DEFAULT.value)
+ if not am:
+ return
+
+ ami = vr_defaults_pose_action_add(am,
+ VRDefaultActions.CONTROLLER_GRIP.value,
+ "/user/hand/left",
+ "/user/hand/right",
+ True,
+ False)
+ if ami:
+ vr_defaults_pose_actionbinding_add(ami,
+ VRDefaultActionbindings.COSMOS.value,
+ VRDefaultActionprofiles.COSMOS.value,
+ "/input/grip/pose",
+ "/input/grip/pose",
+ (0, 0, 0),
+ (0, 0, 0))
+ vr_defaults_pose_actionbinding_add(ami,
+ VRDefaultActionbindings.HUAWEI.value,
+ VRDefaultActionprofiles.HUAWEI.value,
+ "/input/grip/pose",
+ "/input/grip/pose",
+ (0, 0, 0),
+ (0, 0, 0))
+ vr_defaults_pose_actionbinding_add(ami,
+ VRDefaultActionbindings.INDEX.value,
+ VRDefaultActionprofiles.INDEX.value,
+ "/input/grip/pose",
+ "/input/grip/pose",
+ (0, 0, 0),
+ (0, 0, 0))
+ vr_defaults_pose_actionbinding_add(ami,
+ VRDefaultActionbindings.OCULUS.value,
+ VRDefaultActionprofiles.OCULUS.value,
+ "/input/grip/pose",
+ "/input/grip/pose",
+ (0, 0, 0),
+ (0, 0, 0))
+ vr_defaults_pose_actionbinding_add(ami,
+ VRDefaultActionbindings.REVERB_G2.value,
+ VRDefaultActionprofiles.REVERB_G2.value,
+ "/input/grip/pose",
+ "/input/grip/pose",
+ (0, 0, 0),
+ (0, 0, 0))
+ vr_defaults_pose_actionbinding_add(ami,
+ VRDefaultActionbindings.SIMPLE.value,
+ VRDefaultActionprofiles.SIMPLE.value,
+ "/input/grip/pose",
+ "/input/grip/pose",
+ (0, 0, 0),
+ (0, 0, 0))
+ vr_defaults_pose_actionbinding_add(ami,
+ VRDefaultActionbindings.VIVE.value,
+ VRDefaultActionprofiles.VIVE.value,
+ "/input/grip/pose",
+ "/input/grip/pose",
+ (0, 0, 0),
+ (0, 0, 0))
+ vr_defaults_pose_actionbinding_add(ami,
+ VRDefaultActionbindings.WMR.value,
+ VRDefaultActionprofiles.WMR.value,
+ "/input/grip/pose",
+ "/input/grip/pose",
+ (0, 0, 0),
+ (0, 0, 0))
+
+ ami = vr_defaults_pose_action_add(am,
+ VRDefaultActions.CONTROLLER_AIM.value,
+ "/user/hand/left",
+ "/user/hand/right",
+ False,
+ True)
+ if ami:
+ vr_defaults_pose_actionbinding_add(ami,
+ VRDefaultActionbindings.COSMOS.value,
+ VRDefaultActionprofiles.COSMOS.value,
+ "/input/aim/pose",
+ "/input/aim/pose",
+ (0, 0, 0),
+ (0, 0, 0))
+ vr_defaults_pose_actionbinding_add(ami,
+ VRDefaultActionbindings.HUAWEI.value,
+ VRDefaultActionprofiles.HUAWEI.value,
+ "/input/aim/pose",
+ "/input/aim/pose",
+ (0, 0, 0),
+ (0, 0, 0))
+ vr_defaults_pose_actionbinding_add(ami,
+ VRDefaultActionbindings.INDEX.value,
+ VRDefaultActionprofiles.INDEX.value,
+ "/input/aim/pose",
+ "/input/aim/pose",
+ (0, 0, 0),
+ (0, 0, 0))
+ vr_defaults_pose_actionbinding_add(ami,
+ VRDefaultActionbindings.OCULUS.value,
+ VRDefaultActionprofiles.OCULUS.value,
+ "/input/aim/pose",
+ "/input/aim/pose",
+ (0, 0, 0),
+ (0, 0, 0))
+ vr_defaults_pose_actionbinding_add(ami,
+ VRDefaultActionbindings.REVERB_G2.value,
+ VRDefaultActionprofiles.REVERB_G2.value,
+ "/input/aim/pose",
+ "/input/aim/pose",
+ (0, 0, 0),
+ (0, 0, 0))
+ vr_defaults_pose_actionbinding_add(ami,
+ VRDefaultActionbindings.SIMPLE.value,
+ VRDefaultActionprofiles.SIMPLE.value,
+ "/input/aim/pose",
+ "/input/aim/pose",
+ (0, 0, 0),
+ (0, 0, 0))
+ vr_defaults_pose_actionbinding_add(ami,
+ VRDefaultActionbindings.VIVE.value,
+ VRDefaultActionprofiles.VIVE.value,
+ "/input/aim/pose",
+ "/input/aim/pose",
+ (0, 0, 0),
+ (0, 0, 0))
+ vr_defaults_pose_actionbinding_add(ami,
+ VRDefaultActionbindings.WMR.value,
+ VRDefaultActionprofiles.WMR.value,
+ "/input/aim/pose",
+ "/input/aim/pose",
+ (0, 0, 0),
+ (0, 0, 0))
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.TELEPORT.value,
+ "/user/hand/left",
+ "/user/hand/right",
+ "wm.xr_navigation_teleport",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.COSMOS.value,
+ VRDefaultActionprofiles.COSMOS.value,
+ "/input/trigger/value",
+ "/input/trigger/value",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.HUAWEI.value,
+ VRDefaultActionprofiles.HUAWEI.value,
+ "/input/trigger/value",
+ "/input/trigger/value",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.INDEX.value,
+ VRDefaultActionprofiles.INDEX.value,
+ "/input/trigger/value",
+ "/input/trigger/value",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.OCULUS.value,
+ VRDefaultActionprofiles.OCULUS.value,
+ "/input/trigger/value",
+ "/input/trigger/value",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.REVERB_G2.value,
+ VRDefaultActionprofiles.REVERB_G2.value,
+ "/input/trigger/value",
+ "/input/trigger/value",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.SIMPLE.value,
+ VRDefaultActionprofiles.SIMPLE.value,
+ "/input/select/click",
+ "/input/select/click",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.VIVE.value,
+ VRDefaultActionprofiles.VIVE.value,
+ "/input/trigger/value",
+ "/input/trigger/value",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.WMR.value,
+ VRDefaultActionprofiles.WMR.value,
+ "/input/trigger/value",
+ "/input/trigger/value",
+ 0.3,
+ 'ANY',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.NAV_GRAB.value,
+ "/user/hand/left",
+ "/user/hand/right",
+ "wm.xr_navigation_grab",
+ 'MODAL',
+ True,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.COSMOS.value,
+ VRDefaultActionprofiles.COSMOS.value,
+ "/input/squeeze/click",
+ "/input/squeeze/click",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.HUAWEI.value,
+ VRDefaultActionprofiles.HUAWEI.value,
+ "/input/trackpad/click",
+ "/input/trackpad/click",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.INDEX.value,
+ VRDefaultActionprofiles.INDEX.value,
+ "/input/squeeze/value",
+ "/input/squeeze/value",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.OCULUS.value,
+ VRDefaultActionprofiles.OCULUS.value,
+ "/input/squeeze/value",
+ "/input/squeeze/value",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.REVERB_G2.value,
+ VRDefaultActionprofiles.REVERB_G2.value,
+ "/input/squeeze/value",
+ "/input/squeeze/value",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.SIMPLE.value,
+ VRDefaultActionprofiles.SIMPLE.value,
+ "/input/menu/click",
+ "/input/menu/click",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.VIVE.value,
+ VRDefaultActionprofiles.VIVE.value,
+ "/input/squeeze/click",
+ "/input/squeeze/click",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.WMR.value,
+ VRDefaultActionprofiles.WMR.value,
+ "/input/squeeze/click",
+ "/input/squeeze/click",
+ 0.3,
+ 'ANY',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY_FORWARD.value,
+ "/user/hand/left",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.COSMOS.value,
+ VRDefaultActionprofiles.COSMOS.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.HUAWEI.value,
+ VRDefaultActionprofiles.HUAWEI.value,
+ "/input/trackpad/y",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.INDEX.value,
+ VRDefaultActionprofiles.INDEX.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.OCULUS.value,
+ VRDefaultActionprofiles.OCULUS.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.REVERB_G2.value,
+ VRDefaultActionprofiles.REVERB_G2.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.VIVE.value,
+ VRDefaultActionprofiles.VIVE.value,
+ "/input/trackpad/y",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.WMR.value,
+ VRDefaultActionprofiles.WMR.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY_BACK.value,
+ "/user/hand/left",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.COSMOS.value,
+ VRDefaultActionprofiles.COSMOS.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.HUAWEI.value,
+ VRDefaultActionprofiles.HUAWEI.value,
+ "/input/trackpad/y",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.INDEX.value,
+ VRDefaultActionprofiles.INDEX.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.OCULUS.value,
+ VRDefaultActionprofiles.OCULUS.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.REVERB_G2.value,
+ VRDefaultActionprofiles.REVERB_G2.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.VIVE.value,
+ VRDefaultActionprofiles.VIVE.value,
+ "/input/trackpad/y",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.WMR.value,
+ VRDefaultActionprofiles.WMR.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY_LEFT.value,
+ "/user/hand/left",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.COSMOS.value,
+ VRDefaultActionprofiles.COSMOS.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.HUAWEI.value,
+ VRDefaultActionprofiles.HUAWEI.value,
+ "/input/trackpad/x",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.INDEX.value,
+ VRDefaultActionprofiles.INDEX.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.OCULUS.value,
+ VRDefaultActionprofiles.OCULUS.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.REVERB_G2.value,
+ VRDefaultActionprofiles.REVERB_G2.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.VIVE.value,
+ VRDefaultActionprofiles.VIVE.value,
+ "/input/trackpad/x",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.WMR.value,
+ VRDefaultActionprofiles.WMR.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY_RIGHT.value,
+ "/user/hand/left",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.COSMOS.value,
+ VRDefaultActionprofiles.COSMOS.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.HUAWEI.value,
+ VRDefaultActionprofiles.HUAWEI.value,
+ "/input/trackpad/x",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.INDEX.value,
+ VRDefaultActionprofiles.INDEX.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.OCULUS.value,
+ VRDefaultActionprofiles.OCULUS.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.REVERB_G2.value,
+ VRDefaultActionprofiles.REVERB_G2.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.VIVE.value,
+ VRDefaultActionprofiles.VIVE.value,
+ "/input/trackpad/x",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.WMR.value,
+ VRDefaultActionprofiles.WMR.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY_UP.value,
+ "/user/hand/right",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.COSMOS.value,
+ VRDefaultActionprofiles.COSMOS.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.HUAWEI.value,
+ VRDefaultActionprofiles.HUAWEI.value,
+ "/input/trackpad/y",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.INDEX.value,
+ VRDefaultActionprofiles.INDEX.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.OCULUS.value,
+ VRDefaultActionprofiles.OCULUS.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.REVERB_G2.value,
+ VRDefaultActionprofiles.REVERB_G2.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.VIVE.value,
+ VRDefaultActionprofiles.VIVE.value,
+ "/input/trackpad/y",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.WMR.value,
+ VRDefaultActionprofiles.WMR.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY_DOWN.value,
+ "/user/hand/right",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.COSMOS.value,
+ VRDefaultActionprofiles.COSMOS.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.HUAWEI.value,
+ VRDefaultActionprofiles.HUAWEI.value,
+ "/input/trackpad/y",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.INDEX.value,
+ VRDefaultActionprofiles.INDEX.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.OCULUS.value,
+ VRDefaultActionprofiles.OCULUS.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.REVERB_G2.value,
+ VRDefaultActionprofiles.REVERB_G2.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.VIVE.value,
+ VRDefaultActionprofiles.VIVE.value,
+ "/input/trackpad/y",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.WMR.value,
+ VRDefaultActionprofiles.WMR.value,
+ "/input/thumbstick/y",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY_TURNLEFT.value,
+ "/user/hand/right",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.COSMOS.value,
+ VRDefaultActionprofiles.COSMOS.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.HUAWEI.value,
+ VRDefaultActionprofiles.HUAWEI.value,
+ "/input/trackpad/x",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.INDEX.value,
+ VRDefaultActionprofiles.INDEX.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.OCULUS.value,
+ VRDefaultActionprofiles.OCULUS.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.REVERB_G2.value,
+ VRDefaultActionprofiles.REVERB_G2.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.VIVE.value,
+ VRDefaultActionprofiles.VIVE.value,
+ "/input/trackpad/x",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.WMR.value,
+ VRDefaultActionprofiles.WMR.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY_TURNRIGHT.value,
+ "/user/hand/right",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.COSMOS.value,
+ VRDefaultActionprofiles.COSMOS.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.HUAWEI.value,
+ VRDefaultActionprofiles.HUAWEI.value,
+ "/input/trackpad/x",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.INDEX.value,
+ VRDefaultActionprofiles.INDEX.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.OCULUS.value,
+ VRDefaultActionprofiles.OCULUS.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.REVERB_G2.value,
+ VRDefaultActionprofiles.REVERB_G2.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.VIVE.value,
+ VRDefaultActionprofiles.VIVE.value,
+ "/input/trackpad/x",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.WMR.value,
+ VRDefaultActionprofiles.WMR.value,
+ "/input/thumbstick/x",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.NAV_RESET.value,
+ "/user/hand/left",
+ "/user/hand/right",
+ "wm.xr_navigation_reset",
+ 'PRESS',
+ False,
+ "haptic",
+ True,
+ 0.3,
+ 3000.0,
+ 0.5,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.COSMOS.value,
+ VRDefaultActionprofiles.COSMOS.value,
+ "/input/x/click",
+ "/input/a/click",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.HUAWEI.value,
+ VRDefaultActionprofiles.HUAWEI.value,
+ "/input/back/click",
+ "/input/back/click",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.INDEX.value,
+ VRDefaultActionprofiles.INDEX.value,
+ "/input/a/click",
+ "/input/a/click",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.OCULUS.value,
+ VRDefaultActionprofiles.OCULUS.value,
+ "/input/x/click",
+ "/input/a/click",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.REVERB_G2.value,
+ VRDefaultActionprofiles.REVERB_G2.value,
+ "/input/x/click",
+ "/input/a/click",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.VIVE.value,
+ VRDefaultActionprofiles.VIVE.value,
+ "/input/menu/click",
+ "/input/menu/click",
+ 0.3,
+ 'ANY',
+ 'ANY')
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.WMR.value,
+ VRDefaultActionprofiles.WMR.value,
+ "/input/menu/click",
+ "/input/menu/click",
+ 0.3,
+ 'ANY',
+ 'ANY')
+
+ ami = vr_defaults_haptic_action_add(am,
+ VRDefaultActions.HAPTIC.value,
+ "/user/hand/left",
+ "/user/hand/right")
+ if ami:
+ vr_defaults_haptic_actionbinding_add(ami,
+ VRDefaultActionbindings.COSMOS.value,
+ VRDefaultActionprofiles.COSMOS.value,
+ "/output/haptic",
+ "/output/haptic")
+ vr_defaults_haptic_actionbinding_add(ami,
+ VRDefaultActionbindings.HUAWEI.value,
+ VRDefaultActionprofiles.HUAWEI.value,
+ "/output/haptic",
+ "/output/haptic")
+ vr_defaults_haptic_actionbinding_add(ami,
+ VRDefaultActionbindings.INDEX.value,
+ VRDefaultActionprofiles.INDEX.value,
+ "/output/haptic",
+ "/output/haptic")
+ vr_defaults_haptic_actionbinding_add(ami,
+ VRDefaultActionbindings.OCULUS.value,
+ VRDefaultActionprofiles.OCULUS.value,
+ "/output/haptic",
+ "/output/haptic")
+ vr_defaults_haptic_actionbinding_add(ami,
+ VRDefaultActionbindings.REVERB_G2.value,
+ VRDefaultActionprofiles.REVERB_G2.value,
+ "/output/haptic",
+ "/output/haptic")
+ vr_defaults_haptic_actionbinding_add(ami,
+ VRDefaultActionbindings.SIMPLE.value,
+ VRDefaultActionprofiles.SIMPLE.value,
+ "/output/haptic",
+ "/output/haptic")
+ vr_defaults_haptic_actionbinding_add(ami,
+ VRDefaultActionbindings.VIVE.value,
+ VRDefaultActionprofiles.VIVE.value,
+ "/output/haptic",
+ "/output/haptic")
+ vr_defaults_haptic_actionbinding_add(ami,
+ VRDefaultActionbindings.WMR.value,
+ VRDefaultActionprofiles.WMR.value,
+ "/output/haptic",
+ "/output/haptic")
+
+
+def vr_defaults_create_default_gamepad(session_state):
+ am = vr_defaults_actionmap_add(session_state,
+ VRDefaultActionmaps.GAMEPAD.value)
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.TELEPORT.value,
+ "/user/gamepad",
+ "",
+ "wm.xr_navigation_teleport",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.GAMEPAD.value,
+ VRDefaultActionprofiles.GAMEPAD.value,
+ "/input/trigger_right/value",
+ "",
+ 0.3,
+ 'ANY',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY.value,
+ "/user/gamepad",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.GAMEPAD.value,
+ VRDefaultActionprofiles.GAMEPAD.value,
+ "/input/trigger_left/value",
+ "",
+ 0.3,
+ 'ANY',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY_FORWARD.value,
+ "/user/gamepad",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.GAMEPAD.value,
+ VRDefaultActionprofiles.GAMEPAD.value,
+ "/input/thumbstick_left/y",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY_BACK.value,
+ "/user/gamepad",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.GAMEPAD.value,
+ VRDefaultActionprofiles.GAMEPAD.value,
+ "/input/thumbstick_left/y",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY_LEFT.value,
+ "/user/gamepad",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.GAMEPAD.value,
+ VRDefaultActionprofiles.GAMEPAD.value,
+ "/input/thumbstick_left/x",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY_RIGHT.value,
+ "/user/gamepad",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.GAMEPAD.value,
+ VRDefaultActionprofiles.GAMEPAD.value,
+ "/input/thumbstick_left/x",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY_UP.value,
+ "/user/gamepad",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.GAMEPAD.value,
+ VRDefaultActionprofiles.GAMEPAD.value,
+ "/input/thumbstick_right/y",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY_DOWN.value,
+ "/user/gamepad",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.GAMEPAD.value,
+ VRDefaultActionprofiles.GAMEPAD.value,
+ "/input/thumbstick_right/y",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY_TURNLEFT.value,
+ "/user/gamepad",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.GAMEPAD.value,
+ VRDefaultActionprofiles.GAMEPAD.value,
+ "/input/thumbstick_right/x",
+ "",
+ 0.3,
+ 'NEGATIVE',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.FLY_TURNRIGHT.value,
+ "/user/gamepad",
+ "",
+ "wm.xr_navigation_fly",
+ 'MODAL',
+ False,
+ "",
+ False,
+ 0.0,
+ 0.0,
+ 0.0,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.GAMEPAD.value,
+ VRDefaultActionprofiles.GAMEPAD.value,
+ "/input/thumbstick_right/x",
+ "",
+ 0.3,
+ 'POSITIVE',
+ 'ANY')
+
+ ami = vr_defaults_action_add(am,
+ VRDefaultActions.NAV_RESET.value,
+ "/user/gamepad",
+ "",
+ "wm.xr_navigation_reset",
+ 'PRESS',
+ False,
+ "haptic_right",
+ True,
+ 0.3,
+ 3000.0,
+ 0.5,
+ 'PRESS')
+ if ami:
+ vr_defaults_actionbinding_add(ami,
+ VRDefaultActionbindings.GAMEPAD.value,
+ VRDefaultActionprofiles.GAMEPAD.value,
+ "/input/a/click",
+ "",
+ 0.3,
+ 'ANY',
+ 'ANY')
+
+ ami =vr_defaults_haptic_action_add(am,
+ VRDefaultActions.HAPTIC_LEFT.value,
+ "/user/gamepad",
+ "")
+ if ami:
+ vr_defaults_haptic_actionbinding_add(ami,
+ VRDefaultActionbindings.GAMEPAD.value,
+ VRDefaultActionprofiles.GAMEPAD.value,
+ "/output/haptic_left",
+ "")
+
+ ami =vr_defaults_haptic_action_add(am,
+ VRDefaultActions.HAPTIC_RIGHT.value,
+ "/user/gamepad",
+ "")
+ if ami:
+ vr_defaults_haptic_actionbinding_add(ami,
+ VRDefaultActionbindings.GAMEPAD.value,
+ VRDefaultActionprofiles.GAMEPAD.value,
+ "/output/haptic_right",
+ "")
+
+ ami = vr_defaults_haptic_action_add(am,
+ VRDefaultActions.HAPTIC_LEFTTRIGGER.value,
+ "/user/gamepad",
+ "")
+ if ami:
+ vr_defaults_haptic_actionbinding_add(ami,
+ VRDefaultActionbindings.GAMEPAD.value,
+ VRDefaultActionprofiles.GAMEPAD.value,
+ "/output/haptic_left_trigger",
+ "")
+
+ ami = vr_defaults_haptic_action_add(am,
+ VRDefaultActions.HAPTIC_RIGHTTRIGGER.value,
+ "/user/gamepad",
+ "")
+ if ami:
+ vr_defaults_haptic_actionbinding_add(ami,
+ VRDefaultActionbindings.GAMEPAD.value,
+ VRDefaultActionprofiles.GAMEPAD.value,
+ "/output/haptic_right_trigger",
+ "")
+
+
+def vr_get_default_config_path():
+ filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "configs")
+ return os.path.join(filepath, "default.py")
+
+
+def vr_ensure_default_actionmaps(session_state):
+ loaded = True
+
+ for name in VRDefaultActionmaps:
+ if not session_state.actionmaps.find(session_state, name.value):
+ loaded = False
+ break
+
+ if loaded:
+ return loaded
+
+ # Load default action maps.
+ filepath = vr_get_default_config_path()
+
+ if not os.path.exists(filepath):
+ # Create and save default action maps.
+ vr_defaults_create_default(session_state)
+ vr_defaults_create_default_gamepad(session_state)
+
+ action_map.vr_save_actionmaps(session_state, filepath, sort=False)
+
+ loaded = action_map.vr_load_actionmaps(session_state, filepath)
+
+ return loaded
diff --git a/viewport_vr_preview/gui.py b/viewport_vr_preview/gui.py
new file mode 100644
index 00000000..7778882d
--- /dev/null
+++ b/viewport_vr_preview/gui.py
@@ -0,0 +1,277 @@
+# ##### 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>
+
+if "bpy" in locals():
+ import importlib
+ importlib.reload(properties)
+else:
+ from . import properties
+
+import bpy
+from bpy.types import (
+ Menu,
+ Panel,
+ UIList,
+)
+
+### Session.
+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
+ scene = context.scene
+
+ 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()
+
+ col = layout.column(align=True, heading="Tracking")
+ col.prop(session_settings, "use_positional_tracking", text="Positional")
+ col.prop(session_settings, "use_absolute_tracking", text="Absolute")
+
+ col = layout.column(align=True, heading="Actions")
+ col.prop(scene, "vr_actions_enable")
+
+
+### View.
+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.prop(session_settings, "show_selection", text="Selection")
+ col.prop(session_settings, "show_controllers", text="Controllers")
+ col.prop(session_settings, "show_custom_overlays", text="Custom Overlays")
+
+ col = layout.column(align=True)
+ col.prop(session_settings, "controller_draw_style", text="Controller Style")
+
+ col = layout.column(align=True)
+ col.prop(session_settings, "clip_start", text="Clip Start")
+ col.prop(session_settings, "clip_end", text="End")
+
+
+### Landmarks.
+class VIEW3D_MT_vr_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 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 = properties.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_vr_landmark_menu", icon='DOWNARROW_HLT', text="")
+
+ if landmark_selected:
+ layout.prop(landmark_selected, "type")
+
+ if landmark_selected.type == 'OBJECT':
+ layout.prop(landmark_selected, "base_pose_object")
+ layout.prop(landmark_selected, "base_scale", text="Scale")
+ elif landmark_selected.type == 'CUSTOM':
+ layout.prop(landmark_selected,
+ "base_pose_location", text="Location")
+ layout.prop(landmark_selected,
+ "base_pose_angle", text="Angle")
+ layout.prop(landmark_selected,
+ "base_scale", text="Scale")
+
+
+### View.
+class VIEW3D_PT_vr_actionmaps(Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_category = "VR"
+ bl_label = "Action Maps"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ layout = self.layout
+ scene = context.scene
+
+ layout.use_property_split = True
+ layout.use_property_decorate = False # No animation.
+
+ col = layout.column(align=True)
+ col.prop(scene, "vr_actions_use_gamepad", text="Gamepad")
+
+ col = layout.column(align=True, heading="Extensions")
+ col.prop(scene, "vr_actions_enable_reverb_g2", text="HP Reverb G2")
+ col.prop(scene, "vr_actions_enable_cosmos", text="HTC Vive Cosmos")
+ col.prop(scene, "vr_actions_enable_huawei", text="Huawei")
+
+
+### Viewport feedback.
+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
+ scene = context.scene
+ view3d = context.space_data
+ session_settings = context.window_manager.xr_session_settings
+
+ 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_controllers")
+ layout.prop(view3d.shading, "vr_show_landmarks")
+ layout.prop(view3d, "mirror_xr_session")
+
+
+### Info.
+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.")
+
+
+classes = (
+ VIEW3D_PT_vr_session,
+ VIEW3D_PT_vr_session_view,
+ VIEW3D_PT_vr_landmarks,
+ VIEW3D_PT_vr_actionmaps,
+ VIEW3D_PT_vr_viewport_feedback,
+
+ VIEW3D_UL_vr_landmarks,
+ VIEW3D_MT_vr_landmark_menu,
+)
+
+
+def register():
+ for cls in classes:
+ bpy.utils.register_class(cls)
+
+ # 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 = bpy.props.BoolProperty(
+ name="Show VR Camera"
+ )
+ bpy.types.View3DShading.vr_show_controllers = bpy.props.BoolProperty(
+ name="Show VR Controllers"
+ )
+ bpy.types.View3DShading.vr_show_landmarks = bpy.props.BoolProperty(
+ name="Show Landmarks"
+ )
+
+
+def unregister():
+ for cls in classes:
+ bpy.utils.unregister_class(cls)
+
+ del bpy.types.View3DShading.vr_show_virtual_camera
+ del bpy.types.View3DShading.vr_show_controllers
+ del bpy.types.View3DShading.vr_show_landmarks
diff --git a/viewport_vr_preview/operators.py b/viewport_vr_preview/operators.py
new file mode 100644
index 00000000..932dac31
--- /dev/null
+++ b/viewport_vr_preview/operators.py
@@ -0,0 +1,521 @@
+# ##### 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>
+
+if "bpy" in locals():
+ import importlib
+ importlib.reload(properties)
+else:
+ from . import properties
+
+import bpy
+from bpy.types import (
+ Gizmo,
+ GizmoGroup,
+ Operator,
+)
+import bgl
+import math
+from math import radians
+from mathutils import Euler, Matrix, Quaternion, Vector
+
+
+### Landmarks.
+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):
+ cam_selected = False
+
+ vl_objects = bpy.context.view_layer.objects
+ if vl_objects.active and vl_objects.active.type == 'CAMERA':
+ cam_selected = True
+ 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 = 'OBJECT'
+ lm.base_pose_object = 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)
+
+ def execute(self, context):
+ scene = context.scene
+ landmarks = scene.vr_landmarks
+ wm = context.window_manager
+
+ lm = landmarks.add()
+ lm.type = "CUSTOM"
+ scene.vr_landmarks_selected = len(landmarks) - 1
+
+ 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[2]
+
+ 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 = properties.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 = properties.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.
+ properties.vr_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 = properties.VRLandmark.get_selected_landmark(context)
+ if lm.type == 'SCENE_CAMERA':
+ return context.scene.camera is not None
+ elif lm.type == 'OBJECT':
+ return lm.base_pose_object is not None
+
+ return True
+
+ def execute(self, context):
+ scene = context.scene
+ lm = properties.VRLandmark.get_selected_landmark(context)
+ if lm.type == 'SCENE_CAMERA':
+ lm_pos = scene.camera.location
+ elif lm.type == 'OBJECT':
+ lm_pos = lm.base_pose_object.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 = properties.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)
+
+ return {'FINISHED'}
+
+
+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 = properties.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: bpy.props.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'}
+
+
+### Gizmos.
+class VIEW3D_GT_vr_camera_cone(Gizmo):
+ bl_idname = "VIEW_3D_GT_vr_camera_cone"
+
+ aspect = 1.0, 1.0
+
+ def draw(self, context):
+ 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_GT_vr_controller_grip(Gizmo):
+ bl_idname = "VIEW_3D_GT_vr_controller_grip"
+
+ def draw(self, context):
+ bgl.glLineWidth(1)
+ bgl.glEnable(bgl.GL_BLEND)
+
+ self.color = 0.422, 0.438, 0.446
+ self.draw_preset_circle(self.matrix_basis, axis='POS_X')
+ self.draw_preset_circle(self.matrix_basis, axis='POS_Y')
+ self.draw_preset_circle(self.matrix_basis, axis='POS_Z')
+
+
+class VIEW3D_GT_vr_controller_aim(Gizmo):
+ bl_idname = "VIEW_3D_GT_vr_controller_aim"
+
+ def draw(self, context):
+ bgl.glLineWidth(1)
+ bgl.glEnable(bgl.GL_BLEND)
+
+ self.color = 1.0, 0.2, 0.322
+ self.draw_preset_arrow(self.matrix_basis, axis='POS_X')
+ self.color = 0.545, 0.863, 0.0
+ self.draw_preset_arrow(self.matrix_basis, axis='POS_Y')
+ self.color = 0.157, 0.565, 1.0
+ self.draw_preset_arrow(self.matrix_basis, axis='POS_Z')
+
+
+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):
+ 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_controller_poses(GizmoGroup):
+ bl_idname = "VIEW3D_GGT_vr_controller_poses"
+ bl_label = "VR Controller Poses 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_controllers and
+ bpy.types.XrSessionState.is_running(context) and
+ not view3d.mirror_xr_session
+ )
+
+ @staticmethod
+ def _get_controller_pose_matrix(context, idx, is_grip, scale):
+ wm = context.window_manager
+
+ loc = None
+ rot = None
+ if is_grip:
+ loc = wm.xr_session_state.controller_grip_location_get(context, idx)
+ rot = wm.xr_session_state.controller_grip_rotation_get(context, idx)
+ else:
+ loc = wm.xr_session_state.controller_aim_location_get(context, idx)
+ rot = wm.xr_session_state.controller_aim_rotation_get(context, idx)
+
+ rotmat = Matrix.Identity(3)
+ rotmat.rotate(Quaternion(Vector(rot)))
+ rotmat.resize_4x4()
+ transmat = Matrix.Translation(loc)
+ scalemat = Matrix.Scale(scale, 4)
+
+ return transmat @ rotmat @ scalemat
+
+ def setup(self, context):
+ for idx in range(2):
+ self.gizmos.new(VIEW3D_GT_vr_controller_grip.bl_idname)
+ self.gizmos.new(VIEW3D_GT_vr_controller_aim.bl_idname)
+
+ for gizmo in self.gizmos:
+ gizmo.aspect = 1 / 3, 1 / 4
+ gizmo.color_highlight = 1.0, 1.0, 1.0
+ gizmo.alpha = 1.0
+
+ def draw_prepare(self, context):
+ grip_idx = 0
+ aim_idx = 0
+ idx = 0
+ scale = 1.0
+ for gizmo in self.gizmos:
+ is_grip = (gizmo.bl_idname == VIEW3D_GT_vr_controller_grip.bl_idname)
+ if (is_grip):
+ idx = grip_idx
+ grip_idx += 1
+ scale = 0.1
+ else:
+ idx = aim_idx
+ aim_idx += 1
+ scale = 0.5
+ gizmo.matrix_basis = self._get_controller_pose_matrix(context, idx, is_grip, scale)
+
+
+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
+ )
+
+ def setup(self, context):
+ pass
+
+ def draw_prepare(self, context):
+ # first delete the old gizmos
+ for g in self.gizmos:
+ self.gizmos.remove(g)
+
+ scene = context.scene
+ landmarks = scene.vr_landmarks
+
+ for lm in landmarks:
+ if ((lm.type == 'SCENE_CAMERA' and not scene.camera) or
+ (lm.type == 'OBJECT' and not lm.base_pose_object)):
+ 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 == 'OBJECT':
+ lm_mat = lm.base_pose_object.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_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_GT_vr_controller_grip,
+ VIEW3D_GT_vr_controller_aim,
+ VIEW3D_GGT_vr_viewer_pose,
+ VIEW3D_GGT_vr_controller_poses,
+ VIEW3D_GGT_vr_landmarks,
+)
+
+
+def register():
+ for cls in classes:
+ bpy.utils.register_class(cls)
+
+
+def unregister():
+ for cls in classes:
+ bpy.utils.unregister_class(cls)
diff --git a/viewport_vr_preview/properties.py b/viewport_vr_preview/properties.py
new file mode 100644
index 00000000..03f03e8c
--- /dev/null
+++ b/viewport_vr_preview/properties.py
@@ -0,0 +1,244 @@
+# ##### 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 (
+ PropertyGroup,
+)
+from bpy.app.handlers import persistent
+
+
+### Landmarks.
+@persistent
+def vr_ensure_default_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 vr_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 == 'OBJECT':
+ session_settings.base_pose_type = 'OBJECT'
+ elif landmark_active.type == 'CUSTOM':
+ session_settings.base_pose_type = 'CUSTOM'
+
+
+def vr_landmark_active_base_pose_object_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_object
+
+
+def vr_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 vr_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 vr_landmark_active_base_scale_update(self, context):
+ session_settings = context.window_manager.xr_session_settings
+ landmark_active = VRLandmark.get_active_landmark(context)
+
+ session_settings.base_scale = landmark_active.base_scale
+
+
+def vr_landmark_type_update(self, context):
+ landmark_selected = VRLandmark.get_selected_landmark(context)
+ landmark_active = VRLandmark.get_active_landmark(context)
+
+ # Don't allow non-trivial base scale for scene camera landmarks.
+ if landmark_selected.type == 'SCENE_CAMERA':
+ landmark_selected.base_scale = 1.0
+
+ # Only update session settings data if the changed landmark is actually
+ # the active one.
+ if landmark_active == landmark_selected:
+ vr_landmark_active_type_update(self, context)
+
+
+def vr_landmark_base_pose_object_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:
+ vr_landmark_active_base_pose_object_update(self, context)
+
+
+def vr_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:
+ vr_landmark_active_base_pose_location_update(self, context)
+
+
+def vr_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:
+ vr_landmark_active_base_pose_angle_update(self, context)
+
+
+def vr_landmark_base_scale_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:
+ vr_landmark_active_base_scale_update(self, context)
+
+
+def vr_landmark_active_update(self, context):
+ wm = context.window_manager
+
+ vr_landmark_active_type_update(self, context)
+ vr_landmark_active_base_pose_object_update(self, context)
+ vr_landmark_active_base_pose_location_update(self, context)
+ vr_landmark_active_base_pose_angle_update(self, context)
+ vr_landmark_active_base_scale_update(self, context)
+
+ if wm.xr_session_state:
+ wm.xr_session_state.reset_to_base_pose(context)
+
+
+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"),
+ ('OBJECT', "Custom Object",
+ "Use an existing object 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=vr_landmark_type_update,
+ )
+ base_pose_object: bpy.props.PointerProperty(
+ name="Object",
+ type=bpy.types.Object,
+ update=vr_landmark_base_pose_object_update,
+ )
+ base_pose_location: bpy.props.FloatVectorProperty(
+ name="Base Pose Location",
+ subtype='TRANSLATION',
+ update=vr_landmark_base_pose_location_update,
+ )
+ base_pose_angle: bpy.props.FloatProperty(
+ name="Base Pose Angle",
+ subtype='ANGLE',
+ update=vr_landmark_base_pose_angle_update,
+ )
+ base_scale: bpy.props.FloatProperty(
+ name="Base Scale",
+ description="Viewer reference scale associated with this landmark",
+ default=1.0,
+ min=0.000001,
+ update=vr_landmark_base_scale_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]
+ )
+
+
+classes = (
+ VRLandmark,
+)
+
+
+def register():
+ for cls in classes:
+ bpy.utils.register_class(cls)
+
+ bpy.types.Scene.vr_landmarks = bpy.props.CollectionProperty(
+ name="Landmark",
+ type=VRLandmark,
+ )
+ bpy.types.Scene.vr_landmarks_selected = bpy.props.IntProperty(
+ name="Selected Landmark"
+ )
+ bpy.types.Scene.vr_landmarks_active = bpy.props.IntProperty(
+ update=vr_landmark_active_update,
+ )
+
+ bpy.app.handlers.load_post.append(vr_ensure_default_landmark)
+
+
+def unregister():
+ 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
+
+ bpy.app.handlers.load_post.remove(vr_ensure_default_landmark)
diff --git a/viewport_vr_preview/versioning.py b/viewport_vr_preview/versioning.py
new file mode 100644
index 00000000..bf7e891a
--- /dev/null
+++ b/viewport_vr_preview/versioning.py
@@ -0,0 +1,54 @@
+# ##### 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>
+
+# Update Blender version this action map was written in:
+#
+# When the version is ``(0, 0, 0)``, the action map being loaded didn't contain any versioning information.
+# This will older than ``(3, 0, 0)``.
+
+def actionconfig_update(actionconfig_data, actionconfig_version):
+ from bpy.app import version_file as blender_version
+ if actionconfig_version >= blender_version:
+ return actionconfig_data
+
+## # Version the action map.
+## import copy
+## has_copy = False
+##
+## if actionconfig_version <= (3, 0, 0):
+## # Only copy once.
+## if not has_copy:
+## actionconfig_data = copy.deepcopy(actionconfig_data)
+## has_copy = True
+##
+## for (am_name, am_content) in actionconfig_data:
+## # Apply action map updates.
+##
+## am_items = am_content["items"]
+##
+## for (ami_name, ami_args, ami_data, ami_content) in am_items
+## # Apply action map item updates.
+##
+## ami_bindings = ami_content["bindings"]
+##
+## for (amb_name, amb_args) in ami_bindings:
+## # Apply action map binding updates.
+
+ return actionconfig_data