Add/remove EnumProperty items?

I want to use enum property in my addon to have dropdown menu with angle values + I want to have add/remove buttons with operators which will add/remove values from this enum.

I have already read this documentation and I don’t understant how to do this. Absolutely. Getter/setter, of course … but I watch videos about getter setter and it all about pure python. Then I look at example in documentation and it looks for me like just some piece of bullshit.

def get_enum(self):
    import random
    return random.randint(1, 4)


def set_enum(self, value):
    print("setting value", value)


bpy.types.Scene.test_enum = bpy.props.EnumProperty(items=test_items, get=get_enum, set=set_enum)

Getting enum to return random??? Setting enum by printing some value??? How it could help me?

I already spent hours of googling any example and there is no such across the internet. Not tutors, no videos, no nothing… only this piece of documentstion I don’t understand. I already asked on Stack Exchange and only ansver I got look more like “go f*** with your self”… Not too helpfull.

Looks like everybody in entire world know how to do this except me.

Can anyone just show me some 5 or something rows of example working code, please? With some EnumProperty and some simplest operator which can change this “items” value somehow.

I wrote an Example for you, while keeping it as simple as i can

To Achieve the effect that you describe, first you will need a Property Group and Collection Property

First thing you need is Property Group to Store the Number, this is something to use to store multiple properties in blender, in our case right now, we only need to store a number

This class will need to be registered in the register function below

class Test_Number(bpy.types.PropertyGroup):
    number: bpy.props.IntProperty()

Now you have the property type, however, you cannot use this yet, to access this, you can use either pointerproperty, or collectionproperty, in our case, we need collectionproperty, because we need multiple of it to create the enum

bpy.types.Scene.test_collection = bpy.props.CollectionProperty(type=Test_Number)

The Collection type will use the property group class we created earlier, so now, you have a list of that object type you created.

Lets create our Enum Property

To create a Dynamic Property, we creates a function that returns the items that we want.
the items that we need to return is in this format

[(identifier, name, description),(identifier, name, description)]

This code will grab the collectionproperty, and loop through it like a list, each of the item are the propertygroup that we defined, and as what I did previously, it have number property

im not too sure, but I remember enum can only accept string, so I convert them into string and just feed identifier, name and description with the number, of course, you can give it anything, but when you grab the data later for use, it will use the identifier

def test_items(self, context):
    
    Enum_items = []
    
    for test_number in context.scene.test_collection:
        
        data = str(test_number.number)
        item = (data, data, data)
        
        Enum_items.append(item)
        
    return Enum_items


######################
###REGISTER############

bpy.types.Scene.test_number = bpy.props.EnumProperty(items=test_items)

To Add new Object, just simply run the add function in the collection property and set the number to what you want, you can just put this in your operator

Test_Number = context.scene.test_collection.add()
Test_Number.number = random.randint(1, 4)

Below are the full code of the example, I hope it helps

import bpy
import random

class Example_Add_Operator(bpy.types.Operator):

    bl_idname = "example.add"
    bl_label = "Add"


    def execute(self, context):
        
        Test_Number = context.scene.test_collection.add()
        Test_Number.number = random.randint(1, 4)

        return {'FINISHED'}
    
class Example_Remove_Operator(bpy.types.Operator):

    bl_idname = "example.remove"
    bl_label = "Remove"


    def execute(self, context):
        
        if len(context.scene.test_collection) > 0:
            context.scene.test_collection.remove(0)


        return {'FINISHED'}
    

class EXAMPLE_Panel(bpy.types.Panel):

    bl_label = "Panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "Example"

    def draw(self, context):
        layout = self.layout
        
        row = layout.row(align=True)
        row.prop(context.scene, "test_number", text="")
        row.operator("example.add", text="", icon="ADD")
        row.operator("example.remove", text="", icon="REMOVE")
        
def test_items(self, context):
    
    Enum_items = []
    
    for test_number in context.scene.test_collection:
        
        data = str(test_number.number)
        item = (data, data, data)
        
        Enum_items.append(item)
        
    return Enum_items

class Test_Number(bpy.types.PropertyGroup):
    number: bpy.props.IntProperty()
 
    
