[Maya]Scripting Notes
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()
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