mel ShapeEditor

In this tutorial I will go through how I created the final3 multiple shapeEditor. warning: this tutorial will be extremely long because I skip lines when I show examples. Experienced MEL scripters can skip a lot of this, but for an unexperienced one it will benefit them to have it listed like this.

MEL is extremely powerful. If you have the time and wish to learn MEL I welcome you to an incredible world!

Before you jump into scripting there are some stuff to consider:

  • Get an external textEditor ( "ultraEdit", "conText" ) or plug-ins ( "mel studio pro", "mel source") to script with because the script editor in Maya can't be used for this task... It lacks... everything!
  • Will the script be used by others? If so, write code other people can understand! Give important sections comments.

When I first decided to write this tutorial I though that I should skip all the rookie stuff and just dive into the reasons why I picked this and that solution, but then I decided to go through more stuff so it will be fit for a bigger mass of artists. I'll probably go back and forth between UI and commands because I go from top to bottom when I write the script.

If you have no experience with MEL we should go through some basic stuff. If you have some experience, skip this part:

Commands

The simplest thing you can find in MEL are the commands. Maya is build up by tons of commands. Every time you do something in Maya at least one commands is executed, mostly several. To figure out which commands Maya executes you should turn on "echo all commands" in the script editor. (Script --> Echo all commands). Your number one sources when it comes to MEL are the scriptEditor and the help files. These are awesome. Try creating a polygon sphere while you pay attention to the history in the script editor. What you will see when it is created is:

polySphere -r 1 -sx 20 -sy 20 -ax 0 1 0 -tx 2 -ch 1;

It returned a bunch of code here, but the essence is "polySphere". All the other things here are flags and values. You see a command is built up like this:

command [flags] [name]

The -r flag is the radius with a value 1 here and -sx and -sy are divisions in those axes with values of 20. Notice that the command polySphere does not have a name. If you look in the helpfiles for polySphere you see that this command actually does not have a name. ( you can use the flag "name" to name the sphere, but when I say name here I mean the ID at the end [name]) If you are insecure, check the help files. They are extremely helpful!

If we look at the window command the helpfiles show us this:

window [flags] [windowName]

There are also different modes for the flags. "Create" (which is default) , "Edit", "Query" and "Multiple use of flag". I'm going to go through some of them in the following example.

If we take a look at the command window we can add many flags, I'll use -width and -height as an example.

//We're going to create a window with a width of 100 and a height of 200.
window -width 100 -height 200 myWindow;
showWindow myWindow;


Now, what we did was create a window with a width of 100 and a height of 200. We also gave the window a name, "myWindow". This will ID the window later like when we want to open it ("showWindow myWindow"). You see, the window command doesn't show us the window, it just creates it in the background. To actually be able to see it, we have to tell Maya that we want to see it by using the command showWindow. So showWindow is another command you can look up in the help Files.

If we look at the command window again and check out the flags we see that there's a flag called
"-widthHeight". You can also see that inside the parentheses the short form for -withHeight is -wh. This means that we can choose to write the long form or the short form. There are disagreements whether you should use short or long. Personally I like to use short ones because the code will be shorter. On the other hand the long name will be more explanatory. When I edit scripts I tend to jump back and forth between Maya and the help files anyway...
Let's take another look at the window command:

//I want to know the width and the height of a window.
window -q -wh myWindow;


If you look at the history in the scriptEditor you get "result: 100 200". Of course we knew that, but you can use this for any window. If we scaled the window the result would have been different. When we want to gather information from a layout or other UI elements we have to put it in "query"- mode. Say we wanted to get the label of a button we'd write:

button -q -l myButton;

Now, let's try and edit the window size. If you're paying attention you'll know that we have to put the layout/element in edit - mode. The code would look like this:

window -e -wh 150 100 myWindow;

Now we edited the size to a new value. Keep the window open and change the values. You can see the window-size change realtime.

Variables

Say you're writing a letter in a bottle. What you do then is you write a letter and then you store it in the bottle before you throw it in the sea. You can look at variables the same way. You create a variable to store some data in it. Maya has several different types of variables and here's a look at some of them. I'll use the prefix "ma" for most of my stuff. (My initials)

  • string
  • array
  • int
  • float
  • vector

String

This is what the help files says:

Strings are sequences of characters. The literal representation of a string is surrounded by double quotes.
For example:
"MEL is fun!"
"abcdef012345"
":<>()&^%ABC"


A string can hold almost anything. If I want to create a string holding my name and some silly numbers, I'll type:

string $ma_stringVar = "Martin123";

If you want to check what the string holds you can simply use the print command to check it. Maya will print the value and return it in the history.

print $ma_stringVar;

This will print Martin123

Strings can also hold commands. If you want to execute a command and cast it ( cast = store the value INSIDE the string) to a string you have to put the command between backquotes ``.

