How to use InsDataAfter in menu
-
On 01/07/2018 at 08:05, xxxxxxxx wrote:
Hi,
It's been a while ago since I last made some Python plugin/script.
I was playing around with the Object Manager's menu and wanted to add an entry, right after the "Scroll to first active object".I have been able to add it to the end of the "View" menu, but not right at the desired location.
Haven't tried it in C++ as I am kind of prototyping this in Python first.Now, I suppose I need to use
BaseContainer.InsDataAfter
( id , n , last ) but the "last" is troubling me. I thought of needing to useBaseContainer.GetDataPointer
( id ), which has been made available since R17.053 but cannot seem to find out what the "id" is I need to pass.import c4d def main() : omMenu = c4d.gui.GetMenuResource("M_OBJECT_MANAGER") for index, menu in omMenu: if index == c4d.MENURESOURCE_SUBMENU: subtitle = menu.GetString(c4d.MENURESOURCE_SUBTITLE) for idx, submenu in menu: if idx == c4d.MENURESOURCE_COMMAND and submenu == "PLUGIN_CMD_100004769": # found # how to obtain the found entry as a pointer, in order to add a menu entry right after it ? target = ???? menu.InsDataAfter(c4d.MENURESOURCE_COMMAND, "PLUGIN_CMD_1041359", target) c4d.gui.UpdateMenus() break; if __name__=='__main__': main()
-
On 03/07/2018 at 05:46, xxxxxxxx wrote:
Hi C4DS,
I'm afraid it's something not possible in python. At least not for this menu, actually the ID you have to use with GetDataPointer is the ID of the value in the BaseContainer. But since this is a command they are all added with the ID c4d.MENURESOURCE_COMMAND, which is always 3. So all the commands get an ID of 3, and when using GetDataPointer(3) it will simply get the first one with this ID.
So if you want to stick to python, your only way is to rebuild the full list from scratch (which can be tedious). But since you are going to move to C++ here is the code to do it in C++
void MySearchMenuResource(BaseContainer* bc) { if (bc == nullptr) return; BrowseContainer browse(bc); Int32 id = 0; GeData * dat = nullptr; while (browse.GetNext(&id, &dat)) { if (id == MENURESOURCE_SUBMENU || id == MENURESOURCE_STRING) { MySearchMenuResource(dat->GetContainer()); } else if (id == MENURESOURCE_COMMAND) { if (dat->GetString() == String("PLUGIN_CMD_100004769")) { bc->InsDataAfter(MENURESOURCE_COMMAND, String("PLUGIN_CMD_5159"), dat); UpdateMenus(); } } } } Bool YourCommandPlugin::Execute(BaseDocument* doc) { BaseContainer *bc = GetMenuResource(String("M_OBJECT_MANAGER")); if (!bc) return true; MySearchMenuResource(bc); return true; }
Cheers,
Maxime -
On 03/07/2018 at 09:41, xxxxxxxx wrote:
Thanks Maxime, however I am a little confused.
You mention it not to be possible in python to directly use the InsDataAfter and that the full list needs to be rebuild. I would then assume this would be possible in C++.
Still you provide the code in C++ to rebuild the full list. But if I would switch to C++ I wouldn't need to rebuild the list, right?What does "MySeachMenuResource()" represent in your code?
Oh, wait ... I get it. The code you provided is not to rebuild the list, it is how I need to use InsDataAfter in C++. When you mentioned "... here is the code do to it in C++" , I assumed the "it" referred to: how to rebuild the full list.
Not sure I will switch to C++ for this plugin, as I prefer not having to build for Windows and MacOS.
So, I 'll need to figure out how to rebuild the list. -
On 04/07/2018 at 08:15, xxxxxxxx wrote:
Hi Daniels,
Sorry for all the misunderstanding in my previous answers.
So I will try to resay what I have in mind more clearly.To use InsertDataAfter you have to use GetDataPointer which unfortunately only accept an index.
Sadly in menu index are also used to know which kind of menu entry it is (MENURESOURCE_COMMAND, MENURESOURCE_SUBMENU, MENURESOURCE_SEPARATOR) so you end up with a BaseContainer which get the same index a bunch of time. And GetDataPointer will simply take the first one.So in order to avoid that you get two solutions.
Use C++ which get BaseContainerBrowser class which allow you to iterate a BaseContainer and allow you to add data directly where you want. The cleanest solution so far, I updated the code in my previous post (sorry for the misleading information)But if you want to stick to python. Since you can't use InsDataAfter, the only way you have is to use InsData which allow you to insert data at a given ID or after a given ID if it's already present in the BaseContainer. So the whole strategy is to copy all the subMenus (from M_OBJECT_MANAGER), modify submenu, insert them (to M_OBJECT_MANAGER), and deletes the previous one
Here is the codeimport c4d def bcToList(bc) : l = list() for k, v in bc: d = (k, v) l.append(d) return l def listToBc(l) : bc = c4d.BaseContainer() for v in l: bc.InsData(v[0], v[1]) return bc def addCommandAfter(bc, data, valueName) : lst = bcToList(bc) nextToAdd = False for i in range(len(lst)) : if lst[i][1] == valueName: nextToAdd = True continue if nextToAdd: nextToAdd = False lst.insert(i, data) return listToBc(lst) def AddDataToSubMenu(globalMenuName, SubMenuName, previousName, dataToAdd) : globalMenu = c4d.gui.GetMenuResource(globalMenuName) mList = list() for subMenu in globalMenu: if subMenu[c4d.MENURESOURCE_SUBMENU][c4d.MENURESOURCE_SUBTITLE]== SubMenuName: modifiedMenu = addCommandAfter(subMenu[c4d.MENURESOURCE_SUBMENU], dataToAdd, previousName) mList.append((c4d.MENURESOURCE_SUBMENU, modifiedMenu)) else: mList.append(subMenu) for subMenu in mList: globalMenu.InsData(subMenu[0] , subMenu[1]) for x in xrange(len(mList)) : globalMenu.RemoveData(c4d.MENURESOURCE_SUBMENU) c4d.gui.UpdateMenus() def main() : AddDataToSubMenu("M_OBJECT_MANAGER", "IDS_SB_SHOW", "PLUGIN_CMD_100004769", (c4d.MENURESOURCE_COMMAND, "PLUGIN_CMD_5159")) if __name__=='__main__': main()
If you have any question please let me know!
Cheers,
Maxime