def register():
    
    bpy.utils.register_class(EXAMPLE_Panel)
    bpy.utils.register_class(Example_Add_Operator)
    bpy.utils.register_class(Example_Remove_Operator)
    
    bpy.utils.register_class(Test_Number)
    
    bpy.types.Scene.test_number = bpy.props.EnumProperty(items=test_items)
    bpy.types.Scene.test_collection = bpy.props.CollectionProperty(type=Test_Number)
    
    



def unregister():
    
    del bpy.types.Scene.test_number
    del bpy.types.Scene.test_collection
    
    bpy.utils.unregister_class(EXAMPLE_Panel)
    bpy.utils.unregister_class(Example_Add_Operator)
    bpy.utils.unregister_class(Example_Remove_Operator)
    bpy.utils.unregister_class(Test_Number)

if __name__ == "__main__":
    register()

Example.py (2.2 KB)

5 Likes

Thank you for reply. At least someone tried to explain. But what you show not exactly what I seek for (or is it?).

Here my addon code with panel where I have this enum, add/remove operators buttons and those operators/property group (I removed everything else).

bl_info = {
	"name": "FRP",
	"author": "",
	"version": (1, 0),
	"blender": (2, 83, 0),
	"location": "Viev3D > N panel > FG Tools > FRP",
	"description": "",
	"warning": "",
	"wiki_url": "",
	"category": "",
}

import bpy,math

class FRP_PT_Panel(bpy.types.Panel):
	bl_label = 'FRP'
	bl_idname = 'FRP_PT_Panel'
	bl_space_type = 'VIEW_3D'
	bl_region_type = 'UI'
	bl_category = 'FG_Tools'

	def draw(self,context):
		layout = self.layout
		row = layout.row(align=True)

		sc_frp = context.scene.frp_props

		row_a = layout.row(align=True)
		row_a.prop(sc_frp, 'frp_angle_a', text = '')
		row_a.operator('fgt.frp_angle_a_add', text='', icon='ADD')
		row_a.operator('fgt.frp_angle_a_rem', text='', icon='REMOVE')

class FRP_OT_Angle_A_Add(bpy.types.Operator):
	bl_idname = 'fgt.frp_angle_a_add'
	bl_label = 'Add custom angle.'

	angle = bpy.props.FloatProperty(
		name = 'Angel A', 
		default = 0.0, 
		min= 0.0, 
		max= 360.0
	)

	def execute(self, context):

		return{'FINISHED'}

	def invoke(self, context, event):

		return context.window_manager.invoke_props_dialog(self)

class FRP_OT_Angle_A_Rem(bpy.types.Operator):
	bl_idname = 'fgt.frp_angle_a_rem'
	bl_label = 'Remove custom angle.'

	def execute(self, context):
		print(context.scene.frp_props.frp_angle_a)
		return{'FINISHED'}


class FRP_Settings_Props(bpy.types.PropertyGroup):

	frp_angle_a: bpy.props.EnumProperty(
		name = 'Angle_A',
		description= 'Something here',
		items = [('a1', '30', '', 1),('a2', '10', '', 2)]
	)

CTR = [
	FRP_PT_Panel,
	FRP_OT_Angle_A_Add,
	FRP_OT_Angle_A_Rem,
	FRP_Settings_Props,
]

FRP_Keymap = []

def register():
	for cls in CTR:
		bpy.utils.register_class(cls)

	bpy.types.Scene.frp_props = bpy.props.PointerProperty(type=FRP_Settings_Props)

def unregister():
	for cls in CTR:
		bpy.utils.unregister_class(cls)

	del bpy.types.Scene.frp_props


In your code you like create it from list and then register, that’s all. You use collection property to manage it and then convert it to enum property.

Instead of what you show I create this enum right from start with some default values. Then I want to get existing enum property by operator, get whats’s inside (this “items” tuple) and add some angle value to existing values (using popup menu), or remove current picked angle value.

In ADD case operator I guess I have to “get” this enum tuple > append to it > “set” it back to enum. It also have to live inside ADD operator execut to be executed from popup “OK” button as I understand how it work (I may be wrong).

In REMOVE case operator I guess I have to check current picked enum value (context.scene.frp_props.frp_angle_a) > save this value somewhere > get access to enum tuple > find this value value > remove it > “set” this tuple back to my enum


