Permanently expanded submenu in pie menu

If you want to display an enum from an operator, use:

col.operator_enum("wm.template_operator", "preset_enum")
1 Like

And to display operator as menu, I have to use operator_menu_enum, right?

col.operator_menu_enum("wm.template_operator", "preset_enum")

As a drop-down, yes.

1 Like

Iā€™m sorry, but I have another bunch of questions.

  1. How to embed panel (BONE_PT_relations for example) into custom operator?
  2. And into pie menu as button? (like in PME)

2020-09-25 15_02_42-Blender

2020-09-25 15_04_14-Blender
2020-09-25 15_14_22-Blender

import bpy
from bpy.types import Menu, Operator

class CUSTOM_MT_menu(Menu):
    bl_label = "Main Menu"
    bl_idname = "CUSTOM_MT_menu"

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

        pie.operator("object.custom_operator", text='Operator')

class CUSTOM_OT_operator(Operator):
    bl_idname = "object.custom_operator"
    bl_label = "Operator"
    
    def execute(self, context):
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_props_dialog(self, width=70)

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

        layout.operator("mesh.primitive_cube_add", text = "Cube", icon = "MESH_CUBE")
        layout.operator("mesh.primitive_cube_add", text = "Cube", icon = "MESH_CUBE")
        layout.operator("mesh.primitive_cube_add", text = "Cube", icon = "MESH_CUBE")
          
classes = (    
    CUSTOM_MT_menu,
    CUSTOM_OT_operator
    )

register, unregister = bpy.utils.register_classes_factory(classes)

if __name__ == "__main__":
    register()

    bpy.ops.wm.call_menu_pie(name="CUSTOM_MT_menu")
  1. pie.popover doesnā€™t work with armature panels (btw same situation in PME). I assume itā€™s because of

Is it bug? How to deal with it?

Thanks in advance.

Did you try it yourself? If yes but nothing happens, the what does the system console say?

You can use wm.call_panel or write your own operator that displays itself as a button, which when clicked opens a specific panel.

Not really a bug. Some context members arenā€™t meant to exist outside certain areas. You can use the context override I showed earlier to manually assign these members.
Something like:

class Context(dict):
    def __init__(self, context=None, **kwargs):
        super().__init__()
        self.__dict__ = self

        if context is not None:
            self.update(context.copy())
        self.update(**kwargs)


arm = context.object if context.object.type == 'ARMATURE' else None
context_ = Context(context,
                   bone=context.active_bone,
                   armature=arm)
bpy.types.BONE_PT_relations.draw(self, context_)
1 Like

To be fairā€¦Initially I had no idea :laughing: But after re-reading your first answer in this thread, I managed to do it, thank you.

The question is how to add collapse/expand button and label on top, and get rid of ā€œOKā€ button then Iā€™m calling operator from pie menuā€¦(like in PME, third screenshot in my previous message).

import bpy
from bpy.types import Menu, Operator

class CUSTOM_MT_menu(Menu):
    bl_label = "Main Menu"
    bl_idname = "CUSTOM_MT_menu"

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

        pie.operator("object.custom_operator", text='Test Operator')

        pie.popover("DATA_PT_display", text="Relations")
        pie.popover("OBJECT_PT_display", text="Display")

 
class CUSTOM_OT_operator(Operator):
    bl_idname = "object.custom_operator"
    bl_label = "Test Operator"
    
    def execute(self, context):
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_props_dialog(self)

    def draw(self, context):
        layout = self.layout
        
        box = layout.box()
        col = box.column()
        override = Layout(col)
        bpy.types.OBJECT_PT_display.draw(override, context)
        
        if context.object.type == 'ARMATURE':
            context_ = Context(context, armature=context.object.data)
            box = layout.box()
            col = box.column()
            override = Layout(col)
            bpy.types.DATA_PT_display.draw(override, context_)


class Context(dict):
    def __init__(self, context=None, **kwargs):
        super().__init__()
        self.__dict__ = self

        if context is not None:
            self.update(context.copy())
        self.update(**kwargs)

class Layout:
    def __init__(self, layout):
        self.layout = layout

          
classes = (    
    CUSTOM_MT_menu,
    CUSTOM_OT_operator,
    )

register, unregister = bpy.utils.register_classes_factory(classes)

if __name__ == "__main__":
    register()

    bpy.ops.wm.call_menu_pie(name="CUSTOM_MT_menu")

Wellā€¦But itā€™s not pie.popover. Or I donā€™t quite understandā€¦nevermind.
For some reason I forget to insert

        pie.popover("DATA_PT_display", text="Relations")
        pie.popover("OBJECT_PT_display", text="Display")

