Optimising Scenes by Reducing Objects

One of the most efficient ways to optimise a scene in 3dsmax is to cut down on the sheer number of objects in the viewport. I’m not talking about deleting object, just merging objects together to one mesh can seriously improve the redraw rate and improve the handling with the Scene Explorer. It currently seems to struggle above 8000 objects regardless of polygon count. Often a scene that has come from CAD might have a crazy amount of helpers, you can delete a lot of these helpers to speed things up right away, and there’s way to strip down a hierarchy to a certain depth level so you don’t have 20 helpers for each piece of geometry.  Finding lots of similar objects that could easily be represented as one object is a nice way to improve speed.

Attaching Objects in 3dsmax is something that is slooooowww…….. using the built-in tool in 3dsmax it can take hours to attach even just a hundred objects. A few years ago there was a maxscript challenge thread on CGTalk to attach objects together as efficiently as possible. There were some very good solutions, and like any good coder, I borrowed the one I found best for me. Written by Tyson Ibele, it’s called Cluster Attach, it’s very easy to make this a macroscript and use it instead of the built-in attach button in Editable Poly.

Fast Attaching Methods

I find myself using this code all the time…

function clusterAttach objArr =

j = 1
count = objArr.count

undo off
while objArr.count > 1 do
if classof objArr[j] != Editable_Poly then converttopoly objArr[j]

polyop.attach objArr[j] objArr[j+1]
deleteItem objArr (j+1)

j += 1

if (j + 1) > objArr.count then j = 1

return objArr[1]

To call the function you can use this on an active Selection.

clusterAttach (selection as array)

Here’s a real-world solution which can help when say you get a file which has a load of stitches as individual objects rather than just one object for all the stitches.  It’s assuming those stitches are at least linked to a helper that define a set of stitches. If not, you’ll have to manually select them and run the code above.

–find all objects which have a child object with the name ‘stitch’ in its name.
for o in objects where o.children.count != 0 and matchpattern o.children[1].name pattern:”Stitch*” do
–merge all the children of our object
myObj = clusterAttach (for c in o.children collect c)
–reset the
myObj.parent = o


Or another one where maybe we want to find objects that have loooooads of children…

for o in objects where o.children.count > 500 do
–merge all the children of our object
myObj = clusterAttach (for c in o.children collect c)
–reset the
myObj.parent = o


Posted in 3dsmax, MaxScript | Leave a comment

Getting Mapped

Ok I admit it, I’m trying to help people learn maxscript and occasionally there will be things I don’t understand, often the process of writing out a blogpost helps solidify my knowledge and makes me ask the question of whether I actually know what I’m talking about (which I hope is most of the time by now!).

One thing that stumped me for ages and I just didn’t use, because I didn’t quite understand the why and where you would need it was mapped functions.  I saw a few examples and looked it up in the help but it didn’t seem to make a load of sense. It’s so different from a normal function that I was a bit lost.

A little while ago I looked at mapped functions again and now thanks to using MCG I’m familiar with the term ‘Map’, and I get how in MCG you have an array you map it with a function and the function gets run on each element in the array…. but hang on… maybe that’s how mapped functions work.. aha it is!

So here we go, as simply as I can make it, a mapped function  that changes the wirecolor of each item in the array that it gets passed.

mapped function testMe x =
x.wirecolor = (color 0 0 0)

a = selection as array

testMe a

Simples hey!? It’s just like saying for o in myArray do o.wirecolor = (color 0 0 0) etc….

I should have been using this a long time ago…. and will start using it more now.

Posted in MaxScript, Uncategorized | Leave a comment

Return of the Auto back

Autoback is one of those features we love to hate… we love it when it saves our ass, we hate it when it interrupts what we are doing.  One of my main pains is when you open the autoback, you have to go through a resave it in the right place.  Someone asked me why it can’t know where to go back to so I thought I’d write a little script to do just that!

–Declare a persistent global which will stay with the file when we open it
Persistent Global g_OriginalFilePath

–It’s good practice to remove any callback scripts before adding new ones
callbacks.removeScripts #filePostOpen id:#dw_tools
callbacks.removeScripts #filePostSave id:#dw_tools

