Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

Maya MEL Procedural Modeling for Artists - UIs

by

This tutorial is an introduction to building Graphical User Interfaces (GUI) in MEL for Maya 2010 and below. Maya 2011 introduces a new methodology for creating GUIs that will not be covered here. We'll be going over proper coding for UIs, error handling, and we'll put together a very useful script that gives you considerable control over an object's pivot point.

User Interfaces

If you plan on giving your scripts to another artist, then it can be incredibly useful to put together a user-friendly GUI that makes your code accessible to non-programmers. These days, I rarely use custom hotkeys, instead relying on a handful of UIs that I've built in MEL to automate Maya processes. I can carry them with me, and do not have to change any preferences in order to work comfortably on multiple workstations. The ability to create and maintain UIs is a powerful but sometimes frustrating tool in the MEL toolkit.


Step 1

Let's take a look at what we're going to be creating today. Start by saving the "EdW_PivotControl.mel" file included with this tutorial to your scripts directory. Then, open up Maya. I'll be using Maya 2010 for the duration of this tutorial, but the same code should work for most Maya versions. Type the following into the Command Line and press enter:

source EdW_PivotControl; EdW_PivotControl;

Step 2

We should have a window open up with a few buttons on it. Try creating some geometry and experiment with the script. The goal is to create a script that gives you on-the-fly control over object pivot points.


Step 3

Lay out what procedures we'll need to make this script:

  • EdW_PivotControl - launches the main procedure of the script
  • epc_pivotControl - creates the UI
  • epc_getBounds - use the xform command to get the bounding box of the selected object
  • epc_movePivotLocal - moves the object's pivot to a local position (y min, x min, etc.)
  • epc_movePivotToObject - moves the pivot to another object's location

I'm using epc_ as my prefix. Like in previous tutorials, you want to make sure that your procedures have unique names, so as to not interfere with any other scripts.


Step 4

We'll start our script by opening up that old standby, Microsoft Notepad. The first procedure is easy:


Step 5

Save out 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_PivotControl.mel as my file name, but feel free to choose whatever you like.


Step 6

Now we get to the hard part. Creating UIs in MEL has never been a particularly elegant process, so I'll do my best to make the process as pain-free as possible. Sometimes, it's best to just draw something out on paper before we start writing out code, so sketch out a basic layout for what you want the final UI to look like. This script is pretty simple, but when you start to deal with tabs, menus, scrollbars, etc. you want to make sure you have a game-plan in mind.


Step 7

We're going to be looking at three different types of UI commands:

  • Windows - top-level objects that have the standard buttons of any OS window, such as minimize, maximize, and close.
  • Layouts - different ways of organizing the objects within a windows.
  • Controls - buttons, sliders, text fields, etc. These are the interactive elements of the UI.

Here's a breakdown of our sketch according to the MEL commands used to create them:


Step 8

The first step to creating the UI is to establish the window. Because our Window object will have a unique name, Maya cannot have two of the same window open at one time. The first part of our procedure checks to see if the window is already open, and closes it if it is.

// Create UI
global proc pivotControl(){<br />	if ( `window -exists PivotControlMain` ) {<br />	deleteUI PivotControlMain;<br />	};
//...
};

Step 9

Now, let's try creating a window in the code and see what happens:

window -rtf 1 -t "EdW Pivot Control" -mnb 1 -mxb 1 -w 450 -h 300 PivotControlMain;
  • -resizeToFitChildren (-rtf) if true, the window will automatically resize to fit all the layouts and controls you create
  • -title (-t) the text displayed in the title bar at the top of the window
  • -minimizeButton (-mnb) enables or disables the minimize button
  • -maximizeButton (-mxb) enables or disables the maximize button
  • -width (-w) width of the window in pixels
  • -height (-h) height of the window in pixels

Step 10

Right now, the window has been created inside the script, but another command is needed to show it. This command will always come after all of the UI commands for the window.

showWindow PivotControlMain;

Step 11

Our full code should look like this:

//Main Function
global proc EdW_PivotControl() {
pivotControl;
};

