Differences
This shows you the differences between two versions of the page.
Previous revision | |||
— | graphic:python:maya [2021/10/02 08:02] (current) – [selection related] ying | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== 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 <code python> | ||
+ | * Python can run directly in mel mode, like <code python> | ||
+ | * Python can call maya C++ API (maya api 1.0), like <code python> | ||
+ | import maya.OpenMayaAnim as oma | ||
+ | aim_dir = cmds.xform(myLoc, | ||
+ | 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.rotateBy(rot_quaternion) | ||
+ | # 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) <code python> | ||
+ | aim_dir = cmds.xform(myLoc, | ||
+ | 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 | ||
+ | </ | ||
+ | * ref: | ||
+ | * maya 2010 api 1.0 ref: http:// | ||
+ | * maya 2013 api 2.0 doc: http:// | ||
+ | * maya 2017 api 2.0 doc: http:// | ||
+ | * 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 | ||
+ | |||
+ | * to do and merge into this | ||
+ | * [[cgwiki: | ||
+ | * [[cgwiki: | ||
+ | |||
+ | ===== Intro to Maya API concepts ===== | ||
+ | |||
+ | * moved all over from my old cgwiki: | ||
+ | |||
+ | ====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, | ||
+ | - 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. | ||
+ | - 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' | ||
+ | |||
+ | ====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, | ||
+ | * 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:: | ||
+ | * an MFn:: | ||
+ | * an MFn:: | ||
+ | * 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. ([[http:// | ||
+ | * The DAG relates to the DG in that every DAG node is implemented as a DG node, both shapes and transforms. The only " | ||
+ | * 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& | ||
+ | * MDataHandle inputDH=data.inputValue(inAttrName, | ||
+ | * MDataHandle outputDH=data.outputValue(outAttrName); | ||
+ | * data.setClean(plug) | ||
+ | * return MS:: | ||
+ | * static void* creator() | ||
+ | * static MStatus initialize() | ||
+ | * MTypeId yourNode:: | ||
+ | * attr: readable(output? | ||
+ | * attr: multi(array? | ||
+ | * addAttribute(inAttrName); | ||
+ | * MPxLocatorNode: | ||
+ | * MPxIkSolverNode: | ||
+ | * MPxDeformerNode: | ||
+ | * MPxFieldNode: | ||
+ | * MPxEmitterNode: | ||
+ | * MPxSpringNode: | ||
+ | * MPxManipContainer: | ||
+ | * MPxSurfaceShape: | ||
+ | * MPxObjectSet: | ||
+ | * MPxHwShaderNode: | ||
+ | * MPxTransform: | ||
+ | |||
+ | ====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:: | ||
+ | * compound attribute: a collection of other attributes, called child attribute | ||
+ | * MFnCompoundAttribute | ||
+ | * example, color <code cpp> | ||
+ | color1R = nAttr.create( “color1R”, | ||
+ | color1G = nAttr.create( “color1G”, | ||
+ | color1B = nAttr.create( “color1B”, | ||
+ | color1 = nAttr.create( “Sides”, | ||
+ | nAttr.setStorable(true); | ||
+ | nAttr.setUsedAsColor(true); | ||
+ | nAttr.setDefault(1.0f, | ||
+ | addAttribute(color1R); | ||
+ | addAttribute(color1G); | ||
+ | addAttribute(color1B); | ||
+ | addAttribute(color1); | ||
+ | attributeAffects(color1R, | ||
+ | attributeAffects(color1G, | ||
+ | attributeAffects(color1B, | ||
+ | attributeAffects(color1, | ||
+ | </ | ||
+ | * dynamic attribute: a attribute is dynamically created when needed. | ||
+ | * MFnDependencyNode:: | ||
+ | |||
+ | ====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' | ||
+ | * Data creator is for creating data to be into MDataBlock, like output plug of another node, | ||
+ | * mostly for heavy data like mesh | ||
+ | * MFnData (MFnMeshData, | ||
+ | |||
+ | ====DG shader node==== | ||
+ | * static MObject color1R, | ||
+ | * static MObject aOutColorR, aOutColorG, aOutColorB, | ||
+ | * static MObject aNormalCameraX, | ||
+ | * static MObject aPointCameraX, | ||
+ | * void MStatus compute( const MPlug&, MDataBlock& | ||
+ | |||
+ | ====DG shader node type==== | ||
+ | * textures/2d | ||
+ | * textures/3d | ||
+ | * textures/ | ||
+ | * shader/ | ||
+ | * shader/ | ||
+ | * shader/ | ||
+ | * light | ||
+ | * utility/ | ||
+ | * utility/ | ||
+ | * utility/ | ||
+ | * imageplane | ||
+ | * postprocess/ | ||
+ | |||
+ | note: shader=material | ||
+ | |||
+ | ====DG shader node creation==== | ||
+ | |||
+ | * example, Blinn creation <code javascript> | ||
+ | // same as: | ||
+ | createNode blinn; | ||
+ | connectAttr blinn1.message defaultShaderList1.shaders;</ | ||
+ | * if creation option checked "with shading group" <code javascript> | ||
+ | connectAttr -f blinn1.outColor blinn1SG.surfaceShader; | ||
+ | </ | ||
+ | * example, volume creation <code javascript> | ||
+ | shadingNode -asShader lightFog; | ||
+ | sets -renderable true -noSurfaceShader true -empty -name lightFog1SG; | ||
+ | connectAttr -f lightFog1.outColor lightFog1SG.volumeShader; | ||
+ | </ | ||
+ | * example, displacement creation <code javascript> | ||
+ | shadingNode -asShader displacementShader; | ||
+ | sets -renderable true -noSurfaceShader true -empty -name displacementShader1SG; | ||
+ | connectAttr -f displacementShader1.displacement displacementShader1SG.displacementShader; | ||
+ | </ | ||
+ | * example, texture 2d <code javascript> | ||
+ | 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] projection1.pm; | ||
+ | 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] brownian1.pm; | ||
+ | </ | ||
+ | |||
+ | ====DG shape node ==== | ||
+ | |||
+ | * MPxSurfaceShape: | ||
+ | * MPxSurfaceShapeUI: | ||
+ | * MPxGeometryIterator: | ||
+ | * MPxGeometryData: | ||
+ | * MPxNode | ||
+ | |||
+ | * shape component: | ||
+ | * MFnComponent | ||
+ | * MFnSingleIndexedComponent: | ||
+ | * MFnDoubleIndexedComponent: | ||
+ | * MFnTripleIndexedComponent: | ||
+ | |||
+ | * shape component access: | ||
+ | * MFnComponent | ||
+ | * MFn:: | ||
+ | * override access | ||
+ | * MPxSurfaceShape:: | ||
+ | * check call process (name, index, range): | ||
+ | * MPxSurfaceShape:: | ||
+ | * iterate component: | ||
+ | * MPxGeometryIterator <code cpp> | ||
+ | virtual bool acceptsGeometryIterator( bool writeable ); | ||
+ | virtual bool acceptsGeometryIterator( MObject&, | ||
+ | </ | ||
+ | * component manipulator | ||
+ | * MPxSurfaceShape:: | ||
+ | * to speedup of setting attribute values, use MPxNode:: | ||
+ | * tweak storing vs no-history access | ||
+ | * set setAttr/ | ||
+ | * connection around geometry data | ||
+ | * define from MPxGeomtryData based on MPxData | ||
+ | * and iterator: virtual MPxGeometryIterator* iterator(MObjectArray& | ||
+ | * 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, | ||
+ | * MItMeshEdge (Edge comp, e-f, e-v, edge smooth, adjacent comp) | ||
+ | * MItMeshVertex, | ||
+ | * MFnMesh (mesh operate on comp) | ||
+ | * meshFn.numPolygons(); | ||
+ | * meshFn.numEdges(); | ||
+ | * meshFn.numVertices(); | ||
+ | * meshFn.polygonVertexCount(); | ||
+ | * meshFn.numUVs(); | ||
+ | * polygon history | ||
+ | * original polyShape -> modifier -> modifier -> (new modifier)-> | ||
+ | * polygon custom modifier | ||
+ | * MPxCommand | ||
+ | * MStatus doModifyPoly(); | ||
+ | * MStatus redoModifyPoly(); | ||
+ | * MStatus undoModifyPoly(); | ||
+ | * polygon exporter | ||
+ | * class polyExporter, | ||
+ | |||
+ | |||
+ | |||
+ | ====== Maya in command line ====== | ||
+ | |||
+ | * launch Maya GUI interface with custom path setting and startup cmd or script at launch time, windows batch example <code batch> | ||
+ | set MAYA_MODULE_PATH=Z:/ | ||
+ | set MAYA_SHELF_PATH=Z:/ | ||
+ | set MAYA_PRESET_PATH=Z:/ | ||
+ | |||
+ | set MAYA_PLUG_IN_PATH=Z:/ | ||
+ | set MAYA_PLUG_IN_LIST=fbxmaya.mll; | ||
+ | |||
+ | set MAYA_SCRIPT_PATH=Z:/ | ||
+ | set MAYA_SCRIPT_PATH=Z:/ | ||
+ | set MAYA_SCRIPT_PATH=Z:/ | ||
+ | |||
+ | set PYTHONPATH=Z:/ | ||
+ | set PYTHONPATH=Z:/ | ||
+ | |||
+ | " | ||
+ | </ | ||
+ | |||
+ | * process file with Maya in commandline only interface with " | ||
+ | |||
+ | |||
+ | **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 | | ||
+ | |||
+ | |||
+ | |||
+ | ====== Preference ====== | ||
+ | |||
+ | * set viewport display color <code python> | ||
+ | # active=1 if for active color</ | ||
+ | |||
+ | ====== Viewport ====== | ||
+ | |||
+ | * list all maya UI element and close some <code python> | ||
+ | allWindows = cmds.lsUI(windows=1) | ||
+ | toClose_win_list = [x for x in allWindows if x not in [' | ||
+ | for each in toClose_win_list : | ||
+ | cmsd.deleteUI(each) | ||
+ | </ | ||
+ | |||
+ | * select tool filter <code python> | ||
+ | cmds.selectType(allObjects=0) # query by cmds.selectType(allObjects=1, | ||
+ | cmds.selectType(locator=1) | ||
+ | </ | ||
+ | |||
+ | * display layer management <code python> | ||
+ | cmds.select(objList) | ||
+ | cmds.createDisplayLayer(name=" | ||
+ | |||
+ | cmds.setAttr(layer + " | ||
+ | </ | ||
+ | * display defined curve smoother, (note only during current maya session, restart maya will back to default) <code python> | ||
+ | cmds.displaySmoothness([' | ||
+ | </ | ||
+ | |||
+ | ====== 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 <code python> | ||
+ | * get panel currently under pointer <code python> | ||
+ | |||
+ | * list maya UI \\ ref: (scan through all Maya UI elements) http:// | ||
+ | cmds.lsUI(type=' | ||
+ | cmds.lsUI(windows=1) # list all window | ||
+ | |||
+ | # trick cmds | ||
+ | cmds.lsUI(controlLayouts=1, | ||
+ | layoutType = cmds.objectTypeUI(' | ||
+ | childs = cmds.layout(' | ||
+ | </ | ||
+ | * list maya UI's child and object type and parent< | ||
+ | # most maya layout has a -childArray flag to get child | ||
+ | cmds.tabLayout(" | ||
+ | # get UI type | ||
+ | cmds.objectType(" | ||
+ | # then continue call with the object' | ||
+ | |||
+ | # to get parent | ||
+ | cmds.tabLayout(" | ||
+ | </ | ||
+ | * toggle maya window title bar visible and edit< | ||
+ | cmds.window(' | ||
+ | # hide title bar (true no UI by: press ctrl+space; then run this cmd) | ||
+ | |||
+ | newT=" | ||
+ | cmds.window(' | ||
+ | </ | ||
+ | * toggle outline window <code python> | ||
+ | if cmds.window(' | ||
+ | cmds.deleteUI(' | ||
+ | else: | ||
+ | mel.eval(' | ||
+ | </ | ||
+ | |||
+ | * ignore a set of scripting/ | ||
+ | # ref: http:// | ||
+ | import maya.mel as mel | ||
+ | import maya.cmds as cmds | ||
+ | # nextFrame (' | ||
+ | cmds.undoInfo(stateWithoutFlush=0) | ||
+ | mel.eval(' | ||
+ | cmds.undoInfo(stateWithoutFlush=1) | ||
+ | |||
+ | # previousFrame (' | ||
+ | cmds.undoInfo(stateWithoutFlush=0) | ||
+ | mel.eval(' | ||
+ | cmds.undoInfo(stateWithoutFlush=1) | ||
+ | |||
+ | # nextKey (' | ||
+ | cmds.undoInfo(stateWithoutFlush=0) | ||
+ | cmds.currentTime(cmds.findKeyframe(timeSlider=1, | ||
+ | cmds.undoInfo(stateWithoutFlush=1) | ||
+ | |||
+ | # previousKey (',' | ||
+ | cmds.undoInfo(stateWithoutFlush=0) | ||
+ | cmds.currentTime(cmds.findKeyframe(timeSlider=1, | ||
+ | cmds.undoInfo(stateWithoutFlush=1) | ||
+ | |||
+ | # firstKey | ||
+ | cmds.undoInfo(stateWithoutFlush=0) | ||
+ | cmds.currentTime(cmds.findKeyframe(which=' | ||
+ | cmds.undoInfo(stateWithoutFlush=1) | ||
+ | |||
+ | # lastKey | ||
+ | cmds.undoInfo(stateWithoutFlush=0) | ||
+ | cmds.currentTime(cmds.findKeyframe(which=' | ||
+ | cmds.undoInfo(stateWithoutFlush=1) | ||
+ | </ | ||
+ | |||
+ | * group a set of scripting in undo history <code python> | ||
+ | cmds.undoInfo(openChunk=1) | ||
+ | # your scripting block here | ||
+ | cmds.undoInfo(closeChunk=1) | ||
+ | </ | ||
+ | * get maya global variable to python name, example <code python> | ||
+ | mel.eval(' | ||
+ | </ | ||
+ | |||
+ | * toggle channel box nice and short names <code python> | ||
+ | main_cb = ' | ||
+ | next_state = not cmds.channelBox(main_cb, | ||
+ | cmds.channelBox(main_cb, | ||
+ | </ | ||
+ | |||
+ | * toggle script editor echo and clear echo <code python> | ||
+ | cmdPanel = ' | ||
+ | next_state = not cmds.cmdScrollFieldReporter(cmdPanel, | ||
+ | cmds.cmdScrollFieldReporter(cmdPanel, | ||
+ | |||
+ | # clear echo | ||
+ | cmds.cmdScrollFieldReporter(cmdPanel, | ||
+ | </ | ||
+ | |||
+ | * create script editor <code python> | ||
+ | if cmds.cmdScrollFieldExecuter(ui_name, | ||
+ | cmds.deleteUI(ui_name) | ||
+ | mui = cmds.cmdScrollFieldExecuter(ui_name, | ||
+ | |||
+ | selected_text = cmds.cmdScrollFieldExecuter(mui, | ||
+ | # change selected or insert text | ||
+ | cmds.cmdScrollFieldExecuter(mui, | ||
+ | |||
+ | # set search text and search next | ||
+ | cmds.cmdScrollFieldExecuter(mui, | ||
+ | res = cmds.cmdScrollFieldExecuter(mui, | ||
+ | |||
+ | # go to line number | ||
+ | cmds.cmdScrollFieldExecuter(mui, | ||
+ | |||
+ | # get script editor whole text | ||
+ | script_text = cmds.cmdScrollFieldExecuter(mui, | ||
+ | </ | ||
+ | * create script history logger <code python> | ||
+ | mui = cmds.cmdScrollFieldReporter(ui_name)#, | ||
+ | |||
+ | # clear history | ||
+ | cmds.cmdScrollFieldReporter(mui, | ||
+ | </ | ||
+ | |||
+ | * open component editor and show skining tab <code python> | ||
+ | mel.eval(' | ||
+ | # active smooth skin tab | ||
+ | tab_name_list = cmds.tabLayout(" | ||
+ | for i in range(len(tab_name_list)): | ||
+ | if tab_name_list[i] == " | ||
+ | cmds.tabLayout(" | ||
+ | </ | ||
+ | |||
+ | * add selected node into hyperShade and related <code python> | ||
+ | for each in cmds.ls(sl=1): | ||
+ | cmds.hyperGraph(" | ||
+ | | ||
+ | cmds.hyperGraph(" | ||
+ | newBookmark = cmds.hyperGraph(" | ||
+ | cmds.setAttr(newBookmark+" | ||
+ | </ | ||
+ | * add selected node in nodeEditor and related <code python> | ||
+ | for each in cmds.ls(sl=1): | ||
+ | cmds.nodeEditor(' | ||
+ | |||
+ | cmds.getPanel(scriptType=" | ||
+ | cmds.nodeEditor(' | ||
+ | </ | ||
+ | * toggle channel box long short name display< | ||
+ | import maya.mel as mel | ||
+ | mel.eval(' | ||
+ | </ | ||
+ | * maya mel button with python function with parameter <code python> | ||
+ | def process_func(data): | ||
+ | print(data) | ||
+ | cmds.button( command = lambda *args: process_func(' | ||
+ | * show GraphEditor <code python> | ||
+ | # get panel by name | ||
+ | whichPanel = cmds.getPanel(withLabel=' | ||
+ | cmds.scriptedPanel(whichPanel, | ||
+ | wndName = whichPanel + " | ||
+ | </ | ||
+ | ====== Hotkey ====== | ||
+ | |||
+ | **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 <code python> | ||
+ | * create hotkey <code python> | ||
+ | import maya.cmds as cmds | ||
+ | # cmds.hotkey(factorySettings=1) | ||
+ | cmds.hotkey(autoSave=0) | ||
+ | |||
+ | # -- toggle select mode | ||
+ | g_shiToggleMode=0 | ||
+ | def toggleSelectMode(curMode): | ||
+ | global g_shiToggleMode | ||
+ | print([g_shiToggleMode, | ||
+ | if g_shiToggleMode != curMode: | ||
+ | if curMode == 1: | ||
+ | cmds.SelectVertexMask() | ||
+ | elif curMode == 2: | ||
+ | cmds.SelectEdgeMask() | ||
+ | elif curMode == 3: | ||
+ | cmds.SelectFacetMask() | ||
+ | elif curMode == 0: | ||
+ | cmds.SelectToggleMode() | ||
+ | g_shiToggleMode = curMode | ||
+ | else: | ||
+ | cmds.SelectToggleMode() | ||
+ | g_shiToggleMode = 0 | ||
+ | |||
+ | for i in range(1, | ||
+ | cmds.nameCommand(' | ||
+ | cmds.hotkey(k=str(i), | ||
+ | |||
+ | # -- viewport | ||
+ | cmds.nameCommand(' | ||
+ | cmds.hotkey(k=' | ||
+ | # -- windows | ||
+ | cmds.nameCommand(' | ||
+ | cmds.hotkey(k=' | ||
+ | # -- script editor | ||
+ | cmds.nameCommand(' | ||
+ | cmds.hotkey(k=' | ||
+ | #-- hyperShade | ||
+ | cmds.nameCommand(' | ||
+ | cmds.hotkey(k=' | ||
+ | #-- graph editor | ||
+ | cmds.nameCommand(' | ||
+ | cmds.hotkey(k=' | ||
+ | #-- custom | ||
+ | cmds.nameCommand(' | ||
+ | cmds.hotkey(k=' | ||
+ | </ | ||
+ | * Hotkey with Qt <code python> | ||
+ | # ref: http:// | ||
+ | # qt import | ||
+ | try: | ||
+ | from PySide import QtGui, QtCore | ||
+ | import PySide.QtGui as QtWidgets | ||
+ | import shiboken | ||
+ | except ImportError: | ||
+ | try: | ||
+ | from PySide2 import QtCore, QtGui, QtWidgets | ||
+ | import shiboken2 as shiboken | ||
+ | except ImportError: | ||
+ | pass | ||
+ | import maya.cmds as cmds | ||
+ | import maya.OpenMayaUI as omui | ||
+ | hotkey = {} | ||
+ | mayaWindow = shiboken.wrapInstance(long(omui.MQtUtil.mainWindow()), | ||
+ | |||
+ | def shortcut_process(keyname): | ||
+ | if keyname == ' | ||
+ | if ' | ||
+ | print(' | ||
+ | else: | ||
+ | # give key event to maya | ||
+ | hotkey[keyname].setEnabled(0) | ||
+ | e = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, | ||
+ | QtCore.QCoreApplication.postEvent(mayaWindow , e) | ||
+ | cmds.evalDeferred(partial(hotkey[keyname].setEnabled, | ||
+ | else: | ||
+ | pass | ||
+ | |||
+ | # shortcut | ||
+ | hotkey[' | ||
+ | hotkey[' | ||
+ | hotkey[' | ||
+ | </ | ||
+ | |||
+ | ====== Object Level API Process ====== | ||
+ | |||
+ | ** Python Maya API reference ** | ||
+ | |||
+ | * Python script and Python Maya API example: | ||
+ | * http:// | ||
+ | * Cope with Python in Maya C++ API with MScriptUtil, | ||
+ | * http:// | ||
+ | * Maya API 2.0 for python, new python way of the redefined C++ API from Maya 2013 | ||
+ | * http:// | ||
+ | |||
+ | ** common api page offical reference page ** | ||
+ | * all : http:// | ||
+ | |||
+ | ^ MSelectionList | http:// | ||
+ | ^ MDagPath | http:// | ||
+ | ^ MObject | http:// | ||
+ | ^ MFn | http:// | ||
+ | ^ MFnDagNode | http:// | ||
+ | ^ MFnDependencyNode | http:// | ||
+ | ^ MScriptUtil | http:// | ||
+ | ^ MFnMesh | http:// | ||
+ | ^ MItDependencyGraph | http:// | ||
+ | |||
+ | |||
+ | ===== DAG path, object and nodes ===== | ||
+ | |||
+ | **om.MDagPath**: | ||
+ | * 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 <code python> | ||
+ | selectionList = om.MSelectionList() | ||
+ | selectionList.add(mesh_name) | ||
+ | mesh_dagPath = om.MDagPath() | ||
+ | selectionList.getDagPath(0, | ||
+ | print(mesh_dagPath.partialPathName()) # if done correct, it will give same name as mesh_name | ||
+ | </ | ||
+ | * MObject to MDagPath <code python> | ||
+ | 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() | ||
+ | geo_dagNode.getPath(geo_dagPath) | ||
+ | print( geo_dagPath.partialPathName() ) # proof geo_dagPath work correctly | ||
+ | </ | ||
+ | * MFnDagNode to MDagPath() <code python> | ||
+ | geo_dagPath = om.MDagPath() | ||
+ | geo_dagNode.getPath(geo_dagPath) | ||
+ | print( geo_dagPath.partialPathName() ) # proof geo_dagPath work correctly | ||
+ | </ | ||
+ | |||
+ | **om.MObject**: | ||
+ | * MDagPath to MObject conversion, <code python> | ||
+ | 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 <code python> | ||
+ | selectionList = om.MSelectionList() | ||
+ | selectionList.add(skinName) | ||
+ | skinClusterObj = om.MObject() | ||
+ | selectionList.getDependNode(0, | ||
+ | skinClusterObj.hasFn(om.MFn.kSkinClusterFilter) # use for testing become cast to the object function | ||
+ | </ | ||
+ | |||
+ | **MFnDagNode**: | ||
+ | * take input Object and provide function to access object as DAG Node, for its attribute and modify | ||
+ | * it has same function as MDagPath like <code python> | ||
+ | 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, | ||
+ | |||
+ | 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(), | ||
+ | |||
+ | * str to MFnDagNode <code python> | ||
+ | selectionList = om.MSelectionList() | ||
+ | selectionList.add(mesh_name) | ||
+ | mesh_dagPath = om.MDagPath() | ||
+ | selectionList.getDagPath(0, | ||
+ | mesh_dagNode = om.MFnDagNode(mesh_dagPath) | ||
+ | </ | ||
+ | * MDagPath to MFnDagNode <code python> | ||
+ | my_dagNode = om.MFnDagNode(my_dagPath) | ||
+ | </ | ||
+ | * MObject to MFnDagNode <code python> | ||
+ | my_obj.hasFn(om.MFn.kDagNode) | ||
+ | my_dagNode = om.MFnDagNode(my_obj) | ||
+ | </ | ||
+ | |||
+ | <code python> | ||
+ | import maya.OpenMaya as om | ||
+ | selected = om.MSelectionList() | ||
+ | om.MGlobal.getActiveSelectionList(selected) | ||
+ | dagPath = om.MDagPath() | ||
+ | selected.getDagPath(0, | ||
+ | print(dagPath.fullPathName()) # current full path name | ||
+ | print(dagPath.partialPathName()) # current node name | ||
+ | |||
+ | # get shape count | ||
+ | countUtil = om.MScriptUtil() | ||
+ | countUtil.createFromInt(0) | ||
+ | tmpIntPtr = countUtil.asUintPtr() # unsigned int | ||
+ | dagPath.numberOfShapesDirectlyBelow(tmpIntPtr) | ||
+ | print(countUtil.getUint(tmpIntPtr)) | ||
+ | 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 | ||
+ | print(dagNode.fullPathName()) | ||
+ | |||
+ | # 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 | ||
+ | </ | ||
+ | |||
+ | ===== DG nodes ===== | ||
+ | |||
+ | * **MFnDependencyNode**: | ||
+ | my_DGNode = OpenMaya.MFnDependencyNode(myObj) | ||
+ | print(my_DGNode.name()) | ||
+ | </ | ||
+ | * MFnDependencyNode also has function like attribute editor, editing all the plug/ | ||
+ | |||
+ | * Conversion from other data | ||
+ | * str name to MFnDependencyNode <code python> | ||
+ | selectionList = OpenMaya.MSelectionList() | ||
+ | selectionList.add(my_name) | ||
+ | my_obj = OpenMaya.MObject() | ||
+ | selectionList.getDependNode(0, | ||
+ | my_obj.hasFn(om.MFn.kDependencyNode) # check before convert | ||
+ | my_dgNode = om.MFnDependencyNode(my_obj) | ||
+ | </ | ||
+ | * MDagPath to MFnDependencyNode <code python> | ||
+ | my_obj = my_dagPath.node() | ||
+ | my_obj.hasFn(om.MFn.kDependencyNode) # check before convert | ||
+ | my_dgNode = om.MFnDependencyNode(my_obj) | ||
+ | </ | ||
+ | * MFnDagNode to MFnDependencyNode <code python> | ||
+ | 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**: | ||
+ | |||
+ | other common class: | ||
+ | * **om.MPxNode**: | ||
+ | |||
+ | ===== MPlugs and Attributes ===== | ||
+ | |||
+ | ref: http:// | ||
+ | |||
+ | * 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/ | ||
+ | * 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, | ||
+ | * numElement only refer to data in datablock, to get update value, use <code python> | ||
+ | print(my_arraPlugy.numElements())</ | ||
+ | |||
+ | === Logical Index and Physical Index of Joint in skincluster === | ||
+ | |||
+ | **Concept of Dynamic array** - a pre-defined-size list with pre-loaded empty items | ||
+ | * as you can create array with any pre-defined size, Maya keep it in memory up to the size of last non-empty element; (ref: http:// | ||
+ | * in addition, Maya is using the sparse array structure for array plugs(attributes) | ||
+ | * Maya will automatically resize and create new list of elements for those item whose value is not zero. | ||
+ | * thus the 2 methods in MPlug, where you can get plugs of the array by either logical index (sparse, means with zeros) or physical indexes (those with actual data connection). | ||
+ | * ref: http:// | ||
+ | |||
+ | * example MEL code <code javascript> | ||
+ | $nameArray[5]=" | ||
+ | print(" | ||
+ | print $nameArray; // null null null null null objA | ||
+ | print(" | ||
+ | |||
+ | $logical_index = stringArrayFind( " | ||
+ | print(" | ||
+ | print(" | ||
+ | print(" | ||
+ | print(" | ||
+ | print(" | ||
+ | |||
+ | 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(" | ||
+ | print($nameArray_autoNonSparse_means_noEmpty); | ||
+ | print(" | ||
+ | $physical_index = stringArrayFind( " | ||
+ | print(" | ||
+ | |||
+ | // clear from memory | ||
+ | clear($nameArray); | ||
+ | </ | ||
+ | * 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 \\ {{graphic: | ||
+ | |||
+ | **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 \\ {{graphic: | ||
+ | * since " | ||
+ | * the weight table is actual representation of the weight data; | ||
+ | * **including all the vertex** | ||
+ | * **including all the joints linked to skinCluster**, | ||
+ | * (make sure in component editor > option menu > Uncheck hide zero column) | ||
+ | * (make sure in component editor > option menu > Uncheck sort alphabetically column) | ||
+ | * {{graphic: | ||
+ | * and when saving, actual weight data store in a 1 dimension array { vtx[0][jnt1], | ||
+ | * **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. | ||
+ | |||
+ | ===== Math Node before Calculation with API ===== | ||
+ | |||
+ | * **MScriptUtil** ref: http:// | ||
+ | * 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 <code python> | ||
+ | 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 | ||
+ | </ | ||
+ | |||
+ | ===== Transform, Vector, Matrix, Quaternion, Angular Math ===== | ||
+ | * other align vector related reading (mel only) <code javascript> | ||
+ | /* | ||
+ | rot: rot vector around another vector by a degree | ||
+ | rad_to_deg, deg_to_rad | ||
+ | */ | ||
+ | </ | ||
+ | * **MVector** functions <code python> | ||
+ | # ref: https:// | ||
+ | # MVector: rotateBy (MVector:: | ||
+ | # 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), | ||
+ | # 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, | ||
+ | </ | ||
+ | * **Align Object Axis one at a time to Vector direction** (OpenMaya method) <code python> | ||
+ | import maya.OpenMaya as om | ||
+ | import math | ||
+ | def getLocalVecToWorldSpaceAPI(obj, | ||
+ | # ref: http:// | ||
+ | # take obj's local space vec to workspace, default object x axis | ||
+ | selList = om.MSelectionList() | ||
+ | selList.add(obj) | ||
+ | nodeDagPath = om.MDagPath() | ||
+ | selList.getDagPath(0, | ||
+ | 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 = ' | ||
+ | obj = ' | ||
+ | |||
+ | # grab object transform function lib | ||
+ | selectionList = om.MSelectionList() | ||
+ | selectionList.add( obj ) | ||
+ | dagPath = om.MDagPath() | ||
+ | selectionList.getDagPath(0, | ||
+ | fnTransform = om.MFnTransform(dagPath) | ||
+ | |||
+ | # STEP 1: align Y axis first | ||
+ | refLoc_normal_y = getLocalVecToWorldSpaceAPI(refLoc, | ||
+ | aim_vec_y = om.MVector(*refLoc_normal_y) # break array into 3 parameters | ||
+ | obj_normal_y = getLocalVecToWorldSpaceAPI(obj, | ||
+ | obj_vec_y = om.MVector(*obj_normal_y) | ||
+ | rot_quaternion = obj_vec_y.rotateTo(aim_vec_y) | ||
+ | # absolute version | ||
+ | # | ||
+ | # relative transform on top of existing obj transform | ||
+ | fnTransform.rotateBy(rot_quaternion) | ||
+ | |||
+ | # STEP 2: align Z axis | ||
+ | refLoc_normal_z = getLocalVecToWorldSpaceAPI(refLoc, | ||
+ | aim_vec_z = om.MVector(*refLoc_normal_z) | ||
+ | obj_normal_z = getLocalVecToWorldSpaceAPI(obj, | ||
+ | obj_vec_z = om.MVector(*obj_normal_z) | ||
+ | rot_quaternion = obj_vec_z.rotateTo(aim_vec_z) | ||
+ | fnTransform.rotateBy(rot_quaternion) | ||
+ | </ | ||
+ | |||
+ | * get position <code python> | ||
+ | #general | ||
+ | pos = cmds.xform(obj, | ||
+ | |||
+ | # loc | ||
+ | pos = cmds.getAttr(loc+' | ||
+ | pos = cmds.pointPosition(loc) # world position | ||
+ | |||
+ | |||
+ | #cv - local position from origin at creation time | ||
+ | cmds.getAttr(' | ||
+ | cmds.pointPosition(' | ||
+ | |||
+ | # vtx - | ||
+ | cmds.getAttr(' | ||
+ | cmds.getAttr(" | ||
+ | |||
+ | cmds.pointPosition(' | ||
+ | cmds.setAttr(" | ||
+ | cmds.setAttr(" | ||
+ | cmds.setAttr(" | ||
+ | |||
+ | # 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 <code python> | ||
+ | cmds.makeIdentity(cmds.ls(sl=1), | ||
+ | </ | ||
+ | ====== Selection in API ====== | ||
+ | |||
+ | * get current selection <code python> | ||
+ | selected = om.MSelectionList() | ||
+ | om.MGlobal.getActiveSelectionList(selected) | ||
+ | </ | ||
+ | |||
+ | * get current soft selection and weight< | ||
+ | def getVtxSelectionSoft_OpenMaya(): | ||
+ | # ref: http:// | ||
+ | startTime = cmds.timerX() # 0.13s > no string 0.07s | ||
+ | # soft selection | ||
+ | softSelectionList = om.MSelectionList() | ||
+ | softSelection = om.MRichSelection() | ||
+ | om.MGlobal.getRichSelection(softSelection) | ||
+ | softSelection.getSelection(selectionList) | ||
+ | |||
+ | selectionPath = om.MDagPath() | ||
+ | selectionObj = om.MObject() | ||
+ | iter = om.MItSelectionList( selectionList, | ||
+ | #elements = [] | ||
+ | id_list = [] | ||
+ | weights = [] | ||
+ | node_list = [] | ||
+ | while not iter.isDone(): | ||
+ | iter.getDagPath( selectionPath, | ||
+ | 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 | ||
+ | node_list.append(node) | ||
+ | for i in range(compFn.elementCount()): | ||
+ | # | ||
+ | id_list.append(compFn.element(i)) | ||
+ | weights.append(getWeight(i)) | ||
+ | iter.next() | ||
+ | totalTime = cmds.timerX(startTime=startTime) | ||
+ | print(" | ||
+ | return list(set(node_list)), | ||
+ | </ | ||
+ | |||
+ | * API way to get selection vertex ids (single geo case), and set selection using vertex ids <code python> | ||
+ | def createVtxSelection_cmds(objName, | ||
+ | startTime = cmds.timerX() # 0.039s | ||
+ | result_sel = [' | ||
+ | totalTime = cmds.timerX(startTime=startTime) | ||
+ | print(" | ||
+ | return result_sel | ||
+ | | ||
+ | def createVtxSelection_OpenMaya(objName, | ||
+ | # ref: http:// | ||
+ | startTime = cmds.timerX() # 0.009s | ||
+ | sel = om.MSelectionList() | ||
+ | sel.add(objName) | ||
+ | mdag = om.MDagPath() | ||
+ | sel.getDagPath(0, | ||
+ | util = om.MScriptUtil() | ||
+ | util.createFromList(id_list, | ||
+ | ids_ptr = util.asIntPtr() | ||
+ | ids = om.MIntArray(ids_ptr, | ||
+ | | ||
+ | compFn = om.MFnSingleIndexedComponent() | ||
+ | components = compFn.create( om.MFn.kMeshVertComponent ) | ||
+ | compFn.addElements(ids) | ||
+ | | ||
+ | result_sel = om.MSelectionList() | ||
+ | result_sel.add(mdag, | ||
+ | totalTime = cmds.timerX(startTime=startTime) | ||
+ | print(" | ||
+ | return result_sel | ||
+ | | ||
+ | def getVtxSelection_cmd(l=0): | ||
+ | startTime = cmds.timerX() # 0.18s | ||
+ | selected = cmds.ls(sl=1, | ||
+ | id_list = [x.split(' | ||
+ | totalTime = cmds.timerX(startTime=startTime) | ||
+ | print(" | ||
+ | return selected[0].split[' | ||
+ | | ||
+ | def getVtxSelection_OpenMaya(): | ||
+ | startTime = cmds.timerX() # 0.11s > no comp iter: 0.039 | ||
+ | # normal selection | ||
+ | normalSelectionList = om.MSelectionList() | ||
+ | om.MGlobal.getActiveSelectionList(normalSelectionList) | ||
+ | selectionPath = om.MDagPath() | ||
+ | selectionObj = om.MObject() | ||
+ | indexList = om.MIntArray() | ||
+ | iter = om.MItSelectionList( normalSelectionList, | ||
+ | # | ||
+ | node_list = [] | ||
+ | id_list = [] | ||
+ | while not iter.isDone(): | ||
+ | iter.getDagPath( selectionPath, | ||
+ | selectionPath.pop() | ||
+ | node = selectionPath.fullPathName() | ||
+ | compFn = om.MFnSingleIndexedComponent(selectionObj) # flatten the component selection | ||
+ | compFn.getElements(indexList) | ||
+ | id_list.extend(list(indexList)) | ||
+ | node_list.append(node) | ||
+ | ''' | ||
+ | for i in range(compFn.elementCount()): | ||
+ | normalSelectionElements.append(' | ||
+ | id_list.append(compFn.element(i)) | ||
+ | ''' | ||
+ | iter.next() | ||
+ | totalTime = cmds.timerX(startTime=startTime) | ||
+ | print(" | ||
+ | return list(set(node_list)), | ||
+ | </ | ||
+ | ====== Modeling in Python vs Mel ====== | ||
+ | |||
+ | * list of cmds in both python and mel for modeling | ||
+ | |||
+ | <code python> | ||
+ | # mel: DeleteHistory; | ||
+ | cmds.DeleteHistory() | ||
+ | |||
+ | # show poly vertex | ||
+ | # mel: setPolygonDisplaySettings(" | ||
+ | cmds.polyOptions(r=1, | ||
+ | |||
+ | # mel: ToggleFaceNormalDisplay; | ||
+ | cmds.ToggleFaceNormalDisplay() | ||
+ | |||
+ | # mel: ToggleBackfaceGeometry; | ||
+ | cmds.ToggleBackfaceGeometry() | ||
+ | |||
+ | # mel: ReversePolygonNormals; | ||
+ | cmds.ReversePolygonNormals() | ||
+ | |||
+ | # hard edge | ||
+ | # mel: SoftPolyEdgeElements(0); | ||
+ | cmds.polySoftEdge(a=0) | ||
+ | |||
+ | # show border | ||
+ | # mel: ToggleBorderEdges; | ||
+ | cmds.ToggleBorderEdges() | ||
+ | |||
+ | # dialog to change edge width display | ||
+ | # mel: ChangeEdgeWidth; | ||
+ | cmds.ChangeEdgeWidth() | ||
+ | |||
+ | # show UV window | ||
+ | # mel: TextureViewWindow; | ||
+ | cmds.TextureViewWindow(); | ||
+ | # mel: CenterPivot; | ||
+ | cmds.CenterPivot() | ||
+ | |||
+ | # turn on / off symmetric modeling | ||
+ | ''' | ||
+ | symmetricModelling -e -symmetry true; | ||
+ | symmetricModelling -e -about " | ||
+ | symmetricModelling -e -about " | ||
+ | symmetricModelling -e -axis " | ||
+ | ''' | ||
+ | cmds.symmetricModelling(e=1, | ||
+ | cmds.symmetricModelling(e=1, | ||
+ | |||
+ | # face selection to edge border selection | ||
+ | # mel: select -r `polyListComponentConversion -ff -te -bo`; | ||
+ | cmds.select(cmds.polyListComponentConversion(ff=1, | ||
+ | |||
+ | # selection to shell | ||
+ | # mel: ConvertSelectionToShell; | ||
+ | cmds.ConvertSelectionToShell() | ||
+ | |||
+ | # selection to create layer | ||
+ | ''' | ||
+ | string $tmpname[]=`ls -sl`; | ||
+ | createDisplayLayer -name ($tmpname[0]+" | ||
+ | ''' | ||
+ | selected = cmds.ls(sl=1) | ||
+ | if len(selected)> | ||
+ | cmds.createDisplayLayer(name=selected[0]+' | ||
+ | | ||
+ | # remove from layers | ||
+ | # mel: editDisplayLayerMembers -noRecurse " | ||
+ | cmds.editDisplayLayerMembers(" | ||
+ | |||
+ | |||
+ | #### curve ##### | ||
+ | |||
+ | # show CV | ||
+ | # mel: ToggleCVs; | ||
+ | cmds.ToggleCVs() | ||
+ | |||
+ | # show editable point | ||
+ | # mel: ToggleEditPoints; | ||
+ | cmds.ToggleEditPoints() | ||
+ | |||
+ | # rebuild curve dialog | ||
+ | # mel: RebuildCurveOptions; | ||
+ | cmds.RebuildCurveOptions() | ||
+ | |||
+ | # detach surface by edge or isopam | ||
+ | # mel: DetachCurve; | ||
+ | cmds.DetachCurve() | ||
+ | |||
+ | # detach/ | ||
+ | # mel: CutCurve; AttachCurve; | ||
+ | cmds.CutCurve() | ||
+ | cmds.AttachCurve() | ||
+ | </ | ||
+ | |||
+ | ====== Modeling in API ====== | ||
+ | |||
+ | **Vertex check in API way** | ||
+ | |||
+ | * poly count <code python> | ||
+ | vtx_cnt = cmds.polyEvaluate(geoName, | ||
+ | </ | ||
+ | |||
+ | * get transform to shape to vtx <code python> | ||
+ | selected = om.MSelectionList() | ||
+ | om.MGlobal.getActiveSelectionList(selected) | ||
+ | |||
+ | dagPath = om.MDagPath() | ||
+ | selected.getDagPath(0, | ||
+ | |||
+ | 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 <code python> | ||
+ | # test empty geo shape | ||
+ | all_geo_list = cmds.ls(type=' | ||
+ | zeroVtx_list = [] | ||
+ | for each in all_geo_list: | ||
+ | if cmds.polyEvaluate(each, | ||
+ | zeroVtx_list.append(each) | ||
+ | print(zeroVtx_list) | ||
+ | </ | ||
+ | |||
+ | **Face Check and Correct** | ||
+ | |||
+ | * double side face <code python> | ||
+ | shape_list = cmds.ls(type=' | ||
+ | for shape in shape_list: | ||
+ | cmds.setAttr(shape+" | ||
+ | </ | ||
+ | |||
+ | **Curve related** | ||
+ | |||
+ | * get curve construction info <code python> | ||
+ | def getCurveInfo(curveObj, | ||
+ | d = cmds.getAttr(curveObj+' | ||
+ | p = cmds.getAttr(curveObj+' | ||
+ | p_new = [ [numClean(y, | ||
+ | tmpInfo = cmds.createNode( ' | ||
+ | cmds.connectAttr( curveObj + ' | ||
+ | k = cmds.getAttr( tmpInfo + ' | ||
+ | cmds.delete(tmpInfo) | ||
+ | return (d, p_new, k) | ||
+ | | ||
+ | def numClean(fnum, | ||
+ | rule = " | ||
+ | cnum = float(rule.format(fnum)) | ||
+ | if cnum.is_integer(): | ||
+ | cnum = int(cnum) | ||
+ | return cnum | ||
+ | | ||
+ | def setCurveInfo(curveObj, | ||
+ | # input: dict - d, p; p ; d,p,k | ||
+ | if not isinstance(curveInfo, | ||
+ | print(' | ||
+ | return | ||
+ | if all(x in curveInfo.keys() for x in [' | ||
+ | return cmds.curve(curveObj, | ||
+ | elif all(x in curveInfo.keys() for x in [' | ||
+ | return cmds.curve(curveObj, | ||
+ | else: | ||
+ | return cmds.curve(curveObj, | ||
+ | |||
+ | def buildCurveInfo(curveInfo, | ||
+ | # input: dict - d, p; p ; d,p,k | ||
+ | if not isinstance(curveInfo, | ||
+ | print(' | ||
+ | return | ||
+ | if all(x in curveInfo.keys() for x in [' | ||
+ | return cmds.curve(n=n, | ||
+ | elif all(x in curveInfo.keys() for x in [' | ||
+ | return cmds.curve(n=n, | ||
+ | else: | ||
+ | return cmds.curve(n=n, | ||
+ | </ | ||
+ | |||
+ | |||
+ | * curve_mel2py cmd convertor <code python> | ||
+ | #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, | ||
+ | rule = " | ||
+ | cnum = float(rule.format(fnum)) | ||
+ | if cnum.is_integer(): | ||
+ | cnum = int(cnum) | ||
+ | return cnum | ||
+ | |||
+ | def curve_mel2py(txt): | ||
+ | rest, k = txt.split(' | ||
+ | ks = [int(x.strip()) for x in k.split(' | ||
+ | rest, p = rest.split(' | ||
+ | ps = [ x.strip().split() for x in p.split(' | ||
+ | ps = [ [numClean(float(y)) for y in x] for x in ps] | ||
+ | d = int(rest.split(' | ||
+ | | ||
+ | | ||
+ | </ | ||
+ | |||
+ | * create NurbsCurve passing defined point list <code python> | ||
+ | lineCurve = cmds.curve(p=pointList, | ||
+ | nurbsCurve = cmds.fitBspline(lineCurve, | ||
+ | cmds.delete(lineCurve) | ||
+ | </ | ||
+ | |||
+ | * **Research on API vs Cmds in Get and Set Vertex Position based on optional selection** <code python> | ||
+ | # ref: http:// | ||
+ | def getVtxSelection_OpenMaya(): | ||
+ | startTime = cmds.timerX() # 0.11s > no comp iter: 0.039 | ||
+ | # normal selection | ||
+ | normalSelectionList = om.MSelectionList() | ||
+ | om.MGlobal.getActiveSelectionList(normalSelectionList) | ||
+ | selectionPath = om.MDagPath() | ||
+ | selectionObj = om.MObject() | ||
+ | indexList = om.MIntArray() | ||
+ | iter = om.MItSelectionList( normalSelectionList, | ||
+ | # | ||
+ | node_list = [] | ||
+ | id_list = [] | ||
+ | while not iter.isDone(): | ||
+ | iter.getDagPath( selectionPath, | ||
+ | selectionPath.pop() | ||
+ | node = selectionPath.fullPathName() | ||
+ | compFn = om.MFnSingleIndexedComponent(selectionObj) # flatten the component selection | ||
+ | compFn.getElements(indexList) | ||
+ | id_list.extend(list(indexList)) | ||
+ | node_list.append(node) | ||
+ | ''' | ||
+ | for i in range(compFn.elementCount()): | ||
+ | normalSelectionElements.append(' | ||
+ | id_list.append(compFn.element(i)) | ||
+ | ''' | ||
+ | iter.next() | ||
+ | totalTime = cmds.timerX(startTime=startTime) | ||
+ | print(" | ||
+ | return list(set(node_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+" | ||
+ | index_list = range( cmds.polyEvaluate(shapeNode, | ||
+ | else: # 0.244 | ||
+ | shapeNode, index_list = getVtxSelection_OpenMaya() # 0.01 | ||
+ | shapeNode = shapeNode[0] | ||
+ | for i in index_list: #i=0 | ||
+ | pos = cmds.xform( ' | ||
+ | pos_list.append( pos ) | ||
+ | totalTime = cmds.timerX(startTime=startTime) | ||
+ | print(" | ||
+ | return index_list, pos_list | ||
+ | def setVtxPos_cmd( shapeNode, pos_data, sl=0 ): | ||
+ | startTime = cmds.timerX() | ||
+ | cmds.undoInfo(openChunk=1) | ||
+ | index_list, pos_list = pos_data | ||
+ | if sl== 0: # 4.38 | ||
+ | for i in range(len(index_list)): | ||
+ | cmds.xform(' | ||
+ | else: # 5.29 | ||
+ | sel_index_list = getVtxSelection_OpenMaya()[1] | ||
+ | if not set(sel_index_list).issubset(set(index_list)): | ||
+ | print(' | ||
+ | return | ||
+ | for id in sel_index_list: | ||
+ | i = index_list.index(id) | ||
+ | cmds.xform(' | ||
+ | cmds.undoInfo(closeChunk=1) | ||
+ | totalTime = cmds.timerX(startTime=startTime) | ||
+ | print(" | ||
+ | | ||
+ | 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() | ||
+ | selectionList.add(shapeNode) | ||
+ | dagPath = om.MDagPath() | ||
+ | selectionList.getDagPath(0, | ||
+ | | ||
+ | mesh_fn = om.MFnMesh(dagPath) | ||
+ | meshPointArray = om.MPointArray() | ||
+ | mesh_fn.getPoints(meshPointArray, | ||
+ | index_list = range(meshPointArray.length()) | ||
+ | for i in index_list: | ||
+ | pos_list.append( [meshPointArray[i][0], | ||
+ | else: # 0.04 | ||
+ | selectionList = om.MSelectionList() | ||
+ | om.MGlobal.getActiveSelectionList(selectionList) | ||
+ | selectionPath = om.MDagPath() | ||
+ | selectionCompObj = om.MObject() | ||
+ | iter_sel = om.MItSelectionList( selectionList, | ||
+ | while not iter_sel.isDone(): | ||
+ | iter_sel.getDagPath( selectionPath, | ||
+ | iter_vtx = om.MItMeshVertex(selectionPath, | ||
+ | while not iter_vtx.isDone(): | ||
+ | pnt = iter_vtx.position(om.MSpace.kWorld) | ||
+ | index_list.append( iter_vtx.index() ) | ||
+ | pos_list.append([pnt.x, | ||
+ | iter_vtx.next() | ||
+ | iter_sel.next() | ||
+ | totalTime = cmds.timerX(startTime=startTime) | ||
+ | print(" | ||
+ | return index_list, pos_list | ||
+ | |||
+ | def setVtxPos_OpenMaya(shapeNode, | ||
+ | startTime = cmds.timerX() | ||
+ | cmds.undoInfo(openChunk=1) | ||
+ | index_list, pos_list = pos_data | ||
+ | selectionList = om.MSelectionList() | ||
+ | selectionList.add(shapeNode) | ||
+ | dagPath = om.MDagPath() | ||
+ | selectionList.getDagPath(0, | ||
+ | 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], | ||
+ | # get all and get all - 0.069 | ||
+ | meshPointArray = om.MPointArray() | ||
+ | mesh_fn.getPoints(meshPointArray, | ||
+ | for i in range(len(index_list)): | ||
+ | meshPointArray.set(index_list[i], | ||
+ | mesh_fn.setPoints(meshPointArray, | ||
+ | else: # 0.53 | ||
+ | sel_index_list = getVtxSelection_OpenMaya()[1] | ||
+ | if not set(sel_index_list).issubset(set(index_list)): | ||
+ | print(' | ||
+ | return | ||
+ | meshPointArray = om.MPointArray() | ||
+ | mesh_fn.getPoints(meshPointArray, | ||
+ | for id in sel_index_list: | ||
+ | i = index_list.index(id) | ||
+ | meshPointArray.set(id, | ||
+ | mesh_fn.setPoints(meshPointArray, | ||
+ | cmds.undoInfo(closeChunk=1) | ||
+ | totalTime = cmds.timerX(startTime=startTime) | ||
+ | print(" | ||
+ | </ | ||
+ | ====== UV Editing ====== | ||
+ | |||
+ | |||
+ | ====== Material ====== | ||
+ | |||
+ | * assign material to selection or objects <code python> | ||
+ | * material creation <code python> | ||
+ | * change property <code python> | ||
+ | |||
+ | ====== Rigging ====== | ||
+ | |||
+ | ===== delete unknown nodes ===== | ||
+ | |||
+ | * delete unknown nodes, but check " | ||
+ | nodeList = cmds.ls(type=[' | ||
+ | # | ||
+ | def deleteIfNotReferenced( nodeList ): | ||
+ | if not isinstance(nodeList, | ||
+ | nodeList = [nodeList] | ||
+ | for nodeToDelete in nodeList: | ||
+ | if cmds.objExists(nodeToDelete) and not cmds.reference(nodeToDelete, | ||
+ | isLocked = cmds.lockNode(nodeToDelete, | ||
+ | if not isLocked: | ||
+ | cmds.delete( nodeToDelete ) | ||
+ | </ | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | ===== common node and attributes ===== | ||
+ | |||
+ | | obj_transform | .worldMatrix | | ||
+ | |||
+ | ===== Prefix remover ===== | ||
+ | |||
+ | * remove prefix in shape and transform and avoid same node name level collision | ||
+ | * python <code python> | ||
+ | mesh_list = cmds.ls(' | ||
+ | for each_mesh in mesh_list: | ||
+ | info = each_mesh.rsplit(' | ||
+ | if len(info )==2: | ||
+ | cmds.rename(each_mesh, | ||
+ | else: | ||
+ | print(each_mesh) | ||
+ | | ||
+ | obj_list = cmds.ls(' | ||
+ | for each_obj in obj_list: | ||
+ | info = each_obj.rsplit(' | ||
+ | if len(info)==2: | ||
+ | cmds.rename(each_obj, | ||
+ | else: | ||
+ | print(each_obj) | ||
+ | </ | ||
+ | |||
+ | ===== alias attribute ===== | ||
+ | |||
+ | * for blendshape and constraints, | ||
+ | alias_target_weight_list = cmds.aliasAttr( final_bs, q=1 ) # [' | ||
+ | target_list = alias_target_weight_list[:: | ||
+ | weight_attr_list = alias_target_weight_list[1:: | ||
+ | </ | ||
+ | |||
+ | |||
+ | ===== weight ===== | ||
+ | |||
+ | * [[graphic: | ||
+ | |||
+ | * apply single vtx weight to whole geo, good for unify same weight on attachment type geos on another surface <code python> | ||
+ | attach_vtx_id = 16 # use same weight as on vtx[16] | ||
+ | cur_skin = cmds.skinCluster(cur_bones, | ||
+ | cmds.setAttr( cur_skin + " | ||
+ | vtx_weight = cmds.skinPercent(cur_skin, | ||
+ | cmds.skinPercent(cur_skin, | ||
+ | </ | ||
+ | |||
+ | * get only bone affecting the selected vtx <code python> | ||
+ | cur_vtx_list = cmds.ls(sl=1, | ||
+ | if len(cur_vtx_list)> | ||
+ | if ' | ||
+ | cur_skin = weightAPI.findRelatedSkinCluster( cur_vtx_list[0].split(' | ||
+ | affect_boneList = cmds.skinPercent(cur_skin, | ||
+ | </ | ||
+ | * select only vtx affected the defined bone <code python> | ||
+ | cmds.skinCluster(cur_skin, | ||
+ | </ | ||
+ | ===== Blendshape ===== | ||
+ | |||
+ | {{graphic: | ||
+ | |||
+ | * 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, | ||
+ | * 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]**: | ||
+ | |||
+ | |||
+ | * vtx general info code< | ||
+ | cmds.getAttr(' | ||
+ | |||
+ | cmds.polyMoveVertex(localTranslate=(0, | ||
+ | </ | ||
+ | * blendshape per target per vtx weight info cmds way <code python> | ||
+ | cmds.getAttr(cur_bs+' | ||
+ | |||
+ | # target 1 : weight per vtx | ||
+ | cmds.getAttr(' | ||
+ | cmds.getAttr(' | ||
+ | # target 2 : weight per vtx | ||
+ | cmds.getAttr(' | ||
+ | |||
+ | # set target 2: weight per vtx | ||
+ | cmds.setAttr(' | ||
+ | |||
+ | # get current loaded paint weight data | ||
+ | cmds.getAttr(cur_bs+' | ||
+ | |||
+ | # get current base (the final mixing global per vtx weight) | ||
+ | cmds.getAttr(cur_bs+' | ||
+ | </ | ||
+ | |||
+ | {{: | ||
+ | * API: **MFnBlendShapeDeformer** <code python> | ||
+ | import maya.OpenMaya as om | ||
+ | import maya.OpenMayaAnim as oma | ||
+ | selectionList = om.MSelectionList() | ||
+ | selectionList.add(cur_bs) | ||
+ | bs_obj = om.MObject() | ||
+ | selectionList.getDependNode(0, | ||
+ | bs_obj.hasFn(om.MFn.kBlendShape) | ||
+ | bs_fn = oma.MFnBlendShapeDeformer(bs_obj) # blendshape object fn | ||
+ | # current result object | ||
+ | obj_list = om.MObjectArray() | ||
+ | bs_fn.getBaseObjects(obj_list) | ||
+ | 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** <code python> | ||
+ | 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 | ||
+ | bs_fn.weightIndexList(indexList) | ||
+ | |||
+ | w_info_list=[] # weight attr info | ||
+ | w_plug = bs_fn.findPlug(' | ||
+ | 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, | ||
+ | print(w_info_list) | ||
+ | </ | ||
+ | |||
+ | * API: **baseWeights data** (overall deform weight map): <code python> | ||
+ | inputTargetArrayPlug = bs_fn.findPlug(' | ||
+ | inputTargetPlug = inputTargetArrayPlug.elementByLogicalIndex(0) | ||
+ | # base weight list | ||
+ | baseWeightsArrayPlug = inputTargetPlug.child(1) # .inputTarget[0].baseWeights | ||
+ | vtxCnt = cmds.polyEvaluate(base_geo, | ||
+ | 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()): | ||
+ | base_weight_list.append( baseWeightsArrayPlug.elementByLogicalIndex(j).asFloat() ) | ||
+ | ''' | ||
+ | # even this will not get the full index list | ||
+ | baseWeightsArrayPlug.evaluateNumElements() | ||
+ | print(baseWeightsArrayPlug.numElements()) | ||
+ | </ | ||
+ | |||
+ | * API: **targetWeights data for each target** <code python> | ||
+ | inputTargetGroupArrayPlug = inputTargetPlug.child(0) | ||
+ | |||
+ | target_weight_data = [] | ||
+ | for index in indexList: # i=0 | ||
+ | inputTargetGroupPlug = inputTargetGroupArrayPlug.elementByLogicalIndex(index) # inputTargetGroupPlug.name() | ||
+ | targetWeightsArrayPlug = inputTargetGroupPlug.child(1) # .targetWeights | ||
+ | target_weight_list = [1]*vtxCnt | ||
+ | for j in range(targetWeightsArrayPlug.numElements()): | ||
+ | cur_plug = targetWeightsArrayPlug.elementByPhysicalIndex(j) | ||
+ | logicalIndex = cur_plug.logicalIndex() | ||
+ | target_weight_list[logicalIndex] = cur_plug.asFloat() | ||
+ | target_weight_data.append(target_weight_list) | ||
+ | </ | ||
+ | |||
+ | * API: **Geometry result deformed vertex list position info** <code python> | ||
+ | pos_list = [] | ||
+ | geo_iter = om.MItGeometry(obj_list[0]) | ||
+ | while not geo_iter.isDone(): | ||
+ | p = geo_iter.position() | ||
+ | pos_list.append((p.x, | ||
+ | geo_iter.next() | ||
+ | </ | ||
+ | |||
+ | * API: **Data of Target shape vertex list position info** <code python> | ||
+ | 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, | ||
+ | geo_iter.next() | ||
+ | return pos_list | ||
+ | | ||
+ | for index in indexList: # i = 0 | ||
+ | target_object_data = [] | ||
+ | | ||
+ | target_list = om.MObjectArray() | ||
+ | bs_fn.getTargets(bs_base_obj, | ||
+ | # 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]) ) | ||
+ | else: | ||
+ | # 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, | ||
+ | bs_fn.setWeight(n, | ||
+ | # 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, | ||
+ | elif target_cnt == 1: | ||
+ | target_object_data.append( geoObject_to_posList_OpenMaya(target_list[0]) ) | ||
+ | # - | ||
+ | target_data.append(target_object_data) | ||
+ | | ||
+ | # locator creation | ||
+ | def pos_to_loc(pos_list, | ||
+ | for i,pos in enumerate(pos_list): | ||
+ | cur_loc = cmds.spaceLocator(n=' | ||
+ | cmds.xform(cur_loc, | ||
+ | | ||
+ | for i,targets in enumerate(target_data): | ||
+ | pos_to_loc(targets[0], | ||
+ | </ | ||
+ | ====== Animation ====== | ||
+ | |||
+ | **Import and Export animation with atom plugin** | ||
+ | * import <code python> | ||
+ | # select controls to load animation, then | ||
+ | cmds.file(" | ||
+ | </ | ||
+ | **Import and Export animation with Maya built-in file import export** | ||
+ | * method 1: select the animation curve nodes, then just export those nodes for later import and manual reconnect< | ||
+ | cmds.file(" | ||
+ | </ | ||
+ | * the result file will contains, animation nodes + renderGlobals stufs | ||
+ | * method 2: select animated objects, then just export animation info with auto reconnect on import< | ||
+ | cmds.file(" | ||
+ | </ | ||
+ | * the result file will contains, animation nodes + re-connection commands, you can replace namespace on import back with -swapNamespace, | ||
+ | |||
+ | |||
+ | **Get all Animation Curve** | ||
+ | * note: it may include time value driven drive key curve, so need check< | ||
+ | for animCurve_type in [' | ||
+ | curve_list = cmds.ls( type = animCurve_type ) | ||
+ | |||
+ | for each_curve in curve_list: | ||
+ | cur_connection_out = each_curve + " | ||
+ | destinations = cmds.connectionInfo(cur_connection_out, | ||
+ | if len(destinations) == 0: | ||
+ | # optional process for unconnected animation node | ||
+ | cmds.delete(each_curve) | ||
+ | else: | ||
+ | cur_node_attr = destinations[0] | ||
+ | cur_node, cur_attr = cur_node_attr.split(' | ||
+ | # optional get transform node | ||
+ | cur_transform = '' | ||
+ | if cmds.nodeType(cur_node) == ' | ||
+ | cur_transform = cur_node | ||
+ | else: | ||
+ | try_parent = cmds.listRelatives(cur_node, | ||
+ | if try_parent: | ||
+ | cur_transform = cmds.listRelatives(cur_node, | ||
+ | # optional unlock attribute | ||
+ | old_lock = cmds.getAttr(cur_node_attr, | ||
+ | cmds.setAttr(cur_node_attr, | ||
+ | # optional process here | ||
+ | cmds.delete(cur_node_attr, | ||
+ | # lock back | ||
+ | cmds.setAttr(cur_node_attr, | ||
+ | </ | ||
+ | |||
+ | **Clean Static Animation on object** | ||
+ | * note: that operation can't take locked channel, so unlock process may required | ||
+ | * general clean static animation <code python> | ||
+ | cmds.delete(cur_node_attr, | ||
+ | </ | ||
+ | |||
+ | **Bake Animation** | ||
+ | * note: make sure all channel unlocked | ||
+ | * bake animation curve< | ||
+ | * bake object <code python> | ||
+ | |||
+ | ====== Animation Cache====== | ||
+ | |||
+ | * check abc node inside maya, and its cache path <code python> | ||
+ | # get all abc node | ||
+ | all_abc_nodes = cmds.ls(type=' | ||
+ | # check current file path | ||
+ | for each in all_abc_nodes: | ||
+ | print(' | ||
+ | print(' | ||
+ | print(cmds.getAttr(each+' | ||
+ | print(cmds.getAttr(each+' | ||
+ | </ | ||
+ | * to update abc cache path, (default maya only change the " | ||
+ | all_abc_nodes = cmds.ls(type=' | ||
+ | for each in all_abc_nodes: | ||
+ | print(' | ||
+ | print(' | ||
+ | cur_abc_path = cmds.getAttr(each+' | ||
+ | cmds.setAttr(' | ||
+ | </ | ||
+ | ====== Camera and Viewport calculation ====== | ||
+ | |||
+ | * get 2D screen space from 3D position <code python> | ||
+ | import maya.OpenMayaUI as omui | ||
+ | import maya.OpenMaya as om | ||
+ | import maya.cmds as cmds | ||
+ | obj = cmds.ls(sl=1, | ||
+ | pos = om.MPoint(obj) | ||
+ | </ | ||
+ | |||
+ | |||
+ | * get active viewport camera name <code python> | ||
+ | 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 <code python> | ||
+ | omui.M3dView().active3dView().portWidth() | ||
+ | omui.M3dView().active3dView().portHeight() | ||
+ | </ | ||
+ | |||
+ | * Make Move tool aim to camera, or parallel to camera; Good for matchmove <code python> | ||
+ | ''' | ||
+ | ========================================== | ||
+ | 2019.05.21 | ||
+ | by Shining Ying | ||
+ | shiningdesign[with]live.com | ||
+ | |||
+ | 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, | ||
+ | # ref: http:// | ||
+ | # take obj's local space vec to workspace, default object x axis | ||
+ | selList = om.MSelectionList() | ||
+ | selList.add(obj) | ||
+ | nodeDagPath = om.MDagPath() | ||
+ | selList.getDagPath(0, | ||
+ | matrix = nodeDagPath.inclusiveMatrix() | ||
+ | vec = (vec * matrix).normal() | ||
+ | return vec.x, vec.y, vec.z | ||
+ | | ||
+ | def alignObjectToVector(obj, | ||
+ | # grab object transform function lib | ||
+ | selectionList = om.MSelectionList() | ||
+ | selectionList.add( obj ) | ||
+ | dagPath = om.MDagPath() | ||
+ | selectionList.getDagPath(0, | ||
+ | fnTransform = om.MFnTransform(dagPath) | ||
+ | # axis dict | ||
+ | axis_dict = {' | ||
+ | obj_axis_dir = getLocalVecToWorldSpaceAPI(obj, | ||
+ | obj_axis_vec = om.MVector(*obj_axis_dir) | ||
+ | | ||
+ | aim_vec = om.MVector(*aim_dir) | ||
+ | rot_quaternion = obj_axis_vec.rotateTo(aim_vec) | ||
+ | fnTransform.rotateBy(rot_quaternion) | ||
+ | # 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, | ||
+ | else: | ||
+ | side_vec = up_guide_vec ^ aim_vec # side dir | ||
+ | alignObjectToVector(obj, | ||
+ | |||
+ | cur_selection = cmds.ls(sl=1) | ||
+ | |||
+ | #fixCam= ' | ||
+ | fixCam = '' | ||
+ | cmds.setToolTo(' | ||
+ | cur_cam = cmds.modelEditor(cmds.playblast(activeEditor=1), | ||
+ | if fixCam !='': | ||
+ | cur_cam = fixCam | ||
+ | print(' | ||
+ | camPos = cmds.xform(cur_cam, | ||
+ | objPos = cmds.manipMoveContext(' | ||
+ | objPos2 = cmds.xform(cur_selection[0], | ||
+ | if (objPos[0] - objPos2[0])**2 > 0.000001: | ||
+ | objPos_maya = objPos | ||
+ | objPos = objPos2 | ||
+ | print(' | ||
+ | |||
+ | aim_dir = [a-b for a,b in zip(camPos, | ||
+ | up_ref_dir = getLocalVecToWorldSpaceAPI(cur_cam, | ||
+ | |||
+ | # loc | ||
+ | tmp_loc = cmds.spaceLocator(n=' | ||
+ | cmds.xform(tmp_loc, | ||
+ | # align | ||
+ | alignObjectToVector(tmp_loc, | ||
+ | rot_val = cmds.xform(tmp_loc, | ||
+ | cmds.delete(tmp_loc) | ||
+ | # back to old selection | ||
+ | cmds.select(cur_selection) | ||
+ | # set tool | ||
+ | cmds.setToolTo(' | ||
+ | cmds.manipMoveContext( ' | ||
+ | |||
+ | |||
+ | ''' | ||
+ | ========================================== | ||
+ | 2019.05.21 | ||
+ | by Shining Ying | ||
+ | shiningdesign[with]live.com | ||
+ | |||
+ | make move tool paralell to camera (with object/vtx selection) | ||
+ | ========================================== | ||
+ | ''' | ||
+ | |||
+ | cur_selection = cmds.ls(sl=1) | ||
+ | |||
+ | #fixCam= ' | ||
+ | fixCam = '' | ||
+ | cmds.setToolTo(' | ||
+ | cur_cam = cmds.modelEditor(cmds.playblast(activeEditor=1), | ||
+ | if fixCam !='': | ||
+ | cur_cam = fixCam | ||
+ | print(' | ||
+ | # loc | ||
+ | tmp_loc = cmds.spaceLocator(n=' | ||
+ | # align | ||
+ | cmds.parentConstraint(cur_cam, | ||
+ | rot_val = cmds.xform(tmp_loc, | ||
+ | cmds.delete(tmp_loc) | ||
+ | # back to old selection | ||
+ | cmds.select(cur_selection) | ||
+ | |||
+ | # set tool | ||
+ | cmds.setToolTo(' | ||
+ | cmds.manipMoveContext( ' | ||
+ | </ | ||
+ | ====== Render Related ====== | ||
+ | |||
+ | * the ' | ||
+ | cmds.select(' | ||
+ | |||
+ | # get current scene name | ||
+ | cur_scene = cmds.file(q=1, | ||
+ | if cur_scene == '': | ||
+ | cur_scene = ' | ||
+ | print(' | ||
+ | else: | ||
+ | cur_scene = cur_scene.rsplit(' | ||
+ | # get render global output name, update to custom name format | ||
+ | cur_img_prefix = cmds.getAttr(' | ||
+ | |||
+ | if '< | ||
+ | result_txt = cur_img_prefix.replace('< | ||
+ | cmds.setAttr(' | ||
+ | </ | ||
+ | * it maps all the setting in Render Setting - Common Tab, <code python> | ||
+ | image_ext = ' | ||
+ | cmds.setAttr(" | ||
+ | |||
+ | scene_path = os.path.dirname(cmds.file(q=True, | ||
+ | dir_path_info = [scene_path, | ||
+ | render_root_path = os.path.join(*dir_path_info ).replace(' | ||
+ | |||
+ | cmds.setAttr(" | ||
+ | # set as name.#### | ||
+ | cmds.setAttr(" | ||
+ | cmds.setAttr(" | ||
+ | cmds.setAttr(" | ||
+ | cmds.setAttr(" | ||
+ | cmds.setAttr(" | ||
+ | |||
+ | # set width | ||
+ | cmds.setAttr(" | ||
+ | cmds.setAttr(" | ||
+ | </ | ||
+ | * it also contains all the Deadline render job setting | ||
+ | |||
+ | * get absolution render output path (including dynamic changing output path) <code python> | ||
+ | cmds.renderSettings(firstImageName=1, | ||
+ | </ | ||
+ | * get workspace current information <code python> | ||
+ | cmds.workspace(q=1, | ||
+ | cmds.workspace(q=1, | ||
+ | |||
+ | # scene path and scene name | ||
+ | cmds.file(q=True, | ||
+ | cmds.file(q=True, | ||
+ | |||
+ | # segment subpath | ||
+ | cmds.workspace(fr=1, | ||
+ | cmds.workspace(fr=[' | ||
+ | cmds.workspace(fr=[' | ||
+ | cmds.workspace(fileRuleEntry=' | ||
+ | </ | ||
+ | * mel to use python write Pre-Render-Mel, | ||
+ | import datetime; | ||
+ | time_text = datetime.datetime.now().strftime(' | ||
+ | import maya.cmds as cmds; | ||
+ | out_path = cmds.renderSettings(firstImageName=1, | ||
+ | scene_path = cmds.file(q=True, | ||
+ | parent_dir = os.path.dirname(out_path); | ||
+ | info_file=parent_dir+' | ||
+ | os.path.exists(os.path.dirname(info_file)) or os.mkdir(os.path.dirname(info_file)); | ||
+ | f = open(info_file,' | ||
+ | f.writelines([scene_path,' | ||
+ | f.close(); | ||
+ | print(' | ||
+ | </ | ||
+ | * mel version <code javascript> | ||
+ | python(" | ||
+ | </ | ||
+ | * 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%%''' | ||
+ | # 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, | ||
+ | pre_mel = r''' | ||
+ | # set as per render layer instead pre scene, since each folder has different name | ||
+ | cmds.setAttr(" | ||
+ | </ | ||
+ | * preMel: before maya kick render (postMel) | ||
+ | * preRenderLayerMel: | ||
+ | * preRenderMel: | ||
+ | |||
+ | * defaultArnoldRenderOptions: | ||
+ | * the arnold render setting object, contains all render setting for Arnold render | ||
+ | |||
+ | ====== Pipeline ====== | ||
+ | |||
+ | * cross platform maya scene directory mapping and management | ||
+ | * dirmap command: http:// | ||
+ | * ref: https:// | ||
+ | |||
+ | * get mel system level operation and maya launch directory <code python> | ||
+ | import maya.mel as mel | ||
+ | print(mel.eval(' | ||
+ | </ | ||
+ | |||
+ | * Publish workflow | ||
+ | * http:// | ||
+ | * Project Dir <code python> | ||
+ | * File New <code python> | ||
+ | cmds.file(f=1, | ||
+ | </ | ||
+ | * File Open <code python> | ||
+ | cmds.file(filePath, | ||
+ | cmds.file(renameToSave=1) # prevent overwrite save after open | ||
+ | </ | ||
+ | * Importing <code python> | ||
+ | * Reference <code python> | ||
+ | allRefNode = cmds.ls( references=1) | ||
+ | cur_node = allRefNode[0] | ||
+ | # get reference used namespace - : | ||
+ | cur_ns= cmds.referenceQuery(cur_node, | ||
+ | # get reference used file - C:/ | ||
+ | cur_path = cmds.referenceQuery(cur_node, | ||
+ | # get reference used file name - myfile.ma | ||
+ | cur_name = cmds.referenceQuery(cur_node, | ||
+ | </ | ||
+ | * delete unused node <code python> | ||
+ | * checking <code python> | ||
+ | cmds.file(q=1, | ||
+ | cmds.file(q=1, | ||
+ | cmds.file(q=1, | ||
+ | cmds.file(q=1, | ||
+ | </ | ||
+ | * check file change and save <code python> | ||
+ | cmds.file(q=1, | ||
+ | cmds.file(save=1) | ||
+ | </ | ||
+ | * saving as <code python> | ||
+ | # you need to rename first before you can save as | ||
+ | if ext.lower() == " | ||
+ | cmds.file(rename = filePath) | ||
+ | cmds.file(f=1, | ||
+ | save_done = 1 | ||
+ | elif ext.lower() == " | ||
+ | cmds.file(rename = filePath) | ||
+ | cmds.file(f=1, | ||
+ | save_done = 1 | ||
+ | </ | ||
+ | * export <code python> | ||
+ | if format_info == " | ||
+ | if self.uiList[' | ||
+ | cmds.file(filePath, | ||
+ | else: | ||
+ | cmds.file(filePath, | ||
+ | else: | ||
+ | if self.uiList[' | ||
+ | cmds.file(filePath, | ||
+ | else: | ||
+ | cmds.file(filePath, | ||
+ | </ | ||
+ | * mel shelf button< | ||
+ | global string $gShelfTopLevel; | ||
+ | string $currentShelf = `tabLayout -query -selectTab $gShelfTopLevel`; | ||
+ | setParent $currentShelf; | ||
+ | // - step 2: prepare path, name, icon, cmd | ||
+ | string $name=" | ||
+ | string $ver=" | ||
+ | string $icon=" | ||
+ | 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 " | ||
+ | |||
+ | shelfButton -c $cmd -ann $name -l $name -imageOverlayLabel $ver -overlayLabelColor 0 0 0 -overlayLabelBackColor 1 1 1 0.5 -image $icon -image1 $icon -sourceType " | ||
+ | |||
+ | string $curBtn = `shelfButton -parent $currentShelf -c $cmd -mi " | ||
+ | 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 " | ||
+ | </ | ||
+ | * shelf operation with python <code python> | ||
+ | import maya.cmds as cmds | ||
+ | import maya.mel as mel | ||
+ | shelf_holder = mel.eval(' | ||
+ | shelves = cmds.tabLayout(shelf_holder, | ||
+ | shelves = cmds.tabLayout(shelf_holder, | ||
+ | cmds.shelfTabLayout(e=1, | ||
+ | cmds.shelfTabLayout(shelf_holder, | ||
+ | cmds.shelfLayout(shelf_name, | ||
+ | |||
+ | shelf_name = mel.eval(' | ||
+ | mel.eval(' | ||
+ | |||
+ | 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. | ||
+ | - maya.env method (It loads before UI, before userSetup.py)< | ||
+ | USER_SCRIPT_PATH1=C:/ | ||
+ | USER_SCRIPT_PATH2=C:/ | ||
+ | MAYA_SCRIPT_PATH=$MAYA_SCRIPT_PATH; | ||
+ | MAYA_MODULE_PATH = D: | ||
+ | MAYA_PLUG_IN_PATH = D: | ||
+ | PYTHONPATH = D: | ||
+ | </ | ||
+ | - userSetup.py method (load after env, before UI) (~\Documents\maya\version\scripts) <code python> | ||
+ | print(" | ||
+ | </ | ||
+ | - userSetup.mel method (load after UI loaded) (~\Documents\maya\version\scripts) <code javascript> | ||
+ | print(" | ||
+ | $path1 = " | ||
+ | $path2 = " | ||
+ | string $newScriptPath = $path1 + ";" | ||
+ | putenv " | ||
+ | |||
+ | // or source other config script | ||
+ | $script1 = " | ||
+ | $script2 = " | ||
+ | source $script1; | ||
+ | source $script2; | ||
+ | </ | ||
+ | - cmd or bash shell method (run after userSetup.mel)< | ||
+ | // bash | ||
+ | alias proj1=" | ||
+ | // dos | ||
+ | set MAYA_MODULE_PATH=R:/ | ||
+ | " | ||
+ | </ | ||
+ | |||
+ | ===== Batch File Process Example ===== | ||
+ | |||
+ | * convert fbx file to mb file, and cleanup geo naming and material naming and texture node naming <code python> | ||
+ | import maya.cmds as cmds | ||
+ | import maya.mel as mel | ||
+ | import os | ||
+ | root_path = r' | ||
+ | fbx_list = [x for x in os.listdir(root_path ) if x.endswith(' | ||
+ | prefix = ' | ||
+ | for each_fbx in fbx_list: | ||
+ | #each_fbx = fbx_list[0] | ||
+ | name = each_fbx.split(' | ||
+ | id = name.rsplit(' | ||
+ | # new file | ||
+ | cmds.file(f=1, | ||
+ | cmds.file(os.path.join(root_path, | ||
+ | mel.eval(' | ||
+ | anim_list = cmds.ls(type=' | ||
+ | if len(anim_list)> | ||
+ | cmds.delete(anim_list) | ||
+ | # mesh | ||
+ | mesh_list = [ cmds.listRelatives(x, | ||
+ | new_geo = cmds.rename(mesh_list[0], | ||
+ | cmds.scale(0.1, | ||
+ | cmds.xform(new_geo, | ||
+ | cmds.makeIdentity(new_geo, | ||
+ | # material | ||
+ | mat_node = cmds.shadingNode(' | ||
+ | cmds.select(new_geo) | ||
+ | cmds.hyperShade(new_geo, | ||
+ | cmds.sets(new_geo, | ||
+ | # texture | ||
+ | file_node = [x for x in cmds.ls(type=' | ||
+ | new_file = cmds.rename(file_node, | ||
+ | cmds.connectAttr(new_file+' | ||
+ | cmds.connectAttr(new_file+' | ||
+ | # save | ||
+ | mel.eval(' | ||
+ | cmds.file(rename = os.path.join(root_path, | ||
+ | cmds.file(f=1, | ||
+ | # ask for verify as it runs | ||
+ | res = cmds.confirmDialog(title=" | ||
+ | if res == ' | ||
+ | break | ||
+ | </ | ||
+ | ====== Scripting ====== | ||
+ | |||
+ | * load and source mel script <code python> | ||
+ | mel_path = r" | ||
+ | mel.eval(' | ||
+ | </ | ||
+ | |||
+ | * use python to ask maya to execute a python command in its variable scope <code python> | ||
+ | # this enable external python tool to ask maya to run a command with variable in maya's scope | ||
+ | cmds.python(' | ||
+ | </ | ||
+ | ====== Utility ====== | ||
+ | |||
+ | * open socket for external communication <code python> | ||
+ | # open port in maya | ||
+ | import maya.cmds as cmds | ||
+ | port = 7001 | ||
+ | open_already = 0 | ||
+ | try: | ||
+ | cmds.commandPort(name=": | ||
+ | except: | ||
+ | open_already = 1 | ||
+ | if open_already == 1: | ||
+ | print(' | ||
+ | |||
+ | # send to Maya from external python | ||
+ | import socket | ||
+ | host = ' | ||
+ | port = 7001 | ||
+ | client = socket.socket(socket.AF_INET, | ||
+ | client.connect((host, | ||
+ | client.send(' | ||
+ | |||
+ | # close port in maya | ||
+ | cmds.commandPort(name=": | ||
+ | </ | ||
+ | ====== Plugin ====== | ||
+ | |||
+ | * check and load python plugins <code python> | ||
+ | if not cmds.pluginInfo(' | ||
+ | cmds.loadPlugin(' | ||
+ | </ | ||
+ | * group a portion of cmds into one undo <code python> | ||
+ | cmds.undoInfo(openChunk=True) | ||
+ | # after end of the operation and before error rise, put | ||
+ | cmds.undoInfo(closeChunk=True) | ||
+ | </ | ||
+ | |||
+ | * check plugin in use in a scene <code python> | ||
+ | plugin_list_inUse = sorted(cmds.pluginInfo(q=1, | ||
+ | </ | ||
+ | * remove unknown plugin from Maya 2015 onwards <code python> | ||
+ | oldplugins = cmds.unknownPlugin(q=True, | ||
+ | for plugin in oldplugins: | ||
+ | cmds.unknownPlugin(plugin, | ||
+ | </ | ||
+ | ====== Maya Qt GUI Tool Development ====== | ||
+ | |||
+ | |||
+ | * Qt python tool build guide tutorial links: | ||
+ | * this uses Qt designer app to create a UI file, and python code to hook it | ||
+ | * http:// | ||
+ | * this one use Qt codes to build a UI inside code of python | ||
+ | * http:// | ||
+ | * a list of guides | ||
+ | * http:// | ||
+ | |||
+ | * check Python version and bit <code python> | ||
+ | import struct; | ||
+ | |||
+ | * write PyQt4 GUI python code in Maya | ||
+ | - you need | ||
+ | * either compile | ||
+ | * or download PyQt4 binary package | ||
+ | - then | ||
+ | * either copy them into Maya's local python site package | ||
+ | * or add your " | ||
+ | # check Maya python version and bit | ||
+ | import sys; | ||
+ | import struct; | ||
+ | # 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/ | ||
+ | # http:// | ||
+ | sys.path.append(' | ||
+ | |||
+ | from PyQt4 import QtGui, | ||
+ | * or use PySide (PySide version 1.2 is included with Maya 2015, built with Python 2.7 and Maya Qt version 4.8.5.) <code python> | ||
+ | |||
+ | * explanation of above: PyQt binding vs PySide binding (the 2 common way to link Python and Qt GUI library together) ([[http:// | ||
+ | |||
+ | |||
+ | ====== My Python Tool ====== | ||
+ | |||
+ | * shelf load python code <code python> | ||
+ | import maya.cmds as cmds | ||
+ | from sys import platform | ||
+ | import sys | ||
+ | import os | ||
+ | |||
+ | def getOS(): | ||
+ | ''' | ||
+ | if platform.startswith(' | ||
+ | return ' | ||
+ | elif platform.startswith(' | ||
+ | return ' | ||
+ | return ' | ||
+ | |||
+ | def getZ(): | ||
+ | ''' | ||
+ | z_sys=os.getenv(' | ||
+ | z_sys = z_sys.strip() | ||
+ | z_sys = z_sys.replace(" | ||
+ | return z_sys | ||
+ | |||
+ | sys.path.append(z_sys+"/ | ||
+ | </ | ||
+ | |||
+ | ====== 3rd Party Tool ====== | ||
+ | * poseDeform | ||
+ | * http:// | ||
+ | * Sculpt Inbetween Editor | ||
+ | * ref: http:// | ||
+ | |||
+ | ====== Python and Maya Qt interface ====== | ||
+ | ===== PyQt case code ===== | ||
+ | |||
+ | * panel creation | ||
+ | * option: QWidget (it has panel name showing in taskbar), QDialog (not showing in taskbar) | ||
+ | |||
+ | * layout alignment, QtCore.Qt.AlignTop, | ||
+ | * layout.setAlignment(QtCore.Qt.AlignTop) | ||
+ | |||
+ | ====== Environment setup ====== | ||
+ | |||
+ | Environment of Python in Maya | ||
+ | * PYTHONPATH | ||
+ | * Maya.env: <code python> | ||
+ | import sys | ||
+ | sys.path.append("/ | ||
+ | # much better than eval(" | ||
+ | </ | ||
+ | * userStartup.py | ||
+ | * load custom system environment variable, Python shines so much comparing to Mel in this case <code python> | ||
+ | import maya.cmds as cmds | ||
+ | from sys import platform | ||
+ | import os | ||
+ | |||
+ | def getOS(): | ||
+ | if platform.startswith(' | ||
+ | return ' | ||
+ | elif platform.startswith(' | ||
+ | return ' | ||
+ | return ' | ||
+ | |||
+ | def getZ(): | ||
+ | ''' | ||
+ | # mel, about -os " | ||
+ | checkOS = getOS() | ||
+ | | ||
+ | # | ||
+ | #else $z_sys=system(" | ||
+ | | ||
+ | z_sys=os.getenv(' | ||
+ | | ||
+ | ## remove new line, mel | ||
+ | # | ||
+ | # | ||
+ | # | ||
+ | # | ||
+ | |||
+ | z_sys = z_sys.strip() | ||
+ | |||
+ | #$z_sys = substituteAllString($z_sys, | ||
+ | z_sys = z_sys.replace(" | ||
+ | # make sure system environment got defined | ||
+ | if(z_sys!="" | ||
+ | print z_sys | ||
+ | return z_sys | ||
+ | |||
+ | </ | ||
+ | ====== Python in mel basic ====== | ||
+ | * print string and variable <code python> | ||
+ | print(" | ||
+ | print(" | ||
+ | </ | ||
+ | * string and interger <code python> | ||
+ | string=" | ||
+ | </ | ||
+ | * start using mel as python <code python> | ||
+ | import maya.cmds | ||
+ | maya.cmds.select(" | ||
+ | maya.cmds.ls(sl=True) | ||
+ | </ | ||
+ | |||
+ | * python modules <code python> | ||
+ | import myModule | ||
+ | myModule.myFunc() | ||
+ | |||
+ | # reload | ||
+ | reload(myModule) | ||
+ | </ | ||
+ | |||
+ | * eval like in Python <code python> | ||
+ | import maya.mel | ||
+ | maya.mel.eval(" | ||
+ | </ | ||
+ | |||
+ | * python inside mel <code javascript> | ||
+ | string $list[]=python(" | ||
+ | size($list) | ||
+ | </ | ||
+ | |||
+ | ====== Common Python function====== | ||
+ | |||
+ | * to use maya cmds, you need to run this code to get follow working <code python> | ||
+ | ===== selection related ===== | ||
+ | |||
+ | * select all geo from root node< | ||
+ | # method 1, filterExpand | ||
+ | childs = cmds.listRelatives(root, | ||
+ | allgeo = cmds.filterExpand(childs, | ||
+ | |||
+ | # method 2, naming based | ||
+ | cmds.select(root) | ||
+ | cmds.select(hierarchy=1) | ||
+ | tAll=cmds.ls(sl=1, | ||
+ | allgeo=[] | ||
+ | for each in tAll: | ||
+ | if(each.endswith(' | ||
+ | allgeo.append(each) | ||
+ | # | ||
+ | </ | ||
+ | * selected attribute in channel box<code python> | ||
+ | selected_attr_list = cmds.channelBox(" | ||
+ | </ | ||
+ | |||
+ | ===== attribute related ===== | ||
+ | |||
+ | * note attribute, which is not added by default, need to be added manually <code python> | ||
+ | s=""" | ||
+ | 2nd line; | ||
+ | """ | ||
+ | if(cmds.objExists(' | ||
+ | cmds.addAttr(' | ||
+ | cmds.setAttr(' | ||
+ | </ | ||
+ | |||
+ | ===== sets related ===== | ||
+ | |||
+ | * empty group <code python> | ||
+ | * add to a organizing set<code python> | ||
+ | |||
+ | ===== rigging related ===== | ||
+ | * find source of parentConstraint <code python> | ||
+ | # get parentConstraint source | ||
+ | selected = cmds.ls(sl=1)[0] | ||
+ | links = cmds.listConnections(selected+' | ||
+ | if links is None: | ||
+ | links = [] | ||
+ | print(links) | ||
+ | </ | ||
+ | * findRelatedSkinCluster python version (written based on original mel version)< | ||
+ | def findRelatedSkinCluster( skinObject ): | ||
+ | # | ||
+ | skinShape = None | ||
+ | skinShapeWithPath = None | ||
+ | hiddenShape = None | ||
+ | hiddenShapeWithPath = None | ||
+ | |||
+ | cpTest = cmds.ls( skinObject, typ=" | ||
+ | if len( cpTest ): | ||
+ | skinShape = skinObject | ||
+ | |||
+ | else: | ||
+ | rels = cmds.listRelatives( skinObject ) | ||
+ | for r in rels : | ||
+ | cpTest = cmds.ls( " | ||
+ | if len( cpTest ) == 0: | ||
+ | continue | ||
+ | |||
+ | io = cmds.getAttr( " | ||
+ | if io: | ||
+ | continue | ||
+ | |||
+ | visible = cmds.getAttr( " | ||
+ | if not visible: | ||
+ | hiddenShape = r | ||
+ | hiddenShapeWithPath = " | ||
+ | continue | ||
+ | |||
+ | skinShape = r | ||
+ | skinShapeWithPath = " | ||
+ | break | ||
+ | |||
+ | if len( skinShape ) == 0: | ||
+ | if len( hiddenShape ) == 0: | ||
+ | return None | ||
+ | |||
+ | else: | ||
+ | skinShape = hiddenShape | ||
+ | skinShapeWithPath = hiddenShapeWithPath | ||
+ | |||
+ | clusters = cmds.ls( typ=" | ||
+ | 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 | ||
+ | </ | ||
+ | |||
+ | ===== animation related ===== | ||
+ | |||
+ | * setkeyframe <code python> | ||
+ | cmds.setKeyframe(testGeo+' | ||
+ | |||
+ | # just key the channel | ||
+ | cmds.setKeyframe(testGeo+' | ||
+ | </ | ||
+ | |||
+ | * delete key <code python> | ||
+ | # delete all keys | ||
+ | cmds.cutKey(' | ||
+ | |||
+ | # delete some of keys | ||
+ | # 1. get key frame list | ||
+ | key_list = cmds.keyframe(' | ||
+ | key_list = cmds.keyframe(' | ||
+ | |||
+ | # current key value | ||
+ | cur_v = cmds.getAttr(' | ||
+ | </ | ||
+ | |||
+ | * run through frame range <code python> | ||
+ | startFrame = cmds.playbackOptions(q=1, | ||
+ | endFrame = cmds.playbackOptions(q=1, | ||
+ | for f in range(int(startFrame), | ||
+ | cmds.currentTime(f) | ||
+ | </ | ||
+ | |||
+ | ===== dynamic related ===== | ||
+ | |||
+ | * emitter, particle, instance, field <code python> | ||
+ | # | ||
+ | # creation particle system | ||
+ | # | ||
+ | mName=' | ||
+ | mE_subfix=' | ||
+ | mP_subfix=' | ||
+ | mI_subfix=' | ||
+ | mInMesh_subfix=' | ||
+ | mFt_subfix=' | ||
+ | mC_loc_subfix=' | ||
+ | mC_curve_subfix=' | ||
+ | mC_null_subfix=' | ||
+ | |||
+ | # node subfix | ||
+ | mN_mdl_subfix=' | ||
+ | |||
+ | # | ||
+ | # emitter and particle | ||
+ | # | ||
+ | mE=cmds.createNode(' | ||
+ | mP=cmds.createNode(' | ||
+ | |||
+ | # connect dynamic | ||
+ | cmds.connectDynamic(mP, | ||
+ | cmds.connectAttr(' | ||
+ | |||
+ | # set initial value | ||
+ | e_conf={' | ||
+ | for attr, v in e_conf.items(): | ||
+ | cmds.setAttr(mE+' | ||
+ | |||
+ | # | ||
+ | # instance | ||
+ | # | ||
+ | mI=cmds.createNode(' | ||
+ | cmds.connectAttr(mP+' | ||
+ | |||
+ | # instance Mesh | ||
+ | mInMesh=cmds.polyCylinder(n=mName+mInMesh_subfix, | ||
+ | mMesh=mInMesh[0] | ||
+ | mShape=mInMesh[1] | ||
+ | cmds.setAttr(mMesh+' | ||
+ | cmds.connectAttr(mMesh+' | ||
+ | cmds.particleInstancer(mP, | ||
+ | |||
+ | # | ||
+ | # field | ||
+ | # | ||
+ | mFt=cmds.createNode(' | ||
+ | cmds.connectDynamic(mP, | ||
+ | |||
+ | ft_conf={' | ||
+ | for attr, v in ft_conf.items(): | ||
+ | cmds.setAttr(mFt+' | ||
+ | |||
+ | cmds.expression(s=" | ||
+ | </ | ||
+ | ===== math related===== | ||
+ | |||
+ | * sin and cost function <code python> | ||
+ | # | ||
+ | # aim vector to rotation value | ||
+ | # | ||
+ | # rotation | ||
+ | aimDir=cmds.particle(pName, | ||
+ | d=math.sqrt(aimDir[0]*aimDir[0]+aimDir[1]*aimDir[1]+aimDir[2]*aimDir[2]) | ||
+ | |||
+ | rx=math.degrees(math.asin(aimDir[1]/ | ||
+ | ry=0 | ||
+ | if(aimDir[2]> | ||
+ | ry=math.degrees(math.atan(aimDir[0]/ | ||
+ | elif(aimDir[2]==0): | ||
+ | if(aimDir[0]> | ||
+ | ry=90 | ||
+ | elif(aimDir[0]< | ||
+ | ry=-90 | ||
+ | elif(aimDir[0]==0): | ||
+ | ry=0 | ||
+ | else: # for easy reading only | ||
+ | ry=0 | ||
+ | elif(aimDir[2]< | ||
+ | if(aimDir[0]> | ||
+ | ry=math.degrees(math.atan(aimDir[0]/ | ||
+ | elif(aimDir[0]< | ||
+ | ry=math.degrees(math.atan(aimDir[0]/ | ||
+ | elif(aimDir[0]==0): | ||
+ | ry=180 | ||
+ | else: # for easy reading only | ||
+ | ry=0 | ||
+ | else: # for easy reading only | ||
+ | ry=0 | ||
+ | rz=0 | ||
+ | cmds.xform(tGeo, | ||
+ | </ | ||
+ | |||
+ | ====== RnD List======= | ||
+ | |||
+ | * face track: | ||
+ | * http:// | ||
+ | * Sunday pipe and shaders: | ||
+ | * http:// | ||
+ | * http:// | ||
+ | * http:// | ||
+ | * http:// | ||
+ | |||
+ | * python code | ||
+ | * get clipboard: http:// | ||
+ | |||
+ | * Blender shading: | ||
+ | * https:// | ||
+ | * https:// | ||
+ | |||
+ | ====== Angle Calculation ====== | ||
+ | |||
+ | ===== Proof of cos(A+B) formula ===== | ||
+ | |||
+ | * original reference: ([[http:// | ||
+ | |||
+ | ===== Proof of rotation matrix ===== | ||
+ | * 2D rotation of an angle from (x,y) to (x1,y1) \\ {{graphic: | ||
+ | * 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 | ||
+ | x1=R*cos(o1) | ||
+ | y1=R*sin(o1) | ||
+ | |||
+ | as o1=o+a, resulting o1 from turn o in a degree, we have | ||
+ | x1=R*cos(o1)=R*cos(o+a) | ||
+ | y1=R*sin(o1)=R*sin(o+a) | ||
+ | |||
+ | based on formula of cos(A+B)=cosA*cosB-sinA*sinB; | ||
+ | x1=R*cos(o1)=R*cos(o+a)=R*cos(o)*cos(a)-R*sin(o)*sin(a) | ||
+ | y1=R*sin(o1)=R*sin(o+a)=R*sin(o)*cos(a)+R*cos(o)*sin(a) | ||
+ | |||
+ | as in Polar coordinate, the R*cos(o) is actually x, R*sin(o) is actually y, so we have | ||
+ | x1=x*cos(a)-y*sin(a) | ||
+ | y1=y*cos(a)+x*sin(a)=x*sin(a)+y*cos(a) | ||
+ | |||
+ | in matrix form, it is same is the question above, Done. | ||
+ | </ | ||
+ | * thus in 3D space, we expand to have: \\ {{graphic: | ||
+ | |||
+ | ===== understand Matrix multiplication ===== | ||
+ | |||
+ | * ref: http:// | ||
+ | |||
+ | ====== Vector calculation ====== | ||
+ | |||
+ | * in Maya | ||
+ | * OpenMaya MVector method <code python> | ||
+ | from maya.OpenMaya import MVector | ||
+ | |||
+ | # function: make selected points into a sphere/ | ||
+ | |||
+ | points=cmds.ls(sl=1, | ||
+ | center=cmds.ls(sl=1)[0] | ||
+ | |||
+ | pointVectors = [] | ||
+ | for pnt in points: | ||
+ | v = MVector(*cmds.pointPosition(pnt)) | ||
+ | pointVectors.append(v) | ||
+ | |||
+ | 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, | ||
+ | </ | ||
+ | ====== Plane Intersection calcuation ====== | ||
+ | |||
+ | * 2 planes (represented by 2 vectors) intersection line | ||
+ | |||
+ | ====== Math tutorial links ====== | ||
+ | |||
+ | * 3D math for CG artists: | ||
+ | * http:// | ||
+ | |||
+ | * Shapes that tessellate | ||
+ | * http:// | ||
+ | |||
+ | * math fun: http:// | ||
+ | ====== Local space and World space and Screen space ====== | ||
+ | |||
+ | * ref: http:// | ||
+ | |||
+ | |||
+ | ====== Fluid Calculation model ====== | ||
+ | |||
+ | * ref: http:// | ||
+ | |||
+ | ====== 3D Camera model ====== | ||
+ | * camera model: http:// | ||
+ | * stereoscopic camera: http:// | ||
+ | |||
+ | ====== VR rendering in 3D====== | ||
+ | |||
+ | * VR rendering from 3D package | ||
+ | * http:// | ||
+ | * http:// | ||
+ | * https:// | ||
+ | * https:// | ||
+ | * https:// | ||
+ | * https:// | ||
+ | * http:// | ||
+ | * http:// | ||
+ | |||
+ | * article | ||
+ | * http:// | ||
+ | * http:// | ||
+ | |||
+ | * tool | ||
+ | * http:// | ||
+ | |||
+ | * case study: | ||
+ | * https:// |