SOLVED Add menu item to existing submenu in Python

Hello,

I have two C++ plugins which register lots of menu entries. These are not generated under the usual "Extensions" / custom plugin menu, but in an own top level menu and its submenus. That works and look like that:

MyMenu                                        
  MySubmenu1
    MyMenuItem1
  MySubmenu2
    MyMenuItem2

Now I want to register a Python script to add a menu item after MyMenuItem2, but it appears after MyMenuItem1.

MyMenu 
  MySubmenu1
    MyMenuItem1
    MyMenuItem3  // here it appears, created from Python
  MySubmenu2
    MyMenuItem2a
    MyMenuItem2b
    MyMenuItem2c 
    *MyMenuItem3*  // here it should appear

I used the official example for building menus and an older forum post How to add a plugin to a toolbar menu?

My code is

def EnhanceMenu():  

  # getting the main menu resource container
  menu = c4d.gui.GetMenuResource("M_EDITOR")
  
  # find our menu
  customMenu = None
  for bcMenuId, bcMenu in menu:
    #print(" " + str(bcMenuId) + "  " + str(bcMenu))
    if bcMenu != 1: #?
      #print(bcMenu[c4d.MENURESOURCE_SUBTITLE])
      if bcMenu[c4d.MENURESOURCE_SUBTITLE] == "MyMenu":
        customMenu = bcMenu
        #customMenu = menu.GetContainerInstance(bcMenuId)
        break
  print(customMenu)

  # find our submenu
  customMenu = None
  menu = customMenu
  for bcMenuId, bcMenu in menu:
    if bcMenu != 1: #?
      #print(" " + str(bcMenuId) + "  " + str(bcMenu))
      #print(bcMenu[c4d.MENURESOURCE_SUBTITLE])
      if bcMenu[c4d.MENURESOURCE_SUBTITLE] == "MySubMenu2":
        customMenu = bcMenu
        break
  print(customMenu)

  # test debug output submenu content
  menu = customMenu
  for bcMenuId, bcMenu in menu:
    if bcMenu != 1: #?
      print(" " + str(bcMenuId) + "  " + str(bcMenu))
      print(bcMenu[c4d.MENURESOURCE_SUBTITLE])
      print(bcMenu[c4d.MENURESOURCE_STRING])

  # create new menu item
  customMenu.InsData(c4d.MENURESOURCE_COMMAND, "PLUGIN_CMD_" + str(MY_COMMAND_ID))

  c4d.gui.UpdateMenus()


def PluginMessage(id, data):
  if id == c4d.C4DPL_BUILDMENU:
    EnhanceMenu()

I know the code is a bit redundant and I compare the menu text instead of the id, but there was not id set in C++. Worth to mention is that the test debug out submenu content prints

 4  MySubMenu2
i
t
 3  PLUGIN_CMD_1000001
I
N
 3  PLUGIN_CMD_1000002
I
N
 3  PLUGIN_CMD_1000003
I
N

The expected strings should be MyMenuItem2a, MyMenuItem2b and MenuItem2c instead of single letters. The commands are correct. And why is MyMenuItem3 within MySubmenu1?

Has someone any idea?

Chris

I encourage Maxon to simplify things for Python here
I've noted it, we will see if we can improve things.

Well pretty strange that it's working now. There's no magic, cache involved there. It's just some BaseContainer and strings.

I noticed when I add a submenu it is created in the correct place. The code:

# create new menu item
#customMenu.InsData(c4d.MENURESOURCE_COMMAND, "PLUGIN_CMD_" + str(MY_COMMAND_ID))
menu = c4d.BaseContainer()
menu.InsData(c4d.MENURESOURCE_COMMAND, "PLUGIN_CMD_" + str(MY_COMMAND_ID))
customMenu.InsData(c4d.MENURESOURCE_SUBMENU, menu)

But it is not what I want.

Hi,

In your code, i don't understand those lines, the loop will never work for me.

customMenu = None
menu = customMenu
for bcMenuId, bcMenu in menu:

Look at this thread, which might help you.

Cheers,
Manuel

Hello Manuel,

The line

customMenu = None

came accidentally in when cleaning up the code for posting. It is not in the actual code. Sorry for the confusion. My current code revision:

def GetMenuContainer(name, mainMenu=None):
  "searches a submenu in given menu basecontainer. Uses toplevel menu if mainMenu is None. Returns False if not found."
  # get top level menu
  if not mainMenu:
    mainMenu = c4d.gui.GetMenuResource("M_EDITOR")

  # search for submenu or entry
  for bcMenuId, bcMenu in mainMenu:
    #print("bcMenuId " + str(bcMenuId) + "  bcMenu " + str(bcMenu))
    if bcMenu != 1:
      #print(bcMenu[c4d.MENURESOURCE_SUBTITLE])
      if bcMenu[c4d.MENURESOURCE_SUBTITLE] == name:
        return bcMenu

  #return mainMenu.InsData(c4d.MENURESOURCE_SUBTITLE, name) # create
  return None

def AddMenuEntry(menu, plugin_id):
  "add a menu command to given menu basecontainer and returns True on success, else False."
  plugin_cmd = "PLUGIN_CMD_{0}".format(plugin_id)
  menu.InsData(c4d.MENURESOURCE_COMMAND, plugin_cmd)
  return c4d.gui.SearchMenuResource(menu, plugin_cmd)


def EnhanceMenu():
  # search our submenu and add an entry for this script
  ok = False
  menu = GetMenuContainer("MyMenu", None)
  if menu:
    menu = GetMenuContainer("MySubmenu2", menu)
    if menu:
      ok = AddMenuEntry(menu, MY_COMMAND_ID)

  # if that failed because user has moved the (sub)menu then add it to default plugin menu
  if not ok:
    menu = c4d.gui.SearchPluginMenuResource()
    if menu:
      ok = AddMenuEntry(menu, MY_COMMAND_ID)

  if not ok:
    print("Unable to register menu command: " + c4d.plugins.GeLoadString(IDS_XXX))

  c4d.gui.UpdateMenus()


def PluginMessage(id, data):
  if id == c4d.C4DPL_BUILDMENU:
    EnhanceMenu()

I read your linked forum post and had already made experiments with recreating the menu structure and will give it another try.

I encourage Maxon to simplify things for Python here.

Kind Regards,
Chris

It works now. Surprisingly I just restarted the PC and I did not changed the python plugin script! I worked for three days on this menu problem and restarted the PC (Windows 64 10 bit) every morning. During that time I did not do any other changes to the OS or Cinema4D (R21 and R23) settings / plugins or Python or similar. Restarting now a couple of times did not go back to the erroneous behavior. So about the final outcome I am half happy and half unhappy.

I encourage Maxon to simplify things for Python here
I've noted it, we will see if we can improve things.

Well pretty strange that it's working now. There's no magic, cache involved there. It's just some BaseContainer and strings.

Hello @Chris-Chris,

without further questions or postings, we will consider this topic as solved by Wednesday and flag it accordingly.

Thank you for your understanding,
Ferdinand