Intricacies of 3ds Max Part 1

Half of what I know isn’t just how to solve a problem, but why that problem exists and when we are just using work-arounds to get around problems.  Here are a few examples which may seem illogical at first, but are important to understand.

Object Names are not unique…

Objects in 3dsmax don’t need to have unique names, this can make certain things a bit complicated, like if using the getNodeByName() function, it will only return one occurrence of the object, and I’ve noticed that the one it picks can be different in local and slave mode.  There is an extra parameter for getnodebyname “thisName” all:True which will give an array of all the objects with this name.

When you are merging objects in to the scene, either by script using mergeMaxFile or using the dialog when you have duplicate object names you have to choose what you do….. Either:

  1. Rename the object – So you can merge in your object with a new name.
  2. Skip the object – Don’t merge your object in to the scene
  3. Merge the Object – Merge the object in without changing any names creating a conflicting name.

So if 3dsmax doesn’t work on object name basis to identify objects how does it actually work? Well it’s what’s called the handleID of the object.  You can see any handleID of any object by querying it’s handle property.  It’s not something exposed to the UI, and it doesn’t persist with the object when it’s merged in to another scene. You can’t have two objects with the same handle so when merging a file in, all the handles of the merged objects will be += the number of objects in the scene before merging.

Sorting names alphabetically doesn’t quite do what you expect

The sort() function allows you to sort a list of strings into alphabetical order.. Great says the artist, I know all about sorting alphabetically, I learned that at primary school.  Wrong.  In the computer world, objects are sorted slightly differently.

Suppose I gave you this list:

#”(“elephants”,”are”,taking”,”back”,Elephant”,”Tangerines”)

So the ‘logical’ way of sorting this list would result in….

“are”,”back”,”Elephant”,”elephants”,”taking”,”Tagerines”

But alas in 3ds Max it’s

“Elephant”,”Tangeringes”,”are”,”back”,elephants”,”taking”

This is because it’s sorting based on something like byte size order where ‘E’ is before ‘a’. You may have seen this in the scene explorer in max already. And unfortunately/fortunately the sort function returns the same result.

Other intricacies of sorting to be aware of are with differently formatted string numbers.

myAR = #(“2”,”1”,”10”,”11”)

myAR = sort(myAR)

Will return:

#(“1”,”10”,”11”,”2”)

So to avoid this you will need to pass these items to an Integer array and then sort that and then put the values back to strings.

(for n in (sort(for o in myAR collect o as integer)) collect n as string)

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

Interactive Rollout Builder for 3ds Max

For any tool to be truly useful it needs to have an interface. The quality of your interface might not seem like the most important thing but to an artist there’s a quality level that comes through from the neatness and usability of the tool which makes the artists trust it more.  Compare these two interfaces, they do the same function but which one do you think was written by the coder with the better attention to detail and may therefore have less bugs?

layouts

Creating rollouts with maxscript can be done two ways.  The Visual MaxScript editor may seem like the obvious way to create tools, you can drag and drop controls and neatly align the controls.  Unfortunately it isn’t always particularly acurate when you then launch your script. Particularly with spinners, and any other controls or properties it doesn’t know about won’t be dealt with correctly  Whilst this can be a good starting point for learning which controls exist and what their properties are I don’t believe it really teaches the best way to lay out your controls.  The problem comes when you want to add something in to an existing UI you need to shuffle everything around.  

The way I write rollouts is to list the controls out and use the ‘across’, ‘offset’ and ‘align’ properties of the control to get a nice neat layout.  Tweaking the offset parameter in particular can allow you to get things exactly in to line.  But what ends up happening is lots of (Ctrl+E) bashing to test the UI.

RolloutCreator_UI

This is where my new tool comes in,  a maxscript based rollout generator which works on using the neater layout controls rather than exact positioning and allows you to quickly build professional looking UI, and add in extra controls later on.

It works interactively so you can modify controls and see the exact response in a rollout next to the window.  As this window is built by maxscript it is exactly the result you will get when you run the UI code in your own tool.

You can paste rollout code in and load it into the editor but at the moment it has limited functionality as I need to find a way to pass the code through an interpretator to pass it.

Check out the video here:

Even if you aren’t a maxscript writer you could use this tool to create the interface for a tool that you want someone else to write.

Download it here:

http://www.scriptspot.com/3ds-max/scripts/interactive-rollout-builder

Posted in Uncategorized | Leave a comment

Lesson 15: Enumeration Stations….

Sorry this blog has been neglected… Facebook makes things much easier to share…

In some other….. ‘proper’… coding languages you have a function called Enumerate…. this allows you to iterate through an array and not only get the value but also index of the item too….. like this… Who even uses Python??

In a traditional maxscript loop you’d need to use the index of the array….

myArray = #(“a”,”b”,”c”,”d”)

for i  = 1 to myArray.count do
(
format “#%: %\n” i myArray[i]
)

or you had to put a counter outside of the array

myCounter = 0

for o in myArray do
(
myCounter += 1
format “#%: %\n” myCounter o
)

But we can enumerate if we create a function to do this for us.. I wrote this one which uses the rarely used DataPair value type…. DataPair Value Type from Maxscript Help

Data pairs are actually quite useful, especially as you can label the values… so instead of reading o.v1 and o.v2 we can read o.index and o.value… a much more pleasant way to read our code…

fn enumerate ar = for i = 1 to ar.count collect (DataPair index:i value:ar[i])

myArray = #(“a”,”b”,”c”,”d”)

for o in enumerate(myArray) do
(
format “#%, %\n” o.index o.value
)

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

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
)
)
else
(
–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
or
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.
example:

$Sphere001.radius = 50

Where as properties such as:
.renderable
.primaryVisibility
.visibleToReflection
.material
.transform
are on all objects in 3dsmax regardless of their class

Properties of the maxObject, such as in an example of a sphere….
.radius
.segs
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..

$Sphere001.baseObject.radius.

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)
–returns….
(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…

>>myObjs[1].position
— 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]
–returns
#($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]
selectedTransform.getShape()

 

 

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.

VRayRenderMaskMaker

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…

http://www.scriptspot.com/3ds-max/scripts/vray-render-mask-maker

Posted in 3dsmax, MaxScript | Leave a comment

Making MCG Transform the way you work

 

corners

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!

http://www.scriptspot.com/3ds-max/mcg/mcg-transformation-modifiers

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