Maya Python A Pi
Maya Python A Pi
A Complete Reference for Maya Python and the Maya Python API
Adam Mechtley
Ryan Trowbridge
Front Matter
Copyright
Acquiring Editor: Laura Lewin
Development Editor: Sara Scott
Project Manager: Sarah Binns
Designer: Alisa Andreola
Morgan Kaufmann Publishers is an imprint of Elsevier.
225 Wyman Street, Waltham MA 02451
herein.
Library of Congress Cataloging-in-Publication Data
Application submitted
British Library Cataloguing-in-Publication Data
A catalogue record for this book is available from the British Library.
ISBN: 978-0-12-378578-7
For information on all Morgan Kaufmann publications, visit our Web site at
www.mkp.com or www.elsevierdirect.com
Typeset by: diacriTech, India
11 12 13 14 15 5 4 3 2 1
Printed in the United States of America
Acknowledgments
We would like to thank the many people who made this book possible. Foremost,
we want to thank our wives, Laurel Klein and Brenda Trowbridge, without whose
unflagging support we would have been hopeless in this endeavor. We would also
like to thank the various contributors to this project for their individual efforts,
including Seth Gibson of Riot Games for Chapters 3 and 5, Kristine Middlemiss of
Autodesk for Chapter 8, and Dean Edmonds of Autodesk for his technical editing, a
man whose power to explain is surpassed only by his knowledge of the topics
covered herein.
We would also like to thank the team at Focal Press who has helped out on this
project, including Sara Scott, Laura Lewin, and Anas Wheeler. Your support and
patience has been a blessing. Thanks also to Steve Swink for putting us in touch with
the wonderful people at Focal Press.
We want to thank all of the people in the Maya Python and API community who
have been essential not only for our own growth, but also for the wonderful state of
knowledge available to us all. Though we will undoubtedly leave out many important
people, we want to especially thank Chad Dombrova, Chad Vernon, Paul
Molodowitch, Ofer Koren, and everyone who helps maintain the tech-artists.org and
PyMEL communities.
Last, but certainly not least, we want to thank our readers for supporting this
project. Please get in touch with us if you have any feedback on the book!
Hopefully you can see that such tools would save time, make file selection more
accurate, and let the animators focus on their creative task. The best way to create
such tools is by using one of Mayas built-in scripting languagesparticularly Python.
Python is a scripting language that was originally developed outside of Maya, and
so it offers a powerful set of features and a huge user base. In Maya 8.5, Autodesk
added official support for Python scripting. The inclusion of this language built upon
Mayas existing interfaces for programming (the MEL scripting language and the C++
API). Since Maya Embedded Language (MEL) has been around for years, you might
wonder why Python even matters. A broader perspective quickly reveals many
important advantages:
Community: MEL has a very small user base compared to Python because only Maya developers
use MEL. Python is used by all kinds of software developers and with many types of applications.
Power: Python is a much more advanced scripting language and it allows you to do things that are
not possible in MEL. Python is fully object-oriented, and it has the ability to communicate effortlessly
with both the Maya Command Engine and the C++ API, allowing you to write both scripts and plugins with a single language. Even if you write plug-ins in C++, Python lets you interactively test API
code in the Maya Script Editor!
Cross-platform: Python can execute on any operating system, which removes the need to recompile
tools for different operating systems, processor architectures, or software versions. In fact, you do not
need to compile at all!
Industry Standard: Because of Pythons advantages, it is rapidly being integrated into many other
content-creation applications important for entertainment professionals. Libraries can be easily
shared between Maya and other applications in your pipeline, such as MotionBuilder.
Whitespace
Many programming languages are indifferent to leading whitespace, and instead use
mechanisms like curly braces ({ and }) to indicate code blocks, as in the following
hypothetical MEL example, which would print numbers 1 through 5.
for (int $i=0; $i<5; $i++)
{
int $j = $i+1;
print($j+"\n");
}
In this example, the indentation inside the block is optional. The example could
be rewritten like the following lines, and would produce the same result.
for (int $i=0; $i<5; $i++)
{
int $j = $i+1;
print($j+"\n");
}
While Python does not care exactly how you indent inside your blocks, they must
be indented to be syntactically valid! The standard in the Python community is to
either use one tab or four spaces per indentation level.
Because of how this book is formatted, you may not be able to simply copy and
paste examples from an e-book format. We try to mitigate this problem by providing
as many code examples as possible as downloads on the companion web site, which
you can safely copy and paste, and leave only short examples for you to transcribe.
Line Breaks
Python also has some special rules governing line breaks, which is particularly
important for us since we have no way to know exactly how text in this book will
wrap on different e-book hardware.
Most programming languages allow you to insert line breaks however you like, as
they require semicolons to indicate where line breaks occur in code. For example, the
following hypothetical MEL example would construct the sentence I am the very
model of a modern major general. and then print it.
string $foo = "I am the very" +
"model of a modern" +
"major general");
print($foo);
If you tried to take the same approach in Python, you would get a syntax error.
The following approach would not work.
foo = I am the very +
model of a modern +
major general
print(foo)
In most cases in Python, you must add a special escape character (\) at the end of
a line to make it continue onto the following line. You would have to rewrite the
previous example in the following way.
foo = I am the very + \
model of a modern + \
major general
print(foo)
There are some special scenarios where you can span onto further lines without
an escape sequence, such as when inside of parentheses or brackets. You can read
more about how Python handles line breaks online in Chapter 2 of Python Language
Reference. Well show you where you can find this document in Chapter 1.
Our Approach
Because of Pythons lexical requirements, it is difficult to craft code examples that are
guaranteed to be properly displayed for all of our readers. As such, our general
approach is to indicate actual line endings in print with the optional semicolon
character (;) as permitted by Python. We trust you to recognize them as line endings
where there should be a return carriage and to adjust your indentation if necessary.
For instance, though we can be reasonably certain that the previous examples are
short enough to have appeared properly for all our readers, we would have rewritten
them in the following way throughout the remainder of this book.
for i in range(5):
j = i+1;
print(j);
foo = I am the very model of a modern major general;
print(foo);
Since Python allows but does not require the semicolon, we avoid using it in our
actual code examples on the companion web site, though we do show them in the text
for readers who do not have the code examples on their computers in front of them.
Quotation Marks
It is also worth mentioning that some of our code examples throughout the book
make use of double quotation marks ("), some make use of single quotation marks
(), and some make use of triple sequences of either type (""" or ). While it
should be clear which marks we are using when reading a print copy, double
quotation marks may not copy and paste correctly. In short, make sure you double
check any code you try to copy and paste from the book.
Comments
Finally, it is worth noting here that, like other programming languages, Python allows
you to insert comments in your code. Comments are statements that only exist for the
developers reference, and they are not evaluated as part of a program. One way to
create a comment is to simply write it in as a string literal. Though we will talk more
about strings later, the basic idea is that you can wrap a standalone statement in
quotation marks on its own line and it becomes a comment. In the following code
snippet, the first line is a comment describing what the second line does.
Use the print() function to print the sum of 5 and 10
print(5+10);
Many of our examples, especially transcriptions of code from example files on the
companion web site, are devoid of comments. We opted to limit comments to save
printing space, but hope that you will not be so flippant as a developer! Most of our
examples on the companion web site contain fairly thorough comments, and we tend
to break up large sections of code in the text to discuss them one part at a time.
Python Hands On
In our introduction to Python, we have told you how useful it will be to you in Maya.
Well now run through a brief example project to get a taste of things to come. Dont
worry if you do not understand anything in the example scripts yet, as this project is
for demonstration purposes only.
In this example, you are going to execute two versions of the same script, one
written in MEL using only Maya commands, and the other written using the Maya
Python API. These scripts highlight one example of the dramatically different results
you can achieve when using Python in place of MEL.
The scripts each create a basic polygon sphere with 200 by 200 subdivisions (or
39,802 vertices) and apply noise deformation to the mesh. In essence, they iterate
through all of the spheres vertices and offset each one by a random value. This value
is scaled by an amount that we provide to the script to adjust the amount of noise.
Please also note that the Python script in the following example makes use of
functions that were added to the API in Maya 2009. As such, if youre using an
earlier version of Maya, you can certainly examine the source code, but the Python
script will not work for you.
1. Open the Maya application on your computer.
2. In Mayas main menu, open the Script Editor by navigating to Window General Editors
Script Editor.
3. You should now see the Script Editor window appear, which is divided into two halves (Figure
0.1). Above the lower half you should see two tabs. Click on the Python tab to make Python the
currently active language.
4. Download the polyNoise.py script from the companion web site and make note of its location.
5. Open the script you just downloaded by navigating to the menu option File Load Script in the
Script Editor window (rather than the main application window). After you browse to the script and
load it, you will see the contents of the script appear in the lower half of the Script Editor window,
where they may be highlighted.
6. Click anywhere in the bottom half of the Script Editor to place your cursor in it, and execute the
script by pressing Ctrl + Enter.
This final line shows how long it took your computer to create the sphere,
subdivide it, and apply noise to the mesh. In this particular case, it took our computer
0.53 seconds, though your result may be slightly different based on the speed of your
computer.
Now, you will execute the MEL version of this script to see how long it takes to
perform the same operation.
1. In Mayas Script Editor, click the MEL tab that appears above the bottom half of the window.
2. Download the polyNoise.mel script from the companion web site and make note of its location.
3. Open the script you just downloaded by navigating to the menu option File Load Script in the
Script Editor window. After loading the script, you should see its contents appear in the lower half of
the Script Editor window, and they may be highlighted.
4. Click anywhere in the bottom half of the Script Editor to place your cursor in it, and execute the
script by pressing Ctrl + Enter.
You should very quickly notice a problemor should we say very slowly. Maya
will stop responding after executing the MEL script. Mac users will see the infamous
beach ball loading cursor. Dont worry though! Maya has not crashed. It will create
the sphere eventually. You might want to go get a cup of coffee or two before you
come back to check on Maya. After what seems like an eternity, Maya will finally
create the sphere with the noise and print something like the following line in the top
half of the Script Editor window.
Execution time: 203.87 seconds.
Because the Python script and the MEL script both perform the exact same task,
you could theoretically use either one to get the job done. Obviously, however, you
would want to use the Python version. If we created a MEL script that did this task it
would be useless in production. No artist would want to use a script that takes a few
minutes to execute. It would make the tool very difficult for artists to experiment with
and most likely it would be abandoned.
There is only one catch: The Python script was created using the Maya Python
API. If you compare the two scripts side-by-side, you will see that the Python script is
slightly more complex than the MEL script as a result. Using the API is more
complicated than just using Maya commands, yet MEL cannot use the API directly.
This disadvantage on MELs part is the primary reason the performance difference is
so dramatic in this case.
Another issue to point out about the Python example script is that it is not
complete. Because it uses API calls to modify objects, you cannot use undo like you
could with MEL if you didnt like the result. Although Python can certainly use Maya
commands just like MEL (and automatically benefit from the same undo support), we
used the API in this case because it was necessary to gain the substantial speed
increase. If we used Python to call Maya commands, however, the Python script
would have been just as slow as the MEL script.
Although we chose not to in this example for the sake of simplicity, we would
need to turn this script into a Python API plug-in to maintain undo functionality and
still use the API to modify objects. There may in fact be many times you want to do
this very same thing to mock up a working version before adding the final bits of
script to create a plug-in. Dont worryit isnt hard, but it is another step you will
need to take. Fortunately for you, one of the main topics in this book is to explain
how to work with the API using Python, so you will have no trouble creating
lightning-fast tools yourself. Once you understand more about how Python works in
Maya, you could write other scripts that work with meshes. As you can see, MEL
tends to run very slowly on objects with large vertex counts, so this opens the door
for new tools in your production.
Another convenient advantage to using the Python API is it uses the same classes
as the C++ API. If you really needed additional speed, you could easily convert the
Python script into a C++ plug-in. If you dont understand C++, you could pass on the
script as a template for a programmer at your studio, as the API classes are identical in
both languages. C++ programmers can also find the Python API useful because they
can access the Python API in Maya interactively to test out bits of code. If you are
using C++, on the other hand, you absolutely must compile a plug-in to test even one
line of code. You can even mix the Maya API with Python scripts that use Maya
commands.
With all of these features in mind, we hope you are excited to get started learning
how to use Python in Maya. By the end of this book, you should be able to create
scripts that are more complicated than even the example from this chapter. So without
further delay, lets get to it!
Table of Contents
Front Matter
Copyright
Acknowledgments
Introduction: Welcome to Maya Python
PART 1: Basics of Python and Maya
Chapter 1: Maya Command Engine and User Interface
Chapter 2: Python Data Basics
Chapter 3: Writing Python Programs in Maya
Chapter 4: Modules
Chapter 5: Object-Oriented Programming in Maya
PART 2: Designing Maya Tools with Python
Chapter 6: Principles of Maya Tool Design
Chapter 7: Basic Tools with Maya Commands
Chapter 8: Advanced Graphical User Interfaces with Qt
PART 3: Maya Python API Fundamentals
Chapter 9: Understanding C++ and the API Documentation
Chapter 10: Programming a Command
Chapter 11: Data Flow in Maya
Chapter 12: Programming a Dependency Node
Index
PART 1
Basics of Python and Maya
Chapter 1
Maya Command Engine and User Interface
Chapter Outline
Interacting with Maya 4
Maya Embedded Language 4
Python 4
C++ Application Programming Interface 5
Python API 5
Executing Python in Maya 6
Command Line 6
Script Editor 7
Maya Shelf 9
Maya Commands and the Dependency Graph 11
Introduction to Python Commands 14
Flag Arguments and Python Core Object Types 19
Numbers 19
Strings 19
Lists 20
Tuples 20
Booleans 20
Flag = Object Type 21
Command Modes and Command Arguments 22
Create Mode 22
Edit Mode 22
Query Mode 23
Python Command Reference 23
Synopsis 24
Return Value 25
Related 25
Flags 25
Python Examples 26
Python Version 26
Python Online Documentation 26
Concluding Remarks 27
By the end of this chapter, you will be able to:
Compare and contrast the four Maya programming interfaces.
Use the Command Line and Script Editor to execute Python commands.
Create a button in the Maya GUI to execute custom scripts.
Describe how Python interacts with Maya commands.
Define nodes and connections.
Describe Mayas command architecture.
Learn how to convert MEL commands into Python.
Locate help for Python commands.
Compare and contrast command arguments and flag arguments.
Define the set of core Python data types that work with Maya commands.
Compare and contrast the three modes for using commands.
Identify the version of Python that Maya is using.
Locate important Python resources online.
This chapter introduces Maya and Python and compares and contrasts the four
Maya programming interfaces. It also shows how to use the Command Line and the
Script Editor to execute Python commands and how Python interacts with Maya
commands. Mayas command architecture is also explained.
Keywords
Python
Python is a scripting language that was formally introduced to Maya in version 8.5.
Python can execute the same Maya commands as MEL using Mayas Command
Engine. However, Python is also more robust than MEL because it is an objectoriented language. Moreover, Python has existed since 1980 and has an extensive
library of built-in features as well as a large community outside of Maya users.
Python API
When Autodesk introduced Python into Maya, they also created wrappers for many of
the classes in the Maya C++ API. As such, developers can use much of the API
functionality from Python. The total scope of classes accessible to the Python API has
grown and improved with each new version of Maya. This powerful feature allows
users to manipulate Maya API objects in ordinary scripts, as well as to create plug-ins
that add new features to Maya.
In this book, we focus on the different uses of Python in Maya, including
commands, user interfaces, and the Python API. Before we begin our investigation,
we will first look at the key tools that Maya Python programmers have at their
disposal.
Command Line
The first tool of interest is the Command Line. It is located along the bottom of the
Maya GUI. You can see the Command Line highlighted in Figure 1.2.
3. Press Enter.
4. Next enter the following line of code in the text field.
maya.cmds.polySphere();
5. Press Enter. The above command will create a polygon sphere object in the
viewport and will print the following results on the right side of the Command Line.
# Result: [upSphere1, upolySphere1]
You can use the Command Line any time you need to quickly execute a
command. The Command Line will only let you enter one line of code at a time
though, which will not do you much good if you want to write a complicated script.
To perform more complex operations, you need the Script Editor.
Script Editor
One of the most important tools for the Maya Python programmer is the Script Editor.
The Script Editor is an interface for creating short scripts to interact with Maya. The
Script Editor (shown on the right side in Figure 1.2) consists of two panels. The top
panel is called the History Panel and the bottom panel is called the Input Panel. Lets
open the Script Editor and execute a command to make a sphere.
1. Open a new scene by pressing Ctrl + N.
2. Open the Script Editor using either the button located near the bottom right
corner of Mayas GUI, on the right side of the Command Line (highlighted in Figure
1.2), or by navigating to Window General Editors Script Editor in Mayas
main menu. By default the Script Editor displays two tabs above the Input Panel. One
tab says MEL and the other tab says Python.
3. Select the Python tab in the Script Editor.
4. Click somewhere inside the Input Panel and type the following lines of code.
import maya.cmds;
maya.cmds.polySphere();
5. When you are finished press the Enter key on your numeric keypad. If you do
not have a numeric keypad, press Ctrl + Return.
The Enter key on the numeric keypad and the Ctrl + Return shortcut are used
only for executing code when working in the Script Editor. The regular Return key
simply moves the input cursor to the next line in the Input Panel. This convention
allows you to enter scripts that contain more than one line without executing them
prematurely.
Just as in the Command Line example, the code you just executed created a
generic polygon sphere. You can see the code you executed in the History Panel, but
you do not see the same result line that you saw when using the Command Line. In
the Script Editor, you will only see a result line printed when you execute a single line
of code at a time.
6. Enter the same lines from step 4 into the Input Panel, but do not execute them.
7. Highlight the second line with your cursor by triple-clicking it and then press Ctrl
+ Return. The results from the last command entered should now be shown in the
History Panel.
# Result: [upSphere2, upolySphere2]
Apart from printing results, there are two important things worth noting about the
previous step. First, highlighting a portion of code and then pressing Ctrl + Return
will execute only the highlighted code. Second, highlighting code in this way before
executing it prevents the contents of the Input Panel from emptying out.
Another useful feature of the Script Editor is that it has support for marking
menus. Marking menus are powerful, context-sensitive, gesture-based menus that
appear throughout the Maya application. If you are unfamiliar with marking menus in
general, we recommend consulting any basic Maya users guide.
To access the Script Editors marking menu, click and hold the right mouse
button (RMB) anywhere in the Script Editor window. If you have nothing selected
inside the Script Editor, the marking menu will allow you to quickly create new tabs
(for either MEL or Python) as well as navigate between the tabs. As you can see,
clicking the RMB, quickly flicking to the left or right, and releasing the RMB allows
you to rapidly switch between your active tabs, no matter where your cursor is in the
Script Editor window. However, the marking menu can also supply you with contextsensitive operations, as in the following brief example.
1. Type the following code into the Input Panel of the Script Editor, but do not
execute it.
maya.cmds.polySphere()
2. Use the left mouse button (LMB) to highlight the word polySphere in the Input
Panel.
3. Click and hold the RMB to open the Script Editors marking menu. You should
see a new set of options in the bottom part of the marking menu.
4. Move your mouse over the Command Documentation option in the bottom of
the marking menu and release the RMB. Maya should now open a web browser
displaying the help documentation for the polySphere command.
As you can see, the Script Editor is a very useful tool not only for creating and
executing Python scripts in Maya, but also for quickly pulling up information about
commands in your script. We will look at the command documentation later in this
chapter.
At this point, it is worth mentioning that it can be very tedious to continually type
common operations into the Script Editor. While the Script Editor does allow you to
save and load scripts, you may want to make your script part of the Maya GUI. As we
indicated earlier, clicking GUI controls in Maya simply calls commands or executes
scripts that call commands. Another tool in the Maya GUI, the Shelf, allows you to
quickly make a button out of any script.
Maya Shelf
Now that you understand how to use the Command Line and the Script Editor, it is
worth examining one final tool in the Maya GUI that will be valuable to you. Lets say
you write a few lines of code in the Script Editor and you want to use that series of
commands later. Maya has a location for storing custom buttons at the top of the main
interface, called the Shelf, which you can see in Figure 1.3. If you do not see the Shelf
in your GUI layout, you can enable it from Mayas main menu using the Display
UI Elements Shelf option.
2. Click the Custom tab in the Shelf. You can add buttons to any shelf, but the
Custom shelf is a convenient place for users to store their own group of buttons.
3. Click and drag the LMB over the script you typed into the Script Editor to
highlight all of its lines.
4. With your cursor positioned over the highlighted text, click and hold the MMB
to drag the contents of your script onto the Shelf.
5. If you are using Maya 2010 or an earlier version, a dialog box will appear. If you
see this dialog box, select Python to tell Maya that the script you are pasting is
written using Python rather than MEL.
6. You will now see a new button appear in your Custom tab. Left-click on your
new button and you should see a red sphere appear in your viewport as in Figure 1.3.
If you are in wireframe mode, make sure you enter shaded mode by clicking
anywhere in your viewport and pressing the number 5 key.
You can edit your Shelf, including tabs and icons, by accessing the Window
Settings/Preferences Shelf Editor option from the main Maya window. For more
information on editing your Shelf, consult the Maya documentation or a basic Maya
users guide. Now that you have an understanding of the different tools available in
the Maya GUI, we can start exploring Maya commands in greater detail.
ways to issue Maya commands, which more clearly illustrates the distinction.
Figure 1.5 highlights how Python interacts with the Maya Command Engine.
While Python can use built-in commands to retrieve data from the core, it can also call
custom, user-made commands that use API interfaces to manipulate and retrieve data
in the core. These data can then be returned to a scripting interface via the Command
Engine. This abstraction allows users to invoke basic commands (which have
complex underlying interfaces to the core) via a scripting language.
commands can be created with the Maya C++ API or the Python API. Now that you
have a firmer understanding of how Maya commands fit into the programs
architecture, we can go back to using some commands.
1. In the menu for the Script Editor window, select Edit Clear History to clear
the History Panels contents.
2. In the main Maya window, navigate to the menu option Create Polygon
Primitives Cube.
3. Check the History Panel in the Script Editor and confirm that you see something
like the following results.
polyCube -w 1 -h 1 -d 1 -sx 1 -sy 1 -sz 1 -ax 0 1 0 -cuv 4 -ch 1; //
Result: pCube1 polyCube1 //
The first line shown is the polyCube MEL command, which is very similar to the
polySphere command we used earlier in this chapter. As you can see, a MEL
command was called when you selected the Cube option in the Polygon Primitives
menu. That MEL command was displayed in the Script Editors History Panel.
Because Mayas entire interface is written with MEL, the History Panel always
echoes MEL commands when using the default Maya interface. Custom user
interfaces could call the Python version of a command, in which case the History
Panel would display the Python command.
This problem is not terribly troublesome for Python users though. It does not
take much effort to convert a MEL command into Python syntax, so this feature can
still help you learn which commands to use. The following example shows what the
polyCube command looks like with Python.
import maya.cmds;
maya.cmds.polyCube(
w=1, h=1, d=1, sx=1, sy=1, sz=1,
ax=(0, 1, 0), cuv=4, ch=1
);
If you execute these lines of Python code they will produce the same result as the
MEL version. However, we need to break down the Python version of the command
so we can understand what is happening. Consider the first line:
import maya.cmds;
This line of code imports a Python module that allows you to use any Maya
command available to Python. There is only one module that holds all Maya
commands and you only need to import it once per Maya session. Once it is in
memory you dont need to import it again (we only have you reimport it for each
example in case youre picking the book back up after a break from Maya). We will
discuss modules in greater depth in Chapter 4. The next line of code is the Python
command.
maya.cmds.polyCube(
w=1, h=1, d=1, sx=1, sy=1, sz=1,
ax=(0, 1, 0), cuv=4, ch=1
);
As you can see, the name of the command, polyCube, is prefixed by the name of
the module, maya.cmds. The period between them represents that this command
belongs to the Maya commands module. We then supply the command several flag
arguments inside of parentheses. A key-value pair separated by the equals sign, such
as w=1, represents the name and value for the flag argument, and each of these pairs is
separated by a comma.
Each flag may be added using a shorthand abbreviation or long version of the
flag name. Although many Maya programmers tend to use the shorthand flag names in
their code, it can make the code more difficult to read later. In the previous example,
the command is using the shorthand flags so it is hard to understand what they mean.
Here is the same version of the command with long flag names.
maya.cmds.polyCube(
width=1,
height=1,
depth=1,
subdivisionsX=1,
subdivisionsY=1,
subdivisionsZ=1,
axis=(0, 1, 0),
createUVs=4,
constructionHistory=1
);
The long names are easier to read and so it can be good practice to use them
when scripting. Code that is easier to read can be much easier to work with
especially if you or a coworker has to make any changes several months later! You
may now be wondering how to find the long flag names in the future.
1. Type the following lines into the Script Editor and press Ctrl + Return to
execute them.
import maya.cmds;
print(maya.cmds.help(polyCube));
2. Look for the results in the History Panel, which should look like the following
lines.
Synopsis: polyCube [flags] [String]
Flags:
e edit
q query
As you can see, the result first displays the command for which help was
requestedpolyCube in this case. The following items in brackets, [flags] and
[String], show MEL syntax for executing the command. In MEL, the command is
followed by any number of flag arguments and then any number of command
arguments. Well differentiate these two items momentarily.
Next, the output shows the list of flags for the command, displaying the short
name on the left, followed by the long name in the middle column. Each flag is
prefixed by a minus symbol, which is required to indicate a flag in MEL syntax, but
which you can ignore in Python. To the very right of each flag name is the data type
for each argument, which tells us what kind of value each flag requires.
We can see how flags work with the polyCube command. Consider the following
example.
import maya.cmds;
maya.cmds.polyCube();
Executing this command causes Maya to create a polygon cube with default
properties. The parentheses at the end of the command basically indicate that we want
Maya to do somethingexecute a command in this case. Without them, the command
will not execute. We will discuss this topic further in Chapter 3 when we introduce
functions. For now, it suffices to say that any command arguments we wish to specify
must be typed inside of the parentheses, as in the following alternative example.
import maya.cmds;
maya.cmds.polyCube(name=myCube, depth=12.5, height=5);
If you execute the previous lines, Maya will create a polygon cube named
myCube with a depth of 12.5 units and a height of 5 units. The first flag we set,
name, is a string, as indicated in the help results. A string is a sequence of letters and
Type
Numbers
Strings
Lists
Tuples
Examples
1
5
3.14159
9.67
"Maya"
ate
"my dogs"
"""homework"""
True
False
Booleans 1
0
Note that Table 1.1 is not a complete list of Python core object typesthere are
many others that you may use for other purposes in your scripts. However, the core
object types in this list are the only ones that Maya commands have been designed to
use, so we may ignore the others for now. Other Python data types are discussed in
Chapter 2. Lets focus for now on the five types in this list.
Numbers
Maya commands expecting Python numbers will accept any real number. Examples
could include integer as well as decimal numbers, which correspond to int/long and
float/double types, respectively, in other languages.
Strings
The string type is any sequence of letters or numbers enclosed in single quotation
marks, double quotation marks, or a matching pair of triple quotation marks of either
type. For instance, boat, house, and car are equivalent to boat, house, and
car as well as to boat, house, and car. However, the string 3 is
different from the number object 3. Strings are typically used to name objects or
parameters that are accessible from the Maya user interface.
Lists
A list is a sequence of any number of Python objects contained within the bracket
characters [ and ]. A comma separates each object in the list. Any Python object may
be in a list, including another list!
Tuples
The Python tuple is very similar to the list type except that it is not mutable, which
means it cannot be changed. We discuss mutability in greater detail in Chapter 2.
Tuples are contained inside of ordinary parentheses, ( and ).
Booleans
A Boolean value in Python can be the word True or False (which must have the first
letter capitalized), or the numbers 1 and 0 (which correspond to the values True and
False, respectively). These values are typically used to represent states or toggle
certain command modes or flags.
Flag=Object Type
To find out what type of object a command flag requires, you can use the help
command. As you saw earlier in this chapter it will give you a list of the command
flags and what type of value they require. The argument type is not an optionyou
must pass a value of the required type or you will get an error. Using the polyCube
command as an example, lets look at its width flag and pass it correct and incorrect
argument types.
1. Create a new scene by pressing Ctrl + N.
2. Execute the Maya help command for the polyCube command in the Script Editor:
import maya.cmds;
print(maya.cmds.help(polyCube));
3. Look for the width flag in the results displayed in the History Panel and find its
argument type on the right side:
w width Length
As you can see, the width flag requires a Length type argument, as shown to the
right of the flag name. This is technically not a Python type but we can deduce that
Length means a number, so we should pass this flag some sort of number. If the
number needed to be a whole number, the flag would specify Int to the right of the
flag instead of Length. We can therefore also deduce that the flag may be passed a
decimal number in this case. Lets first pass a correct argument.
4. Type the following command into the Script Editor and press Ctrl + Return to
execute it.
maya.cmds.polyCube(width=10);
You should see the following result in the Script Editors History Panel.
# Result: [upCube1, upolyCube1] #
The result lets us know that the command succeeded and also shows that the
command returned a Python list containing the names of two new nodes that have
been created to make our cube object: pCube1 (a transform node) and
polyCube1 (a shape node). Now, lets see what happens when we intentionally
supply the width flag with the wrong data type.
5. Type the following command into the Script Editor and press Ctrl + Return to
execute it.
maya.cmds.polyCube(width=ten);
needed a Length type, Maya is now calling it a distance type. This can be confusing at
first but most of the time it is very clear what the flag argument requires simply by
looking at the flag in context. The help command does not describe what each flag
does, but you can get more detailed descriptions using the Python Command
Reference, which we will examine shortly.
Create Mode
Most commands at least have a create mode. This mode allows users to create new
objects in the scene and specify any optional parameters. By default, the polyCube
command operates in create mode.
1. Create a new Maya scene.
2. Execute the following lines in the Script Editor to create a new cube.
import maya.cmds;
maya.cmds.polyCube();
Note that you do not have to do anything special to execute commands in create
mode. Leave the cube in your scene for the next steps.
Edit Mode
Another mode that many commands support is edit mode. This mode allows users to
edit something the command has created.
3. Execute the following line in the Script Editor to change the cubes width.
maya.cmds.polyCube(pCube1, edit=True, width=10);
As you can see, you specified that the command should operate in edit mode by
setting the edit flag with a value of True. In edit mode, you were able to change the
width of the object named pCube1 to a value of 10. It is worth mentioning that
some flags in MEL do not require an argument, such as the edit flag (see help output
previously). Such flags, when invoked from Python, simply require that you set some
value (True) to indicate their presence.
Another important point worth noting is the syntax for operating in edit and
query modes. The first argument we pass to the command is called a command
argument, and specifies the name of the node on which to operate. As we saw in the
help output previously, MEL syntax expects command arguments to follow flag
arguments, while Python requires the opposite order. The reason for Pythons syntax
requirement will be discussed in greater detail in Chapter 3. Leave the cube in the
scene for the next step.
Query Mode
The last mode that many commands support is query mode. This mode permits users
to request information about something the command has already created.
4. Execute the following line in the Script Editor to print the cubes width.
maya.cmds.polyCube(pCube1, query=True, width=True);
The result in the History Panel should display something like the following line.
# Result: 10.0 #
As with edit mode, query mode requires that you specify a command argument
first and then set the query flag with a value of True. Another point worth noting is
that, although the width flag normally requires a decimal number (when being
invoked from create or edit mode), you simply pass it a value of True in query mode.
The basic idea is that in this case, the Command Engine is only interested in whether
or not the flag has been set, and so it will not validate the value you are passing it.
As you can see, these three modes allowed you to create an object, change it, and
finally pull up information about its current state. We also noted at the outset of this
section that some flags are only compatible with certain command modes. While the
help command will not give you this information, the Command Reference
documentation will.
1. In the main Maya window select the menu item Help Python Command
Reference.
2. At the top of the page is a text field. Click in the search field and enter the word
polyCube.
3. The page will update to show you only the polyCube command. Select the
polyCube command from the list under the label Substring: polyCube. Clicking this
link will show you detailed information for the polyCube command. As you can see,
the Command Reference documents break up information for all commands into
different sections, which we will now look at in more detail.
Synopsis
The synopsis provides a short description of what the command does. In this case the
synopsis states:
polyCube is undoable, queryable, and editable.
The cube command creates a new polygonal cube.
Return Value
The return value section describes what the command will return when it is executed.
In this case the documentation states:
string[] Object name and node name.
This description shows us that it returns a list of string type objects, which will be
the name of the object (transform node) and the (polyCube) node that were created.
Related
This section can help you with finding commands that are similar to the command at
which you are looking. For the polyCube command, this section lists other commands
for creating primitive polygon objects:
polyCone, polyCylinder, polyPlane, polySphere, polyTorus
Flags
For the polyCube command, the axis flag is listed first. It shows a short description
of what the flag does and then it lists the argument type to pass to it. The
documentation shows the following text:
[linear, linear, linear]
The command requires a Python list (or tuple) type and that list should hold three
real numbers. If int is not specified for an argument type, then the argument may be a
decimal number, though integer numbers are still valid. In this case, if we were to
define the argument for the flag it could be something like [1.00, 0, 0].
As we noted earlier, the documentation also displays icons to represent the
command mode(s) with which each flag is compatible.
C (Create): The flag can appear in the create mode.
E (Edit): The flag can appear in the edit mode.
Q (Query): The flag can appear in the query mode.
M (Multiuse): The flag can be used multiple times.
In MEL, multiuse flags are invoked by appending them multiple times after a
command. In Python, however, you can only specify a flag once. As such, Python
requires that you pass a tuple or list argument for a multiuse flag, where each element
in the tuple or list represents a separate use of the flag.
Python Examples
This section can be very useful for those just learning how to script with Python or
those learning how to work with Maya using Python. Here you can find working
examples of the command in use. Some example sections can have several different
samples to help you understand how the command works. The example for the
polyCube command in the documents shows you how to create a polygon cube and
also how to query an existing cubes width.
Python Version
One final point that is worth discussing is how to locate which version of Python your
copy of Maya is using. Python has been integrated into Maya since version 8.5, and
each new version of Maya typically integrates the newest stable version of Python as
well. Since newer versions of Python will have new features, you may want to
investigate them. First, find out what version of Python your version of Maya is using.
1. Open up the Script Editor and execute the following lines.
import sys;
print(sys.version);
You should see a result print in the Script Editors History Panel that looks
something like the following lines.
2.6.4 (r264:75706, Nov 3 2009, 11:26:40)
[GCC 4.0.1 (Apple Inc. build 5493)]
Concluding Remarks
In this chapter you have learned how Maya commands and Python work within
Mayas architecture. We have introduced a few methods of entering Maya commands
with Python to modify Maya scenes. We have also explained how to look up help and
read the Python Command Reference documentation and how to find information
about your version of Python. In the chapters that immediately follow, we will further
explain some of the underlying mechanics and syntax of Python and then start
creating more complicated scripts to use in Maya.
MEL can call Python code using the python command. Python can call MEL code
using the eval function in the maya.mel module. Note that using the python
command in MEL executes statements in the namespace of the __main__ module.
For more information on namespaces and modules, see Chapter 4.
Chapter 2
Python Data Basics
Chapter Outline
Variables and Data 30
Variables in MEL 33
Keywords 33
Pythons Data Model 34
Mutability 35
Reference Counting 36
del() 37
The None Type 37
Using Variables with Maya Commands 37
Capturing Results 39
getAttr and setAttr 40
Compound Attributes 40
connectAttr and disconnectAttr 41
Working with Numbers 43
Number Types 43
Basic Operators 44
Working with Booleans 45
Boolean and Bitwise Operators 45
Working with Sequence Types 46
Operators 46
Concatenation 47
Indexing and Slicing 47
String Types 50
Escape Sequences and Multiline Strings 50
Raw Strings 51
Unicode Strings 51
Formatting Strings 52
More on Lists 53
del() 54
Nested Lists 54
Other Container Types 56
Sets 57
Operators 57
Dictionaries 58
Operators 59
Dictionaries in Practice 60
Concluding Remarks 62
By the end of this chapter, you will be able to:
Define and manipulate data in variables.
Compare and contrast Pythons and MELs typing systems.
Recast variables as different data types.
Prevent naming conflicts with built-in keywords.
Explain how Python manages memory.
Compare and contrast mutable and immutable types.
Use variables in conjunction with commands to manipulate attributes.
Describe the different numeric types in Python.
Use mathematical operators with numeric types.
Use logical and bitwise operators with Boolean variables.
Use common operators with sequence types.
Manipulate nested lists.
Create Unicode and raw strings.
Format strings that contain variable values.
This chapter shows how to define and manipulate data in variables, compares
and contrasts Pythons and MELs typing systems, explains how Python manages
memory, and describes the different numeric types in Python. It also discusses how to
create Unicode and raw strings and how to format strings that contain variable values.
Keywords
Unicode, identity, type, value, Python Standard Library, Python Language
Reference, set, dictionary, hashable, mutable, immutable.
I n Chapter 1, we covered executing commands in Maya using Python. To do
anything interesting, however, we need more tools to create programs. In this chapter
we will explore some of the basics for working with variables in Python. Because
much of this information is available in the online Python documentation, we will not
belabor it a great deal. However, because we make use of certain techniques
throughout the text, it is critical that you have an overview here.
We begin by discussing variables in the context of the data types you learned in
Chapter 1, and show how you can use variables along with some basic Maya
commands. Thereafter, we discuss some more interesting properties of the basic
sequence types we discussed in Chapter 1. Finally, we discuss two additional, useful
container types.
2. Once you create this variable, you can substitute in this name anywhere you
would like this value to appear. Execute the following line of code. You should see
the string toys in the box print on the next line.
print(contents+ in the box);
3. You can change the value assigned to this variable at any time, which will
substitute in the new value anywhere the name appears. Execute the following lines of
code, which should print shoes in the box in the History Panel.
contents = shoes;
print(contents+ in the box);
4. You can also assign completely different types of values to the variable. Execute
the following line of code.
contents = 6;
Python is what is called a strong, dynamically typed language. The line of code in
step 4 demonstrates dynamic typing. You are able to change the type of any Python
variable on-the-fly, simply by changing its assignment value. However, because it is
strongly typed, you cannot simply add this new value to a string.
5. Try to execute the following statement.
print(there are +contents+ things in the box);
The console should supply an error message like the following one.
# Error: TypeError: file <maya console> line 1: cannot concatenate
str and int objects #
Because Python is strongly typed, you cannot so easily intermix different types of
variables. However, you can now perform addition with another number.
6. Execute the following line of code, which should print the number 16.
print(contents+10);
In Python, to intermix types, you must explicitly recast variables as the expected
type.
7. Execute the following line in Python to cast the number stored in contents to its
string representation. You should see there are 6 things in the box in the History
Panel.
print(there are +str(contents)+ things in the box);
In this case, we called the str() function to recast contents as
a string. We
discuss functions in greater detail in Chapter 3. Table 2.1 lists some other built-in
functions for recasting variables.
Table 2.1 Python Functions for Recasting Variable Types
Function
Casts To
Notes
float()
Decimal
number
int()
Integer
number
Python provides a built-in function, type(), which allows you to determine the
type of a variable at any point.
8. Execute the following line to confirm the type of the contents variable.
type(contents);
You should see the following line in the History Panel, which indicates that the
variable is currently pointing to an integer.
# Result: <type int> #
As you can see, casting the variable to a string as you did in step 7 did not change
its inherent type, but only converted the value we retrieved from the variable. As
such, you would still be able to add and subtract to and from this variable as a
number. You could also reassign a string representation to it by assigning the recast
value.
9. Execute the following lines to convert the variable to a string.
contents = str(contents);
print(type(contents));
Variables in MEL
Variables in Python are incredibly flexible. Compare the previous example with a
comparable MEL snippet.
1. Enter the following lines in a MEL tab in the Script Editor. You should again see
the output toys in the box in the History Panel.
$contents = "toys";
print($contents+" in the box");
While MEL allowsbut does not requirethat you explicitly provide the
variables type, the variable $contents is statically typed at this point. It is a string,
and so cannot now become a number. Those unfamiliar with MEL should also note
that it requires variable names to be prefixed with a dollar sign ($).
2. Execute the following lines in MEL.
$contents = 6;
print("there are "+$contents+" things in the box");
Because you get the output you expect (there are 6 things in the box), you may
think that MEL has implicitly handled a conversion in the print call. However, the
number 6 stored in $contents is not in fact a number, but is a string. You can confirm
this fact by trying to perform addition with another number.
3. Execute the following line in MEL.
print($contents+10);
Whereas the Python example printed the number 16 in this case, MEL has printed
610! In MEL, because the type of the variable cannot change, MEL implicitly assumed
that the number 10 following the addition operator (+) was to be converted to a string,
and the two were to be concatenated. While seasoned MEL developers should be well
aware of this phenomenon, readers who are new to Maya will benefit from
understanding this difference between MEL and Python, as you may occasionally
need or want to call statements in MEL.
On the other hand, all readers who are as yet unfamiliar with Python would do
well to remember that variable types could change on-the-fly. This feature offers you
a great deal of flexibility, but can also cause problems if youre frequently reusing
vague, unimaginative variable names.
Keywords
Apart from issues that may arise from vague variable names, Python users have some
further restrictions on names available for use. Like any other language, Python has a
set of built-in keywords that have special meanings. You saw one of these keywords
importin Chapter 1. To see a list of reserved keywords, you can execute the
following lines in a Python tab in the Script Editor.
import keyword;
for kw in keyword.kwlist: print(kw);
The list of reserved keywords printed out are used for various purposes,
including defining new types of objects and controlling program flow. We will be
covering a number of these keywords throughout the text, but it is always a good idea
to refer to the Python documentation if you would like more information. The
important point right now is that these words all have special meanings, and you
cannot give any of these names to your variables.
In this case, you are not actually altering the datas underlying type, but are
pointing to some different piece of data altogether. You can confirm this fact by using
the built-in id() function, which provides the address to the data. For instance,
printing the identity at different points in the following short sample will show you
different addresses when the variable is an integer and when it is a string.
var = 5;
print(int id,id(var));
var = str(var);
print(str id,id(var));
Mutability
In this case, both v1 and v2 are pointing to the same piece of data. If these two
variables are assigned different data (e.g., some other number, a string, etc.), then the
reference count for their previous data (the integer 10) drops to zero. When the
reference count for objects drops to zero, Python normally automatically garbage
collects the data to free up memory as needed. Although this concept has only a few
consequences for our current discussion, it becomes more important in later chapters
as we discuss modules, classes, and using the API.
It is important to note that while variables pointing to data with an immutable
type may show this behavior, two separate assignments to mutable objects with the
same type and value are always guaranteed to be different. For example, even though
the following lists contain the same items, they will be guaranteed to have unique
identities.
list1 = [1, 2, 3];
list2 = [1, 2, 3];
print(list1 id, id(list1));
print(list2 id, id(list2));
Because we use it throughout the text in some cases, it is worth noting that, because of
how Pythons variables work, Python also implements a None type.
var = None;
One use of this type is to initialize a name without wastefully allocating memory
if it is unnecessary. For instance, many of our API examples in this text use the None
type to declare names for class objects whose values are initialized elsewhere. We will
talk more about class objects starting in Chapter 5.
Note that this list does not contain the Maya nodes themselves, but simply
contains their names. For example, using the del() function on this list would not
actually delete Maya nodes, but would simply delete a list with two strings in it.
2. We will discuss this syntax later in this chapter, but you can use square bracket
characters to access items in this list. For example, you can store the name of the
polySphere node (polySphere1) in another variable.
sphereShape = sphereNodes[1];
3. Now that you have stored the name of the shape in a variable, you can use the
variable in conjunction with the polySphere command to query and edit values. For
example, the following lines will store the spheres radius in a variable, and then
multiply the spheres radius by 2. Remember that in each flag argument, the first name
is the flag, and the second name is the value for the flag.
rad = maya.cmds.polySphere(
sphereShape, q=True, radius=True
);
maya.cmds.polySphere(sphereShape, e=True, radius=rad*2);
4. You could now reread the radius attribute from the sphere and store it in a
variable to create a cube the same size as your sphere.
rad = maya.cmds.polySphere(
sphereShape, q=True, radius=True
);
maya.cmds.polyCube(
width=rad*2,
height=rad*2,
depth=rad*2
);
Hopefully you see just how easy it is to use variables in conjunction with Maya
commands.
Capturing Results
In practice, it is critical that you always capture the results of your commands in
variables rather than making assumptions about the current state of your nodes
attributes. For example, Maya will sometimes perform validation on your data, such as
automatically renaming nodes to avoid conflicts.
Some Maya users may insist that this behavior makes certain tools impossible
without advanced, object-oriented programming techniques. In reality, you simply
need to take care that you use variables to store your commands results, and do not
simply insert literal valuesespecially object namesinto your code. The following
example illustrates this point.
1. Create a new Maya scene and execute the following lines in the Script Editor.
This code will create a sphere named head.
import maya.cmds;
maya.cmds.polySphere(name=head);
2. Now execute the following line to try to make a cube with the same name.
maya.cmds.polyCube(name=head);
If you actually look at your cube in the scene, you can see that Maya has
automatically renamed it to head1 so it will not conflict with the name you gave the
sphere. However, if you were writing a standalone tool that creates objects with
specific names and then tries to use those specific names, your artists may run into
problems if they already have objects in the scene with conflicting names.
3. Try to execute the following line, and you will get an error.
maya.cmds.polyCube(head, q=True, height=True);
Always remember that Maya commands work with nodes and attributes on the
basis of their names, which are simply strings. When using ordinary Maya commands,
you should always capture the results of your commands since there is no default
mechanism for maintaining a reference to a specific node. In Chapter 5 we will see
how the pymel module provides an alternative solution to this problem.
getAttr
and setAttr
While there are many commands that pair with common node types, not all nodes
have such commands (and sometimes not all attributes have flags). It may also
become tedious to memorize and use commands with modes, flags, and so on.
Fortunately, Maya provides two universal commands for getting and setting attribute
values that will work with any nodes: getAttr and setAttr. Well demonstrate a
quick example.
1. Open a new Maya scene and execute the following lines of code. These lines will
create a new locator and store the name of its transform node in a variable called loc.
import maya.cmds;
loc = maya.cmds.spaceLocator()[0];
2. Execute the following lines to store the locators x-scale in a variable and print the
result, which will be 1 by default.
sx = maya.cmds.getAttr(loc+.scaleX);
print(sx);
The getAttr command allows you to get the
by name. Other commands only work with specific attributes on specific nodes, and
so can work in a more straightforward way, as you will see in the remainder of this
example.
4. In the scene you created in the previous steps, execute the following line to print
the locators translation using the xform command.
print(maya.cmds.xform(loc, q=True, translation=True));
As you would expect the result is simply a list: [0.0, 0.0, 0.0].
5. Likewise, when using the xform command, you can set a new translation value
using a list, as in the following line.
maya.cmds.xform(loc, translation=[0,1,0]);
T h e xform command can work in this way because
it is designed to work
exclusively with transform nodes, and the command internally knows about the data
type for the appropriate attribute. On the other hand, getAttr and setAttr are not so
straightforward.
6. Execute the following line to print the locators translation using the getAttr
command.
print(maya.cmds.getAttr(loc+.translate));
As you can see from the output, the command returns a list that contains a tuple:
[(0.0, 1.0, 0.0)].
7. Using setAttr for a compound attribute also uses a different paradigm. Execute
the following line to set a new translation value for the locator.
maya.cmds.setAttr(loc+.translate, 1, 2, 3);
As you can see, setting a compound attribute like translation using setAttr
requires that you specify each value in order (x, y, z in this case).
connectAttr
and disconnectAttr
The mechanism for transforming data in the Maya scene is to connect attributes: an
output of one node connects to some input on another node. For instance, you saw in
Chapter 1 how the output attribute of a polySphere node is connected to the inMesh
attribute of a shape node when you execute the polySphere command. While Chapter
1 showed how you can delete the construction history of an object to consolidate its
node network, the connectAttr and disconnectAttr commands allow you to reroute
connections in altogether new ways.
The basic requirement for attributes to be connected is that they be of the same
type. For example, you cannot connect a string attribute to a decimal number attribute.
However, Maya does perform some built-in conversions for you automatically, such
as converting angle values into decimal numbers. The finer distinctions between these
types of values will be clearer when we discuss the API later in this book. In the
following short example, you will create a basic connection to control one objects
translation with another objects rotation.
1. Open a new Maya scene and execute the following lines to create a sphere and a
cube and store the names of their transform nodes.
import maya.cmds;
sphere = maya.cmds.polySphere()[0];
cube = maya.cmds.polyCube()[0];
2. Execute the following lines to connect the cubes y-rotation to the spheres ytranslation.
maya.cmds.connectAttr(cube+.ry, sphere+.ty);
maya.cmds.select(cube);
Use the Rotate tool (E) to rotate the cube around its y-axis and observe the
behavior. The result is very dramatic! Because you are mapping a rotation in degrees
to a linear translation, small rotations of the cube result in large translations in the
sphere.
It is also worth mentioning that the connection is only one way. You cannot
translate the sphere to rotate the cube. In Maya, without some very complicated hacks,
connections can only flow in one direction. Note, too, that an output attribute can be
connected to as many inputs as you like, but an input attribute can only have a single
incoming connection.
3. Execute the following line to disconnect the two attributes you just connected.
maya.cmds.disconnectAttr(cube+.ry, sphere+.ty);
Now if you rotate the cube, the sphere translates 1 unit for every 90 degrees of
rotation.
Number Types
Although you can often intermix different types of numbers in Python, there are four
types of numbers: integers, long integers, floating-point numbers, and complex
numbers. We briefly cover them here, but you can read more about these different
types in Section 5.4 of Python Standard Library.
As you saw in the beginning of this chapter, an integer is simply a whole
(nonfractional) number. Integers can be positive or negative. The type of an integer in
Python is given as int, as the following code illustrates.
var = -5;
print(type(var));
A long integer differs from an integer only in that it occupies more space in
memory. In many cases, ordinary integers suffice and are more efficient, but long
integers may be useful for computation that deals with large numbers. In a language
like C or C++, a long integer occupies twice as many bits in memory as an ordinary
integer. In Python, a long integer can occupy as much memory as you require. To
create a long integer, you can simply assign a really long value to your variable and it
will become a long integer automatically, or you can suffix the value with the
character l or L. The type in Python is given as long.
var = -5L;
print(type(var));
Floating-point numbers are any numbers, positive or negative, with digits after a
decimal point. You can explicitly indicate that a whole number value is to be a
floating-point number by adding a decimal point after it. The type of a floating-point
number is float.
var = -5.0;
print(type(var));
Finally, Python allows you to use complex numbers, which consist of both a real
and an imaginary component. It is highly unlikely that you will need to work with
complex numbers regularly. You can create a complex number by suffixing the
imaginary part with the character j or J. The type is given as complex.
var = -5+2j;
print(type(var));
You can also access the individual real and imaginary parts of complex numbers
using the real and imag attributes.
print(var.real, var.imag);
Basic Operators
Once you start creating variables, you will want to do things with them. Operators are
special symbols you can include in your code to perform special functions. (Note that
we also include some related built-in functions and object methods in our tables.)
Section 5 of Python Standard Library includes information on various operators, and
Section 3.4 of Python Language Reference contains information on overloading
operators, which allows you to assign special functionality to these symbols. Here, we
briefly review some of the basic operators for working with numeric values.
Thus far, youve seen some of the basic math operators, such as + for addition
and for subtraction or negation. Section 5.4 in Python Standard Library contains a
table of Pythons built-in operators for working with numeric types. In Table 2.2 we
have recreated the parts of this table containing the most common operators.
Table 2.2 Important Numeric Operators
Operation
Result
Notes
Sum of x and
y
Difference of
xy
x and y
Product of x
x*y
and y
Quotient of x
x/y
If x and y are integers, result is rounded down
and y
Floored
Use with floating-point numbers to return a
x//y
quotient of x decimal result identical to x/y if x and y were
and y
integers
Remainder
x%y
of x/y
divmod(x, Tuple that is
(x//y, x % y)
y)
x to the y
pow(x, y)
power
x to the y
x ** y
power
x+y
Note that many of these operators also allow in-place operations when used in
conjunction with the assignment operator (=). For example, the following lines create
a variable, v1, with a value of 2, and then subtracts 4 from the same variable, resulting
in 2.
v1 = 2;
v1 -= 4;
print(v1);
Note that Python does not support incremental operators such as ++ and .
For those readers who are new to programming, it is important to point out how
division works with integer numbers. Remember that integers are whole numbers.
Consequently, the result is always rounded down. The following floating-point
division results in 0.5.
1.0/2.0;
You can also mix an integer and a floating-point number, in which case both are
treated as floats. The following division results in 0.5.
1.0/2;
It is also worth mentioning, for those unacquainted with the finer points of
computer arithmetic, that floating-point operations often result in minor precision
errors as a consequence of how they are represented internally. It is not uncommon to
see an infinitesimally small number where you might actually expect zero. These
numbers are given in scientific notation, such as the following example.
-1.20552649145e-10
Operation Result
x or y
x and y
not x
x|y
x&y
Notes
x^y
When working with Boolean variables, the bitwise operators for and (&) and or
(|) function just like their Boolean operator equivalents. The exclusive-or operator (^),
however, will only return True if either x or y is True, but will return False if they are
both True or both False.
There are more bitwise operators that we have not shown here only because we
do not use them throughout this text. Programmers who are comfortable with binary
number systems should be aware that bitwise operators also work with int and long
types.3
Operators
As with numbers and Booleans, sequence types have a set of operators that allow you
to work with them conveniently. Section 5.6 of Python Standard Library has a table of
operations usable with sequence types. We have selected the most common of these
operations to display in Table 2.4. Because they are perhaps not as immediately
obvious as math operators, some merit a bit of discussion.
Table 2.4 Important Sequence Operators
Operation
x in s
Result
True if x is in s
ith item in s
s[i:j]
Slice of s from i to j
Notes
Concatenation
The first operator worthy of a little discussion is the concatenation operator (+). As
you saw in the examples at the outset of this chapter, MEL concatenated two strings
that we expected to be numbers. Python allows sequence concatenation in the same
way. Concatenation creates a new sequence composed of all of the elements of the
first sequence, followed by all of the elements in the second sequence. You can
concatenate any sequence types, but only with other sequences of the same type. You
cannot concatenate a list with a tuple, for instance.
The following line produces the string Words make sentences.
Words + + make + + sentences.;
Because lists are mutable, you can also use this operator to set individual values
for a list. The following line would result in the list [0, 2, 3].
[1,2,3][0] = 0;
However, because they are immutable, you cannot use the index operator to
change individual items in a tuple or characters in a string. The following two lines
would fail.
abcde[2] = C;
(1,2,3)[0] = 0;
You can use a series of index operators to access elements from sequences
embedded in sequences. For instance, the following example will extract just the x
character.
(another, example)[1][1];
supply a negative index. Supplying a negative index gives the result relative to the
length of the sequence (which is one index beyond the final element; Figure 2.3). For
example, the following line would print the number 4.
Pythons index operator also offers powerful, concise syntax for generating slices
from sequences. You can think of a slice as a chunk extracted from a sequence. The
most basic syntax for a slice includes two numbers inside the square brackets,
separated by a colon. The first number represents the start index, and the second
number represents the end index of the slice (Figure 2.4). Consider the following
example.
You could print just the word cats using the following slice. Note that the letter
s itself is index 8.
print(var[5:9]);
As you can see, the end index for the slice is the index just beyond the last
element you want to include.
Slicing syntax assumes that you want to start at index 0 if the first number is not
specified. You could print Holy cats with the following slice.
print(var[:9]);
If you omit the second number, or if it is greater than the sequences length, it is
assumed to be equal to the sequences length. You could print just the word
awesome using the following snippet. The start index is specified using a negative
number based on the length of the word awesome at the end of the string.
print(var[-len(awesome):]);
Or you could print the string Python is awesome using the following line. The
start index is specified using the index() method, which returns the index of the first
occurrence in the sequence of the item you specify.
print(var[var.index(Python):]);
Slicing syntax also offers the option of providing a third colon-delimited number
inside the square brackets. This third number represents the step count for a slice, and
defaults to 1 if it is left empty. Consider the following variable.
nums = (1, 2, 3, 4, 5, 6, 7, 8);
The following line would return a tuple containing the first four numbers, (1, 2,
3, 4).
nums[:4:];
Specifying a skip value would allow you to return a tuple containing all of the
odd numbers, (1, 3, 5, 7).
nums[::2];
Specifying a skip value and a start index would return a tuple with all of the even
numbers, (2, 4, 6, 8).
nums[1::2];
You can also specify a negative number to reverse the sequence! For instance,
the following line would return the tuple (8, 7, 6, 5, 4, 3, 2, 1).
nums[::-1];
String Types
As with numbers, there are in fact multiple string types, which Maya commands allow
you to use interchangeably. Recall that a string is a sequence of numbers or characters
inside of quotation marks that is treated as though it were a word. However, Python
also allows you to prefix strings with special characters to create raw and Unicode
strings. Each of these requires that we first introduce you to escape sequences.
Escape Sequences and Multiline Strings
Ordinarily, you must use what are known as escape sequences to include certain
special characters in your strings, such as Unicode characters (\u), a new line (\n), a
tab (\t), or the same type of quotation marks you use to define your string (\' or \").
You create an escape sequence by including a backslash (\) before a certain ordinary
ASCII character to indicate that it is special. See Section 2.4.1 of Python Language
Reference for more information.
Suppose, for instance, you want to create a string that includes quotation marks
in it. Either of the following lines would be equivalent.
var = "I\m tired of these examples.";
var = "\"Im tired of these examples.\"";
Similarly, if you wanted to include a line break, you would use one of the
following three examples. (Note that we omit semicolons at line endings here for
demonstration, since we can assume this sample is short enough to print correctly.)
var = line 1\nline 2
var = "line 1\nline 2"
var = """line 1
line 2"""
If you were to print var, each
result.
line 1
line 2
Note that using a pair of triple quotation marks allows you to span multiple lines,
where your return carriage is contained in the literal value. Recall that we mentioned
in the introduction that Python also lets you use a backslash character at the end of the
line to carry long statements onto a new line. However, this pattern does not translate
into a literal return carriage. Consider the following lines.
var = line 1 \
line 2
Raw Strings
Python supports what are called raw strings. A raw string, though still a string
according to its underlying type, allows you to include backslashes without escaping
them. You can include the r or R character prefix for your value to indicate that the
value is a raw string, and that backslashes should not be presumed to escape the
following character. The following two examples produce the same result.
var = C:\\Users\\Adam\\Desktop;
var = rC:\Users\Adam\Desktop;
Raw strings can be helpful when creating directory paths or regular expressions.
Regular expressions are special patterns that allow you to search string data for
particular sequences. While they are invaluable, they are also complex enough that
they merit entire books on their own, so we do not cover them here. Consult the
companion web site for some tips on using regular expressions in Maya.
Unicode Strings
A Unicode string allows you to handle special characters, irrespective of region
encoding. For example, with ordinary strings, each character is represented internally
with an 8-bit number, allowing for 256 different possibilities. Unfortunately, while
256 characters may suffice for any one language, they are insufficient to represent a
set of characters across multiple languages (e.g., Roman characters, Korean, Japanese,
Chinese, Arabic). You can create a Unicode string by prefixing the character u or U
onto the value. Their type is Unicode.
var = u-5;
print(type(var));
You can include escape sequences to print special characters. A table of their
values is publicly available at ftp.unicode.org. The following line uses the Unicode
escape sequence \u to print a greeting to our German readers: Gr dich!
print(uGr\u00fc\u00df dich!);
Unicode strings benefit from most of the same operators and functions available
to ordinary strings. Section 5.6.1 of Python Standard Library lists a variety of useful
built-in methods for strings. We strongly recommend browsing these built-in
methods, because there are a number of incredibly useful ones that will save you
loads of time when working in Maya. You can also pull up a list of string methods
directly in your interpreter by calling the built-in help() function.
help(str);
You may have also noticed that Maya returns object names as Unicode characters
when you execute commands. For example, if you create a new cube and print the
result, the History Panel shows you a list of Unicode strings. You can verify this by
printing the result of the polyCube command.
import maya.cmds;
print(maya.cmds.polyCube());
Formatting Strings
While MEL users will feel right at home simply concatenating strings, this approach
can quickly become verbose and inefficient. For instance, the following example
would print the string There are 6 sides to a cube, and about 6.283185308 radians in a
circle.
cube = 6;
pi = 3.141592654;
print(
There are +str(cube)+
sides to a cube, and about +
str(2*pi)+ radians in a circle.
);
Apart from the tedium of casting your variables as strings, the value of 2 is
unnecessarily verbose for human-readable output.
Fortunately, strings and Unicode strings in Python allow you to use powerful,
concise formatting operations to increase efficiency, reduce verbosity, and alter the
appearance of output. The basic approach is that you insert a % character in the string
to initiate a formatting sequence, and then follow the strings value with another %
character and a list of the arguments in order. The previous example could be
rewritten in the following way.
cube = 6;
pi = 3.141592654;
print(
There are %s sides to a cube, and about %.2f radians in a circle%(
cube, 2*pi
)
);
Using this formatting process, the result is now There are 6 sides to a cube, and
about 6.28 radians in a circle.
Section 5.6.2 of Python Standard Library provides information on more detailed
options for string formatting, though we only use this basic paradigm throughout the
book. The documentation also contains a list of available string formatting codes. We
have listed some common patterns in Table 2.5.
Table 2.5 Important String Formatting Patterns
Code Meaning
Notes
d
i
e
f
g
s
Signed integer
Signed integer
Scientific
notation
Floating point
Mixed floating
point
String
The Python documentation also lists other flags that can accompany some of
these codes. Though you should refer to Pythons documents for more information, it
is worth describing the precision modifier, as we use it occasionally.
Note that you can modify the precision that a decimal number will use by
following the % character in the string with a period and number, before the
formatting character. For example, the following lines create a variable pi and print it
to five decimal places.
pi = 3.141592654;
print(%.5f%pi);
More on Lists
Because lists are mutable, they have some properties not shared with other sequence
types. For instance, as you have already seen, lists alone allow for individual element
assignment using the index operator. However, lists have a couple of other unique
properties worth briefly discussing.
del()
Just as you can alter items in a list using the index operator, you can delete items in a
list using the del() function. For instance, the following example produces the list [0,
2, 3, 4, 5, 6, 7].
nums = [0,1,2,3,4,5,6,7];
del(nums[1]);
You also delete a slice from a list in the same way. The following example results
in the list [0, 1, 4, 5, 6, 7].
nums = [0,1,2,3,4,5,6,7];
del(nums[2:4]);
Note in both cases that the original list is being modified. A new list is not being
created. You can execute the following lines to confirm that the identity is intact.
nums = [0,1,2,3];
print(id before, id(nums));
del(nums[1:2]);
print(id after, id(nums));
Nested Lists
As you have seen up to this point, immutable objects, such as tuples, do not allow you
to alter their values. Altering the value of a variable pointing to an object with an
immutable type simply references new data altogether. However, as the index
assignment and del() examples illustrated with lists, mutable objects can have their
values modified. The consequence of this principle is that references to mutable types
are retained when their values are mutated.
Consider the following example, which creates three immutable objects
(numbers) and puts them inside a list.
a =
b =
c =
abc
1;
2;
3;
= [a, b, c];
print(abc); # [1, 2, 3]
You see no change in the list because, when you initially create the variables a, b,
and c, they are all referencing immutable objects in memory. When you initialize the
list using these variables, each element in the list is pointing to the same objects in
memory. However, when you reassign the values of a, b, and c, they are referring to
new data, while the elements in the list are still referring to the original data.
However, making changes to mutable objects, such as lists, will result in changes
in other places the same objects are referenced (Figure 2.5). Consider the following
example, which will print a tuple containing two lists: ([1, 3, 5, 7], [0, 2, 4, 6, 8]).
Figure 2.5 Variables pointing to mutable objects refer to the same data as collections containing
the mutable objects.
At this point, there are two lists, each pointing to a different, mutable location in
memory. There is also a tuple, the elements of which are pointing to these same
locations in memory. At this point, it is worth noting the identities of each of these
variables.
print(odds, id(odds));
print(evens, id(evens));
print(numbers, id(numbers));
Now, you could make some adjustments to your lists, such as adding or
removing elements. If you execute the following lines, you can see that the numbers
tuple has new values when you modify the lists: ([1, 3, 5, 7, 9], [2, 4, 6, 8]).
odds.append(9);
del(evens[0]);
print (numbers);
Because tuples are immutable, you may be wondering how exactly you have
changed the values in the tuple. Remember that the tuple contains two elements, each
referencing the same location in memory as the respective lists. Because the lists are
mutable, their values can be altered, and the reference is retained. If you print the
variables identities now, they should be identical to what they were before altering
the lists.
print(odds, id(odds));
print(evens, id(evens));
print(numbers, id(numbers));
Sets
Section 5.7 of Python Standard Library describes sets as an unordered collection of
distinct hashable objects. While sets have many applications, the most common uses
include membership testing and removing duplicates from a sequence. Note that sets
are a mutable type. Python also implements a comparable type, frozenset, which is
immutable.
Creating a set is as easy as using the set() constructor with an iterable type, such
as any of the sequences we have discussed. For instance, you can create a set from a
string or from a list.
set1 = set(adam@adammechtley.com);
set2 = set([1, 2, 3, 3, 5, 6, 7, 7, 7, 8, 9]);
If you were to print set2,
print(set2);
As you can see, the set appears to be a version of the list that has had its duplicate
elements pruned.
Operators
While sets implement many of the same operations as the sequence types we already
discussed (e.g., in and len()), the fact that they are unordered means that they cannot
make use of others (e.g., indexing and slicing). Table 2.6 lists important set operators.
Table 2.6 Important Set Operators
Operation
Result
True if x is in set
x in set
x not in set True if x is not in set
Length of set
len(set)
set | other New set with all elements from set and other
set & other New set with only common elements from set and other
set other New set with elements in set that are absent in other
set ^ other New set with elements in set or other, but not both
While they fundamentally serve different purposes, sets can test membership
more efficiently than ordinary sequences. Consider the following case.
nums = [1, 2, 3, 3, 5, 6, 7, 7, 7, 8, 9];
If you were writing a program that needed to test membership, you could simply
use the in operator with the list.
8 in nums;
However, this operation effectively needs to test each of the items in the list in
order. The fact that there are several duplicates in the list wastes computation time.
Although this example is fairly simple, you can imagine how a very long list or a large
number of membership tests might slow things down. On the other hand, you can put
this list into a set.
numset = set(nums);
While removing duplicates may seem like the source of the gain, the actual gain
relates to the fact that the items in the set are hashable. Thinking back to our
discussion of Pythons data model, a hashable piece of data is one that will have a
consistent identity for its lifetimean immutable object. While you can generate a set
from a list, a list cannot be an item in a set! The advantage to looking up items on the
basis of a hash is that the cost of a lookup is independent of the size of the set.
Including an item in a set maintains an active reference to the data. Even if the
object loses all of its other references, through reassignments or use of del(), the set
maintains an active reference unless the item is specifically removed from the set
Dictionaries
Another useful container type is the dictionary. Programmers coming from other
languages will recognize the dictionary as a hash table. A dictionary is composed of a
set of hashable (immutable) keys, each of which maps to an arbitrary object.
Like a set, because the keys in a dictionary are hashable, each key can only
appear once, and therefore may only have one value associated with it. Moreover, the
dictionary itself is a mutable object. Consequently, a dictionary cannot be a key in
another dictionary, but it may be a value associated with a particular key in another
dictionary.
Creating a dictionary is very simple, and is somewhat similar to lists and tuples.
To create a dictionary, enclose a set of comma-delimited, colon-separated key-value
pairs inside of curly braces ({ and }). For example, the following assignment creates a
dictionary with two keysthe strings radius and heightwhich have values of 2
and 5 associated with them, respectively.
cylinderAttributes = {radius:2, height:5};
Remember that the keys may be any immutable type, and that the values may be
anything. You could create a dictionary that maps numbers to their names (in German,
no less).
numberNames = {1:eins, 2:zwei, 3:drei};
The keys also do not all need to be the same type. The following dictionary will
map numbers to names, as well as names to numbers.
numberNames = {
1:one,2:two,3:three,
one:1,two:2,three:3
};
Operators
Section 5.8 of Python Standard Library includes operators and methods for
dictionaries. We have included some of the most important ones in Table 2.7. While
the first three operations should be pretty clear by this point, two of the remaining
items in our table merit some discussion.
Table 2.7 Important Dictionary Operators
Operation
Result
Notes
True if key is in d
True if key is not in d
Length of dictionary
Value (object) corresponding to key
List of all keys in the dictionary
Value corresponding to key if it exists, Optional
d.setdefault(key)
otherwise None
default value
key in d
key not in d
len(d)
d[key]
d.keys()
Because dictionaries are a table of hashable objects, the items in a dictionary have
no order, much like a set. Consequently, there is no concept of slicing in a dictionary.
However, unlike sets, dictionaries implement the square bracket operator. Instead of
supplying a dictionary with an index, however, you supply it with a hashable object.
For instance, the following snippet would print the name two associated with the
number 2.
numberNames = {1:one, 2:two, 3:three};
print(numberNames[2]);
Because dictionaries are mutable, you can use this operator not only to get an
object but also to set it. The following lines assign Polish names to our number keys.
numberNames[1] = jeden;
numberNames[2] = dwa;
numberNames[3] = trzy;
However, if you try to access a key that does not yet exist, you will get a
KeyError, as in the following line.
print(numberNames[5]);
Thus, querying a key that does not yet exist would throw an exception and stop
execution of your program. Thankfully, dictionaries implement a setdefault()
method, which, if a key exists, will return its value. If a key does not exist, the method
will add the key and assign a value of None to it. Optionally, you can supply a
parameter for this method that, if the key does not exist, will become its default value.
The following line will add a new entry with the key 5 and value pi that will be
printed.
print(numberNames.setdefault(5, upi\u0119\u0107));
Bardzo dobrze!
Dictionaries in Practice
Dictionaries, though perhaps initially confusing for newcomers, are powerful objects
in practice, especially when working with Maya commands. To take one example,
Python developers can use dictionaries to map rotation orders.
In Maya, some commands return or specify rotation order as a string, such as
xyz or zxy. The following example demonstrates how xform is one such
command, and will print xyz when querying a locators rotation order.
import maya.cmds;
loc = maya.cmds.spaceLocator()[0];
print(maya.cmds.xform(loc, q=True, rotateOrder=True));
demonstrates this issue, and will print 0 when using getAttr to get a rotation order.
import maya.cmds;
loc = maya.cmds.spaceLocator()[0];
print(maya.cmds.getAttr(%s.rotateOrder%loc));
Armed with these two dictionaries, you could do something like the following
example to set the rotation order of a cube and a joint to zxy.
import maya.cmds;
cube = maya.cmds.polyCube()[0];
maya.cmds.setAttr(
%s.rotateOrder%cube,
roStrToInt[zxy]
);
rotateOrder = maya.cmds.getAttr(%s.rotateOrder%cube);
joint = maya.cmds.joint(
rotationOrder=roIntToStr[rotateOrder]
);
Concluding Remarks
Any useful programming task requires the use of variables. You have seen in this
chapter how variables in Python differ from those in MEL and C++, and you have
explored much of their underlying mechanics. You are now able to work effortlessly
with many of Pythons principal built-in objects, and also have a strong foundation
for understanding a range of complex topics that we will explore in this text. The stage
is now set to start developing programs with functions, conditional statements, loops,
and more.
C++ programmers should note that although Python variables are references to
data, they cannot simply be used like pointers. To shoehorn Python into the
language of C++, it always passes parameters by value, but the value of a variable
in Python is a reference. The consequence is that reassigning a variable inside of a
function has no effect on its value outside the function. Chapter 9 discusses these
consequences of Pythons data model in Mayas API.
Immutable containers that reference mutable objects can have their values changed
if the value of one of the mutable objects in the container changes. These containers
are still considered immutable, however, because identities to which they refer do
not change. See the Nested Lists section in this chapter for more information.
It is also worth noting that the bitwise inversion operator (~) is not equivalent to
the Boolean not operator when working with Boolean variables. Booleans are not
simply 1-bit values, but are built on top of integers. Consequently, ~False is 1 and
~True is 2.
For a dictionary where all the values are known to be unique, a concise technique
to automatically generate an inverse mapping, which we avoid here for the sake of
clarity and also progress in the text, would be inverseMap = dict((val, key) for
key, val in originalMap.iteritems()).
Chapter 3
Writing Python Programs in Maya
Chapter Outline
Creating Python Functions 64
Anatomy of a Function Definition 64
Function Arguments 66
Default Arguments 68
Keyword Arguments 69
Variable-Length Argument Lists with the * Operator 71
Variable-Length Keyword Argument Lists with the ** Operator 72
Return Values 74
Maya Commands 75
Listing and Selecting Nodes 76
The file Command 78
Adding Attributes 79
Iteration and Branching 80
The for Statement 80
range() 81
Branching 84
if, elif, and else 85
Using Comparison Operations 86
Emulating Switches 89
continue and break 90
List Comprehensions 93
The while Statement 94
Error Trapping 96
try, except, raise, and finally 96
Designing Practical Tools 99
types and looping statements. Next, we demonstrate how to further control execution
using Pythons conditional statements and branching capabilities. We conclude our
exploration of functions by learning techniques for handling exit cases, including
error and exception handling and returns. Finally, we incorporate all the major topics
of this chapter into a set of functions that work with Maya commands to process
textures.
Since the print() line is encapsulated in the function definition, executing this
code in the Script Editor does nothingor at least nothing immediately obvious.
Recall the definition we offered for the term function. The key phrase in our
definition is that a function is accessed by a developer-defined name. When you
execute a function definition, the definition is registered with the Python interpreter,
much like a name for a variable. Python is then aware of the function, the statements
contained within it, and the name by which those statements should be called.
2. Execute the following line in the Script Editor to display the type associated with
the name of the function you just defined.
type(process_all_textures);
As you can see, the name process_all_textures
3. Likewise, you can define another name that points to the same object, just like
you can with variables. Execute the following lines in the Script Editor to bind another
name to your function, and then compare the identities associated with both names.
processAllTextures = process_all_textures;
print(Ref 1: %s%id(process_all_textures));
print(Ref 2: %s%id(processAllTextures));
Your output should show that the identities for both references are the same.
Executing a function is referred to as calling a function. To instruct Python to
call a function, the function name needs to be supplied to the interpreter, followed by
parentheses containing values to pass to the function.
4. Execute the following lines in the Script Editor to call your function using both of
the names you bound to it.
process_all_textures();
processAllTextures();
While simply using the functions name returns a reference to it (as you saw in
steps 2 and 3), entering a set of parentheses after the name instructs Python to execute
its statements. Consequently, you should see the following lines of output.
Process all textures
Process all textures
While functions allow you to execute any number of statements inside their
bodies, including Maya commands, their real value comes from including arguments.
Function Arguments
Recall the template we offered for function definitions in the previous section.
def function_name(optional, input, parameters):
pass;
The names optional, input, and parameters enclosed
At this point, you could now pass an argument to this function to change its
behavior. Whatever argument is passed is bound to the name texture_node when
executing statements inside the functions body.
6. Execute the following lines of code to create a new file node and pass it to the
process_all_textures() function.
import maya.cmds;
texture = maya.cmds.shadingNode(file, asTexture=True);
process_all_textures(texture);
As you can see, the function has printed the name of the new node that you
passed to it, instead of a static statement.
Processed file1
The important point is that Python maps the incoming reference to the internal
name for the argument. Although the node name is assigned to a variable called
texture in the calling context, this variable maps to texture_node once execution
enters the function body. However, the function now requires that an argument be
passed.
7. Try to call the process_all_textures() function without specifying any
arguments.
process_all_textures();
You can see from the output that Python has thrown an error and told you that
you have specified the wrong number of arguments.
#
Error:
TypeError:
file
<maya
console>
process_all_textures() takes exactly 1 argument (0 given) #
line
1:
9. Execute the following lines to call process_all_textures() and pass its two
arguments.
process_all_textures(texture, my_);
As before, arguments defined in this manner are required to be passed with the
function call. Otherwise, Python returns an error indicating the number of arguments
required and the number found. However, Python provides many different
approaches for declaring and passing arguments that help relax these restrictions
somewhat. Arguments can be default arguments, keyword arguments, and variablelength argument lists.
Default Arguments
Default arguments are values defined along with the functions arguments, which are
passed to the function if it is called without an explicit value for the argument in
question.
10. Execute the following lines to assign a default value to the prefix argument in
the process_all_textures() function definition.
def process_all_textures(texture_node, prefix=my_):
print(Processed %s%s%(prefix, texture_node));
By assigning the value my_ to the prefix argument in
the functions
declaration, you are ensuring that the prefix input parameter will always have a value,
even if one is not supplied when the function is called.
11. Execute the following line to call the function again with its changes.
process_all_textures(texture);
This time, the output shows you that the prefix name used the default value
my_ inside the function body, even though you specified no prefix.
Processed my_file1
You may have noticed that it is not required that every argument carry a default
value. As long as the function is called with a value for every argument that has no
default value assigned, and as long as the function body can properly handle the type
of incoming arguments, the function should execute properly.
As an aside, it is worth briefly noting that a default arguments value can also be
None, which allows it to be called with no arguments without raising an error.
def a_function(arg1=None):
pass;
a_function(); # this invocation works
Keyword Arguments
Keyword arguments are a form of input parameter that can be used when calling a
function to specify the variable in the function to which the supplied data are bound.
12. Execute the following lines to redeclare the process_all_textures() function.
def process_all_textures(
texture_node=None, prefix=my_
):
print(Processed %s%s%(prefix, texture_node));
If the function were declared in this manner, any of the following calls would be
syntactically valid, though the first one wouldnt produce an especially useful result.
# no arguments
process_all_textures();
# single positional argument
process_all_textures(texture);
# multiple positional arguments
process_all_textures(texture, grass_);
All of these examples rely on default values and positional arguments to pass
data. Passing a keyword argument, on the other hand, allows you to specify a
parameter in the form keyword=value. Passing an argument by keyword allows the
caller to specify the name to which the argument will be bound in the function body.
13. Recall that when passing positional arguments, each argument is evaluated in the
order it is passed. Execute the following call to process_all_textures().
process_all_textures(grass_, texture);
Because the string intended to be used as prefix
14. Passing each argument by keyword alleviates this problem. Execute the
following line to pass keyword arguments (a syntax that should look familiar).
process_all_textures(
prefix=grass_,
texture_node=texture
);
16. The function can now be called with a variety of different argument patterns.
Execute the following lines to create new file nodes (textures) and then use different
invocations for the function.
tx1 = maya.cmds.shadingNode(file, asTexture=True);
tx2 = maya.cmds.shadingNode(file, asTexture=True);
tx3 = maya.cmds.shadingNode(file, asTexture=True);
tx4 = maya.cmds.shadingNode(file, asTexture=True);
process_all_textures(grass_);
process_all_textures(grass_, tx1);
process_all_textures(grass_, tx2, tx3, tx4);
You should see the following output lines, indicating that each call was
successful.
(grass_, ())
(grass_, (ufile2,))
(grass_, (ufile3, ufile4, ufile5))
Although this particular syntax isnt terribly useful for our current case, the main
issue to be aware of in this example is the use of the asterisk (*) operator in the
function declaration.
def process_all_textures(*args):
that tuple to the function, as opposed to passing each argument individually. Any
name can be used to declare a variable-length argument list in a function declaration
as long as the asterisk (*) operator precedes the name, though convention is to use the
name args.
In this implementation, the function assumes that the data at position 0
correspond to a prefix, and that all further arguments in the slice from 1 onward are
texture names. Consequently, the current implementation requires at least one
positional argument for the function to execute.
Recall that we passed each of the file nodes as individual arguments in step 16.
The asterisk operator can also be used in the function call to pass the arguments
differently. When used in this manner, the asterisk instructs the Python interpreter to
unpack a sequence type and use the result as positional arguments.
17. Execute the following lines to pack the file nodes you created in step 16 into a
list and pass the list to the function.
node_list = [tx1, tx2, tx3, tx4];
process_all_textures(grass_, node_list);
As you can see in the output, the list itself is contained in a tuple.
(grass_, ([ufile2, ufile3, ufile4, ufile5],))
18. Passing node_list using the asterisk operator fixes this problem. Execute the
following line to call your function and have it unpack the list of file node names.
process_all_textures(grass_, *node_list);
You should see output like that in step 16, confirming that the operation was
successful.
(grass_, (ufile2, ufile3, ufile4, ufile5))
Recall from Chapter 2 that the setdefault() function is a special type of function
associated with dictionary objects. This function searches the associated dictionary for
the key specified in the first positional argument, returning a value of None if a
second positional argument is not specified. In this case, the pre variable is initialized
to my_ if the prefix keyword is not set, while the texture variable is initialized to
None if the texture_node keyword is not specified.
20. Execute the following lines to test different syntax patterns for the
process_all_textures() function.
# calling with no keywords does nothing
process_all_textures();
# calling with an unhandled keyword produces no error
process_all_textures(file_name=default.jpg)
# specifying the texture_node key will process file1
process_all_textures(texture_node=texture);
You should see the following output, confirming that each invocation worked as
expected. The first two completions are from executions that did nothing, while the
third prints a new name for texture. Keywords that you do not explicitly handle in
your function are simply ignored.
my_None
my_None
my_file1
21. As with the single asterisk operator, the double asterisk can also be used to
expand a dictionary and pass it to a function as a list of keyword arguments. Execute
the following code to create an argument dictionary to pass to the function.
arg_dict = {
prefix:grass_,
texture_node:tx1
};
process_all_textures(**arg_dict);
Return Values
While passing arguments to functions is a basic requirement for doing useful work,
so, too, is returning data. Mutable objects, such as lists, can simply be passed as
arguments to a function, and any mutations you happen to invoke inside your
function are reflected in the calling context when the function has concluded. For
instance, the following example creates a new empty list, mutates it inside a function,
and then prints the list in the calling context to indicate that its mutations have affected
the object in the calling context.
def mutate_list(list_arg):
list_arg.append(1);
list_arg.append(2);
list_arg.append(3);
a_list = [];
mutate_list(a_list);
print(a_list);
# prints: [1, 2, 3]
On the other hand, immutable objects are not affected in the same way, as their
values cannot be changed. Incrementing a numbers value inside of a function, for
example, has no result on the variable passed to the function in the calling context.
def increment_num(num):
num += 1;
n = 1;
increment_num(n);
print(n);
# prints: 1
);
23. Execute the following lines to rename the file node specified by texture using
the process_all_textures() function and store the new value in the texture variable.
texture = process_all_textures(texture_node=texture);
print(texture);
# prints: my_file1
As you can see, the new version of the function in fact returns the value returned
by the rename command, which is the new name of the object. Return values are an
essential part of designing useful functions, particularly in cases where you are
working with immutable objects. Well keep working on this function throughout this
chapter to introduce some further language features, but it is worth briefly returning to
Maya commands for a moment before proceeding.
Maya Commands
At this point, you have probably noticed that functions resemble the Maya commands
you have seen so far. In fact, Mayas commands are all functions! (You may have
already used the built-in type() function with some commands names and discovered
this fact.)
Recall that we noted in Chapter 1 that MEL syntax requires command arguments
to follow flag arguments, while Python requires the opposite order. At this point, it is
hopefully clear why Python requires its syntax for working with commands: In
Python, command arguments are simply a variable-length argument list, while flag
arguments are variable-length keyword arguments. For instance, in the following
example, we create a sphere and then execute the polySphere command in edit mode,
passing it first the name of the object and then a series of keyword arguments to set
values for the command flags.
import maya.cmds;
sphere = maya.cmds.polySphere();
maya.cmds.polySphere(
sphere[1], edit=True,
radius=5, sh=16, sa=16
);
Remember that we pointed out that commands like polySphere return a list
containing a transform node and another node (a polySphere node in this case).
print(maya.cmds.polySphere());
# prints: [upSphere2, upolySphere2]
In addition to capturing this return value in a list, you can use special Python
syntax to unpack each individual list item in a separate variable, just as you can for
ordinary Python functions that return sequences.
cube_xform, cube_shape = maya.cmds.polyCube();
print(cube_xform); # prints: pCube1
print(cube_shape); # prints: polyCube1
2. The ls command also allows you to pass a string specifying the type of objects
you would like in your list. Execute the following lines to store all of the transform
nodes in the scene in the nodes variable and print the list.
nodes = maya.cmds.ls(type=transform);
print(nodes);
3. You can also pass strings as positional arguments containing an optional wildcard
character (*) to store all objects of which the names match the pattern (as well as any
other additional filters you specify using flag arguments). Execute the following lines
to store and print the list of nodes of which the names begin with persp.
nodes = maya.cmds.ls(persp*);
print(nodes);
You should see a list containing the transform and shape nodes for the
perspective camera.
[upersp, uperspShape]
4. Another handy, basic command is the select command, which allows you to
populate the current global selection list. Like the ls command, the select command
allows you to specify a wildcard character in its arguments. Execute the following
lines to select the transform and shape nodes for the top and side cameras.
maya.cmds.select(side*, top*);
5. You can also use the sl/selection flag with the ls command to list the currently
selected items. Execute the following line to print the current selection.
print(maya.cmds.ls(selection=True));
You should see that you have the transform and shape nodes for the top and
side cameras currently selected.
[uside, usideShape, utop, utopShape]
6. Both the ls and select commands allow you to pass either a list or any number
of arguments. Execute the following line to create a list of items to select, and then
select it and print the selection. You should see the same items in the selection_list
variable in the output.
selection_list = [front, persp, side, top];
maya.cmds.select(selection_list);
print(maya.cmds.ls(sl=True));
7. You can use the select command in conjunction with the ls command to select
all objects of a certain type. Execute the following lines to select all of the shape
nodes in the scene and print the current selection.
maya.cmds.select(maya.cmds.ls(type=shape));
print(maya.cmds.ls(sl=True));
You should see the following output, indicating you have selected all of the
camera shape nodes.
[ufrontShape, uperspShape, usideShape, utopShape]
4. At this point, you can specify a command argument giving the location of a file
you wish to open in conjunction with the open flag. Execute the following line to
reopen the cube.ma file you saved in step 2.
maya.cmds.file(
os.path.join(
os.getenv(HOME),
cube.ma
),
open=True,
force=True
);
Adding Attributes
Although getting, setting, connecting, and disconnecting attributes are all useful tasks,
it is also possible to add custom attributes to objects in Maya. Such attributes are then
compatible with all of the attribute manipulation commands we discussed in Chapter
2.
1. Execute the following lines to open a new scene, create a sphere named Earth,
and add a mass attribute to its transform node.
import maya.cmds;
maya.cmds.file(new=True, f=True);
sphere_xform, sphere_shape = maya.cmds.polySphere(
n=Earth
);
maya.cmds.addAttr(
sphere_xform,
attributeType=float,
shortName=mass,
longName=mass,
defaultValue = 5.9742e24
);
2. You can now get and set this attribute like any other. Execute the following line
to print the mass of Earth.
print(maya.cmds.getAttr(%s.mass%sphere_xform));
Note that some types of attributes do not use the attributeType flag, but instead
the dataType flag. Execute the following lines to add another attribute to Earth to
use
store an alternate name on it.
maya.cmds.addAttr(
sphere_xform,
dataType=string,
shortName=alt,
longName=alternateName
);
3. When setting a string attribute like this one, you must specify a type flag when
invoking the setAttr command. There are some other types bound to the same
requirements, and they are documented in Mayas Python Command Reference.
Execute the following lines to assign a value to the new alternateName attribute.
maya.cmds.setAttr(
%s.alternateName%sphere_xform,
Terra,
type=string
);
4. Note that you can specify and then subsequently access both long and short
names for custom attributes. Execute the following line to print the alternate name for
Earth using the short name for its custom attribute.
print(maya.cmds.getAttr(%s.alt%sphere_xform));
# prints: Terra
In its present form, the function isnt especially remarkableit can rename a
single file node and return the new name. What happens if we fail to pass it a
texture_node argument, or if we want to process multiple textures? Fortunately, you
can use features such as iteration and branching to add sophisticated behavior to
custom functions.
This block of code instructs the Python interpreter to take the following steps:
Iterate over every element in the collection object.
Temporarily bind the element in the current iteration of collection to the name
item.
When creating a for loop, the in operator has a slightly different meaning. It is
the mechanism for binding the current item in the iteration of the collection (on the
right of the operator) to the name for the item in the current step (on the left of the
operator). Consider the following example.
sequence = [1, 2, 3];
for item in sequence:
print(item);
This loop executes the print() function once for each item in the sequence list.
The loop first prints 1, then 2, then 3.
Pythons syntax differs significantly from the MEL for statement, which uses the
following basic form.
for ($lower_bound; halting_condition; $step)
{
perform_task();
}
For example, the following for loop in MEL would print numbers 1, 2, and 3.
for ($i=1; $i<=3; $i++)
{
print($i+"\n");
}
range()
While Pythons basic for statement is sufficient for most (in fact, almost all) cases, the
MEL for syntax can be emulated in Python using the range() function. This simple
and useful function can be used to generate a numeric progression in a list. It allows
you to iterate over a list of numbers without having to define a literal list for iteration.
Consider the following examples.
# following line prints [0, 1, 2, 3, 4]
print(range(5));
# following line prints [2, 3, 4, 5]
print(range(2,6));
# following line prints [4, 6, 8, 10, 12, 14, 16, 18]
print(range(4,20,2));
# following line prints [0, 1, 2]
print(range(len([sphere,cube,plane])));
As you can see, the range() function allows you to pass up to three arguments
(much like the sequence slicing syntax we investigated in Chapter 2). It can take the
following forms.
# following call returns numeric progression
# from 0 to upper_limit-1
range(upper_limit);
# following call returns numeric progression
# from lower_bound to upper_limit-1
range(lower_bound, upper_limit);
# following call returns numeric progression
# from lower_bound to upper_limit-1, using step value
range(lower_bound, upper_limit, step);
Because the range() function returns a list, you can also apply a slice to reverse it
if you like.
# following line prints [4, 3, 2, 1, 0]
print(range(0,5)[::-1]);
The range() function can be used to generate an index for every element in a
given sequence type, and thereby emulate a MEL-style for loop, as in the following
examples.
"""
MEL equivalent:
string $a_list[] = {"spam","eggs","sausage","spam"};
2. Execute the following lines to create a new Maya scene with a list of three file
nodes and print the list to see their names (which will be file1, file2, and file3).
maya.cmds.file(new=True, f=True);
textures = [];
for i in range(3):
textures.append(
maya.cmds.shadingNode(
file,
asTexture=True
)
);
print(textures);
3. Execute the following lines to pass the new list of textures to the
process_all_textures() function and print the result (which should be dirt_file1,
dirt_file2, and dirt_file3).
new_textures = process_all_textures(
texture_nodes=textures,
prefix=dirt_
);
print(new_textures);
As you can see, using a for statement is a powerful and concise way to repeat a
set of operations. Because of Pythons unique language features, including slicing and
the range() function, you have a number of different options available when creating
a for loop.
Branching
While you have made some important improvements to the process_all_textures()
function, it still has some limitations. For example, passing no texture_nodes
argument will simply cause an error.
4. Execute the followings lines to try to execute the process_all_textures() function
without specifying the texture_nodes argument.
process_all_textures(
prefix=mud_,
);
You should see the following error message print to the console.
# Error: TypeError: file <maya console> line 6: NoneType object is
not iterable #
The problem is that our current implementation assumes that the argument
passed with the texture_nodes keyword is a list or tuple containing object names. If
the texture_nodes argument is not specified, then it initializes to a value of None.
5. Execute the following lines to make modifications to the process_all_textures()
function.
def process_all_textures(**kwargs):
pre = kwargs.setdefault(prefix, my_);
textures = kwargs.setdefault(texture_nodes);
new_texture_names = [];
if (isinstance(textures, list) or
isinstance(textures, tuple)):
for texture in textures:
new_texture_names.append(
maya.cmds.rename(
texture,
%s%s%(pre, texture)
)
);
return new_texture_names;
else:
maya.cmds.error(No texture nodes specified);
As you can see, the function now displays a more descriptive error message.
It can be extended to include elif and else clauses, which are evaluated
sequentially. In the following case, the elif and else clauses would be ignored if
condition is True. If condition were False, then another_condition would be
tested. If no elif clauses evaluate True, then execution defaults to the else block.
if condition:
# do something
elif another_condition:
# do something else
# any number of other elif clauses
else:
# do default action
Operation
Returns
x<y
x <= y
x>y
On the other hand, the following statements would each print False.
v1, v2, v3, v4 = [1,2,3,4];
print(v1 >= v2);
print(v3 <= v1);
print(v4 > v4);
print(v2 != v2);
by
the
statement in the if clause will be printed. However, if xform_list were anything other
than a list (e.g., a string, tuple, or set), the condition would immediately be evaluated
to False, irrespective of whether camera_name happened to be in the collection or not,
and the if block would be skipped.
Moreover, the same principle applies when testing multiple statements using the
or operator.
cameras = maya.cmds.ls(type=camera);
shapes = maya.cmds.ls(type=shape);
camera_shape = perspShape;
if camera_shape in shapes or camera_shape in cameras:
print(
If you examine the test we implemented, you can see that it comprises a few
steps. First, we test whether pre is a string or Unicode object. If this test fails, we drop
to the else block, where pre is assigned an empty value. Otherwise, if pre is a string
or Unicode object, we jump into the first if block and proceed to test whether the final
character in pre is an underscore. If it is, then we do nothing, but if it is not, then we
add an underscore.
8. Execute the following lines to create a new file node and then process it using a
prefix without an underscore.
tex = [maya.cmds.shadingNode(file, asTexture=True)];
print(Before: %s%tex);
tex = process_all_textures(
texture_nodes=tex,
prefix=metal
);
print(After: %s%tex);
As you can see from the output, the function now appends an underscore as
needed, which other tools in your pipeline may require as part of your naming
convention.
Before: [ufile1]
After: [umetal_file1]
Note that the not keyword can be used to test if an expression is not true.
Consider the following test.
if not b:
Moreover, the not keyword is most often used to test against what is called an
implicit false. There are certain values in Python that can be evaluated as Booleans
and will return a value of False. The most common of these include empty values,
such as 0, None, [], and ".
# this statement
a_list = list();
if len(a_list)==0:
print(This list is empty);
# can be written as
if not a_list:
print(This list is empty);
Finally, note that the if statement can also be used as part of an expression in
Python 2.5 and later (e.g., Maya 2008 and later). The following example demonstrates
this concept by presenting a pattern for emulating MELs ternary operator using an if
statement.
// MEL
$result = $value_1 == $value_2 ? "equal" : "not equal";
# Python
result = equal if value_1 == value_2 else not equal;
Emulating Switches
Because function names are accessible like any other name in Python, you can
also use a dictionary to implement a switch in some cases. For example, the following
dictionary-based approach is equivalent to the previous example.
func_dict = {
A:exec_funcA,
B:exec_funcB,
};
func_dict.setdefault(func_test, exec_default)();
10. Execute the following lines to supply the process_all_textures() function with a
list of three items, two of which are invalid, and print the result.
new_textures = [
nothing,
persp,
maya.cmds.shadingNode(file, asTexture=True)
];
print(Before: %s%new_textures);
new_textures = process_all_textures(
texture_nodes=new_textures,
prefix=concrete_
);
print(After: %s%new_textures);
As you can see from the results, the loop simply exited when it reached the first
invalid object, causing it to return an empty list.
Before: [nothing, persp, ufile1]
After: []
12. Execute the following lines to supply the process_all_textures() function with a
new list similar to the one you created in step 10, and print the results.
new_textures = [
nothing,
persp,
maya.cmds.shadingNode(file, asTexture=True)
];
print(Before: %s%new_textures);
new_textures = process_all_textures(
texture_nodes=new_textures,
prefix=concrete_
);
print(After: %s%new_textures);
As you can see from the output, the item with no match in the scene was skipped,
as was the perspective cameras transform node. The resulting list contains only a
valid file node.
Before: [nothing, persp, ufile2]
After: [uconcrete_file2]
List Comprehensions
One of Pythons key features is simple, readable syntax. Often when dealing with
loops and conditionals, code can become obfuscated. Consider the following block of
code that walks through a theoretical directory of textures and builds a list of names
by splitting the file name and extension.
textures = [];
for file_name in folder:
if _diff in file_name:
texture_list.append(name.split(.)[0]);
Rewriting these lines as a list comprehension both reduces the amount of code in
the function and in many cases makes the code more readable. You could rewrite the
preceding hypothetical block as a list comprehension as in the following line.
textures = [
name.split('.')[0] for name in folder if '_diff' in name
];
Here are a few other examples of common Python iteration patterns that can be
rewritten as list comprehensions.
# join a folder and a name to create a path
root_path = C:\\path\\;
name_list = [name_1,name_2,name_3,name_4];
# using a for loop
full_paths = [];
for name in name_list:
full_paths.append(root_path+name);
# as a list comprehension
full_paths = [root_path+name for name in name_list];
# filter a list into a new list
texture_list = [rock_diff,
base_diff,
base_spec,
grass_diff,
grass_bump];
# using a for loop
diff_list = [];
for texture in texture_list:
if _diff in texture:
diff_list.append(texture);
# as a list comprehension
diff_list = [
texture for texture in texture_list if _diff in texture
];
As you can see, list comprehensions are a unique feature that allow for clear and
concise construction of new sequences using a combination of the for statement and
conditional statements.
isinstance(pre, unicode)):
if not pre[-1] == _:
pre += _;
else: pre = ;
textures = kwargs.setdefault(texture_nodes);
new_texture_names = [];
if (isinstance(textures, list) or
isinstance(textures, tuple)):
ctr = len(textures) -1;
while ctr > -1:
texture = textures[ctr];
if (maya.cmds.ls(texture) and
maya.cmds.nodeType(texture)==file):
new_texture_names.append(
maya.cmds.rename(
texture,
%s%s%(pre, texture)
)
);
ctr -= 1;
return new_texture_names;
else:
maya.cmds.error(No texture nodes specified);
Just like for statements, while statements are also helpful tools for iteration. In
contrast, however, they make use of conditional statements to determine whether they
should continue. Consequently, although for statements are useful tools for executing
linear, repeatable steps, while statements may be useful in nonlinear situations.
Moreover, while loops are helpful in situations that need to monitor other objects that
may be manipulated outside the while loop, or that have their values altered as a
cascading effect of something that does happen in the while loop.
Error Trapping
When dealing with large programs, you often would like to be able to handle how
errors are processed and reported back to the developer calling your function. These
situations all concern a functions exit path, and Python provides several different
options. Although we have already covered the return statement, we explore here
some basic strategies for handling errors.
line
1:
An
Error
Raising an error in this case could also be achieved with alternative syntax.
raise StandardError, An Error Occurred;
continue;
new_texture_names.append(
maya.cmds.rename(
texture,
%s%s%(pre, texture)
)
);
return new_texture_names;
else:
raise TypeError(
Argument passed was not a tuple or list
);
2. Execute the following line to try to pass a string to the texture_nodes argument
when calling process_all_textures().
process_all_textures(texture_nodes=this is a string)
Python supports a higher-level pattern for handling errors that allows a developer
to try a block of code, handle any errors, and clean up any results. This pattern is
called a try statement, and it consists of the following parts:
A try clause that contains the code to be tested
An optional except clause or clauses that handle any errors by type
An optional else clause following an except clause, which specifies a block of
code that must run if no errors occur in the try block
An optional finally clause that must be executed when leaving the try block to
perform any cleanup required, and that is always executed whether or not there was
an error
A try statement must further include either an except clause or a finally clause.
Either of the following code snippets is valid.
# example 1
try:
# code to be tested
except:
# raise an exception
# example 2
try:
# code to be tested
finally:
# clean up
You can also combine the two, in which case the finally block will execute
whether or not an error occurs in the try block.
try:
# code to be tested
except:
# raise an exception
finally:
# called no matter what, upon completion
You are also allowed to include an optional else clause following an except
clause. Such an else clause specifies code that must run if no error occurred in the try
block.
try:
# code to be tested
except:
# raise an exception
else:
# called if try block is successful
While you have a variety of options, any errors raised by code in the try clause
will only be handled by the except clause.
3. Execute the following lines to wrap an invalid call to process_all_textures() in a
try-except clause.
try:
process_all_textures();
except TypeError as e:
print(e);
You should see the custom TypeError message you defined inside the function
print as a result, which is the specified action in the except block.
Argument passed was not a tuple or list
When an exception is raised from inside a try clause, the Python interpreter
checks the try statement for a corresponding except clause that defines a handler for
the raised error type and executes the block of code contained within the except
was bound
to it using the as keyword. The variable e is not a required keyword; in fact, any legal
variable name can be used as a target. Targets are also optional, though excluding
them prevents you from obtaining any specific information about the exception. Any
of the following statements use valid except syntax.
except TypeError:
except TypeError as my_error:
except TypeError, improper_type:
Hopefully you can see the tremendous value in protecting the execution of your
code by properly handling errors. It is strongly recommended you refer to the Python
documentation for more information on working with errors, as it represents one of
Pythons most helpful features for tracking down problems.
import maya.cmds;
import os;
def process_all_textures(out_dir = os.getenv(HOME)):
"""
A function that gets a list of textures from the current scene
and processes each texture according to name
"""
texture_nodes = []; # to be replaced
processed_textures = [];
error_textures = [];
skipped_textures = [];
if not texture_nodes:
maya.cmds.warning(No textures found, exiting);
return (
processed_textures,
error_textures,
skipped_textures
);
for name in texture_nodes:
if is_valid_texture(name):
print(Processing texture %s%name);
as_type = None;
status = False;
texture = None;
if _diff in name:
status, texture = process_diffuse(
name, out_dir
);
if status:
processed_textures.append(texture);
as_type = diffuse;
else:
error_textures.append(texture);
elif _spec in name:
status, texture = process_spec(
name, out_dir
);
if status:
processed_textures.append(texture);
as_type = specular;
else:
error_textures.append(texture);
elif _bump in name:
status, texture = process_bump(
name, out_dir
);
if status:
processed_textures.append(texture);
as_type = bump;
else:
error_textures.append(texture);
if status:
print(
Processed %s as a %s texture%(
texture, as_type
)
);
else:
print(Failed to process %%name);
else:
print(%s is not a valid texture, skipping.%name);
skipped_textures.append(name);
return (
processed_textures,
error_textures,
skipped_textures
);
As previously noted, this function is simply a skeleton, and in fact does no actual
work. Calling process_all_textures() at this point would return three empty lists, as it
would fail the first if test.
The next step would be to generate a list of texture nodes. To accomplish this
task, we search the scene and create a list by querying the attributes on all the file
texture nodes. This task is accomplished with a simple call of the ls command,
specifying that we are looking for file nodes.
texture_nodes = maya.cmds.ls(type=file);
This list is really the only piece of data required by the preceding function to
return a result, given properly named textures. One of the shortcomings of such an
approach, however, is that it would process every file node in the scene. Recall from
the original specifications that an is_valid_texture() function should be used to verify
a file node for processing. A minimum specification for the is_valid_texture()
function could be:
Check the file node to determine that it is connected to an assigned shader.
Verify that the file has one of the predefined substrings (_diff, _bump, or
_spec).
The following implementation of is_valid_texture() satisfies these conditions.
def is_valid_texture(file_node):
shaders = maya.cmds.listConnections(
%s.outColor%file_node,
destination=True
);
if not shaders: return False;
This function employs a fairly simple pattern that most MEL developers are
probably familiar with:
A file node is passed to the function.
The node is queried for its connections to a shader via the outColor attribute.
If any connected nodes are found, they are further searched for connections to
meshes.
If meshes are found, the function assumes the shader is assigned and queries the
given file nodes fileTextureName attribute.
The texture name is checked for one of the predefined tokens, and if found,
returns True.
All other cases fail and return False.
Now, rather than calling is_valid_texture() as part of the main for loop, it can
instead be called as part of a list comprehension and used to generate the initial list of
file nodes.
texture_nodes = [
i for i in maya.cmds.ls(type=file)
if is_valid_texture(i)
];
else:
error_textures.append(texture);
if status:
print(
Processed %s as a %s texture%(
texture, as_type
)
);
else:
print(Failed to process %s%name);
return (
processed_textures,
error_textures,
skipped_textures
);
Note that the main for loop has been simplified by excluding the main else
clause, since it is no longer neededour texture list is inherently valid since it is
created using the list comprehension.
Now we can move on to the implementation of the actual texture-processing
functions. For the most part, the texture-processing functions follow a similar pattern.
Consequently, we will only discuss one of these functions in detail,
process_diffuse(). To reiterate, this function should:
Take a verified file node and extract the texture.
Process the texture.
On success:
A new shading network is created.
The new shader is assigned to the original object.
A status of True is returned along with the new texture name.
On failure:
A warning is printed.
The original texture name is returned.
Given this design specification, process_diffuse() could be implemented as
follows. Note that this implementation makes use of the maya.mel module, which
allows you to execute statements in MEL to simplify the creation and setup of the new
file texture node, as well as a set of nested calls for retrieving the shaders assigned
object. We will examine the maya.mel module in further detail in coming chapters.
import maya.mel;
def process_diffuse(file_node, out_dir):
"""
);
for mesh in meshes:
maya.cmds.sets(
mesh,
edit=True,
forceElement=shading_group
);
status = True;
except:
texture = file_node;
status = False;
return (status, texture);
With this function in place, the texture framework is almost complete. The final
step is to generate a new scene name and save the new file. To keep things simple,
process_all_textures() will take the new file name and an output directory as
arguments. Below is the listing for the final version of process_all_textures().
def process_all_textures(
out_dir = os.getenv(HOME),
new_file=processed.ma
):
"""
A function that gets a list of textures from the current scene
and processes each texture according to name
"""
texture_nodes = [
i for i in maya.cmds.ls(type=file)
if is_valid_texture(i)
];
processed_textures = [];
error_textures = [];
skipped_textures = [];
if not texture_nodes:
maya.cmds.warning(No textures found, exiting);
return (
processed_textures,
error_textures,
skipped_textures
);
for name in texture_nodes:
print(Processing texture %s%name);
as_type = None;
status = False;
texture = None;
if _diff in name:
status, texture = process_diffuse(
name, out_dir
);
if status:
processed_textures.append(texture);
as_type = diffuse;
else:
error_textures.append(texture);
elif _spec in name:
status, texture = process_spec(
name, out_dir
);
if status:
processed_textures.append(texture);
as_type = specular;
else:
error_textures.append(texture);
elif _bump in name:
status, texture = process_bump(
name, out_dir
);
if status:
processed_textures.append(texture);
as_type = bump;
else:
error_textures.append(texture);
if status:
print(
Processed %s as a %s texture%(
texture, as_type
)
);
else:
print(Failed to process %s%name);
try:
maya.cmds.file(
rename=os.path.join(
out_dir, new_file
)
);
maya.cmds.file(save=True);
except:
print(Error saving file, %s not saved.%new_file);
finally:
return (
processed_textures,
error_textures,
skipped_textures
);
Concluding Remarks
You now have a firm grasp of many of the fundamentals of programming with
Python, including the ins and outs of functions, loops, branching, and error handling.
While we covered a number of examples in this chapter, you are still only scratching
the surface of Python in Maya. The functions we implemented barely make use of any
advanced Python Standard Library functionality or language-specific features.
This chapter has introduced enough basic concepts that you should be able to
work through all of the examples in further chapters in this book. Nevertheless, we
encourage any developers interested in furthering their knowledge of Python and its
capabilities to browse the online documentation, or to consult any of the other
excellent Python-specific books available. Because you are now capable of creating
Python programs, the next step is to organize them into modules, which we examine
in Chapter 4.
Note that Python itself provides other tools for manipulation of arbitrary files. See
Chapter 7 for an example.
Chapter 4
Modules
Chapter Outline
What Is a Module? 113
Modules and Scope 114
Module Encapsulation and Attributes 115
The __main__ Module 116
Creating a Module 118
The spike Module 118
Default Attributes and help() 120
Packages 121
create.py 122
math.py 123
__init__.py 124
Importing Modules 125
import versus reload() 125
The as Keyword 126
The from Keyword 126
Attribute Naming Conventions and the from Keyword 127
Python Path 127
sys.path 128
Temporarily Adding a Path 129
userSetup Scripts 131
userSetup.py 131
userSetup.mel 131
sitecustomize Module 133
sitecustomize.py 134
Setting Up a PYTHONPATH Environment Variable 135
Maya-Specific Environment Variables with Maya.env 136
This chapter describes what a module is and how to create your own module. It
also describes the default attributes of modules and how to create your own packages
and organize your tools. The chapter also discusses keywords related to modules and
how they relate to Pythons data model. The chapter concludes with a range of
techniques for configuring your Python search path and distributing tools to your
team, as well as information about using a Python IDE for module development.
Keywords
modules, bytecode, keyword, packages, default attributes, environment variable,
IDE, sitecustomize, import, reload, symbol table, scope, userSetup
Thus far, we have used Mayas Script Editor to do all our work. The Script
Editor is perfectly fine for testing ideas, but once your script is functional, you will
want to save it as a module for later use. Although you have been using modules all
along, this chapter will discuss everything you need to start creating modules.
We begin by discussing what exactly a module is and how modules manage and
protect data in a Python environment. We then discuss how to create modules as well
as how to organize modules into packages. Next, we focus on exactly what happens
when a module is imported and examine different options for importing modules. We
then dive into a multitude of ways to configure your environment and deploy the
Python tools to your studio. Finally, we examine a few different options you have for
doing advanced, rapid Python development.
What Is a Module?
A module is a standalone Python file containing a sequence of definitions and
executable statements. Modules can contain as few or as many lines of code as are
required, and often focus on specific tasks. Modules can also import other modules,
allowing you to organize your code by functionality and reuse functionality across
tools. While modules allow you to easily reuse code in this way, they are also
designed to protect your code.
Modules are simply text documents with the .py extension. When you import a
module for the first time, a corresponding .pyc file will be generated on-the-fly, which
is a compiled module. A compiled module consists of what is called bytecode, rather
than source code. While source code is human readable, bytecode has been converted
into a compact format that is more efficient for the Python interpreter to execute. As
such, you can deploy your modules as either .py or .pyc files. Many modules that ship
with Mayas Python interpreter exist only as bytecode to protect their contents from
accidental manipulation.
Python itself ships with a large library of default modules. There are all kinds of
modules for various tasks, including doing math, working with emails, and interfacing
with your operating system. Although we use several of these standard modules
throughout this book, you will want to check out the online Python documentation at
some point to view a full list of built-in modules and descriptions of their functions.
Often, a module already exists that solves some problem you may be encountering!
In addition to these built-in modules, there are many useful modules available in
the Python community at large. For example, the numpy package contains useful
modules for scientific computing and linear algebra, while packages such as pymel
offer powerful tools specifically designed to extend Mayas built-in cmds and API
modules.
In the remainder of this chapter we will focus on creating custom modules.
Before you create your first modules, however, it is worth reviewing the role of scope
in modules.
2. Python has two built-in functions, globals() and locals(), that allow you to see
what objects exist in the current global and local scope, respectively. Execute the
following line in the Script Editor to see the current global symbol table.
print(globals());
If you just started a new Maya session, you should see a fairly short dictionary
like that shown in the following output. If you have been executing other Python
scripts in the Script Editor though, your output may be significantly longer.
{__builtins__: <module __builtin__ (built-in)>, __package__:
None, __name__: __main__, __doc__: None, character: Bob}
In the dictionary, you can see the character variable you just defined, as well as
its current value. However, a variable with such a name is quite generic. It is likely
that you or another programmer will create another variable with the same name. If
your program were to rely on this variable, then you may have a problem.
3. Assign a new value to the character variable and print the global symbol table
again.
character = 1;
print(globals());
As you can see in the output, Bob is no more, and the character variable now
has a number value.
{__builtins__: <module __builtin__ (built-in)>, __package__:
None, __name__: __main__, __doc__: None, character: 1}
If every script we created were storing variables in this scope, it would be very
difficult to keep track of data. For example, if you were to execute two scripts one
after the other, and if they contain variables with the same names, you may have
conflicts. This problem can easily arise when working in MEL, necessitating that you
devise exotic naming conventions for variables. When using Python modules,
however, you can avoid many such conflicts.
In contrast to the global symbol table, the local symbol table, accessible with the
locals() function, lists the names of all symbols defined in the current scope, whatever
that may be. If you simply execute locals() in the same way you have globals(), the
difference may not be obvious, as the symbol tables are identical at the top level.
However, the local scope may be inside a function.
4. Execute the following lines in the Script Editor.
def localExample():
foo = 1;
print(locals());
localExample();
As you can see, the local symbol table in the function contains only the variable
that has been defined in its scope.
{foo: 1}
It is recommended that you consult Section 9.2 of the Python Tutorial online for
more information on scope and how names are located.
The math module contains several such variables. Variables and functions that
are part of a module are called attributes. (Unfortunately, Maya also uses the term
attributes to describe properties of nodes, but you can hopefully follow.) To access
an attribute in the math modulepi in this casewe supplied the name that was
assigned to the imported module, a period, and then the name of the attribute. Follow
the same pattern when accessing functions or other data in a module. As you have
seen in previous chapters, the maya.cmds module contains a variety of functions for
executing Maya commands.
In this case, the pi attribute is said to exist in the namespace of the math module.
This concept ensures that any reference to the pi attribute must be qualified by
specifying its namespace, guarding against unintentional changes to its value. For
example, you can create a new variable pi and it will not interfere with the one in the
math namespace.
pi = apple;
print(our pi: %s%pi);
print(math pi: %.5f%math.pi);
The output clearly shows that the variables are referencing different data.
our pi: apple
math pi: 3.14159
In this example, we have a pi variable defined in the global symbol table, and the
math module continues to have the correct value for its pi attribute in its global
symbol table.
Since Python encapsulates the data in a module, we can safely import a module
into another module. However, it is important to note that module attributes are not
private in the sense used in other languages. It is possible to modify attribute values
externally. Consider the following example.
pitemp = math.pi;
math.pi = custard;
print(override: %s%math.pi);
math.pi = pitemp;
print(reassigned: %.5f%math.pi);
As you can see from the output, it is possible to override the value of the pi
attribute in the math module.
override: custard
reassigned: 3.14159
This aspect of Python is actually incredibly useful in some cases, as it allows you
to override any attributes, including functions! For instance, you can override string
output when printing API objects to the console, a trick of which the value will be
clearer as you start working with the API. However, with great power comes great
responsibility. In this case, we effectively checked out the existing value to another
variable and checked it back in when we were done. It is often important to exercise
good citizenship in this way, as other modules may rely on default values and
behavior.
As you can see, the current module that you are in has the name of __main__.
The Maya Script Editor is the root module and all other modules branch out from it.
This concept is illustrated in Figure 4.1. In Maya, our scripts cannot be the __main__
module, as Maya is acting as the __main__ module.
2. Highlight the code you just entered in the Script Editor and MMB drag the line to
the Shelf. If you are using a version of Maya that asks, make sure you specify the
code is written in Python.
3. Press the new custom button and you should see the new assignment output
appear in the Script Editors History Panel.
4. Execute the following line to print the global symbol table for __main__.
print(globals());
character
Scope is an essential concept not only of modules but also of Python more
generally. Well examine a few more aspects of scope throughout this text, but it is
always a good idea to consult the Python documentation for more information. At this
point, weve covered enough basic information for you to start creating your own
modules.
Creating a Module
Creating your own module is simple, and only requires that you create your script in
any text editor and save it with the .py extension. You can also save a script that you
have entered in Mayas Script Editor directly from its menu. We will start by using the
Script Editor to create our first module: spike. This module contains a function to add
spikes to a polygon object, and will create a spiky ball model as shown in Figure 4.2.
1. Download the spike.py module from the companion web site or copy the
preceding text into the Script Editor. If you have downloaded the script, then open it
in Maya by selecting File Load Script from the Script Editor window. The
contents of the module should now appear in the Input Panel. Do not execute them.
2. Select File Save Script from the Script Editor window. Name the script
spike.py and save it to a location in your Python path based on your operating system:
Windows: C:\Documents and Settings\user\My Documents\maya\
<version>\scripts\
OS X: ~/Library/Preferences/Autodesk/maya/<version>/scripts/
Linux: ~/maya/<version>/scripts/
3. In an empty Python tab, import the spike module and you should see some
output in your History Panel.
import spike;
When you first imported the spike module, its contents were executed. If you
look at the modules source, you can see that it defines a function for adding spikes to
a polygon model, then prints some information about the module and executes the
addSpikes() function, passing in a basic polygon ball. We included the printout
information to provide a better picture of what is going on in the module.
In addition to printing the name of the module, we printed its global symbol table
using the globals() function. As you can see, this modules global symbol table differs
from that of the __main__ module, which we investigated earlier. These names are all
those existing in the modules namespace. As such, its data are separate from those
defined in the __main__ modules namespace, which we investigated earlier. This
example clearly demonstrates the encapsulation principle.
The output under the NAME heading shows the name of the module, which is
identical to the value stored in its __name__ attribute. The name of the module is
followed by the comment at the beginning of the module, which specifies what the
module is for. Similarly, the FILE section shows the path to the modules source file
by using the value in the __file__ attribute.
The FUNCTIONS section shows a list of all of the functions in the file, as well as
their docstrings if they have them. Remember that function docstrings are specified
just like module docstrings by using a literal string comment right at the beginning of
the definition.
The final section, DATA, shows the noninternal attributes contained in the
module. This section lists any module attributes that are not prefixed with
underscores. In this example, we see the final value that was in the k attribute in our
iterator to print the spike modules global symbol table.
Conveniently, you can also use the help() function with individual functions,
which is useful with large modules like math. Consider the following example.
import math;
help(math.atan);
Packages
In addition to ordinary, individual modules, Python allows you to organize your
modules into special hierarchical structures called packages. You can think of a
package as essentially a folder that is treated as a module.
Packages allow you to cleanly deploy suites of tools, rather than requiring that
end users fill up one folder with a bunch of modules, or that you create
extraordinarily long modules. Packages provide both organization and technical
advantages over ordinary modules. First, you are able to bypass naming conflicts by
nesting modules inside of packages (or even packages inside of packages), reducing
the likelihood of butting heads with other tools. Second, modules contained within a
package do not need to have their disk locations explicitly added to your Python
search path. Because the package folder itself is treated as a module, you can access
modules in the package without having to append their specific locations to the search
path. We talk more about the Python search path in the next section.
Packages are easy to create. The basic requirement is that you add a file,
__init__.py, into a folder that is to be treated as a package (or a subpackage). Thats
it! After that point, you can then import the folder as though it were a module, or
import any modules in the folder by using the dot delimiter. Well walk through a
brief example to demonstrate a couple of the features of packages.
1. Return to the location where you saved the spike.py module in the previous
example and create a new folder called primitives. As an alternative to steps 13, you
can also download the primitives package from the companion web site and unzip it
here.
2. Create two files in this new folder called create.py and math.py and copy the
contents from the examples shown here. The create module wraps some default Maya
commands for creating polygon primitives, returning the shape nodes from the
creation calls. The math module contains two simple utility functions for accessing
attributes on primitives that have radius and height attributes.
create.py
import maya.cmds;
def cone(**kwargs):
try: return maya.cmds.polyCone(**kwargs)[1];
except: raise;
def cube(**kwargs):
try: return maya.cmds.polyCube(**kwargs)[1];
except: raise;
def cylinder(**kwargs):
try: return maya.cmds.polyCylinder(**kwargs)[1];
except: raise;
def plane(**kwargs):
try: return maya.cmds.polyPlane(**kwargs)[1];
except: raise;
def sphere(**kwargs):
try: return maya.cmds.polySphere(**kwargs)[1];
except: raise;
def torus(**kwargs):
try: return maya.cmds.polyTorus(**kwargs)[1];
except: raise;
math.py
import maya.cmds;
pi = 3.1415926535897931;
def getCircumference(obj):
try: return maya.cmds.getAttr(%s.radius%obj) * 2.0 * pi;
except: raise;
def getHeight(obj):
try: return maya.cmds.getAttr(%s.height%obj);
except: raise;
3. Add another file in this new folder called __init__.py. This file turns the folder
into a package. Copy the contents from our example here into this file. This file will
simply import two modules in the package. You should now have a directory
structure like that shown in Figure 4.3.
__init__.py
import create, math;
4. In Maya, you can now import the primitives package and use it to create objects
and access information about the objects you create. Execute the following lines.
import primitives;
cyl = primitives.create.cylinder(r=0.25);
print(
Circumference is %.3f%
primitives.math.getCircumference(cyl)
);
The first interesting point about this example is, as you noticed, we created a
module called math. Because this module is nested in a package, its name can conflict
with the built-in math module without our having to worry that we will affect other
tools.
That being said, however, the second interesting point is that our custom math
module does conflict in any of the namespaces in the primitives package. For
instance, we had to add our own attribute pi to the math module, and could not import
th e pi attribute associated with the built-in math module. The same rule goes for
importing the math module from __init__.py.
This point leads nicely to the third, which concerns how we imported the
packages modules in the __init__.py file. We only need to supply the module names
as they exist in the package, and do not need to reference them by nesting them, as in
the following line.
import primitives.create;
Importing Modules
Now that you know how to create modules, we can talk a little more about what
actually happens during the importation process. As you have seen up to this point,
the basic mechanism for loading a module is the import keyword. As you start
working on your own modules, however, it is important to have a clearer
understanding of exactly what happens when you import a module. In this section we
briefly discuss the mechanics of the import keyword, as well as some special syntax
that allows you finer control over your modules namespaces.
The as Keyword
Sometimes a module is embedded deep within a package, or it has an inconveniently
long (albeit descriptive) name. In addition to the package we created earlier, you have
already seen one example of a module embedded within a package many times:
maya.cmds. Unfortunately, prefixing every command with maya.cmds can quickly
become tedious. The as keyword allows you to assign a custom name to the module
when it is added to the global symbol table during importation. As you can see in the
following code, this pattern can help you substantially reduce the amount of typing
(and reading) you have to do, while still retaining the benefits of encapsulation.
import maya.cmds as cmds;
cmds.polySphere();
If the module in which you are working does not have any conflicts in its
namespace, you may find it helpful to just import these attributes directly into the
global symbol table to access them without a prefix. Python allows you to accomplish
this task using the from keyword. This pattern allows you to specify individual,
comma-delimited attributes to import.
from math import degrees, acos;
print(degrees(acos(0.5)));
Python also allows you to import all attributes from a module into the current
namespace using the asterisk character (*).
from math import *;
print(degrees(asin(0.5)));
attribute of the __main__ module with the name of another module you were
importing!
Python Path
In MEL, you are able to call the source directive and pass a path to a script located
anywhere on your computer to load it, even if it is not in your MEL script path.
Importing in Python has no analogous option. When you attempt to import a module,
Python searches all of the directories that have been specified in your Python path. In
Python, all modules must be located in one of these directories, or inside of a package
in one of these directories, and you have a variety of options available for appending
directories to your Python path. In this section we will look more closely at different
ways you can manipulate your Python search path.
sys.path
One handy mechanism for working with your Python path is the path attribute in the
sys module. This attribute contains a list of all directories that Python will search, and
you can interactively work with it like any other list, including appending to it.
import sys;
for p in sys.path: print(p);
If you import the sys module and print the elements in path, you may see
something like the following example (in Windows).
C:\Program Files\Autodesk\Maya2012\bin
C:\Program Files\Autodesk\Maya2012\bin\python26.zip
C:\Program Files\Autodesk\Maya2012\Python\DLLs
C:\Program Files\Autodesk\Maya2012\Python\lib
C:\Program Files\Autodesk\Maya2012\Python\lib\plat-win
C:\Program Files\Autodesk\Maya2012\Python\lib\lib-tk
C:\Program Files\Autodesk\Maya2012\bin
C:\Program Files\Autodesk\Maya2012\Python
C:\Program Files\Autodesk\Maya2012\Python\lib\site-packages
C:\Program Files\Autodesk\Maya2012\bin\python26.zip\lib-tk
C:/Users/Adam/Documents/maya/2012/prefs/scripts
C:/Users/Adam/Documents/maya/2012/scripts
C:/Users/Adam/Documents/maya/scripts
The first several of these directories are built-in paths for Mayas Python
interpreter, and they are all parallel to Mayas Python install location. Depending on
your platform, these are:
Windows: C:\Program Files\Autodesk\Maya<version>\
OS X: /Applications/Autodesk/maya<version>/Maya.app/Frameworks//lib/
Linux: /usr/autodesk/maya/lib/
If you run a separate Python interpreter on your machine, you would see similar
directories if you were to print this same example. These directories are all made
available as soon as your Python session has begun. The final directories in the list are
specific to Maya. Depending on your platform, they will be something like the
following:
Windows: C:\Users\<username>\Documents\maya\
OS X: /Users/<username>/Library/Preferences/Autodesk/maya/
Linux: /home/<username>/maya/
These final directories are appended to the Python path when Maya is initialized.2
It is important to know how the Python path is altered during the launch process in
some cases. For example, if you launch Mayas interpreter, mayapy, on its own to use
Maya without a GUI, this second group of paths is only accessible after you have
imported the maya.standalone module and called its initialize() function. This order of
operations is also important if you want to deploy a sitecustomize module, which we
will discuss shortly. For now, we turn our attention to the most basic way to add
directories to your search path.
1. As we mentioned earlier, because the path attribute is simply a list, you can easily
append to it just as you can any other list. You can take advantage of this functionality
to temporarily add a directory to your Python path during a Maya session. Well show
a quick example to add a module to your desktop and then add your desktop to your
Python search path to import the module. Download the lightplane.py module from
the companion web site and save it to your desktop. The module creates a red light
and a plane in your scene.
2. Now append your desktop to the sys.path attribute by executing the following
lines in the Script Editor.
import os, sys;
home = os.getenv(HOME);
user = os.getenv(USER);
sys.path.append(
os.path.join(
home[:home.find(user)],
user, Desktop
)
);
In this case, we were able to take advantage of a useful built-in module, os, to
help build the path. The first two calls return the value of environment variables with
the specified keys, the first providing a path to the users home folder and the second
returning the name of the user.
The os module has a path extension module with a join() function, which
concatenates multiple strings using whatever directory delimiter your operating system
uses (forward slash on OS X and Linux, backslash on Windows). 3 Unfortunately, in
this example we also have to do some slicing on the home directory, as it points to My
Documents in Windows rather than to the users folder. If you like, you can print the
entries in the sys.path attribute to see the result for your platform at the end of the
output.
3. Now that you have added your desktop to your path, you can import the
lightplane module, after which you should see something like Figure 4.4 if you enable
lighting (press 5 on the keyboard to switch to shaded view, and then 7 on the
keyboard to enable lighting).
If you simply append a location to the sys.path attribute, it will only be in effect
until Maya closes. While this may be handy if you need to add a location from within
some other module, or if you need to test something that you havent yet buried away
in your Python path, you will often want some directories to be consistently added to
your Python path.
While you have a variety of options for automatically configuring your Python
search path across all your sessions, we cover three of the most common options here:
creating a userSetup script, creating a sitecustomize module, and configuring an
environment variable. In some cases, you may find that a combination of these
approaches meets your particular needs based on how your studio deploys tools.
userSetup Scripts
A useful feature of Maya is the ability to create a userSetup script. A userSetup script
simply needs to be in a proper location on your computer, and Maya will execute any
instructions in it when it starts. Depending on your operating system, you can add
such a script to the specified default directory:
Windows: C:\Documents and Settings\user\My Documents\maya\
<version>\scripts\
OS X: ~/Library/Preferences/Autodesk/maya/<version>/scripts/
Linux: ~/maya/<version>/scripts/
Note that you may only have one userSetup script and it must be either a
Python or a MEL scriptyou cannot have both a userSetup.mel and a userSetup.py
script. A MEL script will take precedence if both exist.
In the following brief example, you will alter your userSetup script to
automatically append your desktop to the Python search path and import the
lightplane module that you created in the previous example (which should still be on
your desktop).
1. If you do not have a userSetup script, create either a userSetup.mel or a
userSetup.py script in the appropriate directory for your operating system. Copy the
contents of the appropriate following example into your script.
userSetup.py
import os, sys;
home = os.getenv(HOME);
user = os.getenv(USER);
sys.path.append(
os.path.join(
home[:home.find(user)],
user, Desktop
)
);
import lightplane;
userSetup.mel
user,
the names.
As you can see, the only real change we made was to wrap the importation of the
lightplane module in a call to the executeDeferred() function in the maya.utils
sitecustomize Module
A built-in feature in Python is support for a sitecustomize module. A sitecustomize
module is always executed when your Python session begins. Similar to a userSetup
script, this module allows you to import modules, append to sys.path, and execute
statements. However, unlike a userSetup script, a sitecustomize module encapsulates
its attributes like any other module, and so any modules you import in it are not
automatically in the global symbol table for __main__. We will see this issue in the
next example.
The principle advantage of using a sitecustomize module is that it offers you an
opportunity to set up the Python path without interfering with a userSetup script.
Consequently, you could deploy a studiowide sitecustomize module to configure
users paths to point to network directories and so on. At the same time, your artists
would still be able to set up their own individual userSetup scripts, as either MEL or
Python depending on their needs and comfort levels. Artists would only need to
import one package for your tools, and would be free to load other tools they may
have downloaded from the Internet. You can offer them the flexibility to configure
their environments individually, while also reducing the likelihood that they will
inadvertently affect their search paths.
Recall earlier when we printed the directories in sys.path that there were
essentially two groups of folders: those belonging to Mayas Python interpreter, and
those that are added during Mayas initialization process. Your sitecustomize module
is imported before Mayas directories are added to the search path . Consequently,
convention dictates that you should put your sitecustomize module in your sitepackages directory, which is in your Python path by default. (In practice, you are free
to include it in any directory that is part of your search path prior to Mayas
initialization.) In this brief example, you will simply recreate the path manipulation
functionality of the userSetup script that you created in the previous example.
1. Navigate back to the folder where you put your userSetup script, and remove the
lines from it that you added in the previous example, or delete it altogether.
sitecustomize.py
import os, sys;
home = os.getenv(HOME);
user = os.getenv(USER);
sys.path.append(
os.path.join(
home[:home.find(user)],
user, Desktop
)
);
2. Create a new module called sitecustomize.py and copy the contents of our
example printed here into it. Save it in your site-packages directory in Mayas
installation location. Note that to reach this location in OS X, you must press CmdShift-G from a Save dialog to type in the path (you can also right-click on Maya.app
and select Show Contents to drag a file into this location in Finder). Windows users
may have to supply administrative privileges to write to this folder. The location of
this folder varies based on your operating system:
Windows: C:\Program Files\Autodesk\Maya<version>\Python\lib\sitepackages
OS X:
Applications/Autodesk/maya<version>/Maya.app/Contents/Frameworks/Python.fra
packages
Linux: /usr/autodesk/maya/lib/python<version>/site-packages
3. Close and reopen Maya to start a new session.
4. If you print the contents of your sys.path attribute, you will see that your desktop
has been added to the search path immediately after the site-packages folder.
import sys;
for p in sys.path: print(p);
At this point, you could import the lightplane module if you like. However, there
are a couple of important points worth discussing here. First, notice that the path to
your desktop immediately follows the site-packages folder, indicating that the
adjustments made in sitecustomize.py become immediately availablebefore Maya
has initialized. Consequently, you cannot issue Maya commands from a sitecustomize
module. Although you would be able to import the cmds module without errors (since
it is accessible from the site-packages directory), it is effectively a dummy at this point
in the initialization process, containing only placeholders for Maya commands. The
maya.utils module suffers from the same limitation in this case.
Second, unlike the userSetup script, sitecustomize operates as an ordinary
module, and therefore encapsulates its data. Consequently, if you were to print the
global symbol table for the __main__ module at this point, you would not see any of
the modules you imported or other variables you defined from within
sitecustomize.py, such as os or the home or user variables.
While using a sitecustomize module is handy, you may find it inconvenient to
write to a folder in Mayas install location, especially if you need to update the module
occasionally. Another tool at your disposal for configuring your Python path, which
can work in conjunction with a sitecustomize module and/or a userSetup script, is to
set up an environment variable.
If you already have a value set for PYTHONPATH, or if you want to add more
directories, simply add your operating systems delimiter between them (Windows
uses a semicolon, Linux and OS X use a colon). For example, a Windows user could
enter the following.
PYTHONPATH = C:\Users\Adam\Desktop;C:\mypythonfiles
As you can see, your desktop is now part of the Python search patheven
before many of the default directories.
C:\Program Files\Autodesk\Maya2012\bin
C:\Users\Adam\Desktop
C:\Program Files\Autodesk\Maya2012\bin\python26.zip
C:\Program Files\Autodesk\Maya2012\Python\DLLs
C:\Program Files\Autodesk\Maya2012\Python\lib
C:\Program Files\Autodesk\Maya2012\Python\lib\plat-win
C:\Program Files\Autodesk\Maya2012\Python\lib\lib-tk
C:\Program Files\Autodesk\Maya2012\bin
C:\Program Files\Autodesk\Maya2012\Python
C:\Program Files\Autodesk\Maya2012\Python\lib\site-packages
C:\Program Files\Autodesk\Maya2012\bin\python26.zip\lib-tk
C:/Users/Adam/Documents/maya/2012/prefs/scripts
C:/Users/Adam/Documents/maya/2012/scripts
C:/Users/Adam/Documents/maya/scripts
At this point, you could again import the lightplane module without having to
alter the sys.path attribute.
Modifying the Maya.env file is one of the easiest and most reliable ways to
configure your Python search path with minimal effects on other users. In a majority
of cases, using some combination of the techniques we have discussed thus far is
perfectly sufficient. However, you can also create a systemwide PYTHONPATH
variable.
Systemwide Environment Variables and Advanced Setups
Your operating system allows you to define many custom environment variables that
are shared across all applications. Because setting a systemwide environment variable
will override your settings in Maya.env, and because it will apply to all applications
that make use of it, it can be dangerous to use one, but also quite powerful if you
know what you are doing. Systemwide environment variables simply offer you one
more option for deploying common code across a range of applications.
For example, in addition to your Maya tools, you may have some shared libraries
(e.g., numpy, PyQt4, and some Python utilities) that you use across a range of
applications (e.g., Maya, MotionBuilder, and so on). To avoid deploying these
modules to individual users, you may put them on a network drive, along with your
tools for each specific application.
One option for solving this problem is to modify application-specific
environment variables for each interpreter to point to individual sitecustomize
modules in network directories (one directory/sitecustomize for each version of each
application). In this scenario, each application has its own PYTHONPATH variable
pointing to a network directory where you can easily deploy and update a
sitecustomize module that appends new directories to the path (Figure 4.5). Each
sitecustomize module could append further network directories relevant to the
applications Python version and so on.
Figure 4.5 One way to deploy systemwide modules with separate sitecustomize modules.
However, you may find it troublesome to add new software to your pipeline, or
to upgrade existing software. For example, if you upgrade to a new version of Maya
with a new version of Python, you would need to make sure you were pointing to a
new version of PyQt and so on.
You could also set up a sitecustomize module on a network folder that is
specified via a systemwide environment variable (Figure 4.6). Using a setup with
systemwide environment variables would also enable you to set values for
MAYA_SCRIPT_PATH, which is used to locate MEL scripts. Such a configuration
would allow you to take advantage of the search paths construction order to use
global userSetup scripts for your artists. Using a global userSetup script, you could
automatically import your tools and then search for an individual users userSetup
script to execute it as well, which would allow your artists complete freedom over
their own userSetup scripts without you having to worry that they import your tools.
Figure 4.6 One way to deploy systemwide modules with a systemwide environment variable.
In this setup, your global sitecustomize module could use attributes in the sys
module to find out what application is importing it (sys.executable) and what version
of Python it is running (sys.version). It could then use this information to append
directories to the search path as needed. In this setup, any time you add a new piece of
software to your pipeline, it can access your shared libraries right away. Keeping the
sitecustomize module in one place where you can write to it makes it easy to update
your whole teams toolset immediately. Just dont take that kind of power lightly! (If
you are feeling really creativeor cavalieryou can also import __builtin__ in your
sitecustomize module and add attributes to it for modules you know you will
consistently want available right away, such as math, sys, os, or re.)
You can find more information about systemwide environment variables on the
Internet, but we offer a few quick pointers here to get you started if you are interested.
Windows
Downloading an IDE
While you have many options available for coding in Python, we will only discuss
two popular options here: Wing IDE and Eclipse. We only cover basics to save space,
but you can consult the companion web site for links and more information on how
to set up these tools. Our main goal here is simply to help you find both of the tools
and briefly examine some of their advantages and disadvantages. Knowing the
strengths and weaknesses of each tool can help you find which is most comfortable
for you, or even use them effectively in concert with one another!
Wing IDE
Wing IDE is a commercial product developed by Wingware, and is available at
http://wingware.com/. As a commercial product, Wingware benefits from being a
fairly straightforward tool to install, configure, and use. Moreover, Wingware offers a
personal version, as well as a student version of Wing IDE, each of which is available
at a lower price point than the professional version, yet which omit some features.
Consequently, many users often find that Wing IDE is the easiest entry point when
they begin working.
Wingware offers a handy online guide to setting up Wing IDE for use with Maya,
which includes information on configuring its debugger, code completion, and
sending commands to Maya (Wing uses its own interpreter by default).
Eclipse
Eclipse
is
an
open-source
product,
and
is
available
at
http://www.eclipse.org/downloads/. Like many open-source products, Eclipse may
seem daunting at first to those who are generally only familiar with commercial
software. For starters, going to the download page offers a range of options, as the
community has customized many configurations for common uses. (Note that Eclipse
originated primarily as a Java development platform.) In our case, we are interested in
Eclipse Classic. Once you have installed Eclipse, you need to download and install the
Pydev plug-in, which can be done directly from the Eclipse GUI.
While Eclipse can perhaps be more complicated to configure, it does have some
benefits. The most obvious benefit is of course that it is free. Moreover, Eclipse
allows you to fully configure a variety of interpreters and select which interpreter each
project is using. Furthermore, because the Eclipse community has developed a range
of free plug-ins, you can easily download and install additional tools from within the
application.
Most advanced Python development environments will support some mechanism for
autocompletion of code. The basic idea is that the IDE is aware of the contents of
modules you import, and so it can begin to offer completion suggestions while you
type. As you can imagine, this feature can be a huge time saver and can reduce typos.
While most IDEs can offer interactive completion by analyzing the contents of
the module in which you are working, as well as other modules in your project, this
mode of autocompletion may be sluggish. As such, IDEs such as Eclipse and Wing
IDE allow you to use what are called stubs to generate autocompletion results for
modules. In short, the stubs are special, concise files with either a .py or .pi extension,
which contain dummies for some actual module, allowing them to be parsed for
autocompletion more quickly. Fortunately, there are handy tools for automatically
generating these stubs from Mayas modules. You can find more information about
this process on the companion web site.
Projects
The key mode of organization in a Python IDE is to create projects. Projects are
typically small description files that contain information about a collection of modules
(e.g., their location on disk), the interpreter to be used, version control settings, and so
on. Consequently, project files do not usually store any of your actual data, but rather
allow you to easily locate and organize your files. Many IDEs also offer special tools
for performing projectwide searches, replacements, and so on.
Connecting to Maya
Both Wing IDE and Eclipse offer functionality for advanced users to connect the IDE
directly to Maya. The basic process is that you can call the commandPort command in
your userSetup script to automatically listen for input from a port on which the IDE
will send commands to Maya. Using this feature enables you to execute a whole
module (or individual lines of code that you select) and send them to Mayas
__main__ module, just like using the Script Editor. You can refer to the companion
web site for more information on tools for connecting to Maya.
Debugging
The final important feature we want to note is that many IDEs, such as both Wing IDE
and Eclipse, offer tools for debugging modules. Using an integrated debugger allows
you to set break points in your module before you execute it, so you can interactively
monitor the values of different variables during execution. You can refer to the
companion web site for links to information on the debuggers for Wing IDE and
Eclipse.
Concluding Remarks
You now have a solid grasp of modules and packages, as well as how to optimize
your development and deployment practices. Although we introduced some of
Pythons built-in modules in this chapter, such as math, os, and sys, there are many
more that you will find useful when working in Maya. We encourage you to consult
Pythons online documentation and familiarize yourself with some of the most
common modules, as they have often already solved problems you may otherwise toil
over! Likewise, there are many useful modules in the Python community at large that
you can use in Maya.
Although the power of modules should be clear, particularly compared to
traditional MEL scripts, they are still only the tip of the iceberg for what you can
accomplish using Python in Maya. Now that you are better acquainted with concepts
such as scope and encapsulation, you are ready to start working with one of Pythons
greatest advantages over MEL: the ability to do object-oriented programming.
It is possible to use the eval MEL command in conjunction with the source
directive to trigger recompilation of a MEL script. Consult the MEL Command
Reference document for more information.
Note that directories in your sys.path attribute are supplied as absolute paths, and
do not include the tilde (~) character for OS X and Linux as a shortcut for the
users home directory.
Note that newer versions of Windows use both delimiters fairly interchangeably.
Python can handle them both just fine, but its always safest to use the os.path
module to build paths.
If you want to know more, you can investigate the modules that are part of Mayas
installation.
The
userSetup
script
is
executed
in
sitepackages/maya/app/startup/basic.py.
Chapter 5
Object-Oriented Programming in Maya
Chapter Outline
Object-Oriented versus Procedural Programming 148
Basics of Class Implementation in Python 150
Instantiation 150
Attributes 151
Data Attributes 153
Methods 155
__str__() 155
__repr__() 158
Properties 159
Class Attributes 160
Static Methods and Class Methods 161
Human Class 163
Inheritance 164
Procedural versus Object-Oriented Programming in Maya 168
Installing PyMEL 168
Introduction to PyMEL 168
PyNodes 169
PyMEL Features 170
Advantages and Disadvantages 171
A PyMEL Example 172
Concluding Remarks 175
By the end of this chapter, you will be able to:
Compare and contrast object-oriented and procedural programming.
This chapter takes a high-level look at the two programming paradigms available
when using Python with Maya: object-oriented programming and procedural
programming. The heart of object-oriented programmingobjectsis also discussed
and a custom Python class is created to explore many of the basic components of
classes, including data attributes, class attributes, and methods. This chapter also
compares the maya.cmds module with an alternative, object-oriented suite, pymel.
Keywords
object-oriented programming (OOP), procedural programming, class, immutable
object, derived class, pymel, cmds, pyMEL
As you have seen up to this point, Pythons basic language features can work in
conjunction with Maya commands to build a variety of useful functions and tools.
Nevertheless, because this approach to Python programming differs little from MEL,
the advantages of Python may not yet be immediately clear.
I n Chapter 4, we introduced the concept of modules, and examined the many
ways in which they are more powerful than ordinary MEL scripts. Part of the reason
modules are so useful is because they allow you to encapsulate functions and
variables, called attributes, into namespaces. Although we did not explicitly point out
the relationship, modules are objects, just like any other piece of data in Python
(including integers, strings, lists, dictionaries, and even functions themselves). In fact,
Python is a fully object-oriented language, and most of its built-in types have
attributes and functions that you can access much like those in modules.
In this chapter, we take a high-level look at the two programming paradigms
available when using Python with Maya: object-oriented programming and procedural
programming. After discussing these concepts, we will thoroughly examine the heart
of object-oriented programming: objects. To better understand objects, we will create
a custom Python class and use it to explore many of the basic components of classes,
including data attributes, class attributes, and methods. We then introduce the concept
of inheritance by creating a new class derived from our first one. Thereafter, we turn
back to Maya to compare and contrast the maya.cmds module with an alternative,
object-oriented suite: pymel. We conclude with some basic examples to introduce the
pymel module and illustrate its unique features.
Here you can see that the uppercasing function upper() is associated with the
variable your_name. While we pointed out in Chapter 2 that variables in Python are
objects with a value, type, and identity, we didnt really explore this principle further.
An object can be thought of as an encapsulation of both data and the functionality
required to act on them.
So if a procedural program is defined as a collection of variables and functions
that operate on a data set, an object-oriented program can be defined as a collection of
classes that contain both data and functionality. The term object refers to a unique
2. If you use the dir() function on this class, you can see that it has some attributes
by default, which should be familiar.
dir(NewClass);
# Result: [__doc__, __module__] #
Although this basic syntax is the minimum requirement for defining a class, it is
referred to as an old-style or classic class. Up until Python 2.2, this syntax was the
only one available. Python 2.2 introduced an alternate class declaration, which allows
you to specify a parent from which the class should derive. It is preferable to derive a
class from the built-in object type.
3. Execute the following lines to redefine the NewClass class.
class NewClass(object):
pass;
4. If you use the dir() function now, you can see that the class inherits a number of
other attributes.
dir(NewClass);
#
Result:
[__class__,
__delattr__,
__dict__,
__doc__,
__format__, __getattribute__, __hash__, __init__, __module__,
__new__, __reduce__, __reduce_ex__, __repr__, __setattr__,
__sizeof__, __str__, __subclasshook__, __weakref__] #
Although it is not required to define a basic class by deriving from the object
type, it is strongly recommended. We will talk more about the importance of
inheritance later in this chapter.
Instantiation
Once you have defined a class, you can create an instance of that class by calling the
class (using function notation) and binding its return value to a variable.
5. Execute the following lines to create three NewClass instances.
instance1 = NewClass();
instance2 = NewClass();
instance3 = NewClass();
These creation calls each return a valid, unique NewClass instance. Each instance
is a separate immutable object. Recall that immutability means only that the value of
the underlying data cannot change. As such, each instance is guaranteed to have a
consistent identity over the course of its existence as well (and hence could be used as
a key in a dictionary). However, using OOP, you can effectively mutate instances by
using attributes.
Attributes
Like modules, classes have attributes. However, attributes in classes are much more
nuanced. An attribute may be a piece of data or a function, and it may belong to the
class or to an instance of the class. The basic paradigm for creating an attribute is to
define it in the class definition, as in the following example.
class NewClass():
data_attribute = None;
def function_attribute(*args): pass;
At this point, you could pass NewClass to the dir() function and see its attributes
(note that we do not inherit from the object type, simply to have a shorter list of
attributes for this example).
print(dir(NewClass));
"""
prints:
[__doc__, __module__, data_attribute, function_attribute]
"""
However, Python also allows you to add attributes to a class on-the-fly, after you
have defined the class. You could now execute the following lines to add another data
attribute to the class and see the results.
NewClass.another_data_attribute = None;
print(dir(NewClass));
"""
prints:
[__doc__,
__module__,
data_attribute, function_attribute]
"""
another_data_attribute,
You can also add attributes to individual instances after creating them, which will
not affect other instances of the class. The following example illustrates this process.
instance1 = NewClass();
instance1.instance_attribute = None;
instance2 = NewClass();
# only instance1 has instance_attribute
print(Instance 1:, dir(instance1));
print(Instance 2:, dir(instance2));
Remember that attributes can be functions. Consequently, you can assign the
name of any function to an attribute. A function that is an attribute of a class is
referred to as a method.
def foo(*args): print(foo);
NewClass.another_function_attribute = foo;
instance1.another_function_attribute();
instance2.another_function_attribute();
immediately, adding attributes to the class itself makes the attributes immediately
available to all current and further instances of the same class.
Finally, it is worth noting that reexecuting a class definition will not affect
existing instances of the class. Because of how Pythons data model works, old
instances are still associated with an object type somewhere else in memory.
class NewClass():
just_one_attribute = None;
new_instance = NewClass();
print(New:, dir(new_instance));
"""
prints: (New:, [__doc__, __module__, just_one_attribute])
"""
print(Old:, dir(instance1));
"""
prints: (Old:, [__doc__, __module__, another_data_attribute,
another_function_attribute,
data_attribute,
function_attribute,
instance_attribute])
"""
Data Attributes
While you can add data attributes in a number of ways, a common mechanism for
defining them is to do so in the __init__() method, which is called when an instance is
first created. Because Python automatically searches for this function as soon as an
instance is created, attributes that are created in this way are instance attributes, and
require that you access them from an instance.
1. Execute the following lines to define a Human class with an __init__() method,
which initializes some basic data attributes based on input arguments.
class Human(object):
def __init__(self, *args, **kwargs):
self.first_name = kwargs.setdefault(first);
self.last_name = kwargs.setdefault(last);
self.height = kwargs.setdefault(height);
self.weight = kwargs.setdefault(weight);
2. Now you can pass arguments to the class when you create an instance, and their
values will be bound accordingly. Execute the following code to create three new
Human instances, passing each one different keyword arguments.
me = Human(first=Seth, last=Gibson);
mom = Human(first=Jean, last=Gibson);
dad = Human(first=David, last=Gibson);
3. Execute the following lines to confirm that each instances data attributes have
been properly initialized.
print(me: , me.first_name);
print(mom:, mom.first_name);
print(dad:, dad.first_name);
You should see at the end of the output list that an instance allows you to access
attributes that do not exist as part of the class definition itself. Remember that you can
define attributes in the class definition as well. While data attributes defined both ways
may have unique values for any particular instance, those that exist in the class
definition can be accessed without an instance of the class, as in the following
hypothetical example.
import sys;
class NewClass():
# exists in class definition
data_attribute1 = 1;
def __init__(self):
# added to instance upon instantiation
self.data_attribute2 = 2;
print(NewClass.data_attribute1);
try:
print(NewClass.data_attribute2);
except AttributeError:
print(sys.exc_info()[0]);
instance = NewClass();
print(instance.data_attribute1);
print(instance.data_attribute2);
While you are free to modify data on your instances using this paradigm, another,
more useful technique is to use methods.
Methods
A method is a function existing in a class. Ordinarily its first argument is always the
instance itself (for which we conventionally use the self name). Methods are also a
type of attribute. For example, while len() is simply a built-in function that you can
use with strings, upper() is a method that is accessible on string objects themselves.
You can add further methods to your class definition by remembering that the first
argument will be the instance of the class.
6. Execute the following lines to add a bmi() method to the Human class, which
returns the instances body mass index using metric computation.
def bmi(self):
return self.weight / float(self.height)**2;
Human.bmi = bmi;
Assuming we are working in the metric system, you could now create an instance
and call the bmi() method on it to retrieve the body mass index if height and weight
are defined.
7. Execute the following lines to create a new Human instance and print its BMI.
The result should be 22.79.
adam_mechtley = Human(
first=Adam,
last=Mechtley,
height=1.85,
weight=78
);
print(%.2f%adam_mechtley.bmi());
As we indicated earlier in this chapter, methods are one of the primary tools that
differentiate OOP from procedural programming. They allow developers to retrieve
transformed data or alter data on an instance in sophisticated ways. Apart from the
syntactic requirement that the first argument in a method definition be the instance
owning the method, you are free to use any other argument syntax as you like,
including positional arguments, keyword arguments, and so on.
__str__()
It is also possible to override inherited methods to achieve different functionality. For
example, printing the current instance isnt especially helpful.
8. Execute the following line.
print(adam_mechtley);
The results simply show you the namespace in which the class is defined, the
name of the class, and a memory address. Sample output may look something like the
following line.
<__main__.Human object at 0x130108dd0>
In Python, when printing an object or passing it to the str() function, the objects
__str__() method is invoked. You can override this inherited method to display more
useful information.
9. Execute the following lines to override the __str__() method to print the first and
last name of the Human.
def human_str(self):
return %s %s%(
self.first_name,
self.last_name
);
Human.__str__ = human_str;
10. Print one of the instances you previously created and you should see its first and
last name.
print(mom);
# prints: Jean Gibson
As you can see from the result, the type is given as wrapper_descriptor. The
basic idea is that when __str__() is invoked, it is not calling a particular
implementation on the instance itself, but the implementation defined in the class. For
example, reassigning this method on an instance object has no effect.
12. Execute the following lines to try to reassign __str__() on the adam_mechtley
On the other hand, overriding ordinary instancemethods will only affect the
particular instance that gets the new assignment.
13. Execute the following line to print the type of the bmi() method on
adam_mechtley. You should see the result instancemethod.
print(type(adam_mechtley.bmi));
14. Execute the following lines to create a new Human instance and define a new
function that returns a witty string.
matt_mechtley = Human(
first=Matt,
last=Mechtley,
height=1.73,
weight=78
);
msg = Studies have shown that BMI is not indicative of health.;
def wellActually(): return msg;
15. Execute the following lines to store the bmi() method on adam_mechtley in a
variable, reassign the bmi() name on the adam_mechtley instance, and then print the
results of bmi() for both instance objects.
old_bmi = adam_mechtley.bmi;
adam_mechtley.bmi = wellActually;
print(str(matt_mechtley), matt_mechtley.bmi());
print(str(adam_mechtley), adam_mechtley.bmi());
You can see from the results that the matt_mechtley instance
properly returns a
instance returns the value of the wellActually()
method body.
17. Execute the following line to reassign the original instancemethod to the
adam_mechtley instance. As you can see from the output, the result is about 22.8, as it
should be.
adam_mechtley.bmi = old_bmi;
print(adam_mechtley.bmi());
Unfortunately, the results that you see closely resemble the output you saw in
step 8, before you overrode the __str__() method. Some parts of Python do not
invoke the __str__() method for output, but instead use the __repr__() method.
Convention dictates that this method should return a string that would allow the object
to be reconstructed if passed to the eval() function, for example.
19. Execute the following lines to add a __repr__() method to the Human class.
def human_rep(self):
return """Human(%s=%s, %s=%s, %s=%s, %s=%s)"""%(
first, self.first_name,
last, self.last_name,
height, self.height,
weight, self.weight
);
Human.__repr__ = human_rep;
20. Execute the following line to try to print the instances you created in step 18
again.
print(sis, baby_sis);
You should see something like the following output, indicating that the
__repr__() method has been invoked.
(Human(first=Ruth,
last=Gibson,
height=None,
weight=None),
Human(first=Emily, last=Gibson, height=None, weight=None))
There are many more built-in methods you can override. Consult Section 3.4 of
Python Language Reference for a full listing.
Properties
Properties are special types of methods that can be accessed as though they were
ordinary data attributes, and hence do not require function syntax (e.g.,
print(instance.property_name)). When you add a property in a class definition
itself, you can add the appropriate decorator to the line prior to the methods
definition. A decorator is a special, recognized word, prefixed with the @ symbol.
# for a read-only property
@property
def property_name(self):
# return something here
Note that Python 2.6 and later (Maya 2010 and later) allows you to also use a
decorator to specify that a property can be set by simply passing a value to it like a
data attribute (e.g., instance.property_name = value).
# make the property settable
@property_name.setter
def property_name(self, value):
# set some values here
Another option, compatible with all versions of Maya, is to use the property()
function in conjunction with new style class syntax, which lets you specify a getter
and a setter (as well as some further optional parameters). At minimum a property
must have a getter, but other aspects are optional.
def getter(self):
# return something here
22. Add a full_name property to the Human class as shown in the following lines.
def fn_getter(self):
return %s %s%(self.first_name, self.last_name);
def fn_setter(self, val):
self.first_name, self.last_name = val.split();
Human.full_name = property(fn_getter, fn_setter);
Note the special assignment syntax we use in the fn_setter() method to assign
multiple variables at the same time, based on items in the sequence returned from the
split() method.
23. Execute the following lines to create a new Human and use its property to get
and set the first and last names using the full_name property.
big_sis = Human(first=Amanda, last=Gordon);
# notice that property is not invoked with parentheses
print(big_sis.full_name);
# prints: Amanda Gordon
# set a new value
big_sis.full_name = Amanda Gibson;
print(big_sis.full_name);
# prints: Amanda Gibson
Class Attributes
In addition to instance attributes, Python contains support for class attributes. A class
attribute can be accessed by using the class name, and does not require that you
create an instance. Class attributes can be useful for adding data or methods to a class
that may be of interest to other functions or classes when you may not have a need to
create an instance of the class. You can access them by using the dot operator with the
name of the class.
As we pointed out earlier, class data attributes behave exactly like instance
attributes on instances: changing the value on one instance has no effect on other
instances. However, because they can be accessed without an instance, you may use
them in cases where you have no instance (e.g., when you are creating a new
instance).
24. Execute the following lines to add two basic unit-conversion attributes to the
Human class.
Human.kPoundsToKg = 0.4536;
Human.kFeetToMeters = 0.3048;
25. Execute the following lines to reconstruct a new Human instance using these
class attributes to help construct it from American units (feet and pounds,
respectively).
imperial_height = 6.083;
imperial_weight = 172;
adam_mechtley = Human(
first=Adam,
last=Mechtley,
height=imperial_height*Human.kFeetToMeters,
weight=imperial_weight*Human.kPoundsToKg
);
print(Height:, adam_mechtley.height);
print(Weight:, adam_mechtley.weight);
Python also includes support for some special types of methods known as static
methods and class methods. Static methods and class methods allow you to call a
method associated with the class without requiring an instance of the class. You can
add these types of methods to your class definition using decorators on the line before
a definition. They take the following basic forms.
class ClassName(object):
@staticmethod
some_static_method():
# insert awesome code here
pass;
@classmethod
some_class_method(cls):
# insert awesome code here
pass;
At this point, you could call either of these functions using the following identical
forms.
ClassName.some_static_method();
ClassName.some_class_method();
Note that the only difference between these two options is that a class method
implicitly passes the class itself as the first argument, which allows you shorthand
access to any class attributes (by allowing you to access them using
cls.attribute_name rather than ClassName.attribute_name inside the method). It is
also worth pointing out that, because these types of methods are called using the name
of the class rather than on an instance, they do not pass an instance (self) as an
argument. Otherwise, you are free to add whatever other arguments to them you like.
26. Execute the following lines to add a static method to the Human class that
returns the taller person.
@staticmethod
def get_taller_person(human1, human2):
if (human1.height > human2.height):
return human1;
else: return human2;
Human.get_taller_person = get_taller_person;
Class methods can be used to implement alternate creation methods for a class or
in other situations where access to class data is required separate from an instance.
28. Execute the following lines to add a class method that creates a Human with
some specified default parameters.
@classmethod
def create(cls):
return cls(
first=Adam,
last=Mechtley,
height=6.083*cls.kFeetToMeters,
weight=172*cls.kPoundsToKg
);
Human.create_adam = create;
dopplegaenger = Human.create_adam();
print(dopplegaenger);
As with ordinary class attributes, the value of such methods is in those situations
when you want to expose some functionality without an instance. While you can
exploit a class method to create alternate constructors, they are also handy in cases
when you want to access class data attributes.
Human Class
For your benefit, we have printed the entire code for the Human class example
below, as it would appear if you were to define it all at once.
class Human(object):
kPoundsToKg = 0.4536;
kFeetToMeters = 0.3048;
def __init__(self, *args, **kwargs):
self.first_name = kwargs.setdefault(first);
self.last_name = kwargs.setdefault(last);
self.height = kwargs.setdefault(height);
self.weight = kwargs.setdefault(weight);
def bmi(self):
return self.weight / float(self.height)**2;
@staticmethod
def get_taller_person(human1, human2):
if (human1.height > human2.height):
return human1;
else: return human2;
@classmethod
def create_adam(cls):
return cls(
first=Adam,
last=Mechtley,
height=6.083*cls.kFeetToMeters,
weight=172*cls.kPoundsToKg
);
# Begin properties
def fn_getter(self):
return %s %s%(self.first_name, self.last_name)
def fn_setter(self, val):
self.first_name, self.last_name = val.split()
full_name = property(fn_getter, fn_setter)
# End properties
# Alternate property defs for Maya 2010+
"""
@property
def full_name(self):
return %s %s%(self.first_name, self.last_name);
@full_name.setter
def full_name(self, val):
self.first_name, self.last_name = val.split();
"""
def __str__(self):
return self.full_name;
def __repr__(self):
Inheritance
Another critical concept in OOP is inheritance. Although we made some use of it in
the previous example, we did not explore it in detail. At the high level, inheritance
allows a new class to be derived from a parent class, allowing it to automatically
inherit all of its parents attributes. As we demonstrated briefly in the previous section,
this concept also allows you to override functionality as needed in a descendant class.
For example, suppose you have implemented a class like Human and decide that
you need a similar class, but only with marginal changes. One option might be to just
add the additional required functionality to the original class, but the new functionality
may be substantially different, or may be something that not all instances require.
With inheritance, rather than having to copy or reimplement features from the
original class, you can simply tell Python to create a new class based on an existing
class that preserves all the functionality of the original, while allowing new
functionality to be introduced.
1. Ensure that you have the Human class defined in your current namespace. See
the end of the previous section for details.
2. Execute the following code to implement a class called MyKid that inherits from
the Human class presented in the previous section.
class MyKid(Human):
def __init__(self, *args, **kwargs):
Human.__init__(self, *args, **kwargs);
self.mom = kwargs.setdefault(mom);
self.dad = kwargs.setdefault(dad);
self.siblings = [];
if kwargs.setdefault(sibs) is not None:
self.siblings.extend(
kwargs.setdefault(sibs)
);
def get_parents(self):
return %s, %s%(
self.mom.full_name,
self.dad.full_name
);
def set_parents(self, value):
self.mom, self.dad = value;
parents = property(get_parents, set_parents);
There are two main points related to class derivation that merit discussion. The
first point concerns the class declaration statement.
class MyKid(Human):
Rather than using the built-in object class, MyKid uses Human as the parent or
base class. This syntax is the first step for ensuring that the child/derived class MyKid
retains the functionality implemented in Human.
The second point to take note of is the class initializer for MyKid. This situation
is one of the few instances where a classs __init__() method needs to be called
directly.
def __init__(self, *args, **kwargs):
Human.__init__(self, *args, **kwargs);
self.mom = kwargs.setdefault(mom);
self.dad = kwargs.setdefault(dad);
self.siblings = [];
if kwargs.setdefault(sibs) is not None:
self.siblings.extend(
kwargs.setdefault(sibs)
);
If the base classs initializer is not called, the derived classs functionality may be
a bit unpredictable. For example, a derived class will inherit its parent classs methods
(as we saw in the previous section when inheriting from object). If these methods are
not specifically implemented (overridden) in the child class, some of them may
require the presence of an instance data attribute that is created in the base classs
__init__() method, rather than in the base classs definition.
When any method is executed, inherited or otherwise, it is executed in the scope
of the derived class. If an inherited method references specific data attributes and the
base classs initializer hasnt also been run in the scope of the derived class, the
derived class will not be able to access those data attributes since they do not exist. As
a matter of practice, it is always wise to first call the base classs __init__() method in
the derived classs __init__() method, and then override any data attributes or
methods as needed.
3. Execute the following line to confirm all of the attributes in the MyKid class. As
you can see from the output, the class has inherited all of the attributes you defined in
the Human class earlier, as well as all of the attributes it inherited from object.
print(dir(MyKid));
"""
prints:
[__class__,
__delattr__,
__dict__,
__doc__,
__format__, __getattribute__, __hash__, __init__, __module__,
__new__, __reduce__, __reduce_ex__, __repr__, __setattr__,
__sizeof__,
__str__,
__subclasshook__,
__weakref__,
bmi,
create_adam,
full_name,
get_parents,
get_taller_person,
kFeetToMeters, kPoundsToKg, parents, set_parents]
"""
4. Execute the following lines to create two Human instances to pass as arguments
to a new MyKid instance, and then create the new MyKid instance.
mom = Human(first=Jean, last=Gibson);
dad = Human(first=David, last=Gibson);
me = MyKid(
first=Seth, last=Gibson,
mom=mom, dad=dad
);
5. Print the MyKid instance and note that the base class handles the __str__()
implementation.
print(me);
# prints: Seth Gibson
Although we do not do so here for the sake of economy, you will always want to
override __repr__() in child classes to ensure they will properly return values that
would allow them to be reconstructed as instances of the derived class.
Note also that the siblings attribute could store further instances of the MyKid
class.
6. Execute the following code to use a list comprehension to populate the siblings
attribute on the me instance with further MyKid instances.
me.siblings.extend(
[MyKid(
first=n,
last=Gibson,
mom=mom,
dad=dad
) for n in [Amanda, Ruth, Emily]]
);
7. Execute the following line to print each of the siblings and confirm they were
properly created.
for s in me.siblings: print(s);
"""
prints:
Amanda Gibson
Ruth Gibson
Emily Gibson
"""
Another advantage of inheritance is that you could easily create another derived
class with its own separate functionality, which may be completely unimportant for
other classes.
8. Execute the following code to create another derived class, SpaceMarine.
class SpaceMarine(Human):
def __init__(self):
Human.__init__(self);
self.weapon_skill = 4;
self.ballistic_skill = 4;
self.strength = 4;
self.toughness = 4;
self.wounds = 1;
self.initiative = 4;
self.attacks = 1;
self.leadership = 8;
Deriving classes is a powerful yet flexible way to both reuse code and keep
codebases organized. Developers interested in learning more on this topic are advised
to read both the Python Language Reference, which includes information on both
Pythons data and execution models, and the FAQs included on Pythons design,
which give insight into many of the design decisions behind Python.
Armed with a working knowledge of classes in Python, we can now take a look
at how you can use some of these paradigms in Maya using an alternate Python
implementation to maya.cmds.
Installing PyMEL
The pymel package ships with versions of Maya from 2011 forward. Earlier versions
require a separate install, which can be accomplished a couple of different ways:
The zip archive can be downloaded from
http://code.google.com/p/pymel/downloads/list.
GitHub users can pull source files from https://github.com/LumaPictures/pymel.
If you needed to download pymel for your version of Maya, you can install it
according
to
the
documentation
found
at http://www.lumapictures.com/tools/pymel/docs/1.0/install.html.
The most recent documentation can always be found on the Luma Pictures web
site, http://www.luma-pictures.com/tools/pymel/docs/1.0/index.html.
Introduction to PyMEL
While there are some functions in the pymel package that act the same as their
maya.cmds counterparts, its real advantages come from using proper object-oriented
paradigms. To get started with the pymel package once it has been installed in your
Python search path, it simply needs to be imported. The core extension module is the
primary gateway to working with PyMEL.
1. Open a new Maya scene and execute the following line to import the pymel.core
module. It may take a moment to initialize.
import pymel.core as pm;
3. If you inspect this list, however, you see the first major difference between
maya.cmds and pymel. Execute the following line to print all of the items in the
scene_nodes list.
print(scene_nodes)
"""
prints:
[nt.Transform(ufront),
nt.Transform(uside), nt.Transform(utop)]
"""
nt.Transform(upersp),
4. Compare this result with the standard maya.cmds approach by executing the
following lines.
import maya.cmds as cmds;
print(cmds.ls(type=transform));
"""
prints:
[ufront, upersp, uside, utop]
"""
PyNodes
While the ls command in the cmds module returns a list of Unicode strings, the
PyMEL invocation returns a list of a custom class type, which encapsulates data
related to a Maya transform node.
5. Execute the following line to print the type of the first item in the scene_nodes
list.
print(type(scene_nodes[0]));
# prints <class pymel.core.nodetypes.Transform>
This aspect is one of PyMELs key features: the PyNode class. All commands in
the pymel.core module that mimic maya.cmds syntax return PyNode objects instead
of strings. The reason for this approach is that commands accessible in the pymel.core
module, even those that are syntactically equivalent to maya.cmds calls, are not simply
wrapped. Most of these calls are derived upon importation or are wrapped manually.
This technique allows for greater functionality; API integration (or as the developers
call it, hybridization); increased ease of access; occasional refactoring of large
commands into smaller, logical chunks as needed (such as the file command); and
even function calls unique to PyMEL. We will demonstrate a few of these features at
the end of the chapter in our PyMEL tool.
PyNode objects in and of themselves are an interesting development and could
easily have a whole section devoted to them. In simplest terms, a PyNode object is a
Python class wrapper for an underlying API object of the given scene object it
represents. Each type of node in the scene is represented in the Maya API, and so the
PyNode class is able to simplify interfaces to many API methods. This approach
provides developers with some interesting functionality.
Code can be written more pythonically, as PyNode attributes are accessed
through proper attribute references, as opposed to external queries.
PyNode objects incorporate API-wrapped methods that can sometimes be a bit
faster than their maya.cmds counterparts.
Because PyNode objects have unique identities, tracking them in code becomes
much simpler and more reliable than when working with object names. If the name of
an object represented by a PyNode changes in the scene, the PyNode objects identity
remains intact.
API-based operations tend to be faster than string-processing operations, which
PyMEL Features
Lets take a look at a few more examples of interacting with PyMEL. The following
lines illustrate how it handles the creation and manipulation of objects. As you can
see, the PyNode class allows you to modify attribute values on nodes in the scene
using methods rather than getAttr and setAttr commands.
import pymel.core as pm;
# create a sphere and wrap it in a PyNode
my_sphere = pm.polySphere()[0];
# set an attribute
my_sphere.translateX.set(10);
my_sphere.scaleY.set(6);
# get an attribute
translate_x = my_sphere.translateX.get();
scale_y = my_sphere.scaleY.get();
# get a transforms shape
sphere_shape = my_sphere.getShape();
# connect attributes
my_cube = pm.polyCube()[0];
my_cube.translateX.connect(my_sphere.rotateY);
As you can see, PyMEL natively embraces many of the OOP paradigms we have
covered in this chapter to perform common operations. You can also work with files
quite easily. The PyMEL implementation breaks the file command into a few
different functions instead of using a single command with numerous flags.
pm.newFile(force=True);
pm.saveAs(newSceneName.ma, force=True);
pm.openFile(oldSceneName.ma, force=True);
Although we will not talk about building a GUI with Maya commands until
Chapter 7, it is worth noting that PyMEL can help simplify GUI creation, as it
implements GUI commands as Python contexts using the with keyword.
with pm.window() as w:
with pm.columnLayout():
with pm.horizontalLayout() as h:
pm.button()
pm.button()
pm.button()
h.redistribute(1,5,2)
with pm.horizontalLayout() as h2:
pm.button()
pm.button()
pm.button()
h2.redistribute(1,3,7)
"""horizontalLayout is a custom
frameLayout"""
pymel
ui
widget
derived
from
These examples are merely the tip of the iceberg. To get a better idea of how
PyMEL really works, we recommend porting a script from either MEL or maya.cmds
to PyMEL as an example of not only how different the two are syntactically, but also
as an example of how PyMEL offers different design possibilities with its objectoriented approach to Maya.
A PyMEL Example
The following example demonstrates how you might implement a tool that manages
the level of detail tagging for a game using PyMEL. The basic premise is that objects
can be selected and have an attribute applied to them that determines the level of
detail. Once this tag has been applied, objects can be selected and shown or hidden.
1. Download the lodwindow.py module from the companion web site and save it
somewhere in your Python search path. The lodwindow.py module contains a class,
LODWindow, which has a create() method for drawing the window.
2. Execute the following lines to import the LODWindow class, create an instance
of it, and then draw it using the create() method.
from lodwindow import LODWindow;
win = LODWindow();
win.create();
You should now see a window appear like that shown in Figure 5.1. The
window has a dropdown menu for selecting a level of detail to apply to the currently
selected objects using the Set LOD button. There is also a group of buttons that allows
you to select all objects with a given level of detail, and show/hide all objects with a
given level of detail.
3. Execute the following lines to create a grid of cylinders with different numbers of
subdivisions.
for i in range(3):
cyl = pm.polyCylinder(sa=res*6, n=barrel1);
cyl[0].tx.set(i*cyl[1].radius.get()*2);
cyl[0].tz.set((res-1)*cyl[1].radius.get()*2);
4. Select all of the low-resolution cylinders in the back row, select the Low option
from the LOD window dropdown menu, and press the Set LOD button. Repeat the
same steps for the corresponding medium- and high-resolution cylinders.
5. Press the Select All button in the Medium row of the LOD window and notice
that it selects all of the cylinders in the middle row (those with 12 subdivisions).
6. Press the Toggle Visibility button for the High row in the LOD window. Notice
that all of the high-resolution cylinders disappear. Play around with the other buttons
to your liking.
For the most part, the GUIs implementation is pretty straightforward. Because
we have not yet covered all of the nuances of how GUIs work in Maya, however, we
will only highlight a few of the key PyMEL features at work in the module. After you
have read Chapters 7 and 8, you may want to revisit this example to better appreciate
its advantages.
The first item of note is the use of the horizontalLayout() function.
with pm.horizontalLayout():
pm.text(label=Resolution)
with pm.optionMenu() as self.res_menu:
pm.menuItem(l=Low);
pm.menuItem(l=Med);
pm.menuItem(l=Hi);
#
Third, this example makes use of the setText() method to manage the
status_line text field, as opposed to using the textField command in edit mode.
if selected:
self.tag_nodes(selected, res);
self.status_line.setText(
Set selection to resolution %s%res
);
else:
self.status_line.setText(No selection processed.);
Finally, this example makes use of node types for building object lists rather than
passing in a string corresponding to the name of the type. Using this approach,
developers can minimize errors due to typos, as an IDE can offer automatic code
completion for these types.
poly_meshes = [
i for i in pm.ls(
type=pm.nt.Transform
) if type(i.getShape())==pm.nt.Mesh
];
While these PyMEL examples are relatively simple, we hope they have whet your
appetite for some of the more complex possibilities available with PyMEL. We want to
reiterate that if you are using Maya 2011 or later, because PyMEL is built in, evaluating
it for your workflow is as simple as importing a module. Experiment, have fun, and
consult the companion web site for information on places to communicate with other
developers about this powerful tool.
Concluding Remarks
In this chapter, we summarized one of the final main advantages of the Python
language itself by introducing object-oriented programming. You have seen that
creating classes with a range of different types of attributes is simple, powerful, and
incredibly flexible. You have also seen how the concept of inheritance can help you
design an organized hierarchy of classes. Finally, you have seen how PyMEL offers a
fully object-oriented alternative to Mayas built-in cmds module, enabling you to
harness the power of OOP in your daily work. We hope you have been intrigued and
give it a shot!
In all the chapters that follow, we make heavy use of classes to define GUIs,
design Maya tools, and create API plug-ins, so it is essential that you feel comfortable
with the basics presented in this chapter. Nevertheless, we will return to some highlevel OOP concepts in Chapter 9 when we introduce the Maya API, so working
through the examples in the intervening chapters will help give you more practice.
Hopefully, because you have been taking advantage of some OOP features by using
Python already, many of these concepts are already clear. You now have all the
prerequisites to start building all kinds of great Maya tools, so it is time we start
looking into some!
PART 2
Designing Maya Tools with Python
Chapter 6
Principles of Maya Tool Design
Chapter Outline
Tips When Designing for Users 180
Communication and Observation 181
Ready, Set, Plan! 181
Simplify and Educate 183
Tools in Maya 183
Selection 184
Marking Menus 186
Options Windows 190
Concluding Remarks 192
By the end of this chapter, you will be able to:
Distinguish form and function in tools.
Implement some basic strategies for user-centered development.
Describe the role that selection plays in Mayas tools.
Create custom marking menus.
Identify common patterns across marking menus.
Enumerate some common elements in many Maya GUIs.
This chapter discusses how to distinguish form and function in tools and
implement some basic strategies for user-centered development. It describes the role
that selection plays in Mayas tools, as well as how to create custom marking menus
and identify common patterns across marking menus.
Keywords
function, form, marking menus, tools, GUI
In short, although you must always get your job done quickly, a few simple but
thoughtful decisions can make your code much more pleasant to work with in the
future.
Tools in Maya
Now that we have discussed some of the higher-level considerations you may want to
make when developing tools, we can start looking at some example tools in Maya.
While Mayas tools and interfaces are by no means the gold standard in all cases, there
are some important patterns that can be valuable to follow in many cases.
A good starting point to design for your users, then, is to consider following
some of these patterns so that your tools operate predictably. Developing in this
manner can save you a good deal of trouble when documenting your tools, since
users should generally already know how to use them. In other cases, you may want
to break from convention where you can improve an interface, but familiarizing
yourself with some of Mayas tools can nonetheless be helpful.
Selection
A good starting point for examining Mayas tools is to investigate the role that
selection plays in the execution of a tool. The most basic observation is of course that
operations are applied to currently selected objects. Although this may seem incredibly
obvious, it is important to contrast it with other applications where you might enter a
tool mode and then start clicking on objects, or where tools exist as part of the objects
themselves. The following example illustrates this concept clearly:
1. Open Maya and create a new scene.
2. Create a new polygon cube (Create Polygon Primitives Cube).
3. Enter the Polygons menu set by selecting Polygons from the dropdown menu in
the upper left of the main application window.
4. With the cube still selected, enter the Sculpt Geometry Tool (Mesh Sculpt
Geometry Tool). You can now see the red Artisan brush move over the surface as
you move your cursor over the cube. If you click when this red gizmo appears, then
you will apply some deformation to the cubes vertices depending on the brushs
current mode.
5. Deselect the cube (Edit Deselect).
6. Reenter the Sculpt Geometry Tool (the default hotkey for previous tool is Y).
You will notice that moving your mouse cursor over the cube no longer displays the
red Artisan brush. Likewise, clicking on the cube does nothing.
As you can see, the tool is designed to operate on the current selection, rather
than to operate in isolation. Most other tools, such as the Paint Skin Weights Tool, all
follow the same principle. Following from this point, however, is the more important
issue of how Mayas built-in tools work when multiple objects are concerned. There
are many tools that require multiple objects to be selected, and they generally follow a
common pattern, as the next example demonstrates.
1. Create a new scene.
2. Enter wireframe display mode by pressing the number 4 or by selecting Shading
Wireframe from the menu at the top of your viewport.
As you can see, order matters! The list that was printed has ordered the items
based on the order in which they were selected.
[upCone1, upSphere1, upCube1]
8. With the objects still selected, use the parent command. If you have default
hotkeys, you can press the P key. If not, you can select the menu item (Edit
Parent), or enter it into the Script Editor manually. At this point, the cube is
deselected, and only the sphere and cone are selected. If you select the cube, you can
see that the sphere and cone are now its children, and will follow it around if you
move it.
The important point here is that the last object in the selection list is
semantically important: It corresponds to what the tool is doing. Specifically, the
tools name, Parent, indicates what happens to the final object selected: It becomes the
parent of any other objects that are also selected.
Imagine that the tool had a more ambiguous name, such as Link. A name like
Link tells you absolutely nothing about what the result will be: The cube might be
linked to the sphere, but what does that mean? Which one is the parent and which is
the child? Is the tool implying that they both affect one another? Parent is a much
more useful name as it clearly indicates the direction of the operation.
This same pattern is followed in all of Mayas built-in tools that require multiple
selections, whether objects, geometry components, or something else: The last object
in the selection list corresponds to the action indicated by the tools name.
9. Select only the cube.
Marking Menus
One of the most unique and important workflow features of Mayas user interface is
marking menus, which you saw briefly in the Script Editor in Chapter 1. Marking
menus are not only an incredible interface that can outpace hotkeys and standard
context-sensitive menus for many users, but are also a valuable example for designing
consistency into your tools to leverage a users muscle memory or spatial
understanding. A few brief examples should demonstrate some of the key points
regarding marking menus that you may find helpful.
1. Create a new scene.
2. Create a new polygon cube (Create Polygon Primitives Cube).
3. With the cube selected, move your cursor over the cube and hold the RMB. You
will see a menu appear, where there is a compass under your cursor and a variety of
menu items farther below (Figure 6.1). While holding the RMB, if you move your
cursor over the Vertex item in the marking menus west location (left of the compass
center) and release, then you will switch to working on the object in vertex mode.
4. While in vertex mode, move your cursor over the cube. Press and hold the RMB
and quickly flick the cursor downward, releasing the RMB immediately after your
downward flick. Performing this action will change the cube to face mode. As you
can see, marking menu compasses accommodate flicking as well as a traditional
deliberate usage common of right-click context menus.
5. While in face mode, select a face on the cube.
6. While your cursor is over the cube, hold the Shift key and RMB. Shift is the
shortcut for performing tool operations in the current selection mode. As you can see,
the south item on the compass invokes the Extrude Face tool.
7. With the face still selected, move your cursor over the cube and hold the Ctrl key
and RMB. Ctrl is the shortcut for converting one selection to another type. As you
can see, in this marking menu, the west item converts the selection to vertices, while
the south item converts the selection to faces. If you compare this to the standard
RMB menu, you will see that the compass items correspond to each component type.
In both cases, south corresponds to faces, west to vertices, and so on. In this respect,
two different marking menus follow a similar pattern, which can help users establish
muscle memory for the marking menu rather than having to hold the RMB and read
all of the options individually for a given menu, as is common in many other
applications.
8. Using the Ctrl+RMB marking menu, convert the selected face to vertices.
9. Hold Shift and RMB to look at the tool marking menu for vertices. Just as the
Extrude Face tool was south in face mode, the Extrude Vertex tool is south in vertex
mode. While not all modes have the same corresponding tool types, each direction on
the marking menu compass generally tries to correspond to some equivalent for the
other modes where it makes sense.
Figure 6.2 Testing a custom marking menu in the marking menu editor.
python("import maya.cmds as cmds");
python("cmds.polyEditUV(u=1.0, v=0.0)");
6. In the Label field, enter the word Right and press the Save and Close button.
7. In the Create Marking Menu window, edit the west menu item to have the
following command input. Similar to the command you created in step 5, this marking
menu item will move the currently selected UVs one unit to the left.
python("import maya.cmds as cmds");
python("cmds.polyEditUV(u=-1.0, v=0.0)");
8. In the Label field, enter the word Left and press the Save and Close button.
9. Keeping the Create Marking Menu window open, create a cube and enter UV
editing mode (RMB + east).
10. Open the UV Texture Editor window (Windows UV Texture Editor). You
should see the default UV layout for the cube.
11. Select all the cubes UVs.
12. In the Create Marking Menu window, use the LMB in the test area (lower left)
to try out your new marking menu on the cubes UVs (Figure 6.2).
The important aspect of what you have done, besides simply creating a marking
menu, is leverage the marking menus spatial layout to create a gestural control of
which the form and function correspond: flicking to the right moves the UVs to the
right, while flicking to the left moves them left. As you can see, creating custom
marking menus can be a powerful tool for grouping and organizing similar
operations. In addition to their speed, they can also either take advantage of a users
spatial understanding to make controls that are immediately understood, or also create
new patterns to reinforce a users muscle memory. In the case of the default marking
menus, for instance, there is nothing inherent about the direction west/left that
corresponds with a users understanding of vertices, but Autodesk has nonetheless
created a consistent pattern so users can develop that relationship in the context of
Maya.
At this point, you could give your custom marking menu a name, save it, and
assign it to a Hotbox quadrant or to a hotkey in the Hotkey Editor (see the dropdown
menu in the Marking Menus window labeled Use marking menu in:). When you
save a marking menu here, it exists as a .mel file in a users preferences directory. You
can leverage this fact to easily share custom marking menus with your users.
Options Windows
The final aspect of Mayas interface worth discussing at this point is its options
windows. As we have reiterated throughout the text, Mayas GUI is, fundamentally,
just an interface between the user and the command engine. A majority of the menu
items in Maya are simply mechanisms for executing commands.
The idea behind this principle is that form and function are almost wholly
separated: The functionality of a tool exists in the command, while the form is a GUI
control created via script. In many of your own tools, you can simply include your
form and functionality in the same module, but will almost certainly want to separate
them into different functions at the least. Thus, options windows in Maya are simply
interfaces for exposing different command flags to the user to set up arguments.
Just as with marking menus, Maya has set up some conventions that are generally
worth following for most simple tools. Take a look at some different options
windows in the following example.
1. Enter the Polygons menu set (F3).
2. Select the small square icon to the right of the Extract tool in the main application
menu (Mesh Extract ). Looking at the options window that appears, there are a
few items worth noting. First, the title of the window is Extract Options. Second, the
window menu has an Edit submenu and Help submenu. Take a moment to look at the
contents of each submenu. The central part of the window exists in a group entitled
Settings. Finally, the bottom of the window has three buttons. From left to right they
are Extract, Apply, and Close. The Extract button has the effect of performing the
operation and closing the window simultaneously, while the Apply button simply
performs the operation.
3. Select the small square icon to the right of the Smooth tool in the main
application menu (Mesh Smooth ). Looking at the options window that appears,
you should see the exact same core parts that you saw in the Extract tool, plus
additional groups of related items in the center of the window (Figure 6.3).
Concluding Remarks
Having discussed the higher-level topics related to the creation of tools, such as how
to get in the habit of focusing on your users and where to look for starting ideas, we
can now move into lower-level topics concerning how to actually create some
different interfaces. In the remaining chapters of this part of the book we will create a
variety of user interfaces.
Chapter 7
Basic Tools with Maya Commands
Chapter Outline
Maya Commands and the Maya GUI 194
Basic GUI Commands 196
Windows 196
Building a Base Window Class 198
Menus and Menu Items 199
Executing Commands with GUI Objects 201
Passing a Function Pointer 202
Passing a String 202
Using the functools Module 204
Layouts and Controls 206
Basic Layouts and Buttons 207
Form Layouts 212
Complete AR_OptionsWindow Class 215
Extending GUI Classes 218
Radio Button Groups 219
Frame Layouts and Float Field Groups 220
Color Pickers 222
Creating More Advanced Tools 224
Pose Manager Window 224
Separating Form and Function 226
Serializing Data with the cPickle Module 226
Working with File Dialogs 229
Concluding Remarks 232
By the end of this chapter, you will be able to:
This chapter describes Mayas GUI management system and creates a basic
window using Maya commands. It shows how to leverage classes and OOP when
building windows with commands, and how to implement menus, layouts, and
controls in windows. In addition, it demonstrates how to serialize object data with the
cPickle module and work with file dialogs.
Keywords
GUI, cPickle module, Command Engine, radio button groups, forms, layouts,
controls, serialization, deserialization, file dialogs, recursive
As you delve into the design of custom tools and GUIs, you have a variety of
options available. One of the most basic approaches to designing GUIs in Maya is to
use the functionality available in the cmds module. Because the cmds module is
interacting with Mayas Command Engine, this approach should be immediately
familiar. However, because the Command Engine was originally designed with MEL
in mind, using commands to create a GUI can be a little cumbersome.
Thankfully, Pythons support for object-oriented programming allows you to
develop GUIs in ways that would be impossible with MEL. Taking advantage of
Python classes in conjunction with basic Maya commands is the easiest way to build
and deploy GUI windows. Moreover, working with the cmds module introduces
many of the underlying mechanics of Mayas GUI system.
In this chapter, we will first discuss some core technical concepts related to
Mayas GUI and then develop a base class for tool option windows. We then explore
some of Mayas built-in GUI controls and demonstrate how you can easily extend this
class to quickly create new tools. Finally, we discuss some advanced topics related to
tool creation, such as serializing data and working with files, by examining a simple
Figure 7.1 Maya GUI commands interact with a GUI toolkit, which in turn interacts with the Maya
application (to display graphics or execute other commands).
Fundamentally, the Maya GUI consists of a set of controls and windows created
using MEL commands. Much like nodes in the Dependency Graph, GUI elements in
Maya are accessible via unique string names, which can often become incredibly
verbose. The following example illustrates this general concept.
1. In the Script Editor window, select the menu option History Echo All
Commands.
2. Click in one of the menus in Mayas main menu bar to open it and then move
your cursor left and right to cause other menus to open and close in turn.
3. Look at the output in the Script Editor. You should see a variety of statements
executed, the arguments for which show a path to the open menu. For instance, when
scrolling over menus in the Polygons menu set, you may see something like the
following lines in the History Panel.
editMenuUpdate MayaWindow|mainEditMenu;
checkMainFileMenu;
editMenuUpdate("MayaWindow|mainEditMenu");
ModObjectsMenu MayaWindow|mainModifyMenu;
PolygonsSelectMenu MayaWindow|mainPolygonsSelectMenu;
PolygonsNormalsMenu MayaWindow|mainPolygonsNormalsMenu;
PolygonsColorMenu MayaWindow|mainPolygonsColorMenu;
Notice that the names of these menu items look similar to transform nodes in a
hierarchy: they are given unique, pipe-delimited paths. In fact, the menus in Maya are
constructed in a similar way. In each of these examples, the MayaWindow GUI object is
the parent of some menu item, as indicated by the vertical pipe character separating
them. In MEL, the MayaWindow string is also stored in the global variable
$gMainWindow.
Just like transform nodes in your scene, full GUI object names must be globally
unique. Consequently, the names of GUI controls can sometimes be a little unwieldy
to maintain uniqueness. Nested controls can be even more frightening to look at. In
some versions of Maya, for instance, clearing history in the Script Editor (Edit
Clear History) displays something like the following line in the Script Editors
History Panel.
//
Result:
scriptEditorPanel1Window|TearOffPane|scriptEditorPanel1|formLayout37|formL
//
As you can see, the selected menu option is a child at the end of a very long
sequence of GUI objects. Fortunately, when you design GUIs in a module using cmds,
the commands you execute will return these names, so you will hopefully never have
to concern yourself with them directly. It is important, however, to understand that
because these GUI object names must be unique, you should ensure that your own
GUIs do not have conflicting names at their top levels.
4. Before proceeding, it is advisable that you disable full command echoing
(History Echo All Commands) in the Script Editor, as it can degrade
performance.
Windows
One of the most common GUI objects is a window, which can house other controls as
children. You can create a GUI window using the window command, but must execute
the showWindow command to display it.
1. Execute the following lines in the Script Editor to create a window with the
handle ar_optionsWindow and then show it. You should see an empty window like
that shown in Figure 7.2.
This example first creates a window with a specified (hopefully unique) handle, a
title bar string, and a size equal to that of most of Mayas standard tools (546 pixels
wide by 350 pixels high).1 After the window is created, it is shown.
Note the handle we assigned to our window: ar_optionsWindow. One way that
many Maya programmers attempt to avoid naming conflicts is to prefix their GUI
elements names with their initials or the initials of their studios. At this point, you can
use the windows unique handle to access it further.
2. With your window still up, try to change the title of your window by executing
the following code.
win = cmds.window(
ar_optionsWindow,
title=My Second Window,
widthHeight=(546,350)
);
You should see an error in the History Panel informing you that the name is
already in use.
# Error: RuntimeError: file <maya console> line 4: Objects name
ar_optionsWindow is not unique. #
To make changes to a GUI, you must destroy it, make your change, and then
show it again. You can destroy your window by pressing the close button in its corner
or using the deleteUI command.
3. Execute the following code to delete the UI, assign a new title, and then show it
again.
cmds.deleteUI(win, window=True);
win = cmds.window(
ar_optionsWindow,
title=My Second Window,
widthHeight=(546,350)
);
cmds.showWindow(win);
With our window properly sized and organized in a class, we will want to add a
menu and some controls to be more useful. If you are using the Script Editor as
opposed to an external IDE, leave the AR_OptionsWindow class up in a working
Python tab, as we will modify it throughout the rest of this example.
The commonMenu() method first creates a menu with the label Edit and adds
Mayas ordinary menu items to it: Save Settings, Reset Settings, a divider, and then
two radio buttons to use the window as a tool or as an action.2
Note that we call the radioMenuItemCollection command to initiate a sequence
of items in a radio button group. The subsequent menuItem calls then enable the
radioButton flag to make them members of this group. We also disable these items
by default using the supportsToolAction data attribute, which we added in the
setupWindowAttributes() method in the previous step.
We follow these commands with another call to the menu command to create the
help menu with one item (Help on Options Window). As you can see, each
successive call to the menu command establishes the most recently created menu as the
default parent for successive menuItem calls.
5. In the create() method, add the menuBar flag to the window call, and then add a
call to the commonMenu() method before showing the window.
def create(self):
if cmds.window(self.window, exists=True):
cmds.deleteUI(self.window, window=True);
self.window = cmds.window(
self.window,
title=self.title,
widthHeight=self.size,
menuBar=True
);
self.commonMenu();
cmds.showWindow();
6. If you are working in the Script Editor, execute the AR_OptionsWindow classs
code again to update it. Remember to highlight all of it before pressing Ctrl + Enter
so you do not clear the Input Panel.
7. Create a new AR_OptionsWindow instance and call its create() method. The
modifications you made in the previous steps add a menu bar that resembles Mayas
default menus for tool options, as shown in Figure 7.3.
Although our menu looks pretty good by Autodesks standards, none of its menu
items actually do anything yet. At this point, we should look into adding some
commands.
If you execute your class definition again to update it in __main__, instantiate the
class, and then call its create() method, the help menu item will now launch the
companion web site. (Try to avoid tormenting your coworkers by sending their help
requests to http://lmgtfy.com/.)
testWindow = AR_OptionsWindow();
testWindow.create();
Although this approach is clean, straightforward, and safe, it suffers at least one
important limitation. Namely, it does not allow you to pass arguments to the function
specified with the command flag. If you were to print args inside the helpMenuCmd()
method, you would see that the method is implicitly passed a tuple argument with one
item.
(False,)
While you can largely bypass this problem by putting your GUI into a class and
reading data attributes inside the method you call, this issue prevents you from
specifying a pointer to any commands in the cmds module (since they will expect
different object lists) or from passing a value that you may not have defined as a data
attribute (and hence would like to pass as a keyword argument).
Passing a String
Another option you have for issuing commands is to pass a string of statements. Much
like Pythons built-in eval() function, this technique allows you to simply use string
formatting operations to pass in arguments, as in the following hypothetical snippet,
which creates a personalized polygon sphere.
import os;
import maya.cmds as cmds;
class SphereWindow(object):
def __init__(self):
self.win = arSphereSample;
if cmds.window(self.win, exists=True):
cmds.deleteUI(self.win);
self.win = cmds.window(
self.win,
widthHeight=(100,100),
menuBar=True
);
self.menu = cmds.menu(
label=Create
);
cmds.menuItem(
label=Personalized Sphere,
command=import maya.cmds; +
maya.cmds.polySphere(n="%s")%
os.getenv(USER)
);
cmds.showWindow();
win = SphereWindow();
Apart from being an annoyance, this technique also has a big problem associated
with it. Namely, the string you pass is executed in __main__. Consequently, you may
have problems when trying to access functions in modules. As you saw, because you
cannot make assumptions about what modules have been imported into __main__,
you may need to include import statements if you want to execute Maya commands.
You may find it tedious to include import statements for all of your controls to
simply ensure that modules you require exist. One technique to combat this problem
is to use the eval() function in the maya.mel module to invoke the python command,
While this approach may be tempting, it is also dangerous and ignores the point
of module scope. Thankfully, there is an ideal approach available for most versions of
Maya with Python support.
Using the functools Module
Python 2.5 introduced a module called functools, which provides a range of
operations for working with higher-order functions. Because it is a complex module
we only cover a basic application here. Refer to Section 9.8 of Python Standard
Library for more information on the functools module at large.
If you are working in Maya 2008 or later (i.e., not 8.5), you can use the partial()
function in the functools module to pass a function with arguments to the command
flag. This technique is documented in the Maya help (User Guide Scripting
Python Tips and tricks for scripters new to Python). As such, we show only a
short hypothetical example here. The following code creates a small window with
menu options to create one, two, three, four, or five evenly spaced locators along the
x-axis.
from functools import partial;
import maya.cmds as cmds;
class LocatorWindow(object):
def __init__(self):
self.win = cmds.window(
ar_locSample,
widthHeight=(100,100),
menuBar=True
);
self.menu = cmds.menu(
label=Make Locators
);
for i in range(5):
cmds.menuItem(
l=Make %i%(i+1),
command=partial(self.makeLocCmd, i+1)
);
cmds.showWindow();
def makeLocCmd(self, numLocators, *args):
locs = []
for i in range(numLocators):
locs.append(
cmds.spaceLocator(
p=[-(numLocators+1)*0.5+i+1,0,0]
)[0]
);
cmds.select(locs);
win = LocatorWindow();
As you can see inside the for loop in the __init__() method, the partial()
function lets us pass not only a pointer to a function, but also any number of
arguments to correspond to its parameter list. Fortunately, our present menu items are
relatively simple, but this trick is essential for creating a more involved GUI.
10. Return to the AR_OptionsWindow class and add four placeholder methods for
child classes to override: editMenuSaveCmd(), editMenuResetCmd(),
editMenuToolCmd(), and editMenuActionCmd(). Remember to also specify these
methods for their respective controls. Your complete class should currently look like
the following example.
class AR_OptionsWindow(object):
def __init__(self):
self.window = ar_optionsWindow;
Our base class is now almost wrapped up! We will finally add some buttons and
then it is ready to go.
def __init__(self):
self.window = ar_optionsWindow;
self.title = Options Window;
self.size = (546, 350);
self.supportsToolAction = False;
self.actionName = Apply and Close;
12. Add methods to the AR_OptionsWindow class for the common buttons:
actionBtnCmd(), applyBtnCmd(), and closeBtnCmd().
def actionBtnCmd(self, *args):
self.applyBtnCmd();
self.closeBtnCmd();
def applyBtnCmd(self, *args): pass
def closeBtnCmd(self, *args):
cmds.deleteUI(self.window, window=True);
These methods will be called when the corresponding buttons are pressed.
Because of how we have written these methods, the only one that subclasses will need
to override is the applyBtnCmd() method. The closeBtnCmd() method simply
closes the window, and the actionBtnCmd() method invokes the Apply behavior and
then the Close behavior.
13. Add the following commonButtons() method to the AR_OptionsWindow
class right before the helpMenuCmd() definition. We will refer back to this method
momentarily after some additional changes, so you will be able to compare the code
with the visual result.
def commonButtons(self):
self.commonBtnSize = ((self.size[0]-18)/3, 26);
self.commonBtnLayout = cmds.rowLayout(
numberOfColumns=3,
cw3=(
self.commonBtnSize[0]+3,
self.commonBtnSize[0]+3,
self.commonBtnSize[0]+3
),
ct3=(both,both,both),
co3=(2,0,2),
cl3=(center,center,center)
);
self.actionBtn = cmds.button(
label=self.actionName,
height=self.commonBtnSize[1],
command=self.actionBtnCmd
);
self.applyBtn = cmds.button(
label=Apply,
height=self.commonBtnSize[1],
command=self.applyBtnCmd
);
self.closeBtn = cmds.button(
label=Close,
height=self.commonBtnSize[1],
command=self.closeBtnCmd
);
14. Add a call to commonButtons() in the create() method right after the call to
commonMenu().
def create(self):
if cmds.window(self.window, exists=True):
cmds.deleteUI(self.window, window=True);
self.window = cmds.window(
self.window,
title=self.title,
widthHeight=self.size,
menuBar=True
);
self.commonMenu();
self.commonButtons();
cmds.showWindow();
15. Evaluate all of your updates to the AR_OptionsWindow class and then create a
new AR_OptionsWindow instance. If you are working in the Script Editor,
remember to highlight your class definition and press Ctrl + Enter.
win = AR_OptionsWindow();
win.create();
You should now see a window like that shown in Figure 7.5. If you press the
buttons, the Apply and Close and Close buttons simply close the window, and the
Apply button does nothing yet. Lets look back at the commonButtons() method to
compare the code with our results. We first specify a size for our buttons as a tuple,
commonBtnSize, which stores a (width, height) pair.
Figure 7.5 The intermediate results of the AR_OptionsWindow class using a row layout.
self.commonBtnSize = ((self.size[0]-18)/3, 26);
The basic idea for computing the width is that we want 5 pixels of padding on the
left and right, and 4 pixels of padding in between the buttons (5 + 4 + 4 + 5 = 18).
Hence, we subtract a total of 18 from the windows width before dividing by 3. The
height is 26 pixels.
Next, we create a row layout, commonBtnLayout, which will serve as the parent
for our buttons. Remember that controls like buttons must have a layout parent of
some kind.
self.commonBtnLayout = cmds.rowLayout(
numberOfColumns=3,
cw3=(
self.commonBtnSize[0]+3,
self.commonBtnSize[0]+3,
self.commonBtnSize[0]+3
),
ct3=(both,both,both),
co3=(2.5,0,2.5),
cl3=(center,center,center)
);
When invoking the rowLayout command, we
relative to the left, right, or both sides). These flags offer us about as much refinement
as we will get from a row layout. The alignment flag, cl3, is not strictly necessary for
Maya 2011 and later in this case, as all buttons in newer versions of Maya default to
centered text.
Finally, we added three buttons with the proper names, using our specified
height, pointing to the proper methods that they should invoke.
self.actionBtn = cmds.button(
label=self.actionName,
height=self.commonBtnSize[1],
command=self.actionBtnCmd
);
self.applyBtn = cmds.button(
label=Apply,
height=self.commonBtnSize[1],
command=self.applyBtnCmd
);
self.closeBtn = cmds.button(
label=Close,
height=self.commonBtnSize[1],
command=self.closeBtnCmd
);
As was the case with the menus, when we create any layout or control, Maya
assumes that the most recently created layout is to be the parent. As such, we do not
need to manually specify a parent control using the parent flag.
You have obviously noticed that although the buttons are sized correctly and
appear to function as expected, they are also not at the correct vertical alignment.
(Users of Maya 2010 and earlier will see their buttons at the top of the window, rather
than centered.) The problem is that commonBtnLayout is an immediate child of window,
and so it is located in the default position under window. Figure 7.6 illustrates the
current hierarchy for our GUI window.
16. Add the following line to the create() method, right after the call to the window
command and before the call to commonMenu().
self.mainForm = cmds.formLayout(nd=100);
the
arguments to accomplish this task.
Attaching a control specifies how it should scale when the forms dimensions are
updated (when the user scales the window).3 The basic pattern for each flag is that we
pass lists that specify a control, an edge on the control, and then the values associated
with the edge.
The attachForm flag specifies edges on controls that we want to pin to the
bounds of the form we passed to the command. In this case, we pin the action button
to the left, the close button to the right, and all the buttons to the bottom of the form.
We apply an offset of five pixels on each attachment to pad the buttons in from the
edge of the window.
The attachPosition flag specifies edges that we want to pin to points in our
relative coordinate grid that we defined using the nd flag when we first created
mainForm. The final numbers in each list, 33 for actionBtn and 67 for closeBtn,
correspond to points approximately one-third and approximately two-thirds of the
forms width (33/100 and 67/100, respectively). The penultimate number in each list
represents a pixel offset for the attachment.
The attachControl flag specifies edges on a control that we want to attach to
another control, with an optional offset. In this case, because we pinned the inner
edges of the outermost buttons (actionBtn and closeBtn) using attachPosition, we
can attach both the left and right edges of the center button (applyBtn) to these outer
buttons.
The attachNone flag specifies edges on controls that should not attach to
anything, and hence should not scale. Because we specified that the bottom edges of
our buttons attached to mainForm, setting the top edges as none means they will retain
the fixed height specified when we called the button command to create them, while
the buttons remain pinned to the bottom of the window.
At this point, if you evaluate and then instantiate the AR_OptionsWindow class,
you can see that you are almost done. We have only a few minor adjustments to make
this class ready for other classes to inherit from it.
18. Add a placeholder method to the AR_OptionsWindow class for
displayOptions(). This method will be overridden in child classes to actually display
the contents in the main part of the options window.
def displayOptions(self): pass
19. Add the following lines in the create() method right after the call to
commonButtons() and before the call to the showWindow command. These lines add a
central pane to the window and then call the displayOptions() method you just
created.
self.optionsBorder = cmds.tabLayout(
scrollable=True,
tabsVisible=False,
height=1
);
cmds.formLayout(
self.mainForm, e=True,
attachForm=(
[self.optionsBorder,top,0],
[self.optionsBorder,left,2],
[self.optionsBorder,right,2]
),
attachControl=(
[self.optionsBorder,bottom,5,self.applyBtn]
)
);
self.optionsForm = cmds.formLayout(nd=100);
self.displayOptions();
As you can see, we do not simply nest our form layout, optionsForm, in
mainForm. Rather, we nest it in a tab layout, optionsBorder, which itself is nested in
mainForm. Although Autodesk has specific reasons for implementing this pattern, the
only effect as far as we need to be concerned is that nesting the form layout in the tab
layout displays a nice little border for the options area in Maya 2011 and later. 4 Now,
if you evaluate your changes and create an instance of the AR_OptionsWindow class,
you should see the result we previewed in Figure 7.4.
At this point, you should save the AR_OptionsWindow class into a module,
optwin.py, as we will use it in further examples in this chapter. Alternatively, you can
download the optwin.py module from the companion web site. For your convenience,
we have printed the final contents of the AR_OptionsWindow class here for you to
compare your results. Note that we have also added a class method, showUI(), which
provides a shortcut for creating and displaying a new window instance. The module
on the companion web site additionally contains comments.
def commonMenu(self):
self.editMenu = cmds.menu(label=Edit);
self.editMenuSave = cmds.menuItem(
label=Save Settings,
command=self.editMenuSaveCmd
);
self.editMenuReset = cmds.menuItem(
label=Reset Settings,
command=self.editMenuResetCmd
);
self.editMenuDiv = cmds.menuItem(d=True);
self.editMenuRadio = cmds.radioMenuItemCollection();
self.editMenuTool = cmds.menuItem(
label=As Tool,
radioButton=True,
enable=self.supportsToolAction,
command=self.editMenuToolCmd
);
self.editMenuAction = cmds.menuItem(
label=As Action,
radioButton=True,
enable=self.supportsToolAction,
command=self.editMenuActionCmd
);
self.helpMenu = cmds.menu(label=Help);
self.helpMenuItem = cmds.menuItem(
label=Help on %s%self.title,
command=self.helpMenuCmd
);
def helpMenuCmd(self, *args):
cmds.launch(web=http://maya-python.com);
def editMenuSaveCmd(self, *args): pass
def editMenuResetCmd(self, *args): pass
def editMenuToolCmd(self, *args): pass
def editMenuActionCmd(self, *args): pass
def actionBtnCmd(self, *args):
self.applyBtnCmd();
self.closeBtnCmd();
def applyBtnCmd(self, *args): pass
def closeBtnCmd(self, *args):
cmds.deleteUI(self.window, window=True);
def commonButtons(self):
self.commonBtnSize = ((self.size[0]18)/3, 26);
self.actionBtn = cmds.button(
label=self.actionName,
height=self.commonBtnSize[1],
command=self.actionBtnCmd
);
self.applyBtn = cmds.button(
label=Apply,
height=self.commonBtnSize[1],
command=self.applyBtnCmd
);
self.closeBtn = cmds.button(
label=Close,
height=self.commonBtnSize[1],
command=self.closeBtnCmd
);
cmds.formLayout(
self.mainForm, e=True,
attachForm=(
[self.actionBtn,left,5],
[self.actionBtn,bottom,5],
[self.applyBtn,bottom,5],
[self.closeBtn,bottom,5],
[self.closeBtn,right,5]
),
attachPosition=(
[self.actionBtn,right,1,33],
[self.closeBtn,left,0,67]
),
attachControl=(
[self.applyBtn,left,4,self.actionBtn],
[self.applyBtn,right,4,self.closeBtn]
),
attachNone=(
[self.actionBtn,top],
[self.applyBtn,top],
[self.closeBtn,top]
)
);
def displayOptions(self): pass
Planning has clearly paid off! As you can see, it is a trivial matter to start creating
a new window right away by using a class. Calling the base class __init__() method
and then reassigning some of its attributes produces a window with a new title and a
new action button label.
Note that we did not override the windows unique handle, window, in __init__().
While you certainly could do so if you prefer, we have chosen not to in order to give
our window similar behavior to Mayas option windows. Namely, when you
instantiate a new window derived from this class, it will first clear other windows
derived from the same class (unless you overwrite the window attribute in __init__()).
Although we will not override all of the empty methods in AR_OptionsWindow,
we will work through enough to explore a few more GUI commands.
Note that we set the default selected item to 1. Radio button groups in the GUI
use 1-based, rather than 0-based indices. Consequently, if you evaluate your changes
and then call the inherited showUI() class method, you will see that the Cube option
is selected by default.
3. Override the applyBtnCmd() method by adding the following code to the
AR_PolyOptionsWindow class. This method will execute an appropriate command
based on the current selection.
def applyBtnCmd(self, *args):
self.objIndAsCmd = {
1:cmds.polyCube,
2:cmds.polyCone,
3:cmds.polyCylinder,
4:cmds.polySphere
};
objIndex = cmds.radioButtonGrp(
self.objType, q=True,
select=True
);
newObject = self.objIndAsCmd[objIndex]();
As you can see, we create a dictionary to map the radio indices to different
function pointers. We then query the objType radio button group and use the
dictionary to execute the command that corresponds to the currently selected index.
At this point, you should take a look at the following code, which shows the
entire AR_PolyOptionsWindow class, to make sure yours looks the same as ours.
class AR_PolyOptionsWindow(AR_OptionsWindow):
def __init__(self):
AR_OptionsWindow.__init__(self);
self.title = Polygon Creation Options;
self.actionName = Create;
def displayOptions(self):
self.objType = cmds.radioButtonGrp(
label=Object Type: ,
labelArray4=[
Cube,
Cone,
Cylinder,
Sphere
],
numberOfRadioButtons=4,
select=1
);
def applyBtnCmd(self, *args):
self.objIndAsCmd = {
1:cmds.polyCube,
2:cmds.polyCone,
3:cmds.polyCylinder,
4:cmds.polySphere
};
objIndex = cmds.radioButtonGrp(
self.objType, q=True,
select=True
);
newObject = self.objIndAsCmd[objIndex]();
If you call the showUI() class method, you can use the Create and Apply buttons
to create different polygon primitives.
AR_PolyOptionsWindow.showUI();
4. Add the following lines to the bottom of the displayOptions() method to add a
group for entering default transformations.
self.xformGrp = cmds.frameLayout(
label=Transformations,
collapsable=True
);
cmds.formLayout(
self.optionsForm, e=True,
attachControl=(
[self.xformGrp,top,2,self.objType]
),
attachForm=(
[self.xformGrp,left,0],
[self.xformGrp,right,0]
)
);
self.xformCol = cmds.columnLayout();
self.position = cmds.floatFieldGrp(
label=Position: ,
numberOfFields=3
);
self.rotation = cmds.floatFieldGrp(
label=Rotation (XYZ): ,
numberOfFields=3
);
self.scale = cmds.floatFieldGrp(
label=Scale: ,
numberOfFields=3,
value=[1.0,1.0,1.0,1.0]
);
cmds.setParent(self.optionsForm);
The first command creates a frame layout and stores it in the xformGrp attribute.
Frame layouts are a type of layout that can collect a series of controls in a group with
a label and an optional button to collapse and expand the group. We then issue a call
t o formLayout to attach xformGrp to the objType selector and to the sides of
optionsForm. As earlier, issuing a new layout command sets the newly created layout
as the default parent for subsequent calls to control commands.
Although Maya 2011 and later would allow us to simply proceed to add controls
to xformGrp, earlier versions of Maya throw an exception because we would be trying
to add too many children to the frame layout. Consequently, to support earlier
versions of Maya, we have further nested the controls in a column layout, xformCol.
The next three controls we add all have the column layout as their parent.
The next three calls are to floatFieldGrp, which creates a group of floatingpoint number input fields. We have created a group for each of the basic
transformations: translation, rotation, and scale. Note that when we initialize the value
for the scale field we must pass it four values, since the command does not know by
default if it will consist of one, two, three, or four fields.
Finally, we issue a call to the setParent command to make optionsForm the
current parent again. Without making this call, subsequent controls would be grouped
under the xformCol column layout, as opposed to being children of the optionsForm
layout. You may find this pattern more convenient than manually specifying the
parent argument for each new control or layout you create.
5. Now that you have added your new controls, add the following lines to the
applyBtnCmd() method to apply the transformations to the newly created objects
transform node.
pos = cmds.floatFieldGrp(
self.position, q=True,
value=True
);
rot = cmds.floatFieldGrp(
self.rotation, q=True,
value=True
);
scale = cmds.floatFieldGrp(
self.scale, q=True,
value=True
);
cmds.xform(newObject[0], t=pos, ro=rot, s=scale);
If you call the showUI() class method again, you can now enter default
translation, rotation, and scale values for the primitive you create.
Color Pickers
We will wrap up this example by adding a color picker to assign default vertex colors
to the object.
6. Add the following lines to the end of the displayOptions() method to add a color
picker control.
self.color = cmds.colorSliderGrp(
label=Vertex Colors:
);
cmds.formLayout(
self.optionsForm, e=True,
attachControl=(
[self.color,top,0,self.xformGrp]
),
attachForm=(
[self.color,left,0],
)
);
The colorSliderGrp command creates
next to it.
7. Add the following lines to the end of the applyBtnCmd() method, after the call
to the xform command, to apply the selected color to the new models vertices.
col = cmds.colorSliderGrp(
self.color, q=True,
rgbValue=True
);
cmds.polyColorPerVertex(
newObject[0],
colorRGB=col,
colorDisplayOption=True
);
8. Call the showUI() class method again and you should see an example like that
shown in Figure 7.7. If you are having difficulties, consult the complete example in
the polywin.py module available on the companion web site.
You should see a very barebones GUI window like that shown in Figure 7.8. The
window contains a control group for copying and pasting poses, as well as a control
group for saving and loading poses.
4. Move the time slider to the first frame, select the characters pelvis joint
(CenterRoot), and press the Copy Pose button in the Pose Manager window. The Pose
Manager tool works by copying the values for all of the keyable attributes on
transform nodes in the hierarchy under the nodes you have selected. In this case, it is
copying the transformation values for all of the joints in the characters hierarchy.
5. Scroll to the final frame on the time slider and then press the Paste Pose button.
The character should now be in the same pose it was in when at the start of the
animation.
6. With the CenterRoot joint still selected, select the characters whole hierarchy
using the Edit Select Hierarchy menu option and set a key on all of its joints
(Animate Set Key in the Animation menu set or default hotkey S).
Figure 7.8 The Pose Manager window contains a few simple controls.
If you play the animation now, the start and end frames should match. While this
application of the pose manager is handy, it is not especially revolutionary.
7. Scrub to the first frame of the animation, select the CenterRoot joint, and then
press the Save Pose button in the Pose Manager window. Save the pose as idle.pse in
a location you will remember.
8. Open the file posemgr2.ma and view its animation. You should see the same
character with a different motion applied. Unfortunately, its initial pose doesnt quite
match the other animation well enough to produce a good blend for a game.
9. Scrub the time slider to the first frame in the animation.
10. Press the Load Pose button in the Pose Manager to pull up a file dialog. Browse
to the idle.pse file that you just saved and open it.5 The characters pose should now
match the one you saved.
11. Select the characters hierarchy again and set a key as in step 6. The characters
motion now begins in the same pose as the other animation, and would be able to
produce a much better blend if they were dropped into a game together.
While this tool does not appear to be overly glamorous, it does make use of some
important techniques worth discussing. Open the posemgr.py file and examine its
source code. The file contains some imports, an attribute, a class, and some assorted
functions. While you are free to examine the modules contents in greater detail on
your own, we highlight some key points here.
The first step in this function is to create a file object, f, using Pythons built-in
open() function. This function allows you to specify a path to a file and a flag to
indicate if you would like read or write permissions for the file. When working in
write mode, if no file exists at the specified path, Python will automatically create one.
The return value is a built-in type, file, which contains a number of useful methods.
We recommend consulting Section 5.9 of Python Standard Library online for more
information on working with file objects.
If there is a problem, we create a notification dialog to inform users of the error
before raising an exception. An artist who uses a tool that invokes this method wont
have to hunt around the console, but a developer using Maya in headless mode would
still get an exception and a stack trace.
We then call the saveHierarchy() function (which we will examine in a
moment), and use the cPickle module to serialize the data it returns. The cPickle
module is a built-in module that allows you to serialize and deserialize Python objects
effortlessly. Python also includes an analogous built-in module, pickle, which is
almost identical. The difference between the two modules is that cPickle is written in
C, and so can work up to a thousand times faster than the standard pickle module.
The only key downside is that pickle implements two classes (Pickler and Unpickler)
that can be overridden, while cPickle implements them as functions. Because we do
not need to override their functionality, we can use the cPickle module here and
harness its greater speed.
The most common usage of these modules is to call the dump() function (as we
do here) to write serialized data to a file, and the load() function to read it back in and
deserialize it. Because we cannot explore them in greater depth here, you should
consult Sections 11.1 and 11.2 of Python Standard Library for more information on
the pickle and cPickle modules, as you will find them invaluable in a number of
situations.
After writing the files contents with the dump() function, we close the file.
Remember that you must always close files when you are done with them!6
The saveHierarchy() function creates the data that we dump into the pose file.
As you can see, it has two parameters: rootNodes and data.
def saveHiearchy(rootNodes, data):
for node in rootNodes:
nodeType = cmds.nodeType(node);
if not (nodeType==transform or
nodeType==joint): continue;
keyableAttrs = cmds.listAttr(node, keyable=True);
if keyableAttrs is not None:
for attr in keyableAttrs:
data.append([%s.%s%(node,attr),
cmds.getAttr(%s.%s%(node,attr))]
);
children = cmds.listRelatives(node, children=True);
if children is not None: saveHiearchy(children, data);
return data;
This function is recursive, meaning it calls itself in its body. Recall that when we
invoke this function from exportPose(), we pass it an empty list for the data
parameter.
data = saveHiearchy(rootNodes, []);
The saveHierachy() function iterates through all of the transform nodes passed
in the rootNodes argument, skipping over objects that are not transform or joint
nodes. (You could support any object types you like, but we keep it simple here.) For
each valid node, it gathers all of the objects keyable attributes and then appends an
[object.attribute, value] pair to the data list. Once all of these pairs have been
appended, it sees if the current node has any children.
If there are any children, the function calls itself, passing in the children list as
well as the current state of the data list. Because lists are mutable, this recursive
invocation does not need to capture the result in a return value, as data will be
mutated in-place. Once the iteration has finished, the function returns. When the
function has returned out to the original invocation, where it was called from the
exportPose() context, and it has completed the last node, the data list is returned and
then assigned to a variable in exportPose().
When we read a pose back in, we use the importPose() function, which takes a
path to a pose file as its argument.
def importPose(filePath):
try: f = open(filePath, r);
except:
cmds.confirmDialog(t=Error, b=[OK],
m=Unable to open file: %s%filePath
);
raise;
pose = cPickle.load(f);
f.close();
errAttrs = [];
for attrValue in pose:
try: cmds.setAttr(attrValue[0], attrValue[1]);
except:
try: errAttrs.append(attrValue[0]);
except: errAttrs.append(attrValue);
if len(errAttrs) > 0:
importErrorWindow(errAttrs);
sys.stderr.write(
Not all attributes could be loaded.
);
This function is very straightforward. It begins by trying to open the supplied file
in read mode, again displaying a dialog box if there is some kind of problem. If the
file opens successfully, we then call the load() function in the cPickle module to
deserialize the files contents into the pose list and close the file.
The function then iterates through the items in the pose list. It tries to set the
specified attribute to the specified value in the deserialized [object.attribute, value]
pairs. If the scene does not contain the node, or cannot set the specified attribute for
some other reason, then it appends the attribute name to a list of error attributes
(errAttrs) to be able to inform the user of which attributes were by necessity
ignored. Because the importErrorWindow() function does not introduce anything
novel, we leave it to interested readers to inspect its contents in the source code if so
desired.
);
The method begins with some simple selection validation, and then proceeds to a
try-except clause. Though not strictly necessary, we implement this pattern to support
all versions of Maya with Python support. Maya 2011 added the fileDialog2
command, so we prefer it if it is available. If it does not exist, however, we fall back
on the fileDialog command, which is available in earlier versions of Maya, to
retrieve filePath.
The next lines perform some validation on the results retrieved from the file
dialog. We can return early if filePath is None or has length 0 (e.g., if the user
canceled or dismissed the file dialog). The next test, to see if filePath is a list, is
again just to support all versions of Maya. While fileDialog simply returns a string,
the fileDialog2 command returns a list of paths, as it supports the ability to allow
users to select multiple files. Thereafter, the method can simply call the exportPose()
function using the supplied path. The loadBtnCmd() method implements a similar
process.
def loadBtnCmd(self, *args):
filePath = ;
try: filePath = cmds.fileDialog2(
ff=self.fileFilter, fileMode=1
);
except: filePath = cmds.fileDialog(
dm=*.%s%kPoseFileExtension, mode=0
);
if filePath is None or len(filePath) < 1: return;
if isinstance(filePath, list): filePath = filePath[0]
importPose(filePath);
Apart from not requiring selection validation (or selection at all), the only major
difference in this method concerns the argument values that are passed to the
fileDialog2 and fileDialog commands.
The mode flag on the fileDialog command specifies whether the dialog is in
read or write mode. While we specified a value of 0 here for read, the saveBtnCmd()
method passed a value of 1 for write.7
On the other hand, the fileDialog2 command implements a more flexible
fileMode flag. Although the arguments we passed are the opposite from those we
passed to the fileDialog commands mode flag (we pass 1 when loading, and 0 when
saving), the fileMode flag has a very different meaning from the mode flag on the
fileDialog command. The basic idea is that file dialogs dont really need to know
whether you plan to read or write: you handle all of that functionality separately.
Rather, it needs to know what you want to allow the user to be able to see and select .
This flag allows you to specify what the file dialog will display, and what the
command can return. Although its different argument values are covered in the
Python Command Reference, we summarize the different argument values for this
flag in Table 7.1.
Table 7.1 Possible Argument Values and Their Meanings for the fileMode Flag Used with the
fileDialog2 Command
Argument
Value
0
1
2
3
4
Meaning
Concluding Remarks
In addition to having mastered some basic GUI commands, you have another handson example of some of the benefits of object-oriented programming in Maya using
Python. By creating basic classes for common tool GUIs, you can introduce new tools
into your studios suite much more easily than if you were restricted to MEL or if you
were manually creating new windows for every new tool. Moreover, you have learned
best practices for executing commands with your GUI controls, organizing your GUI
windows, and architecting your GUI code in a larger code base. Finally, you have
been introduced to the basics of data serialization and working with files in Python.
You now have all the foundations necessary to create a range of useful tools, though
Python still has much more to offer!
Although we tend to use the term tool fairly loosely in this text, Maya distinguishes
tools and actions in its GUI. A tool is something that works continuously by
clicking, such as any of the Artisan tools (e.g., Paint Skin Weights, Paint Vertex
Colors) or the basic transform tools (e.g., Move, Rotate, Scale). An action is a
one-shot operation, like most of the menu items. Most windows you will create
with our template in this chapter will be actions, though some menu items, such as
those in the Edit NURBS menu, support both modes. For more information,
browse the Maya help in User Guide General Basics Preferences and
customization Customize how Maya works Switch operations between
actions and tools.
Note that if you scale this window it will save its preferences, and thus subsequent
calls to create() will use the old dimensions. While we wont go into the details of
window preferences here, you can consult the companion web site for more
information. In the meantime, you can assign a different handle to your class, or
call the window command in edit mode after the call to showWindow to force its size.
Autodesk uses this pattern to reduce flickering when changing from one option
window to another. For more information, see the comments in the
createOptionBox() procedure in getOptionBox.mel. See note 1 for more
information.
5
If you cannot find the file you just saved, and are running Maya 2008 or earlier on
OS X, try browsing one folder up your directory tree. See note 7 for more
information.
Python 2.5 added special syntax in the form of the with keyword, which allows
you to open a file and operate on it in a block, after which it is automatically closed.
Consult Section 7.5 of Python Language Reference online for more information on
its features and usage.
Chapter 8
Advanced Graphical User Interfaces with Qt
Chapter Outline
Qt and Maya 234
Docking Windows 236
Installing Qt Tools 237
The Qt SDK 237
Qt Designer 239
Widgets 240
Signals and Slots 241
Qt Designer Hands On 241
Loading a Qt GUI in Maya 246
The loadUI command 248
Accessing Values on Controls 250
Mapping Widgets with Signals and Slots 251
PyQt 254
Installing PyQt 254
Using PyQt in Maya 2011+ 255
Using PyQt in Earlier Versions of Maya 257
Concluding Remarks 257
By the end of this chapter, you will be able to:
Describe what Qt is and how it is integrated into modern versions of Maya.
Dock built-in and custom windows in the Maya GUI.
Locate, download, and install Qt libraries and tools.
This chapter describes what Qt is and how it is integrated into modern versions
of Maya. It shows how to dock built-in and custom windows in the Maya GUI, and
locate, download, and install Qt libraries and tools. Widgets, signals, and slots are also
defined.
Keywords
Qt, Qt Designer, Qt Creator, PyQt, widgets, signals, slots, pyuic4, wrappers
I n Chapter 7, we examined Mayas built-in tools for creating basic windows
using the cmds module. While the cmds module provides a very natural and native
mechanism for creating GUIs in Maya, you can probably imagine that creating more
involved windows, controls, and layouts using commands could be an arduous
programming task. Fortunately, Maya 2011 and later have introduced an alternative
user interface approach that not only provides more options, but also offers a set of
tools that can substantially expedite the creation of a custom GUI. Specifically, Maya
2011 and later have fully integrated the Qt graphics libraries.
In this chapter, we briefly examine what Qt is and how it has been integrated into
Maya. We then discuss how you can install Qt libraries and tools. Next, we discuss the
basics of using Qt Designer to rapidly create GUIs and use them in Maya. Finally, we
provide a brief overview of PyQt.
Qt and Maya
To facilitate GUI flexibility and customization, Maya 2011 and later incorporate a
graphics library called Qt (pronounced cute) as the GUI toolkit. This third-party
technology is not developed or owned by Autodesk, but is nevertheless tightly
integrated into Maya.
The main benefit of the Qt toolkit is that it is a cross-platform C++ application
framework. Recall that Maya ships on three platforms: Windows, OS X, and Linux.
As you can imagine, it requires a lot of resources to maintain three separate GUI
toolkits across these platforms. In addition to its platform diversity, Qt excels at
creating compact, high-performance tools.
The philosophy around Qt is to focus on innovative coding, not infrastructure
coding. The idea is that you should spend most of your time making an elegant
window, rather than trying to place one button in the middle of the interface. Qts
tools have been designed to facilitate this workflow, which makes it an interesting
alternative to using native Maya commands.
Dont worry! Mayas user interface commands are not being phased out. All of
your existing GUIs created with Maya commands will still work. GUI creation with Qt
is purely an addition to the Maya environment, and no functionality has been removed
or lost. To help understand how Qt fits into modern versions of Maya, refer to Figure
8.1.
Figure 8.1 The Qt GUI toolkit exists as another abstraction layer outside the Maya application, yet
can work on all platforms that Maya targets.
Imagine the Qt GUI toolkit as an abstraction layer sitting on top of the Maya
application. This illustration closely resembles that shown in Chapter 7. The key point,
however, is that all target platforms can now make use of the same GUI toolkit. Prior
to Maya 2011, the GUI layer was platform-specific (e.g., Carbon on OS X, Motif on
Linux). Now, because all platforms use the same GUI library, there is much greater
consistency across operating systems. On top of the Qt layer, the native Maya GUI
commands remain unchanged. Behind the scenes, Autodesk has connected the
existing GUI commands to the new Qt layer, which allows you to seamlessly transition
into Maya 2011 and later.
For Maya 2011 and later, the standard Maya user interface is still built using Maya
GUI commands. However, now that the Maya GUI commands are driven by Qt in the
background, you will see that there is much more functionality available when
customizing the Maya interface (Figure 8.2). In addition to featuring a striking dark
color scheme, Mayas Qt-based GUI permits much better window organization and
customization by enabling features like docking, which allows you to insert windows
adjacent to other items in the main GUI window. Another helpful feature is the
inclusion of drag-and-drop functionality when adding and moving windows around
in your layout.
Figure 8.2 The Maya 2011 GUI is a big step up from the Maya 2010 GUI.
Docking Windows
The easiest way to discover the flexibility of Qt is to take a look at an example that
demonstrates some of the new functionality. If you are using Maya 2010 or earlier,
you will unfortunately just have to use your imagination. Lets first examine the
dockControl command, new in Maya 2011, and use it to dock a Graph Editor into the
current interface layout.
1. Open the Script Editor and execute the following short script in a Python tab.
import maya.cmds as cmds;
layout = cmds.paneLayout(configuration=single);
dock = cmds.dockControl(
label=Docked Graph Editor,
height=200,
allowedArea=all,
area=bottom,
floating=False,
content=layout
);
ge = cmds.scriptedPanel(
type=graphEditor,
parent=layout
);
The previous script created a pane layout, layout, for the Graph Editor to sit in
and then created a dock control, dock, to house the pane layout at the bottom of the
Maya interface (using its content flag). It finally created an instance of the Graph
Editor and made it a child of layout.
Similarly, you can use the dockControl command to dock your own custom
windows created using the cmds module. For instance, you can use it to dock the
Pose Manager window we discussed in Chapter 7.
2. Ensure that you have downloaded the posemgr.py module from the companion
web site and have saved it somewhere in your Python search path.
3. Open the Script Editor and enter the following script in a Python tab.
import maya.cmds as cmds;
import posemgr
pwin = posemgr.AR_PoseManagerWindow();
pwin.create();
cmds.toggleWindowVisibility(pwin.window);
layout = cmds.paneLayout(configuration=single);
dock = cmds.dockControl(
label=pwin.title,
width=pwin.size[0],
allowedArea=all,
area=right,
floating=False,
content=layout
);
cmds.control(pwin.window, e=True, p=layout);
As you can see, the process is very similar to that when docking built-in
windows. In this case, right after we create the window, we toggle its visibility off,
since the create() method shows the window by default. Without this toggle, the
window would display both floating in the Maya UI and docked on the right side, as
the dockControl command automatically displays its contents. Note also that closing a
dock control does not delete the dock, but simply hides it. You can access closed dock
controls by clicking the RMB over any docks title.
Installing Qt Tools
The native Maya GUI commands that we introduced in Chapter 7 are sufficient for
most users. However, you may find you require more access to the underlying Qt
toolkit. Maya does not ship with all of the Qt libraries, but rather only those it requires
to run. To work with the additional Qt header files and libraries, you must download
the correct source files, and then install and build them. Keep in mind the additional
Qt toolkit is optional, and is aimed at developers who wish to create more demanding
interfaces through such tools as PyQt and PySide.
The Qt SDK
In this section we will talk briefly about how to obtain and install the Qt Software
Development Kit (SDK). The Qt SDK contains additional libraries and development
tools, one of which is Qt Designer. Qt Designer is a visual GUI editing application,
which allows you to drag and drop controls into a canvas as opposed to manually
coding them. Although Qt Designer is part of the SDK, you do not need to install the
SDK to obtain it. If you are only interested in using Qt Designer, you can skip to the
next section of this chapter, as we discuss an alternative method to install it without all
of the other tools and libraries, many of which you may never use. If you are still
interested, however, read on!
Each version of Maya is built with a specific version of Qt. Before downloading
any files, you must first examine the Autodesk Maya API Guide and Reference
documentation, and determine which version of Qt you need to download. You can
find this information in Mayas help on the page API Guide Setting up your
build environment Configuring Qt for use in Maya plug-ins. For example,
Maya 2011 uses version 4.5.3, while Maya 2012 uses 4.7.1. The Qt version is likely to
change with every major release of Maya, so it is your responsibility to always check
when setting up your Qt environment what version the new Maya release uses.
Once you have verified the Qt version required for your version of Maya,
navigate to the Official Qt SDK download site at http://get.qt.nokia.com/qtsdk/ where
you can locate and download the required source package for your version.1 Table 8.1
lists example links for each platform for Maya 2011 and 2012.
Table 8.1 Links for the Qt SDK for Maya 2011 and 2012
Maya
Version
Operating
System
2011
Windows
2011
2011
2012
2012
2012
Link
ftp://ftp.qt.nokia.com/qt/source/qt-win-opensource4.5.3-mingw.exe
ftp://ftp.qt.nokia.com/qt/source/qt-mac-cocoaOS X
opensource-4.5.3.dmg
ftp://ftp.qt.nokia.com/qt/source/qt-all-opensourceLinux
src-4.5.3.tar.gz
http://get.qt.nokia.com/qtsdk/qt-sdk-win-opensourceWindows
2010.05.exe
http://get.qt.nokia.com/qtsdk/qt-sdk-macOS X
opensource-2010.05.dmg
http://get.qt.nokia.com/qtsdk/qt-sdk-linux-x86_64Linux
opensource-2010.05.bin
Once you have successfully downloaded the required Qt source package, you
may now install it by double clicking the .exe file on Windows or the .dmg file on OS
X. For Windows and OS X operating systems, we recommend that you follow the
installation wizard using default installation settings. On Linux there is no installation
wizardyou are required to configure the source packages yourself. For more
detailed installation instructions and steps, consult the docs/html folder that is part of
the Qt SDK download. Look for the appropriate Installing Qt page based on your
operating system.
After a successful installation, you now have access to a range of Qt tools:
Qt Designer is an application for designing and building GUIs from Qt
components.
Qt Linguist is an application to translate applications into local languages.
Qt Creator is a cross-platform integrated development environment (IDE).
Qt Assistant is a help browser featuring comprehensive documentation to work
with the Qt tools.
Other tools and examples such as plugins, frameworks, libraries, and Qt
Command Prompt.
Qt Designer
Thus far we have designed our GUIs by using Maya commands. An alternative
approach available to modern versions of Maya is to use Qt Designer (Figure 8.3). Qt
Designer is an application for designing and building GUIs from Qt components. You
can compose and customize your GUIs in a visual drag-and-drop environment. Qt
Designer is referred to as a WYSIWYG (what-you-see-is-what-you-get) tool.
In Maya 2011 and later, your WYSIWYG GUIs can be imported directly into
Maya, allowing for easy interface building. Moreover, you can embed Maya
commands into your GUI by creating dynamic properties, which provide you with an
additional level of interface control and complexity. In addition to enabling rapid
prototyping to ensure your designs are usable, Qt Designer also facilitates easy layout
changes without altering the underlying code of your GUI.
With Qt Designer you can create three types of forms: main windows, custom
widgets, and dialogs.
Main windows are generally used to create a window with menu bars, dockable
widgets, toolbars, and status bars.
Custom widgets are similar to a main window, except that they are not embedded
in a parent widget and thus do not provide the various toolbars as part of the window.
Dialogs are used to provide users with an options window.
Main windows and custom widgets are the most commonly used forms within
Maya. Dialogs tend not to be used in Maya because there is no equivalent Maya
command for the button box that is provided with the default dialog templates.
However, you could easily emulate the dialog functionality with a custom widget
using two buttons.
Widgets
All the components that are used within forms are called widgets. A widget is any type
of control in the interface, such as a button, label, or even the window itself. To be
able to query a Qt widget inside of Maya, Maya must have an equivalent
representation of the widget available in the Maya commands. Unfortunately, not all
widgets in Qt Designer are available in Maya. To access the list of available widget
types and their associated Maya commands, use the listTypes flag on the loadUI
command.
import maya.cmds as cmds;
for t in cmds.loadUI(listTypes=True):
print(t);
If you print the items in the list, you will see something like the following results.
CharacterizationTool:characterizationToolUICmd
QCheckBox:checkBox
QComboBox:optionMenu
QDialog:window
QLabel:text
QLineEdit:textField
QListWidget:textScrollList
QMainWindow:window
QMenu:menu
QProgressBar:progressBar
QPushButton:button
QRadioButton:radioButton
QSlider:intSlider
QTextEdit:scrollField
QWidget:control
TopLevelQWidget:window
If you require a widget that is not available, you must manually map the Qt
widget to something that is accessible to Maya. Qt provides a built-in mapping
mechanism referred to as signals and slots.
Qt Designer Hands On
To discuss some of the theory behind GUI design and the workflows that are involved
in creating a window in Qt Designer that will interact with Maya, it is worth walking
through a brief, hands-on example. Note that if you elected not to install the entire Qt
SDK, you will need to download and install the Qt Creator application from the
Official Qt web site (http://qt.nokia.com/products). Qt Creator is an IDE that embeds
the Qt Designer application, among other tools.
In this section, well work through a basic GUI to create a cone that points in a
direction specified using a control in the window.
1. Open Qt Designer (or Qt Creator if you are using it). You can find it at one of the
following locations based on your operating system:
Windows: Start Programs Qt SDK Tools
OS X: /Developers/Applications/Qt/
Linux: Start Programming or Start Development
2. If you are using Qt Creator, select File New File or Project from the main
menu. In the menu that appears, select Qt from the Files and Classes group on the
left, and Qt Designer Form in the group on the right. Press the Choose button.
3. Whichever application you are using, you should now see a dialog where you can
select what type of widget you would like to create. (If using Qt Designer, you should
have seen a default pop-up menu when you launched the application. If you did not,
you can simply navigate to File New.) Select the Main Window widget from this
dialog and Default Size from the Screen Size dropdown menu and proceed.
4. If you are using Qt Creator, you must now specify a file name and location.
Name the file cone.ui and set its location to your home directory. Skip past the next
page in the project wizard, which allows you to specify if you want to use version
control.
Whichever application you are using, you should now be in the main application
interface, which is very similar in both cases. Although we will not go into all of its
details, we will cover what you need to start creating a custom Maya GUI. The main
interface is roughly divided into thirds (Figure 8.4). The drag-and-drop
widgets/controls are on the left (A), the work area is in the middle, and the editors
section is on the right. The Property Editor (B) on the right is one of the most
important panes.
in the work area. To ensure you always have the object you want selected, it helps to
use the Object Inspector window in the upper right corner. If you are working in Qt
Designer and cannot see the Object Inspector window, it may be occluded by another
window. Toggle its visibility from the View menu so you can see it.
5. Select the MainWindow object from the Object Inspector window. You should see
information for the MainWindow object in the Property Editor.
The Property Editor displays all of the properties associated with the currently
selected object. Though it contains many properties, we will only discuss some basics.
The first property in the list is called objectName. This property contains the name
that will be given to your window in Maya, if you need to access it via commands, for
instance.
6. Set the value for objectName to ar_conePtrWindow.
7. Scroll down in the Property Editor list to find the property windowTitle. Set its
value to Create Cone Pointer.
8. Adjust the size of the main window to your liking by clicking on the bottom right
corner of the window and dragging it in and out. Note that the geometry property
updates in the Property Editor in real time. You can also expand this property and
manually enter a height and width to your likinga value like 300 200 is
appropriate for this example.
9. Locate the Widget Box on the left side of the interface. This box allows you to
create all of the basic types of widgets built in to Qt. Add a button to the window by
dragging the Push Button item from the Buttons group into your main window in the
canvas (Figure 8.5).
10. Change the label appearing on the button by double clicking on it. Set the text to
Create Cone Pointer. Note that the text property in the QAbstractButton section of
the Property Editor shares this value. Adjust the size of the button as needed.
win.
Although there are numerous ways of handling this issue depending on your
production environment and tool deployment,2 we will make the assumption that the
class name for the GUI (which we will code in a few steps) will exist in __main__. If
we assume that the class name exists, we can safely access any class attribute
associated with it. The mechanics of this process will be clearer in a few steps when
we design our class, AR_QtConePtrWindow, for this GUI in Python.
13. In the Property Editor, assign the value
AR_QtConePtrWindow.use.createBtnCmd to the +command property.
14. Drag a Label widget into your window and change its display text to YRotation:.
15. Drag in a Line Edit widget next to the label and assign the value inputRotation
to its objectName attribute. This widget corresponds to Mayas text field control.
16. Double click on the inputRotation widget and enter a default value of 0.0.
17. Save your .ui file as cone.ui in your home folder (Documents folder on
Windows). All Qt forms are stored in files with the .ui extension. This file contains
XML code that makes up all the properties and widgets inside of your UI.
q=True, text=True
)
);
except: raise;
cone = cmds.polyCone();
cmds.setAttr(%s.rx%cone[0], 90);
cmds.rotate(0, rotation, 0,
cone[0],
relative=True
);
win = AR_QtConePtrWindow(
os.path.join(
os.getenv(HOME),
cone.ui
)
);
win.create(verbose=True);
Your window should now appear in Maya. One of the first things you may notice
is that your Qt Designer interface is integrated seamlessly into Maya. Specifically, the
interface automatically takes on the look of Mayas GUI skin. This integration
prevents you from having to set all the colors in your custom GUI to match those of
the Maya GUI. Your GUI should create a cone when you press its button. Changing
the value for the rotation input field changes the cones default heading when it is
created.
Although it bears some similarities to those we created in Chapter 7, this GUI
class is more complex and merits some discussion. The first item defined is a class
attribute use, which you may recognize from the value we assigned the +command
attribute in Qt Designer.
use = None;
This attribute, which is initialized to None in the class definition, will temporarily
store the instance being created when its __init__() method is called.
def __init__(self, filePath):
AR_QtConePtrWindow.use = self;
self.window = ar_conePtrWindow;
self.rotField = inputRotation;
self.uiFile = filePath;
The rationale behind this pattern is that when the create() method is called
(which we will investigate next), this attribute will be pointing to the most recently
created instance. Consequently, when the button commands callback method is
bound by referencing this class attribute, it will be pointing to the function associated
with the instance on which create() is called (assuming you call create() after each
instantiation, if you were to create duplicates of this window).
You could easily achieve a similar result using a variety of techniques, so your
decision really boils down to how your tool suite deploys modules and classes. It is
stylistically fairly common to import individual classes using the from-import syntax,
so it would not be unusual to do so in __main__ via a userSetup script, for example.
Feel free to disagree though and do what works for you with your own tools.
After initializing this cache variable, we set up some attributes to match the
names we assigned to the window and to the input field in Qt Designer. Note that we
also necessitate that the constructor be passed a filePath argument, which will
specify the location of the .ui file on disk.
Note also that it is not a requirement to test for the existence of the window and
delete it with the deleteUI command. Unlike the window command, the loadUI
command will automatically increment the created windows name if there is a
conflict, much like naming transform nodes.
Another interesting option for loading a Qt interface into Maya is to set the
uiString flag instead of the uiFile flag when calling the loadUI command. The
uiString flag allows you to embed the XML code contained in a .ui file directly into a
Maya script as a string. This feature allows you to eliminate the requirement of
deploying additional .ui files in your pipeline. Nevertheless, you should always keep
your .ui files around somewhere in case you want to make changes later! If you
opened your .ui file in a text editor and copied its contents, you could paste them into
a loadUI call like the following alternative example.
self.window = cmds.loadUI(
uiString= \
r # Paste your XML code here
,
verbose=verbose
);
Since the XML code will be too long to store in one variable, you need to use the
correct syntax for wrapping text in Python. To wrap text, use the triple-doublequotation-mark syntax. Note also that we have used the r prefix to indicate that this
variable stores a raw string. Refer to Chapter 2 if you need a refresher on this syntax.
Before proceeding, it is worth reiterating that our cache, use, only needs to be
initialized before the loadUI command is called, as its invocation will trigger the
buttons association with the method immediately. Thereafter, we need not be
concerned with the fact that use will be reassigned if another instance of the GUI were
loaded. Recall our discussion on Pythons data model in Chapter 2. The pointer to the
buttons callback method for the first instance of the GUI class will still be pointing to
the same item in memory (the method on the first instance), rather than to the method
on the item to which the cache happens to be pointing when the controls command is
invoked. Consequently, multiple instances of this window will not interfere with one
another, since each will point to its own method when it is created.
The first step is to try to get the rotation value entered in the text field as a float.
We accomplish this task by constructing the full path to the text field and querying its
value. If you examine the Object Inspector in Qt Designer (upper right corner), you
can see the hierarchy of widget names as they will come into Maya, which lets us
know that there is a widget called centralWidget between the main window and the
text field. The rest of the method simply calls appropriate commands to create and
rotate the cone.
2. If you would like to dock your new custom interface, execute the following code.
dock = cmds.dockControl(
allowedArea=all,
height=cmds.window(win.window, q=True, h=True),
area=top,
floating=False,
label=Docked Cone Pointer Window,
content=win.window
);
While your new GUI fits nicely into Maya, its also not quite as good as it could
be. The text input field is a somewhat clunky way for manipulating this GUI, so
another control, such as a dial, may be a more interesting way to manipulate the
default rotation value. Unfortunately, the dial is not a built-in Maya type, and so it
cannot be directly accessed via commands to read its value. Fortunately, as we noted
earlier, Qt provides a mechanism for solving this problem: signals and slots.
widget that accepts an integer. We would only be able to perform actions like selecting
or clearing the text when the dial rotates, which isnt especially useful. In contrast to
using ordinary function pointers with Maya GUI commands, Qts signal/slot
mechanism is type safe, meaning that it will only allow us to link up signals and slots
that are sending and receiving the correct types of arguments. Consequently, to pipe
an integer into the line edit, we unfortunately need to route the dial through another
control.
7. Return to widget mode (Edit Edit Widgets) and add a Spin Box widget to
your canvas. Set its maximum property in the QSpinBox section to 360 as well.
8. Return to Signal/Slot mode (Edit Edit Signals/Slots) and drag the dial onto
the spin box. Select the valueChanged(int) signal on the dial, and the setValue(int)
slot on the spin box, and press OK. Now the dial will update the spin box.
9. Drag the spin box onto the Line Edit widget. Select valueChanged(QString) on
the spin box as the signal, and setText(QString) on the line edit as the slot, and press
OK. Now, when the value of the spin box is changed, it will be passed to the Line
Edit widget as a string.
10. Because the spin box is simply there to perform a conversion for you, you
probably do not want it cluttering up your GUI. Scale it to a small size and move it
over your dial so it fits entirely within the dial. Right-click and select Send To Back
from the context menu to hide the spin box behind the dial.
11. Return to Maya and execute the following code to create a new instance of your
window.
win = AR_QtConePtrWindow(
os.path.join(
os.getenv(HOME),
cone.ui
)
);
win.create(verbose=True);
You can now manipulate the dial control to alter the y-rotation value in your
GUI, which you may find more intuitive. Using signals and slots, you have also
ensured that when a user manipulates the dial control, the value passed to the text field
will contain a numeric value (Figure 8.8). You could probably envision more useful
applications of controls such as these to interactively manipulate character or vehicle
rigs. Feel free to experiment and get creative with your GUIs as you explore the other
Figure 8.8 Using signals and slots allows you to integrate nonstandard GUI controls, such as dials,
into Qt-based interfaces.
PyQt
Up to this point we have focused on Maya 2011 and later, as Qt is seamlessly
integrated into its GUI. However, since the loadUI command is not available in earlier
versions of Maya, developers need another mechanism if they wish to support Qtbased GUIs in earlier versions of Maya. Fortunately, there is a command-line utility
called pyuic4 that allows you to convert Qt Designer files into Python code that you
can access in any Python interpreter. To access this tool, however, you must build a
set of bindings, such as PyQt or PySide.
As the name suggests, PyQt is a combination of the Python Language and the Qt
GUI toolkit. More specifically, PyQt is a set of Python bindings for the Qt GUI toolkit.
Because it is built using Qt, PyQt is cross-platform, so your PyQt scripts will
successfully run on Windows, OS X, and Linux. PyQt combines all the advantages of
Qt and Python, and allows you to leverage all of the knowledge you have gained
using Python in Maya.
Installing PyQt
The first step for building PyQt is to download all of the Qt header files as we
described when installing the Qt SDK. Thereafter, you can download source code for
the PyQt bindings online at http://www.riverbankcomputing.co.uk/software.
Once you have these required files, installing and building PyQt is a two-step
process. First, you must download, install, and build the sip module. The sip module
allows you to create bindings to items in the Qt C++ library. It creates what are called
wrappers, which allow you to map Python functions and classes to corresponding
items in the compiled C++ libraries. The Maya API is automatically available via a
similar utility called SWIG, which we will discuss in Chapter 9. Second, you must
download, install, and build the PyQt module. The PyQt module allows you to access
all the classes and functions for creating Qt windows. When you have acquired the sip
and PyQt modules, place them in your appropriate site-packages directory based on
your operating system:
Windows: C:\Program Files\Autodesk\Maya<version>\Python\lib\site-packages
OS X:
/Applications/Autodesk/maya<version>/Maya.app/Contents/Frameworks/Python.framew
packages
Linux: /usr/autodesk/maya/lib/python<version>/site-packages
For Maya, you will be using the Python bindings PyQt4, which are compatible
with version 4.x of the Qt GUI toolkit. Unfortunately, you must build PyQt yourself.
Because there are different platform installation and build instructions, you should
refer to the companion web site to get the most up-to-date information. Note that PyQt
is not licensed under LGPL, so these built-in modules cannot be freely distributed.
PySide offers parallel functionality and different licensing terms, so you may
investigate it if you require the added flexibility.
The PyQt bindings that you build are provided as a set of modules, which
contain almost 700 classes and over 6,000 functions and methods that map nicely to
the Qt GUI toolkit. Anything that can be done in the Qt GUI toolkit can be
accomplished in PyQt.
Now we can take a look at some PyQt code to create a window inside of Maya.
Autodesk recommends that the safest way to work with Qt from within Maya is to
create your own PyQt windows rather than manipulating the existing Maya interface,
which can leave Maya unstable. Following this guideline, we offer code here for a
window that will act as a template for creating any GUIs inside of Maya. You can
reuse this code over and over again to create custom PyQt windows inside of Maya.
Based on our window class example in Chapter 7, it should be clear how you could
use this template.
def getMayaMainWindow():
accessMainWindow = omui.MQtUtil.mainWindow();
return sip.wrapinstance(
long(accessMainWindow),
QtCore.QObject
);
class PyQtMayaWindow(QtGui.QMainWindow):
def __init__(
self,
parent=getMayaMainWindow(),
uniqueHandle=PyQtWindow
):
QtGui.QMainWindow.__init__(self, parent);
self.setWindowTitle(PyQt Window);
self.setObjectName(uniqueHandle);
self.resize(400, 200);
self.setWindow();
def setWindow(self):
# add PyQt window controls here in inherited classes
pass;
In Maya 2011 and later, there is an API class called MQtUtil, which is a utility
class to work with Qt inside of Maya. This class has some helpful functions to access
the Qt layer underneath the Maya GUI commands layer. We use this class inside of
our helper function, getMayaMainWindow(), to get a handle to the Maya main
window using the mainWindow() method. Because you will learn more about
working with the Maya API in later chapters, you can merely think of this call as a
required step for the time being. We then use the sip modules wrapinstance()
function to return the reference to the main window as a QObject, which is Qts core
object type.
We define our custom window in a PyQtMayaWindow class, which inherits
from Qts QMainWindow class. In our PyQtWindow classs __init__() method, we
use our helper function to set the main Maya window as the default parent when
calling __init__() on the base class. We want to make PyQt windows children of the
main Maya window because we do not want them to be separate from Maya (and thus
be treated by the operating system as though they were separate application windows).
We then set the windows title and size and call the setWindow() method, which
subclasses can override to display any desired controls, or to change the default
window title, object name, and size.
When inheriting from this class and overriding the setWindow() method, it is
advisable to plan the GUI on paper to save time positioning controls, because the
window will be defined programmatically. Refer to the PyQt Class Reference
Documentation for the specific classes and functions needed to create your widgets
and controls, or use the pyuic4 tool in conjunction with Qt Designer to rapidly create
your GUI and get right into adding functionality to it.
To create the window specified in this class, simply create an instance of it.
pyQtWindow = PyQtMayaWindow();
pyQtWindow.show();
Concluding Remarks
Equipped with a better understanding of Qt and Maya, as well as some Qt tools and
PyQt, you now have the skill set to take GUI design to the next level. Qt does a nice
job of catering to a variety of users. Depending on your preferences, you may like the
additional level of control that code provides, making PyQt a great fit for you. On the
other hand, if you excel when working with visual tools, then Qt Designer will be
your tool of choice.
Information on Qt and GUI design could occupy many volumes worth of
knowledge, which is why we have covered only the crucial concepts required to work
with Qt in Maya. If this chapter has piqued your interest in Qt, we recommend picking
up some additional resources to continue on your learning path. The companion web
site offers links to books and web sites where you can find information on working
with Qt, designing GUIs in general, and working with Qt Designer and Maya.
In addition to offering several version options, there are two available license
options: the LGPL (Lesser General Public License) free software license and the
GPL (General Public License) commercial software license. Determine which
license type best suits your needs by consulting the licensing terms and conditions.
For example, when you deploy your class in a package of tools, you could include
an import statement that will execute in __main__ when userSetup is run, using the
eval function in maya.mel: maya.mel.eval(python("from moduleName import
ClassName");).
PART 3
Maya Python API Fundamentals
Chapter 9
Understanding C++ and the API Documentation
Chapter Outline
Advanced Topics in Object-Oriented Programming 264
Inheritance 264
Virtual Functions and Polymorphism 265
Structure of the Maya API 265
Introducing Mayas Core Object Class: MObject 266
Manipulating MObjects Using Function Sets 267
How Python Communicates with the Maya API 268
How to Read the API Documentation 270
Key Differences between the Python and C++ APIs 281
MString and MStringArray 281
MStatus 281
Void* Pointers 281
Proxy Classes and Object Ownership 281
Commands with Arguments 282
Undo 282
MScriptUtil 282
Concluding Remarks 283
By the end of this chapter, you will be able to:
Define what the Maya API is.
Explain how the Maya API uses virtual functions and polymorphism.
Describe the organization of the Maya API.
This chapter explains what the Maya API is and describes its organization. It also
identifies what API functionality is accessible to Python and compares and contrasts
Python and C++ in the API.
Keywords
data objects, function sets, enumerator, SWIG, API, MScriptUtil, mutability
Armed with an understanding of Python and familiarity with the cmds module,
you have many new doors open to you. In addition to being able to access all of the
functionality available in MEL, you can also architect completely new classes to create
complex behaviors. However, these new tools only scratch the tip of a much larger
iceberg. In addition to the basic modules examined so far, Maya also implements a set
of modules that allow developers to utilize the Maya API via Python. 1 These modules
include:
OpenMaya.py
OpenMayaAnim.py
OpenMayaRender.py
OpenMayaFX.py
OpenMayaUI.py
OpenMayaMPx.py
OpenMayaCloth.py2
The specifics of some of these modules are discussed in greater detail throughout
the following chapters. For now, lets take a step back and discuss what the Maya API
is more generally before using it in Python. If you are relatively new to software and
tools development, you might be wondering what exactly the Maya API is in the first
place.
The Maya API (short for application programming interface) is an abstract
layer that allows software developers to communicate with the core functionality of
Maya using a limited set of interfaces. In other words, the API is a communication
pathway between a software developer and Mayas core. What exactly does this mean,
though? How does the API differ from the scripting interfaces we have discussed so
far?
Although Python scripts for Maya are very powerful, they still only interface with
the program at a very superficial level when not using the API. If you recall the
architecture we discussed in Chapter 1, the Command Engine is the users principal
gateway into Maya for most operations. Thus, Python scripts that only use the cmds
module essentially allow us to replicate or automate user behavior that can otherwise
be achieved by manually manipulating controls in Mayas GUI. However,
programming with the API directly enables the creation of entirely new functionality
that is either unavailable or too computationally intensive for ordinary scripts using
only cmds. Using the API modules, you can create your own commands, DG nodes,
deformers, manipulators, and a host of other useful features (Figure 9.1).
Figure 9.1 Using Python to access the Maya API offers richer interfaces with Mayas application
core.
The trouble for Python developers, however, is that the API is written in C++.
Although you dont have to be a C++ programmer to use the API, it is still important
to have some understanding of the language to effectively read the API
documentation. In this chapter, we will first revisit some concepts of object-oriented
programming to examine the structure of the Maya API and understand how Python
communicates with it. Thereafter, we will take a short tour of the API documentation.
The chapter concludes with a brief section detailing some important differences
between Python and C++ with respect to the Maya API.
Inheritance
As we discussed in Chapter 5, OOP fundamentally consists of complex objects that
contain both data and functionality, and that may inherit some or all of these
properties from each other. For instance, a program may have a base class called
Character with some common properties and functionality necessary for all
characters (Figure 9.2). These properties may include health and speed, as well as a
move() method. Because there may be a variety of different types of characters
Human, Dragon, and Toadeach of these characters will likely require its own
further properties or its own special methods not shared with the others. The Human
may have a useTool() method, the Dragon a breatheFire() method, and the Toad a
ribbit() method. Moreover, each of these classes may have further descendant classes
(e.g., Human may break down into Programmer, Mechanic, and Doctor).
constructed as a different
specific type, we can treat them all as equals inside of the iterator when calling the
move() method. As you will soon see, understanding this technique is especially
important when using the Maya API.
Now that we have discussed the basics of how the API is structured, we can
move on to another important question. Namely, if the Maya API is written in C++,
how can we use it with Python?
You can see from the output that the vector is a special type, and not simply a
tuple or list.
"<maya.OpenMaya.MVector; proxy of <Swig Object of type MVector *
at [some memory address]> >"
back to C++ afterward. Consequently, C++ may still ultimately be the best option for
extremely computationally intensive tasks or simulations, though Python will almost
certainly always be the fastest means of prototyping in the API.
The fact that the API is written in C++ has one further consequence as far as
Python developers are concerned: The API documentation is in C++. As a result, you
need to know a little bit of C++ to clearly understand how to use API functionality
with Python.
the header to search by first letter in the top right or by substring just underneath and
to the left. The alphabetical search list is further split by category, with all M classes
on top and MFn (function set) classes underneath.
In the main view of the page are links to each individual class. Each link also has
a basic description of what the class is, as well as a parenthetical notation of which
Python module allows access to the class. If you click on a link to a class, you are
given a list of all of its members (data attributes) and methods, as well as descriptions
of what they do.
The documentation for versions of Maya 2009 and later contains tabs in the
header for navigating (Figure 9.5). As there is both a Modules tab and a Classes
tab, you can navigate for information using either option. Clicking the Modules tab
displays a list of all the Maya Python API modules. Clicking the name of one of these
modules brings up a list of all API classes that are wrapped in it with a partial
description of the class.
Clicking the Classes tab displays a browser that is more similar to the
documentation in Maya 8.5 and 2008. At the top is a list of tabs that allow you to view
the classes as an unannotated alphabetical list (Alphabetical List), an annotated
alphabetical list (Class List), a hierarchically sorted alphabetical list (Class
Hierarchy), or to see an alphabetical list of all class members with links to the classes
in which they exist (Class Members). The Class Members section lets you further
narrow your search by filtering on the basis of only variables, only functions, and so
on. Now that we have discussed how to find the API classes, lets walk through an
example to see some common terminology.
Whichever version of the Maya documentation you are using, navigate to the
MVector class and click its link to open its page (Figure 9.6). At the top, you will see
a brief description telling you what the MVector class does: A vector math class for
vectors of doubles. Immediately below this description is a list of public types and
public members.
The first item in this list is an enumerator called Axis. The Axis enumerator has
four possible values: kXAxis, kYAxis, kZAxis, and kWAxis. Enumerators such as
these are typically included in basic classes and are often required as arguments for
some versions of methods (more on this later). Enumerated types preceded with the
letter k in this fashion typically represent static constants (and are thus accessed
similar to class attributes in Python). Execute the following code in a Python tab in the
Script Editor.
import maya.OpenMaya as om;
print(X-Axis: %s%om.MVector.kXaxis);
print(Y-Axis: %s%om.MVector.kYaxis);
print(Z-Axis: %s%om.MVector.kZaxis);
print(W-Axis: %s%om.MVector.kWaxis);
As you can see, the value of each of these enumerators is an integer representing
the axis in question, rather than an actual vector value.
X-Axis:
Y-Axis:
Z-Axis:
W-Axis:
0
1
2
3
Consequently, you are also able to test and set enumerator values in code using
an integer rather than the name for the enumerator. Such shorthand is typically not
recommended, however, as it makes the code harder to read later than does simply
using logical names.
After this enumerator, the next several functions are constructors. These
functions are all called MVector() and take a variety of arguments (see Figure 9.6). In
C++, a function with a single name can be overloaded to work with different types of
data or values as well as with different numbers of arguments. MVector has seven
different constructor options, depending on what type of data you want to give it. The
first constructor, MVector(), is the default constructor taking no arguments. This will
initialize the members of the MVector object to their default values.
The second, third, fourth, and fifth constructorsMVector (const MVector
&), MVector (const MFloatPoint &) , MVector (const MFloatVector &) , and
MVector (const MPoint &) all follow a common pattern. Namely, they create a
new MVector object and initialize its x, y, z, and w values from some other objects
corresponding members. In our effort to understand C++, we should note two
common features of these constructors: first, each argument is prefixed by the
keyword const, and second, each argument is suffixed by an ampersand (&).
The & symbol means that the object is passed by reference; you can think of the
& symbol as standing for address of something. This symbol is given because C++
Wrong
number
of
arguments
for
Depending on what version of Maya you are using, you may see an error
message like the following output instead.
# Error: TypeError:
double const [3] #
in
method
new_MVector,
argument
of
type
Although our logic was sound in this case, the APIs SWIG layer is not smart
enough (as of Maya 2012) to make the conversion where arrays are concerned. This
situation thus requires Mayas special MScriptUtil class, as in the following example.
import maya.OpenMaya as om;
# create a double array dummy object
doubleArray = om.MScriptUtil();
# populate the dummy with data from a list
doubleArray.createFromList([1.0, 2.0, 3.0], 3);
# create the vector by passing a pointer to the dummy
vec = om.MVector(doubleArray.asDoublePtr());
print((%s, %s, %s)%(vec.x, vec.y, vec.z));
You should see the value (1.0, 2.0, 3.0) printed to confirm that the vector was
correctly created.
The MScriptUtil class is a handy, but somewhat confusing, way of interfacing
with the API where it requires special representations of data that Python does not
implement. Specifically, because Python always passes simple data types by value, a
special tool is needed when the API requires arrays, pointers, or references of simple
types like integers and decimals. The MScriptUtil class is briefly explained in more
detail in the final section of this chapter, and explored in-depth on the companion web
site.
At the end of the MVector classs list of constructors is also a destructor,
prefixed with the tilde character (~). C++ uses functions like this one to deallocate
memory when an object is deleted (just like the built-in __del__() method for class
objects in Python). As mentioned in Chapter 2, however, Python handles memory
management and garbage collection on its own. Consequently, manual deallocation is
not strictly necessary, and may often only be beneficial in situations for which C++ is
better suited than Python anyway.
Next, you should see a set of 18 items prefixed with operator followed by some
symbol. These are operator overloads for the MVector class. Many of these symbols
are normally reserved for working with basic data types like numbers and lists.
However, they have been overridden for this class, meaning that they are available for
shorthand use. Consider the following example.
import maya.OpenMaya as om;
vec1 = om.MVector(1.0, 0.0, 0.0);
vec2 = om.MVector(0.0, 1.0, 0.0);
# without operator overloading, you would have to add manually
vec3 = om.MVector(vec1.x + vec2.x, vec1.y + vec2.y, vec1.z
vec2.z);
# with operator overloading, you can add the objects themselves
vec4 = vec1 + vec2;
# confirm that the results are the same
print(vec3: (%s, %s, %s)%(vec3.x, vec3.y, vec3.z));
print(vec4: (%s, %s, %s)%(vec4.x, vec4.y, vec4.z));
You should see from the output that both addition approaches produce the same
result, but the overloaded operator is obviously preferable.
Operator overloading can also offer the advantage of bypassing the requirement
of typing out method names for common operations. For instance, note that there are
overloads for operator^ (const MVector & right) const and operator* (const
MVector & right) const , which allow for quickly computing the cross-product and
dot-product, respectively. A number of these overloads are also suffixed with the
const keyword. When const appears at the end of a function declaration, it indicates
that the function will not change any of the objects members. Note, in contrast, that
the in-place operators (e.g., +=, -=, *=, /=) do not have the trailing const keyword, as
they will in fact alter the objects members. (Note that Python also allows a similar
technique, as discussed in section 3.4.8 of the Python Language Reference.)
The next items in the MVector class are five different versions of the rotateBy()
method, which rotates a vector by some angle and returns the rotated vector. Note that
you can see the return type (MVector) to the left of the functions name. In the
detailed description lower in the document, you can also see a double colon preceded
by the class name (MVector::rotateBy()). This notation simply indicates that
rotateBy() is an instance method. The following example illustrates an invocation of
the first version, rotateBy (double x, double y, double z, double w) const, which
rotates the vector by a quaternion given as four doubles.
import math;
import maya.OpenMaya as om;
vector = om.MVector(0.0, 0.0, 1.0);
# rotate the vector 90 degrees around the x-axis
halfSin = math.sin(math.radians(90*0.5));
halfCos = math.cos(math.radians(90*0.5));
rotatedVector = vector.rotateBy(halfSin, 0.0, 0.0, halfCos);
# confirm rotated vector is approximately (0.0, -1.0, 0.0)
print(
(%s, %s, %s)%(
rotatedVector.x,
rotatedVector.y,
rotatedVector.z
)
);
As you can see, you simply need to call the rotatedBy() method on an instance
of an MVector object, and the resulting rotated vector will be returned. The second
v e r s i o n , rotateBy
(const
double
rotXYZ[3],
MTransformationMatrix::RotationOrder order) const , rotates the vector using
three Euler angles with a given rotation order. The first parameter in this case is a
double array, while the second is an enumerator defined in the
MTransformationMatrix class. Clicking on the enumerator link, you can see that
this enumerator can have eight different values: kInvalid, kXYZ, kYZX, kZXY,
kXZY, kYXZ, kZYX, kLast. These enumerators are accessed just like the Axis
enumerator within the MVector class.
import maya.OpenMaya as om;
print(om.MTransformationMatrix.kXYZ); # prints 1
The third version of the method, rotateBy (MVector::Axis axis, const double
angle) const, rotates the vector using the axis angle technique. The first parameter is
one of the enumerated axes from the MVector class (kXaxis, kYaxis, or kZaxis), and
the second parameter is the angle in radians by which to rotate around this axis.
The fourth and fifth versions of this method, rotateBy (const MQuaternion &)
const and rotateBy (const MEulerRotation &) const, rotate the vector using
Mayas special objects for storing rotation as either a quaternion or Euler angles,
respectively.
The next function in the list is rotateTo (const MVector &) const . In contrast
to the rotateBy() methods, rotateTo() returns a quaternion rather than a vector, as
indicated by the return type of MQuaternion. This quaternion is the angle between
the MVector calling the method and the MVector passed in as an argument.
The get() method transfers the x, y, and z components of the MVector into a
double array. Again, because Python can only pass simple types by value, you must
use the MScriptUtil class to utilize this method, as in the following example.
import maya.OpenMaya as om;
# create a double array big enough to hold at least 3 values
doubleArray = om.MScriptUtil();
doubleArray.createFromDouble(0.0, 0.0, 0.0);
# get a pointer to the array
doubleArrayPtr = doubleArray.asDoublePtr();
# create the vector
vec = om.MVector(1.0, 2.0, 3.0);
# use the get() method to transfer the components into the array
# there is no MStatus in Python, so try-except must be used instead
try: vec.get(doubleArrayPtr);
except: raise;
# confirm that the values (1.0, 2.0, 3.0) were retrieved
print((%s, %s, %s)%(
doubleArray.getDoubleArrayItem(doubleArrayPtr,0),
doubleArray.getDoubleArrayItem(doubleArrayPtr,1),
doubleArray.getDoubleArrayItem(doubleArrayPtr,2)
));
As you can see, this method acts on the double-array pointer in-place, rather than
returning an array. Instead, the method returns a code from the MStatus class. This
class, used throughout the API, allows C++ programmers to trap errors. For example,
if this call created a problem, it would return the code kFailure defined in the
MStatus class. However, because Python has strong error-trapping built in with tryexcept clauses, the Python API does not implement the MStatus class at all.
Therefore, Python programmers must use try-except clauses instead of MStatus to
handle exceptions.
The next method, length(), simply returns a decimal number that is the
magnitude of the MVector object.
The following two methods, normal() and normalize(), are similar, albeit
slightly different. The first, normal(), returns an MVector that is a normalized copy
of the vector on which it is called. The normalize() method, on the other hand,
returns an MStatus code in C++ because it operates on the MVector object in-place.
Again, nothing in Python actually returns an MStatus code since the class is not
implemented.
Th e angle() method provides a shortcut for computing the angle (in radians)
between the MVector from which it is called and another MVector supplied as an
argument.
The next two methods, isEquivalent() and isParallel(), provide shortcuts for
performing comparison operations between the MVector from which they are called
and another supplied as an argument within a specified threshold. The former tests
both the magnitude and direction of the vectors, while the second compares only the
direction. Each method returns a Boolean value, making them useful in comparison
operations.
The final method accessible to Python is transformAsNormal(), which returns
a n MVector object. While the details in the documentation provide an elegant
technical explanation, it suffices to say that the MVector this method returns is
equivalent to a vector transformed by the nontranslation components (e.g., only
rotation, scale, and shear) of the MMatrix argument supplied.
The final two items listed are operator overloads for the parenthesis () and square
brackets [] characters. You will notice that, in contrast to the similar operators listed
earlier, these two operators are listed as having NO SCRIPT SUPPORT. The reason is
because, as you can see, each of these two specific operator overloads returns a
reference to a decimal number, & double, while the two corresponding operators that
are supported in Python (listed with the math operators) return values. Consequently,
the lack of script support does not mean that you cannot use these two symbol
sequences at all, but rather that you cannot use them to acquire a reference to a
decimal number. As discussed earlier, Python always passes simple types by value.
Consequently, it has no support for references to simple types. For the purposes of
Python programming, you can effectively ignore their lack of compatibility in this
case.
MStatus
As indicated in our discussion of the MVector class, the MStatus class exists in the
C++ API to confirm the success of many operations by returning a status code either
as the return value of a function or as one of its parameters. Whereas Python natively
incorporates robust exception handling via the try-except clause, the MStatus class
has not been implemented in the Python API at all. Consequently, Python
programmers must use try-except clauses instead of MStatus. Trying to use Python
to extract a value from functions that return MStatus may actually crash some
versions of Maya!
Void* Pointers
The C++ API makes use of void* pointers in various places including messages. Such
pointers essentially point to raw memory of an unspecified type, making them ideal
for handling different data types if a programmer knows precisely what to expect.
Because these pointers are unspecified in C++, any Python object can be passed where
a void* pointer is required.
Undo
Because Python allows programmers to use the Maya API directly within a script, it is
worth noting that doing so is not compatible with Mayas undo mechanism. As such,
any scripts that modify the Dependency Graph in any way must be implemented
within a scripted command to support undo functionality. Mayas undo and redo
mechanisms are described in greater detail in Chapter 10.
MScriptUtil
As mentioned in the discussion of the MVector class, Python does not support
references to simple types, such as integers and decimals. Imagined through the lens
of a language like C++, Python technically passes all parameters by value. While the
value of a class object in Python can be imagined as being equivalent to a reference,
the same does not apply for simple types, of which the value corresponds to an actual
number.
Because simple types are always passed by value as far as SWIG is concerned, it
needs a way to extract a reference from these values. This process is accomplished by
effectively collecting the data for the simple type(s) as an attribute (or several
attributes) on a class object, which SWIG can then manipulate as a reference.
As such, functions that require numeric array parameters require the invocation
of an MScriptUtil object to act as a pointer. The MScriptUtil class contains methods
for converting Python objects into a variety of different pointer and array types
required throughout the C++ API. Using the MScriptUtil class can be fairly
cumbersome at times, but it is unfortunately the only means of accessing many
functions in the C++ API. See the companion web site for more information on using
MScriptUtil.
Concluding Remarks
Mayas C++ API contains many classes and function sets that are accessible through
various Python modules. These modules use SWIG to automatically convert Python
parameters into data for C++, as well as to bring the data back into Python. Because of
this intermediate layer, as well as a lack of perfect correspondence between C++ and
Python, programmers working with the Python API must have some basic
understanding of C++ to read the documentation, understand some limitations they
will face, and better grasp classes like MScriptUtil. Though it is somewhat different
from ordinary Python scripting in Maya, the Maya Python API provides an accessible
way to rapidly develop powerful tools for Maya. The remainder of the book focuses
on various uses of the Maya Python API.
Note that Maya 2012 Hotfix 1 introduced the Python API 2.0 in the maya.api
module. This module contains a mostly parallel structure of extension modules
(e.g., maya.api.OpenMaya, maya.api.OpenMayaAnim, and so on). The advantage
of the Python API 2.0 is that it is often faster, eliminates some of the complexities
of working with the API, and allows use of Pythonic features with API objects,
such as slicing on API objects. While the new and old API can be used alongside
each other, their objects cannot be intermixed (you cannot pass an old API object to
a new API function). Nevertheless, they have the same classes and so learning with
the old API is not disadvantageous. Because the new Python API is still in progress,
and because it is only supported in the latest versions of Maya, all of our API
examples in this book are written using the ordinary API modules. Refer to the
Python API 2.0 documentation or the companion web site for more information on
the Python API 2.0.
The OpenMayaCloth.py module is for working with classic Maya cloth, and has
become deprecated in favor of the newer nCloth system (introduced in Maya 8.5).
The nCloth system is part of Maya Nucleus, which is accessed by means of the
OpenMayaFX.py module. Consequently, you will generally not use the
OpenMayaCloth.py module unless you are supporting legacy technology that has
been built around the classic Maya cloth system.
Technically speaking, there are some cases where the Maya API does in fact
provide direct access to data in the core, typically only where needed to improve
performance and only when there is no risk of adversely affecting them. However,
these cases are not documented for developers and are not important to know as a
practical matter. Consequently, the philosophy of the API still holds as a general
rule as far as developers need to be concerned.
4
Some basic types are directly accessible in the API, such as MAngle,
MDataHandle, MTime, etc.
Python API 2.0 is not generated via SWIG, and many of its methods have been
reworked from the ground up to return data differently, which is why it eliminates
the use of MScriptUtil. See note 1.
This issue is why the example in this books Introduction chapter used the
MItGeometry.setAllPositions() method, for example.
8
9
10
Chapter 10
Programming a Command
Chapter Outline
Loading Scripted Plug-ins 286
Anatomy of a Scripted Command 289
OpenMayaMPx Module 289
Command Class Definition 289
doIt() 290
Command Creator 290
Initialization and Uninitialization 291
Adding Custom Syntax 292
Mapping Rotation Orders 296
Class Definition 297
Syntax Creator 298
Flag Arguments 298
Command Objects 300
Initialization of Syntax 301
doIt() 302
Object Validation 302
Storing Flag Arguments 303
doItQuery() 305
Mayas Undo/Redo Mechanism 308
Supporting Multiple Command Modes and Undo/Redo 312
Undo and Redo 313
MDGModifier 313
Command Modes 314
Syntax Creator 317
__init__() Method 318
Initializing for isUndoable() 319
This chapter describes how Maya uses proxy classes to establish object
ownership. It also shows how to implement the basic parts of scripted commands and
add support for create, edit, and query command modes. In addition, it explains
Mayas undo/redo mechanisms and how to implement their functionality into a
scripted command.
Keywords
proxy classes, MSyntax, devkit, Plug-in Manager, flag argument, command
argument, command objects, local space, world space, undo/redo mechanism,
and Maya version. Select the helloWorldCmd.py file and press Open.
4. If you scroll to the bottom of the Plug-in Manager window, you should see
helloWorldCmd.py with some checkboxes and an icon with the lowercase letter i next
to it. This icon is the information button. Click the information button next to the
entry for helloWorldCmd.py.
6. In the Script Editor window, go to a Python tab and execute the command from
the cmds module. You should see the same result.
import maya.cmds as cmds;
cmds.spHelloWorld();
As you can see, loading the plug-in made the new command immediately
available both to MEL and the Python cmds module. When a scripted command plugin is loaded into Maya, there is no need to reload the cmds module. Rather, if you
make changes to a scripted plug-in in an external text editor, you must reload the plugin either using the Plug-in Manager or the unloadPlugin and loadPlugin commands.
Now that you understand the basics of loading and executing a plug-in and have seen
the results of the helloWorld.py plug-in, we can look at code for a basic scripted
command.
OpenMayaMPx Module
The first part of the command simply imports a set of modules. While we have
already discussed the OpenMaya module in previous chapters, you will see one new
module at the end of the list: OpenMayaMPx.
import maya.OpenMaya as om;
import maya.OpenMayaMPx as ommpx;
This module allows access to Maya proxy classes, which belong to a special
category of API objects that includes commands and nodes. We will soon see
examples of proxy classes in this command.
The first thing to notice is that the command inherits from the MPxCommand
class in the OpenMayaMPx module. Most custom commands you write will inherit
from this class. However, the OpenMayaMPx module also contains a variety of more
specific command types with built-in functionality that all inherit from
MPxCommand (e.g., MPxConstraintCommand, MPxModelEditorCommand,
MPxToolCommand). Because of the specific nature of such command classes, we
will not belabor their details here, though you can find more information in the API
documentation.
The class definition contains a class attribute, kPluginName. The value of this
string is the actual name users will enter in Maya to invoke the command. The reason
for defining the string as a class variable is to avoid using string literals in the code
where the name of the command is required elsewhere. This practice makes changing
the name of the command at a later point a much easier exercise. In this particular
instance, users call ar_helloWorld to invoke the command.
The next item in the class is an explicit __init__() method. In this particular case,
all that happens when the class is instantiated is that the __init__() method of the base
class, MPxCommand, is called, which initializes all of the base class attributes. In
your own custom commands, you might use this opportunity to initialize any data
attributes for your command as well, especially including those that will be set by
flags. You will see an application of this process later in our sample scripts dealing
with custom syntax and undo/redo.
doIt()
The next item in the class definition is the most important part of a scripted command:
the doIt() definition.
def doIt(self, args):
print(Hello, world.);
Command Creator
The next item in this scripted command plug-in is the command creator definition.
@classmethod
def cmdCreator(cls):
return ommpx.asMPxPtr(cls());
This function is called when the plug-in is loaded into Maya. The format for the
initializePlugin() definition is fairly standard across most plug-ins, though we will see
some minor differences when we implement a custom syntax later in this chapter, as
well as when we create custom nodes in Chapter 12.
The first thing done in this function is that a function set, plugin, of type
MFnPlugin, is instantiated with its object set to the incoming MObject (the custom
plug-in). The function then attempts to register the command with Maya by calling
registerCommand() on the function set and supplying the plug-ins name and its
creator method as arguments. If the registration fails for some reason, then an
exception is raised and an error message is printed to the console.
Similarly, the final definition in the script is the uninitializePlugin() function.
def uninitializePlugin(obj):
plugin = ommpx.MFnPlugin(obj);
try:
plugin.deregisterCommand(
AR_HelloWorldCmd.kPluginCmdName
);
except:
raise Exception(
Failed to unregister command: %s%
AR_HelloWorldCmd.kPluginCmdName
);
Whereas the initializePlugin() function is called when the plug-in is loaded into
Maya, the uninitializePlugin() function is called when the plug-in is unloaded. This
can happen either by unchecking the Loaded checkbox in the Plug-in Manager or by
invoking the unloadPlugin command. Overall, the definition is very similar to
initializePlugin(), and instead simply calls the deregisterCommand() method on an
MFnPlugin function set, passing in only the name of the command.
It is critical to note again that you must unload and then reload a plug-in to see
changes that you are making in an external text editor become active in Maya.1
Doing so calls the uninitializePlugin() function and then the initializePlugin()
function again.
Now that we have reviewed the essential parts for every scripted commandits
name, its class definition (including the doIt() method), and its creator method, as
well as the plug-ins initialization and uninitialization functionswe can add more
advanced features, the most important of which is the ability to handle custom syntax.
3. In the Plug-in Manager window, click the Refresh button if you do not see
AR_TransformCmd1.py listed in the directory where you saved it. Alternatively, if
you saved it in a directory outside of your plug-in path, then use the Browse button to
locate it.
4. Click the Loaded checkbox next to AR_TransformCmd1.py in the Plug-in
Manager window to make the command active in Maya.
5. Open a Python tab in the Script Editor and execute the following lines of code to
create a locator and print some of its attribute values.
import maya.cmds as cmds;
loc1 = cmds.spaceLocator()[0];
cmds.setAttr(%s.translate%loc1, 1, 2, 3);
cmds.setAttr(%s.rotate%loc1, 45, 45, 45);
print(
translate: %s%
cmds.getAttr(%s.translate%loc1)
);
print(
rotate: %s%
cmds.getAttr(%s.rotate%loc1)
);
As you can see, the script you executed created a new locator, applied some
transformations with the setAttr command, and then queried and printed the values
of the locators translation and rotation using the getAttr command.
translate: [(1.0, 2.0, 3.0)]
rotate: [(45.0, 45.0, 45.0)]
6. Without deleting anything or changing scenes, now execute the following code,
which prints some information using the ar_transform command.
print(
translate: %s%
cmds.ar_transform(
loc1, translation=True
)
);
print(
rotate (xyz): %s%
cmds.ar_transform(
loc1, rotation=True
)
);
print(
rotate (zyx): %s%
cmds.ar_transform(
loc1, rotation=True,
rotateOrder=zyx
)
);
In this case, the ar_transform command allows us to query transformation
information similar to using the getAttr command. However, the result is returned as
a list instead of as a tuple inside of a list, and the command also allows us to manually
specify a custom Euler rotation order for the result as opposed to only getting
information using the transform nodes rotateOrder attribute.
translate: [1.0, 2.0, 3.0]
rotate (xyz): [45.0, 45.0, 45.0]
rotate
(zyx):
[16.324949936895237,
16.324949936895237]
58.600285190080967,
7. Staying in the same scene, execute the following short script. This script will
create two additional locators, one parented to the other, and will print both worldspace and local-space information for the child.
loc2 = cmds.spaceLocator()[0];
cmds.setAttr(%s.translate%loc2, 2, 2, 2);
cmds.setAttr(%s.rotate%loc2, 45, 0, 0);
loc3 = cmds.spaceLocator()[0];
cmds.setAttr(%s.translate%loc3, 1, 1, 1);
cmds.setAttr(%s.rotate%loc3, -45, 0, 0);
cmds.parent(loc2, loc3);
print(
local translate: %s%
cmds.ar_transform(loc2, t=True)
);
print(
world translate: %s%
cmds.ar_transform(loc2, t=True, ws=True)
);
print(
local rotate: %s%
cmds.ar_transform(loc2, r=True)
);
print(
world rotate: %s%
cmds.ar_transform(loc2, r=True, ws=True)
);
As you can see, the ar_transform command returns local-space values by
default (just like the getAttr command), but also allows you to optionally specify that
In the following pages, we will examine the source code for the ar_transform
command to discuss how to implement custom syntax as well as how to use some
basic API classes for working with transform nodes to implement these command
features.
As we will see later in the command, this mapping allows users to supply either a
string or a number as an argument for the rotateOrder flag. We have chosen to create
this mapping because, as discussed in Chapter 2, Maya represents rotation order
differently in different places. For example, using the getAttr or setAttr command
to query or set a rotation order uses an integer in the range of 0 to 5 corresponding to
an enumerator value, while the joint and xform commands use a three-letter
sequence corresponding to the enumerator labels. Because we cannot be certain how
or where users might use this command in another script, we want them to be able to
supply this argument with either type of value to play nicely with other commands
and nodes in Maya.
Class Definition
The next portion of the plug-in defines the command class, AR_TransformCmd,
which of course inherits from MPxCommand. In addition to a string specifying the
commands name, there are five pairs of class attribute strings corresponding to short
and long versions of flag names.
kPluginCmdName = ar_transform;
kWorldSpaceFlag = -ws;
kWorldSpaceLongFlag = -worldSpace;
kTranslationFlag = -t;
kTranslationLongFlag = -translation;
kRotationFlag = -r;
kRotationLongFlag = -rotation;
kRotateOrderFlag = -ro;
kRotateOrderLongFlag = -rotateOrder;
kQuaternionFlag = -rq;
kQuaternionLongFlag = -rotationQuaternion;
While the naming convention and the verbosity of the individual flags are up to
the plug-in developer, the important point is that each flag has two forms. All of the
commands in the cmds module follow this pattern as well, as you have seen
throughout this book. Although it is not necessary to prefix each flag with the hyphen
symbol, we have chosen to do so to reflect what you will find throughout the devkit
examples. Whether or not a hyphen is included at the beginning of a flag, MEL users
will still have to prefix each flag with a hyphen when invoking it in Maya, while
Python users will not.
In the __init__() method, in addition to calling __init__() on the base class, we
also initialize various data attributes.
def __init__(self):
ommpx.MPxCommand.__init__(self);
self.__transformPath = om.MDagPath();
self.__transformFn = om.MFnTransform();
self.__space = om.MSpace.kTransform;
self.__rotateOrder = om.MEulerRotation.kXYZ;
self.__translationArg = False;
self.__rotationArg = False;
self.__quaternionArg = False;
The first two data attributes, __transformPath
Syntax Creator
At this point, we will briefly skip ahead, saving an explanation of the doIt() method
for last. After the definition for the cmdCreator() method, which is identical to that
in the previous example, is a definition for a new class method called
syntaxCreator(). This method creates and returns an MSyntax object with
information about all of the commands flags, and serves two key purposes. First, it
makes the commands syntax known to Maya so that the help command can display
it. Second, and most important, it allows the MArgParser and MArgDatabase
classes to be used to simplify parsing of the commands arguments. Although it is not
strictly necessary, using a syntax creator method in conjunction with one of these
classes is generally advisable.
@classmethod
def syntaxCreator(cls):
syntax = om.MSyntax();
#
return syntax;
Flag Arguments
After the MSyntax object is defined in this method, there is a call to addFlag() for
each flag pair. Looking at its definition in the API documentation, you can see that the
first two arguments for this method are the shortName and longName for the flag.
Immediately following these parameters is an enumerated type, MArgType, which
describes the argument type the flag requires.
syntax.addFlag(
cls.kWorldSpaceFlag,
cls.kWorldSpaceLongFlag,
om.MSyntax.kNoArg
);
syntax.addFlag(
cls.kTranslationFlag,
cls.kTranslationLongFlag,
om.MSyntax.kNoArg
);
syntax.addFlag(
cls.kRotationFlag,
cls.kRotationLongFlag,
om.MSyntax.kNoArg
);
syntax.addFlag(
cls.kRotateOrderFlag,
cls.kRotateOrderLongFlag,
om.MSyntax.kString
);
syntax.addFlag(
cls.kQuaternionFlag,
cls.kQuaternionLongFlag,
om.MSyntax.kNoArg
);
A flag can support multiple arguments with varying types. The purpose of
specifying the argument type is that Mayas Command Engine can automatically
provide users with intelligent feedback and error-trapping if they supply the wrong
kind of argument, all without any extra effort on the developers part.
The first flag in this example allows users to specify that they would like the
command to return the results in world space. If this flag is not set, the command
presumes the user wants local-space information, in which case it will generally
behave like the getAttr command. This flag has specified a type of kNoArg, which
means that the flag only has to be present when the command is called and requires
no argument to follow. This pattern prevents MEL users from having to supply a
value after the flag, as in the following line.
ar_transform -ws;
However, Python users must still set this flag with a value of True.
cmds.ar_transform(ws=True);
Of the remaining flags, three follow the same pattern, including one to request
translation, one to request Euler rotation, and one to request quaternion rotation.
However, there is a flag with a type of kString that allows users to specify a rotation
order in which they would like the Euler angles to be given. Using the dictionary we
defined at the outset of the plug-in, we can then map either a three-letter sequence or a
string containing a number from 0 to 5 to a rotation order in the API. We will see how
this process works when we examine the doIt() method.
Command Objects
The final two items before the syntaxCreator() class method returns concern how
the command should deal with objects. In addition to supporting flag arguments (as
keyword arguments in Python), many commands support providing an object or
series of objects (as a variable-length argument list in Python). Typically, the Maya
paradigm is that such objects are the ones on which the command is operating. In our
case, the object is the transform node for which the command will print information.
syntax.useSelectionAsDefault(True);
passed in. The purpose of this call is simply to mimic the behavior of a variety of
other Maya commands. If users have selected objects when the command is invoked,
then this selection should be considered as the object argument if no selection list is
manually specified. To take an example, users could select an object (e.g., locator1)
and call the command by itself.
cmds.ar_transform();
Users could also call the command with an explicit selection list and would get
the same result.
cmds.ar_transform(locator1);
The final call in the syntax creator is to setObjectType(). The first argument is
an enumerated type, MObjectFormat, while the second is an integer specifying the
minimum number of objects required to invoke the command, and the third is an
integer specifying a maximum. Because our command requires exactly one object, the
Command Engine will prevent its execution if no object or more than one object is
specified.
syntax.setObjectType(om.MSyntax.kSelectionList, 1, 1);
Initialization of Syntax
The final new concept worth discussing before investigating the doIt() method is that
the initializePlugin() definition is also slightly different in this command. Specifically,
the call to registerCommand() has one new argument to include the syntax creator
method.
def initializePlugin(obj):
plugin = ommpx.MFnPlugin(
obj,
Adam Mechtley & Ryan Trowbridge
1.0, Any
);
try:
plugin.registerCommand(
AR_TransformCmd.kPluginCmdName,
AR_TransformCmd.cmdCreator,
AR_TransformCmd.syntaxCreator
);
except:
raise Exception(
Failed to register command: %s%
AR_TransformCmd.kPluginCmdName
);
As you can see from the API documentation (and in light of the previous
example), this argument for the syntax creator method is optional; it only needs to be
included when the command contains support for syntax. The initializePlugin()
function is otherwise identical to that in the previous example. The
uninitializePlugin() function, on the other hand, is sufficiently identical to that in the
AR_HelloWorld.py example, and so merits no individual discussion.
doIt()
Having examined the class definition and the commands custom syntax, we can turn
to the doIt() method. The first step in the doIt() method is to parse the arguments.
def doIt(self, args):
try: argData = om.MArgDatabase(self.syntax(), args);
except: pass;
else:
#
We can simply put a pass statement in the except clause, as Mayas Command
Engine will properly trap errors if it fails to load the argument data for any reason
(e.g., if an argument is invalid or an incorrect type, if the flag/argument/object order is
incorrect, and so on).3 As you can see, the arguments are parsed by creating an
MArgDatabase object rather than calling methods on the MArgList object (args)
itself. As noted in Chapter 9, we must use an MArgDatabase object to find out
which arguments have been entered. Although, strictly speaking, an MArgList object
could be used for commands without flag arguments, commands that use flag
arguments will only work in the cmds module if they use MArgParser or
MArgDatabase to parse the flags. We want to again emphasize that this restriction
applies equally to commands written in C++. Moreover, parsing an MArgList
manually would forego the convenience of automatic argument validation. In short,
you should generally avoid manual argument processing with MArgList.
The remainder of the commands doIt() method is wrapped inside an else clause
following this test.
Object Validation
The next part of the script validates the type of the incoming object argument. The
first few steps of this process create a new selection list object, store the incoming
object argument in the selection list, and then create a selection iterator with a filter
type of kTransform. Using this filter, the iterator will ignore objects in the selection
list that are not transform nodes.
sList = om.MSelectionList();
argData.getObjects(sList);
iter = om.MItSelectionList(sList, om.MFn.kTransform);
if (iter.isDone() or
not iter.itemType==
om.MItSelectionList.kDagSelectionItem):
selectionStrings = [];
sList.getSelectionStrings(selectionStrings);
raise Exception(
%s is not a valid transform node.%
selectionStrings[0]
);
The reason we must initialize the function set with an MDagPath as opposed to
an MObject is because we need to support querying transform data in world space.
Without a DAG path, a particular DAG node could have multiple different worldspace positions if it were instanced.4
Storing Flag Arguments
Once the object has been validated, the command performs a series of tests to set its
data attributes using any remaining arguments. The first of these tests simply changes
t h e __space data attribute to MSpace.kWorld if the worldSpace flag has been
supplied.
if argData.isFlagSet(AR_TransformCmd.kWorldSpaceFlag):
self.__space = om.MSpace.kWorld;
As you can see, we only need to test the short form of the flag. The
MArgDatabase object, argData, knows how to map the long forms accordingly,
since we supplied the MSyntax object generated from the syntax creator in its
initialization.
The next set of tests determines what rotation order to use for Euler angles.
rotation = om.MEulerRotation();
self.__transformFn.getRotation(rotation);
self.__rotateOrder = rotation.order;
if argData.isFlagSet(AR_TransformCmd.kRotateOrderFlag):
rotateOrderStr = argData.flagArgumentString(
AR_TransformCmd.kRotateOrderFlag, 0
);
try:
self.__rotateOrder = kRotateOrderMapping[
rotateOrderStr.lower()
];
except:
self.displayWarning(
%s is not a valid rotate order. Rotate order of %s is being used
instead.%(
rotateOrderStr,
self.__transformPath.partialPathName()
)
);
By default, the command will use the rotation order of the supplied object as its
__rotateOrder value.5 If the rotateOrder flag is set, however, then the command
attempts to locate a rotation order value in the kRotateOrderMapping dictionary that
corresponds to the users input. Because we call the lower() method on the argument
string, users can provide input as a sequence of all uppercase or all lowercase letters
and still have the command accept their input. If no key is found in the dictionary that
matches their input, however, a warning message is printed to the console (using an
inherited method) to notify them that the objects rotation order is being used instead.
Finally, the remaining Boolean data attributes are set based on whether or not
their respective flags are present, and then the doItQuery() method is called to output
the requested information.
self.__translationArg = argData.isFlagSet(
AR_TransformCmd.kTranslationFlag
);
self.__rotationArg = argData.isFlagSet(
AR_TransformCmd.kRotationFlag
);
self.__quaternionArg = argData.isFlagSet(
AR_TransformCmd.kQuaternionFlag
);
self.doItQuery();
doItQuery()
As you can see by looking at the API documentation, doItQuery() is not a method
that actually exists in the MPxCommand class. Strictly speaking, it is not necessary to
move this functionality off to another method. Querying could all be done inside
doIt() in this case. However, we prefer to separate it out into another method in this
example primarily because it helps make our expansion of this command in the next
example much smoother. You will also encounter this paradigm in other devkit
examples.
def doItQuery(self):
doubleArray = om.MScriptUtil();
if not (self.__translationArg^
self.__rotationArg^
self.__quaternionArg):
raise Exception(
Query mode only supports one property at a time.
);
elif self.__translationArg:
translation = self.__transformFn.getTranslation(
self.__space
);
doubleArray.createFromDouble(
translation.x,
translation.y,
translation.z
);
self.setResult(
om.MDoubleArray(
doubleArray.asDoublePtr(), 3
)
);
elif self.__rotationArg:
euler = om.MEulerRotation();
self.__transformFn.getRotation(euler);
if (not (euler.order==self.__rotateOrder) or
self.__space==om.MSpace.kWorld):
rotation = om.MQuaternion();
self.__transformFn.getRotation(
rotation,
self.__space
);
euler = rotation.asEulerRotation();
euler.reorderIt(self.__rotateOrder);
doubleArray.createFromDouble(
math.degrees(euler.x),
math.degrees(euler.y),
math.degrees(euler.z)
);
self.setResult(
om.MDoubleArray(
doubleArray.asDoublePtr(), 3
)
);
elif self.__quaternionArg:
rotation = om.MQuaternion();
self.__transformFn.getRotation(
rotation,
self.__space
);
doubleArray.createFromDouble(
rotation.x,
rotation.y,
rotation.z,
rotation.w
);
self.setResult(
om.MDoubleArray(
doubleArray.asDoublePtr(), 4
)
);
The basic idea is that if the requested rotation order is different from that on the
object, or if the objects world-space orientation is requested, then the objects
orientation is obtained as a quaternion, the quaternion is converted to Euler angles,
and the Euler angles are finally reordered for output. The first two steps are necessary
because there is no method in the MFnTransform class that allows us to obtain an
Euler rotation in a specific MSpace, so we would not be able to query the rotation in
world space.
Now that we have discussed the basics of implementing custom syntax, you can
create commands with a variety of complex and adjustable behaviors. However,
custom syntax still does not allow us to move substantially beyond ordinary Python
scripting unless we can also support undoing and redoing. In the next section, we will
build upon this example command by adding support for multiple command modes
(create, edit, query) as well as support for undoing and redoing.
these commands are added to the undo queue in the order they were invoked.
allows for an infinitely large undo queue, users will theoretically want to limit the
queue size to keep the memory overhead low when working in a scene for a
prolonged period of time. As indicated previously, you can also invoke the flushUndo
command to delete all commands in the queue.
Figure 10.5 An example command queue after two undos (left) and one redo (right).
In addition to the flushUndo command and a full command queue, one other
situation that will delete command objects is when the command history changes.
Because Maya can only support a single command history path, undoing to some
earlier state and then performing a command other than undo or redo will destroy all
of the command objects ahead of the last command to start a new sequence. Figure
10.6 illustrates this concept if we were to undo the scene three times and then use the
polyPoke command on the selected face (Edit Mesh Poke Face). As you can see
in the diagram, the polyExtrudeFace, convertSelectionToEdges, and bevelPolygon
commands are all destroyed when this new command history pathway begins, so we
are no longer able to redo to a scene state where they exist.
Figure 10.6 An example command queue after three undos (left) and a new operation (right).
Recall that we pointed out earlier that the command creator method for a class
derived from MPxCommand does not return the command object itself, but rather
an MPxPtr. It should hopefully be clear now that Maya needs to maintain commands
in memory to support undo and redo. Without this transfer of ownership, Python may
garbage collect the command even though the Command Engine still needs it, which
would result in a dangling pointer. Consequently, you would end up trying to undo to
a state where the command object no longer exists in the undo queue!
One final important note with respect to the undo queue is that it only exists in
the current Maya session. Creating a new file, loading an existing file, or quitting the
application will all flush the undo queue. As such, there will be no command objects
in the queue when users open an existing file later.
queue.
These classes effectively allow developers to accumulate a series of operations on
an object and then simply call that objects doIt() method upon execution.
Correspondingly, there is also an undoIt() method for such objects, which can be
called from within your commands undoIt() method to effortlessly undo the
commands executed by the MDGModifier object. That being said, the best approach
when designing a custom command is to rely on these classes wherever possible,
inserting other manual API functionality only where necessary, and preferably only in
situations where it is not important that it be manually undone.7
Command Modes
Another concept related to undoing and redoing is support for multiple command
modes. As we noted in Chapter 1, many commands support the invocation of
different behavior by supplying flags to specify the mode in which the command is
operating (e.g., q/query for query mode and e/edit for edit mode). However, it is
certainly possible for a command to exhibit the behavior of any particular mode
without requiring users to supply one of the flags.
In our previous example, for instance, the ar_transform command acted like a
command in query mode, though we did not require the query flag. Moreover,
developers can implement these flags manually to do things other than specify a
command mode. For instance, you could implement a flag q that corresponds to a flag
for a quaternion if it makes sense in your command. In general, however, it is bad
practice to deviate from these conventions, as most users will reasonably expect that
these flags be reserved for command modes.
Maya has some built-in functionality for multiple command modes. This
functionality comes primarily in the form of MSyntax.enableEdit() and
MSyntax.enableQuery(),
as
well
as MArgDatabase.isEdit()
and
MArgDatabase.isQuery().
The primary advantage to using these methods is that they provide shortcuts for
supporting these conventional flags. The MSyntax methods enableEdit() and
enableQuery() automatically add the appropriate flags for their respective modes to
the syntax definition. Additionally, the MArgDatabase methods isEdit() and
isQuery() act as shortcuts for testing if the respective flags have been set. However,
there are other advantages to using these methods. Consider the following example,
which creates a sphere, changes its radius to 5, and then prints the queried radius
value.
import maya.cmds as cmds;
sphere = cmds.polySphere()[1];
cmds.polySphere(sphere, edit=True, radius=5);
print cmds.polySphere(sphere, query=True, radius=True);
decimal argument along with the flag in query mode if you were implementing the
mode manually. Otherwise, the Command Engine would issue a syntax error for the
flag in question. This behavior would be confusing and highly unconventional.
While these methods are tremendously helpful, they do introduce two problems.
First, enableQuery() by itself precludes the possibility of including a flag that still
requires an argument even in query mode. As in our previous example, for instance,
you may want to specify the rotation order in which you are querying a rotation value.
Fortunately, the makeFlagQueryWithFullArgs() method on MSyntax objects allows
you to solve precisely this problem, as we will see in the next example.
The second problem, however, is that edit and query modes do not respect any
minimum or maximum object requirements in the syntax definition. In all of Mayas
built-in commands, edit and query modes predictably require exactly one object to be
specified. However, as of this writing (with Maya 2012 as the latest version),
developers unfortunately have to manually confirm an object list when the command
is operating in edit or query mode.
With all of these considerations in mind, we can now take a look at these features
in action. To do so, we will be examining an expanded version of the ar_transform
command (AR_TransformCmd2.py on the companion web site). This command
builds upon the previous example by adding support for multiple command modes as
well as undo and redo functionality. We will first look at a brief demonstration of the
new version of this command and then examine its source code.
1. If you still have the AR_TransformCmd1.py plug-in loaded, then unload it. The
command has the same name in both plug-ins, so the old plug-in must be unloaded to
avoid a conflict.
2. Download the AR_TransformCmd2.py plug-in from the companion web site and
load it into Maya using either the Plug-in Manager or the loadPlugin command (refer
to the previous example if you require more detailed instructions).
3. Execute the following code in a Python tab in the Script Editor, which uses the
ar_transform command to create a new locator at position [0, 1, 0] and print its
translation using query mode.
loc = cmds.ar_transform(t=[0,1,0]);
print(cmds.ar_transform(loc, q=True, t=True));
As you can see, the ar_transform command now operates
in create mode by
default, which allows you to create a new locator with any specified default
transformations applied. Moreover, specifying the q flag puts the command in query
mode, which produces the same behavior as the previous example.
4. Execute the following code to edit the translation and rotation of the newly
created locator and then print its translation ([2, 1, 0]) and rotation ([45, 0, 45])
values.
cmds.ar_transform(loc, e=True, t=[2,-1,0], r=[45, 0, 45]);
print(
Translation: %s%
cmds.ar_transform(loc, q=True, t=True)
);
print(
Rotation: %s%
cmds.ar_transform(loc, q=True, r=True)
);
Using the e flag, you can edit multiple different transformation properties
at once.
Now that you have seen the basic functionality additions in this version of the
command, we can step through its source code.
Syntax Creator
The command begins, just like the previous example, by importing a series of
modules and defining a dictionary for mapping rotation arguments. Because this part
of the command is identical to the previous example, we will skip ahead to the syntax
creator.
The first difference you will see is that we make the required calls to
enableQuery() and enableEdit() to add the two additional command modes.
syntax.enableQuery();
syntax.enableEdit();
The
second
syntax.addFlag(
cls.kQuaternionFlag,
cls.kQuaternionLongFlag,
om.MSyntax.kDouble,
om.MSyntax.kDouble,
om.MSyntax.kDouble,
om.MSyntax.kDouble
);
Finally, we have removed minimum and maximum argument values from the call
to setObjectType(). In create mode, the command still needs to work whether or not
the user has any objects selected. Moreover, as indicated previously, these arguments
have no effect on query and edit modes, and so the object validation now exists inside
the doIt() method, which we will examine shortly.
syntax.useSelectionAsDefault(True);
syntax.setObjectType(om.MSyntax.kSelectionList);
__init__() Method
The initializePlugin() and uninitializePlugin() functions are identical to the previous
example, so we can also skip them. The next place in this version of the plug-in with
some small differences worth noting is the __init__() method.
def __init__(self):
ommpx.MPxCommand.__init__(self)
self.__isQueryUsed = True;
self.__isEditUsed = False;
self.__transformPath = om.MDagPath();
self.__transformFn = om.MFnTransform();
self.__matrix = om.MTransformationMatrix();
self.__space = om.MSpace.kTransform;
self.__rotateOrder = om.MEulerRotation.kXYZ;
self.__translationArg = False;
self.__rotationArg = False;
self.__quaternionArg = False;
self.__newLocator = om.MObject();
self.__dagModify = None;
For the most part, this method looks very similar to the first version of the
command. We have added two Boolean data attributes to designate the mode in which
the command is operating. Their primary purpose is to keep track of the mode in
which the command is operating so the command knows what to do in the doIt() and
redoIt() methods, as well as whether or not the command should be undoable.
Initializing for isUndoable()
We have chosen to initialize the __isQueryUsed variable to True so that, if anything
should fail in the argument parsing process, the command will not be added to the
queue if it prematurely fails. Although the mechanics will only be entirely clear once
we have looked at the doIt() method, it is worthwhile to briefly refer to the
isUndoable() method.
def isUndoable(self):
return not self.__isQueryUsed;
As you can see, the isUndoable() method will only return True if the command
is not in query mode (__isQueryUsed is False). Therefore, by initializing
__isQueryUsed to True, we ensure that the command will not be added to the queue
until it has reached a point that it will begin operating on the scene and we determine
the mode in which the command is operating. You can refer to the section discussing
the doIt() method to see where these steps happen.
doIt()
At this point, we can now begin to discuss the contents of the doIt() method. The first
step of the doIt() method, constructing an MArgDatabase object inside of a
try-except clause, is identical to that in the previous example, so we will skip down
into its else block, where the first step is object validation.
Object Validation
sList = om.MSelectionList();
argData.getObjects(sList);
if (argData.isEdit() or argData.isQuery()):
if sList.length() is not 1:
raise Exception(This command requires exactly 1 argument
specified or selected; found %i.%sList.length());
iter = om.MItSelectionList(sList, om.MFn.kTransform);
if (iter.isDone() or
not iter.itemType()==
om.MItSelectionList.kDagSelectionItem):
selectionStrings = [];
sList.getSelectionStrings(selectionStrings);
raise Exception(
%s is not a valid transform node.%
selectionStrings[0]
);
iter.getDagPath(self.__transformPath);
self.__transformFn.setObject(self.__transformPath);
self.__matrix = self.__transformFn.transformation();
to
be
Because the command does not impose any particular requirements in create
mode, we first test to see if the command is in edit or query mode, where the
command requires that exactly one object be selected. Once we have verified that a
single object is selected, the object validation proceeds identically to that in the
previous example. Finally, the selected objects transformation matrix is stored in the
__matrix data attribute, so that we can revert to it if needed when undoing in edit
mode.
The next block of code, which checks for the worldSpace flag and parses the
argument for the rotateOrder flag, is not appreciably different from the previous
example,9 so we will skip ahead to the if-else clause that determines what action to
take based on the commands mode.
Selecting Query Mode
In Mayas built-in commands, query mode takes precedence if more than one mode is
specified. We have reflected this pattern in our example as well.
if argData.isQuery():
self.__translationArg = argData.isFlagSet(
AR_TransformCmd.kTranslationFlag
);
self.__rotationArg = argData.isFlagSet(
AR_TransformCmd.kRotationFlag
);
self.__quaternionArg = argData.isFlagSet(
AR_TransformCmd.kQuaternionFlag
);
self.doItQuery();
As you can see, if the command is in query mode, it behaves exactly the same as
the previous example (moreover, the contents of the doItQuery() method are also
identical, and so merit no further discussion).
Selecting Edit or Create Mode
If the command is not in query mode, then we need to set up the commands data
attributes using the commands flag arguments as needed. The first step is to search
for any provided flags to create appropriate objects for capturing their argument
values (an MVector for translation, MEulerRotation for rotation, and
MQuaternion for rotationQuaternion). Because Python supports dynamic typing,
we do not need separate data attributes to store these objects even though they no
longer hold Boolean values.
if argData.isFlagSet(AR_TransformCmd.kTranslationFlag):
self.__translationArg = om.MVector(
argData.flagArgumentDouble(
AR_TransformCmd.kTranslationFlag, 0
),
argData.flagArgumentDouble(
AR_TransformCmd.kTranslationFlag, 1
),
argData.flagArgumentDouble(
AR_TransformCmd.kTranslationFlag, 2
)
);
if argData.isFlagSet(AR_TransformCmd.kRotationFlag):
self.__rotationArg = om.MEulerRotation(
math.radians(
argData.flagArgumentDouble(
AR_TransformCmd.kRotationFlag, 0
)
),
math.radians(
argData.flagArgumentDouble(
AR_TransformCmd.kRotationFlag, 1
)
),
math.radians(
argData.flagArgumentDouble(
AR_TransformCmd.kRotationFlag, 2
)
)
);
self.__rotationArg.order = self.__rotateOrder;
if argData.isFlagSet(AR_TransformCmd.kQuaternionFlag):
self.__quaternionArg = om.MQuaternion(
argData.flagArgumentDouble(
AR_TransformCmd.kQuaternionFlag, 0
),
argData.flagArgumentDouble(
AR_TransformCmd.kQuaternionFlag, 1
),
argData.flagArgumentDouble(
AR_TransformCmd.kQuaternionFlag, 2
),
argData.flagArgumentDouble(
AR_TransformCmd.kQuaternionFlag, 3
)
);
After storing these values, if the command is in create mode, then the
__dagModify data attribute is created and two operations are added to it.
if not argData.isEdit():
self.__dagModify = om.MDagModifier();
self.__newLocator = self.__dagModify.createNode(
locator
);
self.__dagModify.renameNode(
self.__newLocator, locator1
);
The first operation requests that a new locator be created, and a pointer to it is
stored in the __newLocator data attribute. The second operation requests a rename of
the new object with a number suffix. Note that we do not actually call the doIt()
method on the __dagModify data attribute here, because we want to simplify support
for redo. We will show how this object works when we inspect the commands
redoIt() method.
Finally, we set the values for __isEditUsed and __isQueryUsed and then
The reason for setting the values of these data attributes last is that, if there
happens to be some sort of unexpected failure at an earlier part in the doIt() method,
we do not want the command to be added to the undo queue. This pattern ensures that
the command is not added to the undo queue until right before it actually does
anything to modify the scene. It is especially important to follow this pattern when
you are designing and debugging your own commands because it prevents you from
having to flush the undo queue before reloading your plug-in if your command
produces some kind of problem before it actually does anything to the scene.
redoIt()
The first step in the redoIt() method is to determine if the command is operating in
create mode, and to create a new locator if it is.
Creating New Nodes
As we pointed out earlier, we added the node creation and renaming requests in the
doIt() method, before entering redoIt(). The reason for issuing the renameNode()
request was to ensure that the name of the new node would be unique, as Maya will
automatically increment nonunique names. If we issued this request inside the
redoIt() method, then every time we issued a redo, the objects name would
continually increment, which would cause problems when trying to access it by name
later. As such, the first step in create mode is to simply call doIt() on __dagModify.
if not self.__isEditUsed:
self.__dagModify.doIt();
om.MFnDagNode(self.__newLocator).getPath(
self.__transformPath
);
self.__transformFn.setObject(self.__transformPath);
self.__dagModify.commandToExecute(
'select %s'%
self.__transformPath.fullPathName()
);
self.__dagModify.doIt();
self.setResult(self.__transformPath.partialPathName());
else: self.setResult(Values edited.);
The reason for issuing this call now is to update the intermediate state of the DG
to ensure we can get a current, conflict-resolved name for the new locator. Thereafter,
since we are guaranteed the new object will be unique, we can store its DAG path in
the __transformPath data attribute and point the __transformFn function set to it. It
is critical that we reinitialize the function set using an MDagPath, as we may need to
be able to use world space to set transformation values (later), which you cannot do if
you initialize with an MObject.
Following Mayas convention, the command then calls commandToExecute()
o n __dagModify to add another operation to the MDagModifier object to select the
new locator. Because we previously called doIt() on the __dagModify data attribute,
we know that the name of our new object will be unique and up-to-date. If we had
failed to do so, it would be possible that we might accidentally select the wrong object
in the scene, as Maya would have renamed our new locator if a conflict were found.10
It is also important to stress here that Maya expects any strings passed to the
commandToExecute() method to be in MEL syntax. If you are so inclined, you
could work around this limitation by calling the MEL python command with a string
of Python code to execute, while at the same time being cautious that these commands
will be executed in the namespace of the __main__ module.
After the new locator has been created, we set the result of the command to
return the locators name, again to adhere to Mayas convention. On the other hand, if
the command is operating in edit mode, then it also follows Maya convention by
setting the result to a string to inform the user that values have been edited.
Editing the Transform Node
At this point, we can proceed to apply any transformations necessary based on the
users input.
if self.__translationArg:
self.__transformFn.setTranslation(
self.__translationArg, self.__space
);
if self.__rotationArg:
self.__transformFn.setRotation(
self.__rotationArg.asQuaternion(),
self.__space
);
elif self.__quaternionArg:
self.__transformFn.setRotation(
self.__quaternionArg,
self.__space
);
Unlike query mode, edit mode supports multiple flags simultaneously in the
Maya built-in commands. As such, we have followed this same pattern. The only
exception is that we have chosen to give Euler rotation precedence over quaternion
rotation (with the general expectation that most users would actually prefer Euler
rotation since quaternions are not otherwise regularly exposed through the Command
Engine, and can also be difficult to understand conceptually). You can also see that
when we set Euler rotation, we convert it to a quaternion. Just as there are no
prototypes of the getRotation() method that accept both an MEulerRotation and
MSpace argument, so, too, are there no such prototypes for setRotation(). Thus, the
object in question, whether it was newly created or supplied as an argument in edit
mode, has all of its transformations applied.
undoIt()
We can now conclude this example by examining the undoIt() method.
def undoIt(self):
self.__transformFn.set(self.__matrix)
if not self.__isEditUsed: self.__dagModify.undoIt()
Concluding Remarks
Equipped with a better understanding of custom syntax, undo and redo functionality,
and support for multiple command modes, you now have the ability to make a range
of more complicated tools. Commands not only give developers access to a means of
supporting undoing and redoing when using API methods, but also make custom
tools easily accessible to both MEL and Python. Because command plug-ins are added
to the Command Engine when they are loaded (and consequently to the cmds
module), they are easy to deploy and straightforward and familiar for users. Custom
commands are one of the most common types of plug-ins, and are a valuable means
for exploring a great deal of the Maya API.
Nevertheless, commands represent one of the most basic types of plug-ins that
developers can create, and, because Python supports object-oriented programming
already, their functionality is not dramatically different from ordinary Python scripts
excluding their ability to support undo alongside API operations that act upon the
scene. However, Maya also supports many other types of plug-ins, including custom
nodes, tools, and contexts. Some of these topics will become our focus in the
remaining chapters of this book as we explore further uses of the Maya API.
If your command supports undo and instances of it are still in the undo queue, you
will also have to call the flushUndo command before reloading a plug-in. Maya
should warn you about unloading a plug-in that still exists in another scene state,
but remember to generally flush the undo queue before attempting to unload and
reload.
Although Maya generally handles a great deal of syntax validation, there are a
couple of notes and fringe cases worth mentioning. As of this writing (i.e., up
through Maya 2012), Maya only prints descriptive error messages to the Command
Line when the command is invoked from MEL. If invalid syntax is found when
invoking the command from Python, the command will properly trap the error, but
will not provide the user with a detailed message explaining what is wrong with the
syntax. The other important note is that Mayas syntax validation will not handle
errors with object parsing when using the APIs built-in support for edit and query
modes. As discussed in the final example in this chapter, adding support for these
modes requires that the object list be manually validated.
See note 2. A pass is preferable to a raise in this situation, since the exception
raised can be extremely verbose and not very descriptive (e.g., (kFailure):
Unexpected Internal Failure).
4
Consult the detailed description for the MSpace and MFnDagNode classes in the
API documentation for more information.
Executing any script (either Python or MEL) that uses commands essentially opens
a chunk in the undo queue into which all of the undoable commands from the
script are added. When the script completes, the chunk is closed. If the chunk
contains no undoable commands, then it is discarded. If it contains undoable
commands, then the chunk is added to the queue as though it were a single
command. Using the undoInfo command with the printQueue flag reveals that
several lines of Python (or MEL) are all lumped into a single item in the queue. On
the other hand, a script using no undoable commands (e.g., a Python script using
only API code) is not added to the queue.
Consider our final example in this chapter, which creates a new transform node
and applies a series of transformations one after another in create mode. In such a
case, it is useful to create the node using an MDagModifier object and then simply
transform it directly using an MFnTransform function set. When the operation is
undone, it is not important that each of the transformations be sequentially undone
in reverse, as the undo would destroy the newly created object anyway when
undoIt() is called on the MDagModifier object.
Consider the following comparison between Python and MEL. In Python, any
nonzero argument after the radius flag would evaluate to True and allow the
command to proceed (e.g., cmds.polySphere(polySphere1, q=True, r=5)).
However, trying to issue a similar command in MEL ( polySphere -q -r 5
polySphere1) could result in a syntax error. (On the other hand, reordering the
flags could allow a MEL invocation, such as polySphere -r 5 -q polySphere1, to
also proceed.)
10
Chapter 11
Data Flow in Maya
Chapter Outline
Dependency Graph 328
Dependency Nodes 330
Attributes 330
Plugs 330
Data Blocks 331
compute() 331
Data Handles 331
Attributes and Data Flow 332
Connections 333
Lazy Evaluation and PushPull 334
Triggering DG Evaluation 336
Debugging the Dependency Graph 336
Heat Map Display 337
The dgTimer Command 338
Directed Acyclic Graph 339
DAG Paths and Instancing 344
The Underworld 347
Concluding Remarks 350
By the end of this chapter, you will be able to:
Define the principal components of the Maya Dependency Graph.
Identify the basic parts of a DG node.
Distinguish attributes, plugs, data blocks, and data handles.
Describe the pushpull mechanism by which the DG updates.
This chapter defines the principal components of the Maya Dependency Graph
(DG) and identifies the basic parts of a DG node. It distinguishes among attributes,
plugs, data blocks, and data handles. Performance problems in the DG are also
described and DAG nodes and DG nodes are compared and contrasted.
Keywords
Dependency Graph (DG), dependency nodes, Directed Acyclic Graph (DAG),
plugs, transform node, shape node, underworld
While custom scripts and even commands are powerful tools for improving a
users interaction with Maya, they barely graze the surface of what is possible with
Python in Maya. Maya offers many different ways to create all-new functionality with
the API, including the ability to create custom dependency nodes, contexts, locators,
and so on.
Before we jump right into a topic like creating custom nodes, however, it is
essential that we review data flow in the Dependency Graph (DG) in greater detail.
Having a solid understanding of how the Dependency Graph works not only enhances
your ability to create some advanced plug-ins, but also allows you to create your own,
more complex behaviors by taking advantage of its underlying mechanisms.
In this chapter, we describe the components of the Dependency Graph in greater
detail, as well as the different parts of dependency nodes. Moreover, we will discuss
how the Dependency Graph operates and how data move throughout the scenes node
networks. Finally, we distinguish the Directed Acyclic Graph (DAG) and the
Dependency Graph.
Dependency Graph
At the heart of every Maya scene is the Dependency Graph. The DG represents all of
the data in a scene. Everything we have discussed up to this point has concerned using
a set of controlled interfaces to manipulate these data.
As we have noted, the DG is composed of two key components: nodes and
connections. Nodes contain the actual data in the scene as well as operations being
performed on the data, while connections establish relationships among the data in the
nodes. As we discussed earlier in this text, a simple operation such as creating a cube
actually creates a series of dependency nodes that are connected in a systematic way.
You can visualize these nodes and connections, including the direction of the
connections, in the Hypergraph when you select the input and output connections
button (Figure 11.1).
manipulating data. Users must issue a command of some kind (e.g., createNode,
multMatrix, polyCube, etc.) to create new dependency nodes. Commands such as
setAttr, connectAttr, and disconnectAttr allow users to change data or alter
connections. The delete command allows users to destroy a node (and thus its data).
The important consequence of this structure in the context of the DG is that new
data and relationships can be easily inserted, removed, or restructured without
sacrificing the integrity of the scene. Each node is simply looking for some type of
data, performs an operation, and then outputs some type of data. Thus, the input data
for a node can be a simple static value (e.g., setting the height of a cube to a value of
5), or it can be computed using a series of connections (e.g., the height of the cube
could be connected to the rotation of a dial).
This design approach is what makes implementing new functionality for Maya so
easy. You can create one or more custom nodes to use as building blocks to transform
data for other purposes. In fact, you can safely make certain changes to custom nodes
that will be reflected the next time a user opens a scene containing them, as long as
their existing interfaces all remain intact.
At this point, it is worth examining these two componentsnodes and
connectionsin greater detail. We will now discuss the anatomy of a dependency
node and the method by which the DG evaluates connections, or passes data through
nodes.
Dependency Nodes
Dependency nodes are the building blocks of the DG. They are the primary
mechanism for storing and accessing data in a Maya scene. A series of important
questions naturally arises. How do nodes create data? How do they intercept and
transmit data? How do they operate on data? The answers to these questions introduce
us to the different parts of nodes. Although we will go into greater detail in Chapter
12, where we will discuss code for custom dependency nodes, it is worth
distinguishing some of these key parts now to clarify the nomenclature and avoid
confusion later.
At the highest level, the structure of a dependency node is defined by its
attributes, and every node contains a compute() method (Figure 11.2). Custom
dependency nodes in the API will inherit from the MPxNode class, which we discuss
further in Chapter 12.
Attributes
While we have used the term attribute in a number of contexts throughout this text, it
has a very particular meaning from the standpoint of an API developer. Attributes on
a node define the name and type of its data members. For instance, on a polyCube
node, there is a height attribute, which describes a decimal number. Attributes may be
inputs, which describe different pieces of data the node needs to operate; they may be
outputs, which describe the data that the node produces; or they may serve both
functions. Attributes themselves do not store data, but rather specify data that a node
will store. The API provides access to attributes as MObject objects that can be
manipulated with the MFnAttribute and descendant classes.
Plugs
Inside of a compute() method, a node will often make use of data handles to obtain
data from the data block for incoming plugs and then again to set values in the data
block for outgoing plugs. While a plug allows you to access all aspects of some
attribute instance, a data handle only offers access to the attributes data. For instance,
you cannot use a data handle to lock an attribute or make connections. Data handles
are intrinsically linked to the data blocks from which they were created, and cease to
have meaning if the data block is destroyed. Plugs, on the other hand, are independent
of any data block, and will create a data block if required to access an attributes data.
Data handles are represented in the API in the MDataHandle and
MArrayDataHandle classes.
In spite of the limitations of data handles compared to plugs, data handles offer at
least two important advantages over plugs when used inside compute(). First,
because they are already linked to a data block and have fewer internal checks than a
plug, they can operate much faster than plugs. Second, data handles allow the retrieval
of a dirty attribute without forcing the DG to be recomputed, which enables the
possibility of even further internal optimization. The importance of this feature will be
clearer when we discuss connections and DG evaluation later in this chapter.
Attributes and Data Flow
While we have noted that attributes can be either inputs or outputs, Maya itself does
not inherently distinguish two different kinds of attributes: you create them in exactly
the same way. However, as you will see in Chapter 12, nodes internally define
attribute relationships that implicitly classify attributes as either inputs or outputs. You
accomplish this task by establishing relationships among attributes to designate that
certain attributes affect certain other attributes. In Figure 11.2, we have included
dotted lines passing through the compute() method to indicate that attributes A and B
affect the value of attribute C, and that they are used for something inside of the
compute() method.
The reason these relationships are important is that the DG in Maya uses what is
known as lazy evaluation, which we will soon be discussing in detail. In short, only
those values that are requested, as well as their dependencies, are actually
evaluated. Suppose, for instance, you have a custom node that is just a parametric
sphere, and that this custom node has input attributes for radius and spans as well as
an output attribute for volume (Figure 11.3). In this sort of node, the volume
attributes value would be affected by radius, but not by spans.
Connections
Having discussed the primary parts of dependency nodes, we can now turn our
attention back to the Dependency Graph itself to better understand connections. At
this point, you may have naturally concluded that the DG works almost like a visual
programming environment: Data are defined and created in nodes and then passed off
to other nodes to be altered or to create new data. This comparison is fairly apt, not
least because many Maya users in fact use the DG in this way. With the variety of
utility nodes available in Maya (e.g., condition, multiplyDivide, vectorProduct), it is
possible to create simple programs that procedurally control the animation or shading
of objects, for example.
However, this analogy introduces some complications. For instance, in contrast
to ordinary programming, there is not necessarily a defined entry point. A user could
change a value at any point in a node network, in which case the DG needs to know
when and how to reevaluate its networks. Constantly reevaluating everything would
be incredibly taxing and would likely cause Maya to operate at a snails pace in
complex scenes. Thus, it is important to understand how the DG manages data
updating, not least because it can allow some developers to creatively exploit different
aspects of the DG.
Lazy Evaluation and PushPull
As we indicated earlier, the Maya DG uses lazy evaluation to ensure it is doing the
minimum amount of work at any given time, which is especially important for large
or complex scenes. Specifically, it uses what is called a pushpull system to ensure
that only exactly that information that is required is actually piped through a node
network. In this respect, Maya manages DG evaluation entirely on its own, while
nodes only know to compute when they are asked to compute. Recall that nodes are
intended to be used as building blocks, and consequently know nothing about other
nodes outside of themselves. To see why the pushpull system is important, consider
the illustration depicted in Figure 11.4.
At this point, no further computation is done until the value of some downstream
plug is required. You can hopefully see how this can save huge amounts of
computation time. Suppose you now use the getAttr command to request the value
of the outgoing plug for node H. Node H first sees that its incoming plug is dirty, and
so requests new information from upstream, from node D. Node Ds incoming plug is
also dirty, and so it also requests new information from upstream. At this time, the
request for new data has reached clean data in node A, and thus the data are pulled
downstream to update D and then H (Figure 11.6). In this fashion, all of the other
nodes have been allowed to remain dormant since none of their data have been
requested.
Figure 11.6 Node H requests fresh data, which are pulled from A, through D (pull).
Understanding these concepts, you can hopefully see very quickly how important
it can be to ensure that you properly establish your attribute relationships when you
create your own nodes: You dont necessarily always want everything to happen each
time. On the other hand, if your nodes compute() methods do something interesting
that you also want to expose as an attribute, you can output all your data at once
instead of requiring that each attribute necessarily be requested individually. These
concepts will become clearer as we move into creating our own custom dependency
nodes, but the bottom line is that you can make your nodes behave quite efficiently if
you take the time to understand Mayas pushpull system and exploit its power.
Triggering DG Evaluation
Having discussed the mechanics of DG evaluation, it is important that we conclude
with a few points regarding when Maya actually updates the DG. As indicated up to
this point, requesting the value for an attribute, either via an upstream connection or
using a command such as getAttr, will trigger a DG update for any data
dependencies that the specific plug in question may have. Other actions that will
request a DG update include displaying a nodes attribute values in the Attribute
Editor or Channel Box. Finally, actions such as rendering or refreshing the scene (as
when scrubbing the time slider) will request a full reevaluation of the DG.
The DG will always behave in the same way, and is always governed by a
consistent set of rules, even if it does not appear to be in some circumstance. For
example, if you move an object of which the transformations are controlled by
animation curves, you will notice that redrawing the scene viewport, as when orbiting
the camera, does not cause them to snap back to place. This behavior is not an error
with the DG updating, but rather is a consequence of the fact that using the interactive
transformation tools sets all of the objects appropriate transformation plugs as clean,
since it is presumed that the users manipulation of the object represents the freshest
data. If you were to scrub the time slider, however, the change in the times value is
propagated downstream and the objects transformation plugs are marked as dirty.
Consequently, their values are reevaluated on the basis of their animation curves
output values at the new time.
5. With your sphere still selected, select Graph Input and Output Connections
in the Hypergraph window.
6. Deselect your sphere (Edit Deselect, or click in empty space in the
Hypergraph).
7. Open the Heat Map Display options window (Options Heat Map Display
in the Hypergraph window).
8. In the Heat Map Controls window, ensure that Compute is selected in the Metric
dropdown menu and that Self is selected in the Type dropdown menu. You should
see a Hypergraph such as that seen in Figure 11.7, albeit with color coding applied to
the nodes. If you do not, press the Redraw Hypergraph(s) button.
Draw represents the amount of time spent drawing the object in the viewport.
The measurement type can be either self, inclusive, or count.
Measuring using the self type simply reports the amount of time the individual
node spent.
The inclusive type totals all preceding time values for each node. Thus, its value
represents the total amount of time spent from the beginning of the graph update up to
and including the node in question.
The count type represents the number of times each particular operation is
applied on the node in question.
If you open the polyStat.txt file that was just written to your home folder, you
can see that the dgtimer command provides you with an incredibly rich data set,
which you could easily process using Pythons text reading and regular expressions
functionality if necessary.
By default, the text file contains a sorted list of all DG nodes in the scene sorted
in order of most intensive to least intensive. It suffices to say that it is worth reading
the detailed information for the dgtimer command in the Python Command
Reference, as we do not have the space to cover all of its features here. It can be a
great way to not only profile your own custom nodes, but also troubleshoot large and
complex scenes to locate problems.
At this point, there is nothing further to say about dependency nodes and DG
evaluation generally. We can thus turn our attention at last to a special class of
dependency nodes: those in the Directed Acyclic Graph.
3. With the newly created spheres still selected, open a Hypergraph window
displaying connections (Window Hypergraph: Connections). You should see a
Hypergraph window like that shown in Figure 11.8.
can clearly see that each shape is in fact a child of its respective transform node.
You should see the following result printed to the History Panel.
|pSphere1|pSphereShape1
As you can again see, the full DAG path for the first spheres shape node,
pSphereShape1, begins at the scene root, and its parent is its respective transform
node, pSphere1.
7. Enter the following code in a Python tab in the Script Editor. This code gets the
transformation matrix associated with the shape node and prints its position and
As we indicated previously, because shape nodes are part of the DAG, they too
technically have their own transformation matrices. As the previous code snippet has
demonstrated, this transformation matrix is identity, which, because the shape node is
the child of a transform node in the scene, means that the shape is aligned to the
position and orientation of its respective transform node in world space.
3. Execute the following code to create an instance of the sphere hierarchy. Your
scene should look something like that shown in Figure 11.11.
4. If you select the small sphere at the end of either hierarchy, you will see that the
small sphere also highlights in the other hierarchy. Enter the following line in a
Python tab in the Script Editor to transform the small sphere.
cmds.xform(sphere2[0], t=[0,2,0]);
Because each of these instances is referencing the same DAG hierarchy, adjusting
the local translation of the small sphere updates in both instances, while the root
object for each path serves to transform each instance of the hierarchy. If you scale,
translate, or rotate either of the large spheres, the other hierarchy is not affected since
the large sphere is the root for each instance path.
5. Open a Hypergraph panel showing the scene hierarchy (Window
Hypergraph: Hierarchy). You can see that there is a dotted line drawn from
pSphere3 to pSphere2, which represents that the children of pSphere3 are all
referencing the same DAG hierarchy as that under pSphere1. Furthermore, this
sharing of hierarchy also applies to shape nodes under each hierarchy.
6. Execute the following code in a Python tab in the Script Editor to transform the
vertex at the top of the large sphere.
shape = cmds.listRelatives(sphere1)[0];
cmds.moveVertexAlongDirection(
%s.vtx[381]%shape,
n=0.5
);
Your scene should look something like that shown in Figure 11.12. Remember
that the shape node of the large sphere is a child of its transform node, and it is
therefore also part of the instance hierarchy. Thus, any modifications made to the
large spheres vertices are reflected in both instances. Likewise, you would see the
same behavior if you attempted to transform the small spheres vertices.
The small spheres shape node (pSphereShape2) has two unique DAG paths to it
in this scene, because its transform node (pSphere2) has two different parents
(pSphere1 and pSphere3). You should see the following output from the previous
lines.
|pSphere1|pSphere2|pSphereShape2
|pSphere3|pSphere2|pSphereShape2
Although we did not explore the subject in detail in Chapter 10 concerning the
creation of commands, it is important to remember that it is possible for a single DAG
node to have multiple paths to it. To take one example, if you initialize an
MFnDagNode function set with an MObject rather than with an MDagPath, the
objects path is not yet known, and it could potentially have multiple paths. While the
getPath() method returns the first path that the function set finds for an object, you
can use the getAllPaths() method to return an MDagPathArray if it is important for
your tool to distinguish objects on the basis of their location in a hierarchy.
Considering what you know about the DAG, you can think of DAG nodes like
folders or files in a directory structure. Just as there can only be one folder or file with
a particular name in one directory path, so too can there be only one DAG node for a
particular path. Just as your operating system allows you to create symbolic links to
include a particular directory structure as a subdirectory anywhere else, so too does
instancing allow you to reference a single DAG hierarchy in multiple different places
without actually duplicating any data.
One final item that is worth noting is that instanced attributes are propagated
differently from ordinary ones. Attributes on instanced nodes do not make use of lazy
evaluation. They are purely pushed, and are thus handled outside the DGs standard
mechanisms.
The Underworld
The last concept worth briefly mentioning while on the topic of the DAG is the
underworld. The underworld refers to the set of DAG nodes of which the
transformations are not described in standard Cartesian coordinates (i.e., xyz) but in
the parametric space of some other object, such as a mesh or a curve.
You can imagine parametric space as another arbitrary coordinate system. One
example is UV space on a NURBS patch: A position along a NURBS surface can be
obtained parametrically by evaluating it in terms of a point in UV space that is
between 0 and 1 in two dimensions. When you define an objects position in this
fashion in Maya, the parametric coordinates are transformed back into standard
Cartesian coordinates to locate the object in 3D space. A brief example should help
illustrate this concept.
1. Open a new Maya scene.
2. Create a new NURBS plane with some deformation applied to it by entering the
following code in a Python tab in the Script Editor.
import maya.cmds as cmds;
plane = cmds.nurbsPlane(w=2, ax=[0,1,0]);
cmds.subdivDisplaySmoothness(s=3);
cvs = %s.cv[0:3][1:2]%plane[0];
cmds.move(0, 0.5, 0, cvs, r=True);
cvs = %s.cv[1:2][0:3]%plane[0];
cmds.move(0, -0.5, 0, cvs, r=True);
3. With the new plane still selected, select the menu option from the main
application window to make the surface live (Modify Make Live). Your plane
should now become deselected and have a dark-green wireframe.
4. Create a new EP curve along the surface of your NURBS plane. Select Create
EP Curve Tool and click out some points on your NURBS surface. When you are
done, you can return to the Select tool (by pressing Q if you have default hotkeys) to
exit the tool. You should now have a curve that is conformed to the surface of your
NURBS plane, as shown in Figure 11.13.
5. With your new curve still selected, enter the following code in a Python tab in the
Script Editor to see the path to the curve.
As you can see, the DAG path for the new curve is given using an arrow symbol
(->) rather than a standard vertical pipe (|).
[unurbsPlaneShape1->curve1]
This symbol indicates that the object is a child in the underworld of the
nurbsPlaneShape1 node rather than in the scenes standard Cartesian coordinate
space.
6. Disable the live mode by selecting Modify Make Not Live from the main
application menu.
7. Select your NURBS plane. You will see the curve highlighted as though it were a
child of the plane. Similarly, if you apply any transformation to the plane, such as
translation or rotation, the curve still follows the plane like a child.
8. Select the curve and press the UP Arrow Key. As you can see, you cannot
pickwalk up to the plane as in a standard hierarchy. Again, this limitation is a result of
the fact that the curve is not a child of the plane in the ordinary sense.
9. Open a new Hypergraph hierarchy window (Window Hypergraph:
Hierarchy). If you only see your nurbsPlane1 object, then navigate to the Options
menu in the Hypergraph window and enable shapes and underworld nodes (Options
Display Shape Nodes and Options Display Underworld Nodes). You
should see a Hypergraph display like that shown in Figure 11.14.
Figure 11.14 A curve on a NURBS surface shown in the Hypergraph hierarchy view.
There is a dotted line connecting the curve1 transform node to the
nurbsPlaneShape1 shape node. This indicates that the hierarchical relationship exists
in the underworld, in parametric space, rather than in Cartesian space. Another
example of a hierarchical relationship in the underworld is when constraining an
object to a path (Animation Motion Paths Attach to Motion Path in the
Animation menu set). Whereas a parametric coordinate on a NURBS surface is
defined using two parameters (U and V), a coordinate on a motion path is described
with a single parameter (U).
Concluding Remarks
At this point, we have covered all of the high-level concepts necessary to start using
the API in more advanced ways, which will allow us to move directly into the creation
of custom dependency nodes in Chapter 12. As you no doubt are aware by now,
Mayas DG is simultaneously a streamlined means for representing and conveying
data and a powerful and flexible tool for accomplishing advanced tasks. Taking the
time to understand the concepts presented in this chapter will allow you to more easily
work with advanced topics as well as troubleshoot further plug-ins you may create.
Note that not all attributes necessarily store data in the data block. Attributes that
are internal to the node, and attributes that do not cache their data (e.g., just pass
data through the node for computation) do not occupy space in the data block. See
Chapter 12 for more information on internal attributes and caching.
Chapter 12
Programming a Dependency Node
Chapter Outline
Anatomy of a Scripted Node 352
The ar_averageDoubles Node 352
Node Class Definition 354
Node Creator 356
Node Initializer 356
compute() 357
Best Practices 358
Initialization and Uninitialization 359
Attributes and Plugs 360
Attribute Properties 361
Readable, Writable, and Connectable 361
Storable Attributes and Default Values 361
Cached Attributes 363
Working with Arrays 363
Logical and Physical Indices 364
Implementing Plug Arrays 364
Implementing Array Plugs 369
Compound Attributes 370
Concluding Remarks 375
By the end of this chapter, you will be able to:
Describe the functional requirements of a dependency node.
Create your own custom dependency nodes.
Describe the key properties of node attributes.
Compare and contrast array plugs and plug arrays.
4. Execute the following lines of code to create two ar_averageDoubles nodes that
compute the average axis and height subdivision attributes of the highres and lowres
spheres and pipe the result into the midres sphere.
avgAxes = cmds.createNode(ar_averageDoubles);
cmds.connectAttr(
%s.sa%sphereHigh[1],
%s.input1%avgAxes
);
cmds.connectAttr(
%s.sa%sphereLow[1],
%s.input2%avgAxes
);
cmds.connectAttr(
%s.output%avgAxes,
%s.sa%sphereMid[1]
);
avgHgt = cmds.createNode(ar_averageDoubles);
cmds.connectAttr(
%s.sh%sphereHigh[1],
%s.input1%avgHgt
);
cmds.connectAttr(
%s.sh%sphereLow[1],
%s.input2%avgHgt
);
cmds.connectAttr(
%s.output%avgHgt,
%s.sh%sphereMid[1]
);
At this point, you can adjust the subdivision attributes (subdivisionsAxis and
subdivisionsHeight) of the two outer spheres (highres and lowres) and the
corresponding subdivision values of the middle sphere (midres) will interactively
update to be the arithmetic mean of the two extremes. Note that the
ar_averageDoubles node automatically works with the subdivision attributes even
though they are long integers rather than doubles. Maya can handle simple numeric
conversions like this one automatically.
As you may have guessed, the former is the type name of the node in Maya,
which you passed to the createNode command in the previous exercise (and that also
appeared in the Plug-in Manager information dialog). The latter, however, is an object
of type MTypeId.
While custom commands must simply have a unique name, custom nodes must
have both a unique name and a unique identifying integer, which is stored in an
MTypeId object. When Maya loads a scene from disk, it needs a mechanism to
reconstruct the data in the scene (nodes, attribute values, and connections). Although
Maya ASCII (.ma) files store a set of MEL commands, and thus require only the name
of the node, Maya binary (.mb) files store nodes by MTypeId only. If a nodes name
and MTypeId are not unique, Maya could easily have problems loading a scene if it
expects the node to have certain attributes that do not exist.
Consequently, plug-in developers must ensure their custom nodes have truly
unique type identifiers. If another plug-in developer assigned the same identifier as
you, there would be conflicts when loading scenes based on which plug-in happened
to be loaded. To solve this problem, Autodesk will distribute a range of 256 values to
developers upon request. You can contact the Autodesk Developer Center online to
request a range of node identifiers, which you should do right away to avoid
problems!
As you see here, we have passed the MTypeId constructor one unsigned, 32-bit
integer argument (which we specify as hexadecimal). Autodesk reserves the range of
identifiers from 0x00000000 to 0x0007FFFF for internal development purposes, so
you can assign one of these identifiers to a node you are testing, though you should
replace it with a globally unique value before you deploy your plug-in to the wide
world. Because our example is somewhat contrived for learning purposes only, we
Each attribute itself is initialized to a value of None, but will eventually point to
an MObject when the node is initialized in Maya, as we will see soon. Assigning a
default value of None effectively lets us declare the class attributes without making an
unnecessary assignment. Similar to the situation we discussed in Chapter 10 for a
custom commands data attributes, this declaration lets our IDE recognize these names
as class attributes. Recall also in Chapter 11 that we distinguished attributes from
plugs, whereby attributes are merely definitions for the nodes data. It is for this
reason that the nodes attributes are defined as class attributes rather than as instance
attributes. The attribute definitions will be shared by all instances of these nodes.
Finally, the __init__() method simply calls __init__() on the base class,
MPxNode.
def __init__(self):
ommpx.MPxNode.__init__(self);
Just as was the case with commands, nodes inherit from a proxy class.
MPxNode is the base class for all custom nodes, but special types of nodes may
inherit functionality from a subclass, such as MPxLocatorNode or
MPxDeformerNode. Any time you develop one of these special types of nodes, it is
a good idea to consult the documentation so that you know what methods you need to
override, and what the state of the nodes data are by the time your override methods
are called.1 MPxNode itself contains many useful methods worth reviewing.
Node Creator
Just like custom commands, custom nodes all require a creator class method. The
primary job of this method is to return the node as a pointer to a proxy object. Refer
to the Command Creator section in Chapter 10 for a refresher on why this transfer
of ownership is important for Python in Maya.
@classmethod
def nodeCreator(cls):
return ommpx.asMPxPtr(cls());
Node Initializer
The node initializer is a class method that is called when the plug-in node is first
loaded in Maya. It may have any name, as you will pass a pointer to it when the plugin is initialized, much like the node creator method. The primary task of the node
initializer method is to create and configure attributes. Although the class already
contains names for the nodes attributes as class attributes, the MObject for each
attribute needs to be configured to link up the attributes names, data type,
relationships with other attributes, and so on.
@classmethod
def nodeInitializer(cls):
nAttr = om.MFnNumericAttribute();
cls.input1Attr = nAttr.create(
cls.kInput1AttrLongName,
cls.kInput1AttrName,
om.MFnNumericData.kDouble
);
nAttr.setKeyable(True);
cls.input2Attr = nAttr.create(
cls.kInput2AttrLongName,
cls.kInput2AttrName,
om.MFnNumericData.kDouble
);
nAttr.setKeyable(True);
cls.outputAttr = nAttr.create(
cls.kOutputAttrLongName,
cls.kOutputAttrName,
om.MFnNumericData.kDouble
);
nAttr.setWritable(False);
nAttr.setStorable(False);
The first part of this nodes initializer method simply creates each attribute,
specifying the attributes long name, short name, and data type. The function set for
the new attribute then specifies some properties for each attribute before creating the
next one.
Although we will talk about these properties momentarily, the important point is
that you will use some class that inherits from MFnAttribute to create your attributes
and establish their properties. In this case, because both our input attributes and the
output attribute are simply double values, we use an MFnNumericAttribute object.
Note that like many function sets in the API, the create() method not only returns the
new MObject, but also automatically sets the new MObject as the function sets
target object.
compute()
As we discussed in Chapter 11, the compute() method is the brains of any
dependency node. It is called when data are requested for a particular plug, and its job
is to ensure that the data for the plug are up-to-date.
As you can see in the AR_AverageDoublesNode class (or in the API
documentation for the MPxNode class), the compute() method is passed a plug and
the nodes data block.
def compute(self, plug, dataBlock):
if (plug == AR_AverageDoublesNode.outputAttr):
#
else: return om.kUnknownParameter;
The plug argument is the particular MPlug for which data have been requested
(e.g., by another node, a getAttr command, or one of the other various mechanisms
we discussed in Chapter 11). Consequently, the first thing most compute() methods
will do is route execution based on which plug is requested. You can accomplish this
task by using the equivalence operator (==), whichas you can see in the API
documentationthe MPlug class has overridden to allow for comparisons with an
MObject representing a node attribute.
If the plug for which data are requested is one that the node does not handle,
th en compute() must return a value of kUnknownParameter. For example, a
custom node will inherit a variety of basic attributes, such as message, caching, and
so on. Returning kUnknownParameter in this case signals to Maya that it should
attempt to evaluate data for the plug using the base class implementation.
We can look now at what happens if the plug for which data are requested is the
output attribute.
dataHandle = om.MDataHandle(
dataBlock.inputValue(
AR_AverageDoublesNode.input1Attr
)
);
input1 = dataHandle.asDouble();
dataHandle = om.MDataHandle(
dataBlock.inputValue(
AR_AverageDoublesNode.input2Attr
)
);
input2 = dataHandle.asDouble();
The first step for computing the output attribute is to retrieve the values of the
input attributes. We accomplish this task by calling the inputValue() method on
dataBlock and passing it the MObject for the attribute in question to retrieve an
When setting the output value, we call the outputValue() method on dataBlock
to construct an MDataHandle. Once we have this data handle, we use it to set the
outgoing value and then finally indicate that the outgoing plug is clean. Subsequent
requests for this plugs value can then rely on this value to minimize redundant DG
calculations.
Best Practices
Strictly speaking, there are few restrictions on what API classes and methods you may
use in the compute() method for custom nodes. Nevertheless, there are some
practices to which you should adhere to maximize computational efficiency and
stability in accord with Mayas data flow model.
First, you should always (only) use the MDataBlock object associated with the
node to get and set any values required for computation. Trying to retrieve data from
outside the data block may trigger unnecessary DG computation, or result in an
infinite loop. For example, some API classes allow you to invoke MEL commands.
Invoking getAttr or setAttr from inside a nodes compute() method will cause
serious problems if they (directly or indirectly) trigger computation of the very plug
being computed!
Related to this first point, you should always treat your node as a black box, and
never make any assumptions about other nodes in the scene. Attempting to traverse
the DG and get data from other nodes can be extremely dangerous or unpredictable.
Remember that altering the DG outside of a node may trigger an endless computation
cycle. Nodes in Maya are intended to serve as individual building blocks, and it is best
if you treat them as such.
It is also important to contrast the inputValue() and outputValue() methods in
the MDataBlock class. As you saw in the example we just investigated, we used the
former method to get input attribute values, and the latter to set the computed output
value. While each of these methods returns an MDataHandle object, the former is
read-only, while the latter allows you to write a new value.
Furthermore, the inputValue() method is always guaranteed to be up-to-date.
Consequently, if getting a data handle for a dirty connection, it will automatically
trigger necessary DG updates to ensure its data are fresh. If the plug is not connected,
the value stored in the data block is returned. If there is no value in the data block for
the plug, then the default value is returned.
Although the importance of this process will be clearer later in this chapter where
we discuss attributes and plugs in greater detail, there may be some cases where
getting a data handle using inputValue() may trigger a good deal of unnecessary
computation. When manipulating mesh data, for instance, it can be faster to copy the
input data to the output plug, and then use outputValue() when creating data handles,
as this method will not trigger DG updates.2
Any plug-in may register any number of nodes and/or commands. As you can
see here, registering a node requires that you pass its unique name, unique identifier, a
pointer to its creator method, and a pointer to its initializer method.
Similarly, uninitialization proceeds similar to that for commands, and requires
only that you pass the nodes unique identifier for deregistration.
def uninitializePlugin(obj):
plugin = ommpx.MFnPlugin(obj);
try:
plugin.deregisterNode(
AR_AverageDoublesNode.kPluginNodeId
);
except:
raise Exception(
Failed to unregister node: %s%
AR_AverageDoublesNode.kPluginNodeTypeName
);
Attribute Properties
In Chapter 11 we mentioned that while we conventionally refer to certain attributes as
outputs and others as inputs, the Maya API does not make such a simple distinction.
Rather, attributes have a variety of properties that determine their behavior, all of
which can be set using MFnAttribute or a descendent class. While these properties
are documented in the MFnAttribute API documentation, we have summarized
some of them in Table 12.1.
Table 12.1 Common Attribute Properties
Property
Default
Readable
True
Writable
True
Connectable
Storable
True
True
Cached
True
Description
False
desired
NiceNameOverride
None
Cached Attributes
Recall that when examining the compute() method of the ar_averageDoubles node,
we pointed out that the inputValue() method for MDataBlock objects has a specific
order of data retrieval to ensure it will always return fresh data. While default values
impact this process, caching does as well.
An attribute that is cached will store a copy of its data in the data block. Caching
offers a speed improvement by minimizing the need for DG traversal, but comes at the
cost of memory. While caching is fine for many simple numeric types, you may not
want to cache large chunks of data, such as geometry. It is worth reiterating that if you
do not cache some particular attribute, you will want to design your node in such a
way as to minimize its retrieval of needed data as much as possible.
underlying index of the plug element (0 and 2, in our hypothetical situation), and are
not guaranteed to be contiguous. Physical indices, on the other hand, are a means for
mapping contiguous indices to their corresponding logical indices. In our hypothetical
example, the first item has both a logical and physical index of 0, but the second item
has a physical index of 1 and a logical index of 2.
As you may imagine, this distinction makes iteration through a plug array not
especially straightforward. For example, the MArrayDataHandle class, which you
may use in conjunction with plug arrays, has a jumpToElement() method that jumps
to a specified logical index. Simply looping from 0 to the length of the array and then
jumping to the current iterator index may result in jumping to a null element, which
raises an exception. We cover the most straightforward technique for working with
plug arrays here.
Implementing Plug Arrays
The AR_AverageNodes.py plug-in contains a node, ar_averageArrayDoubles,
which implements a single plug array input attribute. This node is a more flexible
alternative to the ar_averageDoubles node, because it routes any number of decimal
number inputs through a plug array as opposed to two individual plugs. Before we
examine the implementation details in the source code, walk through the following
quick example to see the node in action.
1. Create a new Maya scene and ensure that the AR_AverageNodes.py plug-in is
loaded.
2. Execute the following lines to create a row of four cones with different heights,
as well as a fifth cone in front of them.
import maya.cmds as cmds;
numCones = 4;
cones = [
cmds.polyCone(height=i+1)
for i in range(numCones)
];
for i in range(numCones):
cmds.setAttr(
%s.tx%cones[i][0],
i*2-numCones+1
);
avgCone = cmds.polyCone(name=avgCone);
cmds.setAttr(%s.tz%avgCone[0], 2);
3. With the cones still in your scene, execute the following lines to create an
ar_averageArrayDoubles node. The height attribute for each cone in the back row
pipes into a separate array plug.
avg = cmds.createNode(ar_averageArrayDoubles);
for i in range(len(cones)):
cmds.connectAttr(
%s.h%cones[i][1],
%s.in%avg, na=True
);
cmds.connectAttr(%s.out%avg, %s.h%avgCone[1]);
At this point, you can adjust the height of any of the cones in the back row and
the height of the cone in front will automatically adjust to the new average height.
Note also that we used the na (or nextAvailable) flag to automatically append each
new connection to the next available array index. Well see how we enabled this
feature shortly.
4. Execute the following lines to print the connection for each cones height
attribute.
for c in cones:
print(
cmds.listConnections(
%s.h%c[1],
p=True,
c=True
)
);
You should see the following output lines in the Script Editors History Panel.
[upolyCone1.height,
[upolyCone2.height,
[upolyCone3.height,
[upolyCone4.height,
uar_averageArrayDoubles1.input[0]]
uar_averageArrayDoubles1.input[1]]
uar_averageArrayDoubles1.input[2]]
uar_averageArrayDoubles1.input[3]]
As you can see, each cones height attribute is connected to an incremented array
index for the ar_averageArrayDoubles nodes input attribute.
5. Execute the following lines to add another cone to the scene, connect it to index
10 in the ar_averageArrayDoubles node, and print the new list of connections.
cones.append(cmds.polyCone(height=numCones));
cmds.setAttr(%s.tz%cones[-1][0], -2);
cmds.connectAttr(%s.h%cones[-1][1], %s.in[10]%avg);
for c in cones:
print(
cmds.listConnections(
%s.h%c[1],
p=True,
c=True
)
);
The output is identical to that which you saw in the previous step, except that you
now also see the new cone at the end of the list of connections. The node is still able
to work even though indices 49 are skipped.
[upolyCone1.height,
[upolyCone2.height,
[upolyCone3.height,
[upolyCone4.height,
[upolyCone6.height,
uar_averageArrayDoubles1.input[0]]
uar_averageArrayDoubles1.input[1]]
uar_averageArrayDoubles1.input[2]]
uar_averageArrayDoubles1.input[3]]
uar_averageArrayDoubles1.input[10]]
If you jump down to the node initializer class method, you can see that this
attribute is created much like the input attributes for the first example node that we
created. The only difference is that some additional properties are configured for the
attribute.
nAttr = om.MFnNumericAttribute();
cls.inputAttr = nAttr.create(
cls.kInputAttrLongName,
cls.kInputAttrName,
om.MFnNumericData.kDouble
);
nAttr.setKeyable(True);
nAttr.setArray(True);
nAttr.setReadable(False);
nAttr.setIndexMatters(False);
nAttr.setDisconnectBehavior(
om.MFnNumericAttribute.kDelete
);
As you can see, the attribute is initially created as though it were an ordinary
double. Passing the setArray() method for the MFnNumericAttribute object a value
of True is the key requirement to make the plug behave as an array.
In this example, we have also passed a value of False to the setIndexMatters()
method. This property ordinarily defaults to True for array plugs, and thus normally
requires that users manually specify an array index when making a connection
(thereby preventing use of the nextAvailable flag with the connectAttr command).
Because computation of an arithmetic mean is a commutative operation, this node
doesnt really care about the order of connections. On the other hand, a node that
We wrap the initialization of the output value (as well as the division by
elementCount()) in a try-except clause, as the array may contain no elements when
compute() is called. Otherwise, the data handle will automatically point to the first
logical index. Inside the loop, the next() method simply moves to the next logical
index, whatever it happens to be.
After this point, we simply set the output value exactly as we did in the
ar_averageDoubles node. Although our example does not show it, you can use the
MArrayDataBuilder class in conjunction with plug arrays if you need to construct
an output value that is an array. Refer to the companion web site for more information
3. Execute the following lines to move through 24 frames and print the current
average age. If your time unit is set to film (24 fps, frames per second) then you
should see a printed result around half a second.
for i in range(25): cmds.currentTime(i);
print(
Avg. age: %.2f seconds%
cmds.getAttr(%s.output%avg)
);
tAttr.setKeyable(True);
In the compute() method, we can use the data() method on the MDataHandle
we create to initialize an MFnDoubleArrayData function set and get the plugs value
as an MDoubleArray. As in the AR_AverageDoublesNode class, we have wrapped
return values in copy constructors only to ensure we will have functional
autocompletion suggestions in our IDE.
dataHandle = om.MDataHandle(
dataBlock.inputValue(
AR_AverageDoubleArrayNode.inputAttr
)
);
doubleArrayFn = om.MFnDoubleArrayData(dataHandle.data());
doubleArray = om.MDoubleArray(doubleArrayFn.array());
In contrast to a plug array, an array plug that corresponds to a data type such as
MDoubleArray will be contiguous. As such, the iteration technique for computing
the output value is much more straightforward than in the previous example. We still
wrap the division operation in a try-except clause in case the array length is 0, in
which case the output will simply be 0.
output = 0.0;
for i in range(doubleArray.length()):
output += doubleArray[i];
try: output /= doubleArray.length();
except: pass;
Compound Attributes
The final remaining topic worth covering here is compound attributes. Compound
attributes allow you to group an arbitrary set of attributes under a single parent
attribute, and to manage connections on individual children or on the parent plug. The
AR_WeightedAverageVectorsNode class in the AR_AverageNodes.py plug-in
demonstrates implementation of compound attributes.
1. Create a new Maya scene and ensure that the AR_AverageNodes.py plug-in is
loaded.
2. Execute the following lines to create three locators.
import maya.cmds as cmds;
loc1 = cmds.spaceLocator()[0];
cmds.setAttr(%s.translate%loc1, 2, 1, 0);
loc2 = cmds.spaceLocator()[0];
cmds.setAttr(%s.translate%loc2, -3, 0, 1);
loc3 = cmds.spaceLocator()[0];
3. With the locators still in your scene, execute the following lines to pipe the
translation of the two outermost locators into an ar_weightedAverageVectors node
and connect the output to the center locator.
avg = cmds.createNode(ar_weightedAverageVectors);
cmds.connectAttr(
%s.translate%loc1,
%s.input1Vector%avg
);
cmds.connectAttr(
%s.translate%loc2,
%s.input2Vector%avg
);
cmds.connectAttr(
%s.output%avg,
%s.translate%loc3
);
You can now move the two outer locators around and the center locators
position will be locked between them. Note that we only need to connect the translate
plugs directly, and do not need to individually connect their x, y, and z plugs.
4. Execute the following lines to set keys on the weight attributes of the
ar_weightedAverageVectors node.
cmds.setKeyframe(avg, at=in1W, t=1, v=1);
cmds.setKeyframe(avg, at=in2W, t=1, v=0);
If you scrub the time slider between frames 1 and 24, you can see the center
locator move between the two targets.
5. Execute the following line to see all of the nodes attributes.
for attr in cmds.listAttr(avg): print(attr);
The first few attributes printed are inherited from MPxNode, but you can also
see the names for all of this nodes specific attributes.
input1
input1Vector
input1Vector0
input1Vector1
input1Vector2
input1Weight
input2
input2Vector
input2Vector0
input2Vector1
input2Vector2
input2Weight
output
output0
output1
output2
Second, you can see that there are three attributes defined for each input. The
first for each is the parent attribute, and the other two will be children of it.
input1Attr = None;
input1VecAttr = None;
input1WgtAttr = None;
#
input2Attr = None;
input2VecAttr = None;
input2WgtAttr = None;
If you jump down to the node initializer method, you can see how these attributes
are created. Both inputs follow the same basic pattern, so we can look at just the first
one here. The first step is to create each of the child attributes.
nAttr = om.MFnNumericAttribute();
cls.input1VecAttr = nAttr.create(
cls.kInput1AttrLongName+cls.kInputVecAttrLongSuffix,
cls.kInput1AttrName+cls.kInputVecAttrSuffix,
om.MFnNumericData.k3Double
);
nAttr.setKeyable(True);
cls.input1WgtAttr = nAttr.create(
cls.kInput1AttrLongName+cls.kInputWgtAttrLongSuffix,
cls.kInput1AttrName+cls.kInputWgtAttrSuffix,
om.MFnNumericData.kDouble,
0.5
);
nAttr.setKeyable(True);
As you can see, the vector attribute will have names input1Vector and in1V, and
the weight attribute will have names input1Weight and in1W, which should be
familiar based on the previous example steps. Note that we assign a default value of
0.5 to the weight attributes, such that each input contributes equal weight when the
node is first created.
At this point, we use an MFnCompoundAttribute object to create a parent
attribute named input1 (or in1) and add the two numeric attributes to it as children.
cAttr = om.MFnCompoundAttribute();
cls.input1Attr = cAttr.create(
cls.kInput1AttrLongName,
cls.kInput1AttrName
);
cAttr.addChild(cls.input1VecAttr);
cAttr.addChild(cls.input1WgtAttr);
If you skip down to the bottom of the node initializer, you can see that we only
need to add the parent attribute and establish a relationship between it and the output.
cls.addAttribute(cls.input1Attr);
cls.addAttribute(cls.input2Attr);
cls.addAttribute(cls.outputAttr);
cls.attributeAffects(cls.input1Attr, cls.outputAttr);
cls.attributeAffects(cls.input2Attr, cls.outputAttr);
While older versions of the API required that each child attribute be added
separately, all versions of Maya that support Python will automatically add all children
when a parent compound attribute is added. Note also that our sample node is
somewhat simple, such that all inputs affect the output value. You can establish
individual relationships between child attributes if you so desire.
You may have noticed that we only explicitly created two compound attributes:
input1 and input2. However, the list of attributes you printed for the node in the
example contained further indexed child attributes for the input vectors and the
output. Notice that these attributes are simply numeric attributes with a special type,
k3Double, specified.
cls.input1VecAttr = nAttr.create(
cls.kInput1AttrLongName+cls.kInputVecAttrLongSuffix,
cls.kInput1AttrName+cls.kInputVecAttrSuffix,
om.MFnNumericData.k3Double
);
#
cls.input2VecAttr = nAttr.create(
cls.kInput2AttrLongName+cls.kInputVecAttrLongSuffix,
cls.kInput2AttrName+cls.kInputVecAttrSuffix,
om.MFnNumericData.k3Double
);
#
cls.outputAttr = nAttr.create(
cls.kOutputAttrLongName,
cls.kOutputAttrName,
om.MFnNumericData.k3Double
);
Types such as k3Double are essentially built-in compound attributes for dealing
with common data types, such as vectors and points. Using such types saves you the
trouble of having to manually create children attributes, with the caveat that their
names will simply be numerically indexed versions of the parent attributes name.
We can now jump to the compute() method to see how we work with these
attributes. Depending on what your node does, the plug test in the compute() method
for compound output attributes may need to be more sophisticated than when dealing
with an ordinary output attribute.5 In the AR_AverageVectorsNode class, we have
chosen to trigger computation if either the parent or its child plugs are requested,
ensuring that all output values are handled at once.
if (plug == AR_AverageVectorsNode.outputAttr or
(plug.isChild() and
plug.parent()==AR_AverageVectorsNode.outputAttr)):
#
Presuming that data are requested for the output attribute or one of its children,
the data are acquired using ordinary MDataHandle objects. The calculation of the
output value simply normalizes the two input weight values. Like a point constraint in
Maya, we simply use default values in case of a division by zero.6
totalWeight = input1Weight+input2Weight;
if (abs(totalWeight) >
abs(totalWeight)*sys.float_info.epsilon):
output = (input1Vector*input1Weight +
input2Vector*input2Weight) / totalWeight;
else: output = (input1Vector+input2Vector)*0.5;
After computing the result, we can output its value much like the other example
nodes that we have investigated. Handily, the built-in compound numeric types, such
a s k3Double, have corresponding MDataHandle methods to set the entire output
value in a single API call.
dataHandle = om.MDataHandle(
dataBlock.outputValue(
AR_WeightedAverageVectorsNode.outputAttr
)
);
dataHandle.set3Double(output.x, output.y, output.z);
Concluding Remarks
You are now equipped with enough of the basics to accomplish just about anything
you need using Python in Maya. You can build simple tools, configure an advanced
work environment, and also create custom plug-ins. In addition to containing some
additional materials and more advanced API examples, the companion web site
provides a portal for you to get the latest information and to help contribute to the
community of knowledge surrounding Python in Maya. We hope youve found this
text to be informative and helpful, and we look forward to hearing from you with
questions, comments, and suggestions.
For example, custom deformer nodes inherit some basic attributes and override the
deform() method instead of the compute() method. For a class derived from
MPxDeformerNode, compute() gets the input geometry, creates an iterator, and
calls deform().
Recall in Chapter 1 when we used the help command in conjunction with some
basic commands that some arguments were listed as type Length rather than float.
Likewise, using the getAttr command with the type flag reveals that some
attributes have type doubleLinear rather than simply double. Such attributes are unit
attributes. If you create a node that semantically is outputting unit-dependent values
(e.g., time, distance, or angles), then it is advisable that you use a unit attribute
rather than simply doubles, as you cannot make assumptions about what unit
settings may be in use. Maya automatically creates a unitConversion node between
unit and nonunit plug-ins as needed.
The API documentation for the MPlug class shows an iteration example for a plug
array. We have included a corresponding implementation in the comments of the
example file. Because the index operator for the MPlug class works by physical
index, it is easier to iterate an input array. However, recall that working with an
MPlug is much less efficient than working with an MDataHandle, as a plug
performs internal validation for operations since it can do more than simply get and
set data.
5
Note that output plug arrays may need more sophisticated techniques as well, such
as testing isElement(). See the companion web site for more information.
Index
positional argument, 69
variable-length argument, 7172
AR_OptionsWindow Class, 215218
AR_PolyOptionsWindow, 218, 223f, 226
Array, 363370
logical and physical indices, 364
plug, 363370
ar_transform
command, 294295
flag, 213
attachForm
flag, 213
attachNone
flag, 214
attachPosition
flag, 213
Attribute Editor, 12
Attribute naming conventions, 127
Attributes, 7980, 151164
__builtins__, 120
cache, 363
class, 160163
static/class methods, 161163
compound, 370375
data, 153155
default values and storable, 361363
__doc__, 120
__file__, 120121
human class, 163164
methods, 155160
__repr__(), 158159
__str__(), 155158
__name__, 116, 121
naming conventions, 127
__package__, 120
properties, 159160, 361, 362t
Autodesk, 268
Automatic code completion, 144
Axis enumerator, 273
command, 214
Buttons, 207212
Bytecode, 113
Caching, 363
CallBack(), 174
Character class, 264265, 267
character
command, 114
command, 223
flag, 244
command, 144
Constant, 274
contents,
32
command, 354
create.py, 122123
command, 328
deleteUI
command, 197
command, 338339
Dictionary, 5861
operators, 59t, 5960
in practice, 6061
Directed acyclic graph (DAG), 13, 339350
DAG Paths, 344347
hierarchy of, 341342f
instancing, 344347
underworld, 347350
disconnectAttr
command, 4142
command, 236
file
command, 230231
command, 230231
fileMode flag used with, 232t
fileDialog2
fileMode
for loop, 95
for statement, 8084
Form of tool, 180
separating, 226
formLayout
command, 212213
getDagPath(), 303
get() method, 279
getPath() method, 346
globals(), 114115, 120
Graphical user interface (GUI)
classes, extending, 218224
Command Line, 68, 7f
commands
basic, 196198
windows, 196198, 197f
Maya, 4
319
Layouts
and buttons, 207212
and controls, 206215
form, 212215
frame, 220222
Lazy evaluation, 332, 334336
Left mouse button (LMB), 9, 11
Lightplane modules, 130f
List comprehensions, 9394
Listing nodes, 7677
Lists, 20
listTypes
flag, 240
command, 287
constant, 274
HTML documentation, 270
Modules tab, 271
MScriptUtil, 275277
MStatus class, 279
MVector class, 272, 273f, 274
rotatedBy method, 278279
Python communicates with, 268270
structure of, 265268
Maya Embedded Language (MEL), 5
command, 126
procedure, 64
Maya Script Editor, 116117
Maya-specific environment variables, 136137
Maya tool design
communication and observation, 181
designing tips for users, 180183
ready, set, plan, 181182
simplifying and education, 183
Maya.env, 136137
MAYA_SCRIPT_PATH, 138
MayaWindow string, 195
MDagModifier, 319, 325
MDagPath, 324
MDagPathArray, 346
MDataBlock class, 331
MDataHandle, 331, 358
MDGModifier, 314
MEL. See Maya Embedded Language
menu
command, 199200
menuItem
command, 199
Packages, 121125
create.py, 122123
__init__py, 124125
math.py, 123
Parametric sphere nodes, 333f
parent
command, 185
argument, 357
command, 311
Polygon Primitives, 15
Polymorphism, 265
polyPoke
command, 312
polySphere
Positional argument, 69
print(), 65
print() function, 81
proc keyword, 64
Procedural programming versus OOP, 148151, 168175
process_all_textures(), 65
process_diffuse() function, 105
Property Editor, 242243
dynamic string property in, 245f
Proxy classes, 281282, 290
pSphereShape node, 13
Push button widget, 244f
Pushpull system, 334336
PyMEL, 113, 168
advantages and disadvantages, 171172
example, 172175
features of, 169171
installing, 168
introduction to, 168169
PyNodes, 169170
PyQt, 254257
installation, 254255
using
in earlier versions of Maya, 255257
in Maya 2011+, 255257
Python, 5, 263, 263f, 274, 277
and C++ API, 281283
class implementation in, 150
Qt
creator, 241242, 243f
designer, 237, 239246, 252f
application, 240f
hands on, 241246
types of forms, 239
GUI
loading, 246254
toolkit, 235, 235f
and MAYA, 234237
signals and slots, 241
Software Development Kit (SDK), 237239
links for, 238t
tools
benefit of, 234
installation, 237239
QtCore module, 255
Query mode, 2324
command, 200
flag, 315
raise statement, 96
range() function, 8184
Readability, 361
redoIt() method, 313, 323325
new nodes creation, 323324
Reference counting, 36f, 3637
registerCommand(), 292
reload() function, 125
rename
command, 75
return statement, 96
Return values, 25, 7475
Right mouse button (RMB), 9
rotatedBy method, 278279
RotateOrder
rowLayout
flag, 297
command, 210
command, 77
setdefault(), 60
function, 73
setDisconnectBehavior() method, 367
setIndexMatters() method, 367
setObjectType(), 318
setParent
command, 222
Sets, 5758
operators, 5758, 57t
setText() method, 174
command, 288
138
138
textField
command, 174
Tools
creating, 224232
in Maya, 183191
marking menus, 186190, 187f, 189f
option windows, 190191
selection, 184186
Transform nodes, 13, 4042, 75, 195, 224, 228, 340, 340
editing, 325
Triggering DG Evaluation, 336
try clause, 97
try statement, 9697
Tuple, 19, 21
Type, 34
type(), 32
type() function, 75
uiFile
flag, 249
uiString
flag, 249
Underworld, 347350
Undo and redo, 313314
MDGModifier, 314
mechanism of Maya, 308313, 309312f
undoInfo
command, 310
Value, 34
Variable-length argument lists
using double asterisk (**) operator, 71
using single (*) operator, 71
Variables, 3037, 32t
with Maya commands, 3742
in MEL, 33
verbose
flag, 249