Intro - Maya in Python

Before you read anything below, read this list first

  • Python can use almost all the mel command, maya.cmds is a module that simply wraps MEL commands in Python syntax, like
    import maya.cmds as cmds; cmds.sphere()
  • Python can run directly in mel mode, like
    import maya.mel as mel; skin = mel.eval('findRelatedSkinCluster "'+geoName+'"')
  • Python can call maya C++ API (maya api 1.0), like
    import maya.OpenMaya as om
    import maya.OpenMayaAnim as oma
    aim_dir = cmds.xform(myLoc, q=1, t=1, ws=1)
    aim_vec = om.MVector(*aim_dir)
    rot_quaternion = myObj_axis_vec.rotateTo(aim_vec)
    # myObj_axis_vec is set as x axis direction
    # myObj_fnTransform is set as the transform api function object
  • Python can call maya C++ API 2.0 (partially since maya 2013, and still partially in 2015, maybe better in latest)
    import maya.api.OpenMaya as om2
    aim_dir = cmds.xform(myLoc, q=1, t=1, ws=1)
    aim_vec = om2.MVector(aim_dir) # can take python list automatically
    # also, no need MScriptUtil for pointer to value stuff
    # can directly return value like normal python
    # also, sometimes faster than api 1.0
  • Python can run almost every python module, as long as compatible with maya's internal python version, and add into sys.path.
  • Info on PyMel:
    • PyMel is created to combine above maya.cmds, maya.mel, maya.OpenMaya (api 1.0) into a seamless python module, that do the coding style translation and with extra with object-oriented features and an extra large library of useful modules; thus a python module to call those maya python modules
    • for me, I currently still prefer handle it directly than learning a new syntax called pymel
  • moved all over from my old cgwiki:maya_api

Directed Acyclic Graph (DAG)

  • Simple example, the DAG describes how an instance of an object is constructed from a piece of geometry.
  • *DAG* defines elements such as the position, orientation, and scale of geometry, and DAG is composed of two types of DAG nodes, transforms and shapes.
    1. Transform nodes : Maintain transformation information (position, rotation, scale, etc.) as well as parenting information.
      • For example, if you model a hand, you would like to apply a single transformation to rotate the palm and fingers, rather than rotating each individually-in this case the palm and fingers would share a common parent transformation node.
    2. Shape nodes : Reference geometry and do not provide parenting or transformation information.
      • For example, when you create a sphere, you create both a shape node (the sphere) and a transformation node that allows you to specify the sphere's position, scale, or rotation. The transformation node's shape node is its child.

DAG Path

  • DagPath is a set of nodes which uniquely identifies the location of a particular node or instance of a node in the graph.
  • The path represents a graph ancestry beginning with the root node of the graph and containing, in succession, a particular child of the root node followed by a particular child of this child, etc., down to the node identified by the path.
  • For instanced nodes, there are multiple paths which lead from the root node to the instanced node, one path for each instance. Paths are displayed in Maya by naming each node in the path starting with the root node and separated by the vertical line character, “|”.

Dependency nodes

  • All data within maya is contained within seperate connectable nodes (in maya terminology, dependency nodes). the nodes that accept and output data are called dependency graph nodes.
  • It is used for animation (deform, simulation, audio) and construction history (model creation)
  • All of these node types, are defined by the enumeration MFn::Type. For example, a textured polygonal mesh may be constructed from
    • an MFn::kMesh node for the polygonal surface shape
    • an MFn::kTransform to position the mesh in the scene
    • an MFn::kLambert node for the surface material
    • an MFn::kFileTexture node for the surface texture
  • If at anytime, you need to find out how a set of nodes are connected, then you can use the hypergraph within maya, to show you a graphical representation of the scene data. It is worth remembering that the hypergraph only displays connections between attributes; It does not show the connections between a transform and the child objects underneath it. The outliner can do that for you.

DAG hierarchy nodes vs Dependency Graph (DG) nodes

  • These are related but different systems, used for different purposes.
  • Unlike DAG nodes, dependency graph nodes can be cyclic, meaning they can pass information both directions. (ref)
  • The DAG relates to the DG in that every DAG node is implemented as a DG node, both shapes and transforms. The only “non-DG” component of the DAG is the parenting relationship.
  • The dependency graph (DG) is a collection of entities connected together. Unlike the DAG, these connections can be cyclic, and do not represent a parenting relationship. Instead, the connections in the graph allow data to move from one entity in the graph to another. The entities in the graph which accept input, and output data, are called dependency graph nodes.
  • Most objects in Maya are dependency graph nodes, or networks of nodes (several nodes connected together). For example, DAG nodes are dependency graph nodes, and shaders are networks of nodes.

DG node creation

  • MPxNode: base of DG node
    • virtual compute(const MPlug& plug, MDataBlock& data)
      • MDataHandle inputDH=data.inputValue(inAttrName, &status); float result=calFun( inputDH.asFloat() );
      • MDataHandle outputDH=data.outputValue(outAttrName); outputDH.set(result);
      • data.setClean(plug)
    • return MS::kSuccess;
    • static void* creator()
    • static MStatus initialize()
    • MTypeId yourNode::id( 0x80000); (any local id bet. 0x0000 0000 to 0x0007 ffff)
    • attr: readable(output?); writable(input?); connectable;storable(toFile?);settable;
    • attr: multi(array?); keyable; hidden
    • addAttribute(inAttrName); attributeAffects(whichInput, output)
  • MPxLocatorNode: DG-DAG-locator node, not renderable
  • MPxIkSolverNode: DG-IK solver (doSolve())
  • MPxDeformerNode: DG-deformer (deform())
  • MPxFieldNode: DG-dynamic field (compute())
  • MPxEmitterNode: DG-emitter
  • MPxSpringNode: DG-dynamic spring (applySpringLaw())
  • MPxManipContainer: DG-manipulator
  • MPxSurfaceShape: DG-DAG-shape
  • MPxObjectSet: set
  • MPxHwShaderNode: hardware shader
  • MPxTransform: matrics (MPxTransformMatrix)

DG node attribute

  • simple attribute: attribute has only 1 plug
    • it also can hold array data, like pointArray, intArray
    • MFnNumericAttribute
    • MFnTypedAttribute
  • array attribute: attribute has array of plugs, each element plug has own value or connection or null, all of them under the top array plug.
    • MFnAttribute::setArray()
  • compound attribute: a collection of other attributes, called child attribute
    • MFnCompoundAttribute
      • example, color
        color1R = nAttr.create( “color1R”, “c1r”,MFnNumericData::kFloat);
        color1G = nAttr.create( “color1G”, “c1g”,MFnNumericData::kFloat);
        color1B = nAttr.create( “color1B”, “c1b”,MFnNumericData::kFloat);
        color1 = nAttr.create( “Sides”, “c1”, color1R, color1G,color1B);
        nAttr.setDefault(1.0f, 1.0f, 1.0f);
        attributeAffects(color1R, color1);
        attributeAffects(color1G, color1);
        attributeAffects(color1B, color1);
        attributeAffects(color1, aOutColor);
  • dynamic attribute: a attribute is dynamically created when needed.
    • MFnDependencyNode::kLocalDynamicAttr

DG node data

  • MDataBlock is a storage object holds DG node's data during DG node's compute() method
  • MDataHandle is a reference into MDataBlock's plugs, and get or set onto plug
  • Data creator is for creating data to be into MDataBlock, like output plug of another node,
    • mostly for heavy data like mesh
    • MFnData (MFnMeshData, DAG-MFnMesh, MFnNurbsSurfaceData, DAG-MFnNurbsSurface)

DG shader node

  • static MObject color1R,color1G,color1B,color1;
  • static MObject aOutColorR, aOutColorG, aOutColorB,aOutColor;
  • static MObject aNormalCameraX, aNormalCameraY,aNormalCameraZ, aNormalCamera;
  • static MObject aPointCameraX, aPointCameraY,aPointCameraZ, aPointCamera;
  • void MStatus compute( const MPlug&, MDataBlock& );

DG shader node type

  • textures/2d
  • textures/3d
  • textures/environment
  • shader/surface
  • shader/volume
  • shader/displacement
  • light
  • utility/general
  • utility/color
  • utility/particle
  • imageplane
  • postprocess/opticalFX

note: shader=material

DG shader node creation

  • example, Blinn creation
    shadingNode -asShader blinn; 
    // same as: 
    createNode blinn;
    connectAttr blinn1.message defaultShaderList1.shaders;
    • if creation option checked “with shading group”
      sets -renderable true -noSurfaceShader true -empty -name blinn1SG;
      connectAttr -f blinn1.outColor blinn1SG.surfaceShader;
  • example, volume creation
    shadingNode -asShader lightFog;
    sets -renderable true -noSurfaceShader true -empty -name lightFog1SG;
    connectAttr -f lightFog1.outColor lightFog1SG.volumeShader;
  • example, displacement creation
    shadingNode -asShader displacementShader;
    sets -renderable true -noSurfaceShader true -empty -name displacementShader1SG;
    connectAttr -f displacementShader1.displacement displacementShader1SG.displacementShader;
  • example, texture 2d
    shadingNode -asTexture checker; // means: 1. createNode checker; 2. add to multilister
    // if with option checked "With New Texture Placement" 
    shadingNode -asUtility place2dTexture; 
    connectAttr place2dTexture1.outUV checker1.uv;
    // if with option checked "As Projection"
    shadingNode -asTexture projection;
    shadingNode -asUtility place3dTexture;
    connectAttr place3dTexture1.wim[0];
    connectAttr checker1.outColor projection1.image;
    // else with option checked "as stencil"
    shadingNode -asTexture stencil;
    shadingNode -asUtility place2dTexture;
    connectAttr place2dTexture2.outUV stencil1.uv;
    connectAttr checker1.outColor stencil1.image;
    // ------ 3d tex
    shadingNode -asUtility place3dTexture;
    connectAttr place3dTexture2.wim[0];

DG shape node

  • MPxSurfaceShape: with component handling function and DG node function
    • MPxSurfaceShapeUI: drawing and selection functions
    • MPxGeometryIterator: component iterator
    • MPxGeometryData: pass geo data
  • MPxNode
  • shape component:
    • MFnComponent
      • MFnSingleIndexedComponent: vertices
      • MFnDoubleIndexedComponent: cvs
      • MFnTripleIndexedComponent: lattice points
  • shape component access:
    • MFnComponent
      • MFn::kMeshVertComponent : vtx[]
    • override access
      • MPxSurfaceShape::componentToPlugs(MObject& componet, MSelectionList& list)
    • check call process (name, index, range):
      • MPxSurfaceShape::matchComponent(const MSelectionList& item, const MAttributeSpecArray& spec, MSelectionList& list)
    • iterate component:
      • MPxGeometryIterator
        virtual MPxGeometryIterator* geometryIteratorSetup(MObjectArray&, MObject&, bool );
        virtual bool acceptsGeometryIterator( bool writeable );
        virtual bool acceptsGeometryIterator( MObject&,bool, bool );
    • component manipulator
      • MPxSurfaceShape::TransformUsing (matrix, components)
      • to speedup of setting attribute values, use MPxNode::forceCache, to access datablock and set directly without compute, vertexOffsetDirection if normal used.
    • tweak storing vs no-history access
      • set setAttr/getAttr behavior, use MFnAttribute::setInternal
    • connection around geometry data
      • define from MPxGeomtryData based on MPxData
      • and iterator: virtual MPxGeometryIterator* iterator(MObjectArray& MObject&, bool);
    • File save data
      • define writeASCII, writeBinary from MPxData

DG deformer node

  • custom deform attributes
    • MPxGeometryData
    • localShapeInAttr
    • localShapeOutAttr
    • worldShapeOutAttr
    • MPxGeomtryIterator
    • match
    • createFullVertexGroup
    • geometryData

