Object sets are mappable which simply means that we can set their shared properties en masse.
select helpers -- selects all helper objects
freeze selection -- freezes currently selected 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
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
valueproperty)? 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.
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
$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'
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,
selectionkeyword is preferred and should be used instead, as it avoids ambiguity –
selectionrefers to a single object, first one in the selection set (if there is such an object),
selectionrefers to the selection set itself.
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
$.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
selectionwas 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 collectionsThat'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.
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
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.)
selectMorefunction, the object it is called on gets appended to the selection set. While the
node.childrencollection contains only direct children of the node, this allows you to get the whole hierarchy:
There's also the
for obj in selection do selectMore obj.children
-- add all children of currently selected nodes to the selection
whileloop which keeps repeating the task until the condition is met:
Additionaly, there are two optional expressions for the
while selection.count > 50 do deselect selection[random 1 selection.count]
-- deselect random objects until only 50 are left selected
forloop itself that we can use to narrow down the items we are interested in accessing,
while. Let's have a look at an example using
Multiple such tests can be combined together. To check if all conditions hold, use
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
and, for one or more
notto 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.
One good example of using conditions in loops is avoiding errors such as invalid property assignments, the classic example would be assigning some fixed
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
intensityto all lights that have the property (remember how the
lights.value *= 0.5example wouldn't work with skylight?):
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
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
collectkeyword instead of the usual
doreturns the array and if you don't need to store it, you can access it directly (the command in brackets evaluates first):
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:
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
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 thoughtsWhile 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.
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.