Scope of Variables

Variables have an attribute called scope, which determines where in MAXScript code a variable can be accessed. 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. Once a 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 next.

MAXScript provides language constructs used to explicitly declare and initialize both global and local variables. The syntax for 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 that 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 is exited.

In certain situations, however, the top-level scope can remains active for extended periods and so the locals declared at that 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, so 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 when you next execute the Macro Script. 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 code in that construct can see the variable and so it does not conflict with other globals.

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.

Note

In 3ds max R2.x, the optional initialization value is applied to global variables only if the declaration creates a new variable. So, for example, if you ran the following script twice, the value of x in the second line would be 10 for the first execution, and 20 for the second execution. In 3ds max R3, 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 R3, 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 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:

Script

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

In this 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 at 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 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.

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.

Script

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 in large programs, these scoping rules can be useful. For example, you may 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 preexisting 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 recommend 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

See also