Scope of Variables

Variables have an attribute called scope that determines where a variable can be accessed in MAXScript code.

MAXScript has two kinds of variable scope, global and local.

Global variables are visible in all running MAXScript code and hold their values until you exit 3ds Max.

Local variables are directly visible to code only in the lexical scope currently in effect, and hold their values only as long as the current scope is active. After the scope is no longer active, you can no longer access the contents of a variable local to that scope. This is similar to how most modern programming languages implement variables. The conditions under which MAXScript creates a new scope are described further on this page.

Explicit Variable Declaration

MAXScript provides language constructs used to explicitly declare and initialize both global and local variables.

The syntax for the variable declaration ( <variable_decls> ) is as follows:

( local | global ) <decl> { , <decl> }

where <decl> is defined as:

<var_name> [ = <expr> ] -- name and optional initial value

EXAMPLES:

global baz -- initialized to 'undefined'
global foo=10, initialized=false -- initialized globals
local x = 1, -- continued on several lines
y = 2,
z = sqrt(123.7)

As mentioned above, local variables hold their values as long as the scope is active. In nested scopes, this is usually a very short period. It starts when a function or loop or block expression runs and ends when it exits.

Each time the scope is entered at runtime, a new set of locals is created and then retired when the scope exits.

It is a good practice to always declare variables explicitly. See further on this page for reasons.

Explicit Global Variable Access

Since 3ds Max 8, MAXScript provides a way to explicitly access a variable name as global. If the variable exists in the global pool of variables, it will be used. If the variable name does not exist in the global scope, it will be created as explicitly global.

The syntax is double-colon '::' and is described in this topic.

Local Variables as 'Private Global' variables

In certain situations, the top-level scope can remain active for extended periods, so the locals declared at the top-level hold their values for that extended period.

In particular, top-level locals declared in scripted rollouts, utilities, plug-ins, script controllers, and Macro Scripts hold their values for as long as the rollout, utility, plug-in, or Macro Script exists.

Typically, they remain in existence until you redefine them, for example, when you define a Macro Script and run it for the first time, the top-level scope is created and remains active over any number of subsequent executions unless you redefine the Macro Script. At this point, the existing top-level scope is retired and a fresh one (along with its top-level locals) is created.

This lets you use top-level locals in these constructs as a kind of private global; the values they hold remain in place over many executions, but only the code in that construct can see the variable, so it does not conflict with actual global variables.

Note that in scripted plug-ins, a separate copy of the top-level local scope is created for each instance of the plugin and this scope remains active while that instance remains in existence up until the end of the current 3ds Max session.

*HISTORY Note: *

In 3ds Max R2.x, the optional initialization value was applied to global variables only when the declaration creates a new variable. For example, if you ran the following script twice, the value of x in the second line will be 10 for the first execution, and 20 for the second execution. In 3ds Max 3 and later, the optional initialization value is always applied to global variables.

global x = 10
print x
x=20

If you want to make the global variable initialization conditional in 3ds Max 3 and higher, use a construct similar to:

global foo; if foo = = undefined do foo = 23

Implicit Variable Declaration

If you do not explicitly declare a variable and the variable name does not exist at a higher scope, MAXScript will create the variable the first time you use it and initialize it to hold the special value undefined . You are not required to explicitly declare a variable or initialize its value before you can use it. Variable names not explicitly declared by one of the previous statements are called implicitly declared variables. The scope of implicitly declared variables is the MAXScript scope context that is currently in effect when the variable is first used. The initial MAXScript scope context is global, and new scope contexts are opened for the following:

Within these new scopes, newly referenced variables will be implicitly declared as local variables.

Scope contexts are nested with the scope of a variable explicitly or implicitly declared at one scope context level extending to all scope contexts below that level.

Take for example the following script:

LINE #

EXAMPLE

1
2
3
4
5
6
7
8
9
10
11
a=10 -- scope context: global
( b=20 -- new scope context: level 1
for i = 1 to 5 do -- new scope context: level 2
( j=random i a
k=random i b
print (j*a+k*b)
) -- end of scope context: level 2
a=a+b
) -- end of scope context: level 1
print a -- back to global scope context
print k

RESULT #1

COMMENTS

10
170
120
290
320
360
30
30
30
undefined
undefined
result of a=10
print from i=1
print from i=2
print from i=3
print from i=4
print from i=5
return value of end of scope level 1
result of print a
return value of print a
result of print k
return value of k

RESULT #2

COMMENTS

10
170
420
410
180
160
30
30
30
5
5
result of a=10
result of print from loop i=1
result of print from loop i=2
result of print from loop i=3
result of print from loop i=4
result of print from loop i=5
return value at end of scope level 1
result of print a
return value of print a
result of print k
return value of k

