Keywords: Maya, Scripting, Python, MEL, PyMel

Common Cases

How to enable command line running

If want to run python script in background process without GUI, you should initialize maya env at first.
On task beginning:

import maya.standalone
maya.standalone.initialize()

On task end:

maya.standalone.uninitialize() 

origin: Initializing and uninitializing in Python

If want to import fbx file in CLI, must load fbx plugin at first, otherwise you would get error RuntimeError: Unrecognized file or RuntimeError: Invalid file type specified: FBX.

Load fbx plugin:

import maya.cmds as cmds

cmds.loadPlugin("fbxmaya")
How to import fbx file
import maya.cmds as cmds
from os.path import join

my_filename = "test.fbx"
my_folder = "C:/workspace/maya"
file_path = join(my_folder, my_filename).replace('\\', '/')

# ReturnNewNodes(rnn) flag will tell us if it added anything
new_nodes = cmds.file(file_path, i=True, rnn=True)
print("Nodes added: {}".format(new_nodes))

# If nothing was added to the scene, import as reference
if not new_nodes:
    cmds.file(file_path, mnc=True, reference=True)
    # Make reference part of the scene
    cmds.file(file_path, importReference=True)

Origin:
https://stackoverflow.com/a/68227985/1645289

How to export fbx file
import maya.mel as mel
import maya.cmds as cmds
# select object wanted to export
cmds.select( 'pCylinder1', visible=True )
#-s means export selection only
mel.eval("FBXExport -f \"{0}\" -s".format("D:/pCylinder1.fbx"))

Don’t export fbx file to system drive in Windows, e.g. C:/test.fbx, and drive character must be upper case. Otherwise script would run failed with error.

Ohter options:

mel.eval('FBXResetExport')
mel.eval('FBXExportBakeComplexAnimation -v 1')
mel.eval('FBXExportInAscii -v 1')
mel.eval('FBXExportFileVersion -v FBX20200')
mel.eval('FBXExportSmoothMesh -v true')
mel.eval('FBXExportUseSceneName -v true')
mel.eval('FBXExportUpAxis y')
mel.eval('FBXExportCameras -v false')
mel.eval('FBXExportLights -v false')
How to get node list

Get all selected objects:

from maya import cmds
selList = cmds.ls(selection=True, long=True) or []
for obj in selList:
    print(obj)

Get all geometry objects in scene:

from maya import cmds
geometryList = cmds.ls(geometry=True, long=True)
for geo in geometryList:
    print(geo)

Select all transform nodes in scene:

import maya.cmds as cmds
nodes = cmds.ls(type = ['transform'])
nodesLen = len(nodes)
for i in range(0, nodesLen):
    print(nodes[i])
    cmds.select(nodes[i], add=True)

Get all objects and check object type:

from maya import cmds

## dag=True : We will only get objects which are listed in the outliner, and none of the hidden objects inside of Maya.
objList = cmds.ls(dag=True, long=True)
for geo in objList:
    if cmds.objectType(geo) == "mesh":
        print(geo)

Get all child nodes (take curve as an example, modify the line width in batch):

from maya import cmds

result = set()
selList = cmds.ls(selection=True, long=True) or []
for obj in selList:
        children = set(cmds.listRelatives(obj, fullPath=True) or [])
        while children:
            result.update(children)
            children = set(cmds.listRelatives(children, fullPath=True) or []) - result

for node in result:
    #print(node)
    if cmds.objectType(node) == "nurbsCurve":
        print(node)
        cmds.setAttr(node  + '.lineWidth', 2)

Select all child node (take joint as an example, select all child joint under the selected nodes):

from maya import cmds

result = set()
selList = cmds.ls(selection=True, long=True) or []
for obj in selList:
        children = set(cmds.listRelatives(obj, fullPath=True) or [])
        while children:
            result.update(children)
            children = set(cmds.listRelatives(children, fullPath=True) or []) - result

firstSelect = False
for node in result:
    if cmds.objectType(node) == "joint":
        if firstSelect:
            # add=True means to select multiple nodes at a time
            cmds.select(node, add=True)
        else:
            cmds.select(node)
            firstSelect = True

Select all child nodes (MEL: select -hi):

string $target = "root";
select -hi $target;
string $joints[] = `ls -sl`;
for($joint in $joints)
{
    print($joint + "\n");
}

Select nodes by type (MEL):

string $joints[] = `ls -type joint`;
for($joint in $joints)
{
    print($joint + "\n");
}
How to rename all mesh objects listed in Outliner

Example:

from maya import cmds

objList = cmds.ls(dag=True, long=True)
lst = []
for obj in objList:
    isMeshTransform = False
    
    if cmds.objectType(obj) == "transform":
        meshList = cmds.listRelatives(obj, children=True, fullPath=True)
        if meshList is not None:
            for mesh in meshList:
                if cmds.objectType(mesh) == "mesh":
                    isMeshTransform = True
                    break
    
    if isMeshTransform:
        lst.append(obj)

for obj in lst:
    newName = "www_#"
    cmds.rename(obj, newName)
How to rename duplicate Objects
import re
from maya import cmds
def renameDuplicates():
    #Find all objects that have the same shortname as another
    #We can indentify them because they have | in the name
    duplicates = [f for f in cmds.ls() if '|' in f]
    #Sort them by hierarchy so that we don't rename a parent before a child.
    duplicates.sort(key=lambda obj: obj.count('|'), reverse=True)
     
    #if we have duplicates, rename them
    if duplicates:
        for name in duplicates:
            # extract the base name
            m = re.compile("[^|]*$").search(name) 
            shortname = m.group(0)
 
            # extract the numeric suffix
            m2 = re.compile(".*[^0-9]").match(shortname) 
            if m2:
                stripSuffix = m2.group(0)
            else:
                stripSuffix = shortname
             
            #rename, adding '#' as the suffix, which tells maya to find the next available number
            newname = cmds.rename(name, (stripSuffix + "#")) 
            print("renamed %s to %s" % (name, newname))
             
        return "Renamed %s objects with duplicated name." % len(duplicates)
    else:
        return "No Duplicates"
         
renameDuplicates()

Origin:
https://erwanleroy.com/maya-python-renaming-duplicate-objects/

How to drop object to floor
import pymel.core as pm

def drop_to_floor():
    """
    Iterate over the user's selection and drop every object to the origin plane.
    """
    for transform in pm.ls(selection=True):
        # This is dirty, but its an easy way to make sure the bounding box is in world space.
        # Create a temp duplicate of the object and freeze its transforms
        temp_dupe = pm.duplicate(transform)[0]
        pm.makeIdentity(temp_dupe, apply=True, translate=True, rotate=True, scale=True, normal=True)
    
        # Query the pivot points and bounding box information, then calculate a new height
        current_position = pm.xform(transform, translation=True, worldSpace=True, query=True)
        bounding_box = pm.xform(temp_dupe, boundingBox=True, query=True, objectSpace=True)
        pivots = pm.xform(temp_dupe, pivots=True, query=True, worldSpace=True)
        new_height = pivots[1] - bounding_box[1]
    
        # Delete the temp and set the original object's new height
        pm.delete(temp_dupe)
        pm.xform(transform, translation=[current_position[0], new_height, current_position[2]], worldSpace=True)

if __name__ == '__main__':
    drop_to_floor()

Origin:
https://www.reddit.com/r/Maya/comments/m7tdzx/comment/grglstl/?utm_source=share&utm_medium=web2x&context=3

How to list all materials used in scene

CLI without graphics:

from maya import cmds
# Doing it with purely cmds
def get_materials_in_scene():
    for shading_engine in cmds.ls(type='shadingEngine'):
        if cmds.sets(shading_engine, q=True):
            for material in cmds.ls(cmds.listConnections(shading_engine), materials=True):
                yield material

Pymel version (GUI input bar):

import pymel.core as pm
def get_materials_in_scene():
    # No need to pass in a string to `type`, if you don't want to.
    for shading_engine in pm.ls(type=pm.nt.ShadingEngine):
        # ShadingEngines are collections, so you can check against their length
        if len(shading_engine):
            # You can call listConnections directly on the attribute you're looking for.
            for material in shading_engine.surfaceShader.listConnections():
                yield material

One line code:

# And because it is stupid, here it is as a one liner
sorted(set(cmds.ls([mat for item in cmds.ls(type='shadingEngine') for mat in cmds.listConnections(item) if cmds.sets(item, q=True)], materials=True)))

Origin:
https://discourse.techart.online/t/list-all-materials-used-in-scene/10185/4

Advanced Cases

List all children with instances

Origin: BigRoy - gist.github.com

"""When using maya.cmds.listRelatives with allDescendents=True it will only return the first instanced child.
Below are some example functions that correctly return all instanced children where they are "somewhat" optimized to rapidly return a result as opposed to slow recursive queries.
"""
import maya.api.OpenMaya as om
from maya import cmds
import time
import re

def iter_parents(node):
    """Iter parents of node from its long name.
    Note: The `node` *must* be the long node name.
    Args:
        node (str): Node long name.
    Yields:
        str: All parent node names (long names)
    """
    while True:
        split = node.rsplit("|", 1)
        if len(split) == 1 or not split[0]:
            return
        
        node = split[0]
        yield node

def get_highest_in_hierarchy(nodes):
    """Return highest nodes in the hierarchy that are in the `nodes` list.
    The "highest in hierarchy" are the nodes closest to world: top-most level.
    Args:
        nodes (list): The nodes in which find the highest in hierarchies.
    Returns:
        list: The highest nodes from the input nodes.
    """

    # Ensure we use long names
    nodes = cmds.ls(nodes, long=True)
    if len(nodes) < 2:
        return nodes
    
    lookup = set(nodes)

    highest = []
    for node in nodes:
        # If no parents are within the nodes input list
        # then this is a highest node
        if not any(n in lookup for n in iter_parents(node)):
            highest.append(node)

    return highest

def list_all_children(nodes):
    """Fast, but slow when nesting is very deep."""
    
    result = set()
    children = set(cmds.listRelatives(nodes, fullPath=True) or [])
    while children:
        result.update(children)
        children = set(cmds.listRelatives(children, fullPath=True) or []) - result
        
    return list(result)

def list_all_children2(nodes):
    """Invalid because it will ignore a child if it's in 'nodes'
    whilst it was a child of another node in the list."""
    
    nodes = cmds.ls(nodes, long=True)
    
    lookup = set(nodes) # parent lookup   
    hierarchy = cmds.ls(nodes, dag=True, long=True, allPaths=True)
    children = []
    for node in hierarchy:
        
        if node in lookup:
            # Ignore self
            continue
        
        if not any(node.startswith(x) for x in lookup):
            # Only include if it has a parent in the original nodes
            continue
        
        children.append(node)
    
    return children

def list_all_children4(nodes):
    """
    Fastest with few input `nodes` (<1000)
    """ 
    
    roots = get_highest_in_hierarchy(nodes)
    
    # Escape the | in the node paths, this is faster than re.escape()
    # And also join an extra "\|" to make sure it will only capture
    # its children.
    src = "|"
    to = r"\|"
    prefixes = ["".join([n.replace(src, to), to]) for n in roots]
    rx = re.compile(''.join(['^(?:', '|'.join(prefixes), ')']))
    
    hierarchy = cmds.ls(roots, dag=True, long=True, allPaths=True)
    return [child for child in children if rx.match(child)]