into my code last time.

Ah. And I know nothing about wm.call_panel. There is not to much information about it. How to use it?

You can create an enum in your custom operator and use layout.operator_enum(). To call the draw function without an ā€œOKā€ button, use context.window_manager.invoke_popup().

import bpy
from bpy.types import Menu, Operator

class CUSTOM_MT_menu(Menu):
    bl_label = "Main Menu"
    bl_idname = "CUSTOM_MT_menu"

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

        pie.operator_enum("object.custom_operator", "enum")

#        pie.popover("DATA_PT_display", text="Relations")
#        pie.popover("OBJECT_PT_display", text="Display")

 
class CUSTOM_OT_operator(Operator):
    bl_idname = "object.custom_operator"
    bl_label = "Test Operator"

    enum: bpy.props.EnumProperty(items=(
        ("RELATIONS", "Relations", ""),
        ("DISPLAY", "Display", ""),
    ))

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

    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_popup(self)

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

        if self.enum == 'DISPLAY':
            layout.label(text="Display")
            box = layout.box()
            col = box.column()
            override = Layout(col)
            bpy.types.OBJECT_PT_display.draw(override, context)

        elif self.enum == 'RELATIONS':
            if context.object.type == 'ARMATURE':
                layout.label(text="Relations")
                context_ = Context(context, armature=context.object.data)
                box = layout.box()
                col = box.column()
                override = Layout(col)
                bpy.types.DATA_PT_display.draw(override, context_)


class Context(dict):
    def __init__(self, context=None, **kwargs):
        super().__init__()
        self.__dict__ = self

        if context is not None:
            self.update(context.copy())
        self.update(**kwargs)

class Layout:
    def __init__(self, layout):
        self.layout = layout

          
classes = (    
    CUSTOM_MT_menu,
    CUSTOM_OT_operator,
    )

register, unregister = bpy.utils.register_classes_factory(classes)

if __name__ == "__main__":
    register()

    bpy.ops.wm.call_menu_pie(name="CUSTOM_MT_menu")

Note that you wonā€™t get control over where each button is placed in the pie. You can get around this by creating an EnumProperty() with one element in each.

1 Like

Thank you!
Is it possible to add those triangle collapse/expand buttons to panels?

To draw the expand/collapse arrows you would need to write the toggle logic in the operator. That means an expand BoolProperty for each panel you want to draw, and drawing it at the top of the panel. To do this programmatically is possible, but it requires a bigger framework. Suggest you take a look at how PME does it.

import bpy
from bpy.types import Menu, Operator

class CUSTOM_MT_menu(Menu):
    bl_label = "Main Menu"
    bl_idname = "CUSTOM_MT_menu"

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

        pie.operator("object.custom_operator")

 
class CUSTOM_OT_operator(Operator):
    bl_idname = "object.custom_operator"
    bl_label = "Test Operator"

    relations_expand: bpy.props.BoolProperty(default=True)
    display_expand: bpy.props.BoolProperty(default=True)

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

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

        box = layout.box()
        row = box.row()
        row.alignment = 'LEFT'
        row.emboss = 'NONE'
        expand = self.display_expand
        icon = "TRIA_DOWN" if expand else "TRIA_RIGHT"
        row.prop(self, "display_expand", text="Display", icon=icon)
        if expand:
            override = Layout(box)
            bpy.types.OBJECT_PT_display.draw(override, context)


        type(context).armature = context.object.data

        box = layout.box()
        row = box.row()
        row.alignment = 'LEFT'
        row.emboss = 'NONE'
        expand = self.relations_expand
        icon = "TRIA_DOWN" if expand else "TRIA_RIGHT"
        row.prop(self, "relations_expand", text="Relations", icon=icon)
        if expand:
            override = Layout(box)
            bpy.types.DATA_PT_display.draw(override, context)

    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_popup(self)

class Context(dict):
    def __init__(self, context=None, **kwargs):
        super().__init__()
        self.__dict__ = self

        if context is not None:
            self.update(context.copy())
        self.update(**kwargs)

class Layout:
    def __init__(self, layout):
        self.layout = layout

          
classes = (    
    CUSTOM_MT_menu,
    CUSTOM_OT_operator,
    )

register, unregister = bpy.utils.register_classes_factory(classes)

if __name__ == "__main__":
    register()

    bpy.ops.wm.call_menu_pie(name="CUSTOM_MT_menu")
2 Likes

Thank you so much!