DG shape - polygon API

  • polygon component
    • vertices (vtx[id] →float pos[3])
    • edges (edge[id] → {vtxID_s, vtexID_e})
    • faces (fe[ids], face[id] → fe_offset)
    • face vertices & uv ( face[id] → fv[id]; fe[ids] → {v_s, v_e})
  • polygon attribute:
    • inMesh (storable)
    • outMesh
    • cachedInMesh (storable)
    • pnts (tweaks) (position offset for each vertex)
      • = modifier → inMesh → tweaks → outMesh
      • or = outMesh
      • or cachedInMesh → tweaks → outMesh -cp→ cachedInMesh
  • polygon access, modify, create
    • MItMeshPolygon (Face comp, f-v, f-e, adjacent comp, uv, color-per-v, smooth info)
    • MItMeshEdge (Edge comp, e-f, e-v, edge smooth, adjacent comp)
    • MItMeshVertex, MItMeshFaceVertex (pos, v-f, v-e, normal, uv, color, adj comp) (f-fv_id, normal, uv, color)
    • MFnMesh (mesh operate on comp)
      • meshFn.numPolygons();
      • meshFn.numEdges();
      • meshFn.numVertices();
      • meshFn.polygonVertexCount();
      • meshFn.numUVs();
  • polygon history
    • original polyShape → modifier → modifier → (new modifier)→ polyNode
  • polygon custom modifier
    • MPxCommand
      • MStatus doModifyPoly();
      • MStatus redoModifyPoly();
      • MStatus undoModifyPoly();
  • polygon exporter
    • class polyExporter, polyWriter, polyRawExporter, polyRawWriter

Maya in command line

  • launch Maya GUI interface with custom path setting and startup cmd or script at launch time, windows batch example
    set MAYA_MODULE_PATH=Z:/example_external_folder/maya/modules/2014;%MAYA_MODULE_PATH% 
    set MAYA_SHELF_PATH=Z:/example_external_folder/maya/shelves/2014;%MAYA_SHELF_PATH%
    set MAYA_PRESET_PATH=Z:/example_external_folder/maya/presets/2014;%MAYA_PRESET_PATH%
    set MAYA_PLUG_IN_PATH=Z:/example_external_folder/maya/plug-ins/2014;%MAYA_PLUG_IN_PATH%
    set MAYA_PLUG_IN_LIST=fbxmaya.mll;objExport.mll;%MAYA_PLUG_IN_LIST%
    set MAYA_SCRIPT_PATH=Z:/example_external_folder/maya/scripts/2014;%MAYA_SCRIPT_PATH%
    set MAYA_SCRIPT_PATH=Z:/example_external_folder/maya/scripts/2014/custom2;%MAYA_SCRIPT_PATH%
    set MAYA_SCRIPT_PATH=Z:/example_external_folder/maya/scripts/2014/custom3;%MAYA_SCRIPT_PATH%
    set PYTHONPATH=Z:/example_external_folder/maya/Python/2014/Lib;%PYTHONPATH%
    set PYTHONPATH=Z:/example_external_folder/maya/tools/2014;%PYTHONPATH%
    "C:\path_to_maya\maya.exe" -c "python(\"execfile('Z:/path_to_custom_script_to_run_on_start/')\")"
  • process file with Maya in commandline only interface with “maya” in mac/linux, with “mayabatch.exe” with win

maya flags

-v version number
-batch start in batch mode
-prompt non-gui mode
-proj [dir] start in define project
-command [mel] start with defined mel command
-script [mel] start with mel script file
-recover recovers last file
-render [file] render defined file
-archive [file] list required or related files to archive a scene
-help display flags help
-noAutoloadPlugins don't load any plugin


  • set viewport display color
    cmds.displayColor('locator', 17, c=1, dormant=1) # 17, gold color
    # active=1 if for active color


  • list all maya UI element and close some
    allWindows = cmds.lsUI(windows=1)
    toClose_win_list = [x for x in allWindows if x not in ['CommandWindow','ConsoleWindow','MayaWindow','ColorEditor'] ]
    for each in toClose_win_list :
  • select tool filter
    cmds.selectType(allObjects=0) # query by cmds.selectType(allObjects=1, q=1)
  • display layer management
    cmds.createDisplayLayer(name="objList_layer", number=1, nr=1) # create layer from selection
    cmds.setAttr(layer + ".displayType", 2) # 0 normal, 1 template, 2 reference
  • display defined curve smoother, (note only during current maya session, restart maya will back to default)
    cmds.displaySmoothness(['guide_R_path', 'guide_L_path'],divisionsU=3, divisionsV=3, pointsWire=16, pointsShaded=4, polygonObject=3)

Maya UI automation scripting

mel name python name description
$gChannelBoxName mainChannelBox channel box
$gCommandReporter cmdScrollFieldReporter1 script editor result panel
$gMainWindow MayaWindow maya main window
graph1HyperShadeEd graph1HyperShadeEd material hypershade
nodeEditorPanel2NodeEditorEd nodeEditorPanel2NodeEditorEd node editor
  • get panel currently in focus
  • get panel currently under pointer
  • list maya UI
    ref: (scan through all Maya UI elements)
    cmds.lsUI(type='tabLayout') # list all maya ui tab layout object
    cmds.lsUI(windows=1) # list all window
    # trick cmds
    cmds.lsUI(controlLayouts=1, l=1)
    layoutType = cmds.objectTypeUI('AErootLayout')
    childs = cmds.layout('AErootLayout', q=1, childArray=1)
  • list maya UI's child and object type and parent
    # most maya layout has a -childArray flag to get child
    # get UI type
    # then continue call with the object's related cmd
    # to get parent
  • toggle maya window title bar visible and edit
    cmds.window('MayaWindow', e=1, titleBar=(not cmds.window('MayaWindow', q=1, titleBar=1)))
    # hide title bar (true no UI by: press ctrl+space; then run this cmd)
    newT="Autodesk Maya 2009 x64 Unlimited: untitled | cool"
    cmds.window('MayaWindow', e=1, title=newT) # mel name as $gMainWindow
  • toggle outline window
    if cmds.window('outlinerPanel1Window',ex=1):
  • ignore a set of scripting/cmd in undo history
    # ref:
    import maya.mel as mel
    import maya.cmds as cmds
    # nextFrame ('Alt+.')
    # previousFrame ('Alt+,')
    # nextKey ('.')
    cmds.currentTime(cmds.findKeyframe(timeSlider=1,which='next'), e=1)
    # previousKey (',')
    cmds.currentTime(cmds.findKeyframe(timeSlider=1,which='previous'), e=1)
    # firstKey
    cmds.currentTime(cmds.findKeyframe(which='first'), e=1)
    # lastKey
    cmds.currentTime(cmds.findKeyframe(which='last'), e=1)
  • group a set of scripting in undo history
    # your scripting block here
  • get maya global variable to python name, example
    mel.eval('global string $gChannelBoxName; $temp=$gChannelBoxName;')
  • toggle channel box nice and short names
    main_cb = 'mainChannelBox'
    next_state = not cmds.channelBox(main_cb, q=1, nn=1)
    cmds.channelBox(main_cb, e=1, ln=next_state, nn=next_state)
  • toggle script editor echo and clear echo
    cmdPanel = 'cmdScrollFieldReporter1'
    next_state = not cmds.cmdScrollFieldReporter(cmdPanel, q=1, eac=1)
    cmds.cmdScrollFieldReporter(cmdPanel, e=1, eac=next_state)
    # clear echo
    cmds.cmdScrollFieldReporter(cmdPanel, e=1, clear=1)
  • create script editor
    if cmds.cmdScrollFieldExecuter(ui_name, q=1, ex=1):
    mui = cmds.cmdScrollFieldExecuter(ui_name, st='python', sth=0, sln=1)# width=200, height=100)
    selected_text = cmds.cmdScrollFieldExecuter(mui, q=1, selectedText=1)
    # change selected or insert text
    cmds.cmdScrollFieldExecuter(mui, e=1, insertText='#==== '+selected_text+' ====')
    # set search text and search next
    cmds.cmdScrollFieldExecuter(mui, e=1, searchDown=1, searchWraps=1, searchMatchCase=0, searchString=text)
    res = cmds.cmdScrollFieldExecuter(mui, q=1, searchAndSelect=1) # 1 or 0
    # go to line number
    cmds.cmdScrollFieldExecuter(mui, e=1, currentLine=lineNum)
    # get script editor whole text
    script_text = cmds.cmdScrollFieldExecuter(mui, q=1, t=1)
  • create script history logger
    mui = cmds.cmdScrollFieldReporter(ui_name)#, width=200, height=100)
    # clear history
    cmds.cmdScrollFieldReporter(mui, e=1, clr=1)
  • open component editor and show skining tab
    # active smooth skin tab
    tab_name_list = cmds.tabLayout("compEdTab", q=1, tabLabel=1)
    for i in range(len(tab_name_list)):
        if tab_name_list[i] == "Smooth Skins":
            cmds.tabLayout("compEdTab",e=1, selectTabIndex=i+1)
  • add selected node into hyperShade and related
    for each in
        cmds.hyperGraph("graph1HyperShadeEd", e=1, addDependNode=each )
    cmds.hyperGraph("graph1HyperShadeEd", e=1, addBookmark=1)
    newBookmark = cmds.hyperGraph("graph1HyperShadeEd", q=1, bookmarkName=1) # return recent created mark
    cmds.setAttr(newBookmark+".description", "my bookmark note", type="string") 
  • add selected node in nodeEditor and related
    for each in
        cmds.nodeEditor('nodeEditorPanel2NodeEditorEd',e=1, addNode=each)
    cmds.getPanel(scriptType="nodeEditorPanel") # add NodeEditorEd at the end to get the nodeEditor object
    cmds.nodeEditor('nodeEditorPanel2NodeEditorEd', q=1, getNodeList=1)
  • toggle channel box long short name display
    import maya.mel as mel
    mel.eval('setChannelLongName(!`channelBox -query -nn $gChannelBoxName`)')
  • maya mel button with python function with parameter
    def process_func(data):
    cmds.button( command = lambda *args: process_func('one')


Note: from Maya 2017 onwards, you need to duplicate default maya hotkey to be able to make changes, it is under Windows > Settings > Hotkey Editor

  • reset hotkey
  • create hotkey
    import maya.cmds as cmds
    # cmds.hotkey(factorySettings=1)
    # -- toggle select mode
    def toggleSelectMode(curMode):
        global g_shiToggleMode
        print([g_shiToggleMode, curMode])
        if g_shiToggleMode != curMode:
            if curMode == 1: 
            elif curMode == 2:
            elif curMode == 3:
            elif curMode == 0: 
            g_shiToggleMode = curMode
            g_shiToggleMode = 0
    for i in range(1,3+1):
        cmds.nameCommand('NC_shiToggleMode_'+str(i), ann='NC_shiToggleMode_'+str(i), c='python("toggleSelectMode({0})")'.format(i))
        cmds.hotkey(k=str(i), ctl=1, n='NC_shiToggleMode_'+str(i))
    # -- viewport
    cmds.nameCommand('NC_view_tWireframe', ann='NC_view_tWireframe', c='python("cmds.modelEditor(cmds.getPanel(up=1), e=1, wireframeOnShaded=(1-cmds.modelEditor(cmds.getPanel(up=1), q=1, wireframeOnShaded=1)))")')
    cmds.hotkey(k='f', ctl=1, alt=1, n='NC_view_tWireframe')
    # -- windows
    cmds.nameCommand('NC_win_outliner', ann='NC_win_outliner', c="OutlinerWindow;")
    cmds.hotkey(k='4', ctl=1, n='NC_win_outliner')
    # -- script editor
    cmds.nameCommand('NC_win_scriptEditor', ann='NC_win_scriptEditor', c="ScriptEditor")
    cmds.hotkey(k='x', ctl=1, alt=1, n='NC_win_scriptEditor')
    #-- hyperShade
    cmds.nameCommand('NC_win_hyperShade', ann='NC_win_hyperShade', c="HypershadeWindow")
    cmds.hotkey(k='s', ctl=1, alt=1, n='NC_win_hyperShade')
    #-- graph editor
    cmds.nameCommand('NC_win_graphEditor', ann='NC_win_graphEditor', c="GraphEditor")
    cmds.hotkey(k='g', ctl=1, alt=1, n='NC_win_graphEditor')
    #-- custom
    cmds.nameCommand('NC_fun01', ann='NC_fun01', c='python("fun01")')
    cmds.hotkey(k='d', ctl=1, alt=1, n='NC_fun01')
  • Hotkey with Qt
    # ref:
    # qt import
        from PySide import QtGui, QtCore
        import PySide.QtGui as QtWidgets
        import shiboken
    except ImportError:
            from PySide2 import QtCore, QtGui, QtWidgets
            import shiboken2 as shiboken
        except ImportError:
    import maya.cmds as cmds
    import maya.OpenMayaUI as omui
    hotkey = {}
    mayaWindow = shiboken.wrapInstance(long(omui.MQtUtil.mainWindow()), QtWidgets.QWidget)
    def shortcut_process(keyname):
        if keyname == 'toggleWire':
            if 'modelPanel' in cmds.getPanel(wf=1):
                print('Done Something')
                # give key event to maya
                e = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_F, QtCore.Qt.ControlModifier | QtCore.Qt.AltModifier)
                QtCore.QCoreApplication.postEvent(mayaWindow , e)
                cmds.evalDeferred(partial(hotkey[keyname].setEnabled, 1))
    # shortcut
    hotkey['toggleWire'] = QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+Alt+F'), mayaWindow)
    hotkey['toggleWire'].activated.connect(partial(shortcut_process, 'toggleWire'))

