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.

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()

Thanks you two, that was exactly what I needed.

Thanks CoDEmanX.