[Closed] MaxScript Exceptions
Hello,
I am new to this forum. First of all, I want to say Hello, and I wish you a happy new year.
So, this is my problem. I am making a simple script. I need to access to the property options of boxes and Spheres geometries.
I am using:
aObjects = geometry as array
Then with a loop I access to all objects.
The problem if when I try to access to the geometry properties. Fo example:
Obj = aObjects[i]
aLength = Obj.Length
The problem is that I have spheres and boxes in my array, so when my variable Obj is an sphere, I get the exception “Unknown property”.
Is there a way which I can separate and select all boxes first and then all spheres?
or Is there a way of using exceptions like “try”, “catch”?
Thank you in advance,
Renzo.
Hi Renzo and welcome!
There are several ways to deal with this:
Yes, you could use a general try()catch() error trap, but in general it is seen as last resort as it tends to be slower (esp. when looping thousands or millions of times) and because it could “mask” an actual bug in your code and suppress a valid error message along with the desired suppression.
The code would look like
for o in geometry do
(
try
(
aLength = o.length
format "The Length of % is %
" o.name aLength
)
catch
format "Object % does not have a Length Property!
" o.name
)
Another, much better way to deal with this is to make sure you are asking for the class of the object before attempting to access a property.
For example, if you want to affect only boxes, you can make sure you access the .lenght only if the class matches:
for o in geometry where classof o == Box do
format "The Length of Box % is %
" o.name o.length
Obviously, if you want to support other object classes that have a length property, you would either have to add a lot of logical AND cases to the where test, use the CASE statement already proposed in this thread, or revert to the try()catch() method above.
But there is a better way to handle ANY cases of .length property WITHOUT knowing the class of the object, by simply asking the object whether it has that property. MAXScript provides two methods for doing this: hasProperty() and isProperty(). They are similar but NOT the same and operate very differently internally. Read the MAXScript Reference about the fine details – in short, the one calls internally getPropNames() and then performs a findItem() for the given search string onto the returned array (this could miss some properties not visible to getPropNames but still valid properties, for example node-level properties); the other actually attempts to access the property internally in a C++ try/catch context and returns true on success, so it catches more reliably, but is a bit like our case one above…
Here is an example of using hasProperty before accessing the property:
for o in geometry where hasProperty o "length" do
format "The Length of % % is %
" (classof o) o.name o.length
On a general note, you don’t have to store all your data into intermediate variables – aObjects, aLength etc. only make sense if you are going to reuse them multiple times. In many cases, it is FASTER to work inline on the existing object collections and access the object properties straight through the loop’s variable – allocating a local variable and converting all scene objects to an array and writing to memory is a relatively big performance hit (relatively speaking, talking about some milliseconds here, but just so you know ;)). The conversion of ‘geometry as array’ makes sense mainly if you are going to delete or add objects to the scene WHILE looping through the objects and want a snapshot of the scene state before you do so.
Also note that in some known cases, looping using
for o in geometry do
or using
myObjects = geometry as array
can cause slowdowns because MAXScript has to rebuild the Geometry object set by doing an internal loop through all objects and implicitly calling SuperclassOf() on each on of them. We have seen some advanced plugins and Particle Flow RECALCULATING COMPLETELY on each such call, which can mean MINUTES of lost time while collecting such simple thing as the Geometry object set. This is because in order to know whether a scene node is actually a Geometry object, MAXScript has to know the class on top of the stack (for example, a Shape like Circle with a Mesh_Select IS Geometry, the same Shape without the modifier is not!) and if the stack appears to be not updated at the current time, Max has to rebuild it. PFlow is rather touchy in this aspect and I had to rewrite all Frantic Films scripts to avoid using such loops for performance reasons.
Thus, using
for o in objects do
or
myObjects = objects as array
and then filtering by ClassOf() or hasProperty() is a better approach – it would have to go through all scene objects, but never call SuperclassOf() implicitly. Calling Classof() is fast and completely ok.
Hi, happy new year to you too
You can filter objects based on their class ID, using something like a switch statement.
(
classOfObj = classof Obj
case (classOfObj) of
(
Sphere : "I'm a sphere!"
Box : "I'm a box!"
default : "I'm someone else!"
)
)
Ah, an interesting tip Bobo! Thanks, didn’t know about the implicit SuperClassID and its hidden performance price. I don’t believe that’s the same for the SDK though, you have to explicitly EvalWorldState on an object to get its end-of-pipeline state. SuperClassID() on a node is just a trivial virtual function call however.
Anyway – what if you actually wanted to get a set of ‘geometry objects’ to export via MXS? ClassOf is fine when you only care about a particular type of object, but what about all renderable geometry (like what the renderer uses)? Is it still undesirable to use SuperClassOf (or implicitly with the ‘geometry’ set)?
We had severe problems in both Krakatoa, Amaretto and Submit Max To Deadline where some of our own plugins and PFlow turned out to update like crazy everytime we did a for loop through Geometry. This is a MAXScript-only thing, because the Geometry object set does not really exist, MAXScript has to create it on the fly from the object database (remember, it is “live” and always reflects the current scene content, so it is never cached in any way).
The reason for this occasional slowdown appears to be the validity interval. Some 3rd party plugins fail to set it correctly, and PFlow is the only case of a shipping plugin that appears to be doing it. Since Krakatoa was meant to work a lot with PFlow, we had the most reports of the problem there.
Since calling Classof() does NOT appear to cause the same problem, I implemented a scripted alternative method for our tools which does this:
fn FastGetGeometry =
(
for o in objects where findItem geometryClass.classes (classof o) > 0 collect o
)
This performs the loop through all objects in MAXScript, but instead of calling SuperClassOf o == GeometryClass it checks whether the class of the object is part of the list of valid GeometryClass classes… It performs flawlessly and avoids the problem with recalculating PFlows and other plugins with validity interval disorders. (Note that PFlow will recaulculate even if you save the scene, Hold the scene or even perform a SaveNodes() / Save Selected with OTHER objects selected than the PFlow elements. In fact, sometimes I believe it updates even if I sneeze…)
Ah, very informative. I see now that I should not underestimate the importance of maintaining a correct validity interval in my own code.
Very creative way to implement a fast geometry collection alternative! Never would have though of that. Can’t test it out right now, but I’ll keep it in mind the next time I need to do something similar to this.
It is indeed a pity that PFlow still has this bug in it. I wonder if Autodesk is aware and will address it in a future service pack.
Hi,
Sorry for the late reply, but I’ve just return from my vacations. I’ve just made the script I wanted. Thank you all.
Bobo: Thank you for your great explanation about performance. I am using max script for very simple things, but I am also a game programmer and I take a lot of care on performance when I use DirectX resources and shaders.
Renzo.