Share

Menu Migration Guide

Menus and quad menus play an important role in delivering a rich user experience when interacting with 3ds Max plugins. 3ds Max 2025 introduced a new menu system which improved performance and menu management. However, this is a breaking change and you must port your pre-2025 menus to work in 3ds Max 2025 and later.

In this guide we describe how to port older menus to work in 3ds Max 2025.

Porting Samples

3ds Max 2025 SDK comes bundled with samples to demonstrate the new menu system. You can access it in the SDK folder: <Max 2025 SDK>/howto/ui/menudemo. This contains samples for creating menus using a menu definition file, C++ SDK, MAXScript and Python (pymxs).

Let's have a look at pre-2025 menu samples in the documentation and how port them to the new menu system. From the 2024 docs here https://help.autodesk.com/view/MAXDEV/2024/ENU/?guid=GUID-258F6015-6B45-4A87-A7F5-BB091A2AE065, let's port over the sample menu to 2025 using both MAXScript and with a menu definition file.

Prerequisites

For this porting guide, we will use a MacroScript to initiate an action item that the menu will invoke. The MacroScript file should be copied under the 3ds Max installation to the folder <3ds Max 202X>/stdplugs/stdscripts.

Here is the script:

-- Sample menu extension script
-- If this script is placed in the "stdplugs\stdscripts"
-- folder, then this will add the new items to MAX's menu bar
-- when MAX starts.
-- A sample macroScript that we will attach to a menu item
macroScript MyTest
category: "Menu Test"
tooltip: "Test Menu Item"
(
   on execute do print "Hello World!"
)

This is a sample menu in the 2024 documentation that creates a submenu called "Test Menu" with a single action item labelled "Menu Test" to execute the MacroScript above.

 -- This example adds a new sub-menu to MAX's main menu bar.
 -- It adds the menu just before the "Help" menu.
 if menuMan.registerMenuContext 0x1ee76d8e then
 (
   -- Get the main menu bar
   local mainMenuBar = menuMan.getMainMenuBar()
   -- Create a new menu
   local subMenu = menuMan.createMenu "Test Menu"
   -- create a menu item that calls the sample macroScript
   local testItem = menuMan.createActionItem "MyTest" "Menu Test"
   -- Add the item to the menu
   subMenu.addItem testItem -1
   -- Create a new menu item with the menu as it's sub-menu
   local subMenuItem = menuMan.createSubMenuItem "Test Menu" subMenu
   -- compute the index of the next-to-last menu item in the main menu bar
   local subMenuIndex = mainMenuBar.numItems() - 1
   -- Add the sub-menu just at the second to last slot
   mainMenuBar.addItem subMenuItem subMenuIndex
   -- redraw the menu bar with the new item
   menuMan.updateMenuBar()
 )

Below is a script showing an equivalent menu in 2025 utilizing the new menu changes. Let's look at the sample first then we'll dissect the changes and what they mean.

function menuCallback =
(
    local menuMgr = callbacks.notificationParam()
    local mainMenuBar = menuMgr.mainMenuBar

    -- Id of "Help" menu in main menu bar. 
    -- Can be found in Menu editor -> right click on menu item -> copy item id
    local helpMenuId = "cee8f758-2199-411b-81e7-d3ff4a80d143"
    local newSubMenu = mainMenuBar.CreateSubMenu "F8FFB827-741C-4A81-8C89-BBF856DCF56D" "Test Menu" beforeId:helpMenuId

    -- Add existing actions from autobackup action table
    local macroScriptActionTableID = 647394
    newSubMenu.CreateAction "6AC5FDFE-2AF7-4069-8493-D8A1462F4E7F" macroScriptActionTableID "MyTest`Menu Test" beforeId:separatorId title:"Menu Test"
)

callbacks.removeScripts id:#menuDemo
callbacks.addScript #cuiRegisterMenus menuCallback id:#menuDemo

As noted earlier, creating menus in 3ds Max can only be done in a callback in MAXScript using #cuiRegisterMenus for regular menus and #cuiRegisterQuadMenus for Quad menus. Once the callback is executed, calling callbacks.notificationParam() within it returns a menu manager object which allows to the main menu bar to create new menu items (sub menus, action items, separators, etc).

Unlike the previous system, the new menu system introduces the use of GUIDs for uniquely identifying the different menu items. When creating a new menu or menu item, you need to provide a GUID, which you can create using a number of tools (such as Visual Studio, or an online generator). You can copy the GUID for an existing menu by right-clicking it in the Menu Editor. The GUIDs for existing menu items can be seen/copied by right clicking on the menu item in the Menu Editor as shown in the image below. The copy option copies the whole menu and its submenu whereas the copy item Id only copies the selected menu/action item GUID to clipboard.

A menu definition file is the preferred way to create menu items. As such, a quick way is to open the menu editor > Under Menus/Quads, click the gear icon > select the Developer Mode. Under the developer mode, only the base and either the selected preset or an empty preset is loaded (no plug-in and user defined menus are present) providing a clean configuration to work with.

Here, selecting an existing menu item > right click > create sub menu gives a dialog to enter a submenu name. With a bare sub-menu item created, drag and drop action items from the left panel containing action items. This requires that these action items are loaded beforehand. With each added menu, submenu or action item, a GUID is automatically added for it. The created menu can then be saved as a mnx/qmnx file and shipped with the plugin (see the packaging section below).

For MacroScript actions, the scripts are placed in the stdplugs/stdscripts folder or if using a PackagingContents.xml file, under the Components section with a description of "macroscripts parts". See the packaging section below for a complete sample. For action items created from the C++ SDK, the corresponding plugin needs to be loaded already. Instead of using the menu editor, we can generate GUIDs using the following commands:

local uid = genGUID()
from pymxs import runtime as rt
uid = rt.genguid()
MaxSDK::MaxGuid::CreateMaxGuid()
Note:

Menu item IDs need to be consistent. Other menu customizations might rely on these ids and thus generating GUIDs dynamically is not recommended for production code.

Using the Menu Editor to create the menu definition file as shown below, we generate a file as shown:

<?xml version="1.0"?>
<MaxMenuTransformations>
    <CreateMenu ParentId="00000000-0000-0000-0000-000000000000" MenuId="92c65129-c29a-49b5-b3ae-c9dcf2a2440a" Title="Test Menu"/>
    <CreateMenuAction MenuId="92c65129-c29a-49b5-b3ae-c9dcf2a2440a" Id="3b183b13-21f6-4fa8-9294-c66fc0dba632" ActionId="647394-MyTest`Menu Test"/>
</MaxMenuTransformations>

On saving the menu definition, we get the menu added, and when the action item is selected, the resultant MacroScript item gets executed, logging "Hello World" in the MAXScript listener window, as shown below.

Comprehensive Menu Sample

Let's look at three ways we can create a menu with both action items and sub menu items as shown below.

  1. In Menu Definition (mnx): In the menu editor with developer mode activated, we select the Help item and right click to add a new sub menu before it. Once done, we search the action items and drag them to the sub menu to create the structure below. The menu can then be copied over or saved to a mnx file and packaged with the plugin.

  2. Using MAXScript: Like our porting sample earlier, we create a sub menu item in the notification callback. From the submenu, we use it to create action items and any additional sub menu items. A script is attached for reference.

  3. Using C++ SDK: Create a new plugin sample using the plugin wizard in VS 2022. In the DllEntry.cpp file in the LibIntialize function, register the function to create menus to the NOTIFY_CUI_REGISTER_MENUS. LibShutdown will also have the unregister of the same notification. Within the callback bound above, we get the main menu bar, create a sub menu from which we add new menu items. See the attached sample. Note that delayed loading of plugins should be disabled or else the menu won't be created since the notification is fired on boot and when menu definition file loads only.

  4. Using Python (pymxs): The approach is similar to MAXScript because pymxs is a thin wrapper on top of the MAXScript. In a callback to the cuiRegisterMenus, we add the menu items. See the attached sample as well.

Packaging Your Project

You are encouraged to utilize the PackageContents.xml method to ship the whole package. A good sample of this approach is CivilView and the Arnold renderer, both of which use a package file. These can be viewed in the %programdata%/ Autodesk/ApplicationPlugins/ folder. See the Packaging Plugins topic for more information.

The snippets below illustrate how you can add a .mnx/.qmnx file to your package file. Notice that 3ds Max 2025 adds the description "menu parts" for regular menus and "quadmenu parts" for quad menus.

<Components Description="menu parts"> 
    <RuntimeRequirements OS="Win64" Platform="3ds Max" SeriesMin="2025" SeriesMax="2025" /> 
    <ComponentEntry ModuleName= "./<relative path to .mnx>" /> 
</Components>

For Quad Menus:

<Components Description="quadmenu parts"> 
    <RuntimeRequirements OS="Win64" Platform="3ds Max" SeriesMin="2025" SeriesMax="2025" /> 
    <ComponentEntry ModuleName= "./<relative path to .qmnx>" /> 
