Is it possible to add submenu (from another custom class) in pie menu is a such way as to make it always expanded (without howering over it). Or maybe it possible with popup menu as submenu? (For example, there is “Expand Popup Dialog” checkbox in Pie Menu Editor addon).
And second question is how to insert panels in pie menu? Again, it is possible with Pie Menu Editor but I want to know how do it by myself, without addons.
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.menu("CUSTOM_MT_submenu", text='SubMenu')
pie.operator("object.custom_operator", text='Operator')
col = pie.column()
col.operator("mesh.primitive_cube_add", text = "Cube", icon = "MESH_CUBE")
col.operator("mesh.primitive_cube_add", text = "Cube", icon = "MESH_CUBE")
col.operator("mesh.primitive_cube_add", text = "Cube", icon = "MESH_CUBE")
class CUSTOM_MT_submenu(Menu):
bl_label = "Sub Menu"
bl_idname = "CUSTOM_MT_submenu"
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")
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_MT_submenu,
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")
So simple! Thank you! I wish blender docs would be provided with more examples instead of this
menu_contents
Parameters
**menu** ( *string* *,* *(* *never None* *)* ) – Identifier of the menu
If you don’t mind, can you please explain second part in more detail and where I can read about ? I tried to insert
DATA_PT_display and for some reason it doesn’t work (with OBJECT_PT_display eveything is ok). And what if I want to insert another panels? I have to use something like this, right?
There aren’t any docs that I know of that really explain this. I’ve just been at it for too long.
When a draw method is called with the two arguments self and context, generally self can be anything as long as it provides an attribute called layout.
With the override, I simply made a generic instance with one attribute, layout, set to the argument it was created with:
col2 = pie.column()
override = Layout(col2)
Maybe you’re wondering why we can pass something like a column as a layout?
When you write something like:
row = layout.row()
row is now an instance derived from layout. A branch of the original, with an inherent horizontal drawing direction, but still a bpy.types.UILayout. Same with layout.column(), layout.box(), layout.box().box().column().split(). The return from chaining those is still a layout.
So, as you pass the override to the panel’s draw function, when the draw function does
layout = self.layout
self now points to the override. And self.layout becomes the column col2 we passed as override.
When something doesn’t work, generally it’s a good idea to look in the system console. The python error messages are extremely helpful. It’s likely the panel has more requirements, or that the context passed doesn’t provide enough data.
Yes. There’s surely other ways to do it, but the gist is to call the panel’s draw with your own layout.
class Layout:
def __init__(self, layout):
self.layout = layout
As I understand it’s internal class. Everything else is pretty clear. Thanks again!
File "X:\Blender\blender-2.83.6-windows64\2.83\scripts\addons\_test_pie_panels.py", line 37, in draw
bpy.types.DATA_PT_display.draw(override, context)
File "X:\Blender\blender-2.83.6-windows64\2.83\scripts\startup\bl_ui\properties_data_armature.py", line 83, in draw
arm = context.armature
AttributeError: 'Context' object has no attribute 'armature'
A class not inheriting from other classes is just a generic class. The reason we define a Layout class is because it needs to hold an attribute other functions can programmatically access. We’re literally just smacking a .layout onto a thingy and tell the draw function, “hey, take this object as your self. It’s got a layout you can access”.
I’m assuming you’re running the script from the text editor. Context is dynamic. The available members depend on the area you are currently in. When you call the panel in the viewport, things like context.armature will be available.
Right. The issue wasn’t the viewport, but the properties editor. context.armature doesn’t exist outside of properties, so you must generate a new context and manually add armature to it.
Good news is, the procedure is same as with the layout override. Adding a check to emulate a poll is a good idea since drawing the panel yourself circumvents the original one. The bad news is, well, there are no bad news.
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)
File "X:\Blender\blender-2.83.6-windows64\2.83\scripts\addons\_test_pie_panels.py", line 43, in draw
bpy.types.CUSTOM_OT_operator(override, context)
AttributeError: 'RNA_Types' object has no attribute 'CUSTOM_OT_operator'
location: <unknown location>:-1
File "X:\Blender\blender-2.83.6-windows64\2.83\scripts\addons\_test_pie_panels.py", line 44, in draw
CUSTOM_OT_operator(override, context)
TypeError: bpy_struct.__new__(type): expected a single argument
File "X:\Blender\blender-2.83.6-windows64\2.83\scripts\addons\_test_pie_panels.py", line 44, in draw
CUSTOM_OT_operator(override)
TypeError: bpy_struct.__new__(type): type 'CUSTOM_OT_operator' is not a subtype of bpy_struct
One more question
Whenever I call Set Roll, Extrude, Duplicate etc. from Armature menu (internal class VIEW3D_MT_edit_armature), everything works.
But if I call from my custom menu
layout.operator("transform.transform", text="Set Roll").mode = 'BONE_ROLL'
or layout.operator("armature.extrude_move")
or layout.operator("armature.duplicate_move")
nothing happens (in case of Extrude or Duplicate pressing G key helps a little) At the same time layout.operator("armature.subdivide", text="Subdivide"), layout.operator("armature.switch_direction")
Setting operator_context sets the current state for subsequently defined operators. The already defined operators keep the previous state when they were instanced.
Another question
How to insert custom enum operator into pie menu ? Just class referencing doesn’t work
Traceback (most recent call last):
File "\ui_pie_enum_operator.py", line 16, in draw
File "\ui_pie_enum_operator.py", line 40, in draw
TypeError: UILayout.prop(): error with argument 1, "data" - Function.data expected a AnyType type, not Layout