RSS FEED

Maxscript one-liners for artists

There are many cases when doing something by hand is too tiresome and you can get it done really fast with some simple maxscript knowledge.

Collections
The first thing worth looking into are object sets, i.e. object (or rather node) collections. The predefined objects sets in max include objects, geometry, lights, cameras, helpers, shapes, systems, spacewarps and selection.
  select helpers -- selects all helper objects
  freeze selection -- freezes currently selected objects
Object sets are mappable which simply means that we can set their shared properties en masse.
  lights.value *= 0.5 -- halve the intensity of all lights
  selection.pos.z = 0 -- move all objects in selection to zero Z elevation
  objects.mat = undefined -- remove materials from all objects
This is all nice but what if we want just a specific subset of this group, for example just the key lights (note as well that when there is for example a skylight in the scene, the snippet above won't work as it doesn't have the value property)? Well, there's a pathname access which allows exactly for that. Pathnames identify individual objects in the scene and support not only hierarchic access but also wildcards. You have to prefix the object sets or object names by $ to tell max you don't try to access a variable but names.
  $lights/Key*.enabled = on -- turn on all lights whose names start with "Key"
  $*_left*.position += [50,20,10] -- adds 50,20,10 units (x,y,z order) to the positions
                                  -- of all objects whose names contain "_left"
  select $/selection/*  -- get only top-level nodes from selection
  deselect $Dummy?? -- substracts from selection objects named Dummy__
                    -- each ? substituting one character
  delete $'car.wheel.*' -- delete object with specified name from scene
                        -- handling special characters in objects' names using single quotes
  $Box001*...* as array -- list containing Box001 and all its children (complete hierarchy)
  hide $*/* -- hide all children in scene (i.e. leave only top-level nodes)
  selectionSets["Boxes"] = $Box* -- make a selection set from objects starting with 'Box'
There are also collections that are only accessible as a property of the node, like the object's modifiers, and then special collection types such as sceneMaterials and meditMaterials. For the complete list of collections in maxscript refer to the collection types (MXS Online Reference). In the next example, the $ followed by a dot refers to the current selection. You can see it mostly in code based on the output of Macro Recorder. However, selection keyword is preferred and should be used instead, as it avoids ambiguity – selection[1] refers to a single object, first one in the selection set (if there is such an object), selection refers to the selection set itself.
  $.modifiers.enabledInViews = off -- sets the state of all modifiers of selected object
                                   -- to "Off in Viewport"
  $.children.wirecolor = red -- modify wirecolor of direct children of a node
  $.selectedVerts.count -- get number of selected vertices
As we are accessing properties here and property access is not mapped (it makes sense to set one common propety in one go, not so much to get probably different properties with no sense of their ordering), the execution would error out if you'd have more than one object selected (which would not happen if selection[1] was used instead) or if the selection was empty (which would happen anyway). But there are many cases where we want to manipulate many objects and where the previous method would not work.

Traversing collections
That's where for-loop comes into play. There are basically two possibilities as to how to use a for-loop, first one is indexed access and the second one is iterating over a collection. The second one executes a certain operation on each member of a collection and will be extremely useful for us. The syntax is simple and it works without glitches even if the collection is empty or has just one member.
  for obj in selection do obj.name = uniqueName "Prefix"
  -- for every object in selection do : set object name to unique name with a given prefix
  -- obj here is a variable (you can use any other variable name instead, i, item etc.)
All the built-in collections are live, which means that if you want to take a snapshot of them at any given time, you have to cast them to array when assigning to a variable. However, it is also easy to make use of this property, for example when you call selectMore function, the object it is called on gets appended to the selection set. While the node.children collection contains only direct children of the node, this allows you to get the whole hierarchy:
  for obj in selection do selectMore obj.children
  -- add all children of currently selected nodes to the selection
There's also the while loop which keeps repeating the task until the condition is met:
  while selection.count > 50 do deselect selection[random 1 selection.count]
  -- deselect random objects until only 50 are left selected
Additionaly, there are two optional expressions for the for loop itself that we can use to narrow down the items we are interested in accessing, where and while. Let's have a look at an example using where:
  for obj in objects where isGroupHead obj do setGroupOpen obj on
  -- for each object, where the object is a Head of a group, set the group open
Multiple such tests can be combined together. To check if all conditions hold, use and, for one or more or and not to flip the resulting value of the expression. They are called boolean operators and to learn more about using them, be sure to look at the Logical Expressions chapter of the MAXScript reference. Note that even though I consistently use uppercase for them, it does not make any difference if you write them all lowercase (or even mixed case) as MAXScript is not case sensitive language and it is a matter of personal preference and/or which coding style you choose to follow.
  for obj in selection where isKindOf obj Helper AND obj.parent == undefined do print obj.name
  -- for each selected helper which is not parented to anything print out its name
One good example of using conditions in loops is avoiding errors such as invalid property assignments, the classic example would be assigning some fixed value or intensity to all lights that have the property (remember how the lights.value *= 0.5 example wouldn't work with skylight?):
  for eachLight in lights where isProperty eachLight #intensity do eachLight.intensity = 1000
  -- for each light that has the property intensity, make that property value 1000
And rather than doing something with each node separately in every single iteration we can collect the nodes to an array, and work with the resulting array instead. For-loop with collect keyword instead of the usual do returns the array and if you don't need to store it, you can access it directly (the command in brackets evaluates first):
  select (for obj in geometry where getNumTVerts obj == 0 collect obj)
  -- select (for each geometry object, where the object doesn't have any texture vertices collect that object)
  -- i.e., this selects all objects without proper mapping coordinates

  select (for obj in $box* where obj.height > 100 collect obj)
  -- select all objects starting with "Box" with height (in system units) bigger than 100

  select ($*_right* as array + $*_left* as array)
  -- select objects whose names contain either _right or _left
Sometimes, you want to change or distribute a number of objects at once. There are several commands in MAXScript to make your life easier, here I've picked a few common tasks:
  for obj in selection do instance $Downlight001 pos:obj.pos
  -- place instances of object at pivots of selected objects

  instanceReplace selection $LampPost01
  -- replace objects in selection with specified object (respecting the transforms)

  (getClassInstances TurboSmooth).enabledInViews = off
  -- turn all the TurboSmooth modifiers off in viewport (stays on in render)

  select (refs.dependentNodes $.baseObject)
  -- select instances and references of the selected node
Closing thoughts
While some of these scripts are suboptimal, in most cases they are good enough and can be easily altered to fit the purpose. There is also an added benefit to oneliners, after executing the script you can easily reevaluate it just by right-clicking the mini listener and picking the corresponding line from the list of recent commands.
Reevaluating a oneliner

DISCLAIMER: All scripts and snippets are provided as is under Creative Commons Zero (public domain, no restrictions) license. The author and this blog cannot be held liable for any loss caused as a result of inaccuracy or error within these web pages. Use at your own risk.

This Post needs Your Comment!

Eric Hance

Thank you Vojtěch, I refer to this alot over the last couple years, great page!

Return to top