automatically update EnumProperty from UI Panel

Hi
I’m trying to create a drop down menu in a panel, so far it’s all working, but I need to make the user update the list manually, using an operator.
If I try to update automatically, I get error message that I’m currently in the readonly State. If I use the update function of the emumproperty that works, but ppl. would have to chose their value twice.

Is there a way to call my_update function for the enumProperty as soon as I choose something from a prop_search list?
That would be the ideal solution.

From the api docs:

For dynamic values a callback can be passed which returns a list in the same format as the static list. This function must take 2 arguments (self, context) WARNING: There is a known bug with using a callback, Python must keep a reference to the strings returned or Blender will crash.

On the risk of sounding stupid, I don’t know how that helps me…
As far as I got it, the callback of this only updates the list once I choose an item from that list. I need it to be altered by another event, preferably by choosing an item from my prop_search. Or does that work with the get / set.
Could you give me an example? Sorry, sometimes an API is very cryptic to me.

I didn’t see there were examples above the place of your link. I guess get / set is the key here. Problem is: in the example the set just prints something and the get returns an integer. I need the list items to be changed.

Sorry, I read the title and missed that you are using UILayout.prop_search.

If you use UILayout.prop an Enum Property will be displayed as a dropdown list. When the user presses the button the items will be queried from the passed callback function.

I.e.


import bpy

def callback(self, context):
    items = [
        ("#1", "First Item",   "", 0),
        ("#2", "Second Item",  "", 1)
    ]
    if context.scene.bool == True:
        items.append(("#3", "Third Item", "", 2))   
    return items

def update(self, context):
   #snap back to the first item when scene.bool becomes False
    if not context.scene.bool and self['test'] == 2:
        self.test = "#1"

bpy.types.Scene.test = bpy.props.EnumProperty(items=callback)
bpy.types.Scene.bool = bpy.props.BoolProperty(update=update)
bpy.context.scene.bool = False
bpy.context.scene.test = "#1"

I have no clue, how you are using EnumProperty with prop_search. Reading the description you will likely have to use a bpy.props.CollectionProperty or any collection which is provided by blender, to search for an item in this collection and assign it to the given data path.

As far as i know there are no subscribable events available.

If you don’t find the api documentation helpful you might try blender.stackexchange.

1 Like

Ok, getting that bool in there to call the update function is an interesting approach, but I think in my case, I can’t use it. I think I need to be more precise in my description:

I have a UI
I use a prop_search to let the user pick a Node
Once the object is selected a dropdown list appears
The items in the dropdown list is dependent on the item chosen from the prop_search, meaning it will change from Node to Node
I cannot use another prop_search, because I want more items in there than there are attributes to that node.
So if I could call the update() by picking an item from the prop search, that would be ideal.
If I use the update call from the EnumProp I can make it update itself, but I guess this is risky and also the user would have to choose his item twice.
Is there maybe an enum_search or similar? Because the prop_search updates itself dynamically.

or maybe an onUIchange function like in Unity?

Maybe this example helps?

import bpy


class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

    def draw(self, context):
        layout = self.layout

        layout.prop(context.scene, "prop1")
        layout.prop(context.scene, "prop2")

def items_prop1(self, context):
    types = {ob.type: ob.type for ob in context.scene.objects}
    return [(t, t, "") for t in types]
    
def items_prop2(self, context):
    return [(ob.name, ob.name, "") for ob in context.scene.objects if ob.type == context.scene.prop1]
    
def update_prop1(self, context):
    print("Prop1 changed to", context.scene.prop1)
    
def update_prop2(self, context):
    print("Prop2 changed to", context.scene.prop2)
    

def register():
    bpy.utils.register_class(HelloWorldPanel)
    bpy.types.Scene.prop1 = bpy.props.EnumProperty(name="Type", items=items_prop1, update=update_prop1)
    bpy.types.Scene.prop2 = bpy.props.EnumProperty(name="Name", items=items_prop2, update=update_prop2)
    
def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)
    del bpy.types.Scene.prop1
    del bpy.types.Scene.prop2


if __name__ == "__main__":
    register()


modified it slightly


import bpy


class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

    def draw(self, context):
        layout = self.layout
        layout.prop_search(
            context.scene.node_tree.nodes,
            "active",
            context.scene.node_tree,
            "nodes"
            )
        layout.prop(
            context.scene.node_tree.nodes.active,
            "test",
            "Choose"
            )
                            
def get(self):
    if 'test' in self:
        return self['test']
    else:
        #default option
        return -1

def set(self, value):
    self['test'] = value

def items_composite():
    return [
        ("#1", "Composite - First Item",  "", 0),
        ("#2", "Composite - Second Item", "", 1),
        ("#3", "Composite - Third Item",  "", 2)
    ]
    
def items_r_layers():
    return [
        ("#1", "Render Layers - First Item",  "", 0),
        ("#2", "Render Layers - Second Item", "", 1)
    ]    

def callback(self, context):
    base = [("##", "None", "", -1)]
    if   self.type == "COMPOSITE":
        return base + items_composite()
    elif self.type == "R_LAYERS":
        return base + items_r_layers()
    else:
        return base

def register():
    bpy.utils.register_class(HelloWorldPanel)
    bpy.types.Node.test = bpy.props.EnumProperty(
        items=callback,
        get=get,
        set=set
        )

def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)


if __name__ == "__main__":
    bpy.context.scene.use_nodes = True
    register()

1 Like

Thanks you two, that was exactly what I needed.

Thanks CoDEmanX.