Object Level API Process


  • path info for Dag node, the normal way, python/mel refering to maya objects like Transform and Shape
  • it is from the world node to a particular object in the DAG
  • string name to MDagPath
    selectionList = om.MSelectionList()
    mesh_dagPath = om.MDagPath()
    selectionList.getDagPath(0, mesh_dagPath)
    print(mesh_dagPath.partialPathName()) # if done correct, it will give same name as mesh_name
  • MObject to MDagPath
    geo_obj.hasFn(om.MFn.kDagNode) # check if it is Dag node, then it can have a dag path
    geo_dagNode = om.MFnDagNode(geo_obj) # now it is a DagNode, then get DagPath from DagNode
    print( geo_dagNode.partialPathName() ) # since DagNode also has name function
    geo_dagPath = om.MDagPath()
    print( geo_dagPath.partialPathName() ) # proof geo_dagPath work correctly
  • MFnDagNode to MDagPath()
    geo_dagPath = om.MDagPath()
    print( geo_dagPath.partialPathName() ) # proof geo_dagPath work correctly


  • MDagPath to MObject conversion,
    geo_transform_ojbect = geo_transform_dagPath.node()
    geo_transform_ojbect.hasFn(om.MFn.kTransform) # true, then can safely cast to MFnTransform
  • string name to MObject conversion
    selectionList = om.MSelectionList()
    skinClusterObj = om.MObject()
    skinClusterObj.hasFn(om.MFn.kSkinClusterFilter) # use for testing become cast to the object function


  • take input Object and provide function to access object as DAG Node, for its attribute and modify
  • it has same function as MDagPath like
    int = MDagPath.childCount() = MFnDagNode.childCount()
    MObject = MDagPath.child(index) = MFnDagNode.child(index)
    int = MDagPath.pathCount() = MFnDagNode.pathCount()
    # node only can return itself while path can return segment
    str = MDagPath.getPath(MDagPath, i) = MFnDagNode.getPath(MDagPath) 
    str = MDagPath.fullPathName() = MFnDagNode.fullPathName()
    str = MDagPath.partialPathName() = MFnDagNode.partialPathName()
  • but MFnDagNode has ability to add and remove child, also duplicate, instanceCount
    • for its matric info, it has transformationMatrix()
  • but MDagPath has ability to get nearby or itself for transform MObject, cast into shape MDagPath
    • for its matric info, it has inclusiveMatrix(), exclusiveMatrix(), inclusiveMatrixInverse(), exclusiveMatrixInverse()
  • str to MFnDagNode
    selectionList = om.MSelectionList()
    mesh_dagPath = om.MDagPath()
    selectionList.getDagPath(0, mesh_dagPath)
    mesh_dagNode = om.MFnDagNode(mesh_dagPath)
  • MDagPath to MFnDagNode
    my_dagNode = om.MFnDagNode(my_dagPath)
  • MObject to MFnDagNode
    my_dagNode = om.MFnDagNode(my_obj)
import maya.OpenMaya as om
selected = om.MSelectionList()
dagPath = om.MDagPath()
print(dagPath.fullPathName()) # current full path name
print(dagPath.partialPathName()) # current node name
# get shape count
countUtil = om.MScriptUtil()
tmpIntPtr = countUtil.asUintPtr() # unsigned int
print(dagPath.childCount()) # another alternative method
# get its Shape as MDagPath
dagPath.extendToShape() # MDagPath: change to shape path, will error if has no shape or more than 1 shape
dagPath.extendToShapeDirectlyBelow(0) # same as above; but won't error when more than 1 shape
print(dagPath.fullPathName()) # shape, if already shape, also return itself
# get itself as Node
dagObj = dagPath.node() # MObject
dagObj.hasFn(om.MFn.kDagNode) # check if its DAG node, not DG node
dagNode = om.MFnDagNode(dagObj) # use function to handle this MObject
# get its Transform as Node
dagObj = dagPath.transform() # MObject: return its transform MObject, if already transform, it return itself
dagNode = om.MFnDagNode(dagObj)
print(dagNode.fullPathName()) # transform node full dag path
  • MFnDependencyNode: it takes a MObject input to do creation and manipulation that dependency graph node, as some dependency node has no DAG path. but all DAG path pointing to a DAG node which is also DG node
    my_DGNode = OpenMaya.MFnDependencyNode(myObj)
  • MFnDependencyNode also has function like attribute editor, editing all the plug/attributes, and its related plugin info
  • Conversion from other data
    • str name to MFnDependencyNode
      selectionList = OpenMaya.MSelectionList()
      my_obj = OpenMaya.MObject()
      my_obj.hasFn(om.MFn.kDependencyNode) # check before convert
      my_dgNode = om.MFnDependencyNode(my_obj)
    • MDagPath to MFnDependencyNode
      my_obj = my_dagPath.node()
      my_obj.hasFn(om.MFn.kDependencyNode) # check before convert
      my_dgNode = om.MFnDependencyNode(my_obj)
    • MFnDagNode to MFnDependencyNode
      my_dagPath = my_dagNode.getPath()
      my_obj = my_dagPath.node()
      my_obj.hasFn(om.MFn.kDependencyNode) # check before convert
      my_dgNode = om.MFnDependencyNode(my_obj)
  • MDGModifier: it act directly on adding nodes, making new connections, and removing existing connections in DG nodes

other common class:

  • om.MPxNode: the the parent class for user defined dependency nodes.


  • MPlug is just API way of calling attribute.
  • MPlug has network(built-in) and non-networked(user-created) ones
  • MPlug can be a single attribute plug, or a array/element plug.
  • MPlug can have its own MPlug, as compound plug, which can be accessed with .child(); the opposite is .parent()
  • Array type MPlug can access its list of plug by .elementByLogicalIndex() and .elementByPhysicalIndex() method; the opposite is .array()

Array in MPlug:

  • array are sparse.
  • Logical Index used in MEL are sparse;
  • Physical Index is not sparse, it range from 0-numElements()-1, while numElements may sometimes only reflect the amount of entry changed from default, thus sometimes, numElements is not equal to the total number of plug in array. MPlug[i] = MPlug.elementByPhysicalIndex(i)
  • numElement only refer to data in datablock, to get update value, use

Logical Index and Physical Index of Joint in skincluster

Concept of Dynamic array - a pre-defined-size list with pre-loaded empty items

  • example MEL code
    string $nameArray[10];
    $nameArray[5]="objA"; // at place 5 in nameArray
    print("\n-------start sparse array--------\n");
    print $nameArray; // null null null null null objA
    $logical_index = stringArrayFind( "objA", 0, $nameArray ); // result: 5
    print("\nlogical index of 'objA': "+ $logical_index);
    print("\nsize: "+size($nameArray)); // size of array currently: 6
    print("\n9th: "+$nameArray[8]); // null
    print("\n11th: "+$nameArray[10]); // null
    print("\nsize: "+size($nameArray)); // size of array currently: 6
    string $nameArray_autoNonSparse_means_noEmpty[];
    for($each in $nameArray){
        if($each != ""){
            $cur_index = size($nameArray_autoNonSparse_means_noEmpty);
            $nameArray_autoNonSparse_means_noEmpty[$cur_index] = $each;
    print("\n-------start no sparse array--------\n");
    $physical_index = stringArrayFind( "objA", 0, $nameArray_autoNonSparse_means_noEmpty ); // result: 0
    print("\nphysical index of 'objA': "+$physical_index+"\n");
    // clear from memory
    • so the logical index of a array is the number is in the [], and as like Python list and other language arrays
      • as shown in Maya skincluster joint list connection

The Physical index of joint in the influence list

  • the most obvious demonstration of physical index of joint is in the component editor - skin weight tab
    • since “joint3” is deleted, and no longer has relationship in “skinCluster1”, thus there is no point put “joint3” column in the weight table;
    • the weight table is actual representation of the weight data;
      • including all the vertex
      • including all the joints linked to skinCluster, even it has 0 in weight
        • (make sure in component editor > option menu > Uncheck hide zero column)
        • (make sure in component editor > option menu > Uncheck sort alphabetically column)
    • and when saving, actual weight data store in a 1 dimension array { vtx[0][jnt1], vtx[0][jnt2] … vtx[7][jnt2] }
      • with joint name as column header, and vtx index as row header
      • so column index is the actual Physical index of joint used in referring weight data table
      • while the logical index of joint used in referring current connection flow.
    • Input: createFrom*() Int, Double, List+len
    • middle: as*Ptr() # Int, Short, Float, Double, Uint, Bool, Char, Uchar, x2-4
    • output: get*() # Int, Short, Float, Double, Uint, Bool, Char, Uchar, x2-4
  • code workflow
    py_var = 0 # your type, say Unsign Int
    util = om.MScriptUtil()
    util.createFromInt(py_var) # same as your type
    util_ptr = util.asUintPtr() # same as your type
    api_obj.api_function_return_a_value(util_ptr) # use pointer to hold the result value
    py_var = util.getUint(util_ptr) # same as your type
    print(py_var) # new value from API
  • other align vector related reading (mel only)
    rot: rot vector around another vector by a degree
    rad_to_deg, deg_to_rad
  • MVector functions
    # ref:!topic/python_inside_maya/-Yp8WfyTkPA
    # MVector: rotateBy (MVector::Axis axis, const double angle) 
    # get a MVector result by rotate one Vector around default x,y,z axis by certain degree
    # MVector: rotateBy (const MQuaternion &) const 
    # case 1: get a MVector result by rotate one Vector around another Vector by certain degree
    res_vector = mainVector.rotateBy( maya.OpenMaya.MQuaternion(math.radians(rotDegree), aimVector) )
    # case 2: get a MVector result by rotate one Vector by a swing motion defined by start vector to end vector
    res_vector = mainVector.rotateBy( maya.OpenMaya.MQuaternion(MotionStartVector, MotionEndVector, optionalPercentage ) )
  • Align Object Axis one at a time to Vector direction (OpenMaya method)
    import maya.OpenMaya as om
    import math
    def getLocalVecToWorldSpaceAPI(obj, vec=om.MVector.xAxis):
        # ref:
        # take obj's local space vec to workspace, default object x axis
        selList = om.MSelectionList()
        nodeDagPath = om.MDagPath()
        selList.getDagPath(0, nodeDagPath)
        matrix = nodeDagPath.inclusiveMatrix()
        vec = (vec * matrix).normal()
        return vec.x, vec.y, vec.z
    # define objects
    # a locator reference that we are aligning our pCube object to
    # of course, you dont need a refLoc, you can use Vector from other data as well
    # here locator is just a visual example
    refLoc = 'locator_1'
    obj = 'pCube1' # a cube we are aligning
    # grab object transform function lib
    selectionList = om.MSelectionList()
    selectionList.add( obj )
    dagPath = om.MDagPath()
    selectionList.getDagPath(0, dagPath)
    fnTransform = om.MFnTransform(dagPath)
    # STEP 1: align Y axis first
    refLoc_normal_y = getLocalVecToWorldSpaceAPI(refLoc, om.MVector.yAxis)
    aim_vec_y = om.MVector(*refLoc_normal_y) # break array into 3 parameters
    obj_normal_y = getLocalVecToWorldSpaceAPI(obj, om.MVector.yAxis)
    obj_vec_y = om.MVector(*obj_normal_y)
    rot_quaternion = obj_vec_y.rotateTo(aim_vec_y)
    # absolute version
    #fnTransform.setRotationQuaternion( rot_quaternion.x, rot_quaternion.y, rot_quaternion.z, rot_quaternion.w ) 
    # relative transform on top of existing obj transform
    # STEP 2: align Z axis
    refLoc_normal_z = getLocalVecToWorldSpaceAPI(refLoc, om.MVector.zAxis)
    aim_vec_z = om.MVector(*refLoc_normal_z)
    obj_normal_z = getLocalVecToWorldSpaceAPI(obj, om.MVector.zAxis)
    obj_vec_z = om.MVector(*obj_normal_z)
    rot_quaternion = obj_vec_z.rotateTo(aim_vec_z)
  • get position
    pos = cmds.xform(obj, q=1, t=1, ws=1)
    # loc
    pos = cmds.getAttr(loc+'.worldPosition')[0] # world position
    pos = cmds.pointPosition(loc) # world position
    #cv - local position from origin at creation time
    # vtx -
    cmds.getAttr('pCube1.vtx[1]')[0] # local offset value since creation
    cmds.getAttr("pCube1.pnts[1]")[0] # local offset value since creation
    cmds.setAttr("pCubeShape1.pnts[5].pntx", 0)
    cmds.setAttr("pCubeShape1.pnts[5].pnty", 0)
    cmds.setAttr("pCubeShape1.pnts[5].pntz", 0)
    # vtx - local position relative to pivot
    # loc, vtx, cv, uv, point - global position
    pos = cmds.pointPosition(pointObj)
  • freeze transform
    • it will make curve like re-created based on current result shape, previous local cv position is same as global position
    • it will make polygon pivot change back to origin, and vtx global position stay same, local position will same as current global position, and its offset value will be based on previous value
    • code

Selection in API

  • get current selection
    selected = om.MSelectionList()
  • get current soft selection and weight
    def getVtxSelectionSoft_OpenMaya():
        # ref:
        startTime = cmds.timerX() # 0.13s > no string 0.07s
        # soft selection
        softSelectionList = om.MSelectionList()
        softSelection = om.MRichSelection()
        selectionPath = om.MDagPath()
        selectionObj = om.MObject()
        iter = om.MItSelectionList( selectionList, om.MFn.kMeshVertComponent)
        #elements = []
        id_list = []
        weights = []
        node_list = []
        while not iter.isDone():
            iter.getDagPath( selectionPath, selectionObj )
            selectionPath.pop() #Grab the parent of the shape node
            node = selectionPath.fullPathName()
            compFn = om.MFnSingleIndexedComponent(selectionObj)   
            getWeight = lambda i: compFn.weight(i).influence() if compFn.hasWeights() else 1.0
            for i in range(compFn.elementCount()):
                #elements.append('{0}.vtx[{1}]'.format(node, compFn.element(i)))
        totalTime = cmds.timerX(startTime=startTime)
        print("\nTotal Time: {0}".format(totalTime))
        return list(set(node_list)), id_list, weights
  • API way to get selection vertex ids (single geo case), and set selection using vertex ids
    def createVtxSelection_cmds(objName, id_list):
        startTime = cmds.timerX() # 0.039s
        result_sel = ['{0}.vtx[{1}]'.format(objName, i) for i in id_list]
        totalTime = cmds.timerX(startTime=startTime)
        print("\nTotal Time: {0}".format(totalTime))
        return result_sel
    def createVtxSelection_OpenMaya(objName, id_list):
        # ref:
        startTime = cmds.timerX() # 0.009s
        sel = om.MSelectionList()
        mdag = om.MDagPath()
        sel.getDagPath(0, mdag)
        util = om.MScriptUtil()
        util.createFromList(id_list, len(id_list))
        ids_ptr = util.asIntPtr()
        ids = om.MIntArray(ids_ptr, len(id_list))
        compFn = om.MFnSingleIndexedComponent()
        components = compFn.create( om.MFn.kMeshVertComponent )
        result_sel = om.MSelectionList()
        result_sel.add(mdag, components)
        totalTime = cmds.timerX(startTime=startTime)
        print("\nTotal Time: {0}".format(totalTime))
        return result_sel
    def getVtxSelection_cmd(l=0):
        startTime = cmds.timerX() # 0.18s
        selected =, fl=1, l=l) # len(selected)
        id_list = [x.split('[',1)[-1][:-1] for x in selected]
        totalTime = cmds.timerX(startTime=startTime)
        print("\nTotal Time: {0}".format(totalTime))
        return selected[0].split['.'][0], id_list
    def getVtxSelection_OpenMaya():
        startTime = cmds.timerX() # 0.11s > no comp iter: 0.039
        # normal selection
        normalSelectionList = om.MSelectionList()
        selectionPath = om.MDagPath()
        selectionObj = om.MObject()
        indexList = om.MIntArray()
        iter = om.MItSelectionList( normalSelectionList, om.MFn.kMeshVertComponent )
        #normalSelectionElements = [] # len(normalSelectionElements)
        node_list = []
        id_list = []
        while not iter.isDone():
            iter.getDagPath( selectionPath, selectionObj )
            node = selectionPath.fullPathName()
            compFn = om.MFnSingleIndexedComponent(selectionObj) # flatten the component selection
            for i in range(compFn.elementCount()):
                normalSelectionElements.append('{0}.vtx[{1}]'.format(node, compFn.element(i)))
        totalTime = cmds.timerX(startTime=startTime)
        print("\nTotal Time: {0}".format(totalTime))
        return list(set(node_list)), id_list

Modeling in Python vs Mel

  • list of cmds in both python and mel for modeling
# mel: DeleteHistory;
# show poly vertex
# mel: setPolygonDisplaySettings("verts");
cmds.polyOptions(r=1, activeObjects=1, dv=1)
# mel: ToggleFaceNormalDisplay;
# mel: ToggleBackfaceGeometry;
# mel: ReversePolygonNormals;
# hard edge
# mel: SoftPolyEdgeElements(0);
# show border
# mel: ToggleBorderEdges;
# dialog to change edge width display
# mel: ChangeEdgeWidth;
# show UV window
# mel: TextureViewWindow;
# mel: CenterPivot;
# turn on / off symmetric modeling
symmetricModelling -e -symmetry true;symmetricModelling -e -symmetry false;
symmetricModelling -e -about "world";
symmetricModelling -e -about "object";
symmetricModelling -e -axis "x"
cmds.symmetricModelling(e=1, symmetry=1)
cmds.symmetricModelling(e=1, symmetry=0)
# face selection to edge border selection
# mel: select -r `polyListComponentConversion -ff -te -bo`;, te=1, bo=1),r=1)
# selection to shell
# mel: ConvertSelectionToShell;
# selection to create layer
string $tmpname[]=`ls -sl`;
createDisplayLayer -name ($tmpname[0]+"_Lyr") -number 1 -nr;
selected =
if len(selected)>0:
    cmds.createDisplayLayer(name=selected[0]+'_layer', number=1, nr=1)
# remove from layers
# mel: editDisplayLayerMembers -noRecurse "defaultLayer" `ls -sl`
cmds.editDisplayLayerMembers("defaultLayer",, noRecurse=1)
#### curve #####
# show CV
# mel: ToggleCVs;
# show editable point
# mel: ToggleEditPoints;
# rebuild curve dialog
# mel: RebuildCurveOptions;
# detach surface by edge or isopam
# mel: DetachCurve;
# detach/attach curve by cut
# mel: CutCurve; AttachCurve;

Modeling in API

Vertex check in API way

  • poly count
    vtx_cnt = cmds.polyEvaluate(geoName, v=1)
  • get transform to shape to vtx
    selected = om.MSelectionList()
    dagPath = om.MDagPath()
    print(dagPath.fullPathName()) # transform
    dagPath.extendToShape() # .extendToShapeDirectlyBelow() if dont want to get child of child; will error if has no shape
    print(dagPath.fullPathName()) # shape, if shape, also return itself
    dagPath.transform() # transform
    mesh = om.MFnMesh(dagPath.node()) # MObject to MeshFunction
    print(mesh.numVertices()) # vtx count

Vertex Check

  • test empty geo shape
    # test empty geo shape
    all_geo_list ='mesh')
    zeroVtx_list = []
    for each in all_geo_list:
        if cmds.polyEvaluate(each, v=1) == 0:

Face Check and Correct

  • double side face
    shape_list ='mesh')    
    for shape in shape_list:
        cmds.setAttr(shape+".doubleSided", 1)

