Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    EnchanceMenu Questions

    Cinema 4D SDK
    c++ sdk
    3
    15
    1.9k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • D
      d_schmidt
      last edited by

      Hello, I was looking over some threads and the SDK examples for EnhancingMainMenu(), so I know how to make menus, but I had a couple of questions.

      If I had a series of plugins that I wanted to have a custom menu dropdown would it be possible to have it getting updated by subsequent plugins loading.

      So for example, Plugin A makes the menu and is inserted underneath it. Then Plugin B is there and I want that in the same menu, but above Plugin A. If I compiled them separately, A before B, so B being inserted had to be done within Plugin B instead of Plugin A, how would I do that?

      This is the simplest case I could think of, but I think there would be more complicated circumstances.

      Thanks for any help,
      Dan

      1 Reply Last reply Reply Quote 0
      • ManuelM
        Manuel
        last edited by Manuel

        hi,

        Sorry for the late reply.
        It's a matter of retrieving the right BaseContainer and Insert the data in the right spot.
        If you want to change the order, you need to "copy" the existing data after adding the new ones first.

        case C4DPL_BUILDMENU:
        // react to this message to dynamically enhance the menu
        // do this only if necessary - otherwise the user will end up with dozens of menus!
        {
        	// This lambda will return the basecontainer of the menu if founded.
        	auto searchMenu = [](const maxon::String& identifier) -> BaseContainer*
        	{
        		BaseContainer* bc = GetMenuResource(String("M_EDITOR"));
        		if (bc == nullptr)
        			return nullptr;
        
        		Int32						id;
        		BaseContainer* resContainer = nullptr;
        		GeData* dat;
        		BrowseContainer browse(bc);
        		while (browse.GetNext(&id, &dat))
        		{
        			if (id == MENURESOURCE_SUBMENU || id == MENURESOURCE_STRING)
        			{
        				BaseContainer* dc = dat->GetContainer();
        				if (dc)
        				if (dc && dc->GetString(MENURESOURCE_SUBTITLE) == identifier)
        				{
        					resContainer = dc;
        					break;
        				}
        			}
        		}
        		return resContainer;
        	};
        
        	// Retrieve the M_EDITOR container.
        	BaseContainer* bc = GetMenuResource(String("M_EDITOR"));
        	if (!bc) return true;
        
        	// Check if the menu is already added.
        	Bool alreadyExist = false;
        	BaseContainer *existingMenu = searchMenu("My menu"_s);
        
        
        	if (existingMenu == nullptr)
        	{
        		// Create a empty basecontainer to add our menu.
        		existingMenu = NewObjClear(BaseContainer);
        		
        	}
        	else
        	{
        		alreadyExist = true;
        	}
                finally {
        	  DeleteObj(existingMenu);
        	};
        	// If the menu isn't created we need to add the string first
        	if (!alreadyExist)
        	{
        		existingMenu->InsData(MENURESOURCE_STRING, String("My menu"));
        	}
        
        	existingMenu->InsData(MENURESOURCE_SEPERATOR, true);
        	existingMenu->InsData(MENURESOURCE_COMMAND, String("PLUGIN_CMD_1021433")); // character
        
        	// we need to insert the menu only if it doesn't exit.
        	if (!alreadyExist)
        	{
        		// We want to insert the menu after the last one.
        		GeData* last = nullptr;
        		last = SearchPluginMenuResource();
        		if (last)
        			bc->InsDataAfter(MENURESOURCE_STRING, *existingMenu, last);
        		else // user killed plugin menu - add as last overall entry
        			bc->InsData(MENURESOURCE_STRING, *existingMenu);
        	}
        	
        	
        	return true;
        	break;
        }
        

        Cheers,
        Manuel

        MAXON SDK Specialist

        MAXON Registered Developer

        D 1 Reply Last reply Reply Quote 0
        • D
          d_schmidt @Manuel
          last edited by

          Hi @m_magalhaes, super sorry about the late response.

          That worked to achieve what I was hoping for, thank you. But now I've got another question.

          How would I do this same thing but within the Plugin/Extension menu? I want the parent folder for the plugin to be in that menu and in the correct place. It seems to be in alphabetic order by default, but when I use InsData or InsDataAfter and SearchPluginMenuResource my folder seems to appear at the very top of the menu or the very bottom.

          Thanks again,
          Dan

          ManuelM 1 Reply Last reply Reply Quote 0
          • ManuelM
            Manuel @d_schmidt
            last edited by

            hi,

            The idea is the same, search the right BaseContainer find it and add your commandData etc.

            Of course you need to register your plugins with the flag PLUGINFLAG_HIDE otherwise it will appear twice.

            This code will add the plugin at the end of the "Extension" menu.

            case C4DPL_BUILDMENU:
            		{
            
            
            			auto searchMenu = [](const BaseContainer* bc, const maxon::String& identifier) -> BaseContainer*
            			{
            				Int32						id;
            				BaseContainer* resContainer = nullptr;
            				GeData* dat;
            				BrowseContainer browse(bc);
            				while (browse.GetNext(&id, &dat))
            				{
            					if (id == MENURESOURCE_SUBMENU || id == MENURESOURCE_STRING)
            					{
            						BaseContainer* dc = dat->GetContainer();
            						if (dc)
            							if (dc && dc->GetString(MENURESOURCE_SUBTITLE) == identifier)
            							{
            								resContainer = dc;
            								break;
            							}
            					}
            				}
            				return resContainer;
            			};
            
            			auto GetLastItem = [](const BaseContainer* bc) -> GeData*
            			{
            				Int32						id;
            				GeData* dat;
            				GeData* res = nullptr;
            				BrowseContainer browse(bc);
            				while (browse.GetNext(&id, &dat))
            				{
            					res = dat;
            				}
            				return res;
            			};
            
            
            			// Retrieve the M_EDITOR container.
            			BaseContainer* bc = GetMenuResource("M_EDITOR"_s);
            			if (!bc) return true;
            
            			GeData* extensionMenuData = nullptr;
            			extensionMenuData = SearchPluginMenuResource("IDS_EDITOR_PIPELINE"_s);
            			if (!extensionMenuData)
            				return false;
            
            			BaseContainer* extentionMenu = extensionMenuData->GetContainer();
            			if (extentionMenu == nullptr)
            				return false;
            
            			// Check if the menu is already added.
            			Bool alreadyExist = false;
            			BaseContainer* myMenu = nullptr;
            			myMenu = searchMenu(extentionMenu, "My Menu"_s);
            
            			if (myMenu == nullptr)
            			{
            				// Create a empty basecontainer to add our menu.
            				myMenu = NewObjClear(BaseContainer);
            			}
            			else
            			{
            				alreadyExist = true;
            			}
            
            			finally {
            				DeleteObj(myMenu);
            			};
            
            			// If the menu isn't created we need to add the string first
            			if (!alreadyExist)
            			{
            				myMenu->InsData(MENURESOURCE_STRING, String("My menu"));
            				myMenu->InsData(MENURESOURCE_SUBTITLE, String("My menu that will be displayed"));
            			}
            			
            			// myMenu->InsData(MENURESOURCE_SEPERATOR, true);
            			myMenu->InsData(MENURESOURCE_COMMAND, String("PLUGIN_CMD_1021433")); // character
            
            			// we need to insert the menu only if it doesn't exit.
            			if (!alreadyExist)
            			{
            				// We want to insert the menu after the last one.
            				GeData* last = GetLastItem(extentionMenu);
            				if (last)
            					extentionMenu->InsDataAfter(MENURESOURCE_SUBMENU, *myMenu, last);
            				else
            					extentionMenu->InsData(MENURESOURCE_STRING, *myMenu);
            			}
            
            
            			return true;
            			break;
            		}
            

            Cheers,
            Manuel

            MAXON SDK Specialist

            MAXON Registered Developer

            D 1 Reply Last reply Reply Quote 0
            • D
              d_schmidt @Manuel
              last edited by

              Hi @m_magalhaes, thanks for the response.

              When I first use your code I was getting this result.

              alt text

              When I swapped:

              //From
              extensionMenuData = SearchPluginMenuResource("IDS_EDITOR_PIPELINE"_s);
              
              //To
              extensionMenuData = SearchPluginMenuResource("IDS_EDITOR_PLUGINS"_s);
              

              I got this result:

              alt text

              This makes sense to me, but just something I wasn't expecting.

              My previous question was about how to have "My menu that be displayed" be in the correct location, between the separators, and above the "Py-OffsetYSpline" in this case.

              Similar to this with the folders at the top:

              alt text

              Dan

              1 Reply Last reply Reply Quote 0
              • ManuelM
                Manuel
                last edited by Manuel

                hi,

                well it's not really possible. The pluginMenu (Extension menu since S22) is dynamic.

                It is possible to insert your plugin before the plugin list like so.
                2bc72f99-e1b6-42ef-8026-8a5b3fcd1ac9-image.png
                But if you pop out the menu, it will appear like so:
                0b6fd5e8-7b77-4e16-9695-58476a1956dd-image.png
                you have to search for a command that have the following string IDM_PLUGINS

                
                auto GetLastItem = [&pluginTitle, &GetTileFromContainer](const BaseContainer* bc) -> GeData*
                {
                	iferr_scope_handler
                	{
                		return nullptr;
                	};
                
                	Int32						id;
                	GeData* dat;
                	GeData* res = nullptr;
                	BrowseContainer browse(bc);
                	// Int32 countToThree = 0;
                	while (browse.GetNext(&id, &dat))
                	{
                		if (id == MENURESOURCE_COMMAND)
                		{
                			const String commandString = dat->GetString();
                			if (commandString.Compare("IDM_PLUGINS"_s) == maxon::COMPARERESULT::EQUAL)
                				return res;
                		}
                		else
                		{
                			res = dat;
                		}
                
                	}
                	return nullptr;
                };
                			
                

                Cheers,
                Manuel

                MAXON SDK Specialist

                MAXON Registered Developer

                D 1 Reply Last reply Reply Quote 0
                • D
                  d_schmidt @Manuel
                  last edited by

                  Hi @m_magalhaes, I think I understand that part, but the limitation seems to harsh.

                  To go back to a more clear example, there is the Arnold plugin folder.

                  alt text

                  So just so I completely understand I have two questions. How is this structure done? I know if you have multiple plugins than the top level folder is created, but if this isn't being done in EnhanceMainMenu fashion I don't get it.

                  Second, would it be possible to do this same sort of layout, but across multiple complies. Imagine if the Arnold Light tools came out later. Would it be possible to put it in this layout?

                  Thanks for sticking through this so far!

                  Dan

                  1 Reply Last reply Reply Quote 0
                  • ManuelM
                    Manuel
                    last edited by

                    hi,

                    I don't have the code of how they are doing it, but it seems those are CommandData using GetSubcontainer and ExecuteSubID to execute the proper command. This will create a menu for each entry that you define in the GetSubcontainer function. (note that this doesn't work currently in python)

                    You could maybe use the Message function to react and build an private data stored in your command. But it seems not possible to send a message to a CommandData.

                    If you want your user to "group" your plugins, they can just create a parent directory for all of them.
                    plugins>parent>plugin1Folder
                    plugins>parent>plugin2Folder

                    That will Create a submenu called "parent" to the Extension menu.

                    I don't think what you are trying to do is possible.

                    Cheers,
                    Manuel

                    MAXON SDK Specialist

                    MAXON Registered Developer

                    D 1 Reply Last reply Reply Quote 0
                    • D
                      d_schmidt @Manuel
                      last edited by

                      Hi @m_magalhaes, I was running a bunch of different tests and trying to figure some stuff out on my own, but I'm back with what I think are my last three questions on this topic.

                      alt text

                      I was able to create this, and it seems to be working pretty well, but I have to have two plugins registered.

                      Bool PluginStart()
                      {
                         if(!RegisterCommandTest1())//empty, not doing anything
                             return FALSE;
                          
                          if(!RegisterCommandTest2())// making the menu
                             return FALSE;
                         
                      return TRUE;
                      }
                      

                      Even though the RegisterCommandTest1() plugin is doing nothing if I doubt register two plugins in the complile the menu isn't created. Even if I use PLUGINFLAG_HIDE on RegisterCommandTest1() the menu still isn't created.

                      alt text

                      Is there a way to create the menu with only one plugin being registered?

                      My other question is with how I'm making the Cube entry in the first picture using

                       bc.InsData(Ocube, "CMD");
                      

                      This seems like the easiest way to create entries for plugins, but if the plugin is hidden with PLUGINFLAG_HIDE, it also doesn't sure up in this menu, is there a way around this?

                      And if not, how is an icon attached to an entry? I find some posts about the issue and some information in the Python SDK, but I wasn't able to get any of it working in C++.

                      Thank you so much for the help through this!
                      Dan

                      1 Reply Last reply Reply Quote 0
                      • D
                        d_schmidt
                        last edited by d_schmidt

                        I was doing more testing and I realized I should be using PLUGINFLAG_HIDEPLUGINMENU not PLUGINFLAG_HIDE, so ignore the part about that, sorry!

                        1 Reply Last reply Reply Quote 0
                        • ManuelM
                          Manuel
                          last edited by

                          hi,

                          Is there a way to create the menu with only one plugin being registered?

                          no, you need to register two plugins to be sure to have the folder in the menu and none have to be hidden of course. The "problem" you will not be able to detach this submenu. (by clicking the doted line on the top of the menu)

                          And if not, how is an icon attached to an entry?

                          The icon will be the icon registered when you register the plugin itself, it's automatic. This is the way you add it to your menu

                          bc.InsData(MENURESOURCE_COMMAND, maxon::String("PLUGIN_CMD_1021433")); // character
                          bc.InsData(MENURESOURCE_COMMAND, maxon::String("PLUGIN_CMD_5159")); // cube
                          

                          Do you want to display another icon than the one you register your plugin with ?

                          yes, use PLUGINFLAG_HIDEPLUGINMENU 🙂

                          Cheers,
                          Manuel

                          MAXON SDK Specialist

                          MAXON Registered Developer

                          D 1 Reply Last reply Reply Quote 0
                          • D
                            d_schmidt @Manuel
                            last edited by

                            Do you want to display another icon than the one you register your plugin with ?

                            I was mostly just curious to how you would do it.

                            Although would the case be that if you want an icon you do it as a command and make a plugin?

                            And if you don't want an icon you could just react to it via ExecuteSubID?

                            Dan

                            1 Reply Last reply Reply Quote 0
                            • ManuelM
                              Manuel
                              last edited by

                              hi,

                              in both case you need to add an action in ExecuteSubID. If you register the command you will just call it, otherwise you have to execute some code.

                              I would say that it's better to register your own CommandData, it will be icon ready and easier if you want to move it to another plugins package.

                              Cheers,
                              Manuel

                              MAXON SDK Specialist

                              MAXON Registered Developer

                              1 Reply Last reply Reply Quote 0
                              • ferdinandF
                                ferdinand
                                last edited by

                                Hello @d_schmidt,

                                without any further questions, we will consider this topic as solved by Monday and flag it accordingly.

                                Thank you for your understanding,
                                Ferdinand

                                MAXON SDK Specialist
                                developers.maxon.net

                                1 Reply Last reply Reply Quote 0
                                • D
                                  d_schmidt
                                  last edited by

                                  Hi! Sorry about not replying and closing the topic. This has been solved. Thank you for all the help!

                                  1 Reply Last reply Reply Quote 0
                                  • First post
                                    Last post