Maya MEL scripting
==================
- learning two things:
- the language itself;
- working with a scene through MEL (how objects are built,
etc.);
- fundamental things in MEL scripting itself:
- local and global procedures; global variables;
- when and how your script is read into Maya; (+whatIs
command)
- userSetup.mel;
- recommended structure for a script;
- plug-in note: using plug-in commands in scripts;
- where to put your scripts;
- modifying/overriding original Maya scripts;
- fundamental things in a Maya scene (that every scripter should be aware
of):
- DAG and DG nodes; shortest unique names;
- transforms and shapes (+components);
- intermediate objects; multiple instancing;
- selections and how to handle them;
- 'exploring' the scene (+related commands: ls, listRelatives,
listConnections, listAttr, ...);
- the hierarchical structure of node types;
- coding style (some quick tips);
- the different formats for calling MEL commands;
- passing expressions as MEL command arguments;
- script/global proc naming (should be the same);
- be aware of string / string[] command results;
- using assignments and conditions (=/==), using expressions
in conditions ( if (1) ... );
- the (<expr> ? <true> : <false>) expression;
- global variables (avoid them if possible ;)
- coding tips/techniques;
- using `ls` for filtering;
- proper Maya-style selection handling (using `ls -dag
-lf -sl`, ...);
- safely querying existence and/or properties using size(`ls
...`);
- setting attributes '.ma style' (`select <node>;
setAttr ".attr" 0;`);
- protecting execution flow with catch();
- using dgeval() -- when and why;
- running in UI/batch mode;
- script working style (the way your script works):
- handling selection;
- ...
- writing complete plug-in modules;
- using MEL and API together for most efficiency;
- what is a module;
- directory structure of a module;
--------------------------------------------------------------------------------------------------
The intention of this document is to give not just an introduction of the
MEL scripting language,
but rather to show how to use it in practice. It intents to address most
of the common issues that
people possibly face when learning Maya/MEL scripting.
I've tried to collect all the most common problems I've faced during my MEL
studies to provide
practical help to people who want to write Maya/MEL scripts. However I'm
not sure this document
is far from complete -- so if there's anything you'd like to read about (and
related to learning
MEL scripting of course ;) please tell me and I'll include.
I'm not going to talk about the syntax of MEL or anything else you can pick
up from the regular
Maya documentations; instead, I try to focus on practical usage and related
things that can come
handy (although there will be some coding tips as well).
(If you're new to MEL, I recommend to look up the docs for each command you
read in the examples
to know what they do exactly. Remember any flags also. Learn. :)
I'm not a native English, but I'm trying to be as clear as I can and provide
examples for better
understanding (and hoping the best ;)
I also tried to organize this document to match an usual MEL learning curve
(so it begins with
the most basic things and gradually proceeds to more complex examples.)
Okay that's enough babbling :)
Two things you need to learn
============================
If you're new to MEL scripting, there are two things you'll need to learn:
1. The language itself.
Of course, whaddidya think? :) It's especially worth to
get some knowledge
about how to write clean and understandable program code
in general. Mostly
recommended is to read C/C++ literature about clean coding
style, as MEL is
very similar (mostly plain C).
My personal opinion is that it's one of the most important
issues. Please, don't
pick your programming style from MEL scripts you downloaded
from the 'net, 'coz
most of them are terrible!
2. How to work with a Maya scene through MEL.
Even if you're an experienced Maya user by 'regular' means,
you'll need to learn
how to handle things through MEL (selections, creating
objects/attributes, etc).
It's basically the same like if you were working with
Maya through the UI -- the
main difference is you have to 'tell' Maya more precise
what you want to do, and
you should aim your script to be working in most imaginable
circumstances. :)
Briefly, you need to be more precise.
If you didn't work with node-based software before (like
Houdini, Shake, Composer,
or Digital Fusion), get a clear understanding of what
the Dependency Graph is,
and how does it work.
Additionally, PLEASE read the Maya docs: "Essentials - 8. Setting Environment
Variables"
to know about the Maya environment variables, Maya.env and stuff like that.
VERY IMPORTANT
(even for windows users :) If you want to do scripting, read the basics first
carefully.
You may spare yourself and other people a few questions (no, I didn't say
stupid questions,
I simply just said _questions_, okay? :)
Two manuals you'll seek often
=============================
1. MEL Command descriptions
Perhaps this needs no explanation. It's advised to carefully
check the command
descriptions as well as the various flags. It can happen
that what you're planning
to write a procedure for, can be done with a single command
with the proper flags
(especially true for ls). Most useful is the help command:
help <cmd>
displays short help about the command
help -doc <cmd>
displays the html man page of the command
2. Node and Attribute reference
It's a list of all existing node types in Maya, with all
their attributes explained.
You can check either in alphabetical or in hierarchical
order (although the latter
is quite incomplete, at least for 4.0; but still very
useful).
And again, familiarize yourself with C/C++. Once you're a pro scripter you
won't even notice
when switching to API :)
Fundamental things about MEL scripting
======================================
Local and global procedures
---------------------------
A MEL script is a simple text file with '.mel' extension, and can contain
MEL code in the
following forms:
- local procedures;
- global procedures;
- MEL code not embedded in any procedure;
When a script is read in (sourced), all MEL code not embedded in a procedure
gets executed.
Also, all the procedures are read by Maya, and after the sourcing is done,
all the global
procedures defined in the script can be called.
You don't have to use procedures at all when scripting, but for any more
serious stuff
(except quickly executing some commands) it's recommended to use them.
The difference between local and global procs:
- local procedures can only be called from the same script
they were defined in
(they're 'local' to the script);
- global procedures can be called from anywhere.
Note that you need to define a procedure before calling it (so if proc B
calls proc A in
a script, you'll need to place proc A to an earlier place in the script than
proc B).
Read the following section below on how to name your scripts and global procedures.
When and how your script is read into Maya
------------------------------------------
There are more ways to get your script sourced (read into Maya). First of
all, you can
type the MEL command
source <script name>;
(You may need to put the script name into quotes if it contains funky characters;
but
you don't have to append the .mel extension, as it will be done automatically).
This command will search all Maya script folders for the script, and if one
is found,
it'll be read in. All MEL commands in the script not enclosed in any procedure
will
be executed in order, and all global procedures in the script will be available
from
now on.
You don't need to explicitly source a script however, as there's an automated
mechanism.
If you enter the name of a global procedure, the following things will happen:
- First, Maya checks if there's such a procedure already
defined. If so,
calls it.
- If no such proc defined, Maya will look in all the script
folders for
a script with the same name as the given procedure.
- If such a script is found, Maya checks if it contains
the global proc
with the given name.
- If it's so, Maya sources the script and then calls the
procedure.
(Otherwise the script won't be sourced.)
Briefly, if you give the same name to a script and a global procedure in
it, you
don't have to source the script -- just enter the proc name, and Maya will
do the
rest.
Note however that in such cases Maya will source the script only ONCE (not
every
time you call the procedure), so if you change anything in the script after
the
first call, you'll need to source it again 'manually'.
(A quick note: you can use the 'whatIs' command to check procedures/scripts.
For
example, 'whatIs myProcedure' will give you info about the global proc myProcedure,
or 'whatIs myScript.mel' tells the path where Maya would read the script
from.)
userSetup.mel
-------------
[Although I don't want to repeat anything existing stuff in the Maya docs,
this is
one of the exceptions, as pretty many people don't even have a clue what
the heck
userSetup.mel is (and keep on writing mails 'i don't find mine, where it
should
be!?' :) ]
userSetup.mel is a 'dedicated' MEL script -- it gets sourced by Maya on every
startup. It can be used to perform your own startup stuff -- usually script
sourcing.
It doesn't have to exist (and it doesn't exist by default after a Maya install),
but if Maya finds a script with such name on startup, it sources it.
[Begging note: PLEASE USE userSetup.mel ONLY FOR SOURCING OTHER SCRIPTS (or
other
short forms of initializations)! But don't define any local/global procs,
or don't
rename any script to 'userSetup.mel' just 'coz you want it to load on startup.
Otherwise your TD will be waiting for you at a dark corner at night... :]
Recommended structure of a script
---------------------------------
In general, when you write a script, it should have the following structure:
- script header (some comment lines containing the name
and other infos
about the script);
- local procedure definition(s);
- global procedure definition(s) [possibly only one];
The script header should contain the script name, version number, creation/last
modification date, a short description/usage notes, possibly a quick version
history. This will help everybody (even you!) to keep track of what's the
hell
was going on with that script. :)
Then the local procedure definitions follow. They're 'local' to the script
(no
one 'outside' sees them), so feel free to name them as you like, and use
as
many dirty coding tricks as you want (as long as they work :)
At the end the global procedure(s).
It's recommended to have only one global procedure in a script, and it should
be
the same name as the script file itself (see previous section), so the users
don't need to explicitly source the script -- they just have to type in the
main
procedure name.
Also, choose you script/global proc name carefully; the name should be unique
(as
if there's an existing global procedure with the same name, it'll be overridden).
The best idea is to choose a 'prefix' that you use for all the scripts and
global
procedures (it can be your monogram or whatever).
So, instead of naming the script 'createBigDragon', use something 'prefixed',
like
'xyCreateBigDragon' (but not the xy prefix as I'm already using it!!! You'll
be
punished if you try to steal it! :) That's usually enough.
(If you followed general structured programming style, any global procedures
are
quite short, and mostly contain calls to the local ones which do all the
grunt
work.)
Note about using plug-in commands in scripts
--------------------------------------------
Please note if use MEL command(s) in a script that are provided by a custom
plug-in
(not internal Maya MEL commands), you need to load the plug-in first, then
source
the script after -- in this specific order.
(If you load the plug-in after the script is sourced, Maya won't recognize
the MEL
commands created by the plug-in and the script will stop. If this happens,
source
the script again.)
Also note, that...
------------------
On Maya startup, the order of script sourcing (including userSetup.mel) and
the
order of plug-ins' loading is _undefined_. In other words, it can happen
that
plug-ins get loaded _after_ userSetup.mel is sourced.
So it can happen you have a plug-in flagged as 'auto-load', and you have
a script
that needs a command from that plug-in, which you source from userSetup.mel
--
and you still get error messages on Maya startup. Usually, it means that
the
script is sourced _before_ the plug-in load, which is no good.
To overcome that, you need to make sure things will go in the proper order
(plugin
load first, script sourcing after). (An unfortunate thing is that as far
as I know
there's no 'official' way to do this.) Here come some ideas:
1. Load the required plug-in(s) 'by hand'. Put something like that into the
first
line(s) of your script (without placing into any procedure):
loadPlugin -q myPlugin.so; // or .mll
on nt
2. Let the script be sourced after all startup tasks are done, and Maya became
idle:
evalDeferred -lp "source myScript";
(But I'm not sure how well this one works when Maya is started in batch mode.)
Where to put your scripts
-------------------------
...
A note about using modified versions of original Maya scripts
-------------------------------------------------------------
It can happen you get a modified/fixed version of an original Maya script
(especially
if you check my home page ;) These usually reside in the C:/aw/MayaX.X/scripts/
folder (linux users should now imagine a pathname for themselves.)
You may think that you need to overwrite the script(s) there, if you have
a modified
version, but that's really not the recommended way. Since the 'core Maya'
script path
is always checked last for a script, it's enough if you copy any modified
Maya scripts
into your scripts/ folder.
Again, briefly: don't overwrite any installed Maya script. Always use _your_
scripts/
folder, and it will work.
A good workflow if you want to change something in Maya is similar: copy
the script
you want to mess with from the Maya installation to your scripts/ directory,
and
mess around with that copy. If you delete the modified version, Maya will
'fall back'
to it's original script provided with the installation.
Fundamental things about Maya/scene handling
============================================
Besides knowing the language, you also need to know about how to manage a
scene
through MEL, or get along with Maya in general. The following sections try
to touch
the most common things needed.
DAG and DG nodes
----------------
Maya scenes are built from nodes -- they're the building blocks of construction
histories,
shading networks, animation curves and clips, constraints, etc. From our
current point of
view, we can distinguish two different main node types:
- DAG nodes: represent the scene hierarchy; parent-child relationships (Directed
Acyclic Graph);
- DG nodes: represent data flow within the scene, like histories, etc.
(Dependency Graph);
All these nodes share a common namespace, ie. every node should have a unique
name (no
two nodes can exist with the same name).
There's a very important issue about node (especially DAG) naming, which
is frequently
ignored by home-scripters (and unfortunately quite often by commercial software
programmers
as well). So we need to discuss about...
Shortest UNIQUE names of DAG objects
------------------------------------
The naming is a bit different for DG and DAG nodes. DG (=history) nodes'
name are simply the
node name, but this is not the case for DAG (hierarchy) nodes.
To uniquely identify a (DAG) node, you need to know it's unique name. In
Maya, two DAG nodes
can have the same _shortest_ name, as long as they don't have the same parent.
It can be so
because in Maya, the 'real' names of DAG nodes are not only the name of the
node, but rather
a hierarchy 'path' specification.
The easiest way is to see an example. Say we have the following hierarchy:
Hierarchy: Full names:
Shortest unique names:
+ root1
|root1 root1
!
+--+ group1 |root1|group1
group1
! !
! +-- pCube1 |root1|group1|pCube1
group1|pCube1
!
!--+ group2 |root1|group2
group2
!
+-- pCube1
|root1|group2|pCube1 group2|pCube1
In the above example we have two object with the same _shortest_ name (pCube1).
But in Maya,
DAG objects are identified by their _shortest unique_ names. So if you try
to select one cube
with the following command:
select -r "pCube1";
It won't work ("more than one objects with that name"). Instead, you should
say one of the
followings:
select -r "group1|pCube1";
select -r "|root1|group1|pCube1";
(This naming style is very similar to relative and absolute filesystem path
specifications).
You should always be take care that your scripts' functions always refer
objects to their
_shortest unique_ or absolute names. Otherwise they'll break like hell any
time. Usually MEL
commands return shortest unique object names so you won't have a problem
with this, but there
are a few exceptions, where you should explicitly tell the command to do
so (like the
listRelatives command, where you should always use the -path flag.)
A good advice is to always check the man page of the list-command you want
to use, if there's
a flag for returning proper node names, and if there's one, always use it.
Transforms and Shapes; multiple instancing
------------------------------------------
This is one of the earliest obstacles in MEL scripting. What you see as one
NURBS, polygons
or whatever object on the screen, is really not one, but rather consists
of two components.
In Maya, all objects you see in the viewport consists of two DAG nodes: a
transform and a
shape. The transform tells where the object is in 3d space (translate, rotate,
etc.), and
the shape contains the actual geometry data.
(You can see the shape nodes in the Outliner by turning on Show Shapes.)
One transform nodes can have more than one shape nodes under it. What's even
more interesting
is that Maya allows to share a single shape node between multiple transform
nodes (this is
called multiple instancing). In other words you can have multiple copies
of the same object,
but the actual geometry data is stored only once (thus saving memory); and
if you change the
shape by modeling or whatever, all instances will reflect the changes.
(You can visualize multiple instancing in the Hypergraph, by enabling the
display of Shape
nodes.)
The DAG naming scheme described in the previous section also applies to shape
nodes, as they're
DAG nodes as well, part of the scene hierarchy. Generally you should think
of shapes as just
another level in the scene DAG hierarchy (except you can't re-parent them
as easily as transforms).
Briefly, you should always use shortest unique names of shape nodes. Even
two transform nodes
under a common parent can have shape nodes with the same shortest name, so
watch out!
What can be confusing for MEL beginners is the users usually select transform
nodes while
working in Maya (when you pick an object in a viewport for example, Maya
selects its transform
node). Unfortunately most of the time we're interested in the shape node.
So, let's say, in your first script you want to determine the type of the
first selected object,
and type something like that:
{
string $sl[]=`ls -sl`;
if (size($sl)>0) print("Type of first selected object:
"+ nodeType( $sl[0] ) +"...\n" );
else print("Nothing selected!\n");
}
The above code always return "transform", no matter what you select. Dammit!
:) Why don't you try
this instead:
{
string $sl[]=`ls -sl`;
// get list
of selected objects
if (size($sl)>0)
// check if there's a selection
{
if (size(`ls -s $sl[0]`)==0)
// check if the object is a shape
node
$sl=`listRelatives
-pa -ni -s $sl[0]`; // if not, get shape node(s)
if (size($sl)>0) print("Selected
object type: "+ nodeType($sl[0]) +"...\n" );
else print("A transform with no shape
(empty group) is selected\n");
}
else print("Nothing selected!\n");
}
The above code works both if a transform or a shape node is selected (it's
also aware of intermediate
objects, see below); you probably won't need more serious checkings for this
than the code above.
By the way, there's a separate section below on how to handle selection lists-n'stuff.
Intermediate Objects
--------------------
As mentioned in the above section, a transform node can have multiple shapes
under it. This is
definitely always the case objects with deformers applied.
When you apply a deformer to an object, Maya builds the object's history
in a way that the 'original'
geometry shape (before deformation) will be preserved -- instead of a single
shape, there will be
two shapes under the object's transform: a 'final' shape with the deformation
(what you see in the
viewport), and an 'intermediate' shape containing the geometry before any
deformations applied.
(The default shortcut CONTROL+I is for cycling between intermediate shapes
on an object).
This is very useful for the user, as (s)he can always go back 'before' the
deformation and apply
changes to the original geometry. Even the history can be deleted from such
an intermediate object
(it will delete only the previous history, but not the deformations -- can
be useful if you skinned
a character but forgot to delete the modeling history :)
Note that usually there's a single intermediate shape for all deformers on
an object (not one for
each deformer). But you can freely add 'own' intermediate shapes anywhere
by manipulating the graph
by hand (or by script! :)
However this feature can hide a trap for the non-aware script writer. Let's
again say we want to
list the shape node of a transform by the command:
string $shapes[]=`listRelatives -pa -s "my_transform1"`;
If our object has any deformers applied, it will return multiple names (as
it will also return
the intermediate shapes' name). But again, we usually need the 'final' shape.
The following
command will do the trick:
string $shapes[]=`listRelatives -pa -s -ni "my_transform1"`;
The -ni flag means -noIntermediate (see the command man page for more details).
Most listing
commands (like ls) has this very same flag, so be aware and use it.
(Just a quick side note: I don't recommend to make any assumptions about
the order of the list
returned by `listRelatives`. It's NOT always the first/last returned object
is the final one
y'know :)
Btw, the intermediate state of a shape node is stored in the ".intermediateObject"
attribute on
the shape. But it's always safer and faster to check with a listing command
with the -ni flag
(especially if you have large object lists).
(It can happen that you have an amount of unused intermediate shapes in the
scene you don't
know about, especially after deleting deformers from objects. In such cases,
Maya deletes only the
deformer node, but not the intermediate shape. You can get rid of them by
using Optimize Scene
with the Delete Unused Deformers option turned on. Then check the script
editor to see how much
nasty stuff was removed :)
Selections (or how to handle a selection list Maya-style)
---------------------------------------------------------
First a few words about the DAG node selection in Maya. While working with
scene objects, usually
the objects' transform nodes get selected (for example, by clicking on an
object in the viewport).
Although shapes can also be selected explicitly, users select transforms
most of the time (but
not always, so be aware!).
Additionally, Maya has a strict selection scheme for DAG hierarchies: if
a transform is selected,
all children are treated as selected aswell: they also get highlighted in
the viewport and any
operation will be applied to them as well (but they won't be included in
the selection list!).
For example, if you select an object and assign a shading group to it, the
same shading group
will be assigned for the children as well. In this case, if you want to make
the assignment for
the selected object only, you need to explicitly select the shape node in
the outliner.
(Now we got a pretty good practical reason why to check if a node is a transform
or a shape,
don't we?:)
As all the children are parented to the transform node and not the shape,
if the shape is
selected, no operation will be applied to any children object.
Generally, in such rare cases when writing scripts that need to work with
selections, it's a good
idea to have the script handle the selections in the Maya fashion described
above, as this is
what the users got used to. Briefly:
- multiple object selections should always be supported;
- for selected transform(s), the operation should be applied to all
chilren as well;
- for selected shape(s), the operation should be applied to the shape
only;
This can be quite messy if you try to collect the actual selection list 'manually'.
Fortunately
the ls command has some quite useful flags that can help us out. Some examples:
ls -dag -lf -ni -s -sl (or: ls
-dagObjects -leaf -noIntermediate -shapes -selection)
This command returns the shape of the selected transform, as well as all
the shapes in the hierarchy
below; if a shape is selected, it returns only the shape name. It's exactly
what we need!
ls -dag -ap -tr -sl
(or: ls -dagObjects -allPaths -transforms -selection)
This command returns the selected transform(s) as well as all the child transforms
under them. Could
be handy as well.
Let's see a simple example. The following code applies a 'reverse poly normal'
operation on the
current selection (handled in Maya fashion off coursor! :)
{
string $sl[]=`ls
-dag -lf -ni -type mesh -sl`, $s;
for($s in $sl)
polyNormal -ch 1
-nm 0 $s;
}
Quite short but still can handle the selection list in Maya fashion. Note
that the ls command is told
to list only shape nodes of type "mesh". You can find more related examples
in the coding tips section.
Exploring and modifying the scene: most frequent commands
---------------------------------------------------------
Exploration:
ls
--
Used to get lists of specified scene elements or the current
selection. It has an
extensive set of flags so it's quite flexible. It can
be used for other things as well.
For example, to check if $object is a transform or not:
if ( size(`ls -tr $object`)!=0 ) print($object+" is a
transform.\n");
listRelatives
-------------
Can be used to query DAG chilren/shape/parent transform
nodes. Always use the -pa or -f
flag!
listHistory
-----------
To list construction history/future nodes of an object.
listAttr
--------
To list attributes of an object.
listConnections
---------------
To list up/downstream (incoming/outgoing) connections
of an object or an attribute.
Some examples:
listConnections -s 1 -d 0 group1;
list all incoming connections to group1
listConnections -s 0 -d 1 group1.translateX;
list all outgoing connections from group1.tx
(See the man page for more details.)
objExists
---------
To check if a node or attribute exists. Some examples:
if ( objExists("group1") ) ... ;
check if node exists
if ( objExists("group1.my_attribute") ) ... ;
check if attribute exists on node
The first example is equivalent to
if ( size(`ls "group1"`) ) ... ;
check if node exists
nodeType
--------
To get the type of the specified node. For example:
nodeType("persp")
returns "transform"
nodeType("perspShape")
returns "camera"
nodeType("initialShadingGroup")
returns "shadingEngine"
getAttr
-------
To get the current value of a node attribute. Examples:
int $v = getAttr("group1.visibility");
float $t[] = getAttr("group1.translate");
$t[0..2]: translate X/Y/Z
int $v1 =`getAttr -t 3 "group1.visibility"`;
(returns attr value at frame 3)
string $type =`getAttr -typ "group1.translate"`;
(returns attr type)
int $size =`getAttr -s "mynode.my_array_attr"`;
(returns size of array attribute)
Modification:
createNode
----------
To create DAG/DG nodes. For example, the following code
creates an empty
polygonal object (a transform and a shape):
string $tr=`group -em -w -n "poly1"`;
string $sh=`createNode mesh -p $tr -n
"polyShape1"`;
group
-----
To group object(s) under a common parent transform, or
to create an empty
transform node (group -em).
addAttr
-------
To create a new attribute on a node.
setAttr
-------
To set the value of a node attribute (for non-numeric
attributes you may need
to explicitly specify the attribute type). Can also be
used to set the locked/
keyable states of attributes. Examples:
setAttr "group1.tx" 0;
setAttr -type "string" "file1.fileTexureName" "image.iff";
setAttr ".visibility" 0;
clears the selected object's
visibility
setAttr -k 0 "group1.translateZ";
connectAttr/disconnectAttr
--------------------------
To create/delete connections between node attributes.
xform
-----
Very useful command to perform/query various transformations
on objects (transform
nodes). Has flags to work in local and world space as
well.
dgeval
------
Can be used to force re-evaluation (refresh) of a node
or attribute. This is a bit
more advanced command, but can be used to 'clean the situation
up' in some cases
(especially when dealing with DG connections).
For example, to transfer the actual geometry data from
one polymesh shape to another:
connectAttr -f "meshShape1.outMesh" "meshShape2.inMesh";
dgeval "meshShape2.outMesh";
disconnectAttr "meshShape1.outMesh" "meshShape2.inMesh";
Here the dgeval command is necessary to force an evaluation
of the destination mesh
shape. There's a topic about dgeval in the coding tips
section.
eval, evalEcho, evalDeferred
----------------------------
All these take a string argument, and execute it as a
MEL command (can be used for
constructing commands on-the-fly).
evalEcho will also print the executed command in the Script
Editor history (allowing
the user to put it on a shelf, hint-hint :)
evalDeferred executes the command the next time Maya gets
into 'idle' state, ie.
not executing anything (probably at the end of your script
execution).
Other:
size
----
To return the size of a string (no. of characters) or
any array (no. of elements
in the array). The latter can be useful for other purposes,
for example:
size(`ls -sl`)
no. of selected items
size(`ls my_object1`) returns
non-zero if 'my_object1' exists
size(`ls -tr my_xform1`) returns non-zero
if 'my_xform1' is a transform.
(See the "hierarchical structure of node types"
section for an explanation.)
match
-----
Very useful substring matching function. (People seems
not using it very much
although, don't know why.)
tokenize
--------
Creates an array of string from a simple string based
on a separator character
(like words of a sentence based on the space character.)
The hierarchical structure of node types
----------------------------------------
[This can be familiar for ones who know about object-oriented programming.]
In Maya, there are different node types, and all the scenes are built from
these (there's
a complete listing of all node types in the Maya Docs: Node and Attribute
descriptions).
These node types are actually form a hierarchy (which you can examine by
choosing
Hierarcical display of nodes in the docs).
The parent-child hierarchical relationships of nodes you can see in the docs
has a similar
idea behind it like in object-oriented programming.
If a parent nodetype has child nodetype(s), it means that all the children
are derived
from the parent type. All the children have all the same attributes as the
parent -- but
have additional attributes as well, specific to each child.
This is very logical (and practical as we'll see) as this is an easy way
to deal with
node types of similar functionality.
A good example is the transform node. If you check the docs, the transform
node has
many children (like place3dTexture, ikHandle, joint, lodGroup) -- these are
basically
transform nodes, but all with additional attributes and functionality.
In the node type hierarchy, there are regular types, and _abstract_ types
(like 'entity'
for example). Abstract nodes can't be created, they just exist to provide
a parent class
for a bunch of child nodes.
The practical usefulness of this is that for all commands you may specify
a node type,
you can use any type (even abstract ones) and enjoy the effects of the hierarchical
structure:
ls -type surfaceShape lists
all nodes of type surfaceShape (including
nurbsSurface, mesh, subdiv nodes)
ls -type field
lists all field nodes (gravity, turbulence, etc.)
As you can see, specifying a parent nodetype will include all child node
types as well.
(Actually, all the above examples use abstract types.)
There are many cases where you can take advantage of the hierarchical node
type structure,
and end up shorter and cleaner code.
Let's see a practical example (actually, a very common one), we want to decide
if our
node ($node) is a transform or not.
We could write the following:
if ( nodeType($node)=="transform" ) print($node+" is a
transform\n");
[The above expression returns true if the _exact_ node type of $node is "transform",
and false if not. Please note that you also need to first check if $node
exist at all,
otherwise your script execution may break.]
This will definitely work for transform nodes. But if you check the hierarchical
node
list in the Maya docs, you'll see that there are a plenty of child nodes
exist below
transform (such as fields, joint, etc. which have the same attributes as
the regular
transform node, along with additional ones.)
So the above line will work only for transforms, but not for those others
which are
fundamentally transforms as well.
Below some better examples:
if ( size(`ls -type transform $node`)>0 ) ... ;
if ( size(`ls -tr $node`)>0 ) ... ;
or even shorter:
if (size(`ls -type transform $node`)) ... ;
if (size(`ls -tr $node`)) ... ;
[The above expressions return true if $node exists and is a transform, and
return false
if $node is not a transform or it doesn't exist.]
These will always give the results we'd expect. An additional advantage of
these latter
methods are that you don't have to check first if $node actually exist --
when using
nodeType() you need to do that beforehand, or your script can stop with an
error if you
give a non-existing node name to nodeType().
Some quick tips on coding style
===============================
Different calling formats of MEL commands:
-----------------------------------------
There are different formats for calling MEL commands: using the 'reverse
aphostrophe'
character (` -- backquote) or using 'C-style' function call format (or you
can mix both).
An example of C-style calling:
string $na=getAttr("persp.translateX");
An example of `` calling:
string $na=`getAttr
"persp.translateX"`;
A mix of both:
string $na=`getAttr("persp.translateX")`;
It's simply a matter of personal preference or taste what you like to use,
but it's
recommended to maintain a consistent style throughout your scripts (just
for aesthetical
reasons :)
[I myself prefer using C-style calling if possible, and using backquoting
only for commands
where flags need to be specified. I prefer it as the resulting script will
be more similar
to C code. ;]
The following commands are also equivalent:
string $sel[]=`ls
-sl -tr`;
string $sel[]=ls("-sl","-tr");
Use whatever you like ;)
Passing expressions as MEL command arguments:
--------------------------------------------
This is an important one. If you pass expressions as arguments for a MEL
command, you always
need to put them into parentheses, otherwise a syntax error will occur (at
least as my
experience goes). Some examples:
Baddie
Goodie
------
------
-
`ls $my_object`
`ls $my_prefix+"_"+$my_object`
`ls ($my_prefix+"_"+$my_object)`
`getAttr $my_xform+".translateX"` `getAttr
($my_xform+".translateX")`
-
`ls $obj[0] $obj[1]`
`ls $pre[0]+$obj[0] $pre[1]+$obj[1]` `ls
($pre[0]+$obj[0]) ($pre[1]+$obj[1])`
Script and global proc naming:
-----------------------------
You should design your script in a way that preferably your script should
have one
global proc, and it should have the same name as the script file name (not
counting the ".mel"
extension). This way you don't have to explicitly source your script before
using.
(See "Recommended structure of a script" section for more details.)
Be aware of string / string[] command results:
---------------------------------------------
Some commands return a single string result, while others return string arrays
(string[]).
You should always be aware of this (and check the command docs beforehand
to know), as it
can break your script easily.
string $na=`ls -sl`;
This will break (ls returns a string array).
string $na[]=`ls -sl`;
This is correct.
But also note that in MEL syntax, once a variable is declared as an array,
you don't need
(==shouldn't) use the [] brackets anymore. So:
{
string $na[];
$na=`ls -sl`;
This is correct.
}
{
string $na[];
$na[]=`ls -sl`;
This is not.
}
Assignments and conditionals differences (just like in C)
---------------------------------------------------------
[Everyone with just a little C experience can bravely skip this. ;]
These things work exactly like in C (and you have to watch out exactly for
the same things
as well :)
An assignment is like:
$a=3 where you simply
assign a value to a variable.
when using this
in an expression, it's like if you simply use the
assignment value
(3).
$a==3 a conditional
check (if $a is 3 or not). results in 1 if true,
0 (zero) otherwise.
Now what you have to always watch out (just like in C) is the difference
between these two.
Beginners may seem the following code differently than it really is:
{
int $a=3;
if ($a=5) print("hey man, 'a' equals
five!"); // but no it doesn't!
}
If you're surprised why the above code 'believes' that $a equals five (it
really doesn't),
check the conditional expression more carefully. It's not a conditional,
but an assignment!
What it really does is, it's first simply performs the $a=5 assignment (puts
5 in $a), then
check the assignment value (5) against zero. In Maya (and C) non-zero values
mean TRUE, so
it executes the print() command.
Some examples:
if (1) ... ;
always true
if (1976) ... ;
if (0) ... ;
always false
if ($a)
true if $a is non-zero, false
otherwise
if (!$a)
reverse of the above
When doing conditionals, they return 1 for 'true' and 0 for 'false'.
$a=( 3==3 );
$a will be 1
$a=( 3==5 );
$a will be 0
This is what you should watch out for. But there also a bright side: by taking
advantage of
this kind of expression evaluation, we can write much shorter code. Another
example:
{
string $sel[];
if ( size($sel=`ls -sl`)>0 )
{
print("The current
selection is:\n");
print($sel);
}
}
In the above code, the 'if' line first puts the current selection list into
the $sel[] array,
then checks if it has more than zero elements, and if so it prints them.
Another common example
could be:
{
if ( ($fh=fopen("my_file","r") ) !=
0 )
{
// read the file
here
fclose($fh);
}
}
I won't explain this, instead think about it yourself. :) A shorter equivalent
of the 'if' line
is:
if ( $fh=fopen("my_file","r") )
...
(Consult some C manuals and code examples if it's still not clear or need
to familiarize yourself
with this.)
The ?: expression
-----------------
Just to quickly mention another useful expression borrowed from C. I won't
get into much detail
(by now you should already got yourself a C book! :), instead some examples:
int $b=( $a!=0 ? 3 : 5 );
the value of $b will be
- 3 if $a is not zero,
- 5 if $a is zero
string $s=( $a<6 ? "smaller" : "larger"
); the $s string will be "smaller" if
$a<6, or "larger" otherwise.
The general form of this expression is:
( <conditional> ? <true-expr> : <false-expr>
)
The above returns the value of <true-expr> if <conditional> is
true, and <false-expr>
otherwise.
Global variables
----------------
Avoid them if possible. :)
But really, pass all your variable-stuff through procedure arguments instead.
They should be avoided at all costs if you plan to use them for passing data
between procedures
of a single script.
Other coding tips'n'techniques
==============================
Using `ls` for filtering and safe query
---------------------------------------
- $sl=`ls -tr $sl`;
- size(`ls -tr ...`)
Proper Maya-style selection handling
------------------------------------
See the "Selections (or how to handle a selection list Maya-style)" section
above on
more about this very important issue.
Setting attributes '.ma' style
------------------------------
A quick way to change more attributes on the same node is the same way Maya
does it
in ascii scene (.ma) files:
{
select -r my_node1;
// note the -r (replace selection)
setAttr .my_attr1 0;
setAttr .my_attr2 1;
// ...
}
If you specify the attribute name the above form for setAttr (no node name,
just "dot
attribute"), it will set the specified attribute on the selected node. (But
don't forget
to select the node first!)
Protecting execution flow with catch()
--------------------------------------
Sometimes you need to issue a command you know beforehand it may fail, but
you still
need to do it (and you don't want your script to be stopped by an error message).
This
is what the catch() command for (note the back-quotes):
catch(`delete my_node`)
This will execute the command, and returns 1 if an error occured, 0 otherwise
(if I
remember correctly ;^) Regardless, script execution won't stop.
[A practical (but a bit non-standard) example. There's a bug in Maya 4.0
in the
connectAttr -f command. If you try to connect to an attribute which has a
connection
already, you given a warning message. But (although it's just a warning)
it stops
script execution. Luckily we have catch(). So instead of using
connectAttr -f node1.attr1 node2.already_connected_attr2;
I always use
catch(`connectAttr -f node1.attr1 node2.already_connected_attr2`);
]
Running in UI/batch mode
------------------------
Maya can be run in interactive (UI) and in batch mode. The UI mode is the
usual way
people use it when doing 'regular' work (modeling, animating, or anything
interactive).
Batch mode is more useful for TDs and technical people. In this mode, no
windows will
be opened, but all Maya functionality (except UI) is available -- scenes
can be loaded
or saved, scripts can be run, etc. Ideal for batch-processing scenes with
custom
scripts, and the like.
You can call Maya in batch mode by entering
maya -batch [-prompt] on
unix/linux, or
mayabatch [-prompt] on
nt
(If you specify the -prompt flag, Maya will open a command prompt in the
current console,
and you can enter commands, etc. Check the manuals (or type maya -help) for
more on this.)
Anyway, your script may needs to know if Maya is in batch or UI mode. This
can be queryed
by the following command:
`about -b`
this will return 1 for batch and 0 for UI mode.
Using dgeval()
--------------
[This is a somewhat 'advanced' topic. Altough I try to be clear as possible,
I assume the
reader also has a clear understanding of the working details of the Dependency
Graph.]
The dgeval command can be used to directly force the evaluation of a specified
node
attribute. Usually there's no need for it (Maya always evaluates what's needed
-- or let's
say usually, khm :) but there can be situations where it comes handy.
An example: you have two mesh shapes, and you want to copy the exact mesh
data from one
to the other. An obvious solution is to connect one mesh shape's 'outMesh'
attribute to
the other's 'inMesh', then disconnect it:
connectAttr meshShape1.outMesh meshShape2.inMesh;
disconnectAttr meshShape1.outMesh meshShape2.inMesh;
If you try this through the Script Editor, after issuing the first command
you'll see that
the second mesh shape became identical to the first. Then you disconnect
the attributes
(we like to get rid of loose ends, aren't we?:)
However, if you try it in a script this will not work at all (or even in
the Script Editor,
if you issue the two commands in one execution.) The reason is that although
Maya performs
both the connect and the disconnect operations, the data simply won't 'get
through' -- to
be so, the destination mesh shape would need an evaluation (which usually
happens when Maya
refreshes the viewport).
So this is one case we need to force an evaluation ourselves. Insert the
following line
between the above first and the second lines:
dgeval meshShape2.outMesh;
This will evaluate the destination mesh's outMesh attribute after the connection
is made,
giving us the result we expected (to put it another way, it's if like meshShape1
would be
in the history of meshShape2).
Writing complete plug-in modules
================================
Once you reached so far you're a scripting pro and don't have anything else
to know about
MEL scripting, you may start thinking about trying writing API/C++ plug-ins.
I recommend
you to begin at once with no hesitation -- Maya has a rich API which is so
easy to use
that it takes not much more than a MEL script (ok, it's a bit more complex,
but still,
nothing psychedelic, or what :)
Using MEL and API together for most efficiency
----------------------------------------------
Once you gave API a few tries, you will see that
- it's much more capable,
- much faster,
- a lot of things are much more hassle than in MEL, goddamit!!!
:)
The last one is the price of more flexibility. API code can be much larger
than the
equivalent MEL code (IMHO it's also more difficult to debug). If it comes
to usual scene
management (node/connection creations, etc.) MEL can be much comfortable
-- just try to
write the equivalent of a simple connectAttr in API and you'll know what
I'm talking about ;)
As far as my experience goes, the most effective way of developing for Maya
(optimal in both
development time and execution speed) is using a MEL/API hybrid code, where
the application
'core' is in API, and there's a MEL 'shell' around it (and the MEL code is
calling the API
routines through custom MEL commands defined by the API part).
What is a module
----------------
A module is a collection of MEL script(s) and plug-in shared object file(s)
placed under a
pre-defined directory structure. To put it in another way, a module is like
an 'extension'
to Maya which provides it's functionality through it's script(s) and/or plug-in(s).
A module can consist of script(s), plug-in(s) or both, but none of them is
mandatory (it
can be scripts and/or plug-ins only). Also, a module is just a convention
-- it's the same
if all scripts were copied to the scripts/ folder and all plug-ins were in
the plug-ins/
folder. The advantage of modules is that you can keep all related things
in one place.
[For example, you can create a module out of scripts only -- it's one good
way to have a
common place for scripts written by the same person. For instance, I myself
have a Billauch
and an Ewert module (just to have a little free embedded advertisement for
some guys :) ]
A module consists of two parts: a module definition (a single line text file)
and a disk
directory containing the module folders.
The directory structure of a module looks the following way:
<module root dir>
|
+- icons/ (shelf
icons)
|
+- plug-ins/ (place
all .so/.mll files here)
|
+- scripts/ (place
all .mel scripts here)
|
+- ...
(optional other folders, like docs, bin, etc.)
All the above folders should have the exact specified name; but all of them
are optional
(ie. doesn't need to exist).
The module definition file is a text file containing a single line in the
following format:
+ <module_name> <module_version> <module_root_path>
For example:
+ xyMayaTools 1.0 T:/!share/maya/4.0/modules/xyMayaTools
The 'plus' is mandatory and should be the first character. All arguments
should be separated
by space(s).
Maya looks for the module definition files (the text files) in the directory
specified by
the MAYA_MODULE_PATH environment variable.