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:
-rw-r--r--viewport_vr_preview.py560
1 files changed, 560 insertions, 0 deletions
diff --git a/viewport_vr_preview.py b/viewport_vr_preview.py
new file mode 100644
index 00000000..e328a1fe
--- /dev/null
+++ b/viewport_vr_preview.py
@@ -0,0 +1,560 @@
+# ##### 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,
+)
+from bpy.props import(
+ CollectionProperty,
+ IntProperty,
+ BoolProperty,
+)
+from bpy.app.handlers import persistent
+from bl_ui.space_view3d import (
+ VIEW3D_PT_shading_lighting,
+ VIEW3D_PT_shading_color,
+ VIEW3D_PT_shading_options,
+)
+
+bl_info = {
+ "name": "Basic VR Viewer",
+ "author": "Julian Eisel (Severin)",
+ "version": (0, 0, 2),
+ "blender": (2, 83, 2),
+ "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.",
+ "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 len(landmarks) == 0:
+ landmarks.add()
+ landmarks[0].type = 'SCENE_CAMERA'
+
+
+def xr_landmark_active_type_update(self, context):
+ wm = context.window_manager
+ session_settings = wm.xr_session_settings
+ landmark_active = VRLandmark.get_active_landmark(context)
+
+ # Update session's base pose type to the matching type.
+ if landmark_active.type == 'SCENE_CAMERA':
+ session_settings.base_pose_type = 'SCENE_CAMERA'
+ elif landmark_active.type == 'USER_CAMERA':
+ session_settings.base_pose_type = 'OBJECT'
+ elif landmark_active.type == 'CUSTOM':
+ session_settings.base_pose_type = 'CUSTOM'
+
+
+def xr_landmark_active_camera_update(self, context):
+ session_settings = context.window_manager.xr_session_settings
+ landmark_active = VRLandmark.get_active_landmark(context)
+
+ # Update the anchor object to the (new) camera of this landmark.
+ session_settings.base_pose_object = landmark_active.base_pose_camera
+
+
+def xr_landmark_active_base_pose_location_update(self, context):
+ session_settings = context.window_manager.xr_session_settings
+ landmark_active = VRLandmark.get_active_landmark(context)
+
+ session_settings.base_pose_location = landmark_active.base_pose_location
+
+
+def xr_landmark_active_base_pose_angle_update(self, context):
+ session_settings = context.window_manager.xr_session_settings
+ landmark_active = VRLandmark.get_active_landmark(context)
+
+ session_settings.base_pose_angle = landmark_active.base_pose_angle
+
+
+def xr_landmark_type_update(self, context):
+ landmark_selected = VRLandmark.get_selected_landmark(context)
+ landmark_active = VRLandmark.get_active_landmark(context)
+
+ # Only update session settings data if the changed landmark is actually the active one.
+ if landmark_active == landmark_selected:
+ xr_landmark_active_type_update(self, context)
+
+
+def xr_landmark_camera_update(self, context):
+ landmark_selected = VRLandmark.get_selected_landmark(context)
+ landmark_active = VRLandmark.get_active_landmark(context)
+
+ # Only update session settings data if the changed landmark is actually the active one.
+ if landmark_active == landmark_selected:
+ xr_landmark_active_camera_update(self, context)
+
+
+def xr_landmark_base_pose_location_update(self, context):
+ landmark_selected = VRLandmark.get_selected_landmark(context)
+ landmark_active = VRLandmark.get_active_landmark(context)
+
+ # Only update session settings data if the changed landmark is actually the active one.
+ if landmark_active == landmark_selected:
+ xr_landmark_active_base_pose_location_update(self, context)
+
+
+def xr_landmark_base_pose_angle_update(self, context):
+ landmark_selected = VRLandmark.get_selected_landmark(context)
+ landmark_active = VRLandmark.get_active_landmark(context)
+
+ # Only update session settings data if the changed landmark is actually the active one.
+ if landmark_active == landmark_selected:
+ xr_landmark_active_base_pose_angle_update(self, context)
+
+
+def xr_landmark_camera_object_poll(self, object):
+ return object.type == 'CAMERA'
+
+
+def xr_landmark_active_update(self, context):
+ xr_landmark_active_type_update(self, context)
+ xr_landmark_active_camera_update(self, context)
+ xr_landmark_active_base_pose_location_update(self, context)
+ xr_landmark_active_base_pose_angle_update(self, context)
+
+
+class VRLandmark(bpy.types.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 definied 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(bpy.types.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="")
+ props = layout.operator("view3d.vr_landmark_activate", text="", icon=('SOLO_ON' if index ==
+ landmark_active_idx else 'SOLO_OFF'))
+ props.index = index
+
+
+class VIEW3D_PT_vr_landmarks(bpy.types.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="")
+
+ 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(bpy.types.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, "show_floor", text="Floor")
+ layout.prop(session_settings, "show_annotation", text="Annotations")
+
+ layout.separator()
+
+ col = layout.column(align=True)
+ col.prop(session_settings, "clip_start", text="Clip Start")
+ col.prop(session_settings, "clip_end", text="End")
+
+ layout.separator()
+
+ layout.prop(session_settings, "use_positional_tracking")
+
+
+class VIEW3D_OT_vr_landmark_add(bpy.types.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_remove(bpy.types.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_vr_landmark_activate(bpy.types.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_session_shading(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_category = "VR"
+ bl_label = "Shading"
+
+ def draw(self, context):
+ layout = self.layout
+ session_settings = context.window_manager.xr_session_settings
+ shading = session_settings.shading
+
+ layout.prop(shading, "type", text="")
+
+
+class VIEW3D_PT_vr_session_shading_lighting(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_category = "VR"
+ bl_label = VIEW3D_PT_shading_lighting.bl_label
+ bl_parent_id = "VIEW3D_PT_vr_session_shading"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ session_settings = context.window_manager.xr_session_settings
+ shading = session_settings.shading
+
+ if VIEW3D_PT_shading_lighting.poll_ex(context, shading):
+ VIEW3D_PT_shading_lighting.draw_ex(self, context, shading)
+
+
+class VIEW3D_PT_vr_session_shading_color(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_category = "VR"
+ bl_label = VIEW3D_PT_shading_color.bl_label
+ bl_parent_id = "VIEW3D_PT_vr_session_shading"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ session_settings = context.window_manager.xr_session_settings
+ shading = session_settings.shading
+
+ if VIEW3D_PT_shading_color.poll_ex(context, shading):
+ VIEW3D_PT_shading_color.draw_ex(self, context, shading)
+
+
+class VIEW3D_PT_vr_session_shading_options(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_category = "VR"
+ bl_label = VIEW3D_PT_shading_options.bl_label
+ bl_parent_id = "VIEW3D_PT_vr_session_shading"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ session_settings = context.window_manager.xr_session_settings
+ shading = session_settings.shading
+
+ if VIEW3D_PT_shading_options.poll_ex(context, shading):
+ VIEW3D_PT_shading_options.draw_ex(self, context, shading)
+
+
+class VIEW3D_PT_vr_viewport_feedback(bpy.types.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
+
+ layout.prop(view3d.shading, "vr_show_virtual_camera")
+ layout.prop(view3d, "show_as_xr_session_mirror")
+
+
+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(GizmoGroup):
+ bl_idname = "VIEW3D_GGT_vr_viewer"
+ bl_label = "VR Viewer Indicator"
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'WINDOW'
+ bl_options = {'3D', 'PERSISTENT', 'SCALE', 'CONTINUOUS_REDRAW'}
+
+ @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.show_as_xr_session_mirror
+
+ def _get_viewer_matrix(self, context):
+ from mathutils import Matrix, Quaternion
+
+ wm = context.window_manager
+
+ loc = wm.xr_session_state.viewer_location
+ rot = wm.xr_session_state.viewer_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_matrix(context)
+
+
+classes = (
+ VIEW3D_PT_vr_session,
+ VIEW3D_PT_vr_session_shading,
+ VIEW3D_PT_vr_session_shading_lighting,
+ VIEW3D_PT_vr_session_shading_color,
+ VIEW3D_PT_vr_session_shading_options,
+ VIEW3D_PT_vr_landmarks,
+ VIEW3D_PT_vr_viewport_feedback,
+
+ VRLandmark,
+ VIEW3D_UL_vr_landmarks,
+
+ VIEW3D_OT_vr_landmark_add,
+ VIEW3D_OT_vr_landmark_remove,
+ VIEW3D_OT_vr_landmark_activate,
+
+ VIEW3D_GT_vr_camera_cone,
+ VIEW3D_GGT_vr_viewer,
+)
+
+
+def register():
+ if not bpy.app.build_options.xr_openxr:
+ 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 Virtual Camera"
+ )
+
+ bpy.app.handlers.load_post.append(ensure_default_vr_landmark)
+
+def unregister():
+ if not bpy.app.build_options.xr_openxr:
+ 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
+
+ bpy.app.handlers.load_post.remove(ensure_default_vr_landmark)
+
+if __name__ == "__main__":
+ register()