View Full Version : Interactive mel execution, performance

08 August 2007, 09:47 PM

I have been trying to develop a couple of tools that work based on the approximation of, for instances, a locator (for instance scale spheres up).

The script works well... for a small number of objects. As soon as I increase the count, it starts becoming a very very slow operation.

I created an expression that is connected to the scale attributes of the spheres. The expression evaluates interactively, as I move the cursor around my scene. The technique becomes impractical when I increase the number to ... say 1000 objects. The objects themselves can be stripped to the minimum, such as poly cubes.

As I drag the locator and move it around, I can see that the expression is exeuted several times, so I thought maybe if I could get the code to execute only once per frame instead. I looked at the scriptJob command but from the get go the documentation states that they do not support the timeChanged event flag in playback mode... I thought maybe this approach would be more efficient as the code would only execute once per frame (such as the nCloth interactive playback feature).

I guess the only other option for me would be the API and writing a plug in, but I am not familiarized with the API and do not know C++. Am I stuck? Has anyone run into a similar roadblock? Performance wise, how can I get this to work? I am including code to illustrate my point. Just paste it in the script editor on a new scene and execute. Try modifying it by adding say, 100 of spheres...

As always, thanks to yall.

08 August 2007, 02:28 PM
If you want to go the API/node route and you have a version of Maya with Python then that may be an easier place to start with the API.

Here is a hacked up version of the API example that is in Maya's devkit.

import math, sys
import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx

kPluginNodeTypeName = "spSineNode"

sineNodeId = OpenMaya.MTypeId(0x87000)

# Node definition
class sineNode(OpenMayaMPx.MPxNode):
# class variables
input1 = OpenMaya.MObject()
input2 = OpenMaya.MObject()
output = OpenMaya.MObject()

def __init__(self):

def compute(self,plug,dataBlock):
if plug == sineNode.output or plug.parent() == sineNode.output:
input1Point = dataBlock.inputValue( sineNode.input1 ).asFloatVector()
input2Point = dataBlock.inputValue( sineNode.input2 ).asFloatVector()

result = input1Point - input2Point

outputHandle = dataBlock.outputValue( sineNode.output )
outputHandle.setMFloatVector( result )
dataBlock.setClean( plug )

return OpenMaya.kUnknownParameter

# creator
def nodeCreator():
return OpenMayaMPx.asMPxPtr( sineNode() )

# initializer
def nodeInitializer():
# input1
nAttr = OpenMaya.MFnNumericAttribute()
sineNode.input1 = nAttr.createPoint( "input1", "in1" )

# input2
nAttr = OpenMaya.MFnNumericAttribute()
sineNode.input2 = nAttr.createPoint( "input2", "in2" )

# output
nAttr = OpenMaya.MFnNumericAttribute()
sineNode.output = nAttr.createPoint( "output", "out" )

# add attributes
sineNode.addAttribute( sineNode.input1 )
sineNode.addAttribute( sineNode.input2 )
sineNode.addAttribute( sineNode.output )

sineNode.attributeAffects( sineNode.input1, sineNode.output )
sineNode.attributeAffects( sineNode.input2, sineNode.output )
sys.stderr.write( "Failed to initialize node: %s" % kPluginNodeTypeName )

# initialize the script plug-in
def initializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
mplugin.registerNode( kPluginNodeTypeName, sineNodeId, nodeCreator, nodeInitializer )
sys.stderr.write( "Failed to register node: %s" % kPluginNodeTypeName )

# uninitialize the script plug-in
def uninitializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
mplugin.deregisterNode( sineNodeId )
sys.stderr.write( "Failed to deregister node: %s" % kPluginNodeTypeName )

...and a small mel script to test the node:

string $locator[] = `spaceLocator`;
string $locatorTransAttr = $locator[0] + ".translate";

seed 7777;

int $i = 0, $numObjs = 1000;
for ($i=0; $i<$numObjs; $i++)
string $nodeName = `createNode "spSineNode"`;
string $input1Attr = $nodeName + ".input1";
string $input2Attr = $nodeName + ".input2";
string $outputAttr = $nodeName + ".output";

string $obj[] = `polySphere -radius 1`;
move -a `rand 25` `rand 25` `rand 25`;

string $sphereTransAttr = $obj[0] + ".translate";
string $sphereScaleAttr = $obj[0] + ".scale";

connectAttr -f $locatorTransAttr $input1Attr;
connectAttr -f $sphereTransAttr $input2Attr;
connectAttr -f $outputAttr $sphereScaleAttr;

select $locator[0];

The Python should be pretty easy to follow.

The node has 3 attributes: input1, input2 and output which are all points/vectors. input1 and input2 both affect the output attribute. Creating the attributes and hooking up what affects what is in the nodeInitializer function.

The "compute" function is where you calculate the output. All this script does is set the output by subtracting the two vectors. All you would need to do to play around with this is change the line that looks like this:

result = input1Point - input2Point

to calculate whatever you want. You can look at the Python documentation for the math package to see what is available math-wise...that is instead of calling out to Maya's math routines like you have in the expression which is possible but would be slower.

In the mel script I create 1000 spheres, 1000 of these nodes and 1 locator. The locators position is connected to each of the nodes "input1" attribute, each of the spheres position is connected to the "input2" attribute and the "output" attribute is connected to each of the spheres "scale" attribute. (With 1000 spheres/nodes I get around 10 fps when dragging around the locator with the move tool)

This is obviously hacked together and doesn't do exactly what you want but if you are looking to start writing some tools then it should at least give you something small to start playing around with.

Derek Wolfe
08 August 2007, 01:14 AM
You can try these changes. I have reduced the work required of the expression by connecting the scaleX of each sphere to drive it's own scaleY and scaleZ. This should help to make things evaluate a little faster.

PS: I can't seem to get the code to format correctly. You will have to remove some spaces in the expression string. ie: "affected 1" should be "affected1". Sorry about that.

string $sphe[] = `sphere -p 0 0 0 -ax 0 1 0 -ssw 0 -esw 360 -r 1 -d 3 -ut 0 -tol 0.01 -s 6 -nsp 6 -ch 1`;
rename $sphe[0] "affected1";
connectAttr -f .scaleX .scaleY;
connectAttr -f .scaleX .scaleZ;
move -r 0 0 -7;
string $sphe[] = `sphere -p 0 0 0 -ax 0 1 0 -ssw 0 -esw 360 -r 1 -d 3 -ut 0 -tol 0.01 -s 6 -nsp 6 -ch 1`;
rename $sphe[0] "affected2";
connectAttr -f .scaleX .scaleY;
connectAttr -f .scaleX .scaleZ;
move -r 0 0 7;
string $sphe[] = `sphere -p 0 0 0 -ax 0 1 0 -ssw 0 -esw 360 -r 1 -d 3 -ut 0 -tol 0.01 -s 6 -nsp 6 -ch 1`;
rename $sphe[0] "affected3";
connectAttr -f .scaleX .scaleY;
connectAttr -f .scaleX .scaleZ;
string $sphe[] = `sphere -p 0 0 0 -ax 0 1 0 -ssw 0 -esw 360 -r 1 -d 3 -ut 0 -tol 0.01 -s 6 -nsp 6 -ch 1`;
rename $sphe[0] "affected4";
connectAttr -f .scaleX .scaleY;
connectAttr -f .scaleX .scaleZ;
move -r -7 0 0;
string $sphe[] = `sphere -p 0 0 0 -ax 0 1 0 -ssw 0 -esw 360 -r 1 -d 3 -ut 0 -tol 0.01 -s 6 -nsp 6 -ch 1`;
rename $sphe[0] "affected5";
connectAttr -f .scaleX .scaleY;
connectAttr -f .scaleX .scaleZ;
move -r 7 0 0;
string $loc[] = `spaceLocator -p 0 0 0`;
rename $loc[0] "main";
connectAttr -f .scaleX .scaleY;
connectAttr -f .scaleX .scaleZ;
move -r 0 3 0 ;
expression -s "vector $main = <<main.translateX,main.translateY,main.translateZ>>;\r\n\r\nvector $loc1 = <<affected1.translateX,affected1.translateY,affected1.translateZ>>;\r\nvector $loc2 = <<affected2.translateX,affected2.translateY,affected2.translateZ>>;\r\nvector $loc3 = <<affected3.translateX,affected3.translateY,affected3.translateZ>>;\r\nvector $loc4 = <<affected4.translateX,affected4.translateY,affected4.translateZ>>;\r\nvector $loc5 = <<affected5.translateX,affected5.translateY,affected5.translateZ>>;\r\n\r\naffected1.scaleX = (`linstep 1 20 (mag($main-$loc1))`*3);\r\naffected2.scaleX = (`linstep 1 20 (mag($main-$loc2))`*3);\r\naffected3.scaleX = (`linstep 1 20 (mag($main-$loc3))`*3);\r\naffected4.scaleX = (`linstep 1 20 (mag($main-$loc4))`*3);\r\naffected5.scaleX = (`linstep 1 20 (mag($main-$loc5))`*3);" -o "" -ae 1 -uc all;

08 August 2007, 06:01 PM
Thank you Tim and Derek for your ideas. I will try connecting the sphere's attributes via the connection editor and avoid the expression and see if it makes a difference performance wise. Tim, your solution is very clever, and faster that my initial expression. Thanks man.

I wanted to share with you the result of some tests I have done. I created a version where I have a distance between node and a clamp, a multiply-devide, etc.

I created 1000 spheres, with 1000 shading networks that controlled their scale properties. These networks were connected to 1000 locators (all at the same position). Each locator controlled one sphere via distances between utility nodes. This got an average 7.7 fps playback and was very good at interactive playback. I switched it to where there was 1 locator controlling the spheres. This was faster, at 9.6 fps.

I also modified the script where instead of all the code residing in one expression I had it split into 1000 expressions, and this has been the fastest yet, at 10.7 fps. As far as I can tell, having 1000 expressions for some reason is much much faster that having all the code in one expression (the slowest of all of my tests). I will conduct some more tests see what happens. Thanks.


PS: I think that I should mention that I have a dual Xeon (3.60s) running Maya on XP 32. Tim, I get an avg. of 2.1 fps using the python plugin approach.

CGTalk Moderation
08 August 2007, 06:01 PM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.