diff options
-rw-r--r-- | viewport_vr_preview/action_map.py | 104 | ||||
-rw-r--r-- | viewport_vr_preview/action_map_io.py | 12 | ||||
-rw-r--r-- | viewport_vr_preview/configs/default.py | 110 | ||||
-rw-r--r-- | viewport_vr_preview/defaults.py | 459 | ||||
-rw-r--r-- | viewport_vr_preview/gui.py | 482 | ||||
-rw-r--r-- | viewport_vr_preview/operators.py | 689 | ||||
-rw-r--r-- | viewport_vr_preview/properties.py | 53 |
7 files changed, 1818 insertions, 91 deletions
diff --git a/viewport_vr_preview/action_map.py b/viewport_vr_preview/action_map.py index 6c62c05d..ce87873c 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,14 +89,22 @@ 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 + # Check for action maps where all bindings require OpenXR extensions. + if am.name == defaults.VRDefaultActionmaps.TRACKER.value: + if not session_settings.enable_vive_tracker_extension: #scene.vr_actions_enable_vive_tracker: + continue + ok = session_state.action_set_create(context, am) if not ok: return @@ -101,7 +153,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 +161,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 +180,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, @@ -149,16 +196,21 @@ def register(): description="Enable bindings for the HTC Vive Focus 3 controllers. Note that this may not be supported by all OpenXR runtimes", default=False, ) + # Stored in session settings to use in session creation as a workaround for SteamVR controller/tracker compatibility issues. + #bpy.types.Scene.vr_actions_enable_vive_tracker = bpy.props.BoolProperty( + # description="Enable bindings for the HTC Vive Trackers. Note that this may not be supported by all OpenXR runtimes", + # default=False, + #) bpy.app.handlers.xr_session_start_pre.append(vr_create_actions) 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 del bpy.types.Scene.vr_actions_enable_vive_focus + #del bpy.types.Scene.vr_actions_enable_vive_tracker bpy.app.handlers.xr_session_start_pre.remove(vr_create_actions) diff --git a/viewport_vr_preview/action_map_io.py b/viewport_vr_preview/action_map_io.py index 072947b8..92b5b676 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/configs/default.py b/viewport_vr_preview/configs/default.py index f63d661c..1b5ff7fe 100644 --- a/viewport_vr_preview/configs/default.py +++ b/viewport_vr_preview/configs/default.py @@ -32,22 +32,22 @@ actionconfig_data = \ ], }, ), - ("teleport", {"type": 'FLOAT', "user_paths": ['/user/hand/left', '/user/hand/right'], "op": 'wm.xr_navigation_teleport', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'}, + ("teleport", {"type": 'FLOAT', "user_paths": ['/user/hand/left'], "op": 'wm.xr_navigation_teleport', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'}, {"op_properties": [("interpolation", 0.9), ("color", (0.0, 1.0, 1.0, 1.0)), ], }, {"bindings": - [("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_paths": ['/input/trigger/value', '/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), - ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_paths": ['/input/trigger/value', '/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), - ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_paths": ['/input/trigger/value', '/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), - ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_paths": ['/input/trigger/value', '/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), - ("simple", {"profile": '/interaction_profiles/khr/simple_controller', "component_paths": ['/input/select/click', '/input/select/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), - ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_paths": ['/input/trigger/value', '/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), - ("vive_cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_paths": ['/input/trigger/value', '/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), - ("vive_focus", {"profile": '/interaction_profiles/htc/vive_focus3_controller', "component_paths": ['/input/trigger/value', '/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), - ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_paths": ['/input/trigger/value', '/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + [("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_paths": ['/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_paths": ['/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_paths": ['/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_paths": ['/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("simple", {"profile": '/interaction_profiles/khr/simple_controller', "component_paths": ['/input/select/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_paths": ['/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("vive_cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_paths": ['/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("vive_focus", {"profile": '/interaction_profiles/htc/vive_focus3_controller', "component_paths": ['/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_paths": ['/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), ], }, ), @@ -225,6 +225,33 @@ actionconfig_data = \ ], }, {"bindings": + [("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_paths": ['/input/home/click', '/input/home/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_paths": ['/input/thumbstick/click', '/input/thumbstick/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_paths": ['/input/thumbstick/click', '/input/thumbstick/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_paths": ['/input/thumbstick/click', '/input/thumbstick/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_paths": ['/input/trackpad/click', '/input/trackpad/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("vive_cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_paths": ['/input/thumbstick/click', '/input/thumbstick/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("vive_focus", {"profile": '/interaction_profiles/htc/vive_focus3_controller', "component_paths": ['/input/thumbstick/click', '/input/thumbstick/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_paths": ['/input/thumbstick/click', '/input/thumbstick/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ], + }, + ), + ("select", {"type": 'FLOAT', "user_paths": ['/user/hand/right'], "op": 'wm.xr_select_raycast', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'}, None, + {"bindings": + [("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_paths": ['/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_paths": ['/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_paths": ['/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_paths": ['/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("simple", {"profile": '/interaction_profiles/khr/simple_controller', "component_paths": ['/input/select/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("vive", {"profile": '/interaction_profiles/htc/vive_controller', "component_paths": ['/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("vive_cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_paths": ['/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("vive_focus", {"profile": '/interaction_profiles/htc/vive_focus3_controller', "component_paths": ['/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_paths": ['/input/trigger/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ], + }, + ), + ("transform", {"type": 'FLOAT', "user_paths": ['/user/hand/left', '/user/hand/right'], "op": 'wm.xr_transform_grab', "op_mode": 'MODAL', "bimanual": 'True', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'}, None, + {"bindings": [("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_paths": ['/input/back/click', '/input/back/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_paths": ['/input/a/click', '/input/a/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_paths": ['/input/x/click', '/input/a/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), @@ -236,6 +263,30 @@ actionconfig_data = \ ], }, ), + ("undo", {"type": 'FLOAT', "user_paths": ['/user/hand/left'], "op": 'ed.undo', "op_mode": 'PRESS', "bimanual": 'False', "haptic_name": 'haptic', "haptic_match_user_paths": 'True', "haptic_duration": '0.30000001192092896', "haptic_frequency": '3000.0', "haptic_amplitude": '0.5', "haptic_mode": 'PRESS'}, None, + {"bindings": + [("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_paths": ['/input/volume_down/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_paths": ['/input/b/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_paths": ['/input/y/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_paths": ['/input/y/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("vive_cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_paths": ['/input/y/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("vive_focus", {"profile": '/interaction_profiles/htc/vive_focus3_controller', "component_paths": ['/input/y/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_paths": ['/input/trackpad/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ], + }, + ), + ("redo", {"type": 'FLOAT', "user_paths": ['/user/hand/right'], "op": 'ed.redo', "op_mode": 'PRESS', "bimanual": 'False', "haptic_name": 'haptic', "haptic_match_user_paths": 'True', "haptic_duration": '0.30000001192092896', "haptic_frequency": '3000.0', "haptic_amplitude": '0.5', "haptic_mode": 'PRESS'}, None, + {"bindings": + [("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_paths": ['/input/volume_up/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("index", {"profile": '/interaction_profiles/valve/index_controller', "component_paths": ['/input/b/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("oculus", {"profile": '/interaction_profiles/oculus/touch_controller', "component_paths": ['/input/b/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("reverb_g2", {"profile": '/interaction_profiles/hp/mixed_reality_controller', "component_paths": ['/input/b/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("vive_cosmos", {"profile": '/interaction_profiles/htc/vive_cosmos_controller', "component_paths": ['/input/b/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("vive_focus", {"profile": '/interaction_profiles/htc/vive_focus3_controller', "component_paths": ['/input/b/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ("wmr", {"profile": '/interaction_profiles/microsoft/motion_controller', "component_paths": ['/input/trackpad/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ], + }, + ), ("haptic", {"type": 'VIBRATION', "user_paths": ['/user/hand/left', '/user/hand/right']}, None, {"bindings": [("huawei", {"profile": '/interaction_profiles/huawei/controller', "component_paths": ['/output/haptic', '/output/haptic']}), @@ -263,13 +314,13 @@ actionconfig_data = \ ], }, {"bindings": - [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_paths": ['/input/trigger_right/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_paths": ['/input/trigger_left/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), ], }, ), ("fly", {"type": 'FLOAT', "user_paths": ['/user/gamepad'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'}, None, {"bindings": - [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_paths": ['/input/trigger_left/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_paths": ['/input/a/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), ], }, ), @@ -373,7 +424,29 @@ actionconfig_data = \ ], }, {"bindings": - [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_paths": ['/input/a/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_paths": ['/input/y/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ], + }, + ), + ("select", {"type": 'FLOAT', "user_paths": ['/user/gamepad'], "op": 'wm.xr_select_raycast', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'}, + {"op_properties": + [("from_viewer", True), + ], + }, + {"bindings": + [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_paths": ['/input/trigger_right/value'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ], + }, + ), + ("undo", {"type": 'FLOAT', "user_paths": ['/user/gamepad'], "op": 'ed.undo', "op_mode": 'PRESS', "bimanual": 'False', "haptic_name": 'haptic_left', "haptic_match_user_paths": 'True', "haptic_duration": '0.30000001192092896', "haptic_frequency": '3000.0', "haptic_amplitude": '0.5', "haptic_mode": 'PRESS'}, None, + {"bindings": + [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_paths": ['/input/x/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), + ], + }, + ), + ("redo", {"type": 'FLOAT', "user_paths": ['/user/gamepad'], "op": 'ed.redo', "op_mode": 'PRESS', "bimanual": 'False', "haptic_name": 'haptic_right', "haptic_match_user_paths": 'True', "haptic_duration": '0.30000001192092896', "haptic_frequency": '3000.0', "haptic_amplitude": '0.5', "haptic_mode": 'PRESS'}, None, + {"bindings": + [("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_paths": ['/input/b/click'], "threshold": '0.30000001192092896', "axis_region": 'ANY'}), ], }, ), @@ -404,6 +477,17 @@ actionconfig_data = \ ], }, ), + ("blender_default_tracker", + {"items": + [("tracker_pose", {"type": 'POSE', "user_paths": ['/user/vive_tracker_htcx/role/left_foot', '/user/vive_tracker_htcx/role/right_foot', '/user/vive_tracker_htcx/role/left_shoulder', '/user/vive_tracker_htcx/role/right_shoulder', '/user/vive_tracker_htcx/role/left_elbow', '/user/vive_tracker_htcx/role/right_elbow', '/user/vive_tracker_htcx/role/left_knee', '/user/vive_tracker_htcx/role/right_knee', '/user/vive_tracker_htcx/role/waist', '/user/vive_tracker_htcx/role/chest', '/user/vive_tracker_htcx/role/camera', '/user/vive_tracker_htcx/role/keyboard'], "pose_is_controller_grip": 'True', "pose_is_controller_aim": 'True'}, None, + {"bindings": + [("vive_tracker", {"profile": '/interaction_profiles/htc/vive_tracker_htcx', "component_paths": ['/input/grip/pose', '/input/grip/pose', '/input/grip/pose', '/input/grip/pose', '/input/grip/pose', '/input/grip/pose', '/input/grip/pose', '/input/grip/pose', '/input/grip/pose', '/input/grip/pose', '/input/grip/pose', '/input/grip/pose'], "pose_location": '(0.0, 0.0, 0.0)', "pose_rotation": '(0.0, 0.0, 0.0)'}), + ], + }, + ), + ], + }, + ), ] diff --git a/viewport_vr_preview/defaults.py b/viewport_vr_preview/defaults.py index b8e76743..aa940c2d 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 @@ -19,12 +18,14 @@ import os.path class VRDefaultActionmaps(Enum): DEFAULT = "blender_default" GAMEPAD = "blender_default_gamepad" + TRACKER = "blender_default_tracker" # Default actions. class VRDefaultActions(Enum): CONTROLLER_GRIP = "controller_grip" CONTROLLER_AIM = "controller_aim" + TRACKER_POSE = "tracker_pose" TELEPORT = "teleport" NAV_GRAB = "nav_grab" FLY = "fly" @@ -37,6 +38,10 @@ class VRDefaultActions(Enum): FLY_TURNLEFT = "fly_turnleft" FLY_TURNRIGHT = "fly_turnright" NAV_RESET = "nav_reset" + SELECT = "select" + TRANSFORM = "transform" + UNDO = "undo" + REDO = "redo" HAPTIC = "haptic" HAPTIC_LEFT = "haptic_left" HAPTIC_RIGHT = "haptic_right" @@ -55,6 +60,7 @@ class VRDefaultActionbindings(Enum): VIVE = "vive" VIVE_COSMOS = "vive_cosmos" VIVE_FOCUS = "vive_focus" + VIVE_TRACKER = "vive_tracker" WMR = "wmr" @@ -68,11 +74,12 @@ class VRDefaultActionprofiles(Enum): VIVE = "/interaction_profiles/htc/vive_controller" VIVE_COSMOS = "/interaction_profiles/htc/vive_cosmos_controller" VIVE_FOCUS = "/interaction_profiles/htc/vive_focus3_controller" + VIVE_TRACKER = "/interaction_profiles/htc/vive_tracker_htcx" 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 +193,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 @@ -336,8 +343,7 @@ def vr_defaults_create_default(session_state): ami = vr_defaults_action_add(am, VRDefaultActions.TELEPORT.value, - ["/user/hand/left", - "/user/hand/right"], + ["/user/hand/left"], "wm.xr_navigation_teleport", 'MODAL', False, @@ -351,72 +357,63 @@ def vr_defaults_create_default(session_state): vr_defaults_actionbinding_add(ami, VRDefaultActionbindings.HUAWEI.value, VRDefaultActionprofiles.HUAWEI.value, - ["/input/trigger/value", - "/input/trigger/value"], + ["/input/trigger/value"], 0.3, 'ANY', 'ANY') vr_defaults_actionbinding_add(ami, VRDefaultActionbindings.INDEX.value, VRDefaultActionprofiles.INDEX.value, - ["/input/trigger/value", - "/input/trigger/value"], + ["/input/trigger/value"], 0.3, 'ANY', 'ANY') vr_defaults_actionbinding_add(ami, VRDefaultActionbindings.OCULUS.value, VRDefaultActionprofiles.OCULUS.value, - ["/input/trigger/value", - "/input/trigger/value"], + ["/input/trigger/value"], 0.3, 'ANY', 'ANY') vr_defaults_actionbinding_add(ami, VRDefaultActionbindings.REVERB_G2.value, VRDefaultActionprofiles.REVERB_G2.value, - ["/input/trigger/value", - "/input/trigger/value"], + ["/input/trigger/value"], 0.3, 'ANY', 'ANY') vr_defaults_actionbinding_add(ami, VRDefaultActionbindings.SIMPLE.value, VRDefaultActionprofiles.SIMPLE.value, - ["/input/select/click", - "/input/select/click"], + ["/input/select/click"], 0.3, 'ANY', 'ANY') vr_defaults_actionbinding_add(ami, VRDefaultActionbindings.VIVE.value, VRDefaultActionprofiles.VIVE.value, - ["/input/trigger/value", - "/input/trigger/value"], + ["/input/trigger/value"], 0.3, 'ANY', 'ANY') vr_defaults_actionbinding_add(ami, VRDefaultActionbindings.VIVE_COSMOS.value, VRDefaultActionprofiles.VIVE_COSMOS.value, - ["/input/trigger/value", - "/input/trigger/value"], + ["/input/trigger/value"], 0.3, 'ANY', 'ANY') vr_defaults_actionbinding_add(ami, VRDefaultActionbindings.VIVE_FOCUS.value, VRDefaultActionprofiles.VIVE_FOCUS.value, - ["/input/trigger/value", - "/input/trigger/value"], + ["/input/trigger/value"], 0.3, 'ANY', 'ANY') vr_defaults_actionbinding_add(ami, VRDefaultActionbindings.WMR.value, VRDefaultActionprofiles.WMR.value, - ["/input/trigger/value", - "/input/trigger/value"], + ["/input/trigger/value"], 0.3, 'ANY', 'ANY') @@ -1085,6 +1082,162 @@ def vr_defaults_create_default(session_state): vr_defaults_actionbinding_add(ami, VRDefaultActionbindings.HUAWEI.value, VRDefaultActionprofiles.HUAWEI.value, + ["/input/home/click", + "/input/home/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.INDEX.value, + VRDefaultActionprofiles.INDEX.value, + ["/input/thumbstick/click", + "/input/thumbstick/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.OCULUS.value, + VRDefaultActionprofiles.OCULUS.value, + ["/input/thumbstick/click", + "/input/thumbstick/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.REVERB_G2.value, + VRDefaultActionprofiles.REVERB_G2.value, + ["/input/thumbstick/click", + "/input/thumbstick/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.VIVE.value, + VRDefaultActionprofiles.VIVE.value, + ["/input/trackpad/click", + "/input/trackpad/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.VIVE_COSMOS.value, + VRDefaultActionprofiles.VIVE_COSMOS.value, + ["/input/thumbstick/click", + "/input/thumbstick/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.VIVE_FOCUS.value, + VRDefaultActionprofiles.VIVE_FOCUS.value, + ["/input/thumbstick/click", + "/input/thumbstick/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.WMR.value, + VRDefaultActionprofiles.WMR.value, + ["/input/thumbstick/click", + "/input/thumbstick/click"], + 0.3, + 'ANY', + 'ANY') + + ami = vr_defaults_action_add(am, + VRDefaultActions.SELECT.value, + ["/user/hand/right"], + "wm.xr_select_raycast", + 'MODAL', + False, + "", + False, + 0.0, + 0.0, + 0.0, + 'PRESS') + if ami: + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.HUAWEI.value, + VRDefaultActionprofiles.HUAWEI.value, + ["/input/trigger/value"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.INDEX.value, + VRDefaultActionprofiles.INDEX.value, + ["/input/trigger/value"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.OCULUS.value, + VRDefaultActionprofiles.OCULUS.value, + ["/input/trigger/value"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.REVERB_G2.value, + VRDefaultActionprofiles.REVERB_G2.value, + ["/input/trigger/value"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.SIMPLE.value, + VRDefaultActionprofiles.SIMPLE.value, + ["/input/select/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.VIVE.value, + VRDefaultActionprofiles.VIVE.value, + ["/input/trigger/value"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.VIVE_COSMOS.value, + VRDefaultActionprofiles.VIVE_COSMOS.value, + ["/input/trigger/value"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.VIVE_FOCUS.value, + VRDefaultActionprofiles.VIVE_FOCUS.value, + ["/input/trigger/value"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.WMR.value, + VRDefaultActionprofiles.WMR.value, + ["/input/trigger/value"], + 0.3, + 'ANY', + 'ANY') + + ami = vr_defaults_action_add(am, + VRDefaultActions.TRANSFORM.value, + ["/user/hand/left", + "/user/hand/right"], + "wm.xr_transform_grab", + 'MODAL', + True, + "", + False, + 0.0, + 0.0, + 0.0, + 'PRESS') + if ami: + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.HUAWEI.value, + VRDefaultActionprofiles.HUAWEI.value, ["/input/back/click", "/input/back/click"], 0.3, @@ -1147,6 +1300,132 @@ def vr_defaults_create_default(session_state): 'ANY', 'ANY') + ami = vr_defaults_action_add(am, + VRDefaultActions.UNDO.value, + ["/user/hand/left"], + "ed.undo", + 'PRESS', + False, + "haptic", + True, + 0.3, + 3000.0, + 0.5, + 'PRESS') + if ami: + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.HUAWEI.value, + VRDefaultActionprofiles.HUAWEI.value, + ["/input/volume_down/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.INDEX.value, + VRDefaultActionprofiles.INDEX.value, + ["/input/b/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.OCULUS.value, + VRDefaultActionprofiles.OCULUS.value, + ["/input/y/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.REVERB_G2.value, + VRDefaultActionprofiles.REVERB_G2.value, + ["/input/y/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.VIVE_COSMOS.value, + VRDefaultActionprofiles.VIVE_COSMOS.value, + ["/input/y/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.VIVE_FOCUS.value, + VRDefaultActionprofiles.VIVE_FOCUS.value, + ["/input/y/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.WMR.value, + VRDefaultActionprofiles.WMR.value, + ["/input/trackpad/click"], + 0.3, + 'ANY', + 'ANY') + + ami = vr_defaults_action_add(am, + VRDefaultActions.REDO.value, + ["/user/hand/right"], + "ed.redo", + 'PRESS', + False, + "haptic", + True, + 0.3, + 3000.0, + 0.5, + 'PRESS') + if ami: + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.HUAWEI.value, + VRDefaultActionprofiles.HUAWEI.value, + ["/input/volume_up/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.INDEX.value, + VRDefaultActionprofiles.INDEX.value, + ["/input/b/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.OCULUS.value, + VRDefaultActionprofiles.OCULUS.value, + ["/input/b/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.REVERB_G2.value, + VRDefaultActionprofiles.REVERB_G2.value, + ["/input/b/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.VIVE_COSMOS.value, + VRDefaultActionprofiles.VIVE_COSMOS.value, + ["/input/b/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.VIVE_FOCUS.value, + VRDefaultActionprofiles.VIVE_FOCUS.value, + ["/input/b/click"], + 0.3, + 'ANY', + 'ANY') + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.WMR.value, + VRDefaultActionprofiles.WMR.value, + ["/input/trackpad/click"], + 0.3, + 'ANY', + 'ANY') + ami = vr_defaults_haptic_action_add(am, VRDefaultActions.HAPTIC.value, ["/user/hand/left", @@ -1199,8 +1478,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, @@ -1219,7 +1498,7 @@ def vr_defaults_create_default_gamepad(session_state): vr_defaults_actionbinding_add(ami, VRDefaultActionbindings.GAMEPAD.value, VRDefaultActionprofiles.GAMEPAD.value, - ["/input/trigger_right/value"], + ["/input/trigger_left/value"], 0.3, 'ANY', 'ANY') @@ -1240,7 +1519,7 @@ def vr_defaults_create_default_gamepad(session_state): vr_defaults_actionbinding_add(ami, VRDefaultActionbindings.GAMEPAD.value, VRDefaultActionprofiles.GAMEPAD.value, - ["/input/trigger_left/value"], + ["/input/a/click"], 0.3, 'ANY', 'ANY') @@ -1429,7 +1708,70 @@ def vr_defaults_create_default_gamepad(session_state): vr_defaults_actionbinding_add(ami, VRDefaultActionbindings.GAMEPAD.value, VRDefaultActionprofiles.GAMEPAD.value, - ["/input/a/click"], + ["/input/y/click"], + 0.3, + 'ANY', + 'ANY') + + ami = vr_defaults_action_add(am, + VRDefaultActions.SELECT.value, + ["/user/gamepad"], + "wm.xr_select_raycast", + 'MODAL', + False, + "", + False, + 0.0, + 0.0, + 0.0, + 'PRESS') + if ami: + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.GAMEPAD.value, + VRDefaultActionprofiles.GAMEPAD.value, + ["/input/trigger_right/value"], + 0.3, + 'ANY', + 'ANY') + + ami = vr_defaults_action_add(am, + VRDefaultActions.UNDO.value, + ["/user/gamepad"], + "ed.undo", + 'PRESS', + False, + "haptic_left", + True, + 0.3, + 3000.0, + 0.5, + 'PRESS') + if ami: + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.GAMEPAD.value, + VRDefaultActionprofiles.GAMEPAD.value, + ["/input/x/click"], + 0.3, + 'ANY', + 'ANY') + + ami = vr_defaults_action_add(am, + VRDefaultActions.REDO.value, + ["/user/gamepad"], + "ed.redo", + 'PRESS', + False, + "haptic_right", + True, + 0.3, + 3000.0, + 0.5, + 'PRESS') + if ami: + vr_defaults_actionbinding_add(ami, + VRDefaultActionbindings.GAMEPAD.value, + VRDefaultActionprofiles.GAMEPAD.value, + ["/input/b/click"], 0.3, 'ANY', 'ANY') @@ -1471,16 +1813,60 @@ def vr_defaults_create_default_gamepad(session_state): ["/output/haptic_right_trigger"]) +def vr_defaults_create_default_tracker(session_settings): + am = vr_defaults_actionmap_add(session_settings, + VRDefaultActionmaps.TRACKER.value) + if not am: + return + + ami = vr_defaults_pose_action_add(am, + VRDefaultActions.TRACKER_POSE.value, + [#"/user/vive_tracker_htcx/role/handheld_object", # SteamVR (1.21) fails to assign interaction profile. + "/user/vive_tracker_htcx/role/left_foot", + "/user/vive_tracker_htcx/role/right_foot", + "/user/vive_tracker_htcx/role/left_shoulder", + "/user/vive_tracker_htcx/role/right_shoulder", + "/user/vive_tracker_htcx/role/left_elbow", + "/user/vive_tracker_htcx/role/right_elbow", + "/user/vive_tracker_htcx/role/left_knee", + "/user/vive_tracker_htcx/role/right_knee", + "/user/vive_tracker_htcx/role/waist", + "/user/vive_tracker_htcx/role/chest", + "/user/vive_tracker_htcx/role/camera", + "/user/vive_tracker_htcx/role/keyboard"], + True, + True) + if ami: + vr_defaults_pose_actionbinding_add(ami, + VRDefaultActionbindings.VIVE_TRACKER.value, + VRDefaultActionprofiles.VIVE_TRACKER.value, + [#"/input/grip/pose", + "/input/grip/pose", + "/input/grip/pose", + "/input/grip/pose", + "/input/grip/pose", + "/input/grip/pose", + "/input/grip/pose", + "/input/grip/pose", + "/input/grip/pose", + "/input/grip/pose", + "/input/grip/pose", + "/input/grip/pose", + "/input/grip/pose"], + (0, 0, 0), + (0, 0, 0)) + + def vr_get_default_config_path(): filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "configs") 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 +1878,12 @@ 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) + vr_defaults_create_default_tracker(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..d7b77a30 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,31 +156,474 @@ 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 + session_settings = context.window_manager.xr_session_settings + 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") + col.prop(session_settings, "enable_vive_tracker_extension", text="HTC Vive Tracker") + #col.prop(scene, "vr_actions_enable_vive_tracker", text="HTC Vive Tracker") col.prop(scene, "vr_actions_enable_huawei", text="Huawei") +### Motion capture. +class VIEW3D_UL_vr_mocap_objects(UIList): + def draw_item(self, context, layout, _data, item, icon, _active_data, + _active_propname, index): + scene_mocap_ob = item + + layout.emboss = 'NONE' + + if scene_mocap_ob.object: + layout.prop(scene_mocap_ob.object, "name", text="") + else: + layout.label(icon='X') + + +class VIEW3D_MT_vr_mocap_object_menu(Menu): + bl_label = "Motion Capture Object Controls" + + def draw(self, _context): + layout = self.layout + + layout.operator("view3d.vr_mocap_objects_enable") + layout.operator("view3d.vr_mocap_objects_disable") + layout.operator("view3d.vr_mocap_objects_clear") + layout.operator("view3d.vr_mocap_object_help") + + +class VIEW3D_PT_vr_motion_capture(Panel): + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = "VR" + bl_label = "Motion Capture" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + session_settings = context.window_manager.xr_session_settings + scene = context.scene + + col = layout.column(align=True) + col.label(icon='ERROR', text="Note:") + col.label(text="Settings here may have a significant") + col.label(text="performance impact!") + + layout.separator() + + row = layout.row() + row.template_list("VIEW3D_UL_vr_mocap_objects", "", scene, "vr_mocap_objects", + session_settings, "selected_mocap_object", rows=3) + + col = row.column(align=True) + col.operator("view3d.vr_mocap_object_add", icon='ADD', text="") + col.operator("view3d.vr_mocap_object_remove", icon='REMOVE', text="") + + col.menu("VIEW3D_MT_vr_mocap_object_menu", icon='DOWNARROW_HLT', text="") + + mocap_ob = properties.vr_mocap_object_selected_get(session_settings) + scene_mocap_ob = properties.vr_scene_mocap_object_selected_get(scene, session_settings) + + if mocap_ob and scene_mocap_ob: + row = layout.row() + col = row.column(align=True) + + col.prop(scene_mocap_ob, "object", text="Object") + col.prop(mocap_ob, "user_path", text="User Path") + col.prop(mocap_ob, "enable", text="Enable") + col.prop(mocap_ob, "autokey", text="Auto Key") + col.prop(mocap_ob, "location_offset", text="Location Offset") + col.prop(mocap_ob, "rotation_offset", text="Rotation Offset") + + ### Viewport feedback. class VIEW3D_PT_vr_viewport_feedback(Panel): bl_space_type = 'VIEW_3D' @@ -228,11 +671,32 @@ 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_motion_capture, 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, + + VIEW3D_UL_vr_mocap_objects, + VIEW3D_MT_vr_mocap_object_menu, ) diff --git a/viewport_vr_preview/operators.py b/viewport_vr_preview/operators.py index 69100762..6d45c500 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,659 @@ 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'} + + +### Motion capture. +class VIEW3D_OT_vr_mocap_object_add(Operator): + bl_idname = "view3d.vr_mocap_object_add" + bl_label = "Add VR Motion Capture Object" + bl_description = "Add a new VR motion capture object" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + mocap_ob = session_settings.mocap_objects.new(None) + if not mocap_ob: + return {'CANCELLED'} + + # Enable object binding by default. + mocap_ob.enable = True + + context.scene.vr_mocap_objects.add() + + # Select newly created object. + session_settings.selected_mocap_object = len(session_settings.mocap_objects) - 1 + + return {'FINISHED'} + + +class VIEW3D_OT_vr_mocap_object_remove(Operator): + bl_idname = "view3d.vr_mocap_object_remove" + bl_label = "Remove VR Motion Capture Object" + bl_description = "Delete the selected VR motion capture object" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + mocap_ob = properties.vr_mocap_object_selected_get(session_settings) + if not mocap_ob: + return {'CANCELLED'} + + context.scene.vr_mocap_objects.remove(session_settings.selected_mocap_object) + + session_settings.mocap_objects.remove(mocap_ob) + + return {'FINISHED'} + + +class VIEW3D_OT_vr_mocap_objects_enable(Operator): + bl_idname = "view3d.vr_mocap_objects_enable" + bl_label = "Enable VR Motion Capture Objects" + bl_description = "Enable all VR motion capture objects" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + for mocap_ob in session_settings.mocap_objects: + mocap_ob.enable = True + + return {'FINISHED'} + + +class VIEW3D_OT_vr_mocap_objects_disable(Operator): + bl_idname = "view3d.vr_mocap_objects_disable" + bl_label = "Disable VR Motion Capture Objects" + bl_description = "Disable all VR motion capture objects" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + for mocap_ob in session_settings.mocap_objects: + mocap_ob.enable = False + + return {'FINISHED'} + + +class VIEW3D_OT_vr_mocap_objects_clear(Operator): + bl_idname = "view3d.vr_mocap_objects_clear" + bl_label = "Clear VR Motion Capture Objects" + bl_description = "Delete all VR motion capture objects from the scene" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + session_settings = context.window_manager.xr_session_settings + + context.scene.vr_mocap_objects.clear() + + while session_settings.mocap_objects: + session_settings.mocap_objects.remove(session_settings.mocap_objects[0]) + + return {'FINISHED'} + + +class VIEW3D_OT_vr_mocap_object_help(Operator): + bl_idname = "view3d.vr_mocap_object_help" + bl_label = "Help" + bl_description = "Display information about VR motion capture objects" + bl_options = {'REGISTER'} + + def execute(self, context): + info_header = "Common User Paths:" + info_headset = "Headset - /user/head" + info_left_controller = "Left Controller* - /user/hand/left" + info_right_controller = "Right Controller* - /user/hand/right" + info_note = "*Requires VR actions for controller poses" + + def draw(self, context): + self.layout.label(text=info_header) + self.layout.label(text=info_headset) + self.layout.label(text=info_left_controller) + self.layout.label(text=info_right_controller) + self.layout.label(text=info_note) + + context.window_manager.popup_menu(draw, title="Motion Capture Objects", icon='INFO') + + return {'FINISHED'} + + ### Gizmos. class VIEW3D_GT_vr_camera_cone(Gizmo): bl_idname = "VIEW_3D_GT_vr_camera_cone" @@ -486,6 +1143,36 @@ 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_OT_vr_mocap_object_add, + VIEW3D_OT_vr_mocap_object_remove, + VIEW3D_OT_vr_mocap_objects_enable, + VIEW3D_OT_vr_mocap_objects_disable, + VIEW3D_OT_vr_mocap_objects_clear, + VIEW3D_OT_vr_mocap_object_help, + VIEW3D_GT_vr_camera_cone, VIEW3D_GT_vr_controller_grip, VIEW3D_GT_vr_controller_aim, diff --git a/viewport_vr_preview/properties.py b/viewport_vr_preview/properties.py index 55a4d4eb..20aa9d49 100644 --- a/viewport_vr_preview/properties.py +++ b/viewport_vr_preview/properties.py @@ -194,8 +194,53 @@ class VRLandmark(PropertyGroup): ) +### Motion capture. +def vr_mocap_object_selected_get(session_settings): + mocap_objects = session_settings.mocap_objects + return ( + None if (len(mocap_objects) < + 1) else mocap_objects[session_settings.selected_mocap_object] + ) + + +def vr_scene_mocap_object_selected_get(scene, session_settings): + mocap_objects = scene.vr_mocap_objects + return ( + None if (len(mocap_objects) < + 1) else mocap_objects[session_settings.selected_mocap_object] + ) + + +def vr_scene_mocap_object_update(self, context): + session_settings = context.window_manager.xr_session_settings + mocap_ob = vr_mocap_object_selected_get(session_settings) + if not mocap_ob: + return + + scene = context.scene + scene_mocap_ob = vr_scene_mocap_object_selected_get(scene, session_settings) + if not scene_mocap_ob: + return + + # Check for duplicate object. + if scene_mocap_ob.object and session_settings.mocap_objects.find(scene_mocap_ob.object): + scene_mocap_ob.object = None + return + + mocap_ob.object = scene_mocap_ob.object + + +class VRMotionCaptureObject(PropertyGroup): + object: bpy.props.PointerProperty( + name="Object", + type=bpy.types.Object, + update=vr_scene_mocap_object_update, + ) + + classes = ( VRLandmark, + VRMotionCaptureObject, ) @@ -213,6 +258,13 @@ def register(): bpy.types.Scene.vr_landmarks_active = bpy.props.IntProperty( update=vr_landmark_active_update, ) + # This scene collection property is needed instead of directly accessing + # XrSessionSettings.mocap_objects in the UI to avoid invalid pointers when + # deleting objects. + bpy.types.Scene.vr_mocap_objects = bpy.props.CollectionProperty( + name="Motion Capture Object", + type=VRMotionCaptureObject, + ) bpy.app.handlers.load_post.append(vr_ensure_default_landmark) @@ -224,5 +276,6 @@ def unregister(): 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_mocap_objects bpy.app.handlers.load_post.remove(vr_ensure_default_landmark) |