0
$\begingroup$

What I'm making is a script that assigns values to each face, I have a GUI that has fields for each value assigned to each face. So when I click a face I the GUI element to be set to whatever the values are for that face.

So what I am looking for is a way to have an event handler for when a face is selected in edit mode. So when I select this face, this function is called.

$\endgroup$
1

2 Answers 2

0
$\begingroup$

I couldn't find a way to do this. So I presume there isn't an easy way. But the next best thing is to do add a keymap that triggers when MOUSESELECT is pressed. Here is an example of that.

# store keymaps here to access after registration
addon_keymaps = []

def register():

    # handle the keymap
    wm = bpy.context.window_manager
    km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY')

    kmi = km.keymap_items.new(ObjectCursorArray.bl_idname, 'SPACE', 'PRESS', ctrl=True, shift=True)
    kmi.properties.total = 4

    addon_keymaps.append(km)


def unregister():

    # handle the keymap
    wm = bpy.context.window_manager
    for km in addon_keymaps:
        wm.keyconfigs.addon.keymaps.remove(km)
    # clear the list
    addon_keymaps.clear()`
$\endgroup$
0
$\begingroup$

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
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .