Meta-level code
There are many situations in which it can be extremely useful to have code generate code. Since MEL is never compiled, all that really means is creating appropriate strings that happen to be valid MEL syntax. By having code generated at run-time, you can create scripts that are customized for the situation at hand. This is especially useful when creating GUIs such as character rig controls that need to be based on the current scene.
In order to generate working code, one has to do two things- manipulate strings in the appropriate way, and find a way to have thier contents run as MEL commands.
String variables in MEL
Strings serve many purposes in MEL, and it would be hard to write a script that didn't have at least one string variable. But as varied as their uses are in the specifics, they can be divided into three categories:
- Input/Output text
This is the simplest way to use strings- they just hold some text that you want to print to the screen, or serve as containers for user-inputted text. Since most scripts have little to do with text, strings are usually only used in this way for feedback and debugging.
- Names of objects/nodes
In order to manipulate anything via MEL, you must first know its name. No matter what kind of object you're dealing with, the name will always be a string. The most common way that this comes up is to store the name of a recently-created object to a string variable in order to manipulate the object later.
- MEL code
There's nothing stopping you from filling up a string variable with text that just so happens to be valid MEL syntax. However, there are a couple of gotchas when building the string, and you'll still need to find a way to have the code execute
Building up string variables for execution as MEL
Once you've identified some MEL that you want to generate as a string variable at run-time, the first thing that you have to do is to determine a generalized form of the code you want to create, and then identify the portions of it that change and those that don't. Anything that changes needs to be a string variable, while anything that doesn't should be a string literal (a quoted string). This often means that you'll switch back and forth between literals and variables very often, which can make for an end result that looks far more complicated than it actually is.
Let's walk through a simple example. Let's say that you want to write a script that sets the translate Y attribute of a series of objects to a specified amount. A specific instance of such a command would look like:
setAttr "nurbsSphere1.translateY" 5;
Okay, so the above is a valid line of MEL, and will work as long as it's nurbsSphere1 that we want to alter, and the value that we're shooting for is 5. However, if we want to translate this into a general-purpose command, we need the ability to change both the name of the object that is being referenced and the value that the attribute is being set to.
So since there are two things that need to change, we'll need two variables- one for the name and one for the value. Everything else will be wrapped in quotes. So we end up with four components:
- "setAttr" and the opening quote
- the name of the object as a string variable
- ".translateY" and the closing quote
- the value as a float variable
Strings, whether quoted literals or variables, can be connected (concatenated) by using the plus sign. As an added bonus, Maya is smart enough to know that if you add a numeric variable onto a string, the number(s) should be treated as characters.
Note that both the first and third portions will need to include a quote. This is a problem, as quotes signify the end or begining of a string literal. Including a quote within a string literal will cause it to be ended prematurely and lead to syntax errors. There's an easy solution, though- all we have to do is to "escape" the quote with a backslash, as in "\"", which would be translated into a single quote upon parsing.
So our four parts become:
- "setAttr \" "
- $objectName
- ".translateY\" "
- $value
Tacking the pieces together and storing them into a string variable ends up looking like:
string $command=("setAttr \""+ $objectName+ ".translateY\" "+ $value);
Note that a space was included after the escaped quote in the ".translateY\" " section. This is important and easy to overlook. If you're not careful to always include spaces, you'll end up with commands and keywords running together and generating sytax errors.
When you start building up complex, multipart string commands, things can get pretty messy pretty quickly. It's often easier (at least while building/debugging) to build it up part by part, as in:
// start the string out with the first part
//(in this case the setAttr literal)
string $command = "setAttr \"";
// add in the name of the object
$command += $objectName;
// add in the quoted attribute name
$command += ".translateY\" ";
// add in the value
$command += $value;
This can get confusing pretty quickly, so I've written a script to make it easier. The psConvertText.mel script allows you to enter in some code and move as many levels up as you need to.
It will automatically escape both quotes and backslashes for you. It also allows you to specify specific parts of the code that should be replaced with a variable name. The literal parts of the text will be encased in quotes, and the variables will be moved to a new line, with plus signs connecting them. So clicking on the "Up a level" button with the above input would give you the following result:
"setAttr \""
+
$objectName
+
".translateY\" "
+
$value
+
";"
Click HERE to grab the script.
Getting Maya to execute strings
Once you have a string that's combination of literals and variables adding up to a valib MEL expression, you'll need a way to have Maya actually run the code. There are a number of different ways to do that-
- Pass the string to the
eval command, causing Maya to run it immediately
- Use the string as the command string for an interface element. This can take a number of different forms, from dropping it onto a button such that it executes when pressed, or tying it to a
-dragCommand, -changeCommand, or similar
- Dropping the string into an expression via
expression -string $commandString
- Dropping the string into a script job via the
scriptJob command
- Dropping the string into a script node vis the
scriptNode command
- Save the text to a new file, either a .MEL file for use in Maya, or even as a source file (using appropriate syntax) for a different language alltogether
As a small example, lets take a look at a script that presents the user with a gui like the following:
Selecting an object, entering a name of an attribute and a value, and clicking on the "Add button" button will add a new button to the GUI that will set the specified attribute to the specified value. For that, we'll need to build up a command string at run time from a few components. We'll need to build up a string similar to the one that we saw in the previous section, in that it will need to have the general form of:
setAttr "objectName.attributeName" value;
This time around, we have three things that will be changing:
- The name of the object (grabbed from the current selection)
- The name of the attribute (entered by the user)
- The value to set the attribute to (entered by the user)
So putting that all together gives us six different parts:
- "setAttr" and the opening quote
- the name of the object
- the dot to connect the object and attribute names (as a string literal)
- the attribute name
- the closing quote (as a literal)
- the value to set it to
If we wanted to set multiple different attributes at once, we would need a seventh component- a closing semicolon as a string literal.
Grabbing the variables we'll need is easy enough- it ends up looking like the following:
// grab the list of currently selected objects
string $temp[] = `ls -sl`;
// store the first item in the $objectName variable
string $objectName = $temp[0];
// grab the attribute name
string $attribute = `textField -query -text attName`;
// grab the attribute value
string $value = `textField -query -text val`;
Once we have all the variables, we can stich them together into a command string as in:
string $command = "setAttr \"";
$command += $objectName;
$command += ".";
$command += $attribute;
$command += "\" ";
$command += $value;
Finally, we can put the string to use and make a new button:
button -parent buttonList -label "New Button" -command $command;
The -parent buttonList tells the button that it should be a child of the buttonList layout. In the example script I'm just stacking everything into a columnLayout, but you can use the same technique to excercise control over where dynamically created interface elements go.
As an added touch, we could make the button label more specific by building up a custom string for it as well, as in:
string $label = "Set ";
$label += $objectName;
$label += ".";
$label += $attribute;
$label += " to ";
$label += $value;
button -width 200 -parent buttonList -label $label -command $command;
After adding in the above changes and adding a few buttons, the script produces something like the following:
That's just a simple example, but the same sort of technique forms the basis for many complex scripts. Specifically, both my psMakeGUI.mel (which allows the user to create a custom GUI without writing any code) and saveJava3d.mel (exports models in java3d format and builds a web page to hold them) scripts are based on that kind of approach.
Resources