UNSOLVED EnchanceMenu Questions

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

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

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

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

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

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

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

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

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

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

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

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

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