</Components>

If we are using, MAXScript, we can use the "pre-startup scripts parts" and "post-startup scripts parts" attributes as shown below. Note that pre-startup scripts are loaded before the UI is ready, so should not be UI dependent like menus.

<Components Description="pre-start-up scripts parts">
    <RuntimeRequirements OS="Win64" Platform="3ds Max" SeriesMin="2025" SeriesMax="2025" />
    <ComponentEntry ModuleName="./<relative path to .ms>" />
</Components>

<Components Description="post-start-up scripts parts">
    <RuntimeRequirements OS="Win64" Platform="3ds Max" SeriesMin="2025" SeriesMax="2025" />
    <ComponentEntry ModuleName="./<relative path to .ms>" />
</Components>

And lastly, if we are making use of MacroScripts, we can tie everything together with the script below:

<Components Description="macroscripts parts"> 
    <RuntimeRequirements OS="Win64" Platform="3ds Max" SeriesMin="2025" SeriesMax="2025" /> 
    <ComponentEntry ModuleName= "./<relative path to .mcr>" /> 
</Components>

To run/test the scripts above, set the envar ADSK_APPLICATION_PLUGINS=<path to the directory above> and start 3ds Max 2025.

When To Use MNX/QMNX Vs Scripts

In general, the menu definition file approach is the simplest and most straight-forward way to create new custom menus. You might want to use code (C++/MAXScript/Pymxs) if you want to create dynamic menus, or if you are very comfortable with scripting.

Dynamic Menus

In 3ds Max, dynamic menus are those generated when the menu item is selected; this is different from the static menu which is created once on the #cuiMenu callback at startup. Dynamic menus are good for menu items that are created depending on some scene or other state in 3ds Max. Note however that dynamic menu items do not show up in the Menu Editor like the other static menus, although their parent container submenu is visible in the Menu Editor.

Similarly, unlike static menus that explicitly require GUIDs, static menus have a cosmetic ID that does not need to be GUID but an ID to uniquely identify the menu item for use when checking the menu item selected by the user.

There are samples in C++, Python and Maxscript that show how dynamic menus are created and used. See <install directory 3s-Max 2025-SDK>\maxsdk\howto\ui\menudemo in the 3ds Max SDK directory.

Note:

Currently, dynamic menus do not have tooltips and icons like static menus. This may change in future releases.

By default, dynamic menus create the menu items in a separate submenu under your menu. If this behavior is not desired, you can flatten the submenu created by dynamic menu by setting its isFlat flag to true as shown below.

  sub = newsubmenu.createaction("Your GUID", 647394, "ActionName`Category")
  sub.isFlat = True

The action table ID shown here (647394) is the system MacroScripts Action Table ID. See next section for more details.

Action Table IDs

Action tables and action table IDs have been in 3ds Max for a long time. Unlike previous Max versions, 2025 brings them to the forefront when dealing with menus. There are several Action Table IDs in 3ds Max 2025, and they can be queried in MAXScript using the script detailed at the end of the documentation for the actionMan interface

When working with MacroScripts in 3ds Max, especially in relation to the menu system, you need to use the system MacroScript Action Table ID. Note that the Action Table ID for all MacroScripts, both user-created and system ones is 647394. To create Action IDs for MacroScript-based menus in 2025, we need the MacroScript Action Table ID and the MacroScript name and category, concatenated as shown below:

<action table ID>-<Action Name>`<Action Category>

i.e. "647394-MyActionName`Some Category"

Note: The backtick (`) between the MacroScript name and its category is not a mistake but rather a requirement. Note: For other types of action items, such as C++ based system actions, use their specific Action Table ID.

Development Hacks : Reloading Menus in 3ds Max

When developing callbacks, you can test them by forcing the system to re-load and re-build the entire menu tree. This is for testing only, in production it would have a negative effect on performance.

While not optimal, you could add to the end of your script:

(
local iCuiMenuMgr = maxops.GetICuiMenuMgr()
iCuiMenuMgr.LoadConfiguration (iCuiMenuMgr.GetCurrentConfiguration())
)

That causes the menu manager to reload the current configuration, causing the menu file chain to be loaded and the callback to occur. If you are also adding quad menus, similar code would be added getting the quad menu manager

(
local iCuiQuadMenuMgr = maxOps.GetICuiQuadMenuMgr()
iCuiQuadMenuMgr.LoadConfiguration (iCuiQuadMenuMgr.GetCurrentConfiguration()
)
Warning: The performance will degrade as this reloads and rebuilds all menus.

Was this information helpful?