Defining Macro Scripts

Macro Scripts are scripts that are associated with toolbar buttons and are executed when the corresponding toolbar button is clicked. Macro Scripts 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 Macro Scripts. Macro Scripts are essentially pieces of MAXScript code that have a name and category, and optionally a tooltip and icon.

Macro Scripts do not automatically create user-interface items. If you want a Macro Script to display a dialog, you will 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.

Define a Macro Script using the following syntax:

macroScript <name> [ category:<string> ]

[ buttonText:<string> ]

                   [ toolTip:<string> ]

                   [ icon:#(<string>, <index>) | icon:<string> ]

                   [ silentErrors:<boolean> ]

( <macro_script_body> )

For example:

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

)

After MAXScript evaluates a macroScript construct, the Macro Script definition will show 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 Macro Scripts.

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, Macro Scripts 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 in the Customize User-Interface dialog the Macro Script name will be listed. The use of categories is intended to help you organize your Macro Scripts into groupings so that the Macro Script 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 will appear in the button, and the icon: argument specifies the icon that will appear 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 Macro Script name is used as the buttonText.

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 present. 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 can be either a two-element array containing the icon bitmap file's base name as a string and the icon's index in that file, or just a base name string, with the index 1 assumed. For example:

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

)

macroScript Cone category:"Objects" tooltip:"Cone"

            icon:"myicon"  -- use first icon in myicon

(

StartObjectCreation Cone

)

See Creating Icon Bitmap Files for more information.

The silentErrors: parameter gives control over whether MAXScript runtime error messages are displayed while executing the Macro Script. If this parameter is set to true, error messages will not be displayed. This may be useful for distributed Macro Scripts that may confuse the user with MAXScript error messages. The default value is false.

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 4. The syntax for defining an event handler is as follows:

on <event_name> do <expr>

The valid <event_name> are:

isChecked - 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 will appear as "pressed in". The do is optional for this event handler. If this event handler is not specified, the item will not be checked.

isEnabled - if <expr> returns false and the macroscript item is in a menu or quad menu, the macroscript item will not appear in the menu or quad menu. If the macroscript is a toolbar button, the button will be disabled. The do is optional for this event handler. If this event handler is not specified, the item will be enabled.

isVisible - if <expr> returns false and the macroscript item is in a menu or quad menu, the macroscript item will 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 will be visible.

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

Examples:

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)

)

MacroScript SubObject_Vertex

ButtonText:"Vertex"
Category:"Modifier Stack"
internalCategory:"Modifier Stack"
Tooltip:"Vertex Sub-object Mode"
Icon:#("SubObjectIcons",1)

(


On IsChecked (SubObjectLevel == 1 and filters.CanSwitchTo_Vertex())
On IsEnabled Filters.CanSwitchTo_Vertex()
On IsVisible Filters.CanSwitchTo_Vertex()
On Execute do
(

If SubObjectLevel == undefined then Max Modify Mode
If SubObjectLevel != 1 then SubObjectLevel = 1 Else SubObjectLevel = 0

)

)

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 Macro Script. Macro Script 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 Macro Script's locals are created the first time you execute the Macro Script, 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 Macro Script, a '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 Macro Script's name is not created as a variable, you cannot access a Macro Script's locals outside the scope of the Macro Script. So, for example, you can create a rollout in a Macro Script, and the rollout's event handlers can access the locals defined in the Macro Script because the rollout is executing within the scope of the Macro Script. However, you cannot access the Macro Script's locals from another utility or from the Listener, because they are not executing within the scope of the Macro Script. See Scope of Variables for more information.

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

There are five ways a Macro Script can be defined:

If you move or delete a file that contains a Macro Script definition that has been loaded, and try to execute the Macro Script, you will get an error message. Further, if you edit a file containing Macro Script definitions, take care to save and re-evaluate the entire file so any other Macro Scripts defined in that file will have their file pointer updated. If you don't do this, you may get an error message saying the currently loaded definition no longer matches its file.

