Update content of combobox?

I am writing an addon for blender 2.8.
And I’d like to know if is possible to create a combobox (or dropdown), that has a number of items I’d like to update or change when click on a button?. I am not able to update the drawing of the combobox in the ui when the button is clicked.

Any advice or support?

35

This is my code:

import bpy
#from bpy.props import *

bl_info = {
    "name": "combobox",
    "description": "mio",
    "author": "zebus3d",
    "version": (0, 0, 1),
    "blender": (2, 80, 0),
    "location": "View3D",
    "wiki_url": "",
    "category": "3D View" }

def accionComboBox(self, context):
    print(self.comboItems)
    context.area.tag_redraw()

class myProperties(bpy.types.PropertyGroup):
    if not hasattr(bpy.types.Scene, "my_items"):
        comboItems = [ ("INTERIOR", "default value", "", 1), ("EXTERIOR", "example", "", 2), ("NONE", "None", "", 3) ]
    else:
        comboItems = bpy.types.Scene.my_items

    comboBox = bpy.props.EnumProperty(
        items=comboItems,
        name="Scene Type",
        description="Scene Type",
        default="NONE",
        update=accionComboBox
    )

class ButtonClass(bpy.types.Operator):
    bl_label = "Button"
    bl_idname = "b.action"
    bl_description = "My first button"
    
    def execute(self, context):
        bpy.types.Scene.my_items = [ ("INTERIOR", "new value", "", 1), ("EXTERIOR", "yes!", "", 2), ("NONE", "None", "", 3) ]
        context.area.tag_redraw()
        
        return {'FINISHED'}


class CustomPanel(bpy.types.Panel):
    bl_label = "Generate"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "myComboBox"

    def draw(self, context):
        layout = self.layout
        col = layout.column()
        col.prop(context.scene.my_addon, "comboBox", text="")
        col.operator("b.action", text="Update content")

def register():
    bpy.utils.register_class(myProperties)
    bpy.utils.register_class(ButtonClass)
    bpy.utils.register_class(CustomPanel)
    bpy.types.Scene.my_addon = bpy.props.PointerProperty(type=myProperties)


def unregister():
    bpy.utils.unregister_class(myProperties)
    bpy.utils.unregister_class(ButtonClass)
    bpy.utils.unregister_class(CustomPanel)
    del bpy.types.Scene.my_addon
    del bpy.types.Scene.my_items


if __name__ == "__main__":
    register()

Thanks.

Instead of passing a fixed list of items to EnumProperty(items=...), you can also pass in a function that generates the items at runtime.

def get_items(self, context):
    return ...

comboBox = bpy.props.EnumProperty(items=get_items, ...)
1 Like

Get this error:

TypeError: EnumProperty(...): 'default' can't be set when 'items' is a function
Traceback (most recent call last):
  File "<blender_console>", line 2, in <module>
  File "<blender_console>", line 2, in register
ValueError: bpy_struct "myProperties" registration error: comboBox could not register

My code:

import bpy
#from bpy.props import *

bl_info = {
    "name": "combobox",
    "description": "mio",
    "author": "zebus3d",
    "version": (0, 0, 1),
    "blender": (2, 80, 0),
    "location": "View3D",
    "wiki_url": "",
    "category": "3D View" }

def getItems(self, context):
    if not hasattr(bpy.types.Scene, "my_items"):
        comboItems = [ ("INTERIOR", "default value", "", 1), ("EXTERIOR", "example", "", 2), ("NONE", "None", "", 3) ]
        bpy.types.Scene.my_items = comboItems        
    else:
        comboItems = bpy.types.Scene.my_items
    
    return comboItems
    
def accionComboBox(self, context):
    print(self.comboItems)
    context.area.tag_redraw()

class myProperties(bpy.types.PropertyGroup):
    comboBox = bpy.props.EnumProperty(
        items=getItems,
        name="Scene Type",
        description="Scene Type",
        default="NONE",
        update=accionComboBox
    )

class ButtonClass(bpy.types.Operator):
    bl_label = "Button"
    bl_idname = "b.action"
    bl_description = "My first button"
    
    def execute(self, context):
        bpy.types.Scene.my_items = [ ("INTERIOR", "new value", "", 1), ("EXTERIOR", "yes!", "", 2), ("NONE", "None", "", 3) ]
        context.area.tag_redraw()
        
        return {'FINISHED'}


class CustomPanel(bpy.types.Panel):
    bl_label = "Generate"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "myComboBox"

    def draw(self, context):
        layout = self.layout
        col = layout.column()
        col.prop(context.scene.my_addon, "comboBox", text="")
        col.operator("b.action", text="Update content")

def register():
    bpy.utils.register_class(myProperties)
    bpy.utils.register_class(ButtonClass)
    bpy.utils.register_class(CustomPanel)
    bpy.types.Scene.my_addon = bpy.props.PointerProperty(type=myProperties)


def unregister():
    bpy.utils.unregister_class(myProperties)
    bpy.utils.unregister_class(ButtonClass)
    bpy.utils.unregister_class(CustomPanel)
    del bpy.types.Scene.my_addon
    del bpy.types.Scene.my_items


if __name__ == "__main__":
    register()

Please read the first line of the error message.

Why do you store my_items in bpy.types.Scene?

I want to perform an action with the button. this button collects certain information from the scene, once that action is done I want to update the combobox with the new data collected to be able to choose between them.

Even if I used a text file or a json to save the information. the problem is that it does not refresh the content of the combobox when I would like that, in this case would be just when pressing the button, do the action and update the combobox.

This is my last code:

import bpy


