Top menu "recursively" executed as it's own submenus, with argument(s) passed to it ?

I’m within my third design of my context menu. Now I have only one menu class for the toplevel/root menu and it’s submenus. Every submenu is a a definition and is stored within it’s own tuple, and each one that contain submenu(s) contains reference(s) to other tuple(s).

The code within draw() of that single generic menu is supposed to iterate through active tuple, fetch each entry, and call layout.operator(), layout.separator(), and layout.menu(), depending of the entry fetched. Here’s my problem. I need to tell layout.menu() to set/pass a variable, which for the purpose of this addon, is a tuple, the active tuple.

My first thought was something along OperatorProperties for operators, and so I figured something like MenuProperties, and although I haven’t tried, the documentation doesn’t contain such a thing so I don’t think that exists. Another option I thought (technically) possible was a parameter (aka the famous almighty ‘userdata’) passed to draw(): draw(self, context, data), appointed with layout.menu(“xol.menu”, data = a_tuple), But the doc doesn’t reveal any such thing either. So the last possibility would be a method of the menu class being called when a submenu was about to open (or draw() receiving that index as parameter), being passed an entry index (identifying the submenu). But I did not see any such thing in the documentation either.

Why is there no “MenuProperties” (OperatorProperties and "MenuProperties btw should be the same thing, with same name) returned by UILayout.menu() ? I think there should. I hope we’re not supposed to programmatically create unique classes for every submenu. That is extremely awkward, and is highly inconsistent with OperatorProperties.

I thought about ‘trickery’, but since any submenu is opened randomly (user’s desire), there is no general programmatic trickery to accomplish this. So I wonder, is there any way to accomplish this with the API (except programmatic creation of unique classes) ? Thank you.

I did a multi level menu using Global vars
a bit tedious but it works

only way I found to make it work

show us some codes as example

problem with class is that they are created at beginning of execution and cannot be change afterwad!

happy bl

The problem is that the menu class, since it’s the only one being executed, and since there is no information available to detect which submenu is opening, is left clueless and dumb. I already provided three different ways the API could support to make the menu aware of this. You cannot do this with global variables. How would that work ?

When a submenu is opened, and since every submenu is declared with layout.menu(SingleMenuClass.bl_idname), that single class’ draw() method will execute. So, regardless if it’s the toplevel menu or a particular submenu for which draw() is being executed, draw() cannot know. There seems to be no information available to detect that. Because of that, draw() has no clue what tuple to fetch the current (opening) menu definitions from.

as I said I did one with any number of sub level menu

sub menu can be call and shown
only problem is to get the sub menu item out of the class and this is where I used a global var for it
then it works fine but for each sub menu you need to create an operator function of sub menu item then it executes some other operator or function!

show us some code you did just to see the flow of info

I have to re locate where I put that multi Panel menu script

mind you it is also possible to do it with the other method for sub menu
which works a lot better and easier no need for global variable
Like adding menu / sub menu in the Add menu on bottom header
and this updates the mesh data as soon as you change a properties
which is one way to do thing.

it is only with the class panel that you need to pass info with global var
mind you this is probably a hack and might not work later In in 2.8 !

happy bl

see my multi level menu sub menu with add mesh menu here

see first post for file

is it what your looking for ?

happy bl

When the draw() function is first called, it fetches a tuple called “menu”, which contains the definition for the toplevel menu. That menu (along with submenus) tuple contains a sequence of tuples, and each such tuple defines a menu item:

menu = (
    ('o', ('OBJECT', 'EDIT_MESH'), "some.operator", "some label"),
    ('o', ('all',), "another.operator", "another label"),
    ('s',),
    ('m', ('all',), menu_mode, "mode"),
    # etc..
)

Each menu item tuple starts with a type identifier: ‘o’ (operator), ‘s’ (separator), and ‘m’ ((sub)menu). The menu in this example refers to a submenu definition tuple named ‘menu_mode’, and the format of that tuple is of course exactly like that of ‘menu’, it just contains different entries. The example menu definition is simplified because it’s inclusion is really irrelevant to the issue. But I included it anyway FWIW.

Here, you asked for code:

class XolMenu(bpy.types.Menu):
    bl_idname = "xol.menu"
    bl_label = "xol menu"


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

        populated = False

        for item in menu:
            if item[0] == 's':
                if populated:
                    layout.separator()
                    populated = False

            elif item[0] == 'o':
                if 'all' in item[1] or mode in item[1]:
                    layout.operator(item[2], text = item[3])
                    populated = True

            elif item[0] == 'm':
                if 'all' in item[1] or mode in item[1]:
                    layout.menu(XolMenu.bl_idname, text = item[3])

                    # would have been nice instead:
                    #     layout.menu(XolMenu.bl_idname, text = item[3], definition = item[2])
                    # or:
                    #     mp = layout.menu(XolMenu.bl_idname, text = item[3])
                    #     mp.definition = item[2]

                    populated = True

The code is also rewritten for simplification and clearity, and doesn’t do error checking yet. No (micro-)optimization and such. I could have deleted the lines with the ‘populated’ boolean variable, but included it so that the code doesn’t look stupid since it otherwise would pile up any separators with empty in-betweens :slight_smile:

I would need to change ‘menu’ in the traversal loop (‘for item in menu:’) to something dynamic depending on what submenu caused the new execution of draw(). But as mentioned, it doesn’t seem possible.

Anyway, you mentioned operators. Of course I thought about operators as menu items that set some global variable and call “wm.call_menu”, but these would require clicks and cause the parent menu to close down. Is that what you are suggesting ?

Edit:

Wait. I just stumbled upon operator_menu_enum(), is that what you mean ?
I have to investigate. But right now, it just appears I would need to register such operators. But then again, I could do that with menu classes as well. It might be slightly nicer this way, but it’s still awkward as far as I’m concerned. (I still need to investigate)

did you check the menus structure of my script for spiral ?
is it what your looking for ?

Code sample

first menu is not even defined
need some data first or at least define the menu var empty

are you working with panel or add menu type menu ?

how many sub menu levels are you working with ?

there are some example for dynamic menu in template look into text editor templates
if you need I can find some examples ?

panel is very limited in what is allowed to work with in BL

mind you all that is gone change again in 2.8 but will have to wait and see what happen later on this year!

happy bl

here is example of dynamic menu



 
#description for add-on's menu
 
"Dynamic 3D View Menu (Double Right click in View3D)"
 
 
import bpy
import mathutils
from math import *
from bpy.props import *
 
layers = [False]*20
layers[0] = True
 
#import dynamic_menu
 
####
 
class VIEW3D_MT_tools(bpy.types.Menu):
 
 bl_label = "Tools"
 
 def draw(self, context):
 
  layout = self.layout
  layout.operator_context = 'INVOKE_REGION_WIN'
 
  ob = context
 
  if ob.mode == 'OBJECT':
 
   layout.operator("mesh.primitive_plane_add")
   layout.operator("mesh.primitive_cube_add")
   layout.operator("mesh.primitive_circle_add")
   layout.operator("mesh.primitive_uv_sphere_add")
   layout.operator("mesh.primitive_ico_sphere_add")
   layout.operator("mesh.primitive_tube_add")
   layout.operator("mesh.primitive_cone_add")
   layout.separator()
   layout.operator("object.lamp_add")
   layout.separator()
   layout.operator("object.armature_add")
   layout.separator()
   layout.operator("object.parent_set")
 
  elif ob.mode == 'EDIT_MESH':
 
   layout.operator("mesh.loopcut_slide",text="Loopcut")
   layout.operator("mesh.merge", text="Merge")
   ob = context.tool_settings
   if ob.mesh_selection_mode == 'FACE':
    layout.operator("mesh.extrude_move_along_normals",text="Extrude")
   else:
    layout.operator("mesh.extrude_move",text="Extrude")
 
   if ob.mesh_selection_mode == 'EDGE':
    layout.operator("transform_OT_edge_slide",text="Edge Slide")  
 
   layout.operator("mesh.remove_doubles",text="Remove Doubles").limit=0.0100
   layout.operator("mesh.subdivide")
   layout.operator("mesh.edge_face_add")
   layout.operator("mesh.select_more")
   layout.separator()
   layout.menu("VIEW3D_MT_addM")
   layout.menu("VIEW3D_MT_editM_Edge")
 
  layout.operator("screen.redo_last", text="Tweak Last")
  layout.operator("transform.snap_type", text="3D cursor")
 
####
 
class VIEW3D_MT_addM(bpy.types.Menu):
 
 bl_label = "Add"
 
 def draw(self, context):
 
  layout = self.layout
  layout.operator_context = 'INVOKE_REGION_WIN'
  layout.operator("mesh.primitive_plane_add")
  layout.operator("mesh.primitive_cube_add")
  layout.operator("mesh.primitive_circle_add")
  layout.operator("mesh.primitive_uv_sphere_add")
  layout.operator("mesh.primitive_ico_sphere_add")
  layout.operator("mesh.primitive_tube_add")
  layout.operator("mesh.primitive_cone_add")
 
####
 
class VIEW3D_MT_editM_Edge(bpy.types.Menu):
 
 bl_label = "Edges"
 
 def draw(self, context):
 
  layout = self.layout
  layout.operator_context = 'INVOKE_REGION_WIN'
 
 
 
  layout.separator()
 
  layout.operator("mesh.mark_seam")
  layout.operator("mesh.mark_seam", text="Clear Seam").clear = True
 
  layout.separator()
 
  layout.operator("mesh.mark_sharp")
  layout.operator("mesh.mark_sharp", text="Clear Sharp").clear = True
  layout.operator("mesh.extrude_move_along_normals",text="Extrude")
 
  layout.separator()
 
  layout.operator("mesh.edge_rotate", text="Rotate Edge CW").direction = 'CW'
  layout.operator("mesh.edge_rotate", text="Rotate Edge CCW").direction = 'CCW'
 
  layout.separator()
 
  layout.operator("TFM_OT_edge_slide", text="Edge Slide")
  layout.operator("mesh.loop_multi_select", text="Edge Loop")
 
  # uiItemO(layout, "Loopcut", 0, "mesh.loop_cut"); // CutEdgeloop(em, 1);
  # uiItemO(layout, "Edge Slide", 0, "mesh.edge_slide"); // EdgeSlide(em, 0,0.0);
 
  layout.operator("mesh.loop_multi_select", text="Edge Ring").ring = True
 
  layout.operator("mesh.loop_to_region")
  layout.operator("mesh.region_to_loop")
 
####
 
 
 
 
 
 
 
 
####
 
 
 
 
def register():
 
 bpy.utils.register_class(VIEW3D_MT_tools)
 bpy.utils.register_class(VIEW3D_MT_addM)
 bpy.utils.register_class(VIEW3D_MT_editM_Edge)
 
 #km = bpy.context.manager.active_keyconfig.keymaps['3D View']
 #kmi = km.add_item('wm.call_menu', 'RIGHTMOUSE', 'DOUBLE_CLICK')
 
 kc = bpy.context.window_manager.keyconfigs['Blender']
 km=kc.keymaps.get('3D View')
 kmi = km.keymap_items.new('wm.call_menu', 'RIGHTMOUSE', 'DOUBLE_CLICK')
 kmi.properties.name = "VIEW3D_MT_tools"
 
 
def unregister():
 
 bpy.utils.unregister_class(VIEW3D_MT_tools)
 bpy.utils.unregister_class(VIEW3D_MT_addM)
 bpy.utils.unregister_class(VIEW3D_MT_editM_Edge)
 
 #km = bpy.context.manager.active_keyconfig.keymaps['3D View']
 
 
 
 
 km=bpy.data.window_managers['WinMan'].keyconfigs['Blender'].keymaps['3D View']
 
 for kmi in km.items:
 
  if kmi.idname == 'wm.call_menu':
 
   if kmi.properties.name == "VIEW3D_MT_tools":
    km.remove_item(kmi)
   break
 
 
 
 
if __name__ == "__main__":
 register()
 
 
 
 
 
 
 
 


happy bl

As I wrote right now I just fetch ‘menu’ in draw(), because there is no way to tell what to fetch given layout.menu() doesn’t support that. The code isn’t operational, so it’s just a template for now.

There is no theoretic limit to the nested level of submenus, it’s all controlled by the tree of linked/referenced tuples, as it should be.

I haven’t looked at your addon. Perhaps I’ll take a look another day. Thanks for the feedback.

try to get a more complete code with panel
it is difficult to work at such a low level
and all the API limitations

may be someone else can help

I can give more examples but need to have a more defined task!

thanks
happy bl

I wrote in detail what the single class is supposed to do. Btw, I’m not doing any Panel. It’s a context menu for View*D areas. Nevertheless, no need for more examples. I’ll try to see what’s he least painful workaround at some point. Thanks, and have a nice day :slight_smile:

Hi @xol !

I have exactly the same problem : I’m trying to create sub menus on the fly, from data on my hard drive… and no way to link the data tree with the sub menus.

Did you find any workaround for this problem ?