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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.