string $ma_windowString = `window -t "windowTitle" windowID`;
showWindow $ma_windowString;


Since we did cast the value ( window command ) to the string we can call the string later like we did with the showWindow- command.

We can also use a string like this:

string $ma_windowName = "ma_superWindow";
window -t "myWindow" -wh 100 100 $ma_windowName;


If you read the code one step earlier you can see that I wrote the name "windowID". This time I used $ma_ superWindow because I had this declared ( declare = Declaring the variable tells Maya you intend to use a variable with this name, and specifies the type of values the variables can hold.) earlier and therefore I could use it now.

Arrays

From the help files:

An array is an ordered list of values. All values in an array must be of the same type. You can make arrays of integers, floats, strings, or vectors. Arrays grow as you add elements to them.

To declare an array variable, use:

  • the keyword of the type which this array will hold,
  • then the variable name,
  • add square brackets ([]) to the end of the variable name.

int $ari[];

The reason I go through array this early is because we use it very often with strings. Let's go through an example:

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

We tell Maya that I want to "fill" the array with values of the kind "string". Then we tell Maya that this is supposed to be an array by adding square brackets "[]". If you look at the backquotes you know that I immediately want to cast a value to the array. I used the command "ls" which is used to list several things.In our case here, selected objects. To make it interesting I want you to do this:

  • create 5 polySpheres.
  • select all of them
  • insert the code above in the scriptEditor
  • highlight all the code and hit numerical- enter and read the result in the historyWindow.

The result will be:

// Result: pSphere1 pSphere2 pSphere3 pSphere4 pSphere5 //

So now the array actually contains the result above. If you want to check that out, use the print command to get maya to print the values.

print $ma_listSelectedObj;

When we write it like this we tell Maya to print the whole array. If we want to print one of the values, say pSphere2, we have to say

print $ma_listSeleectedObj[1];

Now you might have been thinking "Isn't pSphere1 the FIRST value here? Shouldn't we be printing
$ma_listSelectedObj[2]?". You see, Arrays start at 0 so the first thing in the list will be $ma_listSeleectedObj[0].

You don't have to cast values to the array via a command. You can also define your own values like this:

string $ma_myColors[] = {"red","white","blue"};

The syntax: curly braces at the start and end. Semicolons to define the values and commas to separate them. There are no back quotes here because we do not use a command to cast the values to the array. This is actually how we define arrays manually.

If you want to edit the arrayItems you can do this:

string ma_myColors[1] = "black";

Now you suddenly get "red" "black" "blue" if you try to print the array again.

Floating point numbers

As opposed to an integer a floating point number is a number with a fractional part. 10.1255 is a float while an integer can't display what's after the comma. I say float because that's the short form for floating point number. Let's declare a float:

float $ma_myFloat = 1.456;

You can declare a float like this or you can put the number between quotes like you'd do with a string. You'd want to use a float for something like translate values:

float $ma_getTranslateX = `getAttr pSphere1.tx`;

A computer calculates integers faster than floating point numbers so if you can use integers, do so!

Vectors

From the helpfiles:
A vector is a triple of floating point numbers (usually representing X, Y, and Z). It's convenient to have a triple-float data type in MEL because so many operations in 3D involve manipulating X,Y,Z values.

So as it says here vectors are pretty nifty. You don't think much about it, but every time you move an object you set a vector value. It's pretty easy to illustrate:

vector $ma_myVector = `getAttr pSphere1.translate`;

This will return <> , three floats. You can use several vectors to create new vectors, but this is math and I won't go into that for now.

Concatenation: You can also add variables together. Say you create two strings, $ma_hello and $ma_name

string $ma_hello = "Hello";
string $ma_name = "Martin";


If you want to add these up you simply go

string $ma_sayHello = $ma_hello + $ma_name;

Here I declared a new variable and I cast the values of the two other variables into it. If you print $ma_sayHello you can see that the result is HelloMartin. We would probably want a space between those and we have to do that manually.

string $ma_sayHello = $ma_hello + " " + $ma_name;

You could also add + "\n" so Maya goes to a new line when printing the result. You can also write new strings into there like this:

string $ma_sayHello = $ma_hello + " there " + $ma_name + "\n";
print $ma_sayHello;


The \n is a "special character" in MEL. There are several of these characters:

\" - quotation mark
\n - newline
\t - tab
\r - carriage return
\\ - backslash


Now experience with different variables and concatenate them in different ways.



This is the very basics. With what you know now you can create some basic stuff, but this is far from enough to create more advanced scripts with user input or repeating code with different values etc. I would like to go through three more things before I actually attack the script I promised I'd go through.

  • if - statements
  • loops
  • procedures

Sure you can use strings here and there to build something now, but it's when all of this starts reacting to each other the fun starts.

if Statement

