Dynamic Description for Multiple Operator Functions with EnumProperty

I have an operator that uses an EnumProperty so that I can call different functions from the UI without needing multiple operators.

        row.operator("test.operator",text="1").action = 'ONE'
        row.operator("test.operator",text="2").action = 'TWO'
        row.operator("test.operator",text="3").action = 'THREE' 
class TestOp(Operator):
    bl_idname = "test.operator"
    bl_label = "Test Operator"
    bl_description = ""
    bl_options = {'REGISTER', 'UNDO'}

    action: EnumProperty(
        items=[
            ('ONE', 'One', 'This is number 1'),
            ('TWO', 'Two', 'This is number 2'),
            ('THREE', 'Three', 'This is number 3')
        ]
    )    

    def execute(self, context):       
        if self.action == 'ONE': self.test_one()
        elif self.action == 'TWO': self.test_two()            
        elif self.action == 'THREE': self.test_three()            
        return {'FINISHED'}

    def test_one(self):
        print("1")

...etc

In doing this I realized I lose descriptions when hovering over the operator button, then I found out you could dynamically set the description of the operator:

    @classmethod
    def description(cls, context, properties):        
        return properties.action 

However I cannot figure out how to use the third column value in the EnumProperty. It is using “THREE” for the tooltip, how do I get this to use “This is number 3” instead?

image


Full test script:

import bpy
from bpy.props import *
from bpy.types import Operator, AddonPreferences, Panel, PropertyGroup

class TestPanel(bpy.types.Panel):
    bl_label = "Test Panel"
    bl_idname = "OBJECT_PT_Test"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'    

    def draw(self, context):
        layout = self.layout
        row = layout.row()
        row.operator("test.operator",text="1").action = 'ONE'
        row.operator("test.operator",text="2").action = 'TWO'
        row.operator("test.operator",text="3").action = 'THREE'        

class TestOp(Operator):
    bl_idname = "test.operator"
    bl_label = "Test Operator"
    bl_description = ""
    bl_options = {'REGISTER', 'UNDO'}

    action: EnumProperty(
        items=[
            ('ONE', 'One', 'This is number 1'),
            ('TWO', 'Two', 'This is number 2'),
            ('THREE', 'Three', 'This is number 3')
        ]
    )    

    @classmethod
    def description(cls, context, properties):        
        return properties.action    
    
    def execute(self, context):       
        if self.action == 'ONE': self.test_one()
        elif self.action == 'TWO': self.test_two()            
        elif self.action == 'THREE': self.test_three()            
        return {'FINISHED'}

    def test_one(self):
        print("1")
    
    def test_two(self):
        print("2")       
    
    def test_three(self):
        print("3")


def register():    
    bpy.utils.register_class(TestPanel)
    bpy.utils.register_class(TestOp)


def unregister():
    bpy.utils.unregister_class(TestPanel)
    bpy.utils.unregister_class(TestOp)

if __name__ == "__main__":
    register()

It’s a bit convoluted but you get used to it :wink:

    @classmethod
    def description(cls, context, properties):
        enum_identifier = properties.action
        return properties.bl_rna.properties["action"].enum_items[enum_identifier].description

See
https://docs.blender.org/api/current/bpy.types.EnumPropertyItem.html#enumpropertyitem-bpy-struct
https://docs.blender.org/api/current/bpy.types.EnumProperty.html#bpy.types.EnumProperty.enum_items

3 Likes

WOO! Thank you so much, I was losing hope on that one. :rofl:

Whaou, this one is a GEM !! thanks a lot Gorgious ;))))

2 Likes

Note if your items are dynamically defined eg with a callback enum_items will either throw an error or return an empty list. You should do something like that instead.

prop = properties.__annotations__[prop_name]
items = prop.keywords.get("items")
if not isinstance(items, (list, tuple)):
    # items are retrieved through a callback, not a static list :
    items = items(properties, context)
description = next(item[2] for item in items if item[0] == properties.action)

Here’s an implementation in an actual addon

1 Like