Just context.scene.frp_props.frp_angle_a give (of course) just current picked enum identificator. (on GIF when I click on " - " a1, a2 )
context.scene.frp_props.frp_angle_a() give error “TypeError: ‘str’ object is not callable”
context.scene.frp_props.frp_angle_a.items give error “AttributeError: ‘str’ object has no attribute ‘items’”

So there is no easy way to get this tuple inside enum? In documentation EnumProperty has getter/setter definitions so as I understand it, by this definitions I can call and change whenever I want.


So I guess I have to use getter/setter somehov to manage it. But when I watch some video in youtube about python getter/setter (like his one) it’s like about something one and then I look at API Property definitions page and it’s about something comletely different (or maybe I’m the most stupid guy in the world, at least I feel so after days looking for how to do this).

I can’t find any example of such enum property using case code… Here where I need help. How to make those getter/setter and use it (example in API documentation give me “0” clues).

How did you solve it? Can you share it?

I was wrong

What was my mistake from start - wrong understanding of what is class lead me to wrong expectations from what enum update/get/set does.

Those is just functions to be called when something happen (actually as it described in description). This is not functions used set this enum property items value.


After more practicing/learning what is class and how it work, I gues some things. Right now for my addon I use solution proposed by BlenderBoi2018. I guess you, same as me back in the days, have issues with understanding this logic.

Shortly (clearer):

  • you have some iterable (or any else) variable (or CollectionProperty) to store some data used to generate enum items base on this data
  • you have some function which use this data to generate enum items and return them
  • you put this function name in to enum property items parameter

Then code will run this function when you howering addon panel (Blender go through addon code to draw addon UI) and regenerate enum property from variable by using function.


Here example from my addon:

Variable pr_values used to store dictionary. I use it then in function upd_preset_enum to generate enum items. It return output similar as if I set such items manually in enum property parameters. On next image I do for loop for each pr_values dictionary item, generate tuple new_pr and each time append this tuple to list pr_enum which will return then as function output.

Some description for what function return.

If you check again API documentation - it has to return list of tuples each has to contain (identifier(string, has to be different for different enum items), name(string, what shown in UI, can be same as identifier, same for all items or what ever), description (string, what ever you want, shown as a hint whe mouse over enum item), icon(optional, may not be included), number(integer, different for different enum items)).

  • Identifier - what you will get from python API. My addon example bpy.context.scene.sot_props.presets (sot_props - my properties collection, presets - enum property name) will return some 'Preset.002' (currently selected enum item string identifier). You can use it to identify which enum item is selected.
  • Number - not same as identifier, has to be some different integer numbers (1,2,3,4,5) Bledner will use them to manage enum items.

Here - name of this function used as “items” in enum property. Return of this function become value of “items” of my enum property.

As you noticed (in my case) variable pr_values empty from start. It’s OK, I use add/remove operators (on next image) for add/remove interface buttons to manage pr_values dictionary and, as result, manage my enum property items.

Here code of panel where I use this enum property (selected enum property, add and remove operators buttons). As you see I use same pr_values variable also to manage UI, change “remove” button icon, show info about selected preset.

Here how it look in addon UI panel.

Thank you very much for your reply,But the function I want seems different from yours。
I want to continuously register enumproperty or customize enumproperty in the for loop。I don’t know if you can understand what I mean, but it is such a function. Please forgive me. I’m a novice learning blender

This was a big help for me, thanks.
I don’t think I would’ve been able to follow it when I first was doing blender addon development.

helps to have created a collection property first.

I see some people seek for same answers I was seeking before. Here simple (relatively) addon with two buttons for adding/removing enum items.

Just save this as .py file and install as addon, you will have tab on N panel “Example Enum”. I also left descriptions in code, hope it will help you seeker.

bl_info = {
	"name": "Enum Example Addon",
	"author": "IIIFGIII",
	"version": (1),
	"blender": (2, 83, 0),
	"location": "Viev3D > N panel > Example Enum",
	"description": "",
	"warning": "",
	"wiki_url": "",
	"category": "",
}

import bpy, mathutils
bpr = bpy.props