Curve related

  • get curve construction info
    def getCurveInfo(curveObj, index=2):
        d = cmds.getAttr(curveObj+'.degree')
        p = cmds.getAttr(curveObj+'.cv[:]')
        p_new = [ [numClean(y, index) for y in x] for x in p]
        tmpInfo = cmds.createNode( 'curveInfo' )
        cmds.connectAttr( curveObj + '.worldSpace', tmpInfo+'.inputCurve' )
        k = cmds.getAttr( tmpInfo + '.knots[*]' )
        return (d, p_new, k)
    def numClean(fnum, index=2):
        rule = "{0:."+str(index)+"f}"
        cnum = float(rule.format(fnum))
        if cnum.is_integer():
            cnum = int(cnum)
        return cnum
    def setCurveInfo(curveObj, curveInfo):
        # input: dict - d, p; p ; d,p,k
        if not isinstance(curveInfo, dict) or 'p' not in curveInfo.keys():
            print('require curveInfo in dict of d,p,k or d,p or p')
        if all(x in curveInfo.keys() for x in ['d','p','k']):
            return cmds.curve(curveObj,r=1,d=curveInfo['d'], p=curveInfo['p'], k=curveInfo['k'])
        elif all(x in curveInfo.keys() for x in ['d','p']):
            return cmds.curve(curveObj,r=1,d=curveInfo['d'], p=curveInfo['p'])
            return cmds.curve(curveObj,r=1,d=1, p=curveInfo['p'])
    def buildCurveInfo(curveInfo, n='tmpCurve'):
        # input: dict - d, p; p ; d,p,k
        if not isinstance(curveInfo, dict) or 'p' not in curveInfo.keys():
            print('require curveInfo in dict of d,p,k or d,p or p')
        if all(x in curveInfo.keys() for x in ['d','p','k']):
            return cmds.curve(n=n,d=curveInfo['d'], p=curveInfo['p'], k=curveInfo['k'])
        elif all(x in curveInfo.keys() for x in ['d','p']):
            return cmds.curve(n=n,d=curveInfo['d'], p=curveInfo['p'])
            return cmds.curve(n=n,d=1, p=curveInfo['p'])
  • curve_mel2py cmd convertor
    #txt = 'curve -d 1 -p 0 0 1 -p 0 0.5 0.866025 -p 0 0.866025 0.5 -p 0 1 0 -p 0 0.866025 -0.5 -p 0 0.5 -0.866025 -p 0 0 -1 -p 0 -0.5 -0.866025 -p 0 -0.866025 -0.5 -p 0 -1 0 -p 0 -0.866025 0.5 -p 0 -0.5 0.866025 -p 0 0 1 -p 0.707107 0 0.707107 -p 1 0 0 -p 0.707107 0 -0.707107 -p 0 0 -1 -p -0.707107 0 -0.707107 -p -1 0 0 -p -0.866025 0.5 0 -p -0.5 0.866025 0 -p 0 1 0 -p 0.5 0.866025 0 -p 0.866025 0.5 0 -p 1 0 0 -p 0.866025 -0.5 0 -p 0.5 -0.866025 0 -p 0 -1 0 -p -0.5 -0.866025 0 -p -0.866025 -0.5 0 -p -1 0 0 -p -0.707107 0 0.707107 -p 0 0 1 -k 0 -k 1 -k 2 -k 3 -k 4 -k 5 -k 6 -k 7 -k 8 -k 9 -k 10 -k 11 -k 12 -k 13 -k 14 -k 15 -k 16 -k 17 -k 18 -k 19 -k 20 -k 21 -k 22 -k 23 -k 24 -k 25 -k 26 -k 27 -k 28 -k 29 -k 30 -k 31 -k 32'
    # maya float number cleaner
    def numClean(fnum, index=2):
        rule = "{0:."+str(index)+"f}"
        cnum = float(rule.format(fnum))
        if cnum.is_integer():
            cnum = int(cnum)
        return cnum
    def curve_mel2py(txt):
         rest, k = txt.split('-k',1)
         ks = [int(x.strip()) for x in k.split('-k')]
         rest, p = rest.split('-p',1)
         ps = [ x.strip().split() for x in p.split('-p') ]
         ps = [ [numClean(float(y)) for y in x] for x in ps]
         d = int(rest.split('-d',1)[-1].strip())
         result = "created = cmds.curve(d={0}, p={1}, k={2})".format(d, str(ps), str(ks))
         return result
  • create NurbsCurve passing defined point list
    lineCurve = cmds.curve(p=pointList, d=1)
    nurbsCurve = cmds.fitBspline(lineCurve,ch=0,tol=0.01)
  • Research on API vs Cmds in Get and Set Vertex Position based on optional selection
    # ref:
    def getVtxSelection_OpenMaya():
        startTime = cmds.timerX() # 0.11s > no comp iter: 0.039
        # normal selection
        normalSelectionList = om.MSelectionList()
        selectionPath = om.MDagPath()
        selectionObj = om.MObject()
        indexList = om.MIntArray()
        iter = om.MItSelectionList( normalSelectionList, om.MFn.kMeshVertComponent )
        #normalSelectionElements = [] # len(normalSelectionElements)
        node_list = []
        id_list = []
        while not iter.isDone():
            iter.getDagPath( selectionPath, selectionObj )
            node = selectionPath.fullPathName()
            compFn = om.MFnSingleIndexedComponent(selectionObj) # flatten the component selection
            for i in range(compFn.elementCount()):
                normalSelectionElements.append('{0}.vtx[{1}]'.format(node, compFn.element(i)))
        totalTime = cmds.timerX(startTime=startTime)
        print("\nTotal Time: {0}".format(totalTime))
        return list(set(node_list)), id_list
    def getVtxPos_cmd( shapeNode, sl=0 ):
        # full path, duplicate name proof
        startTime = cmds.timerX()
        pos_list = []
        index_list = []
        if sl==0: # 0.885
            #index_list = cmds.getAttr( shapeNode+".vrts", multiIndices=True ) # 0.0089
            index_list = range( cmds.polyEvaluate(shapeNode,v=1) ) # 0.007
        else: # 0.244
            shapeNode, index_list = getVtxSelection_OpenMaya() # 0.01
            shapeNode = shapeNode[0] 
        for i in index_list: #i=0
            pos = cmds.xform( '{0}.vtx[{1}]'.format(shapeNode, i), q=1, t=1, ws=1)
            pos_list.append( pos )
        totalTime = cmds.timerX(startTime=startTime)
        print("\nTotal Time: {0}".format(totalTime))
        return index_list, pos_list
    def setVtxPos_cmd( shapeNode, pos_data, sl=0 ):
        startTime = cmds.timerX()
        index_list, pos_list = pos_data
        if sl== 0: # 4.38
            for i in range(len(index_list)):
                cmds.xform('{0}.vtx[{1}]'.format(shapeNode, index_list[i]), t=pos_list[i], ws=1)
        else: # 5.29
            sel_index_list = getVtxSelection_OpenMaya()[1]
            if not set(sel_index_list).issubset(set(index_list)):
                print('selection is not subset of stored plmp data.')
            for id in sel_index_list:
                i = index_list.index(id)
                cmds.xform('{0}.vtx[{1}]'.format(shapeNode, id), t=pos_list[i], ws=1)
        totalTime = cmds.timerX(startTime=startTime)
        print("\nTotal Time: {0}".format(totalTime))
    def getVtxPos_OpenMaya( shapeNode, sl=0 ):
        # full path, duplicate name proof
        startTime = cmds.timerX() 
        pos_list = []
        index_list = []
        if sl==0: # 0.18
            selectionList = om.MSelectionList()
            dagPath = om.MDagPath()
            selectionList.getDagPath(0, dagPath)
            mesh_fn = om.MFnMesh(dagPath)
            meshPointArray = om.MPointArray()
            mesh_fn.getPoints(meshPointArray, om.MSpace.kWorld)
            index_list = range(meshPointArray.length())
            for i in index_list:
                pos_list.append( [meshPointArray[i][0], meshPointArray[i][1], meshPointArray[i][2]] )
        else: # 0.04
            selectionList = om.MSelectionList()
            selectionPath = om.MDagPath()
            selectionCompObj = om.MObject()
            iter_sel = om.MItSelectionList( selectionList, om.MFn.kMeshVertComponent )    
            while not iter_sel.isDone():
                iter_sel.getDagPath( selectionPath, selectionCompObj )
                iter_vtx = om.MItMeshVertex(selectionPath,selectionCompObj)
                while not iter_vtx.isDone():
                    pnt = iter_vtx.position(om.MSpace.kWorld)
                    index_list.append( iter_vtx.index() )
                    pos_list.append([pnt.x, pnt.y, pnt.z])
        totalTime = cmds.timerX(startTime=startTime)
        print("\nTotal Time: {0}".format(totalTime))
        return index_list, pos_list
    def setVtxPos_OpenMaya(shapeNode, pos_data, sl=0):
        startTime = cmds.timerX()
        index_list, pos_list = pos_data
        selectionList = om.MSelectionList()
        dagPath = om.MDagPath()
        selectionList.getDagPath(0, dagPath)
        mesh_fn = om.MFnMesh(dagPath)
        if sl==0: # 0.069
            # set one by one - 4.22
            #for i in range(len(index_list)):
            #    mesh_fn.setPoint(index_list[i], om.MPoint(*pos_list[i]) )
            # get all and get all - 0.069
            meshPointArray = om.MPointArray()
            mesh_fn.getPoints(meshPointArray, om.MSpace.kWorld)
            for i in range(len(index_list)):
                meshPointArray.set(index_list[i],*pos_list[i] )
            mesh_fn.setPoints(meshPointArray, om.MSpace.kWorld)
        else: # 0.53
            sel_index_list = getVtxSelection_OpenMaya()[1]
            if not set(sel_index_list).issubset(set(index_list)):
                print('selection is not subset of stored plmp data.')
            meshPointArray = om.MPointArray()
            mesh_fn.getPoints(meshPointArray, om.MSpace.kWorld)
            for id in sel_index_list:
                i = index_list.index(id)
                meshPointArray.set(id,*pos_list[i] )
            mesh_fn.setPoints(meshPointArray, om.MSpace.kWorld)
        totalTime = cmds.timerX(startTime=startTime)
        print("\nTotal Time: {0}".format(totalTime))

UV Editing


  • assign material to selection or objects
    cmds.hyperShade(testGeo, assign='refMat')
  • material creation
    cmds.shadingNode('lambert', n='refMat', asShader=1)
  • change property
    cmds.setAttr('{0}.transparency'.format(testMat), 0.5, 0.5, 0.5, type="double3")


obj_transform .worldMatrix
  • for blendshape and constraints, there is always an alias name for the weight Attribute, code example
    alias_target_weight_list = cmds.aliasAttr( final_bs, q=1 ) # ['body_BST', 'weight[0]']
    target_list = alias_target_weight_list[::2] # all the alias target names
    weight_attr_list = alias_target_weight_list[1::2] # all the weight attribute list
  • apply single vtx weight to whole geo, good for unify same weight on attachment type geos on another surface
    attach_vtx_id = 16 # use same weight as on vtx[16]
    cur_skin = cmds.skinCluster(cur_bones, cur_geo, tsb=1, n=cur_geo+'_skinCluster')[0]
    cmds.setAttr( cur_skin + ".skinningMethod", 1)
    vtx_weight = cmds.skinPercent(cur_skin, cur_geo+'.vtx[{0}]'.format(attach_vtx_id), q=1, v=1)
    cmds.skinPercent(cur_skin, cur_geo+'.vtx[:]', tv=zip(cur_bones,vtx_weight))
  • get only bone affecting the selected vtx
    cur_vtx_list =, fl=1)
    if len(cur_vtx_list)>0:
        if '.vtx[' in cur_vtx_list[0]:
            cur_skin = weightAPI.findRelatedSkinCluster( cur_vtx_list[0].split('.')[0] )
            affect_boneList = cmds.skinPercent(cur_skin, cur_vtx_list, q=1, ignoreBelow=0.01, t=None)
  • select only vtx affected the defined bone
    cmds.skinCluster(cur_skin, e=1, selectInfluenceVerts=bone_name)

  • each vertex of a polygon model holds local transform value at [0,0,0], but after some move and sculpt, the vertex local position value changed,
  • you can optionally clear out local vertex transform with cmds.polyMoveVertex(localTranslate=(0, 0, 0)), and delete history
  • however, Blendshape in maya seems not care about those local transform or global transform, it only care its position relative to origin when object is at zero x,y,z transform in channel box
  • move at object level will not affect blendshape unless you tell blendshape to look at global level instead of vertex level
  • also, any deform modifier added on the object will reset all the vertex local value to 0,0,0 in result object shape.
  • inputTarget[0].paintTargetWeights[11]: this is just a temporary weight holder, as anytime you use blendshape paint tool to select which data to paint on, it loads the data into this attribute.
  • vtx general info code
    cmds.getAttr('ball.vtx[0]')[0] # those value in channelbox CV, and component editor
    cmds.polyMoveVertex(localTranslate=(0, 0, 0)) # clear local values
  • blendshape per target per vtx weight info cmds way
    cmds.getAttr(cur_bs+'.it[0].itg[0].tw[0]') # cmds method extremely slow even for a ball
    # target 1 : weight per vtx
    cmds.getAttr('blendShape1.inputTarget[0].inputTargetGroup[0].targetWeights')[0] # get full list of weights
    # target 2 : weight per vtx
    # set target 2: weight per vtx
    cmds.setAttr('blendShape1.inputTarget[0].inputTargetGroup[1].targetWeights[241]', .8)
    # get current loaded paint weight data
    cmds.getAttr(cur_bs+'.it[0].pwt[11]') #vtx 11 at current paint data
    # get current base (the final mixing global per vtx weight)
    cmds.getAttr(cur_bs+'.it[0].bw[11]') #vtx 11 at current paint data

  • API: MFnBlendShapeDeformer
    import maya.OpenMaya as om
    import maya.OpenMayaAnim as oma
    selectionList = om.MSelectionList()
    bs_obj = om.MObject()
    bs_fn = oma.MFnBlendShapeDeformer(bs_obj) # blendshape object fn
    # current result object
    obj_list = om.MObjectArray()
    print(obj_list.length()) # 1, only one blendshape object, even if it has more targets
    bs_base_obj = obj_list[0]
  • API: weight slider attribute and alias target attribute name
    nWeights = bs_fn.numWeights() # number of blend targets
    indexList = om.MIntArray() 
    # get logic index of blend targets, since target can be remove from array, and become sparse
    w_info_list=[] # weight attr info
    w_plug = bs_fn.findPlug('weight')
    for index in indexList:
        cur_plug = w_plug.elementByLogicalIndex(index)
        cur_plug_alias = bs_fn.plugsAlias(cur_plug)
        cur_plug_w = bs_fn.weight(index)
        w_info_list.append([cur_plug_alias, cur_plug_w])
  • API: baseWeights data (overall deform weight map):
    inputTargetArrayPlug = bs_fn.findPlug('inputTarget')
    inputTargetPlug = inputTargetArrayPlug.elementByLogicalIndex(0)  # base_model
    # base weight list
    baseWeightsArrayPlug = inputTargetPlug.child(1) # .inputTarget[0].baseWeights
    vtxCnt = cmds.polyEvaluate(base_geo,v=1)
    base_weight_list = [1]*vtxCnt # default is 1
    for j in range(baseWeightsArrayPlug.numElements()):
        cur_plug = baseWeightsArrayPlug.elementByPhysicalIndex(j) # same as baseWeightsArrayPlug[j]
        logicalIndex = cur_plug.logicalIndex()
        base_weight_list[logicalIndex] = cur_plug.asFloat() # update from sparse array data
    # following method may fail if you start maya fresh and read the scene 
    base_weight_list = []
    for j in range(baseWeightsArrayPlug.numElements()): # fresh start will cause numElements become 0 or sparse count again, since not in datablock memory yet
        base_weight_list.append( baseWeightsArrayPlug.elementByLogicalIndex(j).asFloat() )
    # even this will not get the full index list
  • API: targetWeights data for each target
    inputTargetGroupArrayPlug = inputTargetPlug.child(0)
    target_weight_data = []
    for index in indexList: # i=0
        inputTargetGroupPlug = inputTargetGroupArrayPlug.elementByLogicalIndex(index) #
        targetWeightsArrayPlug = inputTargetGroupPlug.child(1) # .targetWeights
        target_weight_list = [1]*vtxCnt 
        for j in range(targetWeightsArrayPlug.numElements()): # j=0
            cur_plug = targetWeightsArrayPlug.elementByPhysicalIndex(j)
            logicalIndex = cur_plug.logicalIndex()
            target_weight_list[logicalIndex] = cur_plug.asFloat()

