From b172b0292c6d046d2211e87d0558e5cc23ac9664 Mon Sep 17 00:00:00 2001 From: Peter Kim Date: Sun, 20 Feb 2022 15:24:51 +0900 Subject: VR: Customizable Actions https://developer.blender.org/D13421 --- viewport_vr_preview/action_map.py | 93 ++++-- viewport_vr_preview/action_map_io.py | 12 +- viewport_vr_preview/defaults.py | 25 +- viewport_vr_preview/gui.py | 402 ++++++++++++++++++++++++- viewport_vr_preview/operators.py | 565 ++++++++++++++++++++++++++++++++++- 5 files changed, 1042 insertions(+), 55 deletions(-) diff --git a/viewport_vr_preview/action_map.py b/viewport_vr_preview/action_map.py index 6c62c05d..a9728ec4 100644 --- a/viewport_vr_preview/action_map.py +++ b/viewport_vr_preview/action_map.py @@ -4,6 +4,7 @@ if "bpy" in locals(): import importlib + importlib.reload(action_map_io) importlib.reload(defaults) else: from . import action_map_io, defaults @@ -15,22 +16,65 @@ import importlib.util import os.path +def vr_actionmap_active_get(session_settings): + actionmaps = session_settings.actionmaps + return ( + None if (len(actionmaps) < + 1) else actionmaps[session_settings.active_actionmap] + ) + + +def vr_actionmap_selected_get(session_settings): + actionmaps = session_settings.actionmaps + return ( + None if (len(actionmaps) < + 1) else actionmaps[session_settings.selected_actionmap] + ) + + +def vr_actionmap_item_selected_get(am): + actionmap_items = am.actionmap_items + return ( + None if (len(actionmap_items) < + 1) else actionmap_items[am.selected_item] + ) + + +def vr_actionmap_user_path_selected_get(ami): + user_paths = ami.user_paths + return ( + None if (len(user_paths) < + 1) else user_paths[ami.selected_user_path] + ) + + +def vr_actionmap_binding_selected_get(ami): + actionmap_bindings = ami.bindings + return ( + None if (len(actionmap_bindings) < + 1) else actionmap_bindings[ami.selected_binding] + ) + + +def vr_actionmap_component_path_selected_get(amb): + component_paths = amb.component_paths + return ( + None if (len(component_paths) < + 1) else component_paths[amb.selected_component_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: + if not session_state: 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) + session_settings = context.window_manager.xr_session_settings + am = vr_actionmap_active_get(session_settings) + if not am: + return - -def vr_actions_use_gamepad_update(self, context): - vr_actionset_active_update(context) + session_state.active_action_set_set(context, am.name) @persistent @@ -45,11 +89,14 @@ def vr_create_actions(context: bpy.context): if not scene.vr_actions_enable: return - # Ensure default action maps. - if not defaults.vr_ensure_default_actionmaps(session_state): - return + session_settings = context.window_manager.xr_session_settings + + if (len(session_settings.actionmaps) < 1): + # Ensure default action maps. + if not defaults.vr_ensure_default_actionmaps(session_settings): + return - for am in session_state.actionmaps: + for am in session_settings.actionmaps: if len(am.actionmap_items) < 1: continue @@ -101,7 +148,7 @@ def vr_create_actions(context: bpy.context): vr_actionset_active_update(context) -def vr_load_actionmaps(session_state, filepath): +def vr_load_actionmaps(session_settings, filepath): if not os.path.exists(filepath): return False @@ -109,16 +156,16 @@ def vr_load_actionmaps(session_state, 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) + action_map_io.actionconfig_init_from_data(session_settings, 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) +def vr_save_actionmaps(session_settings, filepath, sort=False): + action_map_io.actionconfig_export_as_data(session_settings, filepath, sort=sort) print("Saved XR actionmaps: " + filepath) - + return True @@ -128,11 +175,6 @@ def register(): 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_huawei = bpy.props.BoolProperty( description="Enable bindings for the Huawei controllers. Note that this may not be supported by all OpenXR runtimes", default=False, @@ -155,7 +197,6 @@ def register(): def unregister(): del bpy.types.Scene.vr_actions_enable - del bpy.types.Scene.vr_actions_use_gamepad del bpy.types.Scene.vr_actions_enable_huawei del bpy.types.Scene.vr_actions_enable_reverb_g2 del bpy.types.Scene.vr_actions_enable_vive_cosmos diff --git a/viewport_vr_preview/action_map_io.py b/viewport_vr_preview/action_map_io.py index c4e04581..809a2906 100644 --- a/viewport_vr_preview/action_map_io.py +++ b/viewport_vr_preview/action_map_io.py @@ -189,10 +189,10 @@ def amb_data_from_args(amb, args, type): amb.pose_rotation.z = float(l[2]) -def actionconfig_export_as_data(session_state, filepath, *, sort=False): +def actionconfig_export_as_data(session_settings, filepath, *, sort=False): export_actionmaps = [] - for am in session_state.actionmaps: + for am in session_settings.actionmaps: export_actionmaps.append(am) if sort: @@ -318,7 +318,7 @@ def actionmap_init_from_data(am, am_items): actionmap_item_init_from_data(ami, ami_bindings) -def actionconfig_init_from_data(session_state, actionconfig_data, actionconfig_version): +def actionconfig_init_from_data(session_settings, actionconfig_data, actionconfig_version): # Load data in the format defined above. # # Runs at load time, keep this fast! @@ -327,7 +327,7 @@ def actionconfig_init_from_data(session_state, actionconfig_data, actionconfig_v 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 = session_settings.actionmaps.new(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. @@ -338,9 +338,9 @@ def actionconfig_init_from_data(session_state, actionconfig_data, actionconfig_v actionmap_init_from_data(am, am_items) -def actionconfig_import_from_data(session_state, actionconfig_data, *, actionconfig_version=(0, 0, 0)): +def actionconfig_import_from_data(session_settings, 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) + actionconfig_init_from_data(session_settings, actionconfig_data, actionconfig_version) diff --git a/viewport_vr_preview/defaults.py b/viewport_vr_preview/defaults.py index fe253e10..8d2a38f4 100644 --- a/viewport_vr_preview/defaults.py +++ b/viewport_vr_preview/defaults.py @@ -9,7 +9,6 @@ else: from . import action_map import bpy -from bpy.app.handlers import persistent from enum import Enum import math import os.path @@ -71,8 +70,8 @@ class VRDefaultActionprofiles(Enum): WMR = "/interaction_profiles/microsoft/motion_controller" -def vr_defaults_actionmap_add(session_state, name): - am = session_state.actionmaps.new(session_state, name, True) +def vr_defaults_actionmap_add(session_settings, name): + am = session_settings.actionmaps.new(name, True) return am @@ -186,8 +185,8 @@ def vr_defaults_haptic_actionbinding_add(ami, return amb -def vr_defaults_create_default(session_state): - am = vr_defaults_actionmap_add(session_state, +def vr_defaults_create_default(session_settings): + am = vr_defaults_actionmap_add(session_settings, VRDefaultActionmaps.DEFAULT.value) if not am: return @@ -1199,8 +1198,8 @@ def vr_defaults_create_default(session_state): "/output/haptic"]) -def vr_defaults_create_default_gamepad(session_state): - am = vr_defaults_actionmap_add(session_state, +def vr_defaults_create_default_gamepad(session_settings): + am = vr_defaults_actionmap_add(session_settings, VRDefaultActionmaps.GAMEPAD.value) ami = vr_defaults_action_add(am, @@ -1476,11 +1475,11 @@ def vr_get_default_config_path(): return os.path.join(filepath, "default.py") -def vr_ensure_default_actionmaps(session_state): +def vr_ensure_default_actionmaps(session_settings): loaded = True for name in VRDefaultActionmaps: - if not session_state.actionmaps.find(session_state, name.value): + if not session_settings.actionmaps.find(name.value): loaded = False break @@ -1492,11 +1491,11 @@ def vr_ensure_default_actionmaps(session_state): 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) + vr_defaults_create_default(session_settings) + vr_defaults_create_default_gamepad(session_settings) - action_map.vr_save_actionmaps(session_state, filepath, sort=False) + action_map.vr_save_actionmaps(session_settings, filepath, sort=False) - loaded = action_map.vr_load_actionmaps(session_state, filepath) + loaded = action_map.vr_load_actionmaps(session_settings, filepath) return loaded diff --git a/viewport_vr_preview/gui.py b/viewport_vr_preview/gui.py index 163cbd48..cfbbc4c1 100644 --- a/viewport_vr_preview/gui.py +++ b/viewport_vr_preview/gui.py @@ -4,9 +4,10 @@ if "bpy" in locals(): import importlib + importlib.reload(action_map) importlib.reload(properties) else: - from . import properties + from . import action_map, properties import bpy from bpy.types import ( @@ -68,7 +69,6 @@ class VIEW3D_PT_vr_session_view(Panel): 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") @@ -156,25 +156,392 @@ class VIEW3D_PT_vr_landmarks(Panel): "base_scale", text="Scale") -### View. -class VIEW3D_PT_vr_actionmaps(Panel): +### Actions. +def vr_indented_layout(layout, level): + # Same as _indented_layout() from rna_keymap_ui.py. + indentpx = 16 + if level == 0: + level = 0.0001 # Tweak so that a percentage of 0 won't split by half + indent = level * indentpx / bpy.context.region.width + + split = layout.split(factor=indent) + col = split.column() + col = split.column() + return col + + +def vr_draw_ami(ami, layout, level): + # Similar to draw_kmi() from rna_keymap_ui.py. + col = vr_indented_layout(layout, level) + + if ami.op: + col = col.column(align=True) + box = col.box() + else: + box = col.column() + + split = box.split() + + # Header bar. + row = split.row(align=True) + #row.prop(ami, "show_expanded", text="", emboss=False) + + row.label(text="Operator Properties") + row.label(text=ami.op_name) + + # Expanded, additional event settings. + if ami.op: + box = col.box() + + # Operator properties. + box.template_xr_actionmap_item_properties(ami) + + +class VIEW3D_UL_vr_actionmaps(UIList): + def draw_item(self, context, layout, _data, item, icon, _active_data, + _active_propname, index): + session_settings = context.window_manager.xr_session_settings + + am_active_idx = session_settings.active_actionmap + am = item + + layout.emboss = 'NONE' + + layout.prop(am, "name", text="") + + icon = ( + 'RADIOBUT_ON' if (index == am_active_idx) else 'RADIOBUT_OFF' + ) + props = layout.operator( + "view3d.vr_actionmap_activate", text="", icon=icon) + props.index = index + + +class VIEW3D_MT_vr_actionmap_menu(Menu): + bl_label = "Action Map Controls" + + def draw(self, _context): + layout = self.layout + + layout.operator("view3d.vr_actionmaps_defaults_load") + layout.operator("view3d.vr_actionmaps_import") + layout.operator("view3d.vr_actionmaps_export") + layout.operator("view3d.vr_actionmap_copy") + layout.operator("view3d.vr_actionmaps_clear") + + +class VIEW3D_UL_vr_actions(UIList): + def draw_item(self, context, layout, _data, item, icon, _active_data, + _active_propname, index): + action = item + + layout.emboss = 'NONE' + + layout.prop(action, "name", text="") + + +class VIEW3D_MT_vr_action_menu(Menu): + bl_label = "Action Controls" + + def draw(self, _context): + layout = self.layout + + layout.operator("view3d.vr_action_copy") + layout.operator("view3d.vr_actions_clear") + + +class VIEW3D_UL_vr_action_user_paths(UIList): + def draw_item(self, context, layout, _data, item, icon, _active_data, + _active_propname, index): + user_path = item + + layout.emboss = 'NONE' + + layout.prop(user_path, "path", text="") + + +class VIEW3D_MT_vr_action_user_path_menu(Menu): + bl_label = "User Path Controls" + + def draw(self, _context): + layout = self.layout + + layout.operator("view3d.vr_action_user_paths_clear") + + +class VIEW3D_UL_vr_actionbindings(UIList): + def draw_item(self, context, layout, _data, item, icon, _active_data, + _active_propname, index): + amb = item + + layout.emboss = 'NONE' + + layout.prop(amb, "name", text="") + + +class VIEW3D_MT_vr_actionbinding_menu(Menu): + bl_label = "Action Binding Controls" + + def draw(self, _context): + layout = self.layout + + layout.operator("view3d.vr_actionbinding_copy") + layout.operator("view3d.vr_actionbindings_clear") + + +class VIEW3D_UL_vr_actionbinding_component_paths(UIList): + def draw_item(self, context, layout, _data, item, icon, _active_data, + _active_propname, index): + component_path = item + + layout.emboss = 'NONE' + + layout.prop(component_path, "path", text="") + + +class VIEW3D_MT_vr_actionbinding_component_path_menu(Menu): + bl_label = "Component Path Controls" + + def draw(self, _context): + layout = self.layout + + layout.operator("view3d.vr_actionbinding_component_paths_clear") + + +class VRActionsPanel: bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = "VR" - bl_label = "Action Maps" bl_options = {'DEFAULT_CLOSED'} + +class VIEW3D_PT_vr_actions_actionmaps(VRActionsPanel, Panel): + bl_label = "Action Maps" + def draw(self, context): + session_settings = context.window_manager.xr_session_settings + + scene = context.scene + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + row = layout.row() + row.template_list("VIEW3D_UL_vr_actionmaps", "", session_settings, "actionmaps", + session_settings, "selected_actionmap", rows=3) + + col = row.column(align=True) + col.operator("view3d.vr_actionmap_add", icon='ADD', text="") + col.operator("view3d.vr_actionmap_remove", icon='REMOVE', text="") + + col.menu("VIEW3D_MT_vr_actionmap_menu", icon='DOWNARROW_HLT', text="") + + am = action_map.vr_actionmap_selected_get(session_settings) + + if am: + row = layout.row() + col = row.column(align=True) + + col.prop(am, "name", text="Action Map") + + +class VIEW3D_PT_vr_actions_actions(VRActionsPanel, Panel): + bl_label = "Actions" + bl_parent_id = "VIEW3D_PT_vr_actions_actionmaps" + + def draw(self, context): + session_settings = context.window_manager.xr_session_settings + + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + am = action_map.vr_actionmap_selected_get(session_settings) + + if am: + col = vr_indented_layout(layout, 1) + row = col.row() + row.template_list("VIEW3D_UL_vr_actions", "", am, "actionmap_items", + am, "selected_item", rows=3) + + col = row.column(align=True) + col.operator("view3d.vr_action_add", icon='ADD', text="") + col.operator("view3d.vr_action_remove", icon='REMOVE', text="") + + col.menu("VIEW3D_MT_vr_action_menu", icon='DOWNARROW_HLT', text="") + + ami = action_map.vr_actionmap_item_selected_get(am) + + if ami: + row = layout.row() + col = row.column(align=True) + + col.prop(ami, "name", text="Action") + col.prop(ami, "type", text="Type") + + if ami.type == 'FLOAT' or ami.type == 'VECTOR2D': + col.prop(ami, "op", text="Operator") + col.prop(ami, "op_mode", text="Operator Mode") + col.prop(ami, "bimanual", text="Bimanual") + # Properties. + vr_draw_ami(ami, col, 1) + elif ami.type == 'POSE': + col.prop(ami, "pose_is_controller_grip", text="Use for Controller Grips") + col.prop(ami, "pose_is_controller_aim", text="Use for Controller Aims") + + +class VIEW3D_PT_vr_actions_user_paths(VRActionsPanel, Panel): + bl_label = "User Paths" + bl_parent_id = "VIEW3D_PT_vr_actions_actions" + + def draw(self, context): + session_settings = context.window_manager.xr_session_settings + + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + am = action_map.vr_actionmap_selected_get(session_settings) + + if am: + ami = action_map.vr_actionmap_item_selected_get(am) + + if ami: + col = vr_indented_layout(layout, 2) + row = col.row() + row.template_list("VIEW3D_UL_vr_action_user_paths", "", ami, "user_paths", + ami, "selected_user_path", rows=2) + + col = row.column(align=True) + col.operator("view3d.vr_action_user_path_add", icon='ADD', text="") + col.operator("view3d.vr_action_user_path_remove", icon='REMOVE', text="") + + col.menu("VIEW3D_MT_vr_action_user_path_menu", icon='DOWNARROW_HLT', text="") + + +class VIEW3D_PT_vr_actions_haptics(VRActionsPanel, Panel): + bl_label = "Haptics" + bl_parent_id = "VIEW3D_PT_vr_actions_actions" + + def draw(self, context): + session_settings = context.window_manager.xr_session_settings + + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + am = action_map.vr_actionmap_selected_get(session_settings) + + if am: + ami = action_map.vr_actionmap_item_selected_get(am) + + if ami: + row = layout.row() + col = row.column(align=True) + + if ami.type == 'FLOAT' or ami.type == 'VECTOR2D': + col.prop(ami, "haptic_name", text="Haptic Action") + col.prop(ami, "haptic_match_user_paths", text="Match User Paths") + col.prop(ami, "haptic_duration", text="Duration") + col.prop(ami, "haptic_frequency", text="Frequency") + col.prop(ami, "haptic_amplitude", text="Amplitude") + col.prop(ami, "haptic_mode", text="Haptic Mode") + + +class VIEW3D_PT_vr_actions_bindings(VRActionsPanel, Panel): + bl_label = "Bindings" + bl_parent_id = "VIEW3D_PT_vr_actions_actions" + + def draw(self, context): + session_settings = context.window_manager.xr_session_settings + + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + am = action_map.vr_actionmap_selected_get(session_settings) + + if am: + ami = action_map.vr_actionmap_item_selected_get(am) + + if ami: + col = vr_indented_layout(layout, 2) + row = col.row() + row.template_list("VIEW3D_UL_vr_actionbindings", "", ami, "bindings", + ami, "selected_binding", rows=3) + + col = row.column(align=True) + col.operator("view3d.vr_actionbinding_add", icon='ADD', text="") + col.operator("view3d.vr_actionbinding_remove", icon='REMOVE', text="") + + col.menu("VIEW3D_MT_vr_actionbinding_menu", icon='DOWNARROW_HLT', text="") + + amb = action_map.vr_actionmap_binding_selected_get(ami) + + if amb: + row = layout.row() + col = row.column(align=True) + + col.prop(amb, "name", text="Binding") + col.prop(amb, "profile", text="Profile") + + if ami.type == 'FLOAT' or ami.type == 'VECTOR2D': + col.prop(amb, "threshold", text="Threshold") + if ami.type == 'FLOAT': + col.prop(amb, "axis0_region", text="Axis Region") + else: # ami.type == 'VECTOR2D' + col.prop(amb, "axis0_region", text="Axis 0 Region") + col.prop(amb, "axis1_region", text="Axis 1 Region") + elif ami.type == 'POSE': + col.prop(amb, "pose_location", text="Location Offset") + col.prop(amb, "pose_rotation", text="Rotation Offset") + + +class VIEW3D_PT_vr_actions_component_paths(VRActionsPanel, Panel): + bl_label = "Component Paths" + bl_parent_id = "VIEW3D_PT_vr_actions_bindings" + + def draw(self, context): + session_settings = context.window_manager.xr_session_settings + + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + am = action_map.vr_actionmap_selected_get(session_settings) + + if am: + ami = action_map.vr_actionmap_item_selected_get(am) + + if ami: + amb = action_map.vr_actionmap_binding_selected_get(ami) + + if amb: + col = vr_indented_layout(layout, 3) + row = col.row() + row.template_list("VIEW3D_UL_vr_actionbinding_component_paths", "", amb, "component_paths", + amb, "selected_component_path", rows=2) + + col = row.column(align=True) + col.operator("view3d.vr_actionbinding_component_path_add", icon='ADD', text="") + col.operator("view3d.vr_actionbinding_component_path_remove", icon='REMOVE', text="") + + col.menu("VIEW3D_MT_vr_actionbinding_component_path_menu", icon='DOWNARROW_HLT', text="") + + +class VIEW3D_PT_vr_actions_extensions(VRActionsPanel, Panel): + bl_label = "Extensions" + bl_parent_id = "VIEW3D_PT_vr_actions_actionmaps" + + def draw(self, context): scene = context.scene + layout = self.layout 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_vive_cosmos", text="HTC Vive Cosmos") col.prop(scene, "vr_actions_enable_vive_focus", text="HTC Vive Focus") @@ -228,11 +595,28 @@ classes = ( VIEW3D_PT_vr_session, VIEW3D_PT_vr_session_view, VIEW3D_PT_vr_landmarks, - VIEW3D_PT_vr_actionmaps, + VIEW3D_PT_vr_actions_actionmaps, + VIEW3D_PT_vr_actions_actions, + VIEW3D_PT_vr_actions_user_paths, + VIEW3D_PT_vr_actions_haptics, + VIEW3D_PT_vr_actions_bindings, + VIEW3D_PT_vr_actions_component_paths, + VIEW3D_PT_vr_actions_extensions, VIEW3D_PT_vr_viewport_feedback, VIEW3D_UL_vr_landmarks, VIEW3D_MT_vr_landmark_menu, + + VIEW3D_UL_vr_actionmaps, + VIEW3D_MT_vr_actionmap_menu, + VIEW3D_UL_vr_actions, + VIEW3D_MT_vr_action_menu, + VIEW3D_UL_vr_action_user_paths, + VIEW3D_MT_vr_action_user_path_menu, + VIEW3D_UL_vr_actionbindings, + VIEW3D_MT_vr_actionbinding_menu, + VIEW3D_UL_vr_actionbinding_component_paths, + VIEW3D_MT_vr_actionbinding_component_path_menu, ) diff --git a/viewport_vr_preview/operators.py b/viewport_vr_preview/operators.py index 69100762..d540658d 100644 --- a/viewport_vr_preview/operators.py +++ b/viewport_vr_preview/operators.py @@ -4,9 +4,11 @@ if "bpy" in locals(): import importlib + importlib.reload(action_map) + importlib.reload(defaults) importlib.reload(properties) else: - from . import properties + from . import action_map, defaults, properties import bpy from bpy.types import ( @@ -14,10 +16,12 @@ from bpy.types import ( GizmoGroup, Operator, ) +from bpy_extras.io_utils import ExportHelper, ImportHelper import bgl import math from math import radians from mathutils import Euler, Matrix, Quaternion, Vector +import os.path ### Landmarks. @@ -241,6 +245,542 @@ class VIEW3D_OT_vr_landmark_activate(Operator): return {'FINISHED'} + ### Actions. +class VIEW3D_OT_vr_actionmap_add(Operator): + bl_idname = "view3d.vr_actionmap_add" + bl_label = "Add VR Action Map" + bl_description = "Add a new VR action map to the scene" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = session_settings.actionmaps.new("actionmap", False) + if not am: + return {'CANCELLED'} + + # Select newly created actionmap. + session_settings.selected_actionmap = len(session_settings.actionmaps) - 1 + + return {'FINISHED'} + + +class VIEW3D_OT_vr_actionmap_remove(Operator): + bl_idname = "view3d.vr_actionmap_remove" + bl_label = "Remove VR Action Map" + bl_description = "Delete the selected VR action map from the scene" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = action_map.vr_actionmap_selected_get(session_settings) + if not am: + return {'CANCELLED'} + + session_settings.actionmaps.remove(am) + + return {'FINISHED'} + + +class VIEW3D_OT_vr_actionmap_activate(Operator): + bl_idname = "view3d.vr_actionmap_activate" + bl_label = "Activate VR Action Map" + bl_description = "Set the current VR action map for the session" + bl_options = {'UNDO', 'REGISTER'} + + index: bpy.props.IntProperty( + name="Index", + options={'HIDDEN'}, + ) + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + if (self.index >= len(session_settings.actionmaps)): + return {'CANCELLED'} + + session_settings.active_actionmap = ( + self.index if self.properties.is_property_set( + "index") else session_settings.selected_actionmap + ) + + action_map.vr_actionset_active_update(context) + + return {'FINISHED'} + + +class VIEW3D_OT_vr_actionmaps_defaults_load(Operator): + bl_idname = "view3d.vr_actionmaps_defaults_load" + bl_label = "Load Default VR Action Maps" + bl_description = "Load default VR action maps" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + filepath = defaults.vr_get_default_config_path() + + if not action_map.vr_load_actionmaps(session_settings, filepath): + return {'CANCELLED'} + + return {'FINISHED'} + + +class VIEW3D_OT_vr_actionmaps_import(Operator, ImportHelper): + bl_idname = "view3d.vr_actionmaps_import" + bl_label = "Import VR Action Maps" + bl_description = "Import VR action maps from configuration file" + bl_options = {'UNDO', 'REGISTER'} + + filter_glob: bpy.props.StringProperty( + default='*.py', + options={'HIDDEN'}, + ) + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + filename, ext = os.path.splitext(self.filepath) + if (ext != ".py"): + return {'CANCELLED'} + + if not action_map.vr_load_actionmaps(session_settings, self.filepath): + return {'CANCELLED'} + + return {'FINISHED'} + + +class VIEW3D_OT_vr_actionmaps_export(Operator, ExportHelper): + bl_idname = "view3d.vr_actionmaps_export" + bl_label = "Export VR Action Maps" + bl_description = "Export VR action maps to configuration file" + bl_options = {'REGISTER'} + + filter_glob: bpy.props.StringProperty( + default='*.py', + options={'HIDDEN'}, + ) + filename_ext: bpy.props.StringProperty( + default='.py', + options={'HIDDEN'}, + ) + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + filename, ext = os.path.splitext(self.filepath) + if (ext != ".py"): + return {'CANCELLED'} + + if not action_map.vr_save_actionmaps(session_settings, self.filepath): + return {'CANCELLED'} + + return {'FINISHED'} + + +class VIEW3D_OT_vr_actionmap_copy(Operator): + bl_idname = "view3d.vr_actionmap_copy" + bl_label = "Copy VR Action Map" + bl_description = "Copy selected VR action map" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = action_map.vr_actionmap_selected_get(session_settings) + if not am: + return {'CANCELLED'} + + # Copy actionmap. + am_new = session_settings.actionmaps.new_from_actionmap(am) + if not am_new: + return {'CANCELLED'} + + # Select newly created actionmap. + session_settings.selected_actionmap = len(session_settings.actionmaps) - 1 + + return {'FINISHED'} + + +class VIEW3D_OT_vr_actionmaps_clear(Operator): + bl_idname = "view3d.vr_actionmaps_clear" + bl_label = "Clear VR Action Maps" + bl_description = "Delete all VR action maps from the scene" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + while session_settings.actionmaps: + session_settings.actionmaps.remove(session_settings.actionmaps[0]) + + return {'FINISHED'} + + +class VIEW3D_OT_vr_action_add(Operator): + bl_idname = "view3d.vr_action_add" + bl_label = "Add VR Action" + bl_description = "Add a new VR action to the action map" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = action_map.vr_actionmap_selected_get(session_settings) + if not am: + return {'CANCELLED'} + + ami = am.actionmap_items.new("action", False) + if not ami: + return {'CANCELLED'} + + # Select newly created item. + am.selected_item = len(am.actionmap_items) - 1 + + return {'FINISHED'} + + +class VIEW3D_OT_vr_action_remove(Operator): + bl_idname = "view3d.vr_action_remove" + bl_label = "Remove VR Action" + bl_description = "Delete the selected VR action from the action map" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = action_map.vr_actionmap_selected_get(session_settings) + if not am: + return {'CANCELLED'} + + ami = action_map.vr_actionmap_item_selected_get(am) + if not ami: + return {'CANCELLED'} + + am.actionmap_items.remove(ami) + + return {'FINISHED'} + + +class VIEW3D_OT_vr_action_copy(Operator): + bl_idname = "view3d.vr_action_copy" + bl_label = "Copy VR Action" + bl_description = "Copy selected VR action" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = action_map.vr_actionmap_selected_get(session_settings) + if not am: + return {'CANCELLED'} + + ami = action_map.vr_actionmap_item_selected_get(am) + if not ami: + return {'CANCELLED'} + + # Copy item. + ami_new = am.actionmap_items.new_from_item(ami) + if not ami_new: + return {'CANCELLED'} + + # Select newly created item. + am.selected_item = len(am.actionmap_items) - 1 + + return {'FINISHED'} + + +class VIEW3D_OT_vr_actions_clear(Operator): + bl_idname = "view3d.vr_actions_clear" + bl_label = "Clear VR Actions" + bl_description = "Delete all VR actions from the action map" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = action_map.vr_actionmap_selected_get(session_settings) + if not am: + return {'CANCELLED'} + + while am.actionmap_items: + am.actionmap_items.remove(am.actionmap_items[0]) + + return {'FINISHED'} + + +class VIEW3D_OT_vr_action_user_path_add(Operator): + bl_idname = "view3d.vr_action_user_path_add" + bl_label = "Add User Path" + bl_description = "Add a new user path to the VR action" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = action_map.vr_actionmap_selected_get(session_settings) + if not am: + return {'CANCELLED'} + + ami = action_map.vr_actionmap_item_selected_get(am) + if not ami: + return {'CANCELLED'} + + user_path = ami.user_paths.new("/") + if not user_path: + return {'CANCELLED'} + + # Select newly created user path. + ami.selected_user_path = len(ami.user_paths) - 1 + + return {'FINISHED'} + + +class VIEW3D_OT_vr_action_user_path_remove(Operator): + bl_idname = "view3d.vr_action_user_path_remove" + bl_label = "Remove User Path" + bl_description = "Delete the selected user path from the VR action" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = action_map.vr_actionmap_selected_get(session_settings) + if not am: + return {'CANCELLED'} + + ami = action_map.vr_actionmap_item_selected_get(am) + if not ami: + return {'CANCELLED'} + + user_path = action_map.vr_actionmap_user_path_selected_get(ami) + if not user_path: + return {'CANCELLED'} + + ami.user_paths.remove(user_path) + + return {'FINISHED'} + + +class VIEW3D_OT_vr_action_user_paths_clear(Operator): + bl_idname = "view3d.vr_action_user_paths_clear" + bl_label = "Clear User Paths" + bl_description = "Delete all user paths from the VR action" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = action_map.vr_actionmap_selected_get(session_settings) + if not am: + return {'CANCELLED'} + + ami = action_map.vr_actionmap_item_selected_get(am) + if not ami: + return {'CANCELLED'} + + while ami.user_paths: + ami.user_paths.remove(ami.user_paths[0]) + + return {'FINISHED'} + + +class VIEW3D_OT_vr_actionbinding_add(Operator): + bl_idname = "view3d.vr_actionbinding_add" + bl_label = "Add VR Action Binding" + bl_description = "Add a new VR action binding to the action" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = action_map.vr_actionmap_selected_get(session_settings) + if not am: + return {'CANCELLED'} + + ami = action_map.vr_actionmap_item_selected_get(am) + if not ami: + return {'CANCELLED'} + + amb = ami.bindings.new("binding", False) + if not amb: + return {'CANCELLED'} + + # Select newly created binding. + ami.selected_binding = len(ami.bindings) - 1 + + return {'FINISHED'} + + +class VIEW3D_OT_vr_actionbinding_remove(Operator): + bl_idname = "view3d.vr_actionbinding_remove" + bl_label = "Remove VR Action Binding" + bl_description = "Delete the selected VR action binding from the action" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = action_map.vr_actionmap_selected_get(session_settings) + if not am: + return {'CANCELLED'} + + ami = action_map.vr_actionmap_item_selected_get(am) + if not ami: + return {'CANCELLED'} + + amb = action_map.vr_actionmap_binding_selected_get(ami) + if not amb: + return {'CANCELLED'} + + ami.bindings.remove(amb) + + return {'FINISHED'} + + +class VIEW3D_OT_vr_actionbinding_copy(Operator): + bl_idname = "view3d.vr_actionbinding_copy" + bl_label = "Copy VR Action Binding" + bl_description = "Copy selected VR action binding" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = action_map.vr_actionmap_selected_get(session_settings) + if not am: + return {'CANCELLED'} + + ami = action_map.vr_actionmap_item_selected_get(am) + if not ami: + return {'CANCELLED'} + + amb = action_map.vr_actionmap_binding_selected_get(ami) + if not amb: + return {'CANCELLED'} + + # Copy binding. + amb_new = ami.bindings.new_from_binding(amb) + if not amb_new: + return {'CANCELLED'} + + # Select newly created binding. + ami.selected_binding = len(ami.bindings) - 1 + + return {'FINISHED'} + + +class VIEW3D_OT_vr_actionbindings_clear(Operator): + bl_idname = "view3d.vr_actionbindings_clear" + bl_label = "Clear VR Action Bindings" + bl_description = "Delete all VR action bindings from the action" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = action_map.vr_actionmap_selected_get(session_settings) + if not am: + return {'CANCELLED'} + + ami = action_map.vr_actionmap_item_selected_get(am) + if not ami: + return {'CANCELLED'} + + while ami.bindings: + ami.bindings.remove(ami.bindings[0]) + + return {'FINISHED'} + + +class VIEW3D_OT_vr_actionbinding_component_path_add(Operator): + bl_idname = "view3d.vr_actionbinding_component_path_add" + bl_label = "Add Component Path" + bl_description = "Add a new component path to the VR action binding" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = action_map.vr_actionmap_selected_get(session_settings) + if not am: + return {'CANCELLED'} + + ami = action_map.vr_actionmap_item_selected_get(am) + if not ami: + return {'CANCELLED'} + + amb = action_map.vr_actionmap_binding_selected_get(ami) + if not amb: + return {'CANCELLED'} + + component_path = amb.component_paths.new("/") + if not component_path: + return {'CANCELLED'} + + # Select newly created component path. + amb.selected_component_path = len(amb.component_paths) - 1 + + return {'FINISHED'} + + +class VIEW3D_OT_vr_actionbinding_component_path_remove(Operator): + bl_idname = "view3d.vr_actionbinding_component_path_remove" + bl_label = "Remove Component Path" + bl_description = "Delete the selected component path from the VR action binding" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = action_map.vr_actionmap_selected_get(session_settings) + if not am: + return {'CANCELLED'} + + ami = action_map.vr_actionmap_item_selected_get(am) + if not ami: + return {'CANCELLED'} + + amb = action_map.vr_actionmap_binding_selected_get(ami) + if not amb: + return {'CANCELLED'} + + component_path = action_map.vr_actionmap_component_path_selected_get(amb) + if not component_path: + return {'CANCELLED'} + + amb.component_paths.remove(component_path) + + return {'FINISHED'} + + +class VIEW3D_OT_vr_actionbinding_component_paths_clear(Operator): + bl_idname = "view3d.vr_actionbinding_component_paths_clear" + bl_label = "Clear Component Paths" + bl_description = "Delete all component paths from the VR action binding" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + am = action_map.vr_actionmap_selected_get(session_settings) + if not am: + return {'CANCELLED'} + + ami = action_map.vr_actionmap_item_selected_get(am) + if not ami: + return {'CANCELLED'} + + amb = action_map.vr_actionmap_binding_selected_get(ami) + if not amb: + return {'CANCELLED'} + + while amb.component_paths: + amb.component_paths.remove(amb.component_paths[0]) + + return {'FINISHED'} + + ### Gizmos. class VIEW3D_GT_vr_camera_cone(Gizmo): bl_idname = "VIEW_3D_GT_vr_camera_cone" @@ -486,6 +1026,29 @@ classes = ( VIEW3D_OT_cursor_to_vr_landmark, VIEW3D_OT_update_vr_landmark, + VIEW3D_OT_vr_actionmap_add, + VIEW3D_OT_vr_actionmap_remove, + VIEW3D_OT_vr_actionmap_activate, + VIEW3D_OT_vr_actionmaps_defaults_load, + VIEW3D_OT_vr_actionmaps_import, + VIEW3D_OT_vr_actionmaps_export, + VIEW3D_OT_vr_actionmap_copy, + VIEW3D_OT_vr_actionmaps_clear, + VIEW3D_OT_vr_action_add, + VIEW3D_OT_vr_action_remove, + VIEW3D_OT_vr_action_copy, + VIEW3D_OT_vr_actions_clear, + VIEW3D_OT_vr_action_user_path_add, + VIEW3D_OT_vr_action_user_path_remove, + VIEW3D_OT_vr_action_user_paths_clear, + VIEW3D_OT_vr_actionbinding_add, + VIEW3D_OT_vr_actionbinding_remove, + VIEW3D_OT_vr_actionbinding_copy, + VIEW3D_OT_vr_actionbindings_clear, + VIEW3D_OT_vr_actionbinding_component_path_add, + VIEW3D_OT_vr_actionbinding_component_path_remove, + VIEW3D_OT_vr_actionbinding_component_paths_clear, + VIEW3D_GT_vr_camera_cone, VIEW3D_GT_vr_controller_grip, VIEW3D_GT_vr_controller_aim, -- cgit v1.2.3