data_for_enum = []

	# data_for_enum - variable to store data for enum items. 
	# in this example I used list with tuples of string and object location vector (because why not)
	# name will be used to manage enum and show in UI, location value will be used for description popup

	# it actually can be anything you want to use in your particular case to generate tuples for enum
	# literally any thing you find handy in your case to use - solid string, list, list of lists, dictionary... any


def enum_items_generator(self,context):
	enum_items = []
	for e,d in enumerate(data_for_enum):
		enum_items.append((d[0], d[0], 'Position value = ' + str(d[1]), e))
		# as you see for each tuple in my list I use [0] item to generate enum value/name and [1] item for description + integer from enumerate for id
	return enum_items

	# key thing here is you want to output list of tuples [(),(),(),()]
	# that contain proper data ('string','string','string',integer)
	# first  - value you can get later (when I do later in my addon eea.presets) when you want to get your current enum value
	# second - display name of enum item, can be different that first string
	# third  - description (can be empty, just '')
	# fourth - id for blender


def report(self,message): # Just to report errors
	self.report({'ERROR'}, message)
	return{'CANCELLED'}

class EEA_PT_Example_Enum_Panel(bpy.types.Panel):
	bl_label = 'Example Enum Panel'
	bl_idname = 'EEA_PT_Example_Enum_Panel'
	bl_space_type = 'VIEW_3D'
	bl_region_type = 'UI'
	bl_category = 'Example Enum'

	def draw(self, context):
		layout = self.layout
		col = layout.column(align=True)
		eea = context.scene.eea_props

		col.prop(eea, 'presets', text= '')
		row = col.row(align=True)
		row.operator('eea.eea_enum_add', text= 'Add')
		row.operator('eea.eea_enum_remove', text= 'Remove')



class EEA_OT_Enum_Add(bpy.types.Operator):
	bl_idname = 'eea.eea_enum_add'
	bl_label = 'EEA_OT_Enum_Add'
	bl_description = 'Add new enum item'

	new_name: bpy.props.StringProperty(default= 'Preset name', name = 'Preset name ')

	def execute(self, context):
		global data_for_enum

		eea = context.scene.eea_props
		aob = context.view_layer.objects.active

		if aob == None: # For case when there is no active object
			return report(self,'No active object selected!!!')

		data_for_enum.append((self.new_name, mathutils.Vector(aob.matrix_world.col[3][:3]))) # here I append to data_for_enum new tuple
		data_for_enum = sorted(data_for_enum, key=lambda e : e[0]) # a bit of sorting by name

		eea.presets = self.new_name # here I set my preset value to match value I just added (will refresh when mouse over panel UI)

		return{'FINISHED'}

	def invoke(self, context, event):
		return context.window_manager.invoke_props_dialog(self, width=200)




class EEA_OT_Enum_Remove(bpy.types.Operator):
	bl_idname = 'eea.eea_enum_remove'
	bl_label = 'EEA_OT_Enum_Remove'
	bl_description = 'Remove current enum item'

	def execute(self, context):
		global data_for_enum

		eea = context.scene.eea_props

		if len(data_for_enum) != 0:
			did = [x[0] for x in data_for_enum].index(eea.presets) # I search for index of current enum in my list [0] items of my tuples 
			data_for_enum.pop(did) # I pop out item with this index from my list

			# at this moment my list is already changed and enum already regenereted

			if did>len(data_for_enum)-1 and data_for_enum != []: # here I set enum to value of my list last item in case when I remove last enum item, othervise active enum become empty :)
				eea.presets = data_for_enum[len(data_for_enum)-1][0]

		else: return report(self,'No more items to remove')
		return{'FINISHED'}

class EEA_PR_Properties(bpy.types.PropertyGroup):

		presets: bpr.EnumProperty(items = enum_items_generator, name = 'Position Preset')


ctr = [
	EEA_PT_Example_Enum_Panel,
	EEA_OT_Enum_Add,
	EEA_OT_Enum_Remove,
	EEA_PR_Properties
]

def register():
	for cls in ctr: 
		bpy.utils.register_class(cls)
	bpy.types.Scene.eea_props = bpy.props.PointerProperty(type= EEA_PR_Properties)

def unregister():
	for cls in reversed(ctr): 
		bpy.utils.unregister_class(cls)
	del bpy.types.Scene.eea_props
2 Likes