Defining Macro Scripts

MacroScripts are scripts that are associated with Customize User Interface ActionItems (toolbar buttons, Menu or QuadMenu items, and keyboard shortcuts) and are executed when the corresponding ActionItem is invoked.

MacroScripts have to be defined with the macroScript definition construct and can then be associated with a toolbar button by right-clicking a Shortcut toolbar or Tab and choosing Customize. The Customize User Interface dialog is displayed, which lets you choose from either Command shortcuts or MacroScripts. MacroScripts are essentially pieces of MAXScript code that have a name and category, and optionally a tooltip and icon.

MacroScripts do not automatically create user interface items. If you want a MacroScript to display a dialog, you need to create a rollout floater window and rollout(s) as described in Rollout Floater Windows, and create and install the user-interface items in the rollout(s) as described in Rollout User-Interface Controls.

A MacroScript is defined using the following syntax:

macroScript <name> 
[category:<string>] 
[buttonText:<string>] 
[toolTip:<string>] 
[icon:#(<string>, <index>) | iconName:<string>] 
[silentErrors:<boolean>] 
[autoUndoEnabled:boolean>] 
(<macro_script_body>)

EXAMPLES:

   macroScript Free_Camera category:"Cameras" tooltip:"Free Camera"
   Icon:#("Cameras",2)
   (
     StartObjectCreation FreeCamera
   )
   macroScript Target_Camera category:"Cameras"
   tooltip:"Targeted Camera" Icon:#("Cameras",1)
   (
     StartObjectCreation TargetCamera
   )

Since 3ds Max 2011, the strings defining the name properties of the MacroScript definition can be replaced with localized Resources strings whose definitions are enclosed in tildes. Please see the Resources String Replacement topic for details.

After MAXScript evaluates a macroScript construct, the MacroScript definition shows up in the appropriate category list in the Customize User-Interface dialog. The following figure shows a Customize User Interface dialog containing the previous two MacroScripts.

If a tooltip is specified, that tooltip is the name shown in the Customize User Interface dialog. If a tooltip is not specified, <name> is the name shown. Unlike other similar constructs (Scripted Utilities, Functions, and right-click menus), macroScript does not create a variable with this name. Rather, MacroScripts are stored as pointers into files, as described below.

Note:

You can use non-alpha numeric characters to define any of the argument strings. This includes support for escaped double quote characters: \".

The category: argument specifies in which category of the Customize User-Interface dialog the MacroScript name is listed. The use of categories is intended to help you organize your MacroScripts into groupings so that the MacroScript names are less likely to clash. If you do not specify a category, a default category of "unknown" is used.

The internalCategory argument is intended to identify operations in .cui, .mnu, and .kbd files.

The toolTip: argument specifies the tooltip for the button. If no tooltip is specified, <name> is displayed for the button.

The buttonText: argument specifies the text that appears in the button, and the icon: argument specifies the icon that appears in the button. You can choose in the Customize User Interface dialog whether the buttonText or icon appears in the button. If no buttonText: argument is specified, the MacroScript name is used as the buttonText .

The silentErrors: parameter controls whether the MAXScript runtime error messages are displayed while executing the MacroScript. If this parameter is set to True , error messages are not displayed. This is useful for distributed MacroScripts that might confuse the user with MAXScript error messages. The default value is False.

The autoUndoEnabled: parameters controls whether the MacroScript body is automatically wrapped in a theHold begin/accept calls internally.

When set to False , the execution of the MacroScript body does not generate an undo record for the whole MacroScript.

When set to True or not specified, the whole MacroScript body creates an undo record when executed.

The default value is true .

Available in 3ds Max 2010 and higher.

EXAMPLES:

   MacroScript MacroScript_autoUndoEnabled_test1
   category:"MacroScript_autoUndoEnabled_test"
   buttontext:"test1"
   autoUndoEnabled:false
   (
     format "test1\n"
     format "expecting theHold.holding == false, got: %\n" (theHold.holding())
     format "expecting theHold.IsSuspended == false, got: %\n" (theHold.IsSuspended())
     format "expecting theHold.Restoring == false, got: %\n" (theHold.Restoring())
     format "expecting theHold.Redoing == false, got: %\n" (theHold.Redoing())
     format "expecting theHold.RestoreOrRedoing == false, got: %\n" (theHold.RestoreOrRedoing())
     format "expecting theHold.SuperLevel == 0, got: %\n" (theHold.SuperLevel())
   )

Icons

In 3ds Max 2017 a new method of loading icons was introduced to support high-DPI display scaling. The iconName: argument uses this method. This argument can specify an icon in one of these locations:

  1. Baseline 3ds Max icons located in the compiled resource files IconsDark.rcc and IconsLight.rcc (for the dark and light themes, respectively), located under <3ds Max>\UI_ln. See the Icon Reference Guide in the 3ds Max Developer's Guide for full paths to these icons. All icons are loaded by 3ds Max on startup, and are referred to by <internal_path>/<base_name>, where internal_path is the path to the icon inside the rcc file (see the Icon Reference) and the base_name is the icon name minus the _xx.png suffix, where _xx is the resolution of the icon. Typically 4 resolutions are supplied, and the system picks the best one for the current display resolution.
  2. Custom icons compiled into .rcc resource files placed in <3ds Max>\UI_ln\Icons. Any .rcc file found in this location is loaded by 3ds Max on startup. These files must be compiled with the Qt resource compiler; see The Qt Resource System. You should include both light and dark versions to support both themes, and four resolutions to support high-DPI displays. As with baseline icons, these are referred to by <internal_path>/<base_name>. For more information on creating your own .rcc files, see the Using Multi-Res Icons in Max and Qt topic in the Developer's Guide.
  3. PNG files from disk located in the <3ds Max>\UI_ln\Icons\[Dark|Light]\ - depending on the active theme (you need a copy of the icon in each location). For example, an icon with four resolutions (my_icon_24.png, my_icon_30.png, my_icon_36.png, my_icon_48.png) placed in both <3ds Max>\UI_ln\Icons\Dark\MyIcons\ and <3ds Max>\UI_ln\Icons\Light\MyIcons\ would be referred to in the macroScript as iconName: "MyIcons/my_icon". Note: <3ds Max>\UI_ln\Icons\[Dark|Light]\ do not exist by default, you will have to create these directories.
  4. The location pointed to by #userIcons, and the current theme name under #userIcons, a directory named "Light" or "Dark". See 3ds Max System Directories for information about #userIcons.

Prior to 3ds Max 2017, the icon: argument is used.

Note: this is now considered a "legacy" method of specifying icons, and is supported for backwards compatibility. As of 3ds Max 2017, this method no longer supports .bmp files, icons must be .png format.

The icon: argument specifies the icon bitmap file and the icon image within the icon bitmap file. The icon bitmap file must be located in the current 3ds Max user-interface directory. Icon bitmap files have a base name such as, "MyToolbar", followed by a suffix such as, "_24i.bmp" that specifies the individual icon size and icon bitmap file type. The icon: argument string is just the base name with no extensions. This base name is the name shown in the Image Group list in the Customize User-Interface dialog. Each icon bitmap file can have any number of individual icons, lined up side-by-side in the file. If the icon bitmap file contains multiple icons, <index> specifies which icon in the icon bitmap file to use. The left-most icon has an <index> of 1. The 3ds Max internal icons (Image Group Internal in the Customize User-Interface dialog) are not stored in an icon file, and are referenced by using an empty string as the icon: argument.

So, the icon: argument is a two-element array containing the icon bitmap file’s base name as a string and the icon’s index in that file.

EXAMPLES:

   macroScript Box category:"Objects" tooltip:"Box"
   icon:#("standard", 1) -- use first icon in standard
   (
     StartObjectCreation Box
   )
   macroScript Sphere category:"Objects" tooltip:"Sphere"
   icon:#("", 2) -- use second icon in internal icons
   (
     StartObjectCreation Sphere
   )

See Creating Icon Bitmap Files for more information.

The <macro_script_body> can be one of two forms.

The body can be either a single MAXScript expression, or a set of event handlers.

An <event_handler> is a special function definition local to the MacroScript that handles events generated by 3ds Max.

The valid <event_name> are:

on isChecked do <expr>   

If <expr> returns True and the MacroScript item is in a menu or quad menu, a check mark is placed next to the MacroScript item. If the MacroScript is a toolbar button, the button appears as "pressed in". The do is optional for this event handler. If this event handler is not specified, the item is not checked.

on isIndeterminate do <expr>   

If <expr> returns True and the MacroScript item is in a menu or Quad menu, a horizontal dash symbol is placed next to the MacroScript item. If the MacroScript is a toolbar button, this handler has no effect. This handler is independent from the on isChecked do handler and sets the Indeterminate symbol even if isChecked returns False.

Available in 3ds Max 2015 and higher.

on isEnabled do <expr> 

If <expr> returns False and the macroscript item is in a menu or quad menu, the MacroScript item does not appear in the menu or quad menu. If the MacroScript is a toolbar button, the button is disabled. The do is optional for this event handler. If this event handler is not specified, the item is enabled.

on isVisible do <expr> 

If <expr> returns False and the macroscript item is in a menu or quad menu, the MacroScript item does not appear in the menu or quad menu. If the MacroScript is a toolbar button, this handler has no affect. The do is optional for this event handler. If this event handler is not specified, the item is visible.

on execute do <expr> 

The expression evaluated when the menu or quad menu item is chosen, or the toolbar button clicked. A runtime error is generated if this event handler is not specified.

SCRIPT:

   macroScript Free_Camera
   category:"Lights and Cameras"
   internalcategory:"Lights and Cameras"
   tooltip:"Free Camera"
   buttontext:"Free Camera"
   Icon:#("Cameras",2)
   (
     on execute do StartObjectCreation FreeCamera
     on isChecked return (mcrUtils.IsCreating FreeCamera)
   )

SCRIPT:

   macroScript SubObject_Vertex
   buttonText:"Vertex"
   category:"Modifier Stack"
   internalCategory:"Modifier Stack"
   tooltip:"Vertex Sub-object Mode"
   icon:#("SubObjectIcons",1)
   (
     on isChecked do (subObjectLevel == 1 and filters.canSwitchTo_Vertex())
     on isEnabled do Filters.canSwitchTo_Vertex()
     on isVisible do Filters.canSwitchTo_Vertex()
     on execute do
     (
       if subObjectLevel == undefined then max modify mode
       if subObjectLevel != 1 then subObjectLevel = 1 else subObjectLevel = 0
     )
   )
on altExecute <type> do <expr> 

If a MacroScript implements the altExecute event handler, then a mouse icon (with a dark click button) appears in the quad menu item that corresponds to the MacroScript. When the icon is clicked, the event handler is executed with <type> being #default .

For example, the following adds alternative execute functionality to the standard "Wire Parameters" quad menu. When you click the icon, it pops the Param Wiring Editor instead of putting into the Wiring mode.

SCRIPT:

   macroScript paramWire
   category:"Parameter Wire"
   internalcategory:"Parameter Wire"
   buttonText:"Wire Parameters"
   tooltip:"Start Parameter Wiring"
   Icon:#("MAXScript" ,1)
   (
     on isEnabled return selection.count == 1
     on execute do(paramWire.start())
     on altExecute type do
     (
       paramWire.OpenEditor()
     )
   )
on closeDialogs do <expr> 

The closeDialogs handler is called instead of the on Execute handler whenever the isChecked handler returns True (the button/icon/item is checked). It can be used to TOGGLE back to the unchecked state. This handler must implement any cleanup code that closes any open dialogs and basically returns the MacroScript to a pre-executed state.

Note: The closeDialogs handler depends on the existence of the isChecked handler. If an on closeDialogs handler is defined without an isChecked handler to be defined, a compile-time error is thrown.

Here is a test for a MacroScript that toggles its rollout on and off:

SCRIPT:

   macroScript testCloseDialogs category:"MXS Help"
   (
   rollout testCloseRollout "Test" --define a rollout
   (
   label lb_test "testing CloseDialogs handler..."
   )
   on isChecked do testCloseRollout.open --return true if rollout is open --if isChecked returns false (the rollout is not open in a dialog), --the on execute handler will be called and will create the dialog.

   on execute do createDialog testCloseRollout

     --If isChecked returns true and the user pressed the button again, --instead of calling the on execute handler, the macroScript will call --the on closeDialogs handler and destroy the dialog.

   on closeDialogs do destroyDialog testCloseRollout
   )

     --If you drag the macroScript to a toolbar and click the button, --a dialog should appear and the button should be checked. --Click it again and the dialog will disappear. --Repeat as often as you want - the dialog will be toggled on and off! 
on droppable <window> node:point:do... 
on drop <window> node:point:do... 

These optional MacroScript event handlers are used to define the so-called DropScripts that are used in conjunction with the i-Drop technology.

The <macro_script_body> can contain global and local variables, functions, and structure definitions. The <macro_script_body> is compiled in its own local scope, and the locals are only visible inside the scope of the MacroScript. MacroScript locals are heap-based locals, which are slightly different from normal (stack-based) locals.

Normal locals are visible in the current scope and have a lifetime of one execution of that scope. Heap-based locals are also visible only in the current scope, but have a lifetime equal to the lifetime of the top-level expression where they are defined. A MacroScript’s locals are created the first time you execute the MacroScript, initialized to a value of undefined , or to their specified initialization value. These values are stored in a separate memory stack. On entry to each function (or top level script) in the MacroScript, a ’r;frame’ in the memory stack is marked and when the function (or top level script) exits, all of the values in the frame are freed from the memory.

Because a MacroScript’s name is not created as a variable, you cannot access a MacroScript’s locals outside the scope of the MacroScript. So, for example, you can create a rollout in a MacroScript, and the rollout’s event handlers can access the locals defined in the MacroScript because the rollout is executing within the scope of the MacroScript. However, you cannot access the MacroScript’s locals from another utility or from the Listener because they are not executing within the scope of the MacroScript. See Scope of Variables for more information.

When you execute a macroScript definition, the return value is an integer MacroScript ID value. MAXScript internally stores information about each MacroScript in an array, and the returned MacroScript ID value is the array index for that MacroScript. The information stored for each MacroScript consists of the file in which that MacroScript is defined and a pointer into that file specifying where the MacroScript definition begins. The MacroScript definition is only compiled when you first press a toolbar button that contains the script, or execute the MacroScript using the macros.run() method.

There are five ways a MacroScript can be defined:

If you move or delete a file that contains a MacroScript definition that has been loaded, and try to execute the MacroScript, an error message appears. Further, if you edit a file containing MacroScript definitions, make sure to save and re-evaluate the entire file so that any other MacroScripts defined in the file have their file pointer updated. If you do not do this, you might get an error message indicating that the currently loaded definition no longer matches its file.

If you reevaluate a MacroScript definition, any button using that MacroScript can see any changes that you make.

Any macroScript definition evaluated in MAXScript or created by dropping text onto a toolbar has a separate definition .mcr file created in the MacroScripts directory under the current user-interface directory (typically UI\MacroScripts). The name of the file is <category_name>-<macro_name>.mcr.

FOR EXAMPLE:
DragAndDrop-Macro12.mcr for macroScript Macro12 category:"DragAndDrop"
NURBS-Map_Updater.mcr for macroScript Map_Updater category:"NURBS"

If you evaluate a macroScript definition in the Listener or drop text on a toolbar, its recorded definition file is this backing file in UI\MacroScripts. This definition file is the one that gets opened if you hit Edit MacroScript in the CUI customize menus or dialogs. For macroScript definitions evaluated in Listener, this means the same definition is updated each time you evaluate it, rather than having separate backing file for each evaluation.

If you evaluate macroScript definitions in a .ms or .mcr file that does not already reside in the UI\MacroScripts directory, a copy for each is placed in a separate file in UI\MacroScripts, but the recorded definition file is the original source file, so that hitting Edit MacroScript goes to that file.

This means that if any buttons containing such macroScript definitions are added to toolbars in the startup.cui file, the backing .mcr file in UI\MacroScripts is used as its definition at the next 3ds Max startup. When you start 3ds Max, the macroScript definitions are taken from the backing files in UI\MacroScripts. If these MacroScripts are also defined in MAXScript startup script files, they are re-defined at MAXScript startup and so the definition file of record is updated to point to the script file.

MacroScripts Access:

MAXScript provides several methods that allow you to access and run MacroScripts from within a script. These MacroScript functions are in a structure package named macros . For a description of the available methods, see the Macro Scripts topic.

The following MacroScript allows you to render directly to the RAMPlayer. This MacroScript shows the use of rollouts and rollout floater windows in MacroScripts.

SCRIPT

   -- MacroScript to Render to RamPlayer
   -- Author: Alexander Bicalho
   --**\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*******
   -- MODIFY THIS AT YOUR OWN RISK
   -- This utility will allow you to render directly to the RamPlayer

   MacroScript RAM_Render category:"Tools" tooltip:"Render to Ram"
   (
   --declare local variables and define some functions
   local r_dialogue
   local get_names existFile
   function get_names name a = append a name
   function existFile fname = (getfiles fname).count != 0
   --create the rollout definition
   rollout r_size"Render Parameters"
   (
   local p = 95
   local p2 = p+78
   group "Time Output"
   (
   radiobuttons r_time columns:1 align:#left labels:#("Single","Active Time Segment","Range:" )
   spinner nth "Every Nth Frame:" pos:[215,24] fieldwidth:50 type:#integer range:[0,10000,1] enabled:false
   spinner r_from fieldwidth:60 pos:[75,56] type:#integer range:[0,10000,1] enabled:false
   spinner r_to "To:" fieldwidth:60 pos:[152,56] type:#integer range:[0,10000,100] enabled:false
   )
   group "Render Size"
   (
   spinner rw "Width " fieldwidth:60 pos:[15,p+08] type:#integer range:[0,10000,320]
   spinner rh "Height " fieldwidth:60 pos:[12,p+32] type:#integer range:[0,10000,240]
   spinner aspect "Aspect " fieldwidth:60 pos:[10,p+56] type:#float range:[0,20,1.0]
   button s160 "160x120" pos:[125,p+06] width:75 height:19
   button s256 "256x243" pos:[125,p+30] width:75 height:19
   button s320 "320x240" pos:[205,p+06] width:75 height:19
   button s512 "512x486" pos:[205,p+30] width:75 height:19
   button s640 "640x480" pos:[285,p+06] width:75 height:19
   button s720 "720x486" pos:[285,p+30] width:75 height:19
   button conf_render "Configure" pos:[125,p+54] width:115 height:19
   button wipe "Purge Files" pos:[245,p+54] width:115 height:19
   button go "Render" pos:[125,p+78] width:235 height:19
   )
   label abt0 "Render to RAM" pos:[8,p2+31]
   label abt1 "Version 0.2a" pos:[8,p2+46]
   label abt2 "Alexander Esppeschit Bicalho" pos:[225,p2+31]
   label abt3 "abicalho@brasilmail.com" pos:[249,p2+46]
   --define the rollout event handlers
   on wipe pressed do
   (
   local tempfilename_a = (getdir #image) + "\\ramplayertemp_a.avi"
   local tempfilename_b = (getdir #image) + "\\ramplayertemp_b.avi"
   if existfile tempfilename_a then deletefile tempfilename_a
   if existfile tempfilename_b then deletefile tempfilename_b
   )
   on r_time changed state do
   (
   case state of
   (
   1: nth.enabled = r_from.enabled = r_to.enabled = false
   2: (
   nth.enabled = true
   r_from.enabled = r_to.enabled = false
   )
   3: nth.enabled = r_from.enabled = r_to.enabled = true
   )
   )
   on s160 pressed do (rw.value=160; rh.value=120; aspect.value=1.0)
   on s320 pressed do (rw.value=320; rh.value=240; aspect.value=1.0)
   on s256 pressed do (rw.value=256; rh.value=243; aspect.value=1.266)
   on s512 pressed do (rw.value=512; rh.value=486; aspect.value=1.266)
   on s640 pressed do (rw.value=640; rh.value=480; aspect.value=1.0)
   on s720 pressed do (rw.value=720; rh.value=486; aspect.value=0.9)
   on conf_render pressed do (max render scene)
   on go pressed do
   (
   local tempfilename_a = (getdir #image) + "\\ramplayertemp_a.avi"
   local tempfilename_b = (getdir #image) + "\\ramplayertemp_b.avi"
   if existfile tempfilename_b then
   (
   deletefile tempfilename_a
   copyfile tempfilename_b tempfilename_a
   tempfilename = tempfilename_b
   )
   else
   (
   if existfile tempfilename_a then
   tempfilename = tempfilename_b
   else
   (
   tempfilename = tempfilename_a
   tempfilename_b = ""
   )
   )
   case r_time.state of
   (
   1: ( render outputheight:rh.value outputwidth:rw.value pixelaspect:aspect.value outputfile:tempfilename vfb:off)
   2: ( render outputheight:rh.value outputwidth:rw.value pixelaspect:aspect.value outputfile:tempfilename vfb:off nthframe:nth.value framerange:#active )
   3: ( render outputheight:rh.value outputwidth:rw.value pixelaspect:aspect.value outputfile:tempfilename vfb:off nthframe:nth.value fromframe:r_from.value toframe:r_to.value )
   )
   ramplayer tempfilename_a tempfilename_b
   closerolloutfloater r_dialogue
   )--end of on go pressed
   )--end of rollout r_size
   --close the old rollout floater if it exists
   try(closerolloutfloater r_dialogue)catch()
   --put up new rollout floater and add rollout to it.
   r_dialogue = newrolloutfloater "Render to RAM" 400 300
   addrollout r_size r_dialogue
   --end of MacroScript, rollout takes over...
   )