If you reevaluate a Macro Script definition, any button using that Macro Script will see any changes 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 Macro Script in the CUI customize menus or dialogs. For macroScript definitions evaluated in Listener, this means the same definition will be 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 will be placed in a separate file in UI\MacroScripts, but the recorded definition file will be the original source file, so that hitting Edit Macro Script will go 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 will be used as its definition at the next 3ds max startup. When you start 3ds max, the macroScript definitions will taken from the backing files in UI\MacroScripts. If these Macro Scripts are also defined in MAXScript startup script files, they will be re-defined at MAXScript startup and so the definition file of record will be updated to point to the script file.

MAXScript provides several methods that allow you to access and run Macro Scripts from within a script. These Macro Script functions are in a structure package named macros. The Macro Script methods are:

macros.load [ <path_name_string> ]

Loads all of the Macro Script definition (.mcr) files in the current user-interface directory path, or in directory path specified by <path_name_string>. This method does not change the current user-interface directory path. You can get the current user-interface directory path with the function:

local ui_dir = cui.getDir ()

macros.new <name_string> <category_string> <toolTip_string> \

            <buttonText_string> <body_string>

Creates a new Macro Script with the specified name and category. A file will automatically be created to contain the definition. This file is stored in the current user-interface directory. Returns an integer Macro Script ID value that uniquely identifies the new Macro Script.

macros.run <category_string> <name_string>

macros.run <macro_id_integer>

Executes the specified Macro Script. The Macro Script is identified by either its category and name, or by its unique Macro Script ID value.

macros.edit <category_string> <name_string>

macros.edit <macro_id_integer>

These methods will open the Macro Script definition (.mcr) file that defines the specified Macro Script in a script Editor window. The Macro Script is identified by either its category and name, or by its unique Macro Script ID value.

For example:

macros.load "f:/gametools/macros"

macros.edit "objects" "box"

macros.run 132

macros.run "modifiers" "bend"

The following Macro Script will save the active viewport's image to a bitmap file.

Example:

MacroScript GrabViewport category:"Tools" tooltip:"Grab Viewport"

(

---------------------------------------------------------------------

--GRABVIEWPORT MACROSCRIPT

--Created:3/23/99

--Edited:4/28/99

--by Borislav Petrov

--bobo@email.archlab.tuwien.ac.at

---------------------------------------------------------------------

--Snapshot Active Viewport to Bitmap

--Filename in format:

--VPGRAB_MaxFileName_ViewportName_CurentFrame.ImageFormatExtension

---------------------------------------------------------------------

--

--Init Variables

local grab_bmp          --bitmap to put the snapshot into

local bmp_name          --name of the bitmap to save

local get_viewport_name --viewport name

local gac,gct,mfn       --variables to hold ActiveCamera, CurrentTime, MaxFileName

--

--Start Macro

grab_bmp = gw.getViewportDib()         --get the viewport bitmap into variable

get_viewport_name = viewport.GetType() --get viewport name

gvn = get_viewport_name as string      --convert viewport name to string

gvn = substring gvn 6 (gvn.count-5)    --cut the string

if gvn == "camera" then      --if the remaining string is "camera"

(

gac = getActiveCamera() --get the camera

gvn = gac.name --get the name of the camera

)

mfn = MaxFileName            --get max file name

if mfn == "" then            --if there is no name

mfn = "Untitled" --use "Untitled"

else

mfn = getFileNameFile mfn --use the file name without .MAX extension

gct = SliderTime as string   --get current frame time

bmp_name = "VPGRAB_"+ mfn +"_" +gvn + "_" + gct --build the output file name

--Display file save dialog

bmp_name = getSaveFileName caption:"Save Viewport to:" filename:bmp_name \

types:"BMP(*.bmp)|*.bmp|TIFF(*.tif)|*.tif|JPG(*.jpg)|*.jpg|TGA(*.tga)|*.tga|"

if bmp_name != undefined then   --if user has confirmed / entered a valid name

(

grab_bmp.filename = bmp_name --set output name to the one entered in the save file dialog

save grab_bmp --save the bitmap

display grab_bmp --display the bitmap in a VFB

)

)--end of script

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

Example:

-- 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 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 Macro Script, rollout takes over...

)