Dynamic operator_menu_enum assignment

I’m trying to do a dynamic operator_menu_enum assignment without global variables and at the time of the layout.
I have adapted a piece of CoDEmanX gold and it works almost completely (even the tooltips) except for the final assignment. The Show info bit is just to show that it works.

Has anyone any ideas.


import bpy

class ShowInfo(bpy.types.Operator):
    bl_idname = "object.showinfo"
    bl_label = "Report Information"
    info = bpy.props.StringProperty()
    
    def execute(self, context):
        self.report({'INFO'}, "{:}".format(self.info))
        return {'FINISHED'}
    
    def invoke(self, context, event):
        return self.execute(context)


class SimpleOperator(bpy.types.Operator):
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"
    enum_items = (('ONE','One','First'),('TWO','Two','Second'))
    myprop = bpy.props.EnumProperty(items=enum_items)

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        enum_items_dict = {id: name for id, name, desc in self.enum_items}
        self.__class__.bl_label = enum_items_dict[self.myprop]
        self.__class__.bl_description = enum_items_dict[self.myprop]
        return {'FINISHED'}


class SimpleCustomMenu(bpy.types.Menu):
    bl_label = "Simple Custom Menu"
    bl_idname = "OBJECT_MT_simple_custom_menu"

    def draw(self, context):
        layout = self.layout
        op = layout.operator(ShowInfo.bl_idname, text = 'Say Hi', icon='HAND' ) 
        op.info = "Hello"
        op = layout.operator(ShowInfo.bl_idname, text = 'Say Yes', icon='HAND' ) 
        op.info = "No"
        op2 = layout.operator_menu_enum(SimpleOperator.bl_idname, "myprop", text=SimpleOperator.bl_label)
        op2.enum_items = (('1','One','First'),('2','Two','Second'),('THREE','Three','Third'))
        
def register():
    bpy.utils.register_class(ShowInfo)
    bpy.utils.register_class(SimpleCustomMenu)
    bpy.utils.register_class(SimpleOperator)

if __name__ == "__main__":
    register()

# The menu can also be called from scripts
bpy.ops.wm.call_menu(name=SimpleCustomMenu.bl_idname)



You can’t really do it without global variables:

import bpy

class ShowInfo(bpy.types.Operator):
    bl_idname = "object.showinfo"
    bl_label = "Report Information"
    info = bpy.props.StringProperty()
    
    def execute(self, context):
        self.report({'INFO'}, "{:}".format(self.info))
        return {'FINISHED'}
    
    def invoke(self, context, event):
        return self.execute(context)


def enum_items_cb(self, context):
    l = (('ONE','One','First'), ('TWO','Two','Second'), ('THREE', 'Three', 'Third'))
    enum_items_cb.lookup = {id: name for id, name, desc in l}
    return l

class SimpleOperator(bpy.types.Operator):
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"

    myprop = bpy.props.EnumProperty(items=enum_items_cb)

    def execute(self, context):
        self.report({'INFO'}, enum_items_cb.lookup[self.myprop])
        return {'FINISHED'}


class SimpleCustomMenu(bpy.types.Menu):
    bl_label = "Simple Custom Menu"
    bl_idname = "OBJECT_MT_simple_custom_menu"

    def draw(self, context):
        layout = self.layout
        op = layout.operator(ShowInfo.bl_idname, text = 'Say Hi', icon='HAND' ) 
        op.info = "Hello"
        op = layout.operator(ShowInfo.bl_idname, text = 'Say Yes', icon='HAND' ) 
        op.info = "No"
        op = layout.operator_menu_enum(SimpleOperator.bl_idname, "myprop", text=SimpleOperator.bl_label)
        
def register():
    bpy.utils.register_module(__name__)

def unregister():
    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
    register()

# The menu can also be called from scripts
bpy.ops.wm.call_menu(name=SimpleCustomMenu.bl_idname)

There’s a hidden global variable, which is the callback function for enum items. I use it to store a lookup dictionary of enum entries, so I can map the IDs to their names without a second call to enum_items_cb(), which could possibly return a different sequence of triples if the callback generated a real dynamic sequence.

Excellent thanks again again again.
Man, I have learned so much from you and your great examples

Just checked out the code.
That is a very clever and elegant solution Sir

I know this is probably a dumb python question but why doesn’t this work as well


def enum_items_cb(self, context, l=(('ONE','One','First'), ('TWO','Two','Second'), ('THREE', 'Three', 'Third'))):
    enum_items_cb.lookup = {id: name for id, name, desc in l}
    return l

I know it really hasn’t got anything to do with the solution but I’m just curious what I got wrong in the assignment.
I will work that out one day, I’m still learning.

the registration of myprop property fails, because the items-parameter expects a function with exactly 2 arguments. Yours has 3.

Ahhhhhhh Thank You

I am having trouble getting a handle on the scope in this case.
I want to be able to pass a variable to the enum_items_cb function.
Debugging is really hard as print() doesn’t seem to be run inside the function even though the code runs and fails.
(that is for anything other than actual python errors that break the whole code)

str(getattr(bpy.context.window_manager.windows[i], 'height'))

but something like

self.my_string

doesn’t
It would be cool to be able to do someting like


            op = r.operator_menu_enum(SimpleOperator.bl_idname,
                "myprop",
                text=out["name"])
            op.show_info_on = out["name"]

Any ideas?

I still can’t work out how to assign a variable to a scope that can be read dynamically to the enum.
I would like to utilize the onmouseover effect of operator_menu_enum so that one button/operator shows one list and the next button/operator shows a different list from the same data.
AFAIK this would need to be attached to the individual button/operator. However changing something at draw time is different to setting it at the time of the call.
The only way I can think of is to build individual classes for each situation on the fly and eval them into existence. That seems a bit over the top and unnecessary for this task.

I think that is just a case of me not understanding scope in python.

you can’t pass anything to the callback function, why would you?

If necessary, use global variables that you can check inside the callback. You shouldn’t use one and the same callback for two different sets of entries for two different menus however.

Thanks CoDEmanX
I now realise that what I was after would also the problems associated with changing stuff from the draw function.
What I have created so far is not completely what I was after however it will do for now.
My addon is functional and basically does what is required so I will get back to actually learning Blender.
Thanks you very much for all your help and good luck in all your endeavors.
Thanks again Yardie