Files
blender-addons/viewport_vr_preview/properties.py
Peter Kim 823910c50d 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
2021-10-26 13:45:16 +09:00

245 lines
8.1 KiB
Python

# ##### 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)