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.
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.
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.
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
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:
Top-level open parentheses in a script file or in the Listener
Start of a function body
Start of a for
loop body
Start of a utility, rollout, right-click menu, Macro Script, or tool definition
Start of a rollout, utility, right-click menu, Macro Script, or tool event handler
Start of a when
body
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:
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.
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.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.
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:
All the variable names used in the block or function will be together, which makes it easier to find the variable names and helps prevent using incorrect names in the script.
If more than one script is executed at the same time (for example, you are running a scripted utility and have a scripted controller in the scene), and both scripts use the same global variable name, the two scripts can interfere with one another. This can cause random failures of one or both scripts.
You are sure of using the correct variable in the event that a variable with the same name has already been declared at a higher scope, and you cannot inadvertently change the higher-level variable's value.
Values of variables explicitly defined as local are displayed in error call stack trace-backs.
In 3ds Max R2.5, if a 3ds Max class, a MAXScript method, or a MAXScript read-only global variable has the same name as an implicitly declared local variable, a MAXScript runtime error will be generated if an assignment to that variable name occurs. Because the variable name already existed with a global scope, an implicitly declared local variable is not created. As one or more new 3ds Max classes will be created by any third party plug-ins the user has loaded, you cannot tell ahead of time if a variable name is a global variable name. In 3ds Max R3, if the MAXScript compiler detects that the first reference to such a variable is an assignment, it will implicitly declare a local variable, rather than leaving it as a reference to a read-only system global. For example, executing the following expression in 3ds Max R2.5 results in a value of Box
because Box
is a 3ds Max object class. In 3DS R3, MAXScript detects that the first use of variable box
is an assignment and creates an implicitly declared local variable box
. The result of this expression in 3ds Max R3 is the value undefined
.
(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