def list_all_children5(nodes):
    """
    Fastest with many input `nodes` (1000+)
    """ 
    
    sel = om.MSelectionList()
    traversed = set()
    iterator = om.MItDag(om.MItDag.kDepthFirst)
    for node in nodes:
        
        if node in traversed:
            # Ignore if already processed as a child
            # before
            continue
        
        sel.clear()    
        sel.add(node)
        dag = sel.getDagPath(0)
        
        iterator.reset(dag)
        iterator.next() # ignore self
        while not iterator.isDone():
            
            path = iterator.fullPathName()
        
            if path in traversed:
                iterator.prune()
                iterator.next()
                continue
            
            traversed.add(path)
            iterator.next()
            
    return list(traversed)

def list_all_children6(nodes):
    """Quite slow but accurate - just use Select > Select Hierarchy as trick.."""
    
    sel = cmds.ls(sl=1)
    cmds.select(nodes, hierarchy=True, replace=True)
    hierarchy = cmds.ls(selection=True, long=True)
    cmds.select(sel, replace=True)
    return hierarchy

members = cmds.ls(sl=1, long=True)

s = time.time()
print("list_all_children")
children = list_all_children(members)
e = time.time()
num = len(children)
print num
print e-s

s = time.time()
print("list_all_children4")
children = list_all_children4(members)
e = time.time()
num = len(children)
print num
print e-s

s = time.time()
print("list_all_children5")
children = list_all_children5(members)
e = time.time()
num = len(children)
print num
print e-s

Maya Rigging Script

Create Control
import maya.cmds as cmds

# Function to create a control for each item in given list
# at that item's position and orientation in worldspace.
def CreateControl(joint_name_list=[]):
    for joint_name in joint_name_list:
        circle_name = cmds.circle(n="{0}_CTL".format(joint_name), nr=(1,0,0) )[0]
        group_name = cmds.group(circle_name, n="{0}_OFFSET".format(joint_name) )
        cmds.matchTransform(group_name, joint_name)
        cmds.pointConstraint(circle_name, joint_name)
        cmds.orientConstraint(circle_name, joint_name)
        
# Select desired joints, then call your function.
CreateControl(cmds.ls(sl=1))

## OR ##

# Feed a list of joints, then call your function.
joint_name_list = ["thigh_JNT", "knee_JNT", "ankle_JNT"]
CreateControl(joint_name_list)

## OR ##

# Feed it a list of every joint object in the scene, then call your function.
joint_name_list = cmds.ls(type='joint')
CreateControl(joint_name_list)

Origin:Selecting Joints in hierarchy one at a time in Maya - reddit.com

Issues

Cannot find procedure “internalTimeUnitStringToDisplayFPSString”

Error on executing cmds.file, code:

new_nodes = cmds.file('D:/test01.fbx', i=True, rnn=True, type="FBX")

Log:

Warning: Frame rate mismatch:  The imported scene frame rate 'ntsc' differs from the existing frame rate 'film'.
Imported animation keys may not match scene frames and become fractional.
File read in  0.31 seconds.
Error: line 1: Cannot find procedure "internalTimeUnitStringToDisplayFPSString".

Solution:
Use o=True instead of i=True.

new_nodes = cmds.file('D:/test01.fbx', o=True, rnn=True, type="FBX")
RuntimeError: Unsaved changes.

Error on executing cmds.file(), code:

new_nodes = cmds.file('D:/test01.fbx', o=True, rnn=True, type="FBX")

Log:

RuntimeError: Unsaved changes.

Solution:
Run cmds.file(new=True, force=True) before open new file.

cmds.file(new=True, force=True)
new_nodes = cmds.file('D:/test01.fbx', o=True, rnn=True, type="FBX")

Origin:
https://stackoverflow.com/a/43438675/1645289

References

Blogs

Python maya.cmds模塊代碼示例
https://vimsky.com/zh-tw/examples/detail/python-module-maya.cmds.html

Roy Nieterau
https://gist.github.com/BigRoy

BATCHING ASSETS USING HEADLESS MAYA
https://www.simplygon.com/posts/81ddb551-d62e-4634-b02b-de1fb230a585

Roy Nieterau
https://gist.github.com/BigRoy


“Whatever is rejected from the self, appears in the world as an event.” ― Carl Gustav Jung