Python in Maya

Maya’s scripting commands come in the module package maya.cmds.

import maya.cmds

People usually use the shorthand

import maya.cmds as cmds

You will notice that if you type in help(cmds), you do not get anything useful besides a list of function names. This is because Autodesk converted all of their MEL commands to Python procedurally. To get help with Maya’s Python commands, you will need to refer to the Maya documentation.

Figure 14 - Maya Python Command Documentation

There are so many commands that there is no reason to memorize them all. You will come to memorize many of the commands simply by using them a lot. How do you get started learning commands? Do what you are trying to accomplish with Maya’s interface and look at the script editor. Most the actions you do with the Maya interface output what commands were called in the script editor. The only caveat is that the output is in MEL so we’ll have to do a little bit of translating. Since all of the commands and arguments are the same between the MEL and Python commands, translating between the two is pretty easy.

The Maya Python Command Documentation

In this section, we will go over how to learn Maya’s Python commands by studying the MEL output in the script editor when interacting with Maya. We will then decipher the MEL output and reconstruct the command using the Maya Python documentation.

Creating a polygon sphere outputs the following MEL command into the script editor:

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

MEL commands usually consist of the command name followed by several flags. In the above code, polySphere is the command, and each group of letters following a “-“ is a flag. The numbers after each flag are arguments of their corresponding flag. For example, -r is a flag with an argument of 1, -sx is a flag with an argument of 20, -ax is a flag with 3 arguments: 0, 1, 0. Using this information, we can look up the command in the Maya Python command documentation and write its Python equivalent. Like I said earlier, commands in MEL and Python have the exact same name, look up “polySphere” in the Python documentation. It looks like this:

Figure 15 - Sample Documentation

The documentation page contains all the information you need to work with the command. The Synopsis shows the function and all the possible arguments that can be passed in along with a description of what the command does. In this case, it creates a new polygonal sphere. The return value tells us what the function returns. The polySphere command returns a list containing two strings. The first element of the list will be the name of the transform of the new polygon sphere. The second string in the list will be the name of the polySphere node, which controls how the sphere is constructed.

>>> x = cmds.polySphere()
>>> print x
[u'pSphere1', u'polySphere1']

Notice that each string has a ‘u’ before it. This stands for Unicode string which is a type of string that you can ignore for now. Unicode strings help with international languages so just assume they are the same as normal strings.

Following the Return value section is a list of related Maya Commands. Following these links is a good way to learn about other commands. After the related commands is the Flags section. This section should really be called Arguments or Parameters; Flags are more of a MEL construct. The list of Flags (arguments) contains all the arguments that can be passed into the documented function. Each argument description contains the argument name, an abbreviated argument name, what kind of data you can pass into the argument, in what context the argument is valid, and a description of the argument. Take the radius argument for example. By passing this argument to the polySphere function, we can control the radius of the created sphere.

>>> x = cmds.polySphere(radius=2.5)

or the abbreviated form

>>> x = cmds.polySphere(r=2.5)
>>> print x
[u'pSphere2', u'polySphere2']

Personally, I tend to avoid the abbreviated form as I can never remember what all the abbreviations mean when I read my code. Using the full name is more typing and makes your code longer, but I find it easier to read.

The documentation is not always clear about what type of data is expected with an argument. For example, the documentation says that the radius argument expects some data of type linear. Usually by looking at the equivalent MEL command, you can figure out what to pass into the Python command. However, there are some cases where the documented format of the expected data is just really vague or cryptic. In these cases, if you can’t figure out how to format the command, ask on a forum or mailing list.

After the list of arguments, there is usually an examples section that gives various usage examples of the given command.

Going back to our example MEL command:

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

we can see that each of the MEL flags corresponds to an argument in the Python function. By looking up the flags in the Python documentation, we can write the equivalent Python command:

cmds.polySphere(r=1, sx=20, sy=20, ax=(0, 1, 0), cuv=2, ch=1)

As a personal preference, I would write this command as

cmds.polySphere(radius=1, subdivisionsX=20, subdivisionsY=20,
                axis=(0, 1, 0), createUVs=2, constructionHistory=True)

It’s up to you on whether to use the abbreviated flags or not. Note that I also swapped the ch=1 for ch=True. Refering to the documentation, the constructionHistory argument expects a Boolean value. Remember from the Booleans and Comparing Values section that all non-zero numbers are evaluated as True. I like to actually pass in the value True (or False if you want False) to these types of arguments just for my own preference.

