MEL Scripting: Maya Molecule
An introduction to Maya Embedded Language (MEL) through learning how to set up a simple procedure (or function) to generate shapes . This freedom to automate basic tasks allows the user the ability to generate additional procedures to control attributes and variations, culminating in a user friendly interface to control the entire process. By directly interacting with Maya at its most basic level, freedom to control every aspect of every operation is possible....
|
Our first task was to use basic MEL commands to generate the shape shown above (a "molecule"). The goal was for the shape to be one unit in size and require as few lines of code as possible to generate. The source is shown at right, and at 11 lines it became the basis for future developments by the professor and the class. The first obvious problem that needed to be addressed is that it could not create multiple molecules or molecules of additional sides due to the fact the names of objects and groups are explicitly defined and the movements are hard coded. |
sphere -n sphere1 -r .1;
move .4 0 .4;
cylinder -n cylinder1 -r .05 -hr 13;
move 0 0 .4;
group -n set1 cylinder1 sphere1;
xform -os -piv 0 0 0;
duplicate -n set2;
rotate 0 90 0;
duplicate -n set3 -st;
duplicate -n set4 -st;
group -n molecule set1 set2 set3 set4;
|
Some simple modifications allowed multiple molecules to be created by using variables to store whatever name Maya gave for each group. This time the code is encased in a procedure, which allows Maya to execute the code merely by typing its name with the values it requires - ex. molecule(.1, .05). Included now is the ability to change the radii of the sphere and cylinder, prompting the compensation of the cylinder's height ratio through the variable $ratio. |
global proc molecule(float $srad, float $crad)
{
string $sname[] = `sphere -r $srad`;
move .4 0 .4;
// Limit the size of the cylinder
if($crad > $srad)
$crad = $srad;
float $ratio = 1.0/$radius;
string $cname[] = `cylinder -r $crad -hr $ratio`;
move 0 0 .4;
string $original = `group $cname[0] $sname[0]`;
string $d1[], $d2[], $d3[];
xform -os -piv 0 0 0;
$d1 = `duplicate`;
rotate 0 90 0;
$d2 = `duplicate -st`;
$d3 = `duplicate -st`;
string $final = `group $original $d1[0] $d2[0] $d3[0]`;
}
select -all;
delete;
int $n;
for($n = 0; $n < 10; $n++)
{
molecule(0.15, 0.9);
float $up = $n * 0.25;
move 0 $up 0;
}
|
|
Note that this function has a return value ($original). This is to allow the name of the molecule group to be passed back to the procedure that calls molecule(). This was used primarily later to keyframe the values of the molecules created. |
global proc string molecule(float $srad, float $crad, int $sides)
{
string $sname[] = `sphere -r $srad`;
float $compensationCylinder = .4 + .17*($sides - 4);
float $compensationSphereX = $compensationCylinder*3.11/
($sides - 2)-.9/$sides;
if ($sides == 3)
$compensationSphereX -= .02;
move $compensationSphereX 0 $compensationCylinder;
float $compensater = .8 + .008*($sides-3);
if ($sides > 5 && $sides <26)
$compensater += .04;
if ($sides > 8 && $sides <19 )
$compensater += .02;
if ( $sides == 26 || $sides == 27|| $sides == 5 || $sides == 19)
$compensater += .01;
if ($sides > 31)
$compensater -= .0077*($sides-31);
float $ratio = ($compensater)/($crad);
string $cname[] = `cylinder -r $crad -hr $ratio`;
move 0 0 $compensationCylinder;
string $original = `group $cname[0] $sname[0]`;
pickWalk -d up;
string $d1[];
xform -os -piv 0 0 0;
$d1 = `duplicate`;
float $yrotation = 360.0/$sides;
rotate 0 $yrotation 0;
parent $d1[0] $original;
int $counter;
for($counter = 2; $counter<$sides; $counter++)
{
string $d2[];
$d2 = `duplicate`;
float $duplicaterotation= $yrotation*$counter;
rotate 0 $duplicaterotation 0;
}
pickWalk -d up;
pickWalk -d up;
pickWalk -d up;
return $original;
}
|
||
A three sided molecule, the smallest number of sides possible. |
By contrast, a one hundred and three sided molecule (whew) to illustrate the way the script compensates for additional sides by translating the links outward from the origin. The code to do this is shown at right. |
float $compensationCylinder = .4 + .17*($sides - 4);
float $compensationSphereX = $compensationCylinder*3.11/
($sides - 2)-.9/$sides;
if ($sides == 3)
$compensationSphereX -= .02;
move $compensationSphereX 0 $compensationCylinder;
float $compensater = .8 + .008*($sides-3);
if ($sides > 5 && $sides <26)
$compensater += .04;
if ($sides > 8 && $sides <19 )
$compensater += .02;
if ( $sides == 26 || $sides=27
|| $sides == 5 || $sides == 19)
$compensater += .01;
if ($sides > 31)
$compensater -= .0077*($sides-31);
move 0 0 $compensationCylinder;
|
||
(looking down a 400 sided molecule, the final script limits the sides to 200 for stability) |
The second part, however, is my addition to the basic UI, allowing the user to control the position, scale and rotation of the molecules as a group. Furthermore, I added the ability for these values to be offset for each molecule (i.e. a rotation of 30 degrees would mean each molecule is rotate 30 degrees more than the previous, a scale of 2 would mean each molecule is twice as big as the previous. Some results are shown below.... |
global proc moleculePlusPlusUI ()
{
string $myWindow= `window -s false -mnb true -in "Molecule Generator"
-title "Molecule Generator 1.0" -rtf true`;
columnLayout;
frameLayout -collapsable true - label "General";
columnLayout;
floatFieldGrp -value1 0.2 -label "Sphere Radius" sphereRad;
floatFieldGrp -value1 0.1 -label "Cylinder Radius" cylinderRad ;
intFieldGrp -value1 4 -label "Number of Sides" numSides;
intFieldGrp -value1 1 -label "Number of Copies" numCopies;
floatFieldGrp -value1 .2 -label "Height Offset" heightOffset;
rowLayout -ct1 "left" -co1 100;
checkBox -label "Animate Molecules" animate; //note: this was added later, see below on page.
setParent ..;
setParent ..;
setParent ..;
frameLayout -collapsable true - label "Transform Attributes";
columnLayout;
floatFieldGrp -numberOfFields 3 -value1 0.0 -value2 0.0
-value3 0.0 -label "Translation X Y Z" transGroup;
floatFieldGrp -numberOfFields 3 -value1 0.0 -value2 0.0
-value3 0.0 -label "Rotation X Y Z" rotGroup;
floatFieldGrp -numberOfFields 3 -value1 1.0 -value2 1.0
-value3 1.0 -label "Scale X Y Z" scaleGroup;
rowColumnLayout -nc 4 -cw 1 101 -cs 1 20 -cs 2 20 -cs 2 20;
checkBox -label "Offset Translation " offTran;
checkBox -label "Offset Rotation " offRot;
checkBox -label "Offset Scale" offScale;
setParent ..;
setParent ..;
setParent ..;
rowColumnLayout -nc 3;
button -label "Make Molecule(s)" -align "center"
-command "inputValues(\"sphereRad\",\"cylinderRad\",
\"numSides\",\"numCopies\",
\"heightOffset\",\"transGroup\",
\"rotGroup\",\"scaleGroup\",\"offTran\",
\"offRot\",\"offScale\", \"animate\")";
button -label "Delete Molecule(s)" -align "center" -command "undo";
button -label "Close" -align "center" -command ("deleteUI -window " + $myWindow);
showWindow $myWindow;
}
|
The first successful test of the UI using transform offsets. The first molecule created is the smallest one closest to the origin, the final in the sequence is the largest (highlighted).
The UI function also required an inputValues function to take the numbers provided by the user in the interface and pass them to the generator function. |
global proc inputValues(string $sphereRad, string $cylinderRad,
string $numSides, string $numCopies,
string $heightOffset,string $translationGroup,
string $rotationGroup,string $scaleGroup,
string $offTran, string $offRot,
string $offScale, string $animate)
{
float $srad = `floatFieldGrp -q -value1 $sphereRad`;
float $crad = `floatFieldGrp -q -value1 $cylinderRad`;
int $sides = `intFieldGrp -q -value1 $numSides`;
int $copies = `intFieldGrp -q -value1 $numCopies`;
float $tX = `floatFieldGrp -q -value1 $translationGroup`;
float $tY = `floatFieldGrp -q -value2 $translationGroup`;
float $tZ = `floatFieldGrp -q -value3 $translationGroup`;
float $rX = `floatFieldGrp -q -value1 $rotationGroup`;
float $rY = `floatFieldGrp -q -value2 $rotationGroup`;
float $rZ = `floatFieldGrp -q -value3 $rotationGroup`;
float $sX = `floatFieldGrp -q -value1 $scaleGroup`;
float $sY = `floatFieldGrp -q -value2 $scaleGroup`;
float $sZ = `floatFieldGrp -q -value3 $scaleGroup`;
int $offT = `checkBox -q -v $offTran`;
int $offR = `checkBox -q -v $offRot`;
int $offS = `checkBox -q -v $offScale`;
int $animateTF=`checkBox -q -v $animate`;
float $offH = `floatFieldGrp -q -value1 $heightOffset`;
makeMolecule($srad,$crad,$sides,$copies,$tX,$tY,$tZ,$rX,$rY,$rZ,
$sX,$sY,$sZ,
$offT,$offR,$offS,$offH,$animateTF);
}
|
Also, a series of conditional tests have been added in order to inspect the values provided by the user to ensure no error producing values can be entered. If a number provided would generate a large molecule or a large number of them, the values are either corrected with a warning or the user is warned about proceeding.
The call to the procedure keyframer () at the bottom was added later to allow the molecules to be animated (explained below). |
global proc makeMolecule(float $srad, float $crad, int $sides,
int $copies,float $tX, float $tY,
float $tZ, float $rX, float $rY,
float $rZ, float $sX, float $sY,
float $sZ, int $offT,int $offR,
int $offS, float $offH, int $animate)
float $localsrad=$srad; //radius cannot be smaller than .01, sides no greater than 200. $localtX *= $n; xform -t $localtX $up $localtZ; rotate $localrX $localrY $localrZ; scale $localsX $localsY $localsZ; }
|
||
These settings produced the result shown below... |
|
The final task was to create a function to set keyframes on each molecule's translate and rotate values, assigning them random values, so that each shape created would morph around and back over a range of 120 frames (4 seconds). The code for this function is shown at right. |
global proc keyframer(string $name) {
select -r $name;
currentTime 1;
setKeyframe $name.translate;
setKeyframe $name.rotate;
timeSliderCopyKey;
float $mover= `rand -5 5`;
float $shaker = `rand -360 360`;
currentTime 60;
move -r $mover $mover $mover;
rotate -r $shaker $shaker $shaker;
setKeyframe $name.translate;
setKeyframe $name.rotate;
currentTime 120;
$mover *= -1;
$shaker *= -1;
timeSliderPasteKey false;
setKeyframe $name.translate;
setKeyframe $name.rotate;
playbackOptions -min 1 -max 120;
}
|
A still shot showing all the settings used to create the shape shown above, and the animation shown directly below... |
|
As an interesting side note, both movies were rendered in Maya with two spotlights (a key and a fill), However, the top movie was rendered with PRMan, whereas the bottm was rendered with Maya's Scanline renderer. (See if you can tell the difference.... I can).
The source code in its entirety can be viewed here.
This concludes the molecule project for right now, please feel free to email with any suggestions or questions. I might add code later to texture the molecules automatically to be renderable in renderman (without using SLIM). |