# Python: template_ID()-like UI for PropertyGroup

For a Blender add-on I am trying to create an UI that let's the user

• choose from a list of existing instances of a PropertyGroup stored in a CollectionProperty
• create new instances of that PropertyGroup and name them
• possibly delete instances

This is much like the normal chooser for ID types that you get with the template_ID() function, but for a non-ID type.

First I was thinking if it would be possible to derive an ID-type myself, but I found no information on that online, so I doubt that it is even possible. Note that my items cannot really be represented by any ID-type already present in Blender.

• template_search() gives me a nice way to look up existing properties, but because my items' name properties are not necessarily unique, it is not really an option, because I can't get the original item back from the name string.
• prop() with a dynamically generated EnumProperty sort of works. However there is no way to rename the item without a secondary name input box.

I was wondering if somebody has already created a similar kind of input for another Blender add-on or even as a Python module that I could use for my purposes.

• You can use an UIList, see: Create an interface which is similar to the material list box – p2or Aug 18 at 12:45
• That might be an option, but only if it can be made more compact, as it has to fit inside a node. Would it be possible to display an UIList inside a modal dialogue / popup that can be opened with a button? (I am not too familiar with the correct terms in Blender UI world) I'm ok with not having search functionality. – geloescht Aug 18 at 15:59
• As you can see, UIList has a compact mode which should also work as part of an invoke_props_dialog. – p2or Aug 18 at 17:10
• Thank you, I implemented a solution based on invoke_search_popup() which I wouldn't have found without your link. I am going to post an answer to my own question once I am confident that my code works as intended. – geloescht Aug 18 at 21:30

It is possible to create a group of inputs that behaves very similar to the normal chooser. This is the code to draw the widgets:

def draw_buttons(self, context, layout):
group = layout.row(align=True)
choose_props = group.operator('example.choose_item', text="", icon='PRESET')
if self.item_set:
group.prop(self.get_item(), "name", text="", expand=True)
clear_props = group.operator('example.clear_item', icon='X', text="")
clear_props.node_tree = self.id_data.name
clear_props.node = self.name
else:
new_props.node_tree = self.id_data.name
new_props.node = self.name
choose_props.node_tree = self.id_data.name
choose_props.name = self.name


The choose operator itself uses context.window_manager.invoke_search_popup() to open a search box of available items. Here is example code for the operators:

items = [('one', 'Any', "", 'PRESET', 1), ('two', 'PropertyGroup', "", 'PRESET', 2), ('three', 'type', "", 'PRESET', 3)]

class ChooseItemOperator(bpy.types.Operator):
bl_idname = "example.choose_item"
bl_label = "Choose item"
bl_options = {'INTERNAL'}
bl_property = "enum"

def get_enum_options(self, context):
global items
return items

enum: EnumProperty(items=get_enum_options, name="Items")
node_tree: StringProperty()
node: StringProperty()

def execute(self, context):
tree = bpy.data.node_groups[self.node_tree]
node = tree.nodes[self.node]
node.item_set = true
node.set_item(self.enum)
return {"FINISHED"}

def invoke(self, context, event):
context.window_manager.invoke_search_popup(self)
return {"FINISHED"}

class NewItemOperator(bpy.types.Operator):
bl_idname = "example.new_item"
bl_label = "New Item"
bl_options = {'INTERNAL'}

node_tree: StringProperty()
node: StringProperty()

def execute(self, context):
global items
tree = bpy.data.node_groups[self.node_tree]
node = tree.nodes[self.node]
node.item_set = True
newitem = ('four', 'type', "", 'PRESET', 4)
items.append(newitem)
node.set_item(newitem)
return {'FINISHED'}

class ClearItemOperator(bpy.types.Operator):
bl_idname = "example.clear_item"
bl_label = "Clear Item"
bl_options = {'INTERNAL'}

node_tree: StringProperty()
node: StringProperty()

def execute(self, context):
tree = bpy.data.node_groups[self.node_tree]
node = tree.nodes[self.node]
node.item_set = False
return {'FINISHED'}


Of course this is just example code and adding new items should be smarter than just adding items to a global list, but this is the general idea.