VR: Move actions from keymaps to actionmaps

Among the changes, the VR add-on preferences were removed, meaning
that actions can no longer be stored in user preferences. However,
in addition to now being saved to blend files, actions can also be
imported/exported via python config files. The default config is
described in configs/default.py.
This commit is contained in:
Peter Kim 2021-03-07 21:47:13 +09:00
parent 0d1b80b5e2
commit a26b930830
7 changed files with 1328 additions and 1863 deletions

View File

@ -20,9 +20,9 @@
bl_info = {
"name": "VR Scene Inspection",
"author": "Julian Eisel (Severin), Sebastian Koenig",
"version": (0, 9, 0),
"blender": (2, 90, 0),
"author": "Julian Eisel (Severin), Sebastian Koenig, Peter Kim (muxed-reality)",
"version": (0, 10, 0),
"blender": (2, 93, 0),
"location": "3D View > Sidebar > VR",
"description": ("View the viewport with virtual reality glasses "
"(head-mounted displays)"),
@ -33,6 +33,7 @@ bl_info = {
"category": "3D View",
}
if "bpy" in locals():
import importlib
importlib.reload(main)
@ -41,11 +42,7 @@ else:
from . import main, defaults
import bpy
from bpy.props import (
CollectionProperty,
IntProperty,
BoolProperty,
)
classes = (
main.VIEW3D_PT_vr_session,
@ -69,8 +66,6 @@ classes = (
main.VIEW3D_OT_cursor_to_vr_landmark,
main.VIEW3D_OT_update_vr_landmark,
main.VRAction,
main.VRActionSet,
main.VIEW3D_UL_vr_action_sets,
main.VIEW3D_MT_vr_action_set_menu,
main.VIEW3D_UL_vr_actions,
@ -79,12 +74,13 @@ classes = (
main.VIEW3D_OT_vr_action_set_add,
main.VIEW3D_OT_vr_action_set_remove,
main.VIEW3D_OT_vr_action_set_activate,
main.VIEW3D_OT_vr_action_sets_load_from_prefs,
main.VIEW3D_OT_vr_action_set_save_to_prefs,
main.VIEW3D_OT_vr_action_sets_import,
main.VIEW3D_OT_vr_action_sets_export,
main.VIEW3D_OT_vr_action_set_copy,
main.VIEW3D_OT_vr_action_sets_clear,
main.VIEW3D_OT_vr_action_add,
main.VIEW3D_OT_vr_action_remove,
main.VIEW3D_OT_vr_action_copy,
main.VIEW3D_OT_vr_actions_clear,
main.VIEW3D_GT_vr_camera_cone,
@ -92,21 +88,6 @@ classes = (
main.VIEW3D_GGT_vr_viewer_pose,
main.VIEW3D_GGT_vr_controller_poses,
main.VIEW3D_GGT_vr_landmarks,
main.VRPreferences,
#main.PREFERENCES_PT_vr_actions,
main.PREFERENCES_UL_vr_action_sets,
main.PREFERENCES_MT_vr_action_set_menu,
main.PREFERENCES_UL_vr_actions,
main.PREFERENCES_MT_vr_action_menu,
main.PREFERENCES_OT_vr_action_set_add,
main.PREFERENCES_OT_vr_action_set_remove,
main.PREFERENCES_OT_vr_action_set_copy,
main.PREFERENCES_OT_vr_action_sets_clear,
main.PREFERENCES_OT_vr_action_add,
main.PREFERENCES_OT_vr_action_remove,
main.PREFERENCES_OT_vr_actions_clear,
)
@ -118,27 +99,16 @@ def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.vr_landmarks = CollectionProperty(
bpy.types.Scene.vr_landmarks = bpy.props.CollectionProperty(
name="Landmark",
type=main.VRLandmark,
)
bpy.types.Scene.vr_landmarks_selected = IntProperty(
bpy.types.Scene.vr_landmarks_selected = bpy.props.IntProperty(
name="Selected Landmark"
)
bpy.types.Scene.vr_landmarks_active = IntProperty(
bpy.types.Scene.vr_landmarks_active = bpy.props.IntProperty(
update=main.vr_landmark_active_update,
)
bpy.types.Scene.vr_action_sets = CollectionProperty(
name="Action Set",
type=main.VRActionSet,
)
bpy.types.Scene.vr_action_sets_selected = IntProperty(
name="Selected Action Set",
)
bpy.types.Scene.vr_action_sets_active = IntProperty(
default=0,
update=main.vr_action_set_active_update,
)
bpy.types.Scene.vr_headset_object = bpy.props.PointerProperty(
name="Headset Object",
type=bpy.types.Object,
@ -156,49 +126,32 @@ def register():
)
# 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(
bpy.types.View3DShading.vr_show_virtual_camera = bpy.props.BoolProperty(
name="Show VR Camera"
)
bpy.types.View3DShading.vr_show_controllers = BoolProperty(
bpy.types.View3DShading.vr_show_controllers = bpy.props.BoolProperty(
name="Show VR Controllers"
)
bpy.types.View3DShading.vr_show_landmarks = BoolProperty(
bpy.types.View3DShading.vr_show_landmarks = bpy.props.BoolProperty(
name="Show Landmarks"
)
bpy.app.handlers.load_post.append(main.vr_ensure_default_landmark)
bpy.app.handlers.load_post.append(main.vr_load_action_properties)
bpy.app.handlers.load_post.append(defaults.vr_load_default_action_sets)
bpy.app.handlers.save_post.append(main.vr_save_action_properties)
bpy.app.handlers.load_post.append(defaults.vr_load_default_actionmaps)
bpy.app.handlers.xr_session_start_pre.append(main.vr_create_actions)
# Register add-on key map.
kc = bpy.context.window_manager.keyconfigs.addon
if kc:
kc.keymaps.new(name="XR Session", space_type='EMPTY', region_type='XR')
def unregister():
if not bpy.app.build_options.xr_openxr:
bpy.utils.unregister_class(main.VIEW3D_PT_vr_info)
return
# Unregister add-on key map.
kc = bpy.context.window_manager.keyconfigs.addon
if kc:
km = kc.keymaps.find("XR Session", space_type='EMPTY', region_type='XR')
if km:
kc.keymaps.remove(km)
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.Scene.vr_action_sets
del bpy.types.Scene.vr_action_sets_selected
del bpy.types.Scene.vr_action_sets_active
del bpy.types.Scene.vr_headset_object
del bpy.types.Scene.vr_controller0_object
del bpy.types.Scene.vr_controller1_object
@ -207,7 +160,5 @@ def unregister():
del bpy.types.View3DShading.vr_show_landmarks
bpy.app.handlers.load_post.remove(main.vr_ensure_default_landmark)
bpy.app.handlers.load_post.remove(main.vr_load_action_properties)
bpy.app.handlers.load_post.remove(defaults.vr_load_default_action_sets)
bpy.app.handlers.save_post.remove(main.vr_save_action_properties)
bpy.app.handlers.load_post.remove(defaults.vr_load_default_actionmaps)
bpy.app.handlers.xr_session_start_pre.remove(main.vr_create_actions)

View File

@ -0,0 +1,62 @@
actionconfig_version = (2, 93, 10)
actionconfig_data = \
[("default_oculus",
{"profile": '/interaction_profiles/oculus/touch_controller'},
{"items":
[("controller_pose", {"type": 'POSE', "user_path0": '/user/hand/left', "component_path0": '/input/grip/pose', "user_path1": '/user/hand/right', "component_path1": '/input/grip/pose', "pose_is_controller": 'True', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(-0.8726646304130554, 0.0, 0.0)'}, None),
("raycast_select", {"type": 'BUTTON', "user_path0": '/user/hand/left', "component_path0": '/input/trigger/value', "user_path1": '/user/hand/right', "component_path1": '/input/trigger/value', "threshold": '0.30000001192092896', "op": 'wm.xr_select_raycast', "op_flag": 'MODAL'}, None),
("grab", {"type": 'BUTTON', "user_path0": '/user/hand/left', "component_path0": '/input/squeeze/value', "user_path1": '/user/hand/right', "component_path1": '/input/squeeze/value', "threshold": '0.30000001192092896', "op": 'wm.xr_grab', "op_flag": 'MODAL'}, None),
("undo", {"type": 'BUTTON', "user_path0": '/user/hand/left', "component_path0": '/input/x/click', "user_path1": '', "component_path1": '', "threshold": '0.30000001192092896', "op": 'ed.undo', "op_flag": 'PRESS'}, None),
("redo", {"type": 'BUTTON', "user_path0": '/user/hand/right', "component_path0": '/input/a/click', "user_path1": '', "component_path1": '', "threshold": '0.30000001192092896', "op": 'ed.redo', "op_flag": 'PRESS'}, None),
],
},
),
("default_wmr",
{"profile": '/interaction_profiles/microsoft/motion_controller'},
{"items":
[("controller_pose", {"type": 'POSE', "user_path0": '/user/hand/left', "component_path0": '/input/grip/pose', "user_path1": '/user/hand/right', "component_path1": '/input/grip/pose', "pose_is_controller": 'True', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(-0.7853981852531433, 0.0, 0.0)'}, None),
("raycast_select", {"type": 'BUTTON', "user_path0": '/user/hand/left', "component_path0": '/input/trigger/value', "user_path1": '/user/hand/right', "component_path1": '/input/trigger/value', "threshold": '0.30000001192092896', "op": 'wm.xr_select_raycast', "op_flag": 'MODAL'}, None),
("grab", {"type": 'BUTTON', "user_path0": '/user/hand/left', "component_path0": '/input/squeeze/click', "user_path1": '/user/hand/right', "component_path1": '/input/squeeze/click', "threshold": '0.30000001192092896', "op": 'wm.xr_grab', "op_flag": 'MODAL'}, None),
("undo", {"type": 'BUTTON', "user_path0": '/user/hand/left', "component_path0": '/input/menu/click', "user_path1": '', "component_path1": '', "threshold": '0.30000001192092896', "op": 'ed.undo', "op_flag": 'PRESS'}, None),
("redo", {"type": 'BUTTON', "user_path0": '/user/hand/right', "component_path0": '/input/menu/click', "user_path1": '', "component_path1": '', "threshold": '0.30000001192092896', "op": 'ed.redo', "op_flag": 'PRESS'}, None),
],
},
),
("default_vive",
{"profile": '/interaction_profiles/htc/vive_controller'},
{"items":
[("controller_pose", {"type": 'POSE', "user_path0": '/user/hand/left', "component_path0": '/input/grip/pose', "user_path1": '/user/hand/right', "component_path1": '/input/grip/pose', "pose_is_controller": 'True', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}, None),
("raycast_select", {"type": 'BUTTON', "user_path0": '/user/hand/left', "component_path0": '/input/trigger/value', "user_path1": '/user/hand/right', "component_path1": '/input/trigger/value', "threshold": '0.30000001192092896', "op": 'wm.xr_select_raycast', "op_flag": 'MODAL'}, None),
("grab", {"type": 'BUTTON', "user_path0": '/user/hand/left', "component_path0": '/input/squeeze/click', "user_path1": '/user/hand/right', "component_path1": '/input/squeeze/click', "threshold": '0.30000001192092896', "op": 'wm.xr_grab', "op_flag": 'MODAL'}, None),
("undo", {"type": 'BUTTON', "user_path0": '/user/hand/left', "component_path0": '/input/menu/click', "user_path1": '', "component_path1": '', "threshold": '0.30000001192092896', "op": 'ed.undo', "op_flag": 'PRESS'}, None),
("redo", {"type": 'BUTTON', "user_path0": '/user/hand/right', "component_path0": '/input/menu/click', "user_path1": '', "component_path1": '', "threshold": '0.30000001192092896', "op": 'ed.redo', "op_flag": 'PRESS'}, None),
],
},
),
("default_index",
{"profile": '/interaction_profiles/valve/index_controller'},
{"items":
[("controller_pose", {"type": 'POSE', "user_path0": '/user/hand/left', "component_path0": '/input/grip/pose', "user_path1": '/user/hand/right', "component_path1": '/input/grip/pose', "pose_is_controller": 'True', "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}, None),
("raycast_select", {"type": 'BUTTON', "user_path0": '/user/hand/left', "component_path0": '/input/trigger/value', "user_path1": '/user/hand/right', "component_path1": '/input/trigger/value', "threshold": '0.30000001192092896', "op": 'wm.xr_select_raycast', "op_flag": 'MODAL'}, None),
("grab", {"type": 'BUTTON', "user_path0": '/user/hand/left', "component_path0": '/input/squeeze/value', "user_path1": '/user/hand/right', "component_path1": '/input/squeeze/value', "threshold": '0.30000001192092896', "op": 'wm.xr_grab', "op_flag": 'MODAL'}, None),
("undo", {"type": 'BUTTON', "user_path0": '/user/hand/left', "component_path0": '/input/a/click', "user_path1": '', "component_path1": '', "threshold": '0.30000001192092896', "op": 'ed.undo', "op_flag": 'PRESS'}, None),
("redo", {"type": 'BUTTON', "user_path0": '/user/hand/right', "component_path0": '/input/a/click', "user_path1": '', "component_path1": '', "threshold": '0.30000001192092896', "op": 'ed.redo', "op_flag": 'PRESS'}, None),
],
},
),
]
if __name__ == "__main__":
# Only add keywords that are supported.
from bpy.app import version as blender_version
keywords = {}
if blender_version >= (2, 93, 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,
)

View File

@ -18,126 +18,105 @@
# <pep8 compliant>
if "bpy" in locals():
import importlib
importlib.reload(main)
else:
from . import main
import bpy
from bpy.app.handlers import persistent
import os.path, importlib.util, math
import math
from enum import Enum
from bl_keymap_utils.io import keyconfig_import_from_data_exec
def vr_defaults_action_set_add(scene, name, profile):
bpy.ops.view3d.vr_action_set_add()
def vr_defaults_actionmap_add(ac, name, profile):
am = ac.actionmaps.new(name)
if am:
am.profile = profile
return am
def vr_defaults_actionmap_item_add(am,
name,
user_path0,
component_path0,
user_path1,
component_path1,
threshold,
op,
op_flag):
ami = am.actionmap_items.new(name)
if ami:
ami.type = 'BUTTON'
ami.user_path0 = user_path0
ami.component_path0 = component_path0
ami.user_path1 = user_path1
ami.component_path1 = component_path1
ami.threshold = threshold
ami.op = op
ami.op_flag = op_flag
return ami
def vr_defaults_pose_actionmap_item_add(am,
name,
user_path0,
component_path0,
user_path1,
component_path1,
is_controller,
location,
rotation):
ami = am.actionmap_items.new(name)
if ami:
ami.type = 'POSE'
ami.user_path0 = user_path0
ami.component_path0 = component_path0
ami.user_path1 = user_path1
ami.component_path1 = component_path1
ami.pose_is_controller = is_controller
ami.pose_location = location
ami.pose_rotation = rotation
return ami
def vr_defaults_haptic_actionmap_item_add(am,
name,
user_path0,
component_path0,
user_path1,
component_path1,
duration,
frequency,
amplitude):
ami = am.actionmap_items.new(name)
if ami:
ami.type = 'HAPTIC'
ami.user_path0 = user_path0
ami.component_path0 = component_path0
ami.user_path1 = user_path1
ami.component_path1 = component_path1
ami.duration = duration
ami.frequency = frequency
ami.amplitude = amplitude
action_set = scene.vr_action_sets[scene.vr_action_sets_selected]
if action_set:
action_set.name = name
action_set.profile = profile
return action_set
return ami
def vr_defaults_action_set_remove(scene, name):
action_set_selected_prev = scene.vr_action_sets_selected
idx = 0
for action_set in scene.vr_action_sets:
if (action_set.name == name):
scene.vr_action_sets_selected = idx
bpy.ops.view3d.vr_action_set_remove()
scene.vr_action_sets_selected = action_set_selected_prev
return
idx += 1
def vr_defaults_action_add(action_set,
name,
user_path0,
component_path0,
user_path1,
component_path1,
threshold,
op,
op_flag):
bpy.ops.view3d.vr_action_add()
action = action_set.actions[action_set.actions_selected]
if action:
action.name = name
action.type = 'BUTTON'
action.user_path0 = user_path0
action.component_path0 = component_path0
action.user_path1 = user_path1
action.component_path1 = component_path1
action.threshold = threshold
action.op = op
action.op_flag = op_flag
return action
def vr_defaults_pose_action_add(action_set,
name,
user_path0,
component_path0,
user_path1,
component_path1,
is_controller,
location,
rotation):
bpy.ops.view3d.vr_action_add()
action = action_set.actions[action_set.actions_selected]
if action:
action.name = name
action.type = 'POSE'
action.user_path0 = user_path0
action.component_path0 = component_path0
action.user_path1 = user_path1
action.component_path1 = component_path1
action.pose_is_controller = is_controller
action.pose_location = location
action.pose_rotation = rotation
return action
def vr_defaults_haptic_action_add(action_set,
name,
user_path0,
component_path0,
user_path1,
component_path1,
duration,
frequency,
amplitude):
bpy.ops.view3d.vr_action_add()
action = action_set.actions[action_set.actions_selected]
if action:
action.name = name
action.type = 'HAPTIC'
action.user_path0 = user_path0
action.component_path0 = component_path0
action.user_path1 = user_path1
action.component_path1 = component_path1
action.duration = duration
action.frequency = frequency
action.amplitude = amplitude
return action
# Default action sets.
class VRDefaultActionSetNames(Enum):
# Default actionmaps.
class VRDefaultActionmapNames(Enum):
OCULUS = "default_oculus"
WMR = "default_wmr"
VIVE = "default_vive"
INDEX = "default_index"
# Default actions.
class VRDefaultActionNames(Enum):
# Default actionmap items.
class VRDefaultActionmapItemNames(Enum):
CONTROLLER_POSE = "controller_pose"
RAYCAST_SELECT = "raycast_select"
GRAB = "grab"
@ -145,268 +124,251 @@ class VRDefaultActionNames(Enum):
REDO = "redo"
def vr_defaults_load_oculus(scene):
action_set = vr_defaults_action_set_add(scene,
VRDefaultActionSetNames.OCULUS.value,
"/interaction_profiles/oculus/touch_controller")
if not action_set:
def vr_defaults_create_oculus(ac):
am = vr_defaults_actionmap_add(ac,
VRDefaultActionmapNames.OCULUS.value,
"/interaction_profiles/oculus/touch_controller")
if not am:
return
vr_defaults_pose_action_add(action_set,
VRDefaultActionNames.CONTROLLER_POSE.value,
"/user/hand/left",
"/input/grip/pose",
"/user/hand/right",
"/input/grip/pose",
True,
(0, 0, 0),
(math.radians(-50), 0, 0))
vr_defaults_action_add(action_set,
VRDefaultActionNames.RAYCAST_SELECT.value,
"/user/hand/left",
"/input/trigger/value",
"/user/hand/right",
"/input/trigger/value",
0.3,
"wm.xr_select_raycast",
'MODAL')
vr_defaults_action_add(action_set,
VRDefaultActionNames.GRAB.value,
"/user/hand/left",
"/input/squeeze/value",
"/user/hand/right",
"/input/squeeze/value",
0.3,
"wm.xr_grab",
'MODAL')
vr_defaults_action_add(action_set,
VRDefaultActionNames.UNDO.value,
"/user/hand/left",
"/input/x/click",
"",
"",
0.3,
"ed.undo",
'PRESS')
vr_defaults_action_add(action_set,
VRDefaultActionNames.REDO.value,
"/user/hand/right",
"/input/a/click",
"",
"",
0.3,
"ed.redo",
'PRESS')
action_set.actions_selected = 0
vr_defaults_pose_actionmap_item_add(am,
VRDefaultActionmapItemNames.CONTROLLER_POSE.value,
"/user/hand/left",
"/input/grip/pose",
"/user/hand/right",
"/input/grip/pose",
True,
(0, 0, 0),
(math.radians(-50), 0, 0))
vr_defaults_actionmap_item_add(am,
VRDefaultActionmapItemNames.RAYCAST_SELECT.value,
"/user/hand/left",
"/input/trigger/value",
"/user/hand/right",
"/input/trigger/value",
0.3,
"wm.xr_select_raycast",
'MODAL')
vr_defaults_actionmap_item_add(am,
VRDefaultActionmapItemNames.GRAB.value,
"/user/hand/left",
"/input/squeeze/value",
"/user/hand/right",
"/input/squeeze/value",
0.3,
"wm.xr_grab",
'MODAL')
vr_defaults_actionmap_item_add(am,
VRDefaultActionmapItemNames.UNDO.value,
"/user/hand/left",
"/input/x/click",
"",
"",
0.3,
"ed.undo",
'PRESS')
vr_defaults_actionmap_item_add(am,
VRDefaultActionmapItemNames.REDO.value,
"/user/hand/right",
"/input/a/click",
"",
"",
0.3,
"ed.redo",
'PRESS')
def vr_defaults_load_wmr(scene):
action_set = vr_defaults_action_set_add(scene,
VRDefaultActionSetNames.WMR.value,
"/interaction_profiles/microsoft/motion_controller")
if not action_set:
def vr_defaults_create_wmr(ac):
am = vr_defaults_actionmap_add(ac,
VRDefaultActionmapNames.WMR.value,
"/interaction_profiles/microsoft/motion_controller")
if not am:
return
vr_defaults_pose_action_add(action_set,
VRDefaultActionNames.CONTROLLER_POSE.value,
"/user/hand/left",
"/input/grip/pose",
"/user/hand/right",
"/input/grip/pose",
True,
(0, 0, 0),
(math.radians(-45), 0, 0))
vr_defaults_action_add(action_set,
VRDefaultActionNames.RAYCAST_SELECT.value,
"/user/hand/left",
"/input/trigger/value",
"/user/hand/right",
"/input/trigger/value",
0.3,
"wm.xr_select_raycast",
'MODAL')
vr_defaults_action_add(action_set,
VRDefaultActionNames.GRAB.value,
"/user/hand/left",
"/input/squeeze/click",
"/user/hand/right",
"/input/squeeze/click",
0.3,
"wm.xr_grab",
'MODAL')
vr_defaults_action_add(action_set,
VRDefaultActionNames.UNDO.value,
"/user/hand/left",
"/input/menu/click",
"",
"",
0.3,
"ed.undo",
'PRESS')
vr_defaults_action_add(action_set,
VRDefaultActionNames.REDO.value,
"/user/hand/right",
"/input/menu/click",
"",
"",
0.3,
"ed.redo",
'PRESS')
action_set.actions_selected = 0
vr_defaults_pose_actionmap_item_add(am,
VRDefaultActionmapItemNames.CONTROLLER_POSE.value,
"/user/hand/left",
"/input/grip/pose",
"/user/hand/right",
"/input/grip/pose",
True,
(0, 0, 0),
(math.radians(-45), 0, 0))
vr_defaults_actionmap_item_add(am,
VRDefaultActionmapItemNames.RAYCAST_SELECT.value,
"/user/hand/left",
"/input/trigger/value",
"/user/hand/right",
"/input/trigger/value",
0.3,
"wm.xr_select_raycast",
'MODAL')
vr_defaults_actionmap_item_add(am,
VRDefaultActionmapItemNames.GRAB.value,
"/user/hand/left",
"/input/squeeze/click",
"/user/hand/right",
"/input/squeeze/click",
0.3,
"wm.xr_grab",
'MODAL')
vr_defaults_actionmap_item_add(am,
VRDefaultActionmapItemNames.UNDO.value,
"/user/hand/left",
"/input/menu/click",
"",
"",
0.3,
"ed.undo",
'PRESS')
vr_defaults_actionmap_item_add(am,
VRDefaultActionmapItemNames.REDO.value,
"/user/hand/right",
"/input/menu/click",
"",
"",
0.3,
"ed.redo",
'PRESS')
def vr_defaults_load_vive(scene):
action_set = vr_defaults_action_set_add(scene,
VRDefaultActionSetNames.VIVE.value,
"/interaction_profiles/htc/vive_controller")
if not action_set:
def vr_defaults_create_vive(ac):
am = vr_defaults_actionmap_add(ac,
VRDefaultActionmapNames.VIVE.value,
"/interaction_profiles/htc/vive_controller")
if not am:
return
vr_defaults_pose_action_add(action_set,
VRDefaultActionNames.CONTROLLER_POSE.value,
"/user/hand/left",
"/input/grip/pose",
"/user/hand/right",
"/input/grip/pose",
True,
(0, 0, 0),
(0, 0, 0))
vr_defaults_action_add(action_set,
VRDefaultActionNames.RAYCAST_SELECT.value,
"/user/hand/left",
"/input/trigger/value",
"/user/hand/right",
"/input/trigger/value",
0.3,
"wm.xr_select_raycast",
'MODAL')
vr_defaults_action_add(action_set,
VRDefaultActionNames.GRAB.value,
"/user/hand/left",
"/input/squeeze/click",
"/user/hand/right",
"/input/squeeze/click",
0.3,
"wm.xr_grab",
'MODAL')
vr_defaults_action_add(action_set,
VRDefaultActionNames.UNDO.value,
"/user/hand/left",
"/input/menu/click",
"",
"",
0.3,
"ed.undo",
'PRESS')
vr_defaults_action_add(action_set,
VRDefaultActionNames.REDO.value,
"/user/hand/right",
"/input/menu/click",
"",
"",
0.3,
"ed.redo",
'PRESS')
action_set.actions_selected = 0
vr_defaults_pose_actionmap_item_add(am,
VRDefaultActionmapItemNames.CONTROLLER_POSE.value,
"/user/hand/left",
"/input/grip/pose",
"/user/hand/right",
"/input/grip/pose",
True,
(0, 0, 0),
(0, 0, 0))
vr_defaults_actionmap_item_add(am,
VRDefaultActionmapItemNames.RAYCAST_SELECT.value,
"/user/hand/left",
"/input/trigger/value",
"/user/hand/right",
"/input/trigger/value",
0.3,
"wm.xr_select_raycast",
'MODAL')
vr_defaults_actionmap_item_add(am,
VRDefaultActionmapItemNames.GRAB.value,
"/user/hand/left",
"/input/squeeze/click",
"/user/hand/right",
"/input/squeeze/click",
0.3,
"wm.xr_grab",
'MODAL')
vr_defaults_actionmap_item_add(am,
VRDefaultActionmapItemNames.UNDO.value,
"/user/hand/left",
"/input/menu/click",
"",
"",
0.3,
"ed.undo",
'PRESS')
vr_defaults_actionmap_item_add(am,
VRDefaultActionmapItemNames.REDO.value,
"/user/hand/right",
"/input/menu/click",
"",
"",
0.3,
"ed.redo",
'PRESS')
def vr_defaults_load_index(scene):
action_set = vr_defaults_action_set_add(scene,
VRDefaultActionSetNames.INDEX.value,
"/interaction_profiles/valve/index_controller")
if not action_set:
def vr_defaults_create_index(ac):
am = vr_defaults_actionmap_add(ac,
VRDefaultActionmapNames.INDEX.value,
"/interaction_profiles/valve/index_controller")
if not am:
return
vr_defaults_pose_action_add(action_set,
VRDefaultActionNames.CONTROLLER_POSE.value,
"/user/hand/left",
"/input/grip/pose",
"/user/hand/right",
"/input/grip/pose",
True,
(0, 0, 0),
(0, 0, 0))
vr_defaults_action_add(action_set,
VRDefaultActionNames.RAYCAST_SELECT.value,
"/user/hand/left",
"/input/trigger/value",
"/user/hand/right",
"/input/trigger/value",
0.3,
"wm.xr_select_raycast",
'MODAL')
vr_defaults_action_add(action_set,
VRDefaultActionNames.GRAB.value,
"/user/hand/left",
"/input/squeeze/value",
"/user/hand/right",
"/input/squeeze/value",
0.3,
"wm.xr_grab",
'MODAL')
vr_defaults_action_add(action_set,
VRDefaultActionNames.UNDO.value,
"/user/hand/left",
"/input/a/click",
"",
"",
0.3,
"ed.undo",
'PRESS')
vr_defaults_action_add(action_set,
VRDefaultActionNames.REDO.value,
"/user/hand/right",
"/input/a/click",
"",
"",
0.3,
"ed.redo",
'PRESS')
action_set.actions_selected = 0
@persistent
def vr_load_default_action_sets(context: bpy.context):
scene = bpy.context.scene
vr_defaults_pose_actionmap_item_add(am,
VRDefaultActionmapItemNames.CONTROLLER_POSE.value,
"/user/hand/left",
"/input/grip/pose",
"/user/hand/right",
"/input/grip/pose",
True,
(0, 0, 0),
(0, 0, 0))
vr_defaults_actionmap_item_add(am,
VRDefaultActionmapItemNames.RAYCAST_SELECT.value,
"/user/hand/left",
"/input/trigger/value",
"/user/hand/right",
"/input/trigger/value",
0.3,
"wm.xr_select_raycast",
'MODAL')
vr_defaults_actionmap_item_add(am,
VRDefaultActionmapItemNames.GRAB.value,
"/user/hand/left",
"/input/squeeze/value",
"/user/hand/right",
"/input/squeeze/value",
0.3,
"wm.xr_grab",
'MODAL')
vr_defaults_actionmap_item_add(am,
VRDefaultActionmapItemNames.UNDO.value,
"/user/hand/left",
"/input/a/click",
"",
"",
0.3,
"ed.undo",
'PRESS')
vr_defaults_actionmap_item_add(am,
VRDefaultActionmapItemNames.REDO.value,
"/user/hand/right",
"/input/a/click",
"",
"",
0.3,
"ed.redo",
'PRESS')
if len(scene.vr_action_sets) > 0:
# Don't load defaults for scenes that already contain action sets.
return
# Load default action sets.
vr_defaults_load_oculus(scene)
vr_defaults_load_wmr(scene)
vr_defaults_load_vive(scene)
vr_defaults_load_index(scene)
scene.vr_action_sets_selected = scene.vr_action_sets_active = 0
# Load operator properties for default action sets.
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
if not kc:
return
# Import XR Session key map.
dirpath = os.path.join(os.path.dirname(os.path.abspath(__file__)), '')
filename = os.path.splitext(os.path.basename(__file__))[0] + ".xrkey"
filepath = dirpath + filename + ".py"
if os.path.exists(filepath):
spec = importlib.util.spec_from_file_location(filename, filepath)
xr_keymap = importlib.util.module_from_spec(spec)
spec.loader.exec_module(xr_keymap)
keyconfig_import_from_data_exec(kc, xr_keymap.keyconfig_data, keyconfig_version=xr_keymap.keyconfig_version)
@persistent
def vr_unload_default_action_sets(context: bpy.context):
scene = bpy.context.scene
def vr_load_default_actionmaps(context: bpy.context):
context = bpy.context
for name in VRDefaultActionSetNames:
vr_defaults_action_set_remove(scene, name.value)
actionconfigs = context.window_manager.xr_session_settings.actionconfigs
if not actionconfigs:
return
ac = actionconfigs.default
if not ac:
return
# Set default action config as active.
actionconfigs.active = ac
if len(ac.actionmaps) > 0:
# Don't load defaults for scenes that already contain actionmaps.
return
# Set default action config as active.
#actionconfigs.active = ac
# Load default actionmaps.
loaded = main.vr_load_actionmaps(context)
if not loaded:
# Create and save default actionmaps.
vr_defaults_create_oculus(ac)
vr_defaults_create_wmr(ac)
vr_defaults_create_vive(ac)
vr_defaults_create_index(ac)
main.vr_save_actionmaps(context, sort=False)

View File

@ -1,64 +0,0 @@
keyconfig_version = (2, 93, 5)
keyconfig_data = \
[("XR Session",
{"space_type": 'EMPTY', "region_type": 'XR'},
{"items":
[("wm.xr_select_raycast",
{"type": 'XR_ACTION', "value": 'ANY', "xr_action_set": 'default_oculus', "xr_action": 'raycast_select'},
{"properties":
[("deselect_all", True),
],
},
),
("wm.xr_select_raycast",
{"type": 'XR_ACTION', "value": 'ANY', "xr_action_set": 'default_vive', "xr_action": 'raycast_select'},
{"properties":
[("deselect_all", True),
],
},
),
("wm.xr_grab", {"type": 'XR_ACTION', "value": 'ANY', "xr_action_set": 'default_vive', "xr_action": 'grab'}, None),
("wm.xr_grab", {"type": 'XR_ACTION', "value": 'ANY', "xr_action_set": 'default_oculus', "xr_action": 'grab'}, None),
("wm.xr_select_raycast",
{"type": 'XR_ACTION', "value": 'ANY', "xr_action_set": 'default_wmr', "xr_action": 'raycast_select'},
{"properties":
[("deselect_all", True),
],
},
),
("wm.xr_grab", {"type": 'XR_ACTION', "value": 'ANY', "xr_action_set": 'default_wmr', "xr_action": 'grab'}, None),
("wm.xr_select_raycast",
{"type": 'XR_ACTION', "value": 'ANY', "xr_action_set": 'default_index', "xr_action": 'raycast_select'},
{"properties":
[("deselect_all", True),
],
},
),
("wm.xr_grab", {"type": 'XR_ACTION', "value": 'ANY', "xr_action_set": 'default_index', "xr_action": 'grab'}, None),
("ed.undo", {"type": 'XR_ACTION', "value": 'ANY', "xr_action_set": 'default_vive', "xr_action": 'undo'}, None),
("ed.redo", {"type": 'XR_ACTION', "value": 'ANY', "xr_action_set": 'default_vive', "xr_action": 'redo'}, None),
("ed.undo", {"type": 'XR_ACTION', "value": 'ANY', "xr_action_set": 'default_oculus', "xr_action": 'undo'}, None),
("ed.redo", {"type": 'XR_ACTION', "value": 'ANY', "xr_action_set": 'default_oculus', "xr_action": 'redo'}, None),
("ed.undo", {"type": 'XR_ACTION', "value": 'ANY', "xr_action_set": 'default_wmr', "xr_action": 'undo'}, None),
("ed.redo", {"type": 'XR_ACTION', "value": 'ANY', "xr_action_set": 'default_wmr', "xr_action": 'redo'}, None),
("ed.undo", {"type": 'XR_ACTION', "value": 'ANY', "xr_action_set": 'default_index', "xr_action": 'undo'}, None),
("ed.redo", {"type": 'XR_ACTION', "value": 'ANY', "xr_action_set": 'default_index', "xr_action": 'redo'}, None),
],
},
),
]
if __name__ == "__main__":
# Only add keywords that are supported.
from bpy.app import version as blender_version
keywords = {}
if blender_version >= (2, 92, 0):
keywords["keyconfig_version"] = keyconfig_version
import os
from bl_keymap_utils.io import keyconfig_import_from_data
keyconfig_import_from_data(
os.path.splitext(os.path.basename(__file__))[0],
keyconfig_data,
**keywords,
)

307
viewport_vr_preview/io.py Normal file
View File

@ -0,0 +1,307 @@
# ##### 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",
)
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 am_args_as_data(am):
s = [
f"\"profile\": '{am.profile}'",
]
return "{" + ", ".join(s) + "}"
def am_data_from_args(am, args):
am.profile = args["profile"]
def ami_args_as_data(ami):
s = [
f"\"type\": '{ami.type}'",
f"\"user_path0\": '{ami.user_path0}'",
f"\"component_path0\": '{ami.component_path0}'",
f"\"user_path1\": '{ami.user_path1}'",
f"\"component_path1\": '{ami.component_path1}'",
]
if ami.type == 'BUTTON' or ami.type == 'AXIS':
s.append(f"\"threshold\": '{ami.threshold}'")
s.append(f"\"op\": '{ami.op}'")
s.append(f"\"op_flag\": '{ami.op_flag}'")
elif ami.type == 'POSE':
s.append(f"\"pose_is_controller\": '{ami.pose_is_controller}'")
s.append(f"\"pose_location\": '{ami.pose_location.x, ami.pose_location.y, ami.pose_location.z}'")
s.append(f"\"pose_rotation\": '{ami.pose_rotation.x, ami.pose_rotation.y, ami.pose_rotation.z}'")
elif ami.type == 'HAPTIC':
s.append(f"\"haptic_duration\": '{ami.haptic_duration}'")
s.append(f"\"haptic_frequency\": '{ami.haptic_frequency}'")
s.append(f"\"haptic_amplitude\": '{ami.haptic_amplitude}'")
return "{" + ", ".join(s) + "}"
def ami_data_from_args(ami, args):
ami.type = args["type"]
ami.user_path0 = args["user_path0"]
ami.component_path0 = args["component_path0"]
ami.user_path1 = args["user_path1"]
ami.component_path1 = args["component_path1"]
if ami.type == 'BUTTON' or ami.type == 'AXIS':
ami.threshold = float(args["threshold"])
ami.op = args["op"]
ami.op_flag = args["op_flag"]
elif ami.type == 'POSE':
ami.pose_is_controller = bool(args["pose_is_controller"])
l = args["pose_location"].strip(')(').split(', ')
ami.pose_location.x = float(l[0])
ami.pose_location.y = float(l[1])
ami.pose_location.z = float(l[2])
l = args["pose_rotation"].strip(')(').split(', ')
ami.pose_rotation.x = float(l[0])
ami.pose_rotation.y = float(l[1])
ami.pose_rotation.z = float(l[2])
elif ami.type == 'HAPTIC':
ami.haptic_duration = float(args["haptic_duration"])
ami.haptic_frequency = float(args["haptic_frequency"])
ami.haptic_amplitude = float(args["haptic_amplitude"])
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 actionconfig_export_as_data(ac, filepath, *, all_actionmaps=True, sort=False):
export_actionmaps = []
for am in ac.actionmaps:
if all_actionmaps or am.is_user_modified:
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)}")
am_args = am_args_as_data(am)
fw(am_args)
fw(",\n")
fw(f"{indent(2)}" "{")
fw(f"\"items\":\n")
fw(f"{indent(3)}[")
for ami in am.actionmap_items:
fw(f"(")
ami_args = ami_args_as_data(ami)
ami_data = _ami_attrs_or_none(4, ami)
fw(f"\"{ami.name:s}\"")
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("},\n" f"{indent(5)}")
fw("),\n")
fw(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 >= (2, 93, 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_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 actionmap item '{ami_props.__class__.__name__}'")
except Exception as ex:
print(f"Warning: {ex!r}")
def actionmap_init_from_data(am, am_items):
new_fn = getattr(am.actionmap_items, "new")
for (ami_name, ami_args, ami_data) in am_items:
ami = new_fn(ami_name) #, **ami_args)
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_props, attr, value)
def actionconfig_init_from_data(ac, 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_args, am_content) in actionconfig_data:
am = ac.actionmaps.new(am_name) #, **am_args)
am_data_from_args(am, am_args)
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 actionmaps, 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(name, actionconfig_data, *, actionconfig_version=(0, 0, 0)):
# Load data in the format defined above.
#
# Runs at load time, keep this fast!
import bpy
ac = bpy.context.window_manager.xr_session_settings.actionconfigs.new(name)
actionconfig_init_from_data(ac, actionconfig_data, actionconfig_version)
return ac

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
# ##### 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 ``(2, 93, 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 <= (2, 93, 0):
## # Only copy once.
## if not has_copy:
## actionconfig_data = copy.deepcopy(actionconfig_data)
## has_copy = True
##
## for _am_name, _am_parms, am_items_data in actionconfig_data:
## for (_item_op, item_event, _item_prop) in am_items_data["items"]:
## # Apply version updates here.
return actionconfig_data