Dear Community,
this is our answer 
- Why is c4d.C4DPL_BUILDMENU fired 4 times, instead of once?
In general, we do not make any promises regarding messages being emitted only once or things not being None
\ nullptr
. When there are any guarantees, the message or function description will explicitly say so. The recent change in behavior is caused by the menu being dynamically rebuilt by Cinema 4D.
- If we react to the first pluginMessage only (→ same time in startup sequence), why is R26 behaving differently?
I am not quite sure how you mean that, what do you mean by first? Are you using a module attribute like, for example, didBuiltMenu
? Your code did not show this. You could use a module attribute, and this would mostly work, as *.pyp
plugin modules are persistent, but reloading the Python plugins will throw a wrench into things. I personally would simply check for the existence of a menu item by title.
- Is there something like executedDelayed(), so that we can execute Python code at the very end of the startup sequence, after the UI initialization has been finished?
There are multiple phases in the lifecycle of a Cinema 4D instance, they are all documented under PluginMessage. When you want to update the menu outside of C4DPL_BUILDMENU
, you must call c4d.gui.UpdateMenus()
after the changes.
But this all seems a bit too complicated for the task IMHO. Simply search for the menu entry you want to add, e. g., My Menu, and stop operations when it already does exist. You could also make this more complicated and index based, so that you can distinguish two menus called My Menu. You could also flush and update existing menus, in the end, menus are just an instance of c4d.BaseContainer
. You can more or less do what you want.
Find a simple example for your problem below.
Cheers,
Ferdinand
Result:

The code (must be saved as a pyp file):
"""Demonstrates adding and searching menu entries.
"""
import c4d
import typing
# A type alias for a menu data type used by the example, it is just easier to define a menu as
# JSON/a dict, than having to write lengthy code.
MenuData: typing.Type = dict[str, typing.Union[int, 'MenuData']]
# Define the menu which should be inserted. The data does not carry a title for the root of the
# menu, it will be defined by the #UpdateMenu call. All dictionaries are expanded (recursively)
# into sub-menus and all integer values will become commands. The keys for commands do not matter
# in the sense that Cinema 4D will determine the label of a menu entry, but they are of course
# required for the dictionary. For older version of Cinema 4D, you will have to use #OrderedDict,
# as ordered data for #dict is a more recent feature of Python (3.7 if I remember correctly).
MENU_DATA: MenuData = {
"Objects": {
"0": c4d.Ocube,
"1": c4d.Osphere,
"Splines": {
"0": c4d.Osplinecircle,
"1": c4d.Osplinerectangle
}
},
"0": 13957, # Clear console command
# "1": 12345678 # Your plugin command
}
def UpdateMenu(root: c4d.BaseContainer, title: str, data: MenuData, forceUpdate: bool = False) -> bool:
"""Adds #data to #root under a menu entry called #title when there is not yet a menu entry
called #title.
When #forceUpdate is true, the menu of Cinema 4D will be forced to update to the new state
outside of C4DPL_BUILDMENU.
"""
def doesContain(root: c4d.BaseContainer, title: str) -> bool:
"""Tests for the existence of direct sub containers with #title in #root.
One could also use #c4d.gui.SearchMenuResource, this serves more as a starting point if one
wants to customize this (search for a specific entry among multiple with the same title,
flushing an entry, etc.).
"""
# BaseContainer can be iterated like dict.items(), i.e., it yields keys and values.
for _, value in root:
if not isinstance(value, c4d.BaseContainer):
continue
elif value.GetString(c4d.MENURESOURCE_SUBTITLE) == title:
return True
return False
def insert(root: c4d.BaseContainer, title: str, data: MenuData) -> c4d.BaseContainer:
"""Inserts #data recursively under #root under the entry #title.
"""
# Create a new container and set its title.
subMenu: c4d.BaseContainer = c4d.BaseContainer()
subMenu.InsData(c4d.MENURESOURCE_SUBTITLE, title)
# Iterate over the values in data, insert commands, and recurse for dictionaries.
for key, value in data.items():
if isinstance(value, dict):
subMenu = insert(subMenu, key, value)
elif isinstance(value, int):
subMenu.InsData(c4d.MENURESOURCE_COMMAND, f"PLUGIN_CMD_{value}")
root.InsData(c4d.MENURESOURCE_SUBMENU, subMenu)
return root
# #title is already contained in root, we get out. You could also fashion the function so
# that is clears out an existing entry instead of just reporting its existence, but I did not
# do that here.
if doesContain(root, title):
return False
# Update #root and force a menu update when so indicated by the user.
insert(root, title, data)
if forceUpdate and c4d.threading.GeIsMainThreadAndNoDrawThread():
c4d.gui.UpdateMenus()
return True
def PluginMessage(mid: int, data: typing.Any) -> bool:
"""Updates the menu with some menu data only once when C4DPL_BUILDMENU is emitted.
"""
if mid == c4d.C4DPL_BUILDMENU:
# Get the whole menu of Cinema 4D an insert #MENU_DATA under a new entry "MyMenu"
menu: c4d.BaseContainer = c4d.gui.GetMenuResource("M_EDITOR")
UpdateMenu(root=menu, title="MyMenu", data=MENU_DATA)
def SomeFunction():
"""Can be called at any point to update the menu, as long as the call comes from the main
thread (and is not a drawing thread).
"""
# Get the whole menu of Cinema 4D an insert #MENU_DATA under a new entry "MyMenu"
menu: c4d.BaseContainer = c4d.gui.GetMenuResource("M_EDITOR")
UpdateMenu(root=menu, title="MyMenu", data=MENU_DATA, forceUpdate=True)
if __name__ == "__main__":
pass