I would like to add behavior to the 3d viewport so that when a user double-clicks an object it puts the object in a special move mode (locked along a certain axis depending on conditions). The part I need help with is the first part - monitoring for the event. The second part would be handled by a modal.
All of the examples for modals I've found are either fired off with a test run during register(), tied to a button, or re-routed from an existing event to send in a new direction. I'm not really sure which approach to take - to try and map through Window Manager api, set up an event to monitor for clicks, or... something else.
Most help along these lines suggest re-mapping through Keymap in preferences and saving that config for the addon. However I don't see a way to map anything with a double-click (so maybe that's not even possible and would need to use a different combo).
With other UI toolkits I've set it to listen for events in event queue. Blender has events, but seems to only apply inside a modal once already called. Either way I haven't figured out how to create a double-click event.
I would prefer to tie it to some type of mouse event because a button fired operator in this situation would have the button on the side and the selected object half a screen away. The closest I've found to a workable solution is adding to the right-click menu. That could work; however I had hoped to eventually hide most of the right-click functionality. This is designed for users who know nothing about Blender, so an overlay would eventually replace everything in a slimmed down solution.
EDIT
Here is a script I found that emulates an event queue... modified with a DOUBLE_CLICK monitor. However this is an extra event queue which runs until terminated. So a good solution doesn't feel to right to spin this up in the background.
Also note the DOUBLE_CLICK option does not work. I tested out several other values and verified those events were getting picked up, so I don't believe it's the structure of the code.
import bpy
class ModalTimerOperator(bpy.types.Operator):
"""Operator which runs its self from a timer"""
bl_idname = "wm.modal_timer_operator"
bl_label = "Modal Timer Operator"
_timer = None
def modal(self, context, event):
if event.type in {'LEFTMOUSE'}:
if event.value in {'DOUBLE_CLICK'}:
print('DOUBLE CLICK')
return {'RUNNING_MODAL'}
if event.type in {'RIGHTMOUSE'}:
screen = context.screen
x, y = event.mouse_x, event.mouse_y
areas = [a for a in screen.areas if a.x < x < a.x + a.width
and a.y < y < a.y + a.height]
if areas and areas[0].type not in {'VIEW_3D', 'TEXT_EDITOR'}:
print("Right Click Taken Out")
return {'RUNNING_MODAL'}
if event.type in {'ESC'}:
self.cancel(context)
return {'CANCELLED'}
if event.type == 'TIMER':
pass
return {'PASS_THROUGH'}
def execute(self, context):
wm = context.window_manager
self._timer = wm.event_timer_add(0.1, window = context.window)
wm.modal_handler_add(self)
return {'RUNNING_MODAL'}
def cancel(self, context):
wm = context.window_manager
wm.event_timer_remove(self._timer)
def register():
bpy.utils.register_class(ModalTimerOperator)
def unregister():
bpy.utils.unregister_class(ModalTimerOperator)
if __name__ == "__main__":
register()
# test call
bpy.ops.wm.modal_timer_operator('INVOKE_DEFAULT')
ANOTHER EDIT
Here is the updated code in case anyone else needs it. Thanks to @Tareyes for pointing me in the right direction. I've opened a ticket against the DOUBLE_CLICK not being recognized, which would still be the preferable way to handle this. However this works pretty well and can be worked into an existing modal.
import bpy
import time
class ModalTimerOperator(bpy.types.Operator):
"""Operator which runs its self from a timer"""
bl_idname = "wm.modal_timer_operator"
bl_label = "Modal Timer Operator"
click = False
def modal(self, context, event):
loop_time = time.time()
delta = loop_time - self.last_click
if delta > 0.3:
self.click = False
if event.type in {'LEFTMOUSE'}:
if event.value in {'RELEASE'}:
if not self.click:
self.click = True
self.last_click = time.time()
elif delta < 0.3 and self.click:
print("Double Click")
self.click = False
if event.type in {'ESC'}:
self.cancel(context)
return {'CANCELLED'}
if event.type in {'TIMER'}:
pass
return {'PASS_THROUGH'}
def invoke(self, context, event):
print("INVOKE")
self.last_click = time.time()
self.execute(context)
return {'RUNNING_MODAL'}
def execute(self, context):
wm = context.window_manager
self._timer = wm.event_timer_add(0.1, window = context.window)
wm.modal_handler_add(self)
return {'RUNNING_MODAL'}
def cancel(self, context):
wm = context.window_manager
wm.event_timer_remove(self._timer)
def register():
bpy.utils.register_class(ModalTimerOperator)
def unregister():
bpy.utils.unregister_class(ModalTimerOperator)
if __name__ == "__main__":
register()
# test call
bpy.ops.wm.modal_timer_operator('INVOKE_DEFAULT')