* API: Geometry result deformed vertex list position info

pos_list = []
geo_iter = om.MItGeometry(obj_list[0])
while not geo_iter.isDone():
    p = geo_iter.position()
    pos_list.append((p.x, p.y, p.z))
  • API: Data of Target shape vertex list position info
    target_data = []
    def geoObject_to_posList_OpenMaya(geo_object):
        # MObject as input
        geo_iter = om.MItGeometry(geo_object)
        pos_list = []
        while not geo_iter.isDone():
            pnt = geo_iter.position()
            pos_list.append([pnt.x, pnt.y, pnt.z])
        return pos_list
    for index in indexList: # i = 0
        target_object_data = []
        target_list = om.MObjectArray()
        bs_fn.getTargets(bs_base_obj, index, target_list)
        # target_list.length() #1, ideally one blend target only have one object
        target_cnt = target_list.length()
        if target_cnt > 1:
            # more than one object case
            for j in range(target_cnt ):
                target_object_data.append( geoObject_to_posList_OpenMaya(target_list[j]) )
            # normal one object case
            if target_cnt == 0:
                # no longer object link to it, get it from base
                # switch all shape off and only leave it on
                old_w_list = []
                for n in indexList:
                    old_w_list.append( [ n, bs_fn.weight(n) ] ) # backup
                    bs_fn.setWeight(n, 0)
                bs_fn.setWeight(n, 1)
                # get object data
                target_object_data.append( geoObject_to_posList_OpenMaya(bs_base_obj) )
                # from backup
                for n, w in old_w_list:
                    bs_fn.setWeight(n, w)
            elif target_cnt == 1:
                target_object_data.append( geoObject_to_posList_OpenMaya(target_list[0]) )
        # - 
    # locator creation
    def pos_to_loc(pos_list, prefix='tmp'):
        for i,pos in enumerate(pos_list):
            cur_loc = cmds.spaceLocator(n='loc_{0}_{1}'.format(prefix,i))[0]
            cmds.xform(cur_loc, t=pos)
    for i,targets in enumerate(target_data):
        pos_to_loc(targets[0], 'tgt'+str(i))


Import and Export animation with atom plugin

  • import
    # select controls to load animation, then
    cmds.file("D:/path_to_file/pose_001.atom", i=1, type="atomImport")

Get all Animation Curve

  • note: it may include time value driven drive key curve, so need check
    for animCurve_type in ['animCurveTA', 'animCurveTL', 'animCurveTU']:
        curve_list = type = animCurve_type )
        for each_curve in curve_list:
            cur_connection_out = each_curve + ".output"
            destinations = cmds.connectionInfo(cur_connection_out, destinationFromSource=1)
            if len(destinations) == 0:
                # optional process for unconnected animation node
                cur_node_attr = destinations[0]
                cur_node, cur_attr = cur_node_attr.split('.')
                # optional get transform node
                cur_transform = ''
                if cmds.nodeType(cur_node) == 'transform':
                    cur_transform = cur_node
                    try_parent = cmds.listRelatives(cur_node, p=1)
                    if try_parent:
                        cur_transform = cmds.listRelatives(cur_node, p=1)[0]
                # optional unlock attribute
                old_lock = cmds.getAttr(cur_node_attr, l=1)
                cmds.setAttr(cur_node_attr, l=0)
                # optional process here
                cmds.delete(cur_node_attr, staticChannels=1, unitlessAnimationCurves=0, hierarchy="", controlPoints=0, shape=1)
                # lock back
                cmds.setAttr(cur_node_attr, lock= old_lock)

Clean Static Animation on object

  • note: that operation can't take locked channel, so unlock process may required
  • general clean static animation
    cmds.delete(cur_node_attr, staticChannels=1, unitlessAnimationCurves=0, hierarchy="", controlPoints=0, shape=1)

Bake Animation

  • note: make sure all channel unlocked
  • bake animation curve
  • bake object

Animation Cache

  • check abc node inside maya, and its cache path
    # get all abc node
    all_abc_nodes ='AlembicNode')
    # check current file path
    for each in all_abc_nodes:
        print('For node: {0}'.format(each))
  • to update abc cache path, (default maya only change the “.fn” attribute in attribute editor, you need also change “.fns” in code, which is a string array attribute), here I just copy first path to the string array path
    all_abc_nodes ='AlembicNode')
    for each in all_abc_nodes:
        print('Fixing node: {0}'.format(each))
        cur_abc_path = cmds.getAttr(each+'.fn')
        cmds.setAttr('{0}.fns'.format(each), 1, cur_abc_path, type='stringArray')

Camera and Viewport calculation

  • get 2D screen space from 3D position
    import maya.OpenMayaUI as omui
    import maya.OpenMaya as om
    import maya.cmds as cmds
    obj =, fl=1)[0] # get first vtx or object, assume you have selected sth
    pos = om.MPoint(obj)
  • get active viewport camera name
    dagPath = om.MDagPath() # dag path holder
    omui.M3dView().active3dView().getCamera(dagPath) # get info
    cam_name = om.MFnDagNode(dagPath.transform()).fullPathName() # get cam name
  • get viewport width and height in monitor pixel unit
  • Make Move tool aim to camera, or parallel to camera; Good for matchmove
    by Shining Ying 
    make move tool aim to camera (with object/vtx selection)
    import maya.cmds as cmds
    import maya.OpenMaya as om
    import math
    def getLocalVecToWorldSpaceAPI(obj, vec=om.MVector.xAxis):
        # ref:
        # take obj's local space vec to workspace, default object x axis
        selList = om.MSelectionList()
        nodeDagPath = om.MDagPath()
        selList.getDagPath(0, nodeDagPath)
        matrix = nodeDagPath.inclusiveMatrix()
        vec = (vec * matrix).normal()
        return vec.x, vec.y, vec.z
    def alignObjectToVector(obj, axis, aim_dir, up_guide_dir = None, side_axis=None, side_guide_dir = None ):
        # grab object transform function lib
        selectionList = om.MSelectionList()
        selectionList.add( obj )
        dagPath = om.MDagPath()
        selectionList.getDagPath(0, dagPath)
        fnTransform = om.MFnTransform(dagPath)
        # axis dict
        axis_dict = {'x':om.MVector.xAxis, 'y':om.MVector.yAxis, 'z':om.MVector.zAxis}
        obj_axis_dir = getLocalVecToWorldSpaceAPI(obj, axis_dict[axis])
        obj_axis_vec = om.MVector(*obj_axis_dir)
        aim_vec = om.MVector(*aim_dir)
        rot_quaternion = obj_axis_vec.rotateTo(aim_vec)
        # optional 2nd axis setup
        if up_guide_dir != None and side_axis != None:
            # based on: aim_vec info
            up_guide_vec = om.MVector(*up_guide_dir)
            # make sure aim is not parallel to up_guide_vec
            if up_guide_vec.isParallel(aim_vec):
                # then align side x to provided backup side axis
                if side_guide_dir != None:
                    alignObjectToVector(obj, side_axis, side_guide_dir)
                side_vec =  up_guide_vec ^ aim_vec # side dir
                alignObjectToVector(obj, side_axis, (side_vec.x,side_vec.y,side_vec.z ))
    cur_selection =
    #fixCam= 'camera1'
    fixCam = ''
    cmds.setToolTo('moveSuperContext') # $gMove
    cur_cam = cmds.modelEditor(cmds.playblast(activeEditor=1), q=1, camera=1)
    if fixCam !='':
        cur_cam = fixCam
    print('Aim Camera: {0}'.format(cur_cam))
    camPos = cmds.xform(cur_cam, q=1, t=1, ws=1)
    objPos = cmds.manipMoveContext('Move', q=1, position=1) # only work for 1st time selection, then it got 1/10
    objPos2 = cmds.xform(cur_selection[0],q=1,t=1,ws=1 ) # override with actual measure
    if (objPos[0] - objPos2[0])**2 > 0.000001:
        objPos_maya = objPos 
        objPos = objPos2
        print('Maya make mistake on move tool position, I correct it from {0} to {1}'.format(str(objPos_maya),str(objPos2)))
    aim_dir = [a-b for a,b in zip(camPos,objPos)] 
    up_ref_dir = getLocalVecToWorldSpaceAPI(cur_cam, vec=om.MVector.yAxis)
    # loc
    tmp_loc = cmds.spaceLocator(n='tmp_move_loc')[0]
    cmds.xform(tmp_loc, t=objPos, ws=1)
    # align
    alignObjectToVector(tmp_loc, 'z', aim_dir, up_guide_dir = up_ref_dir, side_axis='x')
    rot_val = cmds.xform(tmp_loc, q=1, ro=1)
    # back to old selection
    # set tool
    cmds.manipMoveContext( 'Move', e=1, activeHandle=2, m=6, orientAxes=[math.radians(a) for a in rot_val])
    by Shining Ying 
    make move tool paralell to camera (with object/vtx selection)
    cur_selection =
    #fixCam= 'camera1'
    fixCam = ''
    cmds.setToolTo('moveSuperContext') # $gMove
    cur_cam = cmds.modelEditor(cmds.playblast(activeEditor=1), q=1, camera=1)
    if fixCam !='':
        cur_cam = fixCam
    print('Aim Camera: {0}'.format(cur_cam))
    # loc
    tmp_loc = cmds.spaceLocator(n='tmp_move_loc')[0]
    # align
    rot_val = cmds.xform(tmp_loc, q=1, ro=1)
    # back to old selection
    # set tool
    cmds.manipMoveContext( 'Move', e=1, activeHandle=2, m=6, orientAxes=[math.radians(a) for a in rot_val])