You will also notice in the documentation the letters in the colored squares. The C, Q, M, and E stand for Create, Query, Multiple, and Edit. These letters tell you in what context an argument is valid. Many commands have different functionality depending on what context you are running the command in. In the previous example, we were creating a sphere, so all the arguments marked C were valid.

When you run a command in query mode, you can find information about an object created with the same command. You run a command in query mode by passing query=True as an argument.

>>> sphere = cmds.polySphere(radius=2.5)[0]
>>> print cmds.polySphere(sphere, query=True, radius=True)
2.5

When you query a command, you pass in the object you want to query first, followed by your arguments. When in query mode, you pass a True or False to the argument you want to query regardless of what the expected type for that argument is documented as. You should only query one argument at a time. You’ll notice that when you query a value, the value returned from the function may not be the same as what the documentation says is returned. In create mode, the polySphere command returns a list of 2 strings. In query mode, the return type depends on the value you are querying.

>>> sphere = cmds.polySphere()[0]
>>> print cmds.polySphere(sphere, query=True, radius=True)
1.0
>>> print cmds.polySphere(sphere, query=True, axis=True)
[0.0, 1.0, 0.0]

Besides create and query modes, you can also run a command in edit mode. Edit mode lets you edit values of an existing node created with the command. You run a command in edit mode by passing in edit=True as an argument to the function.

>>> sphere = cmds.polySphere()[1]
>>> cmds.polySphere(sphere, edit=True, radius=5)  # Change the radius to 5

And finally, sometimes a flag is marked with being available multiple times. For example the curve command has point argument (p) that we use to specify all the cv’s of a curve.

# MEL
curve -p 0 0 0 -p 3 5 6 -p 5 6 7 -p 9 9 9 -p 0 0 0 -p 3 5 6;

# Python
cmds.curve(p=[(0, 0, 0), (3, 5, 6), (5, 6, 7), (9, 9, 9), (0, 0, 0), (3, 5, 6)])

You now know how to look up command syntax and decipher the documentation. You have just about all the knowledge you need now to write your own Maya scripts in Python. All you need now is to learn the various commands. A really good way to do that is to look at other people’s scripts.

Sample Scripts

In most of the sample scripts, you will notice that I always put the code in functions or classes. When you import a module, all of the code in the module gets executed. However, code inside functions does not get run until the function is called. When writing scripts for Maya, it is good practice to structure your code as functions or classes to be called by users. Otherwise, you may surprise your users by executing unwanted code when they import your modules.

lightintensity.py

import maya.cmds as cmds
def scale_light_intensity(percentage=1.0):
    """Scales the intensity of each light in the scene by a percentange.

    @param percentage: Percentage to change each light's intensity.    
    """
    # The ls command is the list command.  It is used to list various nodes
    # in the current scene.  You can also use it to list selected nodes.
    # Notice the strange syntax 'ls() or []'.  If no lights are in the scene, the ls
    # command returns None which would prevent us from running the for loop.  The
    # strange syntax is a convenient way to make sure all_lights is a list if ls
    # returns None.
    all_lights = cmds.ls(type='light') or []

    # Loop through each light
    for light in all_lights:
        # The getAttr command is used to get attribute values of a node
        current_intensity = cmds.getAttr('{0}.intensity'.format(light))
        # Calculate a new intensity
        new_intensity = current_intensity * percentage
        # Change the lights intensity to the new intensity
        cmds.setAttr('{0}.intensity'.format(light), new_intensity)
        # Report to the user what we just did
        print 'Changed the intensity of light {0} from {1:.3f} to {2:.3f}'.format(light, current_intensity, new_intensity)

Concepts used: functions, lists, for loops, conditional statements, string formatting

To run this script, in the script editor type:

import lightintensity 
lightintensity.scale_light_intensity(1.2)

renamer.py

import maya.cmds as cmds

