5
$\begingroup$

I have a custom add-on and I would like to create a dropdown list based on an EnumProperty, but I want the items in the list to take custom icons rather than the icons available within blender. I know that you can use import bpy.utils.previews to load custom icons into add-ons and set label's icon_value to custom icon ids, but I would like to be able to do the same with the items in a drop down list built based on an EnumProperty.

I have the following case for an add-on defining a class that will have a drop-down lists in which I want to have custom icons added to EACE item in the list. I want custom icons to show next to each item in the list as the drop down list is open (not just next to the closed drop down list: see image below):

# defined globally
easingItems = [
    ("EASE_IN", "Ease In", "", "", 0),
    ("EASE_OUT", "Ease Out", "", "", 1),
    ("EASE_IN_OUT", "Ease In-Out", "", "", 2)]

# prop defined in the addon/node class 
easingType = EnumProperty(
        name="Easing",
        default="EASE_IN", 
        items=easingItems,
        update=updateNode)

# then, in the draw UI
def draw_buttons(self, context, layout):
    # this is when I want the enum property to be displayed with custom icons (how?)
    layout.prop(self, ‘easingType', expand=False)

Left: This is a drop down list based on an enum property with some default blender icons. Right: This is a drop-down list based on a list needing custom icons (displayed as having default blender icons)

Default icons vs Custom Icons in open drop-down list

The result after Jerryno’s solution looks like this:

Drop-down list with custom icons

$\endgroup$

2 Answers 2

5
$\begingroup$

For each enum item an icon can be specified. You just need to edit this part of your code:

# defined globally
easingItems = [
    ("EASE_IN", "Ease In", "", customIcon["MY_EASEIN"].icon_id, 0),
    ("EASE_OUT", "Ease Out", "", customIcon["MY_EASEOUT"].icon_id, 1),
    ("EASE_IN_OUT", "Ease In-Out", "", customIcon["MY_EASEINOUT"].icon_id, 2)]
$\endgroup$
5
  • $\begingroup$ Thx @Jerryno. Could you be more specific ? (see my latest comment). And by the way, I want all the items in the drop down list to have icons, not just the collapsed drop down list. $\endgroup$ Feb 17, 2017 at 21:30
  • $\begingroup$ @DolphinDream Hi, thanks for the edit, hopefully now it fits your case better. But I am not sure if it will work, because I don't know where in the ui and how you are using this. draw_buttons and accessing the enum through self indicates it is not an ui panel. Try this and if it does not work I could improve it by seeing a piece of code I could test this on. $\endgroup$ Feb 18, 2017 at 13:40
  • $\begingroup$ I tried it and it does’t quite work. I think the icon_value expects a single int value, thus your second line does change the icon only for the item currently selected in the drop down list, and only show on the closed drop down, not in the opened drop down (e.g. items in the open drop down do not show an icon). $\endgroup$ Feb 18, 2017 at 17:29
  • $\begingroup$ @DolphinDream now it should work $\endgroup$ Feb 18, 2017 at 18:14
  • 1
    $\begingroup$ Awesome, @Jerryno ! :) sweet! The only thing I have to be careful though is that the customIcon is automatically loading the icons from disc (call to loadIcons) since this list may be defined outside of the class and perhaps registers before the custom icon module is registered (and icons loaded). Thanks again! $\endgroup$ Feb 18, 2017 at 19:14
2
$\begingroup$

This thread is unfortunately out of date, since then, developers chose to use __annotations__ space to define bpy.props in PropertyGroup, therefore getting custom icons id is significantly more challenging as the properties will be registered before the icons.

there are two solutions to this problem :

  1. using a function instead of a a tuple for your enum items see docs https://docs.blender.org/api/current/bpy.props.html?highlight=enumproperty#bpy.props.EnumProperty (has downsides)
  2. patching annotation space right before the propgroup are being registered

this 2nd solution looks like this


class MyPropGroup(bpy.types.PropertyGroup): 

    my_enum : bpy.props.EnumProperty(
        default="one", 
        items=( ("one","One","","PATCHME:MY_ICON1",1),
                ("two","Two","","PATCHME:MY_ICON2",2),
                ("thr","Thr","","PATCHME:MY_ICON3",3),
        ),
        )


def patch_custom_icons(cls):
    """because properties are defined in __annotation__ space, we cannot get custom icon value as icons are not registered yet
    thus we need to re-sample the icons right before registering the PropertyGroup, this function will patch EnumProperty when needed"""

    from .myplugin.icons import cust_icon 
    #gathering icon id integer is done with a function here 
    patchsignal = "PATCHME:"

    def patch_needed(itm):
        """check if this item need to be patched, if the icon element of the item has a patchsignal"""
        return type(itm[3]) is str and itm[3].startswith(patchsignal) #3rd element of an item is always the icon, see blender doc

    def patch_item(itm):
        """patch icon element of an item if needed"""
        if patch_needed(itm):
            return tuple(cust_icon(e.replace(patchsignal,"")) if (type(e) is str and e.startswith(patchsignal)) else e for e in itm)
        return itm

    #for all EnumProperties initialized in cls.annotation space, 
    #that have more than 3 element per items 
    #& have at least one icon str value with the patch signal

    for propname,prop in [(propname,prop) for propname,prop in cls.__annotations__.items() 
               if (repr(prop.function)=="<built-in function EnumProperty>")
               and (len(prop.keywords["items"][0])>3)
               and len([itm for itm in prop.keywords["items"] if patch_needed(itm)])
               ]:

        #if found monkey-patch new items tuple w correct icon values
        prop.keywords["items"] = tuple(patch_item(itm) for itm in prop.keywords["items"])

        #print(f"icon patched: {cls}{propname}")
        continue

    return cls 


 

def register():

    #register classes
    bpy.utils.register_class(patch_custom_icons(MyPropGroup))
    #...
    
$\endgroup$

You must log in to answer this question.

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