–lets make a new function which we will call using the callbacks
fn restoreFile =
–If the filepath of our maxfile matches our autobackup we know we’ve got an autoback file!
if maxfilepath == (Getdir #autoback) then
MessageBox “Please resave this autoback file in the original folder.” title:”God bless autoback!” beep:true

–if we’ve got an original path then bring up a save max window with the old path
if g_OriginalFilePath != undefined do
newFileName = getMAXSaveFileName filename:g_OriginalFilePath
if newFileName != undefined do saveMaxFile newFileName
–If not then we need to update the path so that it’s the latest file we’ve opened/saved
g_OriginalFilePath = maxfilepath + maxfilename

–Add a callback for opening / saving files to store our filepath and check when opening/saving
callbacks.addScript #filePostSave “restoreFile()” id:#dw_tools
callbacks.addScript #filePostOpen “restoreFile()” id:#dw_tools

Super simple, and will probably save a couple of hours a year for each max artist. All you need to do is put that in a maxscript file in your startup scripts folder which can be found either:
C:\Program Files\Autodesk\3ds Max 20xx\scripts\Startup
C:\Users\Sherlock.Holmes\AppData\Local\Autodesk\3dsMax\2016 – 64bit\ENU\scripts\startup

Posted in 3dsmax, MaxScript | Leave a comment

Maya – Max: Why doesn’t 3dsmax have a Shape Node?

If you’re coming from Maya  to 3dsmax, or you’re learning Maya and getting confused as to why in Maya there are Transform and Shape node and in 3dmax it doesn’t look like there are… here’s a bit of a explanation, as both of them work the same way….

3dsmax has each object as one node in the scene… there is a baseObject property for each node which is where the class of the node is assigned as an object, this is effectively your shape node. When you create an scene-object, you tell 3dsmax what class of object you want to create by clicking on a ‘Plane’, or ‘Teapot’ for example. 3dsmax creates a ‘node’ object in the scene and then creates an instance of this class and assigns this object to the baseObject property of the scene-object.

Looking at the command panel in 3dsmax, the modify panel is effectively the shape node, the controller, hierachy and display tabs are all for the ‘transform node’ properties.

You can change what an object actually is in 3dsmax by assigning a new maxObject to baseObject property.  Create a Box in your scene and then run this line of code…

$.baseObject = (createInstance Teapot())

Ok so what is this baseObject property? When you use the commonly used*…

classof $

You are actually doing….

classof $.baseObject

*Thanks to Paul Neale for pointing out that classof $ returns the class of the object given any modifiers changing say from a spline to geometry using say the sweep modifier.

When maxscripting, a max node inherits all the properties of the baseObject MaxObject.

$Sphere001.radius = 50

Where as properties such as:
are on all objects in 3dsmax regardless of their class

Properties of the maxObject, such as in an example of a sphere….
are unique to the class that is associated with the node as specified by its baseObject Class.

You can use the baseObject property to access these properties as well..


When you use getClassInstances it returns an array of all the instances of the class but not actual objects, so you have an array of all the maxObjects that are in the ‘baseObject’ properties slots for all the objects in the scene.  To get from this baseObject to the actual node you can use the reference system to get the dependent nodes, as one maxObject may be shared between multiple objects. When objects are instanced in 3dsmax each node is unique but the baseObject property is shared between the instanced nodes.

Create three VRayLights in your scene (or any other kind of object)

>>> myObjs = getClassInstances (VRayLight)
(VRayLight, VRayLight, VRayLight)

See how it list 3 VRayLight types but it’s not pointing to VRayLight001 for example. This means using;

myObjs[1].invisible = true

works… but if you were to try this…  you’d get…

— Unknown property: “position” in VRayLight

That’s because we’ve got an array of maxObjects not scene objects so we are querying the baseObject rather than the Object itself in the scene. We can get the Object my using refs.dependentNodes.

>>>refs.dependentNodes myObjs[1]
#($VRayLight:VRayLight001 @ [21.382523,52.673576,0.000000])

Take VRayLight001 in your scene and clone it 4 times as an instance, re-run the following code….

>>>myObjs = getClassInstances(VRayLight)
#(VRayLight, VRayLight, VRayLight)

See how we still get only 3 maxObjects returned?

>>refs.dependentNodes myObjs[1]
–returns 5 objects now.
#($VRayLight:VRayLight007 @ [252.584503,52.673576,0.000000], $VRayLight:VRayLight006 @ [164.925598,52.673576,0.000000], $VRayLight:VRayLight005 @ [77.266678,52.673576,0.000000], $VRayLight:VRayLight004 @ [-10.392235,52.673576,0.000000], $VRayLight:VRayLight001 @ [-98.051147,52.673576,0.000000])


Other places you can spot this Transform-Shape/BaseObject system in max is in the trackview when you look at an object (say Sphere001) you’ll see the Transform controller as subAnims and the “Object” (which should really be called ‘baseObject’).

In Maya these two are separate nodes… the Transform and the Shape Node.
Transform node = maxObject node
Shape Node = baseObject

In the outline you can expose the shape nodes and you can see the shapes as nodes, If you instance a node in Maya you’ll see that the transform nodes are unique but the shape nodes are the same.

In Maya Cmds there isn’t property inheritance like in max…. if you select the Transform node, the only attributes you can access are those on the Transform node. You have to get the shape node…. which isn’t as simple as just getting the value of a property of the transformNode….

selected_items = cmds.ls(selection=True)
if selected_items:
shapes = cmds.listRelatives(selected_items[0], shapes=True)
if shapes:
print shapes[0]

Using PyMel we can do this far more conveniently as everything is objects.

import pymel.core.general
selectedTransform = pymel.core.general.selected()[0]



Posted in 3dsmax, Lessons, MaxScript | Leave a comment

That doesn’t work. – Part 1

Is anyone out there old enough to remember the fantastic Discworld Computer Games based on the late Terry Pratchett’s novel series? A brilliant and hilarious creation that allowed you to take your character, the embarrassingly useless wizard, Rincewind through the fictional city of Ank-morpork and solve a series of challenges using various items of your inventory.  Trying to use items in places they didn’t work would result in your character saying flatly…. “That doesn’t work…”  which.. just like the wizard is completely useless information.  It didn’t allow you to understand what you were supposed to do instead.

Bugs in software happen all the time. It’s pretty much impossible to write 100% bug-free code in complex programs as people will do things the code writer never envisioned. Sometimes the bug will be a crash and sometimes it’s just the software not doing what you expect.  As more and more of our life becomes digital, even if you aren’t writing your own code it’s really important to understand how to properly report what’s not working.  Just saying ‘That doesn’t work’ doesn’t help the code writers fix your problem.

A guide to helping programmers fix your problems:

  1. Be specific, say exactly what the problem is… i.e
    “the software just crashes” – useless information
    “There’s a problem with adjusting these parameters with this object type” – good information
  2. Write down the steps you did to cause the problem
    “I opened up ‘window A’ and pressed ‘button B’ and then tried to move this and the program hung for about 30 seconds and then crashed to desktop” – really useful information!
  3. Is it repeatable? Can you do it again and cause the same problem, if not then this is a complex problem and will be hard to reproduce.
  4. Does it happen on another machine?
  5. Supply an file or image showing the problem exactly if it’s possible.
  6. Record a video showing the problem if there’s a long process
  7. If the problem is with a script in 3dsmax you’ll get a pop-up saying something like “Unknown Property in Undefined” this tells me the writer what type of error there is but it doesn’t tell me where the error is, instead have a look in the maxscript listener, there will be an error report which will tell the writer which line the error was on as well as what any variables were assigned to. Using the debug report I’ve been able to fix plenty of scripts without even trying the reproduction steps.
  8. Triage the problem, is it a show-stopper that you need to explain that, if there’s an easy work-around then that’ll be a lower priority to get fixed.

I’ll write more on how as a code write to take this information and actually use it in the next part of this post.

Posted in 3dsmax, MaxScript | Leave a comment

Vray Render Mask Maker

The VRay Render Mask was added back in Vray 3.0 I think, you can do include/excludes, choose to only render your selected object or put an image mask in to render this.

Back then, I wrote this tool, which I completely forgot about until today which allows you to basically draw out multiple render regions and set this up as Render Mask.


It has a fairly simple but nifty UI with some clever bits of power, like being able to use MME and adding the selected objects… (using a very dirty hack).  Most of the time I think most people will just draw out areas. Overlapping areas don’t get rendered twice so you can draw some fine detailed areas to re-render if you want, but always give yourself a little bit of bleed as there’s a small precision loss in the scaling.

Find it on…


Posted in 3dsmax, MaxScript | Leave a comment

Making MCG Transform the way you work



MCG is allowing me to rethink the way I work in 3dsmax, there are so many times when we do things which are destructive in their very nature, or a little bit fiddly and not intuitive to see what has been changed.  Something I do fairly often is shift the sub-object of a mesh around to move it to offset the pivot, or I adjust the pivot, or I use an XForm modifier to do this, the last being probably the best way to do this, but there’s something unsettling about the lack of visual feedback with the XForm modifier, it doesn’t explicitly tell you what your offset actually is.

I’ve broken down the 3 stages of a transformation matrix into the 3 component parts. Offset, Rotation and Scale. With MCG I’ve been able to make some very simple modifiers which just take the mesh in, apply some form of offset with a vector and push out the output.

This means if you want to do a -1 scale in Z but not change the transform of an object you can do this precisely, if you need to rotate your mesh in it’s object space 90 degrees, then you can, if you want to move the mesh down slightly when it’s bound to some other kind of position constraint, you can!

The best bit about this is, that it’s procedural, you can turn these modifiers on and off easily, everything is exposed so you can rig and link it up.

Check it out on Scriptspot!


Posted in 3dsmax, MCG, Uncategorized | Leave a comment

MCG Tutorial: How to make a Select By Face Area Modifier

So you want to make a Select Face By Area modifier in MCG…. here’s a quick guide.

You want two things to start with for making any modifier which modulates your existing mesh… a Modifier:TriMesh as your input and an Output: Modifier as your output Node.
Save this as “SelectFaceByArea.maxtool” in the Tools folder of MCG. You can Run this (Ctrl+E) and you’ll find it in your modifier stack and if you apply it, it will just return your original mesh.


Searching for *FaceSelection we find the “SetFaceSelection” Node, when we look at the Description for this node it says:

Signature: (TriMesh, IArray[Boolean]) -> TriMesh

So this means the node wants a TriMesh Input and will give us a TriMesh Output which we can see from the colour coding of the sockets so we can straight away plug this in between our input and output nodes. But what about this faceSelection(IArray) third socket…. Well looking at the description it says IArray[Boolean] so it wants an array of booleans to tell it which faces it wants to select. So our array must be the same size as the number of faces in the mesh.


When we look we for a node which might take our mesh and get the faces we find two, MeshFace and MeshFaces, one gives us just the one specified face and the other gives us all the faces. Using the MeshFaces node we get an IArray of faces as an output. We can plug this into our Modifier: Trimesh Node as a second connection. Looking at the description of the MeshFaces Node we see the Signature says (TriMesh) -> IArray[Face3] So it’s going to make an array of Face3 Values. You’ll find a FaceArea Node which has an input of “Face (face3)” so we can now say we want to loop over all our face3 values in the array. Now when you look for a loop node in MCG you will be dissapointed.. there isn’t one… this seems really strange at first but once you’re used to it, you’ll see why. What we want here is a ‘Map’ node… that takes an IArray and outputs an IArray and it performs a function on each item in the array.


The fxn(Func) input is where we want to define what our function is… So what is our function? We want to go test every face against a value to see if it’s Less than it. So we are starting with a size, comparing it against a size, and returning a true/false value. Which is good as we want to start with an IArray of face3 values and end up with an IArray of Boolean (true/false) values. Our faceArea node outputs a ‘Single’ value, which is basically a float.. we want to test it against another single value so we could either define the value within MGG or we could make it a Parameter so the user can play with the value… so let’s do that. Create a Parameter:Single and then join both of these up with a LessThanOrEqual Node… which should hopefully make sense. The LessThanOrEqual node can have ‘Any’ inputs or outputs, but your inputs should match in type.. you can’t compare Integers and Singles as it doesn’t do type conversion for you, so you may need in another case to use a ‘asFloat’ node as well.


So that’s our function in isolation. Now take the Function(fxn) output of the LessThanOrEqual node and connect it into the fxn(Func) input socket of the Map node. You’ll notice of course that the face3 socket of the FaceArea node doesn’t have anything in it. This is where MCG’s user experience is a bit tricky to get at first. The Map node throws out each item in the IArray and it’s actually clever enough to know where the argument is supposed to go. With the socket open it knows this is where to throw the array item to. So we’ve now got a function which tests our parameter against the face area.


One last annoying user experience quirk. If you Save and Run this (Ctrl+E) it won’t look like it’s working, but actually it is… MCG can’t invoke the sub-selection mode in the modifier stack. So if you put a PolySelect modifier below yours and set it to Face mode, now your tool will work.


Posted in 3dsmax, Lessons, MCG | Leave a comment

MCG Drop the Population

The Populate feature in 3dsmax is one of the most frustrating for me… the technology behind it is pretty damn awesome but the implementation is immensely frustrating, it’s awful for scripts/tool developers as there’s very little to access.  The quality of the models hasn’t been improved and the UI for making people look how you want them is very tricky to use.  When you know how you can actually make people look like business people.


One of the most common frustrations is that the people can only walk on flat surfaces, putting ramps in is complicated and this doesn’t allow you to put things like kerbs in for people to walk off or have slightly undulating surfaces.  Someone asked if it was possible for Autodesk to add support for uneven surfaces and that gave me an idea.  Can we use MCG to move the meshes down to the surface? MCG is pretty good for doing ray-testing and manipulating meshes, so I gave it a go.  Here’s a Dave-friendly way of trying to explain how it works. It’s a fairly simple graph so I recommend opening it up and having a look!  It can probably be used for other things too so have a play and let me know!


The challenge was to work out how to do a hit-test from the point of the character down to an object below. We start by using the Modifier (Matrix) which if we get the row4 element gets us the position of our node in world space. But how do we do a hit test I wondered….. which wasn’t too complicated to do once I discovered the Ray node which wants a position and a vector (direction made from ‘Z axis’ and turned downwards using the ‘Negate’node), this fires a ray off and you pass that to ‘NodeIntersects’ node which is which object in the scene you want to see if the ray intersects with, pass these two together and you’ll get a Tuple Value which consists of a ray and a boolean value, so with this we can find out where our intersection is and if it actually intersected as well. A Tuple value is two values together and to get just one out we use a PairItem1 node which gives us the first value, which in this case is the intersection Ray, so we use the node RayPosition to get the position of the ray and then we extract the Z point of this vector 3 to be able to put this into a new vector 3 value, which is an XYZ value, we want to offset the vertices in our mesh only in X (because Populate is weird) so we set Y and Z to a constant single (float) of 0.0 and then pass our new Z value to it and then pass this new vector to the OffsetMesh node.   I added a little extra Z Offset value to make sure that we could adjust the alignment if it wasn’t quite right (normally isn’t it turns out).

Version 1.1

If the Populate flow was created not at 0 in Z Axis you had to do a fairly hefty manual offset, now you just need to select your Populate Flow node as well and it doesn’t matter how high your Flow is, it’ll still conform down. Note, moving your flow node up and down after creating people won’t be interactive with this tool.

You can get it here on ScriptSpot.


I have real hopes for what MCG will bring to 3dsmax, little things like this are easy to set up and extremely powerful!   I wonder if I can make a Populate person walk along a custom spline… hmmmm

Posted in Uncategorized | 1 Comment

Power Masker

The 3dsmax UI only allows you to set material effects ID up to 16 channels. The VRayMtl allows you to go beyond this using the effects ID override but other types of materials don’t have any UI to adjust this. This tool using a backdoor into 3dsmax to set the effects ID beyond the 16 limit imposed by the UI.

This means you can create masks for every material in your scene. Using this tool you can either assign effects IDs by hand or just press to automatically create them and their mask render elements.

You can also make a Vray Multi-matte mask for each layer in the scene too, this uses the include/exclude lists rather than the GBuffferIds, so that you can therefore use the GBuffer IDs for doing variation with texture maps or mask objects individually if you want to.

Get it on ScriptSpot!



Posted in MaxScript, Uncategorized | 2 Comments