diff --git a/Preferences.py b/Preferences.py index ad8e0fa..1476471 100644 --- a/Preferences.py +++ b/Preferences.py @@ -1,16 +1,23 @@ import bpy +from bpy.props import ( + BoolProperty, + FloatProperty, +) +from bpy.types import AddonPreferences def update_node_keymap(self, context): wm = context.window_manager - kc = wm.keyconfigs.user - for key in kc.keymaps['Node Editor'].keymap_items: + active_kc = wm.keyconfigs.active + for key in active_kc.keymaps['Node Editor'].keymap_items: if ( key.idname == "wm.call_menu" and key.type == "RIGHTMOUSE" ): key.active = not key.active + addon_kc = wm.keyconfigs.addon + for key in addon_kc.keymaps['Node Editor'].keymap_items: if ( key.idname == "blui.right_mouse_navigation" and key.type == "RIGHTMOUSE" @@ -18,32 +25,25 @@ def update_node_keymap(self, context): key.active = not key.active -class RightMouseNavigationPreferences(bpy.types.AddonPreferences): +class RightMouseNavigationPreferences(AddonPreferences): bl_idname = __package__ - time: bpy.props.FloatProperty( + time: FloatProperty( name="Time Threshold", description="How long you have hold right mouse to open menu", - default=0.3, + default=1.0, min=0.1, - max=2 + max=10, ) - distance: bpy.props.FloatProperty( - name="Distance Threshold", - description="How far you have to move the mouse to trigger navigation", - default=20, - min=1, - max=200 - ) - - reset_cursor_on_exit: bpy.props.BoolProperty( + reset_cursor_on_exit: BoolProperty( name="Reset Cursor on Exit", - description="After exiting navigation, this determines if the cursor resets to where RMB was clicked (if checked) or stays in the center (if unchecked)", - default=True + description="After exiting navigation, this determines if the cursor stays " + "where RMB was clicked (if unchecked) or resets to the center (if checked)", + default=False, ) - enable_for_node_editors: bpy.props.BoolProperty( + enable_for_node_editors: BoolProperty( name="Enable for Node Editors", description="Right Mouse will pan the view / open the Node Add/Search Menu", default=False, @@ -55,9 +55,7 @@ def draw(self, context): box = layout.box() box.label(text="Menu / Movement", icon='DRIVER_DISTANCE') - row = box.row() - row.prop(self, 'time') - row.prop(self, 'distance') + box.prop(self, 'time') row = layout.row() box = row.box() @@ -65,4 +63,4 @@ def draw(self, context): box.prop(self, 'reset_cursor_on_exit') box = row.box() box.label(text='Node Editor', icon='NODETREE') - box.prop(self, 'enable_for_node_editors') \ No newline at end of file + box.prop(self, 'enable_for_node_editors') diff --git a/RightMouseNavigation.py b/RightMouseNavigation.py index e4a7892..1884dea 100644 --- a/RightMouseNavigation.py +++ b/RightMouseNavigation.py @@ -1,6 +1,10 @@ -import bpy, ctypes, math, sys +import bpy +from bpy.types import Operator +import ctypes +import sys -class BLUI_OT_right_mouse_navigation(bpy.types.Operator): + +class BLUI_OT_right_mouse_navigation(Operator): """Timer that decides whether to display a menu after Right Click""" bl_idname = "blui.right_mouse_navigation" bl_label = "Right Mouse Navigation" @@ -8,7 +12,6 @@ class BLUI_OT_right_mouse_navigation(bpy.types.Operator): _timer = None _count = 0 - _move_distance = 0 MOUSE_RIGHTUP = 0x0010 _finished = False _callMenu = False @@ -27,34 +30,44 @@ class BLUI_OT_right_mouse_navigation(bpy.types.Operator): 'PAINT_VERTEX': 'VIEW3D_PT_paint_vertex_context_menu', 'PAINT_WEIGHT': 'VIEW3D_PT_paint_weight_context_menu', 'PAINT_TEXTURE': 'VIEW3D_PT_paint_texture_context_menu', - 'SCULPT': 'VIEW3D_PT_sculpt_context_menu'} + 'SCULPT': 'VIEW3D_PT_sculpt_context_menu', + } def modal(self, context, event): preferences = context.preferences addon_prefs = preferences.addons[__package__].preferences + enable_nodes = addon_prefs.enable_for_node_editors + + space_type = context.space_data.type - if context.space_data.type == 'VIEW_3D': + if space_type == 'VIEW_3D': # Check if the Viewport is Perspective or Orthographic if bpy.context.region_data.is_perspective: self._ortho = False else: self._back_to_ortho = True - # The _finished Boolean acts as a flag to exit the modal loop, + # The _finished Boolean acts as a flag to exit the modal loop, # it is not made True until after the cancel function is called if self._finished: def reset_cursor(): # Reset blender window cursor to previous position - context.window.cursor_warp(self.view_x, self.view_y) + area = context.area + x = area.x + y = area.y + x += int(area.width / 2) + y += int(area.height / 2) + bpy.context.window.cursor_warp(x, y) if self._callMenu: # Always reset the cursor if menu is called, as that implies a canceled navigation - reset_cursor() + if addon_prefs.reset_cursor_on_exit and not space_type == 'NODE_EDITOR': + reset_cursor() self.callMenu(context) else: - # Exit of a full navigation. Only reset the cursor if the preference (default False) is enabled + # Exit of a full navigation. Only reset the cursor if the preference is enabled if addon_prefs.reset_cursor_on_exit: reset_cursor() @@ -63,44 +76,37 @@ def reset_cursor(): return {'CANCELLED'} - if context.space_data.type == 'VIEW_3D' or addon_prefs.enable_for_node_editors and context.space_data.type == 'NODE_EDITOR': - # Calculate mousemove distance before it reaches threshold - if event.type == "MOUSEMOVE" and self._move_distance < addon_prefs.distance: - xDelta = event.mouse_x - event.mouse_prev_x - yDelta = event.mouse_y - event.mouse_prev_y - deltaDistance = math.sqrt(xDelta*xDelta + yDelta*yDelta) - self._move_distance += deltaDistance - + if space_type == 'VIEW_3D' or space_type == 'NODE_EDITOR' and enable_nodes: if event.type in {'RIGHTMOUSE'}: if event.value in {'RELEASE'}: - if sys.platform == 'win32': + if sys.platform.startswith('win'): # This fakes a Right Mouse Up event using Ctypes ctypes.windll.user32.mouse_event(self.MOUSE_RIGHTUP) # This brings back our mouse cursor to use with the menu context.window.cursor_modal_restore() - # If the length of time you've been holding down - # Right Mouse and Mouse move distance is longer than the threshold value, + # If the length of time you've been holding down + # Right Mouse and Mouse move distance is longer than the threshold value, # then set flag to call a context menu - if self._move_distance < addon_prefs.distance and self._count < addon_prefs.time: + if self._count < addon_prefs.time: self._callMenu = True self.cancel(context) - # We now set the flag to true to exit the modal operator on the next loop through + # We now set the flag to true to exit the modal operator on the next loop self._finished = True return {'PASS_THROUGH'} if event.type == 'TIMER': if self._count <= addon_prefs.time: - self._count += 0.01 + self._count += 0.1 return {'PASS_THROUGH'} def callMenu(self, context): if context.space_data.type == 'NODE_EDITOR': if context.space_data.node_tree: - bpy.ops.wm.search_menu('INVOKE_DEFAULT') + bpy.ops.wm.search_single_menu('INVOKE_DEFAULT', menu_idname='NODE_MT_add') else: try: bpy.ops.wm.call_menu(name=self.menu_by_mode[context.mode]) - except: + except RuntimeError: bpy.ops.wm.call_panel(name=self.menu_by_mode[context.mode]) def invoke(self, context, event): @@ -112,10 +118,13 @@ def invoke(self, context, event): def execute(self, context): preferences = context.preferences addon_prefs = preferences.addons[__package__].preferences + enable_nodes = addon_prefs.enable_for_node_editors + + space_type = context.space_data.type # Execute is the first thing called in our operator, so we start by # calling Blender's built-in Walk Navigation - if context.space_data.type == 'VIEW_3D': + if space_type == 'VIEW_3D': bpy.ops.view3d.walk('INVOKE_DEFAULT') # Adding the timer and starting the loop wm = context.window_manager @@ -123,19 +132,18 @@ def execute(self, context): wm.modal_handler_add(self) return {'RUNNING_MODAL'} - elif addon_prefs.enable_for_node_editors and context.space_data.type == 'NODE_EDITOR': + elif space_type == 'NODE_EDITOR' and enable_nodes: bpy.ops.view2d.pan('INVOKE_DEFAULT') - wm = context.window_manager # Adding the timer and starting the loop - self._timer = wm.event_timer_add(0.1, window=context.window) + self._timer = wm.event_timer_add(0.01, window=context.window) wm.modal_handler_add(self) return {'RUNNING_MODAL'} - - elif context.space_data.type == 'IMAGE_EDITOR': + + elif space_type == 'IMAGE_EDITOR': bpy.ops.wm.call_panel(name="VIEW3D_PT_paint_texture_context_menu") return {'FINISHED'} def cancel(self, context): wm = context.window_manager - wm.event_timer_remove(self._timer) \ No newline at end of file + wm.event_timer_remove(self._timer) diff --git a/__init__.py b/__init__.py index efbf8d0..27ad760 100644 --- a/__init__.py +++ b/__init__.py @@ -1,17 +1,16 @@ +from .Preferences import RightMouseNavigationPreferences +from .RightMouseNavigation import BLUI_OT_right_mouse_navigation +import bpy + bl_info = { 'name': 'Right Mouse Navigation', 'category': '3D View', 'author': 'Spectral Vectors', - 'version': (2, 1, 0), + 'version': (2, 2, 0), 'blender': (2, 90, 0), 'location': '3D Viewport, Node Editor', "description": "Enables Right Mouse Viewport Navigation" - } - -import bpy - -from .RightMouseNavigation import BLUI_OT_right_mouse_navigation -from .Preferences import RightMouseNavigationPreferences +} addon_keymaps = [] @@ -22,50 +21,41 @@ def register(): bpy.utils.register_class(RightMouseNavigationPreferences) bpy.utils.register_class(BLUI_OT_right_mouse_navigation) - register_keymaps() - - -def register_keymaps(): - keyconfig = bpy.context.window_manager.keyconfigs - areas = 'Window', 'Text', 'Object Mode', '3D View', 'Image', 'Node Editor' - - if not all(i in keyconfig.active.keymaps for i in areas): - bpy.app.timers.register(register_keymaps, first_interval=0.1) - - else: - wm = bpy.context.window_manager - kc = wm.keyconfigs.user + addon_kc = wm.keyconfigs.addon - km = kc.keymaps['3D View'] + km = addon_kc.keymaps.new(name='3D View', space_type='VIEW_3D') kmi = km.keymap_items.new( "blui.right_mouse_navigation", 'RIGHTMOUSE', 'PRESS' - ) + ) kmi.active = True - km2 = kc.keymaps['Node Editor'] + km2 = addon_kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR') kmi2 = km2.keymap_items.new( "blui.right_mouse_navigation", 'RIGHTMOUSE', 'PRESS' - ) + ) kmi2.active = False addon_keymaps.append((km, kmi, km2, kmi2)) - menumodes = ["Object Mode", "Mesh", "Curve", "Armature", "Metaball", "Lattice", "Font", "Pose"] + active_kc = wm.keyconfigs.active + + menumodes = ["Object Mode", "Mesh", "Curve", + "Armature", "Metaball", "Lattice", "Font", "Pose"] panelmodes = ["Vertex Paint", "Weight Paint", "Image Paint", "Sculpt"] # These Modes all call standard menus # "Object Mode", "Mesh", "Curve", "Armature", "Metaball", "Lattice", # "Font", "Pose" for i in menumodes: - for key in kc.keymaps[i].keymap_items: + for key in active_kc.keymaps[i].keymap_items: if ( - key.idname == "wm.call_menu" - and key.type == "RIGHTMOUSE" + # key.idname == "wm.call_menu" + key.type == "RIGHTMOUSE" and key.active ): key.active = False @@ -73,7 +63,7 @@ def register_keymaps(): # These Modes call panels instead of menus # "Vertex Paint", "Weight Paint", "Image Paint", "Sculpt" for i in panelmodes: - for key in kc.keymaps[i].keymap_items: + for key in active_kc.keymaps[i].keymap_items: if ( key.idname == "wm.call_panel" and key.type == "RIGHTMOUSE" @@ -81,15 +71,15 @@ def register_keymaps(): ): key.active = False - # Changing the Walk Modal Map - for key in kc.keymaps["View3D Walk Modal"].keymap_items: + # Changing the Walk Modal Map + for key in active_kc.keymaps["View3D Walk Modal"].keymap_items: if ( key.propvalue == "CANCEL" and key.type == "RIGHTMOUSE" and key.active ): key.active = False - for key in kc.keymaps["View3D Walk Modal"].keymap_items: + for key in active_kc.keymaps["View3D Walk Modal"].keymap_items: if ( key.propvalue == "CONFIRM" and key.type == "LEFTMOUSE" @@ -97,31 +87,32 @@ def register_keymaps(): ): key.type = "RIGHTMOUSE" key.value = "RELEASE" - pass def unregister(): if not bpy.app.background: + bpy.utils.unregister_class(BLUI_OT_right_mouse_navigation) bpy.utils.unregister_class(RightMouseNavigationPreferences) wm = bpy.context.window_manager - kc = wm.keyconfigs.user + active_kc = wm.keyconfigs.active - for key in kc.keymaps['Node Editor'].keymap_items: + for key in active_kc.keymaps['Node Editor'].keymap_items: if (key.idname == 'blui.right_mouse_navigation'): - kc.keymaps['Node Editor'].keymap_items.remove(key) + active_kc.keymaps['Node Editor'].keymap_items.remove(key) addon_keymaps.clear() - menumodes = ["Object Mode", "Mesh", "Curve", "Armature", "Metaball", "Lattice", "Font", "Pose", "Node Editor"] + menumodes = ["Object Mode", "Mesh", "Curve", "Armature", + "Metaball", "Lattice", "Font", "Pose", "Node Editor"] panelmodes = ["Vertex Paint", "Weight Paint", "Image Paint", "Sculpt"] # Reactivating menus # "Object Mode", "Mesh", "Curve", "Armature", "Metaball", "Lattice", # "Font", "Pose" for i in menumodes: - for key in kc.keymaps[i].keymap_items: + for key in active_kc.keymaps[i].keymap_items: if ( key.idname == "wm.call_menu" and key.type == "RIGHTMOUSE" @@ -129,23 +120,23 @@ def unregister(): key.active = True # Reactivating panels - # "Vertex Paint", "Weight Paint", "Image Paint", "Sculpt" + # "Vertex Paint", "Weight Paint", "Image Paint", "Sculpt" for i in panelmodes: - for key in kc.keymaps[i].keymap_items: + for key in active_kc.keymaps[i].keymap_items: if ( key.idname == "wm.call_panel" and key.type == "RIGHTMOUSE" ): key.active = True - # Changing the Walk Modal Map back - for key in kc.keymaps["View3D Walk Modal"].keymap_items: + # Changing the Walk Modal Map back + for key in active_kc.keymaps["View3D Walk Modal"].keymap_items: if ( key.propvalue == "CANCEL" and key.type == "RIGHTMOUSE" ): key.active = True - for key in kc.keymaps["View3D Walk Modal"].keymap_items: + for key in active_kc.keymaps["View3D Walk Modal"].keymap_items: if ( key.propvalue == "CONFIRM" and key.type == "RIGHTMOUSE" @@ -153,5 +144,6 @@ def unregister(): key.type = "LEFTMOUSE" key.value = "PRESS" + if __name__ == "__package__": - register() \ No newline at end of file + register()