Render Related

  • the 'defaultRenderGlobals' object, it contains everything in render setting, and can be accessed through this object'defaultRenderGlobals')
    # get current scene name
    cur_scene = cmds.file(q=1, sn=1, shn=1)
    if cur_scene == '':
        cur_scene = 'untitled'
        print('Not Name Yet')
        cur_scene = cur_scene.rsplit('.',1)[0]
    # get render global output name, update to custom name format
    cur_img_prefix = cmds.getAttr('defaultRenderGlobals.imageFilePrefix')
    if '<Scene>' in cur_img_prefix:
        result_txt = cur_img_prefix.replace('<Scene>', cur_scene.rsplit('_',1)[0])
        cmds.setAttr('defaultRenderGlobals.imageFilePrefix',result_txt , type='string' )
    • it maps all the setting in Render Setting - Common Tab,
      image_ext = 'png'
      cmds.setAttr("defaultRenderGlobals.imageFormat", 32) # png
      scene_path = os.path.dirname(cmds.file(q=True, sceneName=True))
      dir_path_info = [scene_path, 'images']
      render_root_path = os.path.join(*dir_path_info ).replace('\\','/')
      cmds.setAttr("defaultRenderGlobals.imageFilePrefix", render_root_path +'/%s', type='string' )
      # set as name.####.ext format
      cmds.setAttr("defaultRenderGlobals.animation",1) # render multi frame
      cmds.setAttr("defaultRenderGlobals.outFormatControl",0) # use ext
      cmds.setAttr("defaultRenderGlobals.putFrameBeforeExt",1) # use frame num
      cmds.setAttr("defaultRenderGlobals.periodInExt",1) # use .ext
      cmds.setAttr("defaultRenderGlobals.extensionPadding",4) # use padding
      # set width
      cmds.setAttr("defaultResolution.w",1920) # use padding
      cmds.setAttr("defaultResolution.h",1080) # use padding
    • it also contains all the Deadline render job setting
  • get absolution render output path (including dynamic changing output path)
    cmds.renderSettings(firstImageName=1, fullPath=1)
  • get workspace current information
    cmds.workspace(q=1, rootDirectory=True) # root proj dir, holding the scene/image/etc folders
    cmds.workspace(q=1, dir=1) # scene folder full-path
    # scene path and scene name
    cmds.file(q=True, sceneName=True)
    cmds.file(q=True, sceneName=True, shortName=True)
    # segment subpath
    cmds.workspace(fr=1,q=1) # list of segment sub names
    cmds.workspace(fr=['images', 'images']) # set the segment subpath
    cmds.workspace(fr=['depth', 'renderData/depth']) # set the segment subpath
    cmds.workspace(fileRuleEntry='depth') # get the segment path
  • mel to use python write Pre-Render-Mel, to store scene path into render image folder nearby text file (note to escape \ in one line as python(“”) as mel cmd)
    import datetime;
    time_text ='%Y-%m-%d %H:%M:%S');
    import maya.cmds as cmds;
    out_path = cmds.renderSettings(firstImageName=1, fullPath=1)[0]; 
    scene_path = cmds.file(q=True, sceneName=True); 
    parent_dir = os.path.dirname(out_path);
    os.path.exists(os.path.dirname(info_file)) or os.mkdir(os.path.dirname(info_file));
    f = open(info_file,'w');
    print('Render Info written to {0}'.format(info_file))
  • mel version
    python("import datetime;time_text ='%Y-%m-%d %H:%M:%S');import maya.cmds as cmds;out_path = cmds.renderSettings(firstImageName=1, fullPath=1)[0]; parent_dir = os.path.dirname(out_path);info_file=parent_dir+'.info';os.path.exists(os.path.dirname(info_file)) or os.mkdir(os.path.dirname(info_file));scene_path = cmds.file(q=True, sceneName=True);f = open(info_file,'w');f.writelines([scene_path,'\\n',time_text]);f.close();print('Render Info written to '.format(info_file))")
  • use python to set mel setting, note maya sometimes create another tmp ma file to render, and make scene file not the same, thus need to bake current scene name into it. (note the r'''x''' format to auto escape \, the above code also can use r'x' to simplify the escape need)
    # bake scene name into mel cmd, as render will create a another tmp scene file, which cause get scene name different in script
    scene_path = cmds.file(q=1, sceneName=True)
    pre_mel = r'''python("import datetime;time_text ='%Y-%m-%d %H:%M:%S');import maya.cmds as cmds;out_path = cmds.renderSettings(firstImageName=1, fullPath=1)[0]; parent_dir = os.path.dirname(out_path);info_file=parent_dir+'.info';os.path.exists(os.path.dirname(info_file)) or os.mkdir(os.path.dirname(info_file));f = open(info_file,'w');f.writelines(['{0}','\\n',time_text]);f.close();print('Render Info written to {0}'.format(info_file))")'''.format(scene_path)
    # set as per render layer instead pre scene, since each folder has different name
    cmds.setAttr("defaultRenderGlobals.preRenderLayerMel",pre_mel, type="string")
    • preMel: before maya kick render (postMel)
    • preRenderLayerMel: for each render layer, as for each render folder, it do the per render info write (postRenderLayerMel)
    • preRenderMel: per frame (postRenderMel)
  • defaultArnoldRenderOptions:
    • the arnold render setting object, contains all render setting for Arnold render


  • get mel system level operation and maya launch directory
    import maya.mel as mel
  • Publish workflow
  • Project Dir
  • File New
  • File Open
    cmds.file(filePath, o=True)
    cmds.file(renameToSave=1) # prevent overwrite save after open
  • Importing
    cmds.file(filePath, i=True)
  • Reference
    cmds.file(filePath.replace('\\', '/'), r=True, namespace=ns)
    allRefNode = references=1)
    cur_node = allRefNode[0]
    # get reference used namespace - :MyNameSpace
    cur_ns= cmds.referenceQuery(cur_node, namespace=1)
    # get reference used file - C:/user/
    cur_path = cmds.referenceQuery(cur_node, f=1)
    # get reference used file name -
    cur_name = cmds.referenceQuery(cur_node, f=1, shn=1)
  • delete unused node
  • checking
    cmds.file(q=1, sn=1) # filepath
    cmds.file(q=1, sn=1, un=1) # filepath in variable format
    cmds.file(q=1, sn=1, shn=1) # filename
    cmds.file(q=1, sn=1, wcn=1) # withoutCopyNumber
  • check file change and save
    cmds.file(q=1, modified=1)
  • saving as
    # you need to rename first before you can save as
    if ext.lower() == ".ma":
        cmds.file(rename = filePath)
        cmds.file(f=1, save=1, type="mayaAscii")
        save_done = 1
    elif ext.lower() == ".mb":
        cmds.file(rename = filePath)
        cmds.file(f=1, save=1, type="mayaBinary")
        save_done = 1
  • export
    if format_info == "ma":
        if self.uiList['publish_selected_check'].isChecked():
            cmds.file(filePath, f=1, type="mayaAscii", pr=1, exportSelected=1)
            cmds.file(filePath, f=1, type="mayaAscii", pr=1, exportAll=1)
        if self.uiList['publish_selected_check'].isChecked():
            cmds.file(filePath, f=1, type="mayaBinary", pr=1, exportSelected=1)
            cmds.file(filePath, f=1, type="mayaBinary", pr=1, exportAll=1)
  • mel shelf button
    global string $gShelfTopLevel;
    string $currentShelf = `tabLayout -query -selectTab $gShelfTopLevel`;
    setParent $currentShelf;
    // - step 2: prepare path, name, icon, cmd
    string $name="test";
    string $ver="v1.0";
    string $icon="test.png";
    string $cmd ="ls -sl";
    // - step 3: create shelf btn
    shelfButton -c $cmd -ann $name -l $name -imageOverlayLabel $ver -overlayLabelColor 0 0 0 -overlayLabelBackColor 0 0 0 0.0 -image $icon -image1 $icon -sourceType "mel";
    shelfButton -c $cmd -ann $name -l $name -imageOverlayLabel $ver -overlayLabelColor 0 0 0 -overlayLabelBackColor 1 1 1 0.5 -image $icon -image1 $icon -sourceType "mel";
    string $curBtn = `shelfButton -parent $currentShelf -c $cmd -mi "reload" "print ok" -ann $name -l $name -imageOverlayLabel $ver -overlayLabelColor 0 0 0 -overlayLabelBackColor 0 0 0 0.0 -image $icon -image1 $icon -sourceType "mel"`;
    string $curPop = `popupMenu -parent $curBtn -button 1 -ctl 1`; // sh, alt, ctl
    menuItem -p $curPop -l "A A A" -c "print A";
    menuItem -p $curPop -l "Reload" -c "print reload";
  • shelf operation with python
    import maya.cmds as cmds
    import maya.mel as mel
    shelf_holder = mel.eval('$tmpVar=$gShelfTopLevel') # get tab holder
    shelves = cmds.tabLayout(shelf_holder, q=1, selectTab=1) # get selected tab
    shelves = cmds.tabLayout(shelf_holder, q=1, childArray=1) # names of the layout's immediate children.
    cmds.shelfTabLayout(e=1, selectTabIndex=3, shelf_holder) # change active tab by index
    cmds.shelfTabLayout(shelf_holder, e=1, selectTab = shelf_name) # change active tab by name
    cmds.shelfLayout(shelf_name, ex=True) # check shelf exists by name
    shelf_name = mel.eval('addNewShelfTab %s' % shelf_name) # add shelf
    mel.eval('deleteShelfTab %s' % shelf_name) # delete shelf
    cmds.saveAllShelves(shelf_holder) # save all shelfs
  • Maya path customize: It looks like maya only initialize those PATHs info during startup, manually add them after restart, maya still complain can't find the path. So use following method first.
    1. maya.env method (It loads before UI, before
      MAYA_MODULE_PATH = D:\MayaModule\mgear_3.5.1
      MAYA_PLUG_IN_PATH = D:\whatever\plugin
      PYTHONPATH = D:\whatever\scripts
    2. method (load after env, before UI) (~\Documents\maya\version\scripts)
      print("\nLoading Pipeline from\n")
    3. userSetup.mel method (load after UI loaded) (~\Documents\maya\version\scripts)
      print("\nLoading Pipeline from userSetup.mel\n");
      $path1 = "C:/myscripts/whatever/path1";
      $path2 = "C:/myscripts/whatever/path2";
      string $newScriptPath = $path1 + ";" +  $path2 + ";" +`getenv "MAYA_SCRIPT_PATH"`;
      putenv "MAYA_SCRIPT_PATH" $newScriptPath;
      // or source other config script
      $script1 = "C:/myscripts/whatever/script1.mel";
      $script2 = "C:/myscripts/whatever/script2.mel";
      source $script1;
      source $script2;
    4. cmd or bash shell method (run after userSetup.mel)
      // bash
      alias proj1="export MAYA_SCRIPT_PATH=path/to/scripts:$MAYA_SCRIPT_PATH; maya;"
      // dos
      set MAYA_MODULE_PATH=R:/Pipeline/maya/modules;
      "C:/Program Files/Autodesk/Maya2020/bin/maya.exe" -c "python(\"print('Loading Pipeline from Launch Cmd')\")"
  • convert fbx file to mb file, and cleanup geo naming and material naming and texture node naming
    import maya.cmds as cmds
    import maya.mel as mel
    import os
    root_path = r'__________fbx______folder_path'
    fbx_list = [x for x in os.listdir(root_path ) if x.endswith('.fbx') ]
    prefix = 'rock_' # prefix for new name
    for each_fbx in fbx_list:
        #each_fbx = fbx_list[0]
        name = each_fbx.split('.',1)[0] # get file name (eg. rockName_1.fbx)
        id = name.rsplit('_',1)[-1] # get file index number
        # new file
        cmds.file(os.path.join(root_path, each_fbx), i=1)
        anim_list ='animCurveTA')'animCurveTL')'animCurveTU')
        if len(anim_list)>0:
        # mesh
        mesh_list = [ cmds.listRelatives(x,p=1)[0] for x in'mesh')]
        new_geo = cmds.rename(mesh_list[0], prefix+str(id)+'_geo') # assume single geo in scene
        cmds.xform(new_geo, t=(0,0,0),ro=[0,0,0])
        cmds.makeIdentity(new_geo, s=1,apply=1)
        # material
        mat_node = cmds.shadingNode('lambert', asShader=1, n=prefix+str(id)+'_mat')
        cmds.hyperShade(new_geo, assign=mat_node)
        cmds.sets(new_geo, e=1, forceElement=mat_node+'SG')
        # texture
        file_node = [x for x in'file') if x.startswith('Map')][0] # assume the file node named "Map***"
        new_file = cmds.rename(file_node, prefix+str(id)+'_map')
        cmds.connectAttr(new_file+'.ot', mat_node+'.it')
        cmds.connectAttr(new_file+'.oc', mat_node+'.c')
        # save
        cmds.file(rename = os.path.join(root_path, name+'.mb'))
        cmds.file(f=1, save=1, type='mayaBinary')
        # ask for verify as it runs
        res = cmds.confirmDialog(title="Confirm", message="Next?", button=["Yes", "No"], defaultButton="Yes", cancelButton="No", dismissString="No")
        if res == 'No':


  • load and source mel script
    mel_path = r"D:\Dev\Pipeline\PyMaya\maya_3rd_tool\someMelScript.mel"
    mel.eval('source "{0}"'.format(mel_path.replace('\\','/')))
  • use python to ask maya to execute a python command in its variable scope
    # this enable external python tool to ask maya to run a command with variable in maya's scope


  • open socket for external communication
    # open port in maya
    import maya.cmds as cmds
    port = 7001
    open_already = 0
        cmds.commandPort(name=":{0}".format(port), sourceType="python")
        open_already = 1
    if open_already == 1:
        print('Port already open')
    # send to Maya from external python
    import socket
    host = '' # localhost
    port = 7001
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect((host, port))
    client.send('print "cool from external"')
    # close port in maya
    cmds.commandPort(name=":7001", close=1)


  • check and load python plugins
    if not cmds.pluginInfo('', query=True, loaded=True):
  • group a portion of cmds into one undo
    # after end of the operation and before error rise, put
  • check plugin in use in a scene
    plugin_list = sorted(cmds.pluginInfo(q=1,listPlugins=1))
    plugin_list_inUse = sorted(cmds.pluginInfo(q=1,pluginsInUse=1))
  • remove unknown plugin from Maya 2015 onwards
    oldplugins = cmds.unknownPlugin(q=True, list=True)
    for plugin in oldplugins:
        cmds.unknownPlugin(plugin, remove=True)

