Years later, and still there doesn't seem to be a simple way to subscribe to the selection event. I've been reading about it and one of the common solutions is to wrap the target operator—view3d.select in this case, I think—in another function.
Another creative solution is to attach a new draw callback to the 3D viewport, as seen in this excellent example. This one solution is good for when you not only want to catch selections but changes in the mesh.
Then there's the idea in the other answer in this question.
Using Keymap to trigger an Operator
Using the keymap is pretty interesting. I find it slightly less dirty that the wrapper method, although not as solid as the draw callback. It's ideal for when you only want to watch for direct selections and don't care about what the operator originally mapped to the key returns.
The only issue is that the example has nothing to do with the proposed solution; it's just the generic keymap example from the manual. This answer was tested on Blender 3.1.
This is what the register/unregister functions should look.
# Store keymaps for unregister access
addon_keymaps = []
def register():
# Add Keymap Entries
wm = bpy.context.window_manager
kc = wm.keyconfigs
# Create only if it doesn't exist already
# Use "Mesh" for Edit Mode keymap entries
if not (km := kc.addon.keymaps.get("Mesh")):
km = kc.addon.keymaps.new(name="Mesh", space_type='EMPTY', region_type='WINDOW')
# SELECT
# Get the default key mapped for 3D viewport selection
key = kc.active.keymaps['3D View'].keymap_items['view3d.select'].type
"""
[Option 1]
This method relies on using 'RELEASE' to avoid overwriting the original key
functionality—in this case, Vertex/Edge/Face Selection.
If there's anything else mapped to the 'RELEASE' event, then it'll be overwritten.
"""
kmi = km.keymap_items.new(idname=demoOperatorRELEASE.bl_idname, type=key, value='RELEASE')
addon_keymaps.append((km, kmi)) # Register for unregister
"""
[Option 2]
This method uses a modified Operator that won't overwrite the original key
functionality. It'll have the downside of requiring 2x Undos to undo the action, and
could have other unforseens consequences.
"""
kmi = km.keymap_items.new(idname=demoOperatorPASS.bl_idname, type=key, value='PRESS')
addon_keymaps.append((km, kmi)) # Register for unregister
def unregister():
# Remove Keymap Entries
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
# Clean Up: Remove empty addon keymap
if not len(km.keymap_items):
bpy.context.window_manager.keyconfigs.addon.keymaps.remove(km)
addon_keymaps.clear()
The biggest issue with the keymap trick is that by default new operators overwrite the original operators mapped to the same key. Thus you'll need to either map it to a different action of the same key, hoping it's also not mapped to something else, or use a special operator.
Option 1
Operator for keymap using RELEASE.
class demoOperatorRELEASE(Operator):
"""Operator used in conjunction with keymaps to automate some function"""
bl_idname = "object.demo_operator_release"
bl_label = "Demo Operator Name that Appears in Keymap (Release)"
bl_description="Hello World!"
# Using 'REGISTER' will make the operator remember the last custom props used.
# It'll also always show a popup when using the operator.
# When you use it and have options you DON'T want remembered, use options={'SKIP_SAVE'}
# in the props defintion
# bl_options={'REGISTER', 'UNDO', 'INTERNAL'}
bl_options={'UNDO', 'INTERNAL'}
key: bpy.props.StringProperty(default='', options={'SKIP_SAVE'})
keypress: bpy.props.StringProperty(default='PRESS', options={'SKIP_SAVE'})
@classmethod
def poll(cls, context):
# You can use aditional conditions here so the operator, and the shortcut by extension,
# won't be trigged unless the conditions are met
return context.active_object.random_special_condition
def invoke(self, context, event):
# It's possible to capture the key name and press type here
self.key = event.type
self.keypress = event.value
return self.execute(context)
def execute(self, context):
# Include your function calls here
# (...)
# Example of performing an action based on key press type
if self.keypress == 'RELEASE':
print("Was a Release!")
return {'FINISHED'}
Option 2
This Operator lets the event through, so it reaches other operators mapped to the same key. I couldn't find any documentation on the consequences of using PASS_THROUGH in the execute method, thus I'm not sure using this trick is a good idea, but hey, it works.
class demoOperatorPASS(Operator):
"""Operator used in conjunction with keymaps to automate some function"""
bl_idname = "object.demo_operator_pass"
bl_label = "Demo Operator Name that Appears in Keymap (Pass)"
bl_description="Hello World!"
bl_options={'UNDO', 'INTERNAL'}
key: bpy.props.StringProperty(default='')
keypress: bpy.props.StringProperty(default='PRESS')
@classmethod
def poll(cls, context):
# You can use aditional conditions here so the operator, and the shortcut by extension,
# won't be trigged unless the conditions are met
return context.active_object.random_special_condition
def invoke(self, context, event):
# It's possible to capture the key name and press type here
self.key = event.type
self.keypress = event.value
return self.execute(context)
def execute(self, context):
# Include your function calls here
# (...)
# Example of performing an action based on key press type
if self.keypress == 'RELEASE':
print("Was a Release!")
return {'PASS_THROUGH'} # Keep keymap shortcut from overriding default keymap