一半君的总结纸

听话只听一半君

#95 Introduction To MEL

You only have to use Maya for a few minutes to become acquainted with MEL, the embedded scripting language Maya uses. Every function Maya performs is echoed in a MEL command whether it be a simple selection, adjusting Soft Body weights or programming a complex shader network. You’ll soon realize that it is necessary to delve deep into this integral system to harness Maya’s power.

Note: This article was originally written in November 1998. Maya has changed a lot since then, and I apologize for any information appearing below that is now out-of-date. I accept any comments or questions to: maya@ewertb.com

Working with MEL Scripts

You will no doubt want to write your own MEL scripts and functions as your Maya projects progress. Many MEL authors have generously made their scripts available for download from the internet. To take advantage of these new scripts you must install them properly so Maya can make them available in your working environment.

MEL scripts are normally installed in the ‘scripts’ directory in your Maya home directory. Under IRIX this is:

/usr/people/<username>/maya/scripts/

Under NT this is:

<drive>:\aw\<username>\maya\scripts\

Maya does not scan any subdirectories within this folder. It is possible to specify additional directories for storing MEL scripts by setting the MAYA_SCRIPT_PATH environment variable.

To write effective MEL scripts it is important to know how they are handled within Maya. The first time you enter a MEL command Maya scans your script directories for a script file that matches the name of the command. For example, if you enter ‘showOnlySelected’ Maya will try and find the script ‘showOnlySelected.mel.’ Within this script will be a global procedure of the same name; it will look like this:

global proc showOnlySelected()
{
   // MEL commands for this script go here
}

All of the above criteria are necessary for Maya to be able to execute the command.

After the script is loaded Maya will remember it and run it from memory. If you make any changes to the script after it has been loaded you will have to tell Maya to re-read the file from disk using the ‘source’ command:

source showOnlySelected;

You do not need to specify the ‘.mel’ extension. Maya knows you are sourcing a MEL script and will reload the new contents.

When writing your own scripts, follow these simple guidelines:

  1. The main function of the script must be declared as a global procedure with ‘global proc <function_name>’.
  2. Name the script using the same name as the main function. If your main function is ‘global proc makeBingo()’ then name your script ‘makeBingo.mel’.
  3. Add as many other functions to the script as necessary. Only use the ‘global’ declaration when it is necessary — global functions use more resources. If a procedure is used to perform a function initiated from a UI (such as the command for a button press) it must be a global procedure.

Parsing the Selection List

One of the MEL functions you will need most often is also one of the easiest to use. Most MEL scripts interact with the user’s current selection. Your scripts will have to be able to determine what objects are selected and, in some cases, what types of objects they are and how they relate to other elements within the scene.

It is easy to get the current selection list using MEL. Simply use the ‘ls’ command with the ‘select’ flag, abbreviated as ‘−sl’:

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

If your script changes the active selection you may wish to restore it when it is appropriate to do so. Keep the array you obtained with ‘ls -sl‘ and pass it to the ‘select’ command just before your script exits:

select -r $select;

The ‘ls -sl’ command returns a string array that lists all of the selected elements. If nothing is selected, the array is empty and has a size of zero. Else, the array contains ‘n’ elements, where ‘n’ is equal to the number of selected objects. In all cases, even if ‘ls’ returns only a single string, it is still returned as an array.

To process each node in the selection list you can use a variation of the for loop that is specialized for use with arrays:

for ( $node in $select )     // process each selection
{
   /* … */
}

Maya automatically declares $node as a string datatype; you may option to do this yourself in advance but it is not required.

On the first pass, $node is equal to $select[0]; on the second pass, $node is $select[1], and so on. This method also has the advantage of handling a zero selection without any additional work on your part. If $select[] is empty, the for() loop does not execute.

While the “+” operation may be used with the string datatype it is not allowed on a string array. That means you cannot directly add something to the selection list like this:

$select = $select + "selectThisToo";
// Error: Illegal operation "+" on data of type string[]. //

The correct way to do this is to determine the number of items in the array using the ‘size’ function and then writing the new element after the last one:

int $lastSelect = size( $select );
$select[ $lastSelect ] = "selectThisToo";

The ‘ls’ command is extremely useful, but it does not provide any indication as to what type of elements are in the selection list. In many cases you will need to process only NURBS objects, or only particle groups, or textures and shading groups. Do not allow your scripts to assume the user will always pick the proper object type. Check the selection list and provide the proper warnings and assistance dialogs if the selection cannot be processed.

You can use the ‘ls’ command to list only one or more specific types of objects. The ‘−type’ flag restricts the list to the named types.

ls -sl;
// Result: pSphereShape1 nurbsSphereShape1 emitterShape1 //

ls -sl -type "nurbsSurface";
// Result: nurbsSphereShape1 //

ls -sl -type "mesh";
// Result: pSphereShape1 //

ls -sl -type "nurbsSurface" -type "pointEmitter";
// Result: nurbsSphereShape1 emitterShape1 //

Note that in this example the user has explicitly selected the shape nodes for each object. The ‘−type’ flag will not infer the selection of a shape node from its transform node, nor vice versa. There are other ways to achieve this – read on.

There are several methods of determining an object’s type without restricting the list results. One of the easiest ways is to check each item is using either the ‘nodeType’ or ‘objectType’ command. Both of these commands return very similar results and, in some cases, can be used interchangeably. The ‘objectType’ command has an additional option which allows it to return a Boolean result if the object’s type matches a specified type.

string $select[] = `ls -sl`;
// Result: pSphereShape1 nurbsSphereShape1 //

nodeType "pSphereShape1";
// Result: mesh //

nodeType "nurbsSphereShape1"
// Results: nurbsSurface //

objectType -isType "nurbsSurface" nurbsSphereShape1;
// Result: 1 //

By using these checks you can verify that your script can process the types of objects selected. You should ignore types not supported by your script, if possible, or alert the user as to the requirements of your script.

In the example above both nodes listed were shape nodes. Unfortunately, you cannot assume that the selection list will always cooperate and give you the shape nodes when you need them. Very often you will be given the transform node instead.

Compare the results below:

string $select[] = `ls -sl`;
// Result: pSphere1 nurbsSphere1 //

nodeType "pSphere1";
// Result: transform //

nodeType "nurbsSphere1"
// Results: transform //

That’s hardly helpful for determining if you are being given a polygon object, a NURBS object, or even a camera. In this case it will be necessary to find the shape node yourself.

Use the ‘listRelatives’ command to retrieve the shape nodes that are associated with a transform. There can be more than one shape node associated with a transform; like the ‘ls’ command, ‘listRelatives’ returns a string array.

string $relatives[] = `listRelatives -shapes "pSphere1"`;
// Result: pSphereShape1 //

Be careful – attempting to retrieve the shape relatives for a shape node will result in an empty array:

listRelatives -shapes "pSphereShape1";
// Result: //

A favorite trick of Paul Anand from Alias|wavefront is to use the ‘pickWalk’ command to traverse to the shape node:

pickWalk -d down;

If the current node is the transform node this drops down a level to the shape node. It the shape node is already selected then it remains selected. This method changes the active selection; be sure to restore it when you’re done.

A convenient method for obtaining the list of shape nodes for the selected objects is using the combination of ‘−dagObjects’ and ‘−leaf’ flags for the ‘ls’ command:

ls -sl;
// Result: pCube1 pSphereShape1 //
ls -sl -dag -lf;
// Result: pCubeShape1 pSphereShape1 //

(Thanks to Julian Mann for alerting me to this technique!)

There is another approach, similar to the ‘ls -type’ flag, to assure that the list Maya provides contains only the required types of objects. The lesser-known ‘filterExpand’ command works similarly to the ‘ls -type’ command but overcomes the limitation expressed above. Even if you have selected the transform node for the required object type ‘filterExpand’ will match the selection criteria and include the object in the resulting list.

The ‘filterExpand’ command is provided with a numerical mask which restricts the items returned to a specified type.

string $select[] = `ls -sl`;
// Result: pSphere1 nurbsSphereShape1 //

filterExpand -selectionMask 12;   // Poly Mesh
// Result: pSphere1 //

filterExpand -selectionMask 10;   // Nurbs Surfaces
// Result: nurbsSphere1;

The ‘filterExpand‘ command returns the transform node even if the selection list includes the explicit selection of the shape node.

Another advantage to this command is that it will expand the range selections to individual items. This makes it considerably easier to parse component selections. Maya normally compresses adjacent component selections using a “[s:e]” format, where ‘s’ indicates the start of the range and ‘e’ indicates its end. While this makes lists much more readable it does make them hellishly complex to deal with inside of a script. Compare the results below:

ls -sl;
// Result: nurbsSphereShape1.cv[4][3:5] //

filterExpand -sm 28   // Control Vertices (CVs)
// Result: nurbsSphere1.cv[4][3] nurbsSphere1.cv[4][4] nurbsSphere1.cv[4][5] //

Maya’s documentation suggests that the ‘filterExpand’ command does not include masks for polygon component selections. There are, in fact, Selection Masks for polygon components; however, for some reason A|W neglected to document them. Here are some examples:

Selection Mask Component Type
31 Polygon Vertices
32 Polygon Edges
34 Polygon Facets
35 Polygon UV Map Coordinates
46 Lattice Points
47 Particle Components
70 Polygon VtxFace

You can use the H2O MEL ScriptfindFilterExpand.mel’ to aid in finding these undocumented Selection Mask values.

Note: As of Maya v3 the filterExpand selectionMasks are finally included in the on-line documentation

Traversing Scene Hierarchies

In addition to knowing what types of objects are selected it is often important to know how they relate to other scene elements. A single transform node can control many underlying nodes. When working with skeletons it will be necessary to be aware of which joints are above or below others joints.

There are two approaches to determining hierarchy information – the important difference between the two methods is that the latter alters the active selection, whereas the former does not.

To retrieve a list of all of the children of an object:

string $children[] = `listRelatives -children pSphere1`;

To determine the parent for an object:

string $parent[] = `listRelatives -parent pSphere1`;

The ‘listRelatives’ command does not alter the current selection. In contrast, the ‘pickWalk’ command does change the active selection but it also conveniently returns a string array that describes the newly selected elements.

To select the next or previous object at the same level:

// Pick next object on same level
string newSelect[] = `pickWalk -direction right`;

// Pick previous object on same level
string newSelect[] = `pickWalk -direction left`;

To select the object up or down a level from the current selection:

// Move down to and select child
string newSelect[] = `pickWalk -direction down`;

// Move up to and select parent
string newSelect[] = `pickWalk -direction up`;

Finding Specific Scene Information

In many cases it will sufficient to allow the user’s actions to determine the list of objects to act upon. In others you will want to find a specific item in the scene, or to determine if the user has yet created it. Determining whether a named object or an object type exists is easy; determining the same of an attribute for an object requires a little more work.

To determine if an object exists within the scene, simply use the name as a pattern for the ‘ls’ command. For example, in one of my scripts I needed to create a new node to store some custom data. However, there is no need to create a node if it already exists, and attempting to do so will result in a decorated node (ie. Maya will append a number to the end of the name).

The test is simple:

if ( `objExists "recallData"` )
{
   // The data node exists
}

The ‘objExists’ command returns a value of 1 if the object exists, otherwise it returns 0.

For a more specific test the ‘ls’ command can be used. When used with the ‘−type’ flag ‘ls’ can be used to determine if a particular type of node has been created. For example, to determine if there any trims performed on the scene’s NURBS geometry:

if ( size( `ls -type trim` ) > 0 )
{
   // Trim performed on NURBS geometry
}

The ‘ls’ command returns a string array that contains all nodes of type “trim.” If no nodes exist of this type, the array is empty and has a size of 0.

Maya provides the animator the ability to add custom attributes to any object. As for the default attributes you retrieve the value for your custom attributes using the ‘getAttr’ command. However, attempting to get the value for an attribute that does not exist will result in an error which will unceremoniously terminate the user’s script.

float $myAttr = `getAttr pSphere1.noAttr`;
// Error: No object matches name: pSphere1.noAttr //

For this reason it is important to verify the existence of a custom attribute before attempting to query or assign any values. Maya’s ‘attributeQuery‘ command is used to retrieve the status of an attribute. For example, to determine if the custom attribute ‘toeCurl‘ exists for the ‘leftFoot‘ object:

if ( `attributeQuery -node "leftFoot" -exists "toeCurl"` )
{
  // Safe to use 'toeCurl' attribute
}

The ‘attributeQuery‘ command can also be used to determine the minimum and maximum value that was defined for the attribute when it was created. The result is returned as a float array.

addAttr -ln "blink" -at double -min 0 -max 100 -dv 0 eyeRight;
attributeQuery -node "eyeRight" -range "blink";
// Result: 0 100 //

Unfortunately, you cannot use this method to change the value range for the attribute.

10 Tips For Writing Better MEL Scripts

    1. When appropriate, restore the user’s selection list.
    2. Always check the object types in the selection list. Don’t assume the user will always pick what you expect. Ignore inappropriate types where reasonable. Inform the user what the script expects.
    3. Include a section of comments at the beginning of the script to explain its function and usage. If your script requires parameters, document exactly what is needed for the script to run properly.
    4. Allow as much flexibility as possible for the script’s input. For example, if the script requires that the user select object or component type use the techniques described above to determine if the selection matches the criteria – even if this means pick-walking down from a transform or manually parsing a component selection.
    5. Always parent your scriptJobs to your main UI window. This will ensure that the scriptJob is deleted when it is no longer needed.
    6. Before you create a window always check if the window already exists. If it does you can either skip its creation and simply perform a ‘showWindow’ or, if necessary, delete the window and rebuild it. Use the ‘window -exists’ command to check for an existing window:
      if ( `window -exists myWindowName` )
         deleteUI myWindowName;
      
    7. Whenever you need to concatenate two strings, or a string and a value, you can simply add them together within parenthesis just as if you were printing them:
      string $objectName = "nurbsSphere";
      
      print ( $objectName + ".rotateY" );
      // Result: nurbsSphere.rotateY //
      
      float $ry = `getAttr ( $objectName + ".rotateY" )`;
      // Result: 45.0 //
      
    8. If you are writing a script that is selecting isoparms or a point on a surface you may often by plagued by the repetitive warning message:
      // Warning:  Some items cannot be moved //
      

      This is caused by an active manipulator tool such as the Move Tool. To avoid these messages while your script is running instruct Maya to drop the tool before performing your selections:

      global string $gSelect;   // Sacred Select Tool
      setToolTo $gSelect;
      
    9. Don’t litter your script with global strings for the names of your UI elements. Instead, pick descriptive and unique names for the elements and reference them by that name in your support procedures.
      proc getValue()
      {
         int $number = `intField -q -value int_recallItemsInList`;
      }
      
      global proc recall()
      {
         /* … */
         intField int_recallItemsInList;
         /* … */
      }
      

      You could also pass the names for your UI widgets as a parameter to other functions. If you do this make sure to document the purpose of the parameter and also the origin of the UI element.

      proc getValue( string $field )
      // Parameters: $field = intField that holds value;
      //             defined in global proc recall()
      {
         int $number = `intField -q -value $field`;
      }
      
      global proc recall()
      {
         /* … */
         string $field = `intField`;
         // Must build $field into command, and encapsulate in quotes
         intField -e -changeCommand ( "getValue( \"" + $field + "\" )" ) $field;
         /* … */
      }
      
    10. As you are developing your scripts you will likely need to frequently fix bugs and tweak their behavior. However, once Maya has loaded your script it will not automatically check to see if it has been changed and needs to be reloaded. To speed things up create a temporary Shelf Item that re-sources the script and then calls its main function. If your script is named “recall.mel” then enter the following into the Script Editor:
      // Update and execute Recall script
      source recall;
      recall;
      

      Highlight these three lines and drag them to a new Shelf item. Now with a quick click of a Shelf Tool Maya will integrate the changes you’ve made and execute the result. The comment is included to provide a description of the Shelf Tool in the Feedback Line whenever you move the mouse over the icon, as demonstrated by the Rhonda Graphics team at SIGGRAPH’98.

December 05, 2000

Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: