Hey @pim,
Using BaseContainer.InsDataAfter()
does not make too much sense in this case, as you cannot get hold of the PyCObject
that is wrapping the data entries you are after. The method has its place in other cases, but for menus, it is pretty much meaningless in Python. A visualization of a menu container looks like this:
{
MENURESOURCE_SUBTITLE: "My Menu"
MENURESOURCE_COMMAND: [
ID_COMMAND_1,
ID_COMMAND_2,
ID_COMMAND_3
]
MENURESOURCE_SUBMENU: [
{
MENURESOURCE_SUBTITLE: "Weeh, I am a sub menu!"
MENURESOURCE_COMMAND: [ ID_FOO, ID_BAR ]
},
{
MENURESOURCE_SUBTITLE: "Can you please be quiet up there?"
MENURESOURCE_COMMAND: [ ID_BOB, ID_IS, ID_YOUR, ID_UNCLE ]
}
]
}
As you can see here and in your own code, all entries in a menu that are commands, are stored under the same ID, namely MENURESOURCE_COMMAND
. So, you would have to get hold of for example the data pointer for ID_COMMAND_1
to insert something after it. But you cannot in Python, you can only get the PyCObject
data pointers for whole entries with BaseContainer.GetDataPointer
. What you must do in your case, is rebuilding the whole submenu. Find a code example below.
:warning: WE DO NOT CONDONE PLUGINS THAT REORDER CONTENT IN THE NATIVE MENUS OF CINEMA 4D. In general, plugins should either appear under the main menu entry "Extensions" or have their own main menu entry, e.g., "XParticles". They should not inject themselves into native main menu entries, e.g., "Render". When it is unavoidable to do this (which it never is), you should at least NOT REORDER things. This applies only for publicly shipped plugins/scripts. In a private or studio environment, you are free to remodel all menus to your liking. |
Cheers,
Ferdinand
Result:

Code:
"""Demonstrates how to insert an item into an existing menu.
WE DO NOT CONDONE PLUGINS THAT REORDER CONTENT IN THE NATIVE MENUS OF CINEMA 4D. In general,
plugins should either appear under the main menu entry "Extensions" or have their own main menu
entry, e.g., "XParticles". They should not inject themselves into native main menu entries, e.g.,
"Render". When it is unavoidable to do this (which it never is), you should at least NOT REORDER
things:
NOT OK - "Render": [a, myNewStuff, ..., n]
OK-ish - "Render": [a, ..., n, myNewStuff]
This has less a technical and more a UX reason, as we want to avoid having plugins going around that
break the UX habits of users, i.e., the muscle memory of clicking the item that is "that much" pixels
below the root of "Render".
This applies only for publicly shipped plugins/scripts. In a private or studio environment, you are
free to remodel all menus to your liking.
"""
import c4d
def main() -> None:
"""Runs the example.
"""
for _, data in c4d.gui.GetMenuResource("M_EDITOR"):
if not (isinstance(data, c4d.BaseContainer) and
data[c4d.MENURESOURCE_SUBTITLE] == "IDS_EDITOR_RENDER"):
continue
# To do what you want to do, you will have to flush the container. When you are not careful
# and remove menu items which Cinema 4D expects to be present, or change menu structures
# that Cinema 4D considers to be given, you can crash Cinema 4D.
# Get all items in 'M_EDITOR' as a list of key-value pairs and bail when our insertion point
# is not in there. "IDM_RENDERAUSSCHNITT" as an insertion point alone is ambiguous, as we
# do not care about a MENURESOURCE_SUBTITLE with that value for example.
insertionPoint: tuple[int, str] = (c4d.MENURESOURCE_COMMAND, "IDM_RENDERAUSSCHNITT")
items: list[tuple[int, str | c4d.BaseContainer]] = [item for item in data]
if insertionPoint not in items:
continue
# Insert a new command for the cube object after that value.
menuEntry: tuple[int, str] = (c4d.MENURESOURCE_COMMAND, "PLUGIN_CMD_5159")
items.insert(items.index(insertionPoint) + 1, menuEntry)
# Flush the container and write the new data.
data.FlushAll()
for cid, value in items:
data.InsData(cid, value)
# Make sure to update menus sparingly, your code did this with every iteration of the loop.
c4d.gui.UpdateMenus()
if __name__ == '__main__':
main()