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. And if you can get to a scope via one of those, you can access variables in those scopes.

So, 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 can not 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 couldn't tell what exactly the problem was just by looking at the values printed in the error trace-back, but if you could do some more digging to see other variables you could (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. But you may want to see what's 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:

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 would get:

** thread data: threadID:2224

** [stack level: 0]

** In i loop

** member of: anonymous codeblock

-- Parameters:

-- i: 184349

-- Locals:

-- ss: 2.78488

-- i: 184349

-- Externals:

-- owner: <CodeBlock:anonymous>

-- r: Global:r : 0

-- pi: 3

-- V: Global:V : 527028.0

-- Owner:

-- Locals:

-- pi: 3

-- Externals:

** [stack level: 1]

** called from anonymous codeblock

-- Locals:

-- pi: 3

-- Externals:

** [stack level: 2]

** called from top-level

 

 

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. In this case there are 3 levels - the body of the FOR loop (which internally is handled as a function call), the code within the outer parenthesis, 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 doesn't 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 would 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 would 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. If, for example, the function was in a scripted plug-in, the owner would 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.

Let's say you wanted 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 via:
'owner.pi'

but if we had nested function calls, you wouldn't be able to get to it that way. Instead, you would 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.

 

Manual Break Points using the Break() function

You can add manual break points to your code by using the break() function to invoke the Debugger.

For example:

for i = 1 to 10000 do

(

theRandom = random 1 100

if theRandom == 50 do break()

)

In this simple example, the Debugger will be called if the random value generated within the loop generated the integer 50.

 

Note:

Some parts of the code are "locked" and cannot be stopped from executing.

One of the "locks" that prevents breaking into the debugger is one associated with arrays. When you are iterating across an array using the FOR statement, the lock is set.

For example:

for i in #(1,2) do break()

The result will be *** Break attempt timed out *** printed to the Debugger Output without a successful break.

 

Debugger variables for Functions

Each function has a special .owner variable that points to the owner of the function, if any. An owner can be a rollout, scripted plugin, rcmenu, mouse tool, macroscript, or anonymous code block. If a function is defined in the global scope, it does not have an owner. If it does have an owner, and the owner is not an anonymous code block, another debugger variable with the same name as the owner is created. Both of these variables contain the owner value. These variables are not true local variables, and are not accessible in the function. If the function declares a variable with the name 'owner', the debugger 'owner' variable will not be created.

See Also:

The MAXScript Debugger

Interface: MXSDebugger