Chad Vernon
  • Home
  • Reel/Resume
  • Work
  • Resources
  • About
  • Contact
sideBar

Search

Categories

  • CG
  • cvxporter
  • Maya
    • API
    • Plug-ins
  • Personal

Archives

  • May 2012
  • March 2012
  • September 2011
  • June 2011
  • March 2011
  • December 2010
  • November 2010
  • September 2010
  • August 2010
  • July 2010
  • May 2010
  • April 2010
  • March 2010
  • December 2009
  • October 2009
  • September 2009
  • August 2009
  • July 2009
  • June 2009
  • May 2009
  • April 2009
  • March 2009
  • February 2009
  • January 2009
  • December 2008
  • November 2008
  • October 2008
  • September 2008
  • August 2008
  • July 2008
  • June 2008
  • May 2008
  • April 2008
  • March 2008
  • February 2008
  • January 2008
  • December 2007
  • November 2007
  • October 2007
  • September 2007
  • August 2007

Rss

  • Main Entries RSS
  • Comments RSS
Home » Resources » Python Scripting for Maya Artists » Python in Maya

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

Or

import maya.cmds as mc

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

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

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, and E stand for Create, Query, and Edit (You can ignore the M, I never pay attention to it). 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.

>>> x = cmds.polySphere(radius=2.5)
>>> print cmds.polySphere(x[1], 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.

>>> x = cmds.polySphere()
>>> print cmds.polySphere(x[0], query=True, radius=True)
1.0
>>> print cmds.polySphere(x[0], 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.

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

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. 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 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 changeLightIntensity(percentage=1.0):
    """
    Changes the intensity of each light in the scene by a percentange.

    Parameters:
        percentage - Percentange to change each light's intensity. Default value is 1.

    Returns:
        Nothing.

    """
    # 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.
    lightsInScene = cmds.ls(type='light')

    # If there are no lights in the scene, there is no point running this script
    if not lightsInScene:
        raise RuntimeError, 'There are no lights in the scene!'

    # Loop through each light
    for light in lightsInScene:
        # The getAttr command is used to get attribute values of a node
        currentIntensity = cmds.getAttr('%s.intensity' % light)
        # Calculate a new intensity
        newIntensity = currentIntensity * percentage
        # Change the lights intensity to the new intensity
        cmds.setAttr('%s.intensity' % light, newIntensity)
        # Report to the user what we just did
        print 'Changed the intensity of light %s from %.3f to %.3f' % (light, currentIntensity, newIntensity)

Concepts used: functions, lists, for loops, conditional statements, string formatting, exceptions.
To run this script, in the script editor type:

import samples.lightIntensity as lightIntensity
lightIntensity.changeLightIntensity(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.

    Parameters:
        name - A renaming format string.  The string must contain a consecutive
               sequence of '#' characters
        nodes - List of root nodes you want to rename.  If this argument is omitted,
               the script will use the currently selected nodes.

    Returns:
        Nothing.
    """

    # 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 == 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 nodes == None:
            raise RuntimeError, 'Select a root node to rename.'

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

    # We need to verify that all the '#' characters are in one sequence.
    substring = '#' * numDigits             # '#' * 3 is the same as '###'
    newsubstring = '0' * numDigits          # '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' + str(numDigits) + 'd')

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

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

    Parameters:
        node - The node to rename.
        name - A renaming format string.  The string must contain a
               consecutive sequence of '#' characters
        number - The number to use in the renaming.

    Returns:
        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
    newName = (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, newName)

    # 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', fullPath=True)

    # Since we renamed the current node, we increment the number for the next
    # node to be renamed.
    number += 1
    if children:
        for child in children:
            # We will call the renameChain function for each child of this node.
            number = renameChain(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 samples.renamer as 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 function 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.

blendShapes.py

import maya.cmds as cmds
import os

# The weights of these shapes are the product of the weights of the two listed shapes
COMBINATION_SHAPES = {
    'innerbrowraiser'                     : ('browsdown', 'browsup'),
    'outerbrowraiser'                     : ('browsup', 'nosewrinkler'),
    'nosewrinklesmile'                    : ('nosewrinkler', 'lipcornerpuller'),
    'eyesclosedsquint'                    : ('eyesclosed', 'squint'),
    'browsdowneyesclosedsquint'           : ('eyesclosedsquint', 'browsdown'),
    'nosewrinklerbrowsdown'               : ('nosewrinkler', 'browsdown'),
    'innerbrowraisernosewrinkler'         : ('innerbrowraiser', 'nosewrinkler'),
    'lipcornerdepressorpucker'            : ('lipcornerdepressor', 'lippucker'),
    'lipcornerpullerpucker'               : ('lipcornerpuller', 'lippucker'),
    'lipcornerpullerupperlipraiser'       : ('lipcornerpuller', 'upperlipraiser'),
    'eyeslookupleft'                      : ('eyeslookup', 'eyeslookleft'),
    'eyeslookupright'                     : ('eyeslookup', 'eyeslookright'),
    'eyeslookdownleft'                    : ('eyeslookdown', 'eyeslookleft'),
    'eyeslookdownright'                   : ('eyeslookdown', 'eyeslookright'),
}

def importShapes(directory):
    """
    Imports the shapes from directory.

    Parameters:
        directory - Directory that holds all the shape obj's.

    Returns:
        The created blendShape node, the neutral transform, and a list of all the imported shape transforms.
    """
    # Get shape file paths
    shapes = []
    for root, dirs, files in os.walk(directory):
        for name in files:
            path = os.path.join(root, name)
            if name.startswith('neutral') and name.endswith('.obj'):
                # Put the neutral at the start of the list
                shapes.insert(0, path)
            elif name.endswith('.obj'):
                # Append the shape to the list
                shapes.append(path)

    # Create node to control all shape weights in 0-1 range.
    faceShapesTransform = cmds.createNode('transform', name='faceShapes')

    targets = []
    neutral = ''

    # Import shapes from blendshape folder and create blendShape node on neutral
    for shape in shapes:
        # Import the shape into a namespace called TEMP
        cmds.file(shape, i=True, namespace='TEMP', type='OBJ', options='mo=0')
        # Get the imported shape
        transform = cmds.ls('TEMP:*', type='transform')[0]
        # Rename the shape based off of the file name
        newName = os.path.basename(shape).split('.')[0]
        transform = cmds.rename(transform, newName)
        print 'Importing %s' % transform
        # Delete everything else in the namespace
        try:
            cmds.delete('TEMP:*')
        except:
            pass
        # Remove the temporary namespace since we don't need it anymore
        cmds.namespace(removeNamespace='TEMP')

        if transform.startswith('neutral'):
            # Create blendShape on neutral.
            neutral = transform
            # Unfreeze the normals, sometimes they get frozen in obj's
            cmds.polyNormalPerVertex(transform, unFreezeNormal=True)
            # Soften the normals on the neutral
            cmds.polySoftEdge(transform, angle=180)
            # Delete history on the neutral
            cmds.delete(transform, constructionHistory=True)
            # Create a blendshape on the neutral
            blendShape = cmds.blendShape(neutral, name='faceShapes_BLS')[0]
        else:
            # Store the target transform in the list
            targets.append(transform)

    # Sort the targets alphabetically
    targets.sort()

    # index will store the index of the target on the blendshape node
    index = 0

    # inbetweens will store all the inbetween targets and what index they belong to on
    # the blendshape node
    inbetweens = []

    for target in targets:
        if target.endswith('_100'):
            # Strip off the '_100' from the name
            target = cmds.rename(target, target[:-4])
            # Add the target to the blendshape
            addShapeToBlendShape(neutral, blendShape, target, index, faceShapesTransform)
            index += 1
        else:
            # Add inbetweens later since the target needs to exist first
            inbetweens.append((target, index))

    # Add the inbetweens
    for node in inbetweens:
        # Called the weight at which the inbetween should turn on between 0-1
        onIndex = float('%.3f' % (int(node[0].split('_')[-1]) / 100.0))
        # Add the inbetween target to the blendshape
        addShapeToBlendShape(neutral, blendShape, node[0], node[1], faceShapesTransform, onIndex)

    # Connect combination shapes
    for key in COMBINATION_SHAPES.keys():
        # Combination shapes are blendshapes on top of other blendshapes
        # When two or more shapes are turned on, they trigger another shape
        # to be turned on
        # Create a multiplyDivide node to multiply two weights together
        mdn = cmds.createNode('multiplyDivide', name='%s_MDN' % key)
        # Hook up the inputs to the multiplyDivide node
        cmds.connectAttr('%s.%s' % (faceShapesTransform, COMBINATION_SHAPES[key][0]), '%s.input1X' % mdn)
        cmds.connectAttr('%s.%s' % (faceShapesTransform, COMBINATION_SHAPES[key][1]), '%s.input2X' % mdn)
        print 'Connecting combination shape %s driven by the product of %s and %s' % (key,
            COMBINATION_SHAPES[key][0], COMBINATION_SHAPES[key][1])
        # Connect the output of the multiplyDivide node to the blendShape control
        cmds.connectAttr('%s.outputX' % mdn, '%s.%s' % (faceShapesTransform, key))

def addShapeToBlendShape(neutral, blendShape, mesh, index, faceShapesTransform, onIndex=1.0):
    """
    Adds a shape to the blendShape node.

    Parameters:
        neutral         - The neutral mesh
        blendShape      - The blendShape node to add shapes to.
        mesh            - Target mesh
        index           - BlendShape target index
        faceShapesTransform  - Node to add control attributes to.
        onIndex         - Index at which the target is fully on.  Normally this
                          is 1.0.  For inbetweens, this is between 0 and 1.

   Returns:
        Nothing
    """

    if onIndex == 1.0:
        # Add a new shape
        print 'Adding shape %s to %s at index %d' % (mesh, blendShape, index)
        cmds.blendShape(blendShape, edit=True, target=(neutral, index, mesh, onIndex))
    else:
        # Add an inbetween shape
        print 'Adding inbetween shape %s to %s at index %d with an onWeight of %.3f' % (mesh, blendShape, index, onIndex)
        cmds.blendShape(blendShape, edit=True, target=(neutral, index, mesh, onIndex), inBetween=True)

    if onIndex == 1.0:
        # Add attribute to the control node to control this blendshape target
        cmds.addAttr(faceShapesTransform, longName=mesh, shortName=mesh, attributeType='float', keyable=True, min=0.0, max=1.0)
        # Connect the new control attribute to the blendshape weight
        cmds.connectAttr('%s.%s' % (faceShapesTransform, mesh), '%s.%s' % (blendShape, mesh))
    cmds.delete(mesh)
    # Sometimes Maya crashes when it runs out of memory so free some up by clear the
    # undo queue.
    cmds.flushUndo()

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

To run this script, in the script editor type:

import samples.blendShapes as blendShapes
blendShapes.importShapes(r”C:\pathToBlendShapesFolder”)

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.
Run “mayapy” from the Run… dialog in Windows, or run “mayapy” from a command line:

Figure 16 - Running Maya from the Command Line

Figure 16 - Running Maya from the Command Line

To open a file in batch 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 assignDefaultShader(fileToOpen):
    # Start Maya in batch mode
    maya.standalone.initialize(name='python')

    # Open the file with the file command
    cmds.file(fileToOpen, 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)

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 optparse module which would allow you to start the mayapy interpreter and call the script all in one line from the command line.

4 Responses to “Python in Maya”

Subscribes to this topic Comment RSS or TrackBack URL

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

haiqiao on April 2nd, 2011 at 4:36 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

Benjamin on October 4th, 2011 at 2:07 am

Trackbacks & Pingbacks

[...] Python in Maya http://www.chadvernon.com/blog/resources/python-scripting-for-maya-artists/python-in-maya/ [...]

Pingback from Python in Maya | Ben's Computer Graphics Blog in July 29th, 2011 at 11:02 am

[...] other, more informative posts on how to get Python running in standalone mode (I learned a lot from this one in particular), but the gist of it is that you start by running /bin/mayapy.exe, and then initialize the Maya [...]

Pingback from python in maya standalone » Toadstorm Nerdblog in February 22nd, 2012 at 10:49 pm

Leave A Reply

Allowed tag : <blockquote>, <p>, <code>, <em>, <small>, <ul>, <li>, <ol>, <a href=>..

 Username

 Email Address

 Website

Sticky: Always double check your comment before posting Please Note: Comment Moderation Maybe Active So There Is No Need To Resubmit Your Comments
Home » Resources » Python Scripting for Maya Artists » Python in Maya

© 2011 Chad Vernon