Maya Qt GUI Tool Development

  • check Python version and bit
    import sys;print(sys.version)
    import struct;print( 8 * struct.calcsize("P"))
  • write PyQt4 GUI python code in Maya
    1. you need
      • either compile
      • or download PyQt4 binary package
    2. then
      • either copy them into Maya's local python site package
      • or add your “correct Python version and bit PyQt4” path to sys.path in Maya
        # check Maya python version and bit
        import sys;print(sys.version)
        import struct;print( 8 * struct.calcsize("P"))
        # check Maya python included path
        import sys;
        for each in sys.path:
            print each
        # maya's local addition python package place:
        # example on windows: C:\Program Files\Autodesk\Maya2015\Python\lib\site-packages
        # or add my downloaded PyQt4 install python,
        # PyQt4 windows binary link (you can install/extra to any place, just link like below):
        from PyQt4 import QtGui,QtCore
    • or use PySide (PySide version 1.2 is included with Maya 2015, built with Python 2.7 and Maya Qt version 4.8.5.)
      from PySide import QtGui,QtCore
  • explanation of above: PyQt binding vs PySide binding (the 2 common way to link Python and Qt GUI library together) (ref)

My Python Tool

  • shelf load python code
    import maya.cmds as cmds
    from sys import platform
    import sys
    import os
    def getOS():
        ''' get os '''    
        if platform.startswith('win'):
            return 'windows'
        elif platform.startswith('darwin'):
            return 'mac'
        return 'linux'
    def getZ():
        ''' get z_sys path '''    
        z_sys = z_sys.strip()
        z_sys = z_sys.replace("\\","/") # fix windows path    
        return z_sys
    sys.path.append(z_sys+"/xTool/resMaya/py") # much better than source

3rd Party Tool

Python and Maya Qt interface

  • panel creation
    • option: QWidget (it has panel name showing in taskbar), QDialog (not showing in taskbar)
  • layout alignment, QtCore.Qt.AlignTop, AlignRight
    • layout.setAlignment(QtCore.Qt.AlignTop)

Environment setup

Environment of Python in Maya

  • Maya.env:
    import sys
    # much better than eval("source "+$scriptName+".mel")
  • load custom system environment variable, Python shines so much comparing to Mel in this case
    import maya.cmds as cmds
    from sys import platform
    import os
    def getOS():
        if platform.startswith('win'):
            return 'windows'
        elif platform.startswith('darwin'):
            return 'mac'
        return 'linux'
    def getZ():
        ''' get z_sys path '''
        # mel, about -os "nt", "win64", "mac", "linux" and "linux64"
        checkOS = getOS() 
        #if($checkOS == "windows") $z_sys=system("echo %z_sys%"); # windows
        #else $z_sys=system("printf $z_sys") # linux, osx (bash,csh) echo do the name but with new line
        ## remove new line, mel
        #$a=stringToStringArray($z_sys,"\r"); // windows echo use \r newline
        #$a=stringToStringArray($aS,"\n"); // linux echo use \n newline    
        z_sys = z_sys.strip()
        #$z_sys = substituteAllString($z_sys, "\\", "/"); // to normal directory format for windows
        z_sys = z_sys.replace("\\","/")
        # make sure system environment got defined
            print z_sys
        return z_sys

Python in mel basic

  • print string and variable
    print("This is %s" % word)
    print("This is "+word)
  • string and interger
    for i in range (1,10):
  • start using mel as python
    import maya.cmds"ball01",type="mesh")
  • python modules
    import myModule
    # reload
  • eval like in Python
    import maya.mel
    maya.mel.eval("ls -sl")
  • python inside mel
    string $list[]=python("['a','b','c']");

Common Python function

  • to use maya cmds, you need to run this code to get follow working
    import maya.cmds as cmds
  • select all geo from root node
    # method 1, filterExpand
    childs = cmds.listRelatives(root, ad=1, f=1 )
    allgeo = cmds.filterExpand(childs, selectionMask=12)
    # method 2, naming based,type='transform')
    for each in tAll:
        #mel:if(`gmatch $each "*_geo"`) $selected[size($selected)]=$each;
  • note attribute, which is not added by default, need to be added manually
    s="""Long Text
    2nd line;
    cmds.setAttr('base_prefs_null.notes', s, type="string")
  • empty group'bulletGeoGrp',em=1)
  • add to a organizing set
    cmds.sets([c_start, c_end],add='LocatorSet')
  • findRelatedSkinCluster python version (written based on original mel version)
    def findRelatedSkinCluster( skinObject ):
        skinShape = None
        skinShapeWithPath = None
        hiddenShape = None
        hiddenShapeWithPath = None
        cpTest = skinObject, typ="controlPoint" )
        if len( cpTest ):
            skinShape = skinObject
            rels = cmds.listRelatives( skinObject )
            for r in rels :
                cpTest = "%s|%s" % ( skinObject, r ), typ="controlPoint" )
                if len( cpTest ) == 0:
                io = cmds.getAttr( "%s|" % ( skinObject, r ) )
                if io:
                visible = cmds.getAttr( "%s|%s.v" % ( skinObject, r ) )
                if not visible:
                    hiddenShape = r
                    hiddenShapeWithPath = "%s|%s" % ( skinObject, r )
                skinShape = r
                skinShapeWithPath = "%s|%s" % ( skinObject, r )
        if len( skinShape ) == 0:
            if len( hiddenShape ) == 0:
                return None
                skinShape = hiddenShape
                skinShapeWithPath = hiddenShapeWithPath
        clusters = typ="skinCluster" )
        for c in clusters:
            geom = cmds.skinCluster( c, q=True, g=True )
            for g in geom:
                if g == skinShape or g == skinShapeWithPath:
                    return c
        return None
  • setkeyframe
    cmds.setKeyframe(testGeo+'.v',time=10, value=False)
    cmds.setKeyframe(testGeo+'.v',time=11, value=True)
    # just key the channel
  • delete key
    # delete all keys
    cmds.cutKey('locator1.t',cl=1) # clear clear without copy to clipboard
    # delete some of keys
    # 1. get key frame list
    key_list = cmds.keyframe('locator1.tx', q=1)
    key_list = cmds.keyframe('locator1', q=1, at='tx')
    # current key value
    cur_v = cmds.getAttr('locator1.tx', time=1)
  • run through frame range
    startFrame = cmds.playbackOptions(q=1, min=1)
    endFrame = cmds.playbackOptions(q=1, max=1)
    for f in range(int(startFrame), int(endFrame+1)):
  • emitter, particle, instance, field
    # creation particle system
    # node subfix
    mN_mdl_subfix='_mdl' #multDoubleLinear
    # emitter and particle
    # connect dynamic
    # set initial value
    for attr, v in e_conf.items():
        cmds.setAttr(mE+'.'+attr, v)
    # instance
    cmds.connectAttr(mP+'.instanceData[0].instancePointData', mI+'.inputPoints')
    # instance Mesh
    cmds.connectAttr(mMesh+'.matrix', mI+'.inputHierarchy[0]')
    cmds.particleInstancer(mP, e=1, name=mI, aimDirection='velocity')
    # field
    for attr, v in ft_conf.items():
        cmds.setAttr(mFt+'.'+attr, v)
  • sin and cost function
    import math
    # aim vector to rotation value
    # rotation
    aimDir=cmds.particle(pName,q=1,at='velocity', order=i)
    if(aimDir[2]>0): # check +z normal condition, result (-90,90)
    elif(aimDir[2]==0): # check z=0 xy panel, which rotateX=+-90 condition -90 or 90 or 0
        if(aimDir[0]>0): # +xy panel condition
        elif(aimDir[0]<0): # -xy panel condition
        elif(aimDir[0]==0): # 0=xy panel
        else: # for easy reading only
    elif(aimDir[2]<0): # check -z +-90 condition (-90,-180) and (90,180) and 180
        if(aimDir[0]>0): # +xy panel condition
        elif(aimDir[0]<0): # -xy panel condition
        elif(aimDir[0]==0): # 0=xy panel condition
        else: # for easy reading only
    else: # for easy reading only

RnD List

Angle Calculation

  • 2D rotation of an angle from (x,y) to (x1,y1)
  • Proof
    let A (x,y) to A1 (x1,y1) turns degree of a in respect of origin.
    in Polar coordinate, 
    so, A (o,R), A1 (o1,R)
    from A1, we have
    as o1=o+a, resulting o1 from turn o in a degree, we have
    based on formula of cos(A+B)=cosA*cosB-sinA*sinB; sin(A+B)=sinA*cosB+cosA*sinB;
    as in Polar coordinate, the R*cos(o) is actually x, R*sin(o) is actually y, so we have
    in matrix form, it is same is the question above, Done.
  • thus in 3D space, we expand to have:

Vector calculation

  • in Maya
    • OpenMaya MVector method
      import maya.cmds as cmds
      from maya.OpenMaya import MVector
      # function: make selected points into a sphere/circle based on the defined center.
    , fl=1)[0]
      pointVectors = []
      for pnt in points:
          v = MVector(*cmds.pointPosition(pnt))
      v_center = MVector(*cmds.pointPosition(center))
      pointDistances = []
      for v in pointVectors:
          pointDistances.append((v - v_center).length())
      minRadius = min(pointDistances)
      for pnt,v in zip(points, pointVectors):
          new_v = (v - v_center).normal()*minRadius + v_center
          cmds.xform(pnt, ws=1, t=(new_v.x, new_v.y, new_v.z))

Plane Intersection calcuation

  • 2 planes (represented by 2 vectors) intersection line

Math tutorial links

Local space and World space and Screen space

Fluid Calculation model

3D Camera model

VR rendering in 3D