In the above script, variable a is first used in the global scope context and its scope includes scope contexts global, level 1, and level 2.

Variable b is first used in scope context level 1, so it is implicitly declared as a local variable and its scope includes scope contexts level 1 and 2.

Variables i and j are first used in scope context level 2 and their scope is only scope context level 2.

The scope of variable k varies depending on whether this script has been run before.

The first time the script is run, variable k is first defined at line 5 and its scope is scope context level 2.

In line 11, variable k is used again. Because the variable k defined in line 5 is no longer in scope, a new variable k is defined whose scope is global.

The second time you run the script, at line 5 MAXScript detects that variable k already exists and uses it.

So, the first time you run the script, line 11 will print undefined , and the second time you run the script, line 11 will print the last value calculated at line 5.

Note: The scope of the variable used as a for loop counter (variable i in the above script) is a special case. The for loop counter variable's scope is always the scope context created for the for loop. This is true even if the for loop counter variable's name has already been implicitly or explicitly declared at a higher scope context level.

Lexical Scoping

When you explicitly declare a local variable, you can reuse the same name as a variable at a higher scope context level.

If you do this, the newly declared local variable hides the outer variables with the same name.

Any reference to that variable name later in the local variable's scope refers to the new local variable.

At the end of the local variable's scope, the next outer variable becomes visible again.

This visibility scheme is called lexical scoping. An example of lexical scoping is shown in the following script.

#

EXAMPLE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
global foo = 23, x = 20 -- explicit declaration of foo and x,
-- scope is global
y = 10 -- implicit declaration of y,
-- scope is global
format "context level global: foo= %\n" foo
if x > y then
( -- top-level open parentheses
-- new scope is created
local baz = foo + 1 -- uses the global foo
local foo = y - 1 -- declares 1st local foo, hides global foo
format "context level 1: foo= %\n" foo
b=box() -- b implicitly declared as local
b.pos.x = foo -- uses 1st local foo
if (foo > 0) then
(
local a
local foo = y - x -- a nested 2nd local foo, hides 1st local
format " context level 2: foo= %\n" foo
a = sin foo -- uses 2nd local foo
format "a= %\n" a
) -- leave scope
b.pos.y= foo - 1.5 -- back to 1st local foo
format "context level 1: foo= %\n" foo
) -- leave scope
-- b, baz and foo no longer in scope
format "context level global: foo= %\n" foo -- back to global foo

 

OUTPUT

 

20 -- result of line 1
10 -- result of line 3
context level global - foo= 23 -- output from line 5
OK -- result of line 5
context level 1: foo= 9 -- output from line 11
context level 2: foo= -10 -- output from line 18
a= -0.173648 -- output from line 20
context level 1: foo= -10 -- output from line 23
OK -- result of if expression lines 6 to 24
context level global: foo= 23 -- output from line 26
OK -- result of line 26

This might seem a strange way of over-using a single variable name, but these scoping rules can be useful in large programs.

For example, you might add a new user-interface item and its handlers to a utility script. By explicitly declaring the variables used in the handlers as local, you ensure these variables are independent of any pre-existing variables with the same names in the remainder of the script.

When writing scripts, it is good programming practice to explicitly declare your local and global variables.

Implicit declaration is provided as a short-hand, typically used when working in the Listener interactively or developing short scripts.

When developing extended scripts, explicitly declaring variables can reduce errors and improve readability of the code.

It is also recommended that you declare as local all variables unless you really want them to be global variables. There are several reasons for this practice:

(if false do box=10; box)

In the following script, an error was introduced by using undefined variable k in line 7. In the output, the error call stack trace-back shows the value for variable b in function afunc and in the block-expression calling afunc .

SCRIPT

fn afunc =
(
local b
b = box()
for i in 1 to 10 do
for j in 1 to 10 do
instance b pos:[i*20,j*30,30*sin(k*36)]
)
--
(
global c=10
local b
b="hello"
afunc()
)

OUTPUT:

afunc()
-- Error occurred in j loop
-- Frame:
-- k: undefined
-- j: 1
-- called in i loop
-- Frame:
-- i: 1
-- called in afunc()
-- Frame:
-- b: $Box:Box102 @ [0.000000,0.000000,0.000000]
-- called in <block>()
-- Frame:
-- b: "hello"
-- No ""*"" function for undefined
OK

If the same script is run with lines 3 and 12 removed, the following output is generated. Because function afunc and the block-expression are in different MAXScript scope contexts, variable b is implicitly declared as local in each and contain different values. However, because they were implicitly defined, they are not included in the error call stack trace-back.

OUTPUT:

afunc()
-- Error occurred in j loop
-- Frame:
-- k: undefined
-- j: 1
-- called in i loop
-- Frame:
-- i: 1
-- called in afunc()
-- Frame:
-- No ""*"" function for undefined
OK