1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
22 from bpy
.types
import (
31 from bpy
.props
import (
36 from bpy
.app
.handlers
import persistent
39 "name": "VR Scene Inspection",
40 "author": "Julian Eisel (Severin), Sebastian Koenig",
42 "blender": (2, 90, 0),
43 "location": "3D View > Sidebar > VR",
44 "description": ("View the viewport with virtual reality glasses "
45 "(head-mounted displays)"),
46 "support": "OFFICIAL",
47 "warning": "This is an early, limited preview of in development "
48 "VR support for Blender.",
49 "doc_url": "{BLENDER_MANUAL_URL}/addons/3d_view/vr_scene_inspection.html",
50 "category": "3D View",
55 def ensure_default_vr_landmark(context
: bpy
.context
):
56 # Ensure there's a default landmark (scene camera by default).
57 landmarks
= bpy
.context
.scene
.vr_landmarks
60 landmarks
[0].type = 'SCENE_CAMERA'
63 def xr_landmark_active_type_update(self
, context
):
64 wm
= context
.window_manager
65 session_settings
= wm
.xr_session_settings
66 landmark_active
= VRLandmark
.get_active_landmark(context
)
68 # Update session's base pose type to the matching type.
69 if landmark_active
.type == 'SCENE_CAMERA':
70 session_settings
.base_pose_type
= 'SCENE_CAMERA'
71 elif landmark_active
.type == 'USER_CAMERA':
72 session_settings
.base_pose_type
= 'OBJECT'
73 elif landmark_active
.type == 'CUSTOM':
74 session_settings
.base_pose_type
= 'CUSTOM'
77 def xr_landmark_active_camera_update(self
, context
):
78 session_settings
= context
.window_manager
.xr_session_settings
79 landmark_active
= VRLandmark
.get_active_landmark(context
)
81 # Update the anchor object to the (new) camera of this landmark.
82 session_settings
.base_pose_object
= landmark_active
.base_pose_camera
85 def xr_landmark_active_base_pose_location_update(self
, context
):
86 session_settings
= context
.window_manager
.xr_session_settings
87 landmark_active
= VRLandmark
.get_active_landmark(context
)
89 session_settings
.base_pose_location
= landmark_active
.base_pose_location
92 def xr_landmark_active_base_pose_angle_update(self
, context
):
93 session_settings
= context
.window_manager
.xr_session_settings
94 landmark_active
= VRLandmark
.get_active_landmark(context
)
96 session_settings
.base_pose_angle
= landmark_active
.base_pose_angle
99 def xr_landmark_type_update(self
, context
):
100 landmark_selected
= VRLandmark
.get_selected_landmark(context
)
101 landmark_active
= VRLandmark
.get_active_landmark(context
)
103 # Only update session settings data if the changed landmark is actually
105 if landmark_active
== landmark_selected
:
106 xr_landmark_active_type_update(self
, context
)
109 def xr_landmark_camera_update(self
, context
):
110 landmark_selected
= VRLandmark
.get_selected_landmark(context
)
111 landmark_active
= VRLandmark
.get_active_landmark(context
)
113 # Only update session settings data if the changed landmark is actually
115 if landmark_active
== landmark_selected
:
116 xr_landmark_active_camera_update(self
, context
)
119 def xr_landmark_base_pose_location_update(self
, context
):
120 landmark_selected
= VRLandmark
.get_selected_landmark(context
)
121 landmark_active
= VRLandmark
.get_active_landmark(context
)
123 # Only update session settings data if the changed landmark is actually
125 if landmark_active
== landmark_selected
:
126 xr_landmark_active_base_pose_location_update(self
, context
)
129 def xr_landmark_base_pose_angle_update(self
, context
):
130 landmark_selected
= VRLandmark
.get_selected_landmark(context
)
131 landmark_active
= VRLandmark
.get_active_landmark(context
)
133 # Only update session settings data if the changed landmark is actually
135 if landmark_active
== landmark_selected
:
136 xr_landmark_active_base_pose_angle_update(self
, context
)
139 def xr_landmark_camera_object_poll(self
, object):
140 return object.type == 'CAMERA'
143 def xr_landmark_active_update(self
, context
):
144 wm
= context
.window_manager
146 xr_landmark_active_type_update(self
, context
)
147 xr_landmark_active_camera_update(self
, context
)
148 xr_landmark_active_base_pose_location_update(self
, context
)
149 xr_landmark_active_base_pose_angle_update(self
, context
)
151 if wm
.xr_session_state
:
152 wm
.xr_session_state
.reset_to_base_pose(context
)
155 class VIEW3D_MT_landmark_menu(Menu
):
156 bl_label
= "Landmark Controls"
158 def draw(self
, _context
):
161 layout
.operator("view3d.vr_landmark_from_camera")
162 layout
.operator("view3d.update_vr_landmark")
164 layout
.operator("view3d.cursor_to_vr_landmark")
165 layout
.operator("view3d.camera_to_vr_landmark")
166 layout
.operator("view3d.add_camera_from_vr_landmark")
169 class VRLandmark(PropertyGroup
):
170 name
: bpy
.props
.StringProperty(
174 type: bpy
.props
.EnumProperty(
177 ('SCENE_CAMERA', "Scene Camera",
178 "Use scene's currently active camera to define the VR view base "
179 "location and rotation"),
180 ('USER_CAMERA', "Custom Camera",
181 "Use an existing camera to define the VR view base location and "
183 ('CUSTOM', "Custom Pose",
184 "Allow a manually defined position and rotation to be used as "
185 "the VR view base pose"),
187 default
='SCENE_CAMERA',
188 update
=xr_landmark_type_update
,
190 base_pose_camera
: bpy
.props
.PointerProperty(
192 type=bpy
.types
.Object
,
193 poll
=xr_landmark_camera_object_poll
,
194 update
=xr_landmark_camera_update
,
196 base_pose_location
: bpy
.props
.FloatVectorProperty(
197 name
="Base Pose Location",
198 subtype
='TRANSLATION',
199 update
=xr_landmark_base_pose_location_update
,
202 base_pose_angle
: bpy
.props
.FloatProperty(
203 name
="Base Pose Angle",
205 update
=xr_landmark_base_pose_angle_update
,
209 def get_selected_landmark(context
):
210 scene
= context
.scene
211 landmarks
= scene
.vr_landmarks
214 None if (len(landmarks
) <
215 1) else landmarks
[scene
.vr_landmarks_selected
]
219 def get_active_landmark(context
):
220 scene
= context
.scene
221 landmarks
= scene
.vr_landmarks
224 None if (len(landmarks
) <
225 1) else landmarks
[scene
.vr_landmarks_active
]
229 class VIEW3D_UL_vr_landmarks(UIList
):
230 def draw_item(self
, context
, layout
, _data
, item
, icon
, _active_data
,
231 _active_propname
, index
):
233 landmark_active_idx
= context
.scene
.vr_landmarks_active
235 layout
.emboss
= 'NONE'
237 layout
.prop(landmark
, "name", text
="")
240 'RADIOBUT_ON' if (index
== landmark_active_idx
) else 'RADIOBUT_OFF'
242 props
= layout
.operator(
243 "view3d.vr_landmark_activate", text
="", icon
=icon
)
247 class VIEW3D_PT_vr_landmarks(Panel
):
248 bl_space_type
= 'VIEW_3D'
249 bl_region_type
= 'UI'
251 bl_label
= "Landmarks"
252 bl_options
= {'DEFAULT_CLOSED'}
254 def draw(self
, context
):
256 scene
= context
.scene
257 landmark_selected
= VRLandmark
.get_selected_landmark(context
)
259 layout
.use_property_split
= True
260 layout
.use_property_decorate
= False # No animation.
264 row
.template_list("VIEW3D_UL_vr_landmarks", "", scene
, "vr_landmarks",
265 scene
, "vr_landmarks_selected", rows
=3)
267 col
= row
.column(align
=True)
268 col
.operator("view3d.vr_landmark_add", icon
='ADD', text
="")
269 col
.operator("view3d.vr_landmark_remove", icon
='REMOVE', text
="")
270 col
.operator("view3d.vr_landmark_from_session", icon
='PLUS', text
="")
272 col
.menu("VIEW3D_MT_landmark_menu", icon
='DOWNARROW_HLT', text
="")
274 if landmark_selected
:
275 layout
.prop(landmark_selected
, "type")
277 if landmark_selected
.type == 'USER_CAMERA':
278 layout
.prop(landmark_selected
, "base_pose_camera")
279 elif landmark_selected
.type == 'CUSTOM':
280 layout
.prop(landmark_selected
,
281 "base_pose_location", text
="Location")
282 layout
.prop(landmark_selected
,
283 "base_pose_angle", text
="Angle")
286 class VIEW3D_PT_vr_session_view(Panel
):
287 bl_space_type
= 'VIEW_3D'
288 bl_region_type
= 'UI'
292 def draw(self
, context
):
294 session_settings
= context
.window_manager
.xr_session_settings
296 layout
.use_property_split
= True
297 layout
.use_property_decorate
= False # No animation.
299 col
= layout
.column(align
=True, heading
="Show")
300 col
.prop(session_settings
, "show_floor", text
="Floor")
301 col
.prop(session_settings
, "show_annotation", text
="Annotations")
303 col
= layout
.column(align
=True)
304 col
.prop(session_settings
, "clip_start", text
="Clip Start")
305 col
.prop(session_settings
, "clip_end", text
="End")
308 class VIEW3D_PT_vr_session(Panel
):
309 bl_space_type
= 'VIEW_3D'
310 bl_region_type
= 'UI'
312 bl_label
= "VR Session"
314 def draw(self
, context
):
316 session_settings
= context
.window_manager
.xr_session_settings
318 layout
.use_property_split
= True
319 layout
.use_property_decorate
= False # No animation.
321 is_session_running
= bpy
.types
.XrSessionState
.is_running(context
)
323 # Using SNAP_FACE because it looks like a stop icon -- I shouldn't
324 # have commit rights...
326 ("Start VR Session", 'PLAY') if not is_session_running
else (
327 "Stop VR Session", 'SNAP_FACE')
329 layout
.operator("wm.xr_session_toggle",
330 text
=toggle_info
[0], icon
=toggle_info
[1])
334 layout
.prop(session_settings
, "use_positional_tracking")
337 class VIEW3D_PT_vr_info(bpy
.types
.Panel
):
338 bl_space_type
= 'VIEW_3D'
339 bl_region_type
= 'UI'
344 def poll(cls
, context
):
345 return not bpy
.app
.build_options
.xr_openxr
347 def draw(self
, context
):
349 layout
.label(icon
='ERROR', text
="Built without VR/OpenXR features")
352 class VIEW3D_OT_vr_landmark_add(Operator
):
353 bl_idname
= "view3d.vr_landmark_add"
354 bl_label
= "Add VR Landmark"
355 bl_description
= "Add a new VR landmark to the list and select it"
356 bl_options
= {'UNDO', 'REGISTER'}
358 def execute(self
, context
):
359 scene
= context
.scene
360 landmarks
= scene
.vr_landmarks
364 # select newly created set
365 scene
.vr_landmarks_selected
= len(landmarks
) - 1
370 class VIEW3D_OT_vr_landmark_from_camera(Operator
):
371 bl_idname
= "view3d.vr_landmark_from_camera"
372 bl_label
= "Add VR Landmark from camera"
373 bl_description
= "Add a new VR landmark from the active camera object to the list and select it"
374 bl_options
= {'UNDO', 'REGISTER'}
377 def poll(cls
, context
):
380 vl_objects
= bpy
.context
.view_layer
.objects
381 if vl_objects
.active
and vl_objects
.active
.type == 'CAMERA':
385 def execute(self
, context
):
386 scene
= context
.scene
387 landmarks
= scene
.vr_landmarks
388 cam
= context
.view_layer
.objects
.active
390 lm
.type = 'USER_CAMERA'
391 lm
.base_pose_camera
= cam
392 lm
.name
= "LM_" + cam
.name
394 # select newly created set
395 scene
.vr_landmarks_selected
= len(landmarks
) - 1
400 class VIEW3D_OT_vr_landmark_from_session(Operator
):
401 bl_idname
= "view3d.vr_landmark_from_session"
402 bl_label
= "Add VR Landmark from session"
403 bl_description
= "Add VR landmark from the viewer pose of the running VR session to the list and select it"
404 bl_options
= {'UNDO', 'REGISTER'}
407 def poll(cls
, context
):
408 return bpy
.types
.XrSessionState
.is_running(context
)
411 def _calc_landmark_angle_from_viewer_rotation(rot
):
412 from mathutils
import Vector
414 # We want an angle around Z based on the current viewer rotation. Idea
415 # is to create a vector from the viewer rotation, project that onto a
416 # Z-Up plane and use the resulting vector to get an angle around Z.
418 view_rot_vec
= Vector((0, 0, 1))
419 view_rot_vec
.rotate(rot
)
420 angle_vec
= view_rot_vec
- view_rot_vec
.project(Vector((0, 0, 1)))
422 # We could probably use a 3D version of Vector.angle_signed() here, but
423 # that's not available. So manually calculate it via a quaternion delta.
424 forward_vec
= Vector((0, -1, 0))
425 diff
= angle_vec
.rotation_difference(forward_vec
)
427 return diff
.angle
* -diff
.axis
[2]
429 def execute(self
, context
):
430 scene
= context
.scene
431 landmarks
= scene
.vr_landmarks
432 wm
= context
.window_manager
436 scene
.vr_landmarks_selected
= len(landmarks
) - 1
438 loc
= wm
.xr_session_state
.viewer_pose_location
439 rot
= wm
.xr_session_state
.viewer_pose_rotation
440 angle
= self
._calc
_landmark
_angle
_from
_viewer
_rotation
(rot
)
442 lm
.base_pose_location
= loc
443 lm
.base_pose_angle
= angle
448 class VIEW3D_OT_update_vr_landmark(Operator
):
449 bl_idname
= "view3d.update_vr_landmark"
450 bl_label
= "Update Custom VR Landmark"
451 bl_description
= "Update the selected landmark from the current viewer pose in the VR session"
452 bl_options
= {'UNDO', 'REGISTER'}
455 def poll(cls
, context
):
456 selected_landmark
= VRLandmark
.get_selected_landmark(context
)
457 return bpy
.types
.XrSessionState
.is_running(context
) and selected_landmark
.type == 'CUSTOM'
459 def execute(self
, context
):
460 wm
= context
.window_manager
462 lm
= VRLandmark
.get_selected_landmark(context
)
464 loc
= wm
.xr_session_state
.viewer_pose_location
465 rot
= wm
.xr_session_state
.viewer_pose_rotation
.to_euler()
467 lm
.base_pose_location
= loc
468 lm
.base_pose_angle
= rot
470 # Re-activate the landmark to trigger viewer reset and flush landmark settings to the session settings.
471 xr_landmark_active_update(None, context
)
476 class VIEW3D_OT_vr_landmark_remove(Operator
):
477 bl_idname
= "view3d.vr_landmark_remove"
478 bl_label
= "Remove VR Landmark"
479 bl_description
= "Delete the selected VR landmark from the list"
480 bl_options
= {'UNDO', 'REGISTER'}
482 def execute(self
, context
):
483 scene
= context
.scene
484 landmarks
= scene
.vr_landmarks
486 if len(landmarks
) > 1:
487 landmark_selected_idx
= scene
.vr_landmarks_selected
488 landmarks
.remove(landmark_selected_idx
)
490 scene
.vr_landmarks_selected
-= 1
495 class VIEW3D_OT_cursor_to_vr_landmark(Operator
):
496 bl_idname
= "view3d.cursor_to_vr_landmark"
497 bl_label
= "Cursor to VR Landmark"
498 bl_description
= "Move the 3D Cursor to the selected VR Landmark"
499 bl_options
= {'UNDO', 'REGISTER'}
502 def poll(cls
, context
):
503 lm
= VRLandmark
.get_selected_landmark(context
)
504 if lm
.type == 'SCENE_CAMERA':
505 return context
.scene
.camera
is not None
506 elif lm
.type == 'USER_CAMERA':
507 return lm
.base_pose_camera
is not None
511 def execute(self
, context
):
512 scene
= context
.scene
513 lm
= VRLandmark
.get_selected_landmark(context
)
514 if lm
.type == 'SCENE_CAMERA':
515 lm_pos
= scene
.camera
.location
516 elif lm
.type == 'USER_CAMERA':
517 lm_pos
= lm
.base_pose_camera
.location
519 lm_pos
= lm
.base_pose_location
520 scene
.cursor
.location
= lm_pos
525 class VIEW3d_OT_add_camera_from_vr_landmark(Operator
):
526 bl_idname
= "view3d.add_camera_from_vr_landmark"
527 bl_label
= "New Camera from VR Landmark"
528 bl_description
= "Create a new Camera from the selected VR Landmark"
529 bl_options
= {'UNDO', 'REGISTER'}
531 def execute(self
, context
):
534 scene
= context
.scene
535 lm
= VRLandmark
.get_selected_landmark(context
)
537 cam
= bpy
.data
.cameras
.new("Camera_" + lm
.name
)
538 new_cam
= bpy
.data
.objects
.new("Camera_" + lm
.name
, cam
)
539 scene
.collection
.objects
.link(new_cam
)
540 angle
= lm
.base_pose_angle
541 new_cam
.location
= lm
.base_pose_location
542 new_cam
.rotation_euler
= (math
.pi
, 0, angle
)
547 class VIEW3D_OT_camera_to_vr_landmark(Operator
):
548 bl_idname
= "view3d.camera_to_vr_landmark"
549 bl_label
= "Scene Camera to VR Landmark"
550 bl_description
= "Position the scene camera at the selected landmark"
551 bl_options
= {'UNDO', 'REGISTER'}
554 def poll(cls
, context
):
555 return context
.scene
.camera
is not None
557 def execute(self
, context
):
560 scene
= context
.scene
561 lm
= VRLandmark
.get_selected_landmark(context
)
564 angle
= lm
.base_pose_angle
565 cam
.location
= lm
.base_pose_location
566 cam
.rotation_euler
= (math
.pi
/ 2, 0, angle
)
571 class VIEW3D_OT_vr_landmark_activate(Operator
):
572 bl_idname
= "view3d.vr_landmark_activate"
573 bl_label
= "Activate VR Landmark"
574 bl_description
= "Change to the selected VR landmark from the list"
575 bl_options
= {'UNDO', 'REGISTER'}
582 def execute(self
, context
):
583 scene
= context
.scene
585 if self
.index
>= len(scene
.vr_landmarks
):
588 scene
.vr_landmarks_active
= (
589 self
.index
if self
.properties
.is_property_set(
590 "index") else scene
.vr_landmarks_selected
596 class VIEW3D_PT_vr_viewport_feedback(Panel
):
597 bl_space_type
= 'VIEW_3D'
598 bl_region_type
= 'UI'
600 bl_label
= "Viewport Feedback"
601 bl_options
= {'DEFAULT_CLOSED'}
603 def draw(self
, context
):
605 view3d
= context
.space_data
607 col
= layout
.column(align
=True)
608 col
.label(icon
='ERROR', text
="Note:")
609 col
.label(text
="Settings here may have a significant")
610 col
.label(text
="performance impact!")
614 layout
.prop(view3d
.shading
, "vr_show_virtual_camera")
615 layout
.prop(view3d
.shading
, "vr_show_landmarks")
616 layout
.prop(view3d
, "mirror_xr_session")
619 class VIEW3D_GT_vr_camera_cone(Gizmo
):
620 bl_idname
= "VIEW_3D_GT_vr_camera_cone"
624 def draw(self
, context
):
627 if not hasattr(self
, "frame_shape"):
630 frame_shape_verts
= (
631 (-aspect
[0], -aspect
[1], -1.0),
632 (aspect
[0], -aspect
[1], -1.0),
633 (aspect
[0], aspect
[1], -1.0),
634 (-aspect
[0], aspect
[1], -1.0),
636 lines_shape_verts
= (
638 frame_shape_verts
[0],
640 frame_shape_verts
[1],
642 frame_shape_verts
[2],
644 frame_shape_verts
[3],
647 self
.frame_shape
= self
.new_custom_shape(
648 'LINE_LOOP', frame_shape_verts
)
649 self
.lines_shape
= self
.new_custom_shape(
650 'LINES', lines_shape_verts
)
652 # Ensure correct GL state (otherwise other gizmos might mess that up)
654 bgl
.glEnable(bgl
.GL_BLEND
)
656 self
.draw_custom_shape(self
.frame_shape
)
657 self
.draw_custom_shape(self
.lines_shape
)
660 class VIEW3D_GGT_vr_viewer_pose(GizmoGroup
):
661 bl_idname
= "VIEW3D_GGT_vr_viewer_pose"
662 bl_label
= "VR Viewer Pose Indicator"
663 bl_space_type
= 'VIEW_3D'
664 bl_region_type
= 'WINDOW'
665 bl_options
= {'3D', 'PERSISTENT', 'SCALE', 'VR_REDRAWS'}
668 def poll(cls
, context
):
669 view3d
= context
.space_data
671 view3d
.shading
.vr_show_virtual_camera
and
672 bpy
.types
.XrSessionState
.is_running(context
) and
673 not view3d
.mirror_xr_session
677 def _get_viewer_pose_matrix(context
):
678 from mathutils
import Matrix
, Quaternion
680 wm
= context
.window_manager
682 loc
= wm
.xr_session_state
.viewer_pose_location
683 rot
= wm
.xr_session_state
.viewer_pose_rotation
685 rotmat
= Matrix
.Identity(3)
688 transmat
= Matrix
.Translation(loc
)
690 return transmat
@ rotmat
692 def setup(self
, context
):
693 gizmo
= self
.gizmos
.new(VIEW3D_GT_vr_camera_cone
.bl_idname
)
694 gizmo
.aspect
= 1 / 3, 1 / 4
696 gizmo
.color
= gizmo
.color_highlight
= 0.2, 0.6, 1.0
701 def draw_prepare(self
, context
):
702 self
.gizmo
.matrix_basis
= self
._get
_viewer
_pose
_matrix
(context
)
705 class VIEW3D_GGT_vr_landmarks(GizmoGroup
):
706 bl_idname
= "VIEW3D_GGT_vr_landmarks"
707 bl_label
= "VR Landmark Indicators"
708 bl_space_type
= 'VIEW_3D'
709 bl_region_type
= 'WINDOW'
710 bl_options
= {'3D', 'PERSISTENT', 'SCALE'}
713 def poll(cls
, context
):
714 view3d
= context
.space_data
716 view3d
.shading
.vr_show_landmarks
719 def setup(self
, context
):
722 def draw_prepare(self
, context
):
723 # first delete the old gizmos
724 for g
in self
.gizmos
:
725 self
.gizmos
.remove(g
)
727 from math
import radians
728 from mathutils
import Matrix
, Euler
729 scene
= context
.scene
730 landmarks
= scene
.vr_landmarks
733 if ((lm
.type == 'SCENE_CAMERA' and not scene
.camera
) or
734 (lm
.type == 'USER_CAMERA' and not lm
.base_pose_camera
)):
737 gizmo
= self
.gizmos
.new(VIEW3D_GT_vr_camera_cone
.bl_idname
)
738 gizmo
.aspect
= 1 / 3, 1 / 4
740 gizmo
.color
= gizmo
.color_highlight
= 0.2, 1.0, 0.6
745 if lm
.type == 'SCENE_CAMERA':
747 lm_mat
= cam
.matrix_world
if cam
else Matrix
.Identity(4)
748 elif lm
.type == 'USER_CAMERA':
749 lm_mat
= lm
.base_pose_camera
.matrix_world
751 angle
= lm
.base_pose_angle
752 raw_rot
= Euler((radians(90.0), 0, angle
))
754 rotmat
= Matrix
.Identity(3)
755 rotmat
.rotate(raw_rot
)
758 transmat
= Matrix
.Translation(lm
.base_pose_location
)
760 lm_mat
= transmat
@ rotmat
762 self
.gizmo
.matrix_basis
= lm_mat
766 VIEW3D_PT_vr_session
,
767 VIEW3D_PT_vr_session_view
,
768 VIEW3D_PT_vr_landmarks
,
769 VIEW3D_PT_vr_viewport_feedback
,
772 VIEW3D_UL_vr_landmarks
,
773 VIEW3D_MT_landmark_menu
,
775 VIEW3D_OT_vr_landmark_add
,
776 VIEW3D_OT_vr_landmark_remove
,
777 VIEW3D_OT_vr_landmark_activate
,
778 VIEW3D_OT_vr_landmark_from_session
,
779 VIEW3d_OT_add_camera_from_vr_landmark
,
780 VIEW3D_OT_camera_to_vr_landmark
,
781 VIEW3D_OT_vr_landmark_from_camera
,
782 VIEW3D_OT_cursor_to_vr_landmark
,
783 VIEW3D_OT_update_vr_landmark
,
785 VIEW3D_GT_vr_camera_cone
,
786 VIEW3D_GGT_vr_viewer_pose
,
787 VIEW3D_GGT_vr_landmarks
,
792 if not bpy
.app
.build_options
.xr_openxr
:
793 bpy
.utils
.register_class(VIEW3D_PT_vr_info
)
797 bpy
.utils
.register_class(cls
)
799 bpy
.types
.Scene
.vr_landmarks
= CollectionProperty(
803 bpy
.types
.Scene
.vr_landmarks_selected
= IntProperty(
804 name
="Selected Landmark"
806 bpy
.types
.Scene
.vr_landmarks_active
= IntProperty(
807 update
=xr_landmark_active_update
,
809 # View3DShading is the only per 3D-View struct with custom property
810 # support, so "abusing" that to get a per 3D-View option.
811 bpy
.types
.View3DShading
.vr_show_virtual_camera
= BoolProperty(
812 name
="Show VR Camera"
814 bpy
.types
.View3DShading
.vr_show_landmarks
= BoolProperty(
815 name
="Show Landmarks"
818 bpy
.app
.handlers
.load_post
.append(ensure_default_vr_landmark
)
822 if not bpy
.app
.build_options
.xr_openxr
:
823 bpy
.utils
.unregister_class(VIEW3D_PT_vr_info
)
827 bpy
.utils
.unregister_class(cls
)
829 del bpy
.types
.Scene
.vr_landmarks
830 del bpy
.types
.Scene
.vr_landmarks_selected
831 del bpy
.types
.Scene
.vr_landmarks_active
832 del bpy
.types
.View3DShading
.vr_show_virtual_camera
833 del bpy
.types
.View3DShading
.vr_show_landmarks
835 bpy
.app
.handlers
.load_post
.remove(ensure_default_vr_landmark
)
838 if __name__
== "__main__":