Maya & Scripting

Maya Python Snippets 1

A first-week reference for using Python inside Maya. The examples move from reading Maya’s commands and building a small joint chain to naming, selection, loops, and reusable functions. Run snippets in Maya’s Python tab; several examples intentionally demonstrate errors.

#Maya #Python #Rigging

1.1 Echo: From Clicks to Code (A) [MEL - read only, do not run in Python tab]

Maya can show the MEL command generated by a UI action. This is useful for discovering what an operation does; this block is MEL syntax and should not be pasted into the Python tab.

// History panel after you click the Joint tool:
joint -p 0 0 0;
joint -p 0 5 0 -n "test";
// Result: test //

1.1 Echo: From Clicks to Code (B)

The Python version calls the same joint-creation commands through maya.cmds. The cmds module is Maya’s command interface for scene operations.

import maya.cmds as cmds

cmds.joint(p=(0, 0, 0))
cmds.joint(p=(0, 5, 0), n="test")

1.2 Print, Comments, Versions

Use print() to inspect values and # to leave notes that Python ignores. sys.version reports Maya’s bundled Python version; cmds.about() reports the Maya version.

A comment: Python ignores this line

import sys
import maya.cmds as cmds

print(sys.version)
print(cmds.about(version=True))
print("Hello, rigger")
print(2 + 3)
print("2 + 3")
print("2" + "3")

2.1 Build the Test Arm (A)

These commands create three joints in sequence. Maya parents each newly created joint under the previously selected joint, forming a simple hierarchy.

import maya.cmds as cmds

cmds.select(clear=True)
cmds.joint(name="L_shoulder_jnt", position=(2, 15, 0))
cmds.joint(name="L_elbow_jnt",    position=(6, 15, -0.5))
cmds.joint(name="L_wrist_jnt",    position=(10, 15, 0))

2.1 Build the Test Arm (B)

edit=True changes an existing joint chain. Joint orientation defines the local axes that rotations and rig controls will use.

cmds.joint("L_shoulder_jnt", edit=True, orientJoint="xyz",
           secondaryAxisOrient="yup", children=True,
           zeroScaleOrient=True)

2.3 Create, Query, Edit

A query reads scene data, while a set/edit command changes it. worldSpace=True asks for a position in world coordinates rather than relative to a parent.

jnt = "L_elbow_jnt"

print(cmds.xform(jnt, query=True, worldSpace=True, translation=True))
print(cmds.getAttr(f"{jnt}.translateX"))

cmds.setAttr(f"{jnt}.radius", 2)
cmds.xform("L_wrist_jnt", worldSpace=True, translation=(10, 14, 0))

2.5 Getting Help

Maya’s command help is a quick way to inspect flags and usage from the Script Editor. The Python Commands reference provides the full command documentation.

cmds.help("xform")
cmds.help("joint")

Docs: help.autodesk.com/view/MAYAUL/2026/ENU/

Technical Documentation > Python Commands

3.1 Variables and Types

Variables give useful names to values. Maya scripts commonly use strings for node names, numbers for dimensions, booleans for options, lists for collections, and tuples for fixed coordinate values.

joint_count = 3
radius = 1.5
side = "L"
is_ik = True
joints = ["L_shoulder_jnt", "L_elbow_jnt", "L_wrist_jnt"]
pos = (2, 15, 0)

for value in (joint_count, radius, side, is_ik, joints, pos):
    print(type(value), value)

4.2 Four Ways to Build a Name

All four forms assemble the same name. F-strings are usually easiest to read; :02d formats an integer with at least two digits, such as 01.

side = "L"
part = "arm"
kind = "ctrl"
num = 1

print(side + "_" + part + "_" + kind + "_" + str(num).zfill(2))
print("%s_%s_%s_%02d" % (side, part, kind, num))
print("{}_{}_{}_{:02d}".format(side, part, kind, num))
print(f"{side}_{part}_{kind}_{num:02d}")

4.4 Taking Names Apart

split() turns a delimited name into a list of parts; unpacking assigns those parts to separate variables. join() combines parts back into one string.

name = "L_arm_ik_ctrl"
parts = name.split("_")
print(parts)

side, part, mode, kind = parts
print(side, part, mode, kind)

print("_".join(["R", part, mode, kind]))

4.5 The Replace Bug Hunt (A) [CONTAINS ERRORS ON PURPOSE]

replace() changes every matching substring, not just a leading side prefix. The examples show why blind replacement can alter a name unexpectedly.

print("L_arm_ctrl".replace("L_", "R_"))
print("L_leg_L_ctrl".replace("L_", "R_"))
print("FL_hoof_ctrl".replace("L_", "R_"))

4.5 The Replace Bug Hunt (B) [CONTAINS ERRORS ON PURPOSE]

startswith() checks only the beginning of a name, so it avoids changing an L_ that appears later in a word such as FL_hoof_ctrl.

name = "FL_hoof_ctrl"

if name.startswith("L_"):
    print("R_" + name[2:])
else:
    print("No L_ prefix, leaving alone:", name)

5.1 Freeze a Control (A) [needs L_elbow_jnt from 2.1 / Snippet 1]

The circle is snapped to the elbow joint, then its transform values are inspected. This distinguishes the visible world position from the node’s stored translate values.

ctrl = cmds.circle(name="demoA_ctrl", normal=(1, 0, 0),
                   radius=2, constructionHistory=False)[0]
cmds.matchTransform(ctrl, "L_elbow_jnt", pos=True, rot=True)

print(cmds.getAttr(f"{ctrl}.translate"))
print(cmds.xform(ctrl, q=True, ws=True, t=True))

5.1 Freeze a Control (B)

makeIdentity() applies transforms to the shape and resets the control’s transform channels. This is often called freezing transforms.

See: Snippet 1 (test arm);

cmds.makeIdentity(ctrl, apply=True,
                  translate=True, rotate=True, scale=True)

print(cmds.getAttr(f"{ctrl}.translate"))
cmds.setAttr(f"{ctrl}.rotateY", 45)

5.2 Control Inside a Group

A group can hold the control at the joint location while the control itself keeps clean local transforms. This offset-group pattern is common in rigging.

ctrl2 = cmds.circle(name="demoB_ctrl", normal=(1, 0, 0),
                    radius=2, constructionHistory=False)[0]
grp = cmds.group(ctrl2, name="demoB_grp")
cmds.matchTransform(grp, "L_elbow_jnt", pos=True, rot=True)

print(cmds.getAttr(f"{ctrl2}.translate"))
print(cmds.getAttr(f"{grp}.translate"))
cmds.setAttr(f"{ctrl2}.rotateY", 45)

6.1 Guarding with if / else

The selection command returns a list. The condition handles an empty selection before the script tries to use an item from that list.

sel = cmds.ls(selection=True)

if not sel:
    cmds.warning("Select a joint first.")
else:
    print(f"Working on {sel[0]}")

6.3 Reading Tracebacks

Each example triggers a different common error. Read the final error type and the line number first; then check whether the value has the expected type, name, or index.

# Error 1 - run alone
sel = cmds.ls(selection=True)
print("Selected: " + sel)

# Error 2 - run alone
print(my_joint)

# Error 3 - run alone
print([1, 2, 3][5])

# Error 4 - run alone
cmds.xform("not_a_real_node", query=True, translation=True)

7.1 Your First Loop

A for loop repeats the same operation for each item in a list, which avoids writing nearly identical lines by hand.

arm = ["L_shoulder_jnt", "L_elbow_jnt", "L_wrist_jnt"]

for jnt in arm:
    print(jnt)

7.5 Build a Spine with a Loop

range(1, 6) produces 1 through 5. The formatted index keeps joint names consistently padded, and the position expression spaces them along Y.

cmds.select(clear=True)

for i in range(1, 6):
    cmds.joint(name=f"spine_{i:02d}_jnt",
               position=(0, 10 + i * 2, 0))

8.1 Your First Function

A function packages a repeatable operation. Parameters provide inputs, and return sends a result back to the caller. The final make_name2 call is an intentional undefined-name error.

def make_name(side, part, kind):
    return f"{side}_{part}_{kind}"

print(make_name("L", "arm", "ctrl"))
print(make_name(kind="grp", part="leg", side="R"))

print(make_name2("L", "arm", "ctrl"))

8.2 make_ctrl: One-Call Control (A)

This first part defines the function and checks that the target exists and the requested control name is available before creating anything.

See: Snippet 3 (complete make_ctrl)

def make_ctrl(name, target, radius=1.0, color=17):
    """Make a zeroed circle control at target.
    Returns (ctrl, group)."""
    if not cmds.objExists(target):
        cmds.warning(f"Target '{target}' does not exist.")
        return None, None
    if cmds.objExists(f"{name}_ctrl"):
        cmds.warning(f"'{name}_ctrl' already exists.")
        return None, None

8.2 make_ctrl: One-Call Control (B) [continues the function from the previous slide]

This continuation creates the curve and its group, then aligns the group to the target so the control can start with clean local transforms.

See: Snippet 3 (complete make_ctrl)

ctrl = cmds.circle(name=f"{name}_ctrl", normal=(1, 0, 0),
                       radius=radius, constructionHistory=False)[0]
    grp = cmds.group(ctrl, name=f"{name}_grp")
    cmds.matchTransform(grp, target, pos=True, rot=True)

8.2 make_ctrl: One-Call Control (C) [continues the function from the previous slide]

The shape override sets display color. Locking scale and visibility helps prevent accidental edits to channels the rig intends to control.

See: Snippet 3 (complete make_ctrl)

shape = cmds.listRelatives(ctrl, shapes=True)[0]
    cmds.setAttr(f"{shape}.overrideEnabled", True)
    cmds.setAttr(f"{shape}.overrideColor", color)

    for attr in ["scaleX", "scaleY", "scaleZ", "visibility"]:
        cmds.setAttr(f"{ctrl}.{attr}", lock=True,
                     keyable=False, channelBox=False)

    return ctrl, grp

print(make_ctrl("L_elbow", "L_elbow_jnt", radius=2, color=6))

8.3 Loop the Function over the Arm

The loop derives a control base name from each joint and calls the reusable function. The joint names must exist in the current Maya scene.

See: Snippet 3 and Snippet 2 (clean-up)

arm = ["L_shoulder_jnt", "L_elbow_jnt", "L_wrist_jnt"]

for jnt in arm:
    base = jnt.replace("_jnt", "")
    make_ctrl(base, jnt, radius=2, color=6)

Snippet 1: Test Arm

Run this setup when you need the sample joints for later demonstrations. It creates and orients a three-joint chain.

Used with: 2.1, 5.1 (builds the test arm if a scene is missing it)

import maya.cmds as cmds
 
cmds.select(clear=True)
cmds.joint(name="L_shoulder_jnt", position=(2, 15, 0))
cmds.joint(name="L_elbow_jnt",    position=(6, 15, -0.5))
cmds.joint(name="L_wrist_jnt",    position=(10, 15, 0))
 
cmds.joint("L_shoulder_jnt", edit=True, orientJoint="xyz",
           secondaryAxisOrient="yup", children=True,
           zeroScaleOrient=True)

Snippet 2: Clean-Up Everything

This removes only the listed demo nodes if they exist. Review the names before running it in a scene that contains work you want to keep.

Used with: 5.1, 5.2, 8.2, 8.3 (deletes the demo objects, keeps the arm)

import maya.cmds as cmds
 
leftovers = ["demoA_ctrl", "demoB_grp", "demoC_grp", "demoD_offset",
             "test_loc", "test_ctrl", "mid_loc", "spine_01_jnt",
             "ret_loc", "ret_grp", "ret_circle", "demoE_ctrl"]
 
for n in leftovers:
    if cmds.objExists(n):
        cmds.delete(n)

Snippet 3: make_ctrl Complete

This is the full reusable version assembled from the earlier steps. It returns both the control curve and its parent group for later rigging operations.

Used with: 8.2, 8.3 (the finished function)

import maya.cmds as cmds
 
def make_ctrl(name, target, radius=1.0, color=17):
    """Make a zeroed circle control at target.
    Returns (ctrl, group)."""
    if not cmds.objExists(target):
        cmds.warning(f"Target '{target}' does not exist.")
        return None, None
    if cmds.objExists(f"{name}_ctrl"):
        cmds.warning(f"'{name}_ctrl' already exists.")
        return None, None
 
    ctrl = cmds.circle(name=f"{name}_ctrl", normal=(1, 0, 0),
                       radius=radius, constructionHistory=False)[0]
    grp = cmds.group(ctrl, name=f"{name}_grp")
    cmds.matchTransform(grp, target, pos=True, rot=True)
 
    shape = cmds.listRelatives(ctrl, shapes=True)[0]
    cmds.setAttr(f"{shape}.overrideEnabled", True)
    cmds.setAttr(f"{shape}.overrideColor", color)
 
    for attr in ["scaleX", "scaleY", "scaleZ", "visibility"]:
        cmds.setAttr(f"{ctrl}.{attr}", lock=True,
                     keyable=False, channelBox=False)
 
    return ctrl, grp

AUTORIG STARTER FILE (also saved as Autorig_Starter.py)

This is a small starting point for a rigging script: one function creates a control, and another helper mirrors a left-side name. The note describes a file copy; keep this reference in sync with that script if you edit the starter separately.

import maya.cmds as cmds

# 
# AUTORIG STARTER FILE  (Python in Maya, Week 1)
# Naming convention: side_part_kind   e.g. L_arm_ctrl, L_arm_grp, L_arm_jnt
# Rule: do it by hand once. The second time, make it a function.
# 

def make_ctrl(name, target, radius=1.0, color=17):
    """Make a zeroed circle control at target.
    Returns (ctrl, group)."""
    if not cmds.objExists(target):
        cmds.warning(f"Target '{target}' does not exist.")
        return None, None
    if cmds.objExists(f"{name}_ctrl"):
        cmds.warning(f"'{name}_ctrl' already exists.")
        return None, None

    ctrl = cmds.circle(name=f"{name}_ctrl", normal=(1, 0, 0),
                       radius=radius, constructionHistory=False)[0]
    grp = cmds.group(ctrl, name=f"{name}_grp")
    cmds.matchTransform(grp, target, pos=True, rot=True)

    shape = cmds.listRelatives(ctrl, shapes=True)[0]
    cmds.setAttr(f"{shape}.overrideEnabled", True)
    cmds.setAttr(f"{shape}.overrideColor", color)

    for attr in ["scaleX", "scaleY", "scaleZ", "visibility"]:
        cmds.setAttr(f"{ctrl}.{attr}", lock=True,
                     keyable=False, channelBox=False)

    return ctrl, grp


def mirror_name(name):
    """L_ name in, R_ name out. Anything else: warn and return None."""
    if name.startswith("L_"):
        return "R_" + name[2:]
    cmds.warning(f"'{name}' does not start with L_")
    return None

More

These are additional working snippets. They demonstrate skeleton creation, selection inspection, control grouping, and parenting controls into a chain; some are rough notes rather than a single ready-to-run script.

#—————————————————————-
#—- Build Skeleton ——————————————
import maya.cmds as cmds

Create the joints

cmds.select(clear=True)
cmds.joint(name=”Spine0”, position=(0, 5, 0))
cmds.joint(name=”Spine1”, position=(0, 7, 0))
cmds.joint(name=”Spine2”, position=(0, 9, 0))
cmds.joint(“Spine0”, edit=True, orientJoint=”xyz”, secondaryAxisOrient=”yup”,
children=True, zeroScaleOrient=True)
joints = [“Spine0”, “Spine1”, “Spine2”]
print(joints)
#—————————————————————-
#—- Create Controls —————————–

A control for each joint: snap it to the joint, then let the control drive the

joint
for jnt in joints:
ctl = cmds.circle(name=jnt + “_Ctl”, normal=(1, 0, 0),
constructionHistory=False)[0]
cmds.delete(cmds.pointConstraint(jnt, ctl))
cmds.delete(cmds.orientConstraint(jnt, ctl))
cmds.pointConstraint(ctl, jnt)
cmds.orientConstraint(ctl, jnt)

Parent each control under the control above it

for i in range(1, len(joints)):
cmds.parent(joints[i] + “_Ctl”, joints[i - 1] + “_Ctl”)
#—————————————————————-
#—- Build Skeleton ——————————————
import maya.cmds as cmds

Create the joints

cmds.select(clear=True)
cmds.joint(name=”Spine0”, position=(0, 5, 0))
cmds.joint(name=”Spine1”, position=(0, 7, 0))
cmds.joint(name=”Spine2”, position=(0, 9, 0))
cmds.joint(“Spine0”, edit=True, orientJoint=”xyz”, secondaryAxisOrient=”yup”,
children=True, zeroScaleOrient=True)
joints = [“Spine0”, “Spine1”, “Spine2”]
print(joints)
#—————————————————————-
#—- Miscellaneous Commands ——————————–
import maya.cmds as cmds

joints = cmds.ls(sl=True)
print(joints)
import maya.cmds as cmds
joints = cmds.ls(sl=True)
print(“, “.join(joints)) # joint1, joint2, joint3
print(“\n”.join(joints)) # one name per line
joints = cmds.ls(sl=True)
for jnt in joints:
print(jnt)
#—————————————————————-
#—- Create Groups for controls —————————–
new_groups = [] # defined BEFORE the loop
for jnt in joints:
base = jnt.replace(“_jnt”, “”)
grp = cmds.group(empty=True, name=f”{base}_grp”)
new_groups.append(grp) # remember each one
print(new_groups)
#—————————————————————-
#—- Create Nulls and Controls —————————–
import maya.cmds as cmds

Create the joints

cmds.select(clear=True)
cmds.joint(name=”Spine0”, position=(0, 3, 0))
cmds.joint(name=”Spine1”, position=(1, 8, 0))
cmds.joint(name=”Spine2”, position=(0, 13, 0))
cmds.joint(“Spine0”, edit=True, orientJoint=”xyz”, secondaryAxisOrient=”yup”,
children=True,)
joints = [“Spine0”, “Spine1”, “Spine2”]
print(joints)

A control and a null group for each joint

for jnt in joints:
ctl = cmds.circle(name=jnt + “_Ctl”, normal=(1, 0, 0), radius=2)[0]
null = cmds.group(empty=True, name=jnt + “_Ctl”)
cmds.matchTransform(null, jnt)
cmds.matchTransform(ctl, jnt)
cmds.parent(ctl, null)
cmds.setAttr(ctl + “.translate”, 0, 0, 0)

cmds.setAttr(ctl + “.rotate”, 0, 0, 0)
cmds.pointConstraint(ctl, jnt)
cmds.orientConstraint(ctl, jnt)

Parent the groups and controls together in their own chain

for i in range(1, len(joints)):
cmds.parent(joints[i] + “_Ctl”, joints[i - 1] + “_Ctl”)