3
$\begingroup$

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.

example of template_ID() chooser

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.

Things I've already tried:

  • 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.

$\endgroup$
5
  • 1
    $\begingroup$ You can use an UIList, see: Create an interface which is similar to the material list box $\endgroup$
    – p2or
    Commented Aug 18, 2019 at 12:45
  • $\begingroup$ 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. $\endgroup$
    – geloescht
    Commented Aug 18, 2019 at 15:59
  • 1
    $\begingroup$ As you can see, UIList has a compact mode which should also work as part of an invoke_props_dialog. $\endgroup$
    – p2or
    Commented Aug 18, 2019 at 17:10
  • $\begingroup$ 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. $\endgroup$
    – geloescht
    Commented Aug 18, 2019 at 21:30
  • $\begingroup$ @geloescht I'm trying to use template_search() but it's not properly working. I'm not sure how to use template_search(). If you can take a look at here, that would be really helpful. blender.stackexchange.com/q/229373/115433 $\endgroup$
    – Reigen
    Commented Jul 8, 2021 at 12:04

1 Answer 1

3
$\begingroup$

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 = group.operator('example.new_item', icon='ADD', text='New')
        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.

$\endgroup$

You must log in to answer this question.

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