bl_info = {
    "name": "combobox",
    "description": "mio",
    "author": "zebus3d",
    "version": (0, 0, 2),
    "blender": (2, 80, 0),
    "location": "View3D",
    "wiki_url": "",
    "category": "3D View" }


def changedCombo(self, context):
    print(self.comboItems)


def get_items():
    casos = ["example 1", "example 2", "example 3", "example 4"]
    comboItems = []
    comboItems.append(('NONE', "None", "", 0)) 
    for i in range(len(casos)):
        comboItems.append((casos[i].upper(), casos[i], "", i+1))

    # bpy.types.Scene.my_items = comboItems
    return comboItems


class myProperties(bpy.types.PropertyGroup):
    
    #comboItems = get_items()

    # comboItems = [
    #     ("A", "a", "", 1),
    #     ("B", "b", "", 2),
    #     ("NONE", "None", "", 3),
    # ]
    
    comboBox = bpy.props.EnumProperty(
        items=get_items(),
        name="desplegable",
        description="desplegable",
        default="NONE",
        update=changedCombo
    )


class myButton(bpy.types.Operator):
    bl_label = "Button"
    bl_idname = "b.action"
    bl_description = "My button"
    
    def execute(self, context):
        nuevo = [ ("A", "a", "", 1), ("B", "b", "", 2), ("NONE", "None", "", 3) ]
        bpy.types.Scene.my_items = nuevo
        print(bpy.types.Scene.my_items)
        return {'FINISHED'}


class myPanel(bpy.types.Panel):
    bl_label = "Title"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "myComboBox"

    def draw(self, context):
        layout = self.layout
        col = layout.column()
        col.prop(context.scene.my_addon, "comboBox", text="")
        col.operator("b.action", text="Update content")


classes = (
    myProperties,
    myButton,
    myPanel
)


def register():
    from bpy.utils import register_class

    for cls in classes:
        register_class(cls)
    
    bpy.types.Scene.my_addon = bpy.props.PointerProperty(type=myProperties)


def unregister():
    from bpy.utils import unregister_class

    for cls in classes:
        unregister_class(cls)

    del bpy.types.Scene.my_addon
    del bpy.types.Scene.my_items


if __name__ == "__main__":
    register()

I have been able to solve it with a custom menu looking like comobox, but more flexible.
Thanks @jacqueslucke!

Did you look at the Ui Previews Dynamic Enum template? I just had to do something similar and that template gave me some ideas about how to go about it.

1 Like

I’m in trouble again, if I can solve it, I’ll put it here.

This work for me:

import bpy


bl_info = {
    "name": "comoboxTest",
    "description": "learn how work combobox in blender",
    "author": "zebus3d",
    "version": (0, 0, 1),
    "blender": (2, 80, 0),
    "location": "View3D",
    "wiki_url": "",
    "category": "3D View" 
}


def comboCanged(self, context):
    current = bpy.context.scene.comboBox
    print("change to: ", current)

def updateComboContent(cItems):
    if cItems:
        bpy.types.Scene.comboBox = bpy.props.EnumProperty(
            items=cItems,
            name="Items",
            description="Item",
            default=None,
            update=comboCanged
        )


def mySceneProperties():
    bpy.types.Scene.Iname = bpy.props.StringProperty( name = "", default = "", description = "Item Name")

    comboItems = [
        ("no_items", "No items", "", 1),
    ]
    updateComboContent(comboItems)


class myButton(bpy.types.Operator):
    bl_label = "Button"
    bl_idname = "b.action"
    bl_description = "My button"
    
    def execute(self, context):
        fp = bpy.context.blend_data.filepath
        bn = bpy.context.scene.Iname
        if bn:
            if fp:
                comboItems = [
                    ("anew", "Anew", "", 1),
                    ("bnew", "Bnew", "", 2),
                    ("cnew", "Cnew", "", 3),
                    ("no_items", "No items", "", 4),
                ]
                updateComboContent(comboItems)
                try:
                    bpy.context.scene.comboBox = bn
                except:
                    bpy.context.scene.comboBox = "no_items"
            else:
                self.report({'ERROR'}, 'It is necessary to first save your scene')
        else:
            self.report({'ERROR'}, 'Invalid name for Item')

        return {'FINISHED'}


class myPanel(bpy.types.Panel):
    bl_label = "ComboBox"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "ComboBox"

    def draw(self, context):
        scn = bpy.context.scene
        layout = self.layout
        col = layout.column()
        col.prop(scn, "comboBox", text="")
        col.prop(scn, "Iname")
        col.operator("b.action", text="Add Item")


classes = (
    myButton,
    myPanel
)


def register():
    from bpy.utils import register_class

    mySceneProperties()

    for cls in classes:
        register_class(cls)
    
    # mySceneProperties()



def unregister():
    from bpy.utils import unregister_class

    for cls in classes:
        unregister_class(cls)

    del bpy.types.Scene.Iname
    del bpy.types.Scene.comboBox


if __name__ == "__main__":
    register()

That first line says nothing about where its stored, it states items cant be a function. Which is not true, we can use functions.

That storing of items in types.scene is a nono since 2.8

Im also seeing this same error now when its assigned in propertygroup. This is from the sketchfab addon. I guess it needs a None in this case, since its looking for something while by or scene doesnt even excist yet

def get_sorting_options(self,context):
    api = get_sketchfab_props().skfb_api
    if len(api.user_orgs) and api.use_org_profile:
        return (
            ('RELEVANCE', "Relevance", ""),
            ('RECENT', "Recent", "")
        )
    else:
        return Config.SKETCHFAB_SORT_BY
1 Like