def rename(name, nodes=None):
    """Renames a hierarchy of transforms using a naming string with '#' characters.
    If you select the root joint of a 3 joint chain and pass in 'C_spine##_JNT',
    the joints will be named C_spine01_JNT, C_spine02_JNT, and C_spine03_JNT.

    @param name: A renaming format string.  The string must contain a consecutive
                 sequence of '#' characters
    @param nodes: List of root nodes you want to rename.  If this argument is
                  omitted, the script will use the currently selected nodes.
    """
    # The variable "nodes" has a default value of None.  If we do not specify a value
    # for nodes, it will be None.  If this is the case, we will store a list of the
    # currently selected nodes in the variable nodes.
    if nodes is None:
        # The ls command is the list command. Get all selected nodes 
        # of type transform.
        nodes = cmds.ls(sl=True, type='transform')

        # If nothing is selected, nodes will be None so we don't need
        # to continue with the script
        if not nodes:
            raise RuntimeError('Select a root node to rename.')

    # Find out how many '#' characters are in the passed in name.
    digit_count = name.count('#')
    if digit_count == 0:
        raise RuntimeError('Name has no # sequence.')

    # We need to verify that all the '#' characters are in one sequence.
    substring = '#' * digit_count     # '#' * 3 is the same as '###'
    newsubstring = '0' * digit_count  # '0' * 3 is the same as '000'

    # The replace command of a string will replace all occurances of the first
    # argument with the second argument.  If the first argument is not found in 
    # the string, the original string is returned.
    newname = name.replace(substring, newsubstring)

    # If the string returned after the replace command is the same as 
    # the original string, it means that the sequence of '#' was not found in
    # our specified name.  This would happen if the '#' characters were not all
    # consecutive (e.g. 'my##New##Name').
    if newname == name:
        raise RuntimeError('Pound signs must be consecutive..')

    # Here we are creating a format string to use in our naming.
    # The number of digits is determined by the number of consecutive '#' characters.
    # Example 'C_spine##_JNT' has 2 '#' characters.  
    # In a chain of 3 joints, we would want to name the joints 
    # C_spine01_JNT, C_spine02_JNT, C_spine03_JNT.
    # In a format string we would want to say 'C_spine%02d_JNT' % number.
    # We are creating the '%02d' part here.
    name = name.replace(substring, '%0{0}d'.format(digit_count))

    # Start at number 1
    number = 1
    for node in nodes:
        # Loop through each selected node and rename its child hierarchy.
        number = rename_chain(node, name, number)

def rename_chain(node, name, number):
    """Recursive function that renames the passed in node to name % number.

    @param node: The node to rename.
    @param name: A renaming format string.  The string must contain a 
                 consecutive sequence of '#' characters
    @param number: The number to use in the renaming.
    @return The next number to use in the renaming chain.
    """
    # Create the new name.  The variable name is a string like 'C_spine%02d_JNT'
    # so when we say "name % number", it is the same as 'C_spine%02d_JNT' % number
    new_name = (name % number)

    # The rename command renames a node.  Sometimes you have to be careful.
    # If you try to rename a node and there's already a node with the same name,
    # Maya will add a number to the end of your new name.  The returned string of 
    # the rename command is the name that Maya actually assigns the node.
    node = cmds.rename(node, new_name)

    # The listRelatives command is used to get a list of nodes in a dag 
    # hierarchy.  You can get child nodes, parent nodes, shape nodes, or all 
    # nodes in a hierarchy.  Here we are getting the child nodes.
    children = cmds.listRelatives(node, children=True,
                                  type='transform', path=True) or []

    # Since we renamed the current node, we increment the number for the next 
    # node to be renamed.
    number += 1
    for child in children:
        # We will call the rename_chain function for each child of this node.
        number = rename_chain(child, name, number)

    return number

Concepts used: functions, recursive functions, lists, for loops, conditionals, string formatting, exceptions.

To run this script, select the root joint of a joint chain and in the script editor type:

import renamer
renamer.rename('C_tail##_JNT')

The renamer script uses a concept called recursive functions. A recursive function is a function that calls itself. Recursive functions are useful when you are performing operations on data in a hierarchical graph, like Maya’s DAG. In recursive functions, you must specify an ending condition or else the function will call itself in an infinite loop. In the above example, the function calls itself for each child node in the hierarchy. Since there are always a limited number of children in a Maya hierarchy, the recursive function is guaranteed to stop at some point.

Calling MEL from Python

Most Maya commands (MEL commands) have been implemented into the maya.cmds module. There are still some cases where MEL must be used because Maya does not fully incorporate Python is all aspects of its architecture.

MEL can be called from Python with the maya.mel module:

import maya.cmds as cmds
selection = cmds.ls(sl=True)

import maya.mel as mel
selection = mel.eval("ls -sl")

We can also invoke python from MEL

string $list[] = python( "['a', 'b', 'c']" );
size( $list );
// Result: 3 //

To source/execute existing MEL scripts in Python:

import maya.mel as mel
mel.eval('source "myScript.mel"')
mel.eval('source "myOtherScript.mel"')
mel.eval('mySourcedFunction(1)')

Maya Python Standalone Applications

Maya provides the maya.standalone module for creating command-line applications. These applications allow us to create and run operations without opening Maya’s interface. Maya Python standalone applications are run with the mayapy interpreter which ships with Maya.

Figure 16 - Running Maya from the Command Line