What do you do when you are thirsty? You go get a drink... If I'm not thirsty, I'll not get a drink. For simplicity I'm going to make an int called thirsty.

int $ma_thirsty = 1; //This represents me being thirsty...

Now I've defined that I'm thirsty so Maya will understand this. Then I'll make Maya ask me if I'm thirsty and if I am I want her to tell me that she'll bring me a glass of water in a minute. If I'm not thirsty I'll make her tell me to let her know when I am. I'll write down the code first and then go through it:

if ($ma_thirsty == 1){
print "I'll get you a glass of water in a minute";
}


Then you can go:

if ($ma_thirsty == 0){
print "Let me know if you get thirsty then:)";
}


But in Maya you can also say "if variable is true --> do this", "else --> do that". So we could rewrite this:

if ($ma_thirsty == 1){
print "I'll get you a glass of water in a minute";
}


else{
print "Let me know if you get thirsty then:)";
}


What we say here is "if variableis 1, get glass of water". "If it's anything else, let me know". So if the variable was 0, 2, 3, 4, 6 or whatever, but 1 it will print "Let me know if you get thirsty then:)"... Let's say we want the if statement to print something else when the variable is 2, but still print the other things we already stated we could use something called else if:

if ($ma_thirsty == 1){
print "I'll get you a glass of water in a minute";
}


else if ($ma_thirsty == 2){
print "Get it yourself";
}


else{
print "Let me know if you get thirsty then:)";
}


So now Maya takes "2" into consideration too while filtering all the other numbers out in the else- section. 1 of course will still be in the first if statement.

We could have declared the variable as a string too. We could check if the string said "yes" or "no" instead of 1 and 0. 2 could have been "maybe" and so on. Notice the syntax here:

if(){

}


What you want to check goes in between the brackets, the commands you want to execute based on the check goes between the curly braces. There are two ways of writing the curly braces. Right now I'm in a conversion. I used to write it like this below because it is easier to read, but now I save a line by writing it like above. If you check the scripts Alias wrote you can see they also write it like this.

if ()
{

}




You can also use a command directly in the if statement. Let me write down an example:

string $ma_testArray[] = {"red","white","blue"};

if (`size($ma_testArray)` == 3){
print "yup, you have the colors of the norwegian flag";
polySphere -n "superSphere";
window -e -wh 100 100 $ma_superWindow;
}


I have to use backquotes because the command is executed while I check it. Remember how we used back-quotes when we declared a variable? We had to do that because we wanted the result of the executed command cast into that string. This time we want to execute the command directly to check the value. Size is a command you can look up in the helpfiles. I will not go through all commands I use because there are so many of them and some of the fun in MEL is to discover new commands and flags. I also had to use double equalsign. When we use a single one we give the one side a value of the other side. When we use double we check if the two sides are equal or not.

The reason I put those other commands in the if statement was just to show you that you can have several commands between the curly braces. You can put loops in here, procs, commands... Anything you want.

Next page will deal with loops.

for Loops

I had a REALLY hard time understanding loops. I really twisted my head for days and weeks, but I just couldn't get them into my head. Now that I understand loops I can't manage to script without them. Loops will deal with repetitive tasks and save you many many lines.

From the helpfiles:

for (initialization; condition; change of condition) {
statement;
statement;
...
}


There are more info on it in the helpfiles. This is just a portion.

I'll go through two examples. One loop creates 11 nurbsSpheres, another loop will create buttons for a UI.

int $ma_i;
for ($ma_i = 0; $ma_i <= 11; $ma_i = $ma_i +1){
sphere -name ("spherName" + $ma_i);
}

window -t "just a test" ma_superWindow;
rowColumnLayout -nc 5 ma_superColumn;


for ($ma_a = 1; $ma_a<= 50; $ma_a++){
button -l ("nr." + $ma_a);
}


showWindow ma_superWindow;

I know the layout stuff appeared a bit early here, but I needed it to show the example.

OK, now I will go through the code:

example one:

  • First I declare a variable ($ma_i) (note that you don't have to declare it before the loop, it actually gets declared inside the loop when you set the initialization)

  • The initialization requires that I cast a value to the integer I just declared. Since I want it to start at "0" I set $ma_i = 0.
  • The next thing to consider is the condition. (How long should this loop last.) All I want is 11 spheres so I'll say "as long as there are 10 or less spheres", create one.
  • The change of condition will be "add one to the variable" so that the next time you loop the variable will have another value. In this case the previous value + one. ($ma_i = $ma_i +1)

The spheres clearly illustrates this. The command was

sphere -n ("sphereName" + $ma_i);

Since the flag -n (name) expects a single string I have to concatenate inside brackets.You can see that the names of the spheres start at 0 and ends at 10. If you change the initialization, condition
or the change of condition you'll see what happens. If you want the naming to start at "1" and stop at "5" you simply change the code to this:

int $ma_i;
for ($ma_i = 1; $ma_i <= 5; $ma_i = $ma_i +1){
sphere -name ("sphereName" + $ma_i);
}


See where I'm going with this?

example two:

  • I declared the variable inside the loop here ($ma_a = 1)
  • I say that the loop should continue until I have 50 buttons.
  • And then I suddenly write $ma_a++. But that's not what I wrote down earlier. This is the same as $ma_a = $ma_a + 1; The reason I can write it like this is this:

in programming these two lines mean the exact same:

$variable = $variable +1;
$variable += 1;


You see that since I cast a new value to the variable using the variable itself I can throw the equation around. Say the variable $variable was 2. After executing the first line I get $variable = 2+1. If I print this variable now I get 3. So I actually redeclared it. Since we're adding "one" I can actually leave the right side out and write $variable++ ( guess how you subtract with one ;) ). This is also very helpful when we write expressions.
For instance:

pSphere.rotateX += time; // this will rotate the X value with itself and add time.

Then the command is very simple. It's just a button command with the label ("nr." + $ma_a). This is similar to the sphereLoop.

for- in loops

This kind of loop is very effective to use when you want to change something for many objects. I'll go through an example that shows this very well. I'll create a variable where I store all the selected objects and then I'll translate all of these objects in random directions.

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

for ($eachObj in $ma_selectedObj){
setAttr ($eachObj + ".translate") 0 1.2 0;
}


Say you create 5 polySpheres, select them and list them in the string $ma_selectedObj[]. The command "ls" lists things and the flag "-sl" is for selected. So we list all the selected objects in that string by using the command. Also, we need the backquotes because we use a command to cast the value into it.

This is what I get in the scriptEditor when I declare- and print the variable:

string $ma_selectedObj[] = `ls -sl`;
// Result: pSphere1 pSphere2 pSphere3 pSphere4 pSphere5 //
print $ma_selectedObj;
pSphere1
pSphere2
pSphere3
pSphere4
pSphere5


So if we manually want to set the translate value to 0 1.2 0 we have to set this value 5 times like this:

setAttr ( $ma_selectedObj[0] + ".translate" ) 0 1.2 0;
setAttr ( $ma_selectedObj[1] + ".translate" ) 0 1.2 0;
setAttr ( $ma_selectedObj[2] + ".translate" ) 0 1.2 0;
setAttr ( $ma_selectedObj[3] + ".translate" ) 0 1.2 0;
setAttr ( $ma_selectedObj[4] + ".translate" ) 0 1.2 0;


If you have 5000 objects you can't do it like this... That's why we create the loop. The for-in loop does this by taking the first object in the list and executing the command. Then it reads what the next item in the selection is and does the same with that one. When there are no more objects left in the list to pick from the loop stops. The $eachObj variable here will substitute $ma_selectedObj[0], then [1] and so on and so forth.

This loop sends me into another thing I'd like to talk about. If I want a random value for all the translate-values and I want to use them inside the loop, I have to declare the random values INSIDE the loop. If I declare it outside the loop the random value will not be redeclared for all the objects. I'll write down the wrong way and the correct way here so you can try them both.

//First off I cast the selection to a string
string $ma_selectedObj[] = `ls -sl`;


//I'll use a vector to set the random values:
vector $ma_translateVector = <>;


//As you can see above the translateAttribute need three floats to determine the values //for x, y and z. You can also use translateX, translateY or translateZ to set the
//value for just one of them.


//Now, here's the loop:
for ($eachObj in $ma_selectedObj){
setAttr ($eachObj + ".translate") ($ma_translateVector.x) ($ma_translateVector.y) ($ma_translateVector.z);
}


The problem here is that within the loop we get the same random values all the time because we declared it once outside the loop. If we want a random value for each element in the loop we have to make sure Maya gets a chance to redeclare the variable every time the loop is executed. To do this we need to put the variable INSIDE the loop like this:

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

for ($eachObj in $ma_selectedObj){
vector $ma_translateVector = <>;
setAttr ($eachObj + ".translate") ($ma_translateVector.x) ($ma_translateVector.y) ($ma_translateVector.z);

while loop

From the helpfiles:

A while loop has the form:

while (condition) {
statement1;
statement2;
...
}


The while statement checks the condition, and if it's true, executes the block, then repeats. As long as the condition is true, the block continues to execute.

So a while loop is actually a loop that "includes" an if statement. While something is true, execute the code inside the curly braces.

int $ma_while = 2;

while ($ma_while < 5){
sphere -n ("sphereName" + $ma_while);
$ma_while++;
}


if you don't increase $ma_while here Maya will crash because $ma_while will ALWAYS be less than 5. You can also look up do - while loop It's built like this: do this (execute commands) while statement and you could also look up "switch...case" if you like.

procedures

There is a way in Maya to collect several commands in something called a procedure. There are local procs and global procs. If you want to use the proc outside the script you have to make it global. When we create a button and we want that button to call the procedure we have to make it global because the window is outside the script. (in Maya, but outside the code...) Let's define a proc.

global proc ma_createPrinitives(){
polySphere;
polyCube;
polyPlane;
}


The syntax: "global proc" tells Maya that we want to define the procedure. "ma_createPrimitives" is the name of the proc that we have to use when we want to call it later. The brackets can include arguments (which we will go through next.) Now you can see that we have three commands between the curly braces and that's basically it for this proc. If you highlight the code above, copy it into Maya and run it you see that nothing happens. This is because we just store the info in Maya. If we want the chunk of code to be executed we have to call the proc by writing ma_createPrimitives. Nothing more, nothing less. If you use it in a longer code you have to terminate the line with ";" of course.

Now I'll go through an example with an argument:

global proc ma_print (string $name){
print ("Hi, my name is " + $name);
}


Here you can see that the argument is a string. You can have several arguments, you just have to separate them with a comma. We'll use two arguments in the next example. When we use an argument like this we tell Maya that we don't have a defined name right now so we depend on user interference. The user/ or scripter have to tell Maya which name to print. When we call the proc it looks like this:

ma_print Martin;

this will print "Hi, my name is Martin". If we write ma_print Robert it will print "Hi, my name is Robert". Now we'll include an int called $age.

global proc ma_print (string $name, int $age){
print ("Hi, my name is " + $name + ". I'm " + $age + " years young");
}


So if I write ma_print Martin 24 it will print "Hi, my name is Martin. I'm 24 years young". Just as a final example I'll create a proc that I'm actually going to use with a button. I'll start by creating the window:

window -t "test" ma_labelWin;
columnLayout;
textField -tx "insert label here" -w 150 ma_labelText;
button -l "click me to change label" ma_labelButton;
showWindow ma_labelWin;


This will make a window that looks like this:

We can change the text here, but when we click the button nothing happens. We want the button to change the label on itself to whatever is in the textField. The proc would look like this:

global proc ma_changeLabel(){
string $ma_getLabel = `textField -q -tx ma_labelText`;
button -e -l $ma_getLabel ma_labelButton;
}


You can see that I get the input from the textField and cast it into the string $ma_getLabel by querying -tx. Then I put the button in edit mode to edit the label for the ma_labelButton. The label will get the new value $ma_getLabel. The final thing we have to do now is to "attach" the proc to the button. If you check the helpfiles you can see that the button has a "command-flag". What we do now is put the proc as a value to that flag. Also I included an if statement that checks if the window exists. You know the error you get whenyou try to open a window that already exists, this will prevent this.

if (`window -ex ma_labelWin` == 1){
deleteUI ma_labelWin;
}


window -t "test" ma_labelWin;
columnLayout;
textField -tx "insert label here" -w 150 ma_labelText;
button -l "click me to change label" -c ma_changeLabel ma_labelButton;
showWindow ma_labelWin;


global proc ma_changeLabel(){
string $ma_getLabel = `textField -q -tx ma_labelText`;
button -e -l $ma_getLabel ma_labelButton;
}


Go through the code and see if you understand it. If not, break it down. Look at the sections:

  • You check if the window exits (`window -ex ma_labelWin`). This will return 0 or 1 depending if the window is open or not. You don't need to write == really. Think of it like this: "If icecream..." If icecream exists it has to return 1.
  • window [flags] [name]
  • a layout for the window
  • textField [flags] [name]
  • button [flags] (the most important flag "-c /-command") [name]
  • the showWindow command to show the window.
  • Defining proc.
  • query the text written.
  • use the queried text to edit the label for the button.

final 3_mulitpleAttrEditor

I think it's time to go through the script now. You probably have enough knowlege to attack scripts now. Remember that even if something here is not too clear right now you'll learn from experience. Remember how I told you I couldn't get my head around loops? I wasn't kidding when I said it took weeks. Even when someone explained them to me I just couldn't. Stupid, stupid, stupid... Then I started using them and I felt more comfortable with them right away. You will learn faster by doing. Everyone knows this! Do it!

Ok, what I want the script to do is this:

  • List the objects I want to edit. I want to be able to select just some of the objects from the list too. I also want to be able to change the selection without reopening the window.
  • I want a layout with check boxes for the renderStats.
  • I want a button that cycles through all the choices I made with the check boxes and applies the new render settings.

When I picture this script in my head it looks like this: ( quick paint over...)

I'm also writing down which commands I think I'll end up using. Programmers have their own way of scripting. Someone likes to do all the code first and make that work before they even think about the UI, others script the UI first and then worry about the core. I am a 3d artist and not a programmer so I tend to care more about how thing looks, maybe a bit too much really :) But anyways, with this script I'll go from top to bottom:

window: You should ask yourself: "Does the script really need a UI?" If you don't rely on user interaction there is no need for a UI. It could be a shelfButton instead. In this case we actually need an interface. I'll also make sure Maya deletes my window if it exists, just like we did earlier.

if(`window -ex ma_multiAttrEdWin`)deleteUI ma_multiAttrEdWin;

window -t "final3 Multiple renderStatsEditor" -wh 280 294 ma_multiAttrEdWin;
showWindow ma_multiAttrEdWin;


You might noticed that the deleteUI-command is on the same line as the if statement and I also left the curly braces out. When you only have ONE line to go between the curly braces you can skip them. For beginners I recommend you stick with the curly braces so you won't forget them later. Stick with this code for a while:

if(`window -ex ma_multiAttrEdWin`){
deleteUI ma_multiAttrEdWin;
}


I gave the window a title, defined the width and height using the -wh flag and I also gave the window an ID. But in Maya you will always need some sort of layout to hold anything at all. If you try to put a button in there between the window and showWindow command you'll get this error:
// Error: line 4: Controls must have a layout. No layout found in window : ma_multiAttrEdWin //

If you hit f1 you can read more about Maya's UI controls. Under Developer resources -> Mel commands you'll see them listed on the far right:

I'd say the most common layout is columnLayout. If we look at the sketch I drew we see that I need more than one column, I need two ( one for the textScrollList and one for the checkBoxes. I still have to use a columnLayout to hold it all together though so I create a columnLayout first and then the rowColumnLayout. In a rowColumnLayout you can define how many columns you want per row:

window -t "final3 Multiple renderStatsEditor" -wh 280 294 ma_multiAttrEdWin;
columnLayout;
rowColumnLayout -nc 2 -cw 1 152 -cw 2 120;
showWindow ma_multiAttrEdWin;


What the flag -cw does is define the width for the columns. The width for column 1 should be 152 and the width for column 2 should be 120. These are numbers I have found best for this exact script. You need to try out many values here and there before you find something that fits. When you create a window like this you'll have to nest several layouts and controls. There are different ways of achieving the same looks so basicly do it the way you feel like doing it as long as it works. This is how I set up the layouts for my window:

window -t "final3 Multiple renderStatsEditor" -wh 285 295 ma_multiAttrEdWin;
columnLayout;
rowColumnLayout -nc 2 -cw 1 154 -cw 2 122 -h 230;
frameLayout -l "objects:" -fn "smallFixedWidthFont" -bs "etchedIn" -h 232;
columnLayout;
textScrollList -w 150 -h 190 -ams 1 ma_multiAttrEdTSL;
button -l "update selection" -w 148;


setParent..;
setParent..;
frameLayout -l "options:" -fn "smallFixedWidthFont" -bs "etchedIn" -h 200;
columnLayout ma_optionsColumn;


showWindow ma_multiAttrEdWin;

If you execute this code you get this:

You can see how the rowColumnLayout sets the different width for the two frame layouts, I have labels for both my frameLayouts and I also have the controls textScrollList (empty white list) and a button. The reason I haven't put in the controls for the options-side yet is because I plan to do this in a very different way. You can also see that I put in a new command called setParent..; What this command does it to guide us out of the current layout and one step upwards. See this paint over:

  • The green layout is the main columnLayout. This is the first layout we put ourselves in.
  • Then we create the rowColumnLayout. Now we are inside this one and this layout is also inside the green one
  • Then we create a frameLayout. This layout is inside the rowColumnLayout that is inside the green one.This frameLayout represents the first column in the rowColumnLayout.
  • The layouts are defined and we create a columnLayout to hold the controls. This layout is then inside the frameLayout. So far we have gone like this:
    columnLayout-->rowColumnLayout-->frameLayout-->columnLayout...
    So when we go back once (setParent..;) we go back to frameLayout. Then the second setParent will guide us back to the layout above this one again which is rowColumnLayout. Now we are ready to define the second column. So after the second setParent we simply write frameLayout because that's the layout we want for the optionsColumn. It would have been easier to see this if we named the layouts, but I only name the ones I know I'll be modifying later.

Ok, now that we have the basic layout we can start fixing the parts one by one. I will start with the textScrollList. I want this control to list selected objects. Now, we have already talked about the command we have to use here "ls". But how are we going to get this command to throw values into the textScrollList. We create a proc for this, see code below (I had to put the code in a table to make it more readable. If you wish to copy the code, use the code with a white background below.):

global proc ma_multiAttrEdList ()
{
string $ma_selected[] = `ls -sl`;
if (!(`size($ma_selected)`))
{
warning "nothing selected. List will not display any objects";
}


else
{
textScrollList -e -ra ma_multiAttrEdTSL;
for ($ma_sel in $ma_selected)
{
string $ma_pickWalked[] = `pickWalk -d down $ma_sel`;
textScrollList -e -a $ma_pickWalked ma_multiAttrEdTSL;
}
}
}


Now this proc has a lot of actions, it includes an "if statement" and a "for in loop". So now we get to see how this really works. I'm also going to explain the commands.

  • global proc ma_multiAttrEdList () : This is just the name of the proc, I have no arguments here.
  • string $ma_selected stores the selected objects in an array.
  • if (!(size($ma_selected)`){...}: I check if the size of $ma_selected is NOT. If it's not, it has to be "0". Then inside the curly braces I have a print command telling the user that nothing is selected. We need a selection for this script to work properly.
  • else: This is what will happen if we have a selection.
  • textScrollList -e -ra ma_multiAttrEdTSL: First we want to empty the textScrollList in case there were already a selection present. If you have something listed there already and you appended another selection it would simply add to the bottom of the list. What we want is the list updating with the new selection and therefore we have to empty it first and then add the new selection.
  • for ($ma_sel in $ma_selection) {...} Here we tell Maya that we want to do something to each object in the selection. Of course I want to append each object to the list. Well, not really, I want the SHAPES to be listed because they are the ones being manipulated when we change the renderStats.
  • string $ma_pickWalked[]: There is a command called pickWalk. If you check the documentation it has certain directions and the one we are looking for is "down". listRelatives could probably also do the trick. Try it out if you want to.
  • Last thing is an edit of the textScrollList. We want to append the shapes which is stored in $ma_pickWalked and the layout being edited is ma_multiAttrEdTSL.

That's basically all the proc does. Now I'm going to do two things with this proc. First I'm going to put it in the command for the button and then I'll put it somewhere in the code between "window" and "showWindow". This way the list will be updated if the user has a selection when he/she opens the window. The changes are in bold letters:

if(`window -ex ma_multiAttrEdWin`)deleteUI ma_multiAttrEdWin;

window -t "final3 Multiple renderStatsEditor" -wh 285 295 ma_multiAttrEdWin;
columnLayout;
rowColumnLayout -nc 2 -cw 1 154 -cw 2 122 -h 230;
frameLayout -l "objects:" -fn "smallFixedWidthFont" -bs "etchedIn" -h 232;
columnLayout;
textScrollList -w 150 -h 190 -ams 1 ma_multiAttrEdTSL;
button -l "update selection" -w 148 -c ma_multiAttrEdList;


setParent..;
setParent..;
frameLayout -l "options:" -fn "smallFixedWidthFont" -bs "etchedIn" -h 200;
columnLayout ma_optionsColumn;
ma_multiAttrEdList;
showWindow ma_multiAttrEdWin;


You can try it out. If you click the button after selecting some objects the list gets updated. Funny stuff huh? :)

Now the tricky part begins. I want to build the checkboxes and I also want to create a button that changes the renderStats... Well, you might think. That shouldn't be a problem. You can just add a lot of checkBoxes and create integers that check the values and then you can just let the button go through all that?

Well, I don't want to use a lot of lines for this and I also want it to be easy to remove or add checkBoxes. Therefore I create an array that holds all the info I need to create the checkBoxes and the command for the button. Then there's some mixin' and trixin' to nest all this together. I'll write down the code and go through it step by step.

//Array that defines the commands I want to use
global string $ma_meshArray[] = {"castsShadows" , "receiveShadows" , "motionBlur", "primaryVisibility" ,
"smoothShading" , "visibleInReflections" , "visibleInRefractions" , "doubleSided" , "opposite"};


I know which attributes the renderStats hold so I simply create an array that can store all of these. Now I can use these as labels as well as commands. ( Because I do it like this the labels will not have the EXACT same name as in the attributeEditor.) The way I found out which strings to write down was simple. I turn "echo all commands" on in the scriptEditor, select an object, open the attributeEditor and check/uncheck stats. Then I can read directly in the scriptEditor what Maya changes. Notice that I declared the array as a GLOBAL string. This is because I want to use it later inside another loop and if I want to use strings like that I have to make them global.

//A loop building all the "options" - UI
for ($i = 0; $i < `size($ma_meshArray)`; $i++)
{
checkBox -l $ma_meshArray[$i] -v 1 -p ma_optionsColumn ($ma_meshArray[$i] + "checkBox");
}


You should be able to read this now. This is very close to the example I used earlier, only the command here is different. The loop checks the size of the array ( which is 9 (count the strings in the array above) ) Let's say we go through the loop with the first string in the array as an example "castsShadow".

checkBox -l "castsShadow" -v 1 -p optionsColumn castsShadowcheckBox;

Notice that there is a flag here "-p" that parents this checkBox to the optionsColumn. This column was empty earlier. Now it's time to fill it. We'll fill it the way we filled a window with 50 buttons in the loop example.

If you look at the sketch again you see that I also want a button here. So right after the loop I add these lines: ( I decided to put a separator there too...)

separator -h 46 -w 115;
button -w 116 -l "Edit RenderStats";


Now we already have a UI where the button on the left can update our list and we also have a lot of check boxes here :) Pluss a separator and a button ( A button that doesn't do anything yet... )

Let's see if the checkBox loop works. This is how it looks so far:

I think it's looking very good. This is exactly what we wanted it to look like. I have also selected a few primitives to check what that looks like.

The remaining stuff to add here are two procs. The first one is a bit advanced, but the other one is rather easy.This is the first one:

global proc ma_renderStatCommand (int $ma_whichCommand)
{
global string $ma_meshArray[];
string $ma_listShapes[] = `textScrollList -q -si ma_multiAttrEdTSL`;
for ($ma_shapes in $ma_listShapes)
{
setAttr ($ma_shapes + "." + $ma_meshArray[$ma_whichCommand]) (`checkBox -q -v ($ma_meshArray[$ma_whichCommand] +
"checkBox")`);
}
}


Here we actually have a proc with an argument for the first time during the scripting of this script. Time to puton the "focus-hat" ladies and gentlemen...

  • ma_renderStatCommand (int $ma_whichCommand): Just the name of the proc and we also tell Maya that we wish to use an integer to do something inside the proc.
  • global string $ma_meshArray[]; But this is the variable we declared earlier in the script... Yes it is, but we have to tell Maya that we intend to use it her too. Since we are inside the proc, Maya pays no attention to what's on the outside. We just went inside a proc-house. Now we say "Maya! Remember that I declared a value outside the proc?... Well I intend to use it again so I'll just redeclare it in here OK?".
    We're allowed to use it again as long as we redeclare it. However you don't have to cast values to it. Maya will remember the values.
  • Then we query the selected objects in the textScrollList ( -q -si (selectItem) ) and we cast the list to the array $ma_listShapes.
  • We use a for in loop to go through all the selected objects and setting the new renderStats for them. Now, let's take a closer look at the command in that loop....
    --> setAttr ( command for "set attribute" )
    --> ($ma_shapes + "." + $ma_meshArray[$ma_whichCommand]) ( object + attribute ) Here we include the integer we wanted as an argument. This means that when I call this proc I have to write:ma_renderStatCommand 0 to set the value for castsShadows and so on.
  • (`checkBox -q -v ($ma_meshArray[$ma_whichCommand] + "checkBox")`): But since setAttr needs two arguments like this: "setAttr object.attribute value" we need to add this. This line is a bit tricky. We directly query the state of the checkBox and then this becomes the value we set (say we call the proc again with the argument 0: checkBox -q -v castsShadowcheckBox ) If it equals 1 the attribute will be turned on, if it equals 0 the attibute will be turned off.

But what are we going to use this proc for? We cant call it 9 times to go through it? ( the array have 9 values, but starts at 0 remember so the last one is 8 / see the next proc ) Are we going to assign that proc to the button? That won't make sense will it?

We build another proc, that's what we do. And inside that proc we'll loop through all the checkBoxes and set the attributes according to the queried values. Here it is:

//Check the checkBox-values and edit the renderStats:
global proc ma_cycleValues()
{
for ($i = 0; $i <= 8; $i++)
{
ma_renderStatCommand $i;
}
}


This proc does all the job for us. It inserts the integer we need as an argument for the previous proc and loops through it by setting 0, 1,2 and so on... It will loop through all the checkBoxes and set the attributes USING the other proc... Pretty clever huh? Now this is the proc we'd want to add to our button. Now you already know how to do that so I won't write down the code for that here. Of course you can download the code below here and you can also download my final version of this script on the resources- page. This is how my version looked when I was finished with it:

You should be able to make something similar if you browse the helpFiles a bit ;)

NOTE: The script we wrote together is downloadable here, but it's not cleaned. I suggest you download the one in the resource section and compare them. Because the script I have there is an official release and therefore the script is commented, icons included and also a readMe with install-notes are supplied.

There are several things you could do to improve the script, but I think this will do for now. Of course you can do some research and try rewriting it to do other tasks too :)

In this tutorial I have gone through the very essence of MEL scripting. I have only scratched the surface, but I feel the things I went through her is essential if you want to write short code that can do big things.

--> commands
--> variables
--> strings, ints, arrays, floats
--> if statements
--> loops
--> procedures.

And as I have said before: Even if it can seem a bit unclear you'll grow with experience and learn while doing. I'm just trying to push you in the right direction here. If you have any comments or questions you can reply in this cgTalk thread or use the contact form on my site to get in touch with me directly.

to download, rightClick and

to download, rightClick and

Fetching comments...

Post a comment