# How to capture a double left mouse click with script for custom action?

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)
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)
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')

• Anyone have any ideas? I can't get double click to work at all. I messed around with double click timing in the Input preferences and can't get it to recognize. – Sam Vimes Aug 27 at 17:31
• maybe I'm missing something, buy you could something like: if event.type is left click --> start modal timer operator --> if event.type = timer, delete the timer and reset, but if you get an event of type "left click" before the timer ends, then it means you had a double click and you can run your functions – Tareyes Aug 28 at 18:31
• That's a good idea to get the functionality emulated after the first click. The problem is the how to monitor for that first left click, without running a modal the whole time waiting for the interrupt. I'm just learning about modals, but my experience with queues and blocking threads suggests that's a potentially brittle solution. – Sam Vimes Aug 28 at 18:42
• every time a left click is detected a timer is started (like 0.3 seconds). After those 0.3 seconds that timer can be deleted, so it's not running all the time. But in any case, in my experience, modal operator are far less power consuming or fragile than it seems, at least with simple functions. But if you don't want to use timers, every time a left click is pressed you could use the time.localtime() function to get the input time, and in the next left click check if the time elapsed is less than your tolerance – Tareyes Aug 28 at 21:54

You can do it in 2 ways:

## Modal Timer

You can do something like:

def modal(self, context, event):
if event.type in {'LEFTMOUSE'}:
wm = context.window_manager
if self._timer is not None:
print("Double click")       #your function goes here
wm.event_timer_remove(self._timer)
else:
self._timer = wm.event_timer_add(0.3, window = context.window)
return {'RUNNING_MODAL'}

if event.type in {'TIMER'}:
wm.event_timer_remove(self._timer)  #remove the timer
return {'RUNNING_MODAL'}


## use time.time()

you can check the time of each click and check the time elapsed between them

import time

#operator declaration etc

_timer = None
t = 0

def modal(self, context, event):
if event.type in {'LEFTMOUSE'}:
temp = time.time()
if (temp - self.t) < 0.3:
print("Double Click")       #your function goes here
self.t = temp


Disclaimer: I haven't tested the code since this scripts are more of a explainer rather than actual code, so you'll have to check for bugs (I'm lazy ;))

• That got me pretty close to the answer, which had to take into account an existing click and loop timer. There was also a LEFTMOUSE event credited when I ran the script, so I added to check for RELEASE to make sure it was a click. I'll update my original post with the updated code. – Sam Vimes Aug 29 at 2:19