To open a file in maya standalone mode, you would run:

mayapy X:\file.py

You can also write scripts to run operations on Maya files in batch mode.

Example: Open a Maya file, assign the default shader to all meshes, and save the scene.

import maya.standalone
import maya.cmds as cmds
  
def assign_default_shader(file_path):
    # Start Maya in batch mode
    maya.standalone.initialize(name='python')

    # Open the file with the file command
    cmds.file(file_path, force=True, open=True)

    # Get all meshes in the scene
    meshes = cmds.ls(type="mesh", long=True)
    for mesh in meshes:
        # Assign the default shader to the mesh by adding the mesh to the
        # default shader set.
        cmds.sets(mesh, edit=True, forceElement='initialShadingGroup')

    # Save the file       
    cmds.file(save=True, force=True)

    # Starting Maya 2016, we have to call uninitialize to properly shutdown
    if float(cmds.about(v=True)) >= 2016.0:
        maya.standalone.uninitialize()

In order to run this script, you need to use the mayapy Python interpreter and not the normal Python interpreter. In addition to stand alone scripts, I recommend reading about the argparse module which would allow you to start the mayapy interpreter and call the script all in one line from the command line.

11 thoughts on “Python in Maya”

haiqiao April 2, 2011 at 4:36 am

Thanks a lot for the amazing tutorial! It is the best one.

Benjamin October 4, 2011 at 2:07 am

amazing tut. It’s very cool you can do these sort of batch render procedures to save memory. However I would like my python script to open stand-alone and have a button to open maya and then run a number of scripts inside of maya. Would this be a daunting task or could you put me in the right direction for this. many thanks

Jim Dunford September 30, 2013 at 6:33 am

Hi Chad,

Great post! I have a question regarding Modules…

I have recently put together a collection of functions which create letters in 1 degree curves. The functions are set out:
def letterA():
cmds.curve(point coordinates)

def letterB():
cmds.curve(point coordinates)

and so on down to letterZ.

I’ve also created a userSetup.py file and included the path to a directory I have created in my scripts directory called “pyscripts”.

The python file with the letter functions is saved as “alphabet” and placed in the pyscripts folder. I then reload Maya and execute the following:

import alphabet
alphabet.letterA()

when I execute the second line I get an error saying “cmds is not defined”. I have imported maya.cmds as cmds and added import maya.cmds as cmds to the top of alphabet.py (before the first function) and still get the same error.

In your example above (renamer.py) you have a couple of functions and the import “maya.cmds as cmds” line at the top. What I want to know is how to avoid the cmds error so I can just call the functions from the module?

Many Thanks

Jim

Mickaël December 18, 2013 at 4:33 am

Hi,

And many thanks for sharing your codes

Following your ‘ligh in scene’ script, I’m now stuck on accessing attribute containing a ‘dot’ whithin its name.
I can access each attribute of a mesh object like ‘renderVolume’ or ‘visibility’, but i can not access ‘vrts.vrtx’, ‘vrts.vrty’ or ‘vrts.vrtz’ attributes.

Do you have any clue on how to retrieve these attributes ?

Thanks again
Mickaël

Chad December 18, 2013 at 10:48 pm

Jim: Can you post the stacktrace? Also, “print alphabet” to make sure it is reading the correct file? You should just need to import maya.cmds in the alphabet.py file.

Mickaël: vrts is an array attribute, so you would need to access the individual elements like vrts[34].vrtx.

Andy Brunton July 16, 2014 at 10:20 am

I just wanted to say thank you. You just saved my sanity by pointing out cmds.flushUndo()

Thank you!!

😀

Tobias Sabathius August 25, 2014 at 11:45 am

Chad,
I have found your blog immensely useful and informative.
I am creating a plugin to import short animations to be applied to simple block men and am relativekly new to animation in Maya.

I have run into the following problem.
When i apply x,y,z coords from the anim file and setTranslation (boneName, x, y, z) the position of the object is never where I expect it should be.
If I use “float $pos[] = ‘xform -q -ws -t’; in the script editor the return is never what was entered in the setTransform.

I was wondering if there was a command which would set the position absolutely in world space relative to the origin, regardless of the history, inheritance or relative positioning of the object.

I have read round and around on the topic and found nothing that explains the issue i am having or a possible solution.

Any insight would be welcome

Many thanks

Chad September 17, 2014 at 9:13 am

You’ll need to apply the transforms in breadth first order…parents first, then children because moving the parents will move the children. I believe xform will let you move things in worldspace.

Leave A Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

<code> For inline code.
[sourcecode] For multiline code.