Using the MAXScript Debugger

The advantage of the MAXScript Debugger over using print/format is that when you hit the break, you can look at local variables, variables up the call tree (in each stack frame), and global variables. If you can get to a scope using one of those, you can access variables in those scopes.

For example, you break in a scripted plug-in event handler. Normally, you would not see the scripted plug-in's local variables or parameters. Using the Debugger you cannot only see these, you can also change their value before continuing execution.

How many times have you gotten some sort of MAXScript error, and you cannot determine what is the problem by looking at the values printed in the error trace-back, but if you do some more digging to see other variables you can determine it (thus the adding of print/format statements in the hope that they give you enough info).

3ds Max is multithreaded and when you hit a breakpoint, there can be multiple scripts running in different threads. The usual case of this is when you hit a scripted plug-in or controller while rendering. If you hit a breakpoint or exception in this case, the Debugger will default to the thread that threw it. But, if you broke by hitting the break button, then it defaults to the main thread. You might want to see what is running in a different thread, and can do so.

As you run a script and call functions, each function call creates its own stack frame. The stack frame simply contains the local variables associated with that function call. In each stack frame, the following variables are shown:

FOR EXAMPLE,

if you run the following code,

   v = 0
   r = 0
   (
   local pi = 3
   for i = 1 to 1000000 do
   (ss = random e pi; v += ss; v += r; if i == 999999 do throw "A")
   )

and then hit Break, you will get:

   #
   ** thread data: threadID:3364
   ** ------------------------------------------------------
   ** [stack level: 0]
   ** In i loop; filename: C:\Program Files\Autodesk\3ds Max 2010\ui\macroscripts\; position: 94; line: 6
   ** member of: anonymous codeblock
   --  Parameters:
   --   i: 260174
   --  Locals:
   --   ss: 2.79242
   --   i: 260174
   --  Externals:
   --   owner: <CodeBlock:anonymous>
   --   pi: 3
   --   r: Global:r : 0
   --   V: Global:V : 744008.0
   --  Owner:
   --   Locals:
   --    pi: 3
   --   Externals:
   ** ------------------------------------------------------
   ** [stack level: 1]
   ** called from anonymous codeblock; filename: C:\Program Files\Autodesk\3ds Max 2010\ui\macroscripts\; position: 126; line: 6
   --  Locals:
   --   pi: 3
   --  Externals:
   ** ------------------------------------------------------
   ** [stack level: 2]
   ** called from top-level

The first line just tells us which thread we are in. Most of the time this is not important. Then we start walking out through the function calls, dumping the stack frame for each call. In this case, there are three levels: the body of the for loop (which internally is handled as a function call), the code within the outer parentheses, and the Listener executing the for loop.

For level 0, first we have the function name (i loop) and then we say what owns the function. In this case, its the code block defined by the outer parenthesis. This code block is anonymous because it does not have a name. Other code blocks will.

FOR EXAMPLE,

If you drag the above code onto a toolbar to make a MacroScript, run the MacroScript and hit Break, the first couple of lines will look like:

   ** thread data: threadID:2224
   ** [stack level: 0]
   ** In i loop; filename:
   C:\3dsmax8\UI\MacroScripts\DragAndDrop-Macro1.mcr; position: 186
   ** member of: codeblock macroScript: DragAndDrop_Macro1

Next thing shown is the Parameters, which for a for loop is just the for loop counter. The values shown here are the values passed to the function. Next are the Locals. There are 2- ss and i. Here, if a new value was assigned to variable i (the for loop counter), that new value will be shown. Next, the Externals are shown. These are variables that are used in the function, but are not local variables. They can be either defined in an outer scope, global scope, or some other outside scope. The owner variable allows you to access the owner of the function. For example, if the function was in a scripted plug-in, the owner will be the scripted plug-in instance. Through this, you can get and set the owner's variables.

Finally, if there is an owner, the information for the owner is shown. In this case, it is not that important since the next stack level shows the same thing, but in general that is not true (functions in scripted plug-ins, MacroScripts, rollouts).

The next stack level dumps the info for the caller of the function, the anonymous code block.

Consider that you want to change the value of variable 'pi'. You can only change a variable if you can access the variable. In this case, you can access 'pi' from within the for loop function using: 'owner.pi'

However, if we had nested function calls, you will not be able to get to it that way. Instead, you will have to set the current stack level to the desired level and set the variable.

FOR EXAMPLE:

   >> setframe 1
   ** ok
   >>locals
   ** thread data: threadID:2224
   ** [stack level: 1]
   ** In anonymous codeblock
   -- Locals:
   -- pi: 3.1
   -- Externals:
   >> setvar pi 3.5
   3.5
   >> getvar pi
   3.5

Compare the above to the error traceback from throwing the exception:

   -- Error occurred in i loop
   --  Frame:
   --   ss: 2.99878
   --   i: 999999
   --   called in anonymous codeblock
   --  Frame:
   --   pi: 3
   -- Runtime error: A

There is also a Stop button in the MAXScript Debugger. After doing a Break, if you hit Run you will continue executing the code. If you hit Stop, the script will stop running.