Do all buttons on a menu need to be an operator?

In short, yes. Each button, unless it’s a bool property with toggle=True flag, is an operator.

Some operators, however, are so-called enum operators, where an EnumProperty controls the type of action the operator does. Blender has a special layout for these called layout.operator_enum(op_name, enum_name).

When this is called in a menu layout, each item of the enum is placed as its own button. When one of these buttons are clicked, the operator is specifically called with the enum identifier as argument.

As an example, try adding to your menu:

layout.operator_enum("object.light_add", "type")

To build your own enum operator, the syntax is essentially:

class OBJECT_OT_some_operator(bpy.types.Operator):
    bl_idname = "object.some_operator"
    bl_label = "Some Operator"

    enum_items = (
        ("item1", "Item 1", "Description 1"),
        ("item2", "Item 2", "Description 2"),
        ("item3", "Item 3", "Description 3"),
    )
    action: bpy.props.EnumProperty(items=enum_items)

    def execute(self, context):
        print(self.action)
        return {'FINISHED'}

To place a single operator with just one of the items:

layout.operator("object.some_operator").action = 'item1'

To place all items:

layout.operator("object.some_operator", "action")

It’s also possible to have an operator dynamically generate enums. This generates an enum using a simple range(), though you could use anything, like dynamically getting a piece of script. Note that the function must take self and context. self points to the operator instance.

def generate_enum(self, context):
    enum = []

    for index in range(10):
        id_ = "item" + str(index)  # id_ used by operator.
        name = id_  # Name is shown in ui
        desc = "Description " + str(index)  # Description.
        icon = "FILE"  # Icon name. Can also be an integer.
        enum.append((id_, name, desc, icon, index))
    return enum

This requires the items argument of an enum property to point to the function.

action: bpy.props.EnumProperty(items=generate_enum)

image

The whole thing:

import bpy

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

    def draw(self, context):
        layout = self.layout
        layout.operator_enum("object.some_operator", "action")

def generate_enum(self, context):
    enum = []

    for index in range(10):
        id_ = "item" + str(index)  # id_ used by operator.
        name = id_  # Name is shown in ui
        desc = "Description " + str(index)  # Description.
        icon = "FILE"  # Icon name. Can also be an integer.
        enum.append((id_, name, desc, icon, index))
    return enum

class OBJECT_OT_some_operator(bpy.types.Operator):
    bl_idname = "object.some_operator"
    bl_label = "Some Operator"

    action: bpy.props.EnumProperty(items=generate_enum)

    def execute(self, context):
        print(self.action)
        return {'FINISHED'}

if __name__ == "__main__":
    bpy.utils.register_class(OBJECT_OT_some_operator)
    bpy.utils.register_class(SimpleCustomMenu)
    bpy.ops.wm.call_menu(name=SimpleCustomMenu.bl_idname)
6 Likes