View Full Version : Beginner Q: Script takes ages to run, wondering whether to learn some C++


Psyked
08 August 2008, 07:22 AM
Hi, I've been working through some of David Gould's excellent books (Vol 2 coming in really handy with poly specific code), and I'm attempting to write some basic greebling scripts.

I've got some basic code working, but for even moderately complex results (with > 1000 extrude operations) the script takes *ages* to run. I think this cube (http://psytek.highlyillogical.org/maya/greeble_cube.jpg) took maybe 40 minutes or so, these 'buildings' (http://psytek.highlyillogical.org/maya/30_buildings.jpg) taking maybe a minute or 2 each. (I'm running a Core2Duo E6600 @ 2.4GHz with 4GB RAM, though it doesn't seem to ever maximise cpu usage when running mel scripts even though it does rendering)

My process is:
create a poly
step through all faces, extruding to 0.9 scale in x and y, and then subdividing this new face into 5x5 faces or similar.
Step through each of the new faces, checking if each one is square (to prevent extruding the non square faces generated by estruding the face in), and if so, extruding it a random amount and number of times.

I think the bottleneck is checking whether a face is square or not. I'm doing this by retreiving the face vertices, picking 3 concurrent vertices, and working out the angle between them. If it's 90 degrees, then I assume it's square (I know I should check 3 angles but this works for the time being as a shortcut).

From this, I'm wondering if learning C++ would be a logical next step, or will I just end up calling MEL commands anyway, in which case I can only assume it will go at the same speed?

My second question is the process I'm using above, and whether it seems logical? Perhaps there's a custom 'is it square' routine I could already use.

I've had a look through the forums but couldn't see anything similar, so forgive me if this has been covered.

Thanks in advance!

RhettAllen
08 August 2008, 10:20 AM
I don't understand why you would want to step through all faces to extrude them the same amount. Why not select all faces at once and extrude them at once with whatever scale and distance?

I don't understand why you are checking for non-square faces. What is it you are trying to accomplish?

You could alternately learn python. You could start within maya converting mel to python and then dive into the API once you're comfortable. You can do everything in API without using any mel commands and yes it would be much faster as long as you knew what you are doing.

kojala
08 August 2008, 10:46 AM
there might be somethin wrong with your script cos its takign so long..
have you turned off the undo queue for the time of the script? this might
slow it down a lot.
I see why you dont want to extrude non square faces.. there might be a faster way

NaughtyNathan
08 August 2008, 11:09 AM
yeah, undo queue will affect the speed a lot, as will construction history.. make sure you remove the objects history before you start, and use the "-ch off" flag for all your commands so none is created. This should increase run speed a massive amount.

but, like kojala, I'm guessing that your code may not be that optimal too! ;)

:nathaN

Psyked
08 August 2008, 11:41 AM
I don't understand why you would want to step through all faces to extrude them the same amount. Why not select all faces at once and extrude them at once with whatever scale and distance?

I don't understand why you are checking for non-square faces. What is it you are trying to accomplish?

You could alternately learn python. You could start within maya converting mel to python and then dive into the API once you're comfortable. You can do everything in API without using any mel commands and yes it would be much faster as long as you knew what you are doing.

Well the actual greebling, which I suspect is taking the time, is extruding all faces a random amount. It is only the initial faces that I extrude all the same amount (so, on a polyCube this is just the initial 6 faces). Though I see that I could do this to all the 6 faces simultaneously - I'll try this later.

The reason I only want to extrude sqaure faces is to prevent extruding the non-square faces around the edge - see here http://psytek.highlyillogical.org/maya/8%20by%209%20wireframe.jpg

all faces, except non square ones, are extruded in this image. This is really just a personal thing; I only want to end up with square extrusions, and non odd shaped ones.

The reason I'm extruding the 6 faces in to begin with is that subdividing each face causes problems if they're not extruded first as the new edge vertices generated by dividing the face, interferes with the adjacent face when subdividing it. I guess I could set up the subdivisions at creation time, but I plan on running this script on existing, more complex polys, and randomly subdividing faces up before extruding them.

I'll try that "-ch off" tip, cheers NaughtyNathan - I think I use it one command at the moment, but will try adding it to all the extrude\scale operations I'm doing.

Also, GravidEngine, thats good to know that doing this via the API wouldn't mean calling all these MEL commands, that's what I was hoping!

One annoying thing is the amount of built in functions that return a string of answers, rather than a array of points or similar - means I've got lots of code converting a float to a string and then to an int - I'm sure some of these bits could be optimised. Case in point, to generate a random integer, I'm generating a random number as a float, convert to a string, use substring to get the first digit (or 2) , before the decimal point and then assign this to an int!

Anyway, thanks for pointers, I've got a few more things to try now :)

My code is as follows (and I admit it's probably wildly unoptimised!)

/////////////////////////////////////////////
// Greebler 3
//
// - Creates a cube of random size
// - Extrudes and subdivides each face
// - randomly extrudes some of the faces after checking if they are square

// Script variables
global float $greebleFrequency = 0.2; // Probability any given face will be greebled (between 0 and 1)
global int $greebleMinDepth = 2; // On faces to be greebled, the minimum greebling depth
global int $greebleMaxDepth = 8; // On faces to be greebled, the maximum greebling depth
global float $greebleMinHeight = 0.01;
global float $greebleMaxHeight = 0.06;
global float $greebleMinX = 0.4;
global float $greebleMaxX = 0.9;
global float $greebleMinY = 0.4;
global float $greebleMaxY = 0.9;
global int $divisionsU = 10;
global int $divisionsV = 10;

// Create the foundation cube
proc createGreeblePoly()
{
if ( `objExists greeblePoly` )
{
select -r greeblePoly;
doDelete;
}
float $randomBaseWidth = `rand 2 3`;
float $randomBaseDepth = `rand 2 3`;
float $randomBaseHeight = `rand 2 3`;

polyCube -n greeblePoly -w $randomBaseWidth -d $randomBaseDepth -h $randomBaseHeight;
}

// Extrude each face in by small amount, and subdivide the extruded face
proc extrudeAndSubdivide() {
global int $divisionsU;
global int $divisionsV;
select -r greeblePoly;
$initialFaceCountArray = `polyEvaluate -face`; // retrieve array of informaiton about the selected poly
int $initialFaceCount = $initialFaceCountArray[0]; // retrieve number of poly faces from info array
// Go through each face in turn, extruding once and then subdividing.
// We extrude the face first to prevent any edge vertices inteferring when we subdivide
for ( $foo = 0; $foo < $initialFaceCount; $foo = $foo + 1 ) {
select greeblePoly.f[$foo];
polyExtrudeFacet -lsx 0.8 -lsy 0.8;
polySubdivideFacet -duv $divisionsU -dvv $divisionsV -sbm 1 -ch 1;
}
}

// Given a face id, this proc returns true (1) if the face is square
proc int isItSquare( int $faceId ) {
int $result;
select -r greeblePoly.f[$faceId];
string $faceVertexInfo[] = `polyInfo -fv`;
string $vertexArray[];
tokenize $faceVertexInfo[0] $vertexArray;

// get the id's of three consecutive vertices
int $vertex1Id = $vertexArray[2];
int $vertex2Id = $vertexArray[3];
int $vertex3Id = $vertexArray[4];

// Retrieve their coordinates
vector $vertex1 = `pointPosition -world greeblePoly.vtx[$vertex1Id]`;
vector $vertex2 = `pointPosition -world greeblePoly.vtx[$vertex2Id]`;
vector $vertex3 = `pointPosition -world greeblePoly.vtx[$vertex3Id]`;

// Our two vectors run from vtx 2 -> vtx 1 and vtx 2 -> vtx 3.
// We'll minus vtx2 from both 1 and 3 so we can treat them as vectors and not just coordinates space
// as they're now translated as if vtx 1 is 0,0,0
$vertex1 = ( $vertex1 - $vertex2 );
$vertex3 = ( $vertex3 - $vertex2 );

// Work out the angle
float $cornerAngleTemp = ( rad_to_deg( angle( $vertex1, $vertex3 ) ) );
string $cornerAngle = $cornerAngleTemp; // convert it to a string
// and retrieve the first 2 digits (before the decimal point)
// This is to prevent floating point answers like 90.00000001 resulting from lack of floating point accuracy
$cornerAngle = `substring $cornerAngle 1 2`;
tolerance -l 0.001;
int $result;
if ( $cornerAngle == 90 )
$result = 1;
else
$result = 0;
return $result;
}


// ****************************************************
// Proc: greeble
// Steps through each face on the selected poly
// extruding a given number, but only if they are square, or rectangular
// ****************************************************
proc greeble() {
global float $greebleFrequency;
global int $greebleMinDepth;
global int $greebleMaxDepth;
global float $greebleMinHeight;
global float $greebleMaxHeight;
global float $greebleMinX;
global float $greebleMaxX;
global float $greebleMinY;
global float $greebleMaxY;

select -r greeblePoly;
$initialFaceCountArray = `polyEvaluate -face`; // retrieve array of informaiton about the selected poly
int $initialFaceCount = $initialFaceCountArray[0]; // retrieve number of poly faces from info array
progressWindow -minValue 0 -maxValue $initialFaceCount -title "Greeble progress" -status "Working...";
for ( $foo = 0; $foo < $initialFaceCount; $foo = $foo + 1 ) {
select greeblePoly.f[$foo];
if ( isItSquare($foo) == 1 ) { // Ie is the face square or not. Going purely by one angle
float $greeble = rand ( 0, 1 );
if ( $greeble > $greebleFrequency ) { // if greeble is greater than the greeble frequency, lets greeble
// Now work out how many greebles to do
$greebleDepthTemp = rand ( $greebleMinDepth, $greebleMaxDepth );
string $greebleString = $greebleDepthTemp;
string $greebleString = `substring $greebleString 1 2`;
int $greebleDepth = $greebleString; // The number of greebles for this face
// $greebleDepth = rand ( $greebleMinDepth, $greebleMaxDepth );
for ( $bar = 1; $bar <= $greebleDepth; $bar += 1 ) { //
// Generate a random number to choose with greebling routine to use
$greebleRoutineTemp = rand ( 1, 6 );
string $greebleRoutineString = $greebleRoutineTemp;
$greebleRoutineString = `substring $greebleRoutineString 1 4`;
int $greebleRoutine = $greebleRoutineString;
// Work out the distanes for these greebles
$greebleScaleX = rand ( $greebleMinX, $greebleMaxX );
$greebleScaleY = rand ( $greebleMinY, $greebleMaxY );
$greebleHeight = rand ( $greebleMinHeight, $greebleMaxHeight );
// Now to pick a greebling sub routine
switch( $greebleRoutine ) {
case 11:
polyExtrudeFacet -ltz $greebleHeight -lsx $greebleScaleX -lsy $greebleScaleY;
break;
case 2:
polyExtrudeFacet -lsx $greebleScaleX -lsy $greebleScaleY;
polyExtrudeFacet -ltz $greebleHeight;
break;
case 31:
$greebleHeight = ( $greebleHeight * -1 );
polyExtrudeFacet -ltz $greebleHeight -lsx $greebleScaleX -lsy $greebleScaleY;
break;
case 4:
$greebleHeight = ( $greebleHeight * -1 );
polyExtrudeFacet -lsx $greebleScaleX -lsy $greebleScaleY;
polyExtrudeFacet -ltz $greebleHeight;
break;
case 5:
polyExtrudeFacet -ltz $greebleHeight;
break;
case 6:
$greebleHeight = ( $greebleHeight * -1 );
polyExtrudeFacet -ltz $greebleHeight;
break;
}


}
}
}
progressWindow -edit -progress $foo -status ("Working...");
}
progressWindow -endProgress;
flushUndo;
select -r greeblePoly;
DeleteHistory;
}




createGreeblePoly;
extrudeAndSubdivide;
greeble;
select -r greeblePoly;

I'll start looking at Python this weekend I think, as this would be an easier, in between step, on the way to learning C++!

goleafsgo
08 August 2008, 12:58 PM
Turning off construction history and the undo queue might not speed things up as fast as you might think.

The biggest boost that you'll probably get is to make sure you batch extrudes together as much as possible. i.e. executing 500 extrude commands with 1 face each will be a lot slower then executing 1 extrude command while passing it 500 faces.

ThE_JacO
08 August 2008, 01:45 PM
C++ is not always going to be the best bang for buck in terms of optimization (although it's guaranteed to produce faster results than MEL), and the learning curve might be a bit steep.
Also there's a lot about programming that you might find easier to learn on a scripted language than while also struggling with all the issues C++ comes bundled with (not to mention the API is very different from MEL's).

There are several things that affect performance at execution time in a script like yours that you could work on (keeping it simple):
-Fetching and marshalling data
-Maths and algorithmic solutions that process the data
-Execution overhead

All can be optimized differently.
Performance tweaking is an art in its own, and its largely tied to research and experience.
It's useful to try and break down the problem and figure out where you can get the biggest speed boost with the least work, and to do that you might want to execute different parts of your tool separately and optimize/tweak each one first.
Once that is done you have to figure out if the best solution for each is also compatible with the best of the other part (IE: some algorithmic solutions can go through a hash table at the speed of light, but hashing your data would take longer at marshalling/formatting time than you gain speed by having an hash table).

I haven't read the scripts, but here's a few ideas:
Fetch all your data, and before you work on it create subsets to work on.
If your script first check and then operates on each face you're wasting a lot of time.
Execution wise, since Maya does the extrusion work for you, it's a lot faster to run one extrude on N polys than it is to run N extrudes for each poly at once.
Collect data, sort it, exclude what you don't want to work on, process. It's a smarter way to do it than bruteforcing the same execution for each step of the loop

Algorithm wise I don't know how you're checking your angles, but it sounds like you're running needlessly expensive computation, but I'm assuming here.
I'm also assuming you don't mean square as much as orthogonal quadrilaterals, otherwise, if you really mean square, you also need to check that the length of the sides is the same.

If you're using trigonometry for angles and all, you can use linear algebra more efficiently.

Build vectors from the sides, check that two opposite ones are parallel running a dot product (absolute needs to be 1), and then check either against the two sides left, and make sure the dot product is 0 (orthogonal).
This is most likely going to be faster than comparing angles (which runs a dot product internally and then compares it against a inv cosine table) if you're using linear algebra, and it's definitely going to smoke anything you can get out of trigonometry if you were doing it the hard way.
It also makes it a lot easier to implement tolerance, which at some point you'll find important to deal with rounding errors, or to simply be able to greeble non 100% guaranteed meshes.

If the datasets are extremely large there might also be an argument to be made to operate differently, like building a lookup table of neighborhood and checking for edge paths, but that might become overly complicated for a first go at learning optimization.

I'm not advocating NOT learning C++, it's very valuable knowledge to have, but maybe you would benefit the most first from working a bit on your logic, maths, research and implementation skills a bit, I think you'll get a lot more mileage out of those.

Basically you now have a working prototype, and you need to re-factor :)

RhettAllen
08 August 2008, 05:37 PM
do you want this only for making a crazy extruded cube or do you plan to use it on anything besides cubes?

Because if you only want it for making this crazy kind of cube thing then you make your script run WAY faster. Tell me which and I'll respond accordingly

Psyked
08 August 2008, 07:21 AM
do you want this only for making a crazy extruded cube or do you plan to use it on anything besides cubes?

Because if you only want it for making this crazy kind of cube thing then you make your script run WAY faster. Tell me which and I'll respond accordingly

Crazy extruded cubes is what I'm interested in at the moment. Way faster sounds good :)

Long term I'd like to adapt it to other shapes, but this is all learning at the moment.

RhettAllen
08 August 2008, 09:48 AM
well even if you adapt it to other shapes my method should work. As long as you are creating a very defined pattern that doesn't change much from object to object.

Because, you should understand that maya creates new faces on an object in a very specific order thus the numbers are in a very specific order.

Say I have a cube and smooth it. I can write a very simple script that will always select the top right hand face.

select -cl;
for($x = 0; $x<6; $x++)
{
select -add polyCube1.f[`getAttr polySmooth1.subdivisionLevels`*$x];
}

now I don't have maya open so I'm not sure if the script is without error and I don't know exactly what face it will select, but my point is that whatever face it selects, it will select that face on all sides once smoothed.

So if you need to see it in action. Run my script (after debugging it) after each smooth operation (you may need to turn continuity off to see it better) to see what happens.

So once you figure out Maya's pattern for what you are doing. You can simply write a few loops that will go through and select the faces you need and then you can extrude them all at once.

Hope you understand it.

Good luck

NaughtyNathan
08 August 2008, 11:04 AM
Turning off construction history and the undo queue might not speed things up as fast as you might think.

The biggest boost that you'll probably get is to make sure you batch extrudes together as much as possible. i.e. executing 500 extrude commands with 1 face each will be a lot slower then executing 1 extrude command while passing it 500 faces.

he wants each face to have a different random extrusion. he can't realistically batch extrude faces, unless he doesn't mind several faces having the same extrusion, and even so this would have to be a quite small number of each... If the extrude's random attribute worked "properly" this would help, but it doesn't...

I turned off undo and construction history without optimizing any other parts of the original script and it went down from 46 seconds (for a 4x4 div cube) to 2.09 seconds...
when I tried it on an 8x8 cube the progress bar got to 75% in 21 minutes and then my Maya crashed. When I relaunched it and turned off history and undo it did an 8x8 cube in 22.06 seconds.

I'd say that was a damn spectacular speed/performance increase...

:nathaN

Psyked
08 August 2008, 11:41 AM
...

I'd say that was a damn spectacular speed/performance increase...

:nathaN

Crikey, that is a spectacular increase! Amazing what a change an extra couple of bits of code can do. I'll surely try this out this evening when I'm back at home (No internet at home at the moment). I've made my computer grind to a halt a number of times if trying cubes anything more than 7 x 7. Thanks muchly

goleafsgo
08 August 2008, 02:17 PM
he wants each face to have a different random extrusion. he can't realistically batch extrude faces, unless he doesn't mind several faces having the same extrusion, and even so this would have to be a quite small number of each... If the extrude's random attribute worked "properly" this would help, but it doesn't...

I turned off undo and construction history without optimizing any other parts of the original script and it went down from 46 seconds (for a 4x4 div cube) to 2.09 seconds...
when I tried it on an 8x8 cube the progress bar got to 75% in 21 minutes and then my Maya crashed. When I relaunched it and turned off history and undo it did an 8x8 cube in 22.06 seconds.

I'd say that was a damn spectacular speed/performance increase...

:nathaN
Well I did say "might" not speed things up as fast as you might think :-)

Actually, I saw something exactly like this not long ago where someone was choosing a random face on a cube and doing an extrude...over and over and over again...in order to come up with something similar to this. It was taking a long time even though they had turned off the undo queue and deleted history. In their example they could get the same look by extruding something like 300 random faces a few times instead of a thousand random faces. i.e. 3 extrude calls with 300'ish faces instead of 1000 extrude calls with 1 face. So by batching the calls they went from minutes to 0.1 seconds...

kojala
08 August 2008, 05:48 AM
here are a fiew tips for your script:

1. you keep selecting every face that you extrude and work with.. not efficient.
try like this. and I never use straight object names.. maya keeps changing them sometimes: polyExtrudeFacet -lsx 0.8 -lsy 0.8 ($my_poly + ".f[" + $foo + "]");

2. once I made a script that got randomly faces from an object and the fastest way was to use polySelectConstraints. there is a built in random tool.. well the object that I used had over 100000 polys so Im not sure If you have to be that fast.

3. Im using this command in the begining of the script: undoInfo -stateWithoutFlush 0;
and this in the end: undoInfo -stateWithoutFlush 1;


(http://forums.cgsociety.org/) 4. and: delete -ch $my_poly; is the straight command. deleteHistory is a mel command that links to this

CGTalk Moderation
08 August 2008, 05:48 AM
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.


1