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;
	}



			

 



The next step was to modidy the function to allow a user to specify not only the radii of the shapes, but also the number of sides the molecule had and the number of copies that were made. This required compensating for the translation on the links so that the shape would appear to still be intact (hence the 10 sided molecule above is larger than 1 unit in length and width. The final molecule function is shown at right.

 

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)

 

 

 


I created a loop to generate molecules from 100 sides down to 3 to demonstrate the way the program spaces molecules of increasing sides (click for a larger image). Rollover to see from 250 sides to 3 (Maya became very unstable with that much geometry, prompting me to limit both the number of sides and the number of copies for the final script.

 

 

 


The last part of the initial assignment was to design a user interface (UI) to allow a Maya user without program knowledge simple access to the script's power. The first section shown above was all that was required for class (both radii, the sides, copies and height). These values are fairly self explanatory as they plug into the same values of the script.

 

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);

}







			

 

 

 


In order to create the additional translations, rotations and scales on the molecules, a separate procedure, makeMolecule(), was created. This function calls the molecule procedure to generate the molecule, then transforms it according to the user provided values in the transfrom section of the UI.

 

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)
{
int $n;
//int $crazy;
//for ($crazy=100; $crazy>2; $crazy--){
//$crazy can be put in place of the last number to create a
//series of shapes, the first with sides = initial value of
//$crazy, decrementing by one side until the final shape which
//has 3 sides.

float $localsrad=$srad;
float $localcrad=$crad;
int $localsides=$sides;
float $localtX=$tX; float $localtY=$tY; float $localtZ=$tZ;
float $localrX=$rX; float $localrY=$rY; float $localrZ=$rZ;
float $localsX=$sX; float $localsY=$sY; float $localsZ=$sZ;

//radius cannot be smaller than .01, sides no greater than 200.

if ($sX==0 ||$sX==0. || $sX==0.0 ) {
$localsX=1.0;
warning "ScaleX entered as zero, subtituting 1.0";
}
if ($sY==0 ||$sY==0. || $sY==0.0 ) {
$localsY=1.0;
warning "ScaleY entered as zero, subtituting 1.0";
}
if ($sZ==0 ||$sZ==0. || $sZ==0.0 ) {
$localsZ=1.0;
warning "ScaleZ entered as zero, subtituting 1.0";
}
if ($sides < 3) {
$localsides = 3;
warning "Number of Sides too small, Substituting 3";
}
if ($sides > 200) {
$localsides = 200;
warning "Number of Sides too large, Substituting 200";
}
if ($srad < .01) {
$localsrad = .01;
warning "Radius of Spheres too small, Substituting .01";
}
if($crad > $srad){
$localcrad = $srad*.66;
warning "Radii of Cylinders exceed Radii of Spheres, Substituting `print $crad`";
}
if ($copies > 150) {
string $returnVal=`confirmDialog -title "Warning! Creating more than 150 Copies."
-message "This may compromise system stability. \nAre you sure?"
-button "Proceed"
-defaultButton "Proceed"
-button "Cancel"
-cancelButton "Cancel"
-dismissString "Cancel"`;
if ($returnVal=="Cancel")
error "Operation Aborted by User.";
}
for($n = 0; $n<$copies; $n++)
{
float $up = $n * $offH;

if ($offT){
$localtX=$tX; $localtY=$tY; $localtZ=$tZ;

$localtX *= $n;
$localtY *= $n;
$localtZ *= $n;
}
if ($offR){
$localrX=$rX; $localrY=$rY; $localrZ=$rZ;
$localrX *= $n;
$localrY *= $n;
$localrZ *= $n;
}
if ($offS){
int $i;
$localsX=$sX; $localsY=$sY; $localsZ=$sZ;
for($i=0;$i<$n;$i++){
$localsX *= $sX;
$localsY *= $sY;
$localsZ *= $sZ;
}
}
string $name=molecule($localsrad, $localcrad,$localsides);
pickWalk -d up;
pickWalk -d up;
pickWalk -d up;
$up += $localtY;

xform -t $localtX $up $localtZ;

rotate $localrX $localrY $localrZ;

scale $localsX $localsY $localsZ;
if ($animate) {
keyframer($name);

}
}

}

 
      
       

 

 

 


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).