What follows is a five-part introduction to MEL, the 'Maya Embedded Language'. MEL is *not* object-oriented, it is pretty simple to learn.
Knowing MEL, maya.cmds *and* PyMEL will make you quite valuable in the world of CG production :)
Here is how the topics are organized into small subtopics:
MEL characteristics:
/* This is a comment that spans multiple lines. Eg. you can include a para about how an algorithm (that follows these comment lines) works. You can also use this style of commenting to create a block of 'attribution' data - wrote the code, when, where, what the code does etc. */ // this is a one liner
Throughout these pages of notes, what's shown against a light background is MEL code which you can copy and paste verbatim into your script ed. window, eg:
sphere; // create a NURBS sphere..
Variables form the “currency” of modern programming:
Here is a way to picture a variable:
Exactly five built-in variable types in MEL:
Of the above five types, only int, float, string and vector types can be arrays (see below). So MEL has a total of 9 built-in variable types: 5 'primitive' ones, 4 array ones. NO MORE! In other words, new var. types CANNOT be added (no 'structs' or 'classes').
int $i; int $j=5; int $numCams = 24;
When we assign a (new) value to a var, that value is what is stored:
int $k, $l; int $i = 4.765; // will be truncated (NOT rounded) to 4
float $f1; float $f1a, $f1b; float $f2 = 6.65; float $f3 = 1/3; // $f3 will be 0, NOT 0.3333333 float $f4 = 1.0/3; // will be 0.333333333 as expected
string $a; string $fnm = "Los "; string $lnm ="Angeles"; // concatenation; also note the use of a space character string $fullName = $fnm + " " + $lnm;
vector $v1 = <<1.0,0.56,-0.96>>; print($v1.x); // NEED the () $v1 = <<$v1.x,$v1.y,10.0>>; // NEED to reset all values, can't just set $v1.z [see below] $v1.z = 10.0; // NOT allowed, syntax error
matrix $n[2][2]; matrix $i2[2][3] = <<1,2,3;-6,.78,.45>>; // 2 rows,3 columns. Note the use of ';' to separate rows $j2 = $i2; // $j2 is another matrix created with same contents as $i2 [copied over] $k2 = -$i2; // $k2 is a new matrix with values from $i2 NEGATED and copied over
Facts about matrix vars:
Deformations such as twist, taper and bend can be expressed in the form of matrices. Every vertex/CV of a shape can then be multiplied with such matrices to yield deformed shapes.
int $a2[]; int $b2[100]; // here's how to initialize an array [specify all values while creating the variable] float $flArr[6]= {0.8,-.6,1.,14.,12.3,-7.6}; clear $b2; // empty out contents, size goes to 0 $b2[9999] = 1969; // this assignment will grow the size to 10000
Operators come in three categories/'flavors' - arithmetic, comparison, logical [this is in addition to '=' (assignment) and ',' (comma) which are also operators].
+ - * / % += -= *= /= ++ [equivalent to +=1] -- [equivalent to -=1] Note: there's no [need for a] '**' or '//' operator!
Variables to the left of an '=' sign RECEIVE values, ie are written into. Vars to the right behave as "read only", ie their values are accessed and used in calculations, but are not modified/written into.
These express relationships between two variables' contents (ie. values they contain).
== != > < >= <=
// The result of a comparison is captured in an int variable. Eg. int $areTheyIdentical = ($a==$b); // are 'a' and 'b' identical?
The three 'logical' operators AND (&&), OR (||) and NOT (!) implement "Boolean" logic. These are HEAVILY used in loops and branches to express program logic, so you do need to understand these very, very well! Without logical operators, modern computer programming simply cannot exist.
In the following "truth tables", A and B are 'tests' (questions) where their answer is always a yes (1) or no (0). So are the results of using A and B along with the logical operators..
A B A_AND_B 0 0 0 1 0 0 0 1 0 1 1 1 "A AND B" is true *ONLY IF* A is true and also B is true.
A B A_OR_B 0 0 0 1 0 1 0 1 1 1 1 1 "A OR B" is true *IF EITHER* A is true or B is true (or both are true).
A NOT_A 0 1 1 0 'NOT A' is the opposite of A (duh!!).
Interestingly, as per "de Morgan's laws", we can use the pair (AND, NOT) to replace OR [likewise, the pair (OR, NOT) to replace AND]:
'A OR B' identity (OR is expressed purely in terms of AND, NOT): NOT(NOT(A) AND NOT(B)) = NOT(NOT(A)) OR NOT(NOT(B)) = A OR B 'A AND B' identity (AND is expressed purely in terms of OR, NOT): NOT(NOT(A) OR NOT(B)) = NOT(NOT(A)) AND NOT(NOT(B)) = A AND B
In the above, A and B are tests, eg. A can be ($a==$c) and B can be ($a>$d). Using these as examples, the 'A AND B' identity above means this:
int $b; $b = ($a==$c) && ($a>$d); // A AND B $b = !( !($a==$c) || !($a>$d) ); // NOT( NOT(A) || NOT(B) )
If you create variables $a, $b, $c, $d, provide values for $a, $c and $d, then run the above two MEL expressions, you'll find that $b comes out to be the same in both cases (both will come out 1 or come out 0). Doing verifies the 'A AND B' identity for us.
So if the && operator is banished from MEL (or from all programming languages in the world!) we can make do with ||. Likewise, if || goes away, we can use && instead. But ! CANNOT go away, it is ESSENTIAL. In other words "you cannot not use NOT!".
Exercise 1: use truth tables to prove the above two identities.
Exercise 2: verify the 'A OR B' identity above, using MEL, just like we did for 'A AND B'.
The &&, II and ! operators are related to logical circuits (the fundamental building blocks of computation!) and to set theory.
Here are the three logic "gates" [regular and IEC (International Electrotechnical Commission) symbols, truth table]:
Likewise, these are the set operations:
For sets A and B (circles), their 'intersection' (shown in red) is equivalent to && - elements in the red area are in A *and* in B.
The || operator is equivalent to set 'union' - an element can be in A *or* in B to be in the union set.
The ! operator is like a set's complement (inversion) - it denotes elements (in gray) *not* in the set.
Expressions make use of operators and variables, to form parts of statements..
In other words, a whole programming "sentence" is a 'statement'; a "phrase", an 'expression'. To put it differently, statements are comprised of expressions.
Here's an assortment of statements - pick out the expression(s) in each:
// overall evaluation happens right to left int $i = $j + $k; float $f = 2.718; int $ang = 375 % 360; // modulus int $k = ($j == $r); // always 0 or 1 int $v = ( ($f == $a) && ($g < $s) ); $i = 45; // OK 45 = $i; // *not* OK! $i = ($f==3.1415); // OK $i = (3.1415==$f); // also OK
Flow constructs:
Flow constructs help BREAK UP THE LINEARITY of statements/logic!
The three flow-related constructs are:
Due to flow control constructs, we need a 2D surface (not just a line) to express program logic using diagrams.
Two (three) ways:
Three variations:
// if ($a==6)$k=5;else $k=556; int $k = ($a==6)?5:556;
"Should I stay or should I go?"
Courtesy of Eric Keller:
if ($go) { $trouble = 1; } else { $trouble = $trouble*2; }
Another powerful, elegant way to manipulate program flow is recursion, which MEL supports. Here's an example:
// first, a non-recursive version of factorial: proc int iterativeFactorial(int $n) { int $result = 1, $i; for($i=1;$i<=$n;$i++) { $result *= $i; } return $result; }// iterativeFactorial(() // run the non-recursive (loop-based) version: int $rslt = iterativeFactorial(6); // next, a 'recursive' version - it's called that because we use // recursiveFactorial() // in its own definition, ie. we are // explaining/defining something by "using itself" to do so proc int recursiveFactorial(int $n) { if($n==0) return 1; // 0!=1, by definition else return ($n*recursiveFactorial($n-1)); // recursion!! }// recursiveFactorial(() // run the recursive version int $rslt = recursiveFactorial(6);
Recursion is very good for creating certain kinds of snowflake curves, tiling patterns, cloud/fireball shapes, plants/trees, etc.
To recap, ‘flow’ refers to flow of program statements, which really reflect flow of logic.
Before you write a single line of MEL, make sure that you can write your program out in pseudocode on index cards, run through the main parts in your head and explain it to your non-technical neighbor.
DESIGN, **then** CODE. Coding is the “easy” part..
// "horn" int $theta; for($theta=0;$theta<360;$theta+=10) { float $r=1.0; float $x = $r*cos($theta*3.14159278/180); float $y = $r*sin($theta*3.14159278/180); sphere -p $x $y 0.0 -r (.05+($theta*.001)); }
// grid of spheres for($i=0;$i<5;$i++) { for($j=0;$j<5;$j++) { sphere -r .1 -p $i $j 0.0; } }
// box of spheres proc boxOfSpheres(int $nS, float $rMin, float $rMax,int $size) { int $i; for($i=0;$i<$nS;$i++) { float $x = rand(-$size,$size); float $y = rand(-$size,$size); float $z = rand(-$size,$size); float $r = rand($rMin,$rMax); print("rad is " + $r + "\n"); sphere -p $x $y $z -r $r; } }// boxOfSpheres // usage: boxOfSpheres(100,1,2,10);
// spiral phyllotaxis float $goldenAngle = 137.5*3.14159278/180.0; int $n=500; float $c=1, $seedRad=1; for($i=1;$i<=$n;$i++) { float $r = $c*sqrt($i); float $a = $goldenAngle*$i; float $x=$r*cos($a), $y=$r*sin($a); cylinder -ax 0 0 1 -hr 2 -d 3 -r $seedRad; // -s 8 -nsp 1 move $x $y 0; }// next $i
From now on, start thinking about programming problems in terms of inputs and outputs, NOT in terms of variables, for() loops, etc. In other words, think in terms of your problem, not in terms of programming language constructs.
Putting it another way, programming is not as easy as ABCD, but it *is* as easy as BDCA:
Specifically, design before you start coding. This advice holds for any language/OS, any software development project. The idea is to not get caught up in details right at the start. "Big picture first", "top down programming", "divide and conquer", "don't lose the forest for the trees" - all these phrases are other ways of saying the same thing.
In MEL you can create well-structured programs by using a construct called a 'proc'. Procedure, function, subroutine, method - these are names used to denote the same concept in other programming languages.
Notes on MEL procs:
A proc is like a new 'word' that needs to be 'taught' to Maya, the same way a child is taught the meaning of a new word. So a proc needs to be created/defined/declared to Maya before it can be used/called/executed. The declaration happens just once, and from then on, the calling can happen any number of times. It is an error to use a proc before defining it, just like it doesn't make sense in a long novel to start talking about a character at length before the character has been introduced to the reader.
MEL programming can be summarized as 'writing procs that communicate with each other via their inputs and outputs'. In the process, useful things happen to/in your scene (things get created, deleted, modified).
// proc syntax // things in square brackets (INCLUDING the square brackets!) are OPTIONAL, // ie. you can leave them out if necessary [global] proc [return_type] proc_name ([args]) { [MEL stmts] }
// the SIMPLEST MEL proc possible - NO inputs, no output, no code inside proc doNothing() { } // a proc that is a 'wrapper' for the built-in 'sphere' command proc mkSph() { sphere; // NOT an output but a 'side effect' }
proc printHi() { print("Hello MEL world!\n"); // NOT an output, another side effect }
// With inputs, but no output proc addAandB(int $a, int $b) { int $sum = $a+$b; print("Sum: " + $sum + "\n"); // again, NOT an output }
// Full-blown case: inputs AND output proc int addAandB(int $a, int $b) { int $sum = $a+$b; print("Sum: " + $sum + "\n"); // NOT an output return $sum; // THIS is what constitutes output }
// again, has input AND output proc string printAge(int $howOld) { string $rslt; $rslt = "You are " + $howOld + " years old\n"; return $rslt; // note - we *could* combine all three stmts here into just one [how?!] }// printAge()
A proc can have any NUMBER and TYPE (from the 9 MEL types) of inputs, but ONLY ONE output (again from the 9 MEL types). The aim of the proc is to compute that output to return. What to do with output (return value)? CAPTURE IT, ie. store it in our own variable. That variable can now input to ANOTHER proc, etc. Output of one proc can become an input, even for multiple procs!
// output is lost (we're not receiving and storing it in a variable) printAge(40); // compared to above, output is captured (stored) into our own var called $myAge string $myAge = printAge(36); // ALTERNATE syntax to capture a proc's result: string $myAge = `printAge 36`;
You can either use the () notation or the ` ` notation to capture the result (output) of a proc, they are totally equivalent in terms of what happens.
// define another proc, to "wrap" print() into a useful debugging proc proc prtStr(string $s) { print("Debug: " + $s + "\n"); } // output from `printAge 36` captured in // string $myAge = `printAge 36` // becomes new input to prtStr. string $myAge = `printAge 36`; // output from `printAge 36`is captured and stored in $myAge prtStr($myAge); // $myAge becomes an input to prtStr (which itself has no output) // alternative to above , without using the $myAge variable: prtStr(`printAge 36`); // or equivalently, prtStr(printAge(36));
Summary: Chaining (making one proc's output to be another's input) is what it's *ALL* about! Write useful, reusable units of code (procs) with good inputs and outputs, then hook them up :) This is how programming gets done in every prog. language.
You should always strive to make your procs more lucid/efficient/compact etc., while keeping the input/output "signature" (input TYPES in their proper sequence, and output TYPE) unaltered. Doing so is called "code refactoring".
One example is a program to add first N natural numbers, ie. 1+2+3+...N. Two ways to do this:
// Try the following example using the brute force method as // well as Gauss' formula (both given below), and compare running times: // sumOfFirstNNaturalNumbers 1007785; // Brute force method global proc int sumOfFirstNNaturalNumbers(int $n) { int $sum = 0, $i; float $startTime = `timerX`; for($i=1;$i<=$n;$i++) $sum += $i; // timeElapsed will receive the diff. between current time and // the stored 'startTime' value.. float $timeElapsed = `timerX -startTime $startTime`; print ("Time taken: "+$timeElapsed + "\n"); return $sum; } // Gauss' 'constant time' method global proc int sumOfFirstNNaturalNumbers(int $n) { float $startTime = `timerX`; int $sum = (0.5*($n)*($n+1)); // NO NEED FOR A LOOP!! float $timeElapsed = `timerX -startTime $startTime`; print ("Time taken: "+$timeElapsed + "\n"); return $sum; }
In the above, the for() loop version takes longer and longer as the input number gets bigger. In contrast, Gauss' clever formula takes the SAME, FIXED amount of time, regardless of input size!
Another example is an 'add4Nums()' proc which will add four floats that are input to it. Assuming that an 'add2Nums' proc is known to MEL, we can successively refine our approach like so:
// Make sure we have this first: proc float add2Nums(float $a, float $b) { return ($a+$b); }// add2Nums // Now we can start writing add4Nums() // version 0 proc float add4Nums(float $a, float $b, float $c, float $d) { float $sum = $a + $b + $c + $d; return $sum; } // Eg.: // add4Nums 0.1 0.2 -.2 -.1; // version 0.5 proc float add4Nums(float $a, float $b, float $c, float $d) { return ($a + $b + $c + $d); // no need for a $sum variable } // version 1 proc float add4Nums(float $a, float $b, float $c, float $d) { float $sum1 = add2Nums($a,$b); // CALL another proc! float $sum2 = add2Nums($c,$d); // CALL another proc! return ($sum1+$sum2); } // version 2 proc float add4Nums(float $a, float $b, float $c, float $d) { float $sum1 = add2Nums($a,$b); // CALL another proc! float $sum2 = add2Nums($c,$d); // CALL another proc! return add2Nums($sum1,$sum2); // no '+' sign anywhere in this proc :) }// add4Nums // version 3 - the MOST COMPACT way to do this! proc float add4Nums(float $a, float $b, float $c, float $d) { return add2Nums(add2Nums($a,$b),add2Nums($c,$d)); // uses "function call chaining" }
MEL scripts are simply text files that contain MEL code (usually inside procs). We place code in files on disk, in order to avoid typing it all in each time we restart Maya.
In order for Maya to automagically run your proc from a file ("MEL script"), follow these steps:
To run a MEL proc. from an arbitrary location (eg. Desktop, C:/temp etc. on a PC), use the 'source' directive (the same command mentioned above, to refresh a proc in Maya after you've changed it on disk).
Now that you know about procs calling other procs, the 'timer' code from the 'sumOfFirstNNaturalNumbers' proc above (either version) can be abstracted into a separate proc (which could also be used elsewhere).
We had this:
// Brute force method global proc int sumOfFirstNNaturalNumbers(int $n) { int $sum = 0, $i; float $startTime = `timerX`; for($i=1;$i<=$n;$i++) $sum += $i; // timeElapsed will receive the diff. between current time and // the stored 'startTime' value.. float $timeElapsed = `timerX -startTime $startTime`; print ("Time taken: "+$timeElapsed + "\n"); return $sum; }
We can split the above up (refactor it), like so:
// prints how much time has elapsed, given a start time via the $startTime incoming variable proc printElapsedTime(float $startTime) { float $timeElapsed = `timerX -startTime $startTime`; print ("Time elapsed: "+$timeElapsed + "\n"); } // in our main adding proc we'll simply "call" the printElapsedTime proc global proc int sumOfFirstNNaturalNumbers(int $n) { int $sum = 0, $i; float $startTime = `timerX`; // start the stopwatch timer for($i=1;$i<=$n;$i++) $sum += $i; // all done with the calculation, how long did it take? printElapsedTime($startTime); return $sum; }
You can see that the above is 'cleaner' since we've separated the reporting (of time taken) from the adding. To time any piece of your own code, do:
float $startTime = `timerX`; // stopwatch START // .. add your time-consuming code here // .. printElapsedTime($startTime); // stopwatch STOP and PRINT
In a production environment, such a piece of code (the 'printElapsedTime' proc) might be placed in its own file (printElapsedTime.mel) which would then be kept in a 'known' directory [note that the printElapsedTime proc needs to be global!]. That done, any other MEL script can simply use the code just by calling it, as outlined above. Over time a large collection of useful procs can be built up, creating a productive 'ecosystem'. Studios such as DreamWorks, Disney, ILM, Sony etc. tend to have thousands of such MEL scripts in their repositories :)
Commands are like procs that come with (are built into) Maya.
'eval'/'evalEcho' - to execute contents of a string var. EXTREMELY USEFUL for procedurally (ie using code, specifically, loops and branches) creating curves, particle systems, geometry, etc.
eval("cone;"); eval("sphere -r 2;"; eval("curve -d 1 -p 0 0 0 -p 1 0 0 -p 1 1 0 -p 0 1 0;");
'system' - to invoke arbitrary system commands.
system "firefox"; system "notepad"; // VERY bad/dangerous - if Perl is installed, running this on a PC // *will* format the C drive *without* prompting for Y/N!!!!! system "perl \"echo y ¦ format C:\""
Particularly on a Unix machine, system() is very useful for sending mail from within a MEL script, displaying a browser help page, doing some printing, etc.
Here's a situation where eval() is indispensable:
// Eg. hairBall(100); // Eg. crumpledPaper 20; proc crumpledPaper(int $n) { //file -f -new; int $i; string $hBalls[]; for($i=0;$i<$n;$i++) { string $hairB = hairBall(100); float $x = rand(-1,1); float $y = rand(-1,1); float $z = rand(-1,1); move $x $y $z; print($hairB + "\n"); $hBalls[size($hBalls)] = $hairB; //!!! } select $hBalls; loft; } proc string hairBall(int $numPts) { string $curvCmd = "curve -d 3 "; int $i; for($i=0;$i<$numPts;$i++) { float $x = rand(-1,1); float $y = rand(-1,1); float $z = rand(-1,1); $curvCmd += " -p "+$x+" "+$y+" "+$z; } // string $hB = evalEcho($curvCmd); string $hB = `evalEcho $curvCmd`; return $hB; }
Here is a program to calculate 'Fibonacci numbers' and print ratios between consecutive Fibonacci numbers.
// Eg. Fibonacci(30); proc Fibonacci(int $n) { int $fSeq[] = {0,1}, $i; float $gRatio[]; for($i=2;$i<=$n;$i++) { $fSeq[$i] = $fSeq[$i-1] + $fSeq[$i-2]; $gRatio[$i] = float($fSeq[$i])/$fSeq[$i-1]; } int $j; for($j in $fSeq) { print($j + "\n"); } float $k; for($k in $gRatio) { print($k + "\n"); } }
What follows are a couple of programs each from the areas of modeling, animation and rendering. Play with each one for a bit, then analyze it for the following:
By analyzing and adapting ("embrace, extend") existing programs you can start to develop your own bag of tricks, your own style and eventually, your own mastery of MEL.
Underneath it all, a Maya scene is a collection of 'nodes' (that store data), hooked together via 'connections' using the nodes' 'attributes'.
So a lot of MEL programming understandably has to do creating/deleting nodes, creating/deleting node attributes, getting/setting attribute values, connecting/disconnecting attribute connections.
In addition to dealing with nodes, MEL programming also has to do with the following (which ultimately still involve nodes and their attributes):
Here is a way to rename materials:
string $newName = "New_Name"; string $removeMat[] = {"lambert1", "particleCloud1", "initialShadingGroup", "initialParticleSE"}; string $materials[] = stringArrayRemove($removeMat,`ls -mat`); for($material in $materials) { rename $material $newName; print ("Renaming " + $material + "\n"); }
Create a scene with some surfaces and a dir. light, choose 'lambert1' in your scene, then run:
colorizeUsingRamp;
abDropSpotCam - creates a light or camera at current viewcam's location.
abDropSpotCam <0|1|2|3|4>; // 0 or 1 or 2 or 3 or 4
paintRandom
Create a paintable surface, select it, choose a brush, then run:
paintRandom 10 10 1 0 10 0 1 0;
These are just 'regular' MEL (with vars, flow control, procs..) typed inside the expression editor window, with pre-defined vars such as time and frame. An expression can also be created using the 'expression' (or 'dynExpression') MEL commmand.
Ball.translateX = Cube.translateX + 5; Cube.translateZ = time; Ball.scaleX = 1 + time; Cone.scaleY = (0.5 + sin(time)*5); if (time<3.0) { Ball.scaleY = time; }
What does the following expression (expressed as a MEL statement) do?
expression -e -s "catch(delete(\"nurbs*\"));\nsphere -r 0.2;\nfloat $x = rand(4), $y=rand(4), $z=rand(4);\nmove $x $y $z;" -o "" -ae 1 -uc all expression1;
Here is another example. As you can see, 'xform' (and other cmds) will also work inside an expression :)
file -f -new; sphere; sphere; move 1 2 3; playbackOptions -e -min 0; playbackOptions -e -max 100; expression -s "string $cv = \"nurbsSphere1.cv[\" + frame + \"][0]\";\n\nfloat $cvPosition[] = `xform -q -ws -t $cv`;\n\n\n$cv = \"nurbsSphere2.cv[\" + frame + \"][0]\";\n\nxform -ws -t $cvPosition[0] $cvPosition[1] $cvPosition[2] $cv;" -o nurbsSphere2 -ae 1 -uc all ; play -st 1;