//Create the UI
global proc pivotControl(){
	if ( `window -exists PivotControlMain` ) {
	deleteUI PivotControlMain;
	};

window -rtf 1 -t "EdW Pivot Control" -mnb 1 -mxb 1 -w 200 -h 350 PivotControlMain;

showWindow PivotControlMain;
};

Step 12

Source in the new code and run it from the Maya command line. This is what you should get:

source EdW_PivotControl; EdW_PivotControl;

<span class="tutorial_image"><img src="http://cdn.tutsplus.com/cg/uploads/legacy/249_MAYA_EdWhetstone3/6.jpg" data-original-url="http://cgtuts.s3.amazonaws.com/249_MAYA_EdWhetstone3/6.jpg" alt="" border="0" /></span>

Step 13

The Window is the topmost object in our UI hierarchy. All of the layouts and controls are children of this object. The first layout we'll use is a column layout, to hold the buttons:

columnLayout -adjustableColumn 1 -rowSpacing 0 EPC_MainColumnLayout;
  • -adjustableColumn (-ac) the column will automatically resize according to the width of the window
  • -rowSpacing (-rs) the distance in pixels between each row in the column

Step 14

I've found that including some instructions or clarifications within the UI can make the script more useable. Add a text control to the script:

text -l "Move Pivot To:";
  • -label (-l) the actual text of the control


Step 15

Next, we want to add a layout to hold the buttons at the top of the window, for moving the pivots around. One layout we can use is gridLayout, which creates a set of evenly-spaced cells that contain one object each.

gridLayout -cwh 60 24 -nrc 2 5;
  • -cellWidthHeight (-cwh) sets the width and height of each individual cell.
  • -numberOfRowsColumns (-nrc) sets the number of horizontal rows and vertical columns in the grid


Step 16

Each cell in the grid can contain a single control. These are automatically filled when you create the objects underneath the layout. In our case, we want to create nine buttons. We will use the -command flag later to tell the buttons which procedure to call:

button -l "Center";
button -l "Y Min";
button -l "Y Max";
button -l "Origin";
button -l "Selected";
button -l "X Min";
button -l "X Max";
button -l "Z Min";
button -l "Z Max";
  • -label (-l) the text displayed on the button


Step 17

Now, we need a way to tell Maya that we're done with the gridLayout, and we want to add more elements to the columnLayout. In order to do so, we will use a MEL command to set the parent of the gridLayout.

setParent ..;

The ..; indicates that you want to parent to the layout one step up the hierarchy chain. We could also use the name of the layout, but this is only useful if all of the layouts have explicit names:

setParent EPC_MainColumnLayout;

Step 18

Our pivotControl script should now look like this:

global proc pivotControl(){
	if ( `window -exists PivotControlMain` ) {
	deleteUI PivotControlMain;
	};
window -rtf 1 -t "EdW Pivot Control" -mnb 1 -mxb 1 -w 200 -h 350 PivotControlMain;
columnLayout -adjustableColumn 1 -rowSpacing 0 EPC_MainColumnLayout;
text -l "Move Pivot To:";
gridLayout -cwh 60 24 -nrc 2 5 -ag 1;
		button -l "Center";
		button -l "Y Min";
		button -l "Y Max";
		button -l "Origin";
		button -l "Selected";
		button -l "X Min";
		button -l "X Max";
		button -l "Z Min";
		button -l "Z Max";
	setParent ..;

showWindow PivotControlMain;
};

Step 19

Just like the gridLayout, the columnLayout needs to be closed by setting its parent.

setParent ..;

Step 20

Source in the script and see what you get:


Step 22

The UI procedure is done!

//Main Function
global proc EdW_PivotControl() {
pivotControl;
};

//Create the UI
global proc pivotControl(){
	if ( `window -exists PivotControlMain` ) {
	deleteUI PivotControlMain;
	};
window -rtf 1 -t "EdW Pivot Control" -mnb 1 -mxb 1 -w 200 -h 350 PivotControlMain;
	columnLayout -adjustableColumn 1 -rowSpacing 0 EPC_MainColumnLayout;
	text -l "Move Pivot To:";
		gridLayout -cwh 60 24 -nrc 2 5 -ag 1;
				button -l "Center";
				button -l "Y Min";
				button -l "Y Max";
				button -l "Origin";
				button -l "Selected";
				button -l "X Min";
				button -l "X Max";
				button -l "Z Min";
				button -l "Z Max";
			setParent ..;
		setParent ..;
showWindow PivotControlMain;
};

Step 23

Now we need to create the code for actually moving an object's pivot point. To do so, we're going to create two procedures that will work together:

  • epc_getBounds will use an xform command and a little arithmetic to return the bounding box minimums, maximums, and averages.
  • epc_movePivotLocal will retrieve the bounding box information using epc_getBounds to move the pivot locations.

Step 24

Lay out the pseudo-code for epc_getBounds:

  • select the object passed from epc_movePivotLocal
  • write the result of a queried xform command to an array
  • get the averages of the x, y, and z minimums and maximums returned from the xform
  • append the averages onto the return array
  • return the bounding box array along with the averages

Step 25

create the skeleton for the procedure, complete with a return type and a passed argument.

global proc float[] epc_getBounds(string $objSel){

};

Step 26

select the object passed as an argument, and get the bounding box information.

global proc float[] epc_getBounds(string $objSel){
select -r $objSel;
float $getBoundArray[] = `xform -q -ws -bb`;
};
  • -query (-q) query the command, instead of actually transforming anything
  • -worldSpace (-ws) make sure the
  • -boundingBox (-bb) returns the min and max positions of the bounding box

The -boundingBox flag returns six values in an array: x minimum, x maximum, y minimum, y maximum, z minimum, and z maximum.


Step 27

calculate the averages between the minimums and maximums. Remember that arrays always start with an index of zero.

float $bbXAv = (($getBoundArray[3] + $getBoundArray[0])/2);
//xmax plus xmin divided by two
float $bbYAv = (($getBoundArray[4] + $getBoundArray[1])/2);
//ymax plus ymin divided by two
float $bbZAv = (($getBoundArray[5] + $getBoundArray[2])/2);
//zmax plus zmin divided by two

Step 28

calculate the averages between the minimums and maximums. Remember that arrays always start with an index of zero.

float $bbXAv = (($getBoundArray[3] + $getBoundArray[0])/2);
//xmax plus xmin divided by two
float $bbYAv = (($getBoundArray[4] + $getBoundArray[1])/2);
//ymax plus ymin divided by two
float $bbZAv = (($getBoundArray[5] + $getBoundArray[2])/2);
//zmax plus zmin divided by two

Step 28

Append the newly calculated averages and return the final array.

$getBoundArray[6] = $bbXAv;
$getBoundArray[7] = $bbYAv;
$getBoundArray[8] = $bbZAv;
return $getBoundArray;

Step 29

The final procedure should look like this:

global proc float[] epc_getBounds(string $objSel){
select -r $objSel;
float $getBoundArray[] = `xform -q -ws -bb`;
float $bbXAv = (($getBoundArray[3] + $getBoundArray[0])/2);
float $bbYAv = (($getBoundArray[4] + $getBoundArray[1])/2);
float $bbZAv = (($getBoundArray[5] + $getBoundArray[2])/2);
$getBoundArray[6] = $bbXAv;
$getBoundArray[7] = $bbYAv;
$getBoundArray[8] = $bbZAv;
return $getBoundArray;
};

Step 30

Now we can put this code to work inside our epc_movePivotLocal procedure. Write out the pseudo-code:

  • write a list of selected objects to an array
  • get the bounding box information for each object selected, using the epc_getBounds procedure
  • create a switch-case statement to control where the pivot should move to

Step 31

create the skeleton for the procedure. Set up a for-in loop so that the code is executed once for every object selected in the scene.

global proc epc_movePivotLocal(string $mode){
string $sel[] = `ls -sl`;
	for ($thisObj in $sel){
    //code goes here
	};
};

Step 32

Use the procedure return-value from epc_getBounds to write a float array:

float $pos[] = `epc_getBounds $thisObj`;

Step 32

Now, we'll use a switch-case statement to actually move the pivot around. The basic structure of a switch-case is this:

switch($variable) {
//the variable can be any type, including strings and floats
	case variableValue:
    //if the variable matches variableValue
    //run this code
    break;
    
    case otherValue:
    //code here
    break;

};

The switch-case statement is a cascading system, which means that if you don't include the "break" command after every case, the rest of the cases will execute as well. For our procedure, we want to add a switch-case that will allow us to use the same procedure to move a pivot to many different positions. Our switch-case will look like this:

switch($mode){
			case "center":
			CenterPivot $thisObj;
			break;
			
			case "ymin":
			move -a -rpr $pos[6] $pos[1] $pos[8] ($thisObj + ".rotatePivot") ($thisObj + ".scalePivot");
			break;
            };

Step 33

Continue adding cases to the script, so that each of our UI buttons has a corresponding case. The full procedure should look like this:

global proc epc_movePivotLocal(string $mode){
string $sel[] = `ls -sl`;
	for ($thisObj in $sel){
	float $pos[] = `epc_getBounds $thisObj`;
		switch($mode){
			case "center":
			CenterPivot $thisObj;
			break;
			
			case "ymin":
			move -a -rpr $pos[6] $pos[1] $pos[8] ($thisObj + ".rotatePivot") ($thisObj + ".scalePivot");
			break;
			
			case "ymax":
			move -a -rpr $pos[6] $pos[4] $pos[8] ($thisObj + ".rotatePivot") ($thisObj + ".scalePivot");
			break;
			
			case "origin":
			move -a -rpr 0 0 0 ($thisObj + ".rotatePivot") ($thisObj + ".scalePivot");
			break;
			
			case "xmin":
			move -a -rpr $pos[0] $pos[7] $pos[8] ($thisObj + ".rotatePivot") ($thisObj + ".scalePivot");
			break;
			
			case "xmax":
			move -a -rpr $pos[3] $pos[7] $pos[8] ($thisObj + ".rotatePivot") ($thisObj + ".scalePivot");
			break;
			
			case "zmin":
			move -a -rpr $pos[6] $pos[7] $pos[2] ($thisObj + ".rotatePivot") ($thisObj + ".scalePivot");
			break;
			
			case "zmax":
			move -a -rpr $pos[6] $pos[7] $pos[5] ($thisObj + ".rotatePivot") ($thisObj + ".scalePivot");
			break;
		};
	};
};

Step 33

Our last procedure follows pretty much the same idea.

global proc epc_movePivotToObject() {
string $selLast[] = `ls -sl -tail 1`;
string $copyToObj = $selLast[0];
select -deselect $copyToObj;
string $selSet[] = `ls -sl`;
float $pivotSel[] = `xform -q -piv -ws $copyToObj`;
print $pivotSel;
	for ($each in $selSet){
	move -a $pivotSel[0] $pivotSel[1] $pivotSel[2] ($each + ".rotatePivot") ($each + ".scalePivot");
	};
};

There are only two new flags in this procedure:

  • -tail (-tl) only writes the last object selected to the array (ls command)
  • -pivot (-piv) queries the current location of the pivot on an object (xform command)

Step 35

Now all that remains is to make our buttons in the UI actually call the procedures we've written.

button -l "Center" -c "epc_movePivotLocal center";
button -l "Y Min" -c "epc_movePivotLocal ymin";
button -l "Y Max" -c "epc_movePivotLocal ymax";
button -l "Origin" -c "epc_movePivotLocal origin";
button -l "Selected" -c "epc_movePivotToObject";
button -l "X Min" -c "epc_movePivotLocal xmin";
button -l "X Max" -c "epc_movePivotLocal xmax";
button -l "Z Min" -c "epc_movePivotLocal zmin";
button -l "Z Max" -c "epc_movePivotLocal zmax";
  • -command (-c) calls a command or list of commands whenever the button is pressed

Step 35

And we're done!


Advertisement