Video icon 64
Want to learn graphic design? Start your free trial today.
Advertisement

Procedural Modeling with MEL Scripts in Maya - Day 2

by

This tutorial is an introduction to using external MEL files and procedures, functions, loops, and scripted interaction within the Maya workspace. We'll be going over basic coding practices, and putting together a nifty geometric object that would be very difficult to create without scripting.


Additional Files/ Plugins:


Creating Procedures

In this tutorial, we will be creating an external script file with four "procedures" - self-contained scripts that are meant to achieve specific goals. The first procedure will take any number of objects selected in the Maya viewport and create a chained hierarchy for easy double-transformations. The second script will un-parent selected objects, and the third will move the pivot points of selected objects to the world-space origin. These three scripts will allow you to easily create some interesting effects with multiple objects. The fourth procedure will combine all of the first three to create a 'Fermat's Spiral', similar to the complex whorl found on sunflower heads.


Step 1

Open Maya. For the course of this tutorial, I'll be using Maya 2010, but the same commands should work for most versions of Maya. Start by creating a cube in the center of the viewport.


Step 2

Go to 'Edit > Duplicate Special' and click the option box to bring up this dialog. Create nine copies.


Step 3

Go to 'Window > Hypergraph Hierarchy'. We now need to parent the cubes together to create a "chain". To do this, first click 'pCube2' then shift click 'pCube1', and hit 'p' on your keyboard. This parents the first selected object (pCube2) to the second selected (pCube1).

Continue this process (selecting the child, then the parent and then hitting 'p' to chain them together,) for each of our cubes. Once complete, each object in the middle of the hierarchy should have one parent and one child cube, and the overall hierarchy should look like this:


Step 4

Close the 'Hypergraph Hierarchy' window. Select all of the objects by marquee-dragging over the cubes in the middle of the viewport. Then, keeping all of the cubes selected, try moving and rotating them.

Because each cube's transformations are now relative to the cube one step above in the chain, the resulting double-transformation allows you to move the cubes in a very unique way - small translations quickly become large changes.


Step 5

With all of the cubes still selected, hit 'Shift-P' on your keyboard to un-parent the cubes. We've now walked through all of the major steps that we want to automate with our script. So start a new Maya file, and open up a text editor. My personal favorite is Notepad++, which even has a MEL language plugin available. Feel free to use whatever text editor you prefer, but for now I'll use good old Microsoft Notepad. Notepad is simple, but it gets the job done.


Step 6

Go ahead and save your file. When saving a MEL document, make sure to choose "All Files" under "save as type". Save your document as a .mel file in your Maya scripts directory. I'll be using EdW_DoubleTransform.mel as my file name.


Step 7

In order to create a procedure, you have to know a couple of things beforehand. First, you must define a "scope" for the procedure. Scope establishes where a procedure can be accessed from. A "local" scope means that the code is only accessible to other procedures inside the same .mel file. "Global" means that the procedure is available to other script files and the user, and is also loaded into memory. In general, I use a lot of global procedures. This is not the absolute best coding practice, but I like to have my procedures available across multiple scripts. Second, you must choose a name for your procedure. In general, try to pick something unique and descriptive - if the procedure is global, you don't want the name to conflict with any other procedure in any other script. I usually work some kind of initials into the name, and because the script we're writing is called "DoubleTransform", I'll go with "dt" in this case. At it's most basic, this is the procedure we're building:

 
global proc dt_makeParentChain(){
//actions of the script 
};

Step 8

The first thing we'll do with our procedure is define a variable that will eventually house a list of object names. In MEL, variables are denoted by a dollar-sign ($) before the variable name. There are several different kinds of data that a variable can contain - strings of text, whole-numbers called integers, floating-point decimal numbers, vector values, etc. The syntax for declaring a variable is this:

 
dataType $variableName;

For now, we'll make an empty string variable:

 
string $selectedObjects;

If we want, we can also assign data to the variable in the same line of code. Double-quotes are used to define strings of text.

 
string $selectedObjects = "this is a string of text";

a single equals-sign means "set the variable to". A double equals-sign means "is equal to", and is used when comparing variables.


Step 9

In order to test our new variable, we need to add a print command. This will output a string to the Script Editor when the script is run. In this case, we are simply printing the contents of the variable '$selectedObjects'.

 
print $selectedObjects;

Step 10

Add a comment to your new procedure. I tend to add a comment before each procedure, so that I can quickly find them later. Some of my scripts have dozens of procedures, and comments make the process of debugging and changing code much easier. The full code should now look like this:

 
// Make Parent Chain
global proc dt_makeParentChain(){
string $selectedObjects = "this is a string of text";
print $selectedObjects;
};

Step 11

Save the file and go back to Maya. Whenever Maya first starts up, it scans the scripts directory and loads a list of all the files there. If you've added a script to the directory while Maya is already open, then the system doesn't know that the file exists. In this case you have two options, either restart Maya, or you can use the "rehash" command to force Maya to update that list of files. We'll use the second method, so go to 'Window > General Editors > Script Editor', type the following :

 
rehash;

Now hit 'Ctrl-Enter' to run the command.


Step 12

To test out your script, you'll need to "initialize" it in Maya. The "rehash" command tells maya that the file exists, and the "source" command tells Maya to load the script into memory. Type the following into the script editor:

 
source EdW_DoubleTransform.mel;

Then, type the command for the procedure we created inside the script:

 
dt_makeParentChain;

This is what you should get in the script editor after executing the procedure:


Step 13

Now that we've put together a functioning procedure, let's figure out the pseudocode for what we want that procedure to do in our final script:

  • write a list of selected objects to a variable
  • get the number of selected objects
  • parent each object to the previous one in the list

Step 14

In order to get a list of the objects that are selected in the Maya viewport, use the list command, "ls". List is usually used with a flag that tells the command to grab whatever is currently selected in the maya viewport, -sl. Try selecting some objects in the maya viewport, and type the full command (shown below) in the script editor, remember to hit 'Ctrl-Enter' to run the command.

 
ls -sl;

Step 15

The list command outputs a bunch of strings that represent the full list of selected objects. In order to make this information useful to us, we need to write the result of the "list" command to a variable. To do this, wrap the list command in tick-marks (Note, the tick-mark, also called a backquote, is usually located above the tab key on the keyboard... be sure not to use the single-quote-mark next to the enter key). Wrapping a command in tick-marks translates as "the output of". Our command should now look like this:

 
string $selectedObjects = `ls -sl`;

Step 16

The variable $selectedObjects is a single string of text. What we really need is something called a string array -- an ordered list of strings that are contained in a single variable. To make our string into a string array, use square brackets when declaring the variable:

 
string $selectedObjects[] = `ls -sl`;

Now, whenever we refer to the variable, we can use either the entire array:

 
$selectedObjects //all the strings in the array

... Or an individual string inside the array, by supplying an index - the position of the string we want within the array itself. The index values in MEL always start at zero as shown below :

 
print $selectedObjects[0]; // returns the first string in the array
print $selectedObjects[2]; // returns the third string in the array

Step 17

We will also need a way to figure out how many objects are in the array. You can use the "size" command to do just that. Let's create a new variable to store this information. A whole number is represented by an integer, which is always refered to in MEL as an "int":

 
int $numSelectedObjects = `size($selectedObjects)`;

Step 18

Now we need a way to parent each object according to its place in the array. There are a handful of ways to do this, but for now we'll use a basic "for" loop. For-loops are common in programming as a way to do an action a number of times. The syntax of a for-loop that runs ten times is this:

 
for (int $i = 0; $i < 9; $i++){ //action to be done };

The loop structure has three parts. $i = 0 defines an integer variable, $i < 9 defines where the loop should end (in this case, so long as $i is less than 9, the loop executes). The third part, $i++ is shorthand for "add one to the variable". Our for-loop will look like this:

 
for($i = 0; $i < $numSelectedObjects; $i++) {
//action to be performed 
};

Step 19

We now need to use a 'parent' command to create the hierarchy chain. The syntax for this command is as follows :

 
parent childObject parentObject;

So in our case, the parent command would be :

 
for($i = 1; $i < $numSelectedObjects; $i++) {
parent $selectedObjects[(i-1)] $selectedObjects[i];
};

Step 20

With our loop completed, we now need to deselect the last object in the selection, using the "select" command with the "deselect" flag. To calculate the index of the last object selected, we take the total number of objects, and then, because the index of any array in Maya begins at 0, subtract 1.

 
select -deselect $selectedObjects[($numSelectedObjects - 1)];

Step 21

And with that, our first procedure is now complete:

 
//Make Parent Chain
global proc dt_makeParentChain(){
string $selectedObjects[] = `ls -sl`;
int $numSelectedObjects = `size($selectedObjects)`;
	for($i = 1; $i < $numSelectedObjects; $i++) {
	parent $selectedObjects[($i - 1)] $selectedObjects[$i];
	};
select -deselect $selectedObjects[($numSelectedObjects - 1)];
};

Step 22

Underneath 'dt_makeParentChain', create a new procedure to unparent the selected objects. The code below parents the selected objects to the world, which is the same as un-parenting them.

 
global proc dt_unParentSelected(){ 
parent -w; 
};

Step 23

Our third procedure will automate the process of moving an object's pivot point to the origin. Just like before, we start by creating the procedure outline:

 
global proc dt_pivotToOrigin(){ 
//procedure actions
};

Step 24

We then use the same technique as in the first procedure to write a list of the selected objects to a variable:

 
string $selectedObjects[] = `ls -sl`;

Step 25

We now need to add in a loop. Whilst we could use the same kind of for-loop as in the first script, let's use a for-each loop instead. A "for-each" loop is a specialized version of the for-loop that executes once for each value in an array. It also lets you write a variable that represents the current index's value. The syntax looks like this:

 
for( $each in $arrayVariable ){
//do this action with $each
};

The $each variable contains the value of the current index of the array. For our script, this is what the code should look like:

 
for($thisObj in $selectedObjects){
//move the pivot of $thisObj
};

This same technique can be used to automate any maya command for any number of selected objects.


Step 26

Using the $thisObj variable we can now select the object currently being looked-at by the loop:

 
select -r $thisObj;

Step 27

In order to move the pivot to the origin, we can use the "xform" command and reset the -rp (rotatePivot) and -sp (scalePivot) flags to 0 on the X, Y and Z axis:

 
xform -ws -rp 0 0 0 -sp 0 0 0;

Step 28

The major components of the script are now finished, and your code, containing all three procedures, should look like this:

 
//Make Parent Chain
global proc dt_makeParentChain(){
string $selectedObjects[] = `ls -sl`;
int $numSelectedObjects = `size($selectedObjects)`;
	for($i = 1; $i < $numSelectedObjects; $i++) {
	parent $selectedObjects[($i - 1)] $selectedObjects[$i];
	};
select -deselect $selectedObjects[($numSelectedObjects - 1)];
};
 
// Un-Parent Objects
global proc dt_unParentSelected(){
parent -w;
};
 
// Move Pivot to Origin
global proc dt_pivotToOrigin(){ 
string $selectedObjects[] = `ls -sl`;
	for($thisObj in $selectedObjects){
	select -r $thisObj;
	xform -ws -rp 0 0 0 -sp 0 0 0;
	};
};

Step 29

We're now going to create a fourth procedure that will use the previous three to create an interesting geometric progression called "Fermat's Spiral", which based on the angular relationship of the Golden Ratio, phi. It's a shape that is commonly found in nature, and it follows all kinds of interesting geometric rules. Let's start out by writing out the pseudocode:

  • create a sphere
  • create 1,000 copies
  • parent-chain all the spheres
  • move the objects in the x-axis
  • move all sphere's pivot points to the origin
  • rotate the spheres in Y 137.5 degrees (the Golden Angle)

Step 30

So, to begin with let's create a new procedure outline:

 
global proc dt_fermatSpiral(){ 

}

Step 31

Now we're going to create a sphere with 8 radial and 8 height divisions, and write the resulting transform and shape nodes to a string array so that we can keep track of them. Most commands that create geometry output two strings - the name of the transform node, and the name of the shape node. For the most part, you want to work with the transform node string, which will be stored in the [0] index in the $seedSphere array variable created below:

 
string $seedSphere[] = `polySphere -sx 8 -sy 8`;

Step 32

We're now going to create an empty string array variable:

 
string $allSpheres[];

Step 33

Then open a for-loop that runs 1,000 times:

 
for ($i=1; $i<1000; $i++){

Step 34

We now need to duplicate our newly created sphere, and write the resulting transform and shape nodes to a variable:

 
string $duplicatedSphere[] = `duplicate`;

Step 35

Here, we append the name of our duplicated transform node to the end of the $allSpheres array. You can use the "size" command to determine how many objects are already in the array, and use the result as the index for our new value.

 
$allSpheres[size($allSpheres)] = $duplicatedSphere[0];

Step 36

Here we select all of the created spheres and run the parent chain command we previously created:

 
select -r $allSpheres;
dt_makeParentChain;

Step 37

After parenting the spheres together, we select them again and move them all .05 in X using the "move" command:

 
select -r $allSpheres;
move 0.05 0 0;

Step 38

After resetting the origin position using our previously created "pivotToOrigin" procedure, we select the spheres one final time, and rotate them 137.5 degrees in Y. With that done, run our unparent script:

 
dt_pivotToOrigin;
select -r $allSpheres;
rotate 0 137.5 0;
dt_unParentSelected;

Step 39

And that completes our final procedure. So, our full script should look like this:

 
//Make Parent Chain
global proc dt_makeParentChain(){
string $selectedObjects[] = `ls -sl`;
int $numSelectedObjects = `size($selectedObjects)`;
	for($i = 1; $i < $numSelectedObjects; $i++) {
	parent $selectedObjects[($i - 1)] $selectedObjects[$i];
	};
select -deselect $selectedObjects[($numSelectedObjects - 1)];
};
 
// Un-Parent Objects
global proc dt_unParentSelected(){
parent -w;
};
 
// Move Pivot to Origin
global proc dt_pivotToOrigin(){ 
string $selectedObjects[] = `ls -sl`;
	for($thisObj in $selectedObjects){
	select -r $thisObj;
	xform -ws -rotatePivot 0 0 0 -scalePivot 0 0 0;
	};
};
 
//Create Fermat's Spiral of Spheres
global proc dt_fermatSpiral(){ 
string $seedSphere[] = `polySphere -sx 8 -sy 8`;
string $allSpheres[];
	for ($i=1; $i<1000; $i++){
	string $duplicatedSphere[] = `duplicate`;
	$allSpheres[size($allSpheres)] = $duplicatedSphere[0];
	};
select -r $allSpheres;
dt_makeParentChain;
select -r $allSpheres;
move 0.05 0 0;
dt_pivotToOrigin;
select -r $allSpheres;
rotate 0 137.5 0;
dt_unParentSelected;
};

Step 40

And with that we're done! Save the script, and re-run the 'rehash'/'source' process as detailed above. Then enter the following command into Maya's script editor, hit 'Ctrl-Enter' and wait a few seconds:

 
dt_fermatSpiral();

And there you have Fermat's Spiral, constructed entirely using MEL. Try playing with different angles, shapes and transformations in the final procedure - you can get all kinds of interesting patterns!


This tutorial is Day 2 in a series - Go to Day 1.


Advertisement