Differences
This shows you the differences between two versions of the page.
Next revision | Previous revision | ||
devwiki:python [2021/08/22 19:33] – created ying | devwiki:python [2024/03/25 06:36] (current) – [Python and interaction with other API] 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: | + | ====== Modern Python Practice ====== |
- | * 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 | + | * nowaday, now one use python 2.x anymore, use python 3 standard, use this |
- | * [[cgwiki: | + | * use pathlib instead of os.path |
- | * [[cgwiki: | + | * use f" |
- | ===== Intro to Maya API concepts ===== | ||
- | * moved all over from my old cgwiki: | + | ====== Online Python run ====== |
- | ====Directed Acyclic Graph (DAG)==== | + | |
- | | + | * to install locally: < |
- | * *DAG* defines elements such as the position, orientation, | + | |
- | | + | |
- | * 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. | + | |
- | | + | |
- | * 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==== | + | ====== Install related ====== |
- | * DagPath is a set of nodes which uniquely identifies the location of a particular node or instance of a node in the graph. | + | ===== install Python3 on mac ===== |
- | * 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==== | + | |
- | | + | * to install Python3 from official python website, |
- | * 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 | + | |
- | | + | - run python3 |
- | | + | |
- | | + | |
- | | + | - go Apple developer downloads page, search command line tools for xcode, download |
- | * 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 | + | |
- | ====DAG hierarchy nodes vs Dependency Graph (DG) nodes==== | + | ===== Manage and handle multiple versions of python case ===== |
- | * 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==== | + | Situation: |
- | * MPxNode: base of DG node | + | * sometimes, you may have to keep both python 2.7, python 3.x |
- | * virtual compute(const MPlug& plug, MDataBlock& | + | * and sometimes you want to try python with AI, and AI need to install tons of AI related python modules |
- | * MDataHandle inputDH=data.inputValue(inAttrName, & | + | * sometimes you just to keep python module in the min form to test something |
- | * 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==== | + | Solution: |
- | * simple attribute: attribute has only 1 plug | + | * for those case, when install python, never add python to system path |
- | * it also can hold array data, like pointArray, intArray | + | * just download and make python folder a portable folder (you can install and copy the python whole folder to other machine as a portable app, better zip to copy, faster) |
- | * MFnNumericAttribute | + | * then your computer cmd never know where is python and won't random get your python from somewhere |
- | | + | * then, dynamically set python into python in cmd session |
- | * array attribute: attribute has array of plugs, each element plug has own value or connection | + | * session based method |
- | * MFnAttribute:: | + | * batch file method <code dos test.batch> |
- | * compound attribute: a collection of other attributes, called child attribute | + | @echo off |
- | * MFnCompoundAttribute | + | SetLocal EnableDelayedExpansion |
- | * example, color < | + | set CustPython=D: |
- | color1R | + | call !CustPython!python.exe my_script.py |
- | color1G = nAttr.create( “color1G”, | + | |
- | color1B = nAttr.create( “color1B”, | + | |
- | color1 = nAttr.create( “Sides”, | + | |
- | nAttr.setStorable(true); | + | |
- | nAttr.setUsedAsColor(true); | + | |
- | nAttr.setDefault(1.0f, 1.0f, 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==== | + | ====== Python 3 Changes and How to Make 2.7 Code Works ====== |
- | * static MObject color1R, | + | |
- | * static MObject aOutColorR, aOutColorG, aOutColorB, | + | |
- | * static MObject aNormalCameraX, | + | |
- | * static MObject aPointCameraX, | + | |
- | * void MStatus compute( const MPlug&, MDataBlock& | + | |
- | ====DG shader node type==== | + | |
- | | + | # in code editor, using Regular Expression to replace |
- | * textures/3d | + | find: (print)\s(.+) |
- | * textures/ | + | replace: $1\($2\) or \1(\2) depending on your code editor |
- | * shader/ | + | </code> |
- | * shader/volume | + | * other stuff <code python> |
- | * shader/ | + | # python 2 reload |
- | * light | + | reload(myModule) |
- | * utility/ | + | # python 3 reload |
- | * utility/ | + | from imp import reload |
- | * utility/ | + | reload(myModule) |
- | * imageplane | + | |
- | * postprocess/ | + | |
- | note: shader=material | + | # python 2 execfile |
- | + | execfile(" | |
- | ====DG shader node creation==== | + | # python 3 |
- | + | exec(open(" | |
- | * example, Blinn creation <code javascript> | + | |
- | // same as: | + | |
- | createNode blinn; | + | |
- | connectAttr blinn1.message defaultShaderList1.shaders;</ | + | |
- | * if creation option checked | + | |
- | 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 | + | * make compatible to python 3 <code python> |
- | shadingNode -asUtility place3dTexture; | + | if sys.version_info[: |
- | connectAttr place3dTexture2.wim[0] brownian1.pm; | + | import importlib |
+ | reload = importlib.reload # add reload | ||
+ | raw_input = input # add raw_input | ||
+ | xrange = range # range | ||
+ | long = int # int | ||
+ | unicode = str # str | ||
</ | </ | ||
- | ====DG shape node ==== | + | * integer division difference in py3 vs py2, a/b |
+ | * ref: https:// | ||
+ | * for safe, use < | ||
+ | 35// | ||
- | * MPxSurfaceShape: | + | 35/60 = 0.583 #py3 |
- | * MPxSurfaceShapeUI: | + | 35//60 = 0 # py3 |
- | * 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 ==== | + | ====== Python basics ====== |
- | * custom deform attributes | + | * online python code runner: https:// |
- | * MPxGeometryData | + | * Python download: http:// |
- | * localShapeInAttr | + | * a cool python shell: http:// |
- | * localShapeOutAttr | + | |
- | * worldShapeOutAttr | + | |
- | * MPxGeomtryIterator | + | |
- | * match | + | |
- | * createFullVertexGroup | + | |
- | * geometryData | + | |
- | ====DG shape - polygon API ==== | + | ** My Wiki Page related** |
+ | * how to publish Python script as standalone application: | ||
+ | * My App code share: [[devwiki: | ||
+ | * My Python GUI code template: [[devwiki: | ||
- | | + | ** to read: ** |
- | | + | * https:// |
- | | + | * Code Like a Pythonista: Idiomatic Python http:// |
- | | + | |
- | * 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 | + | |
- | * polygon custom modifier | + | |
- | * MPxCommand | + | |
- | * MStatus doModifyPoly(); | + | |
- | * MStatus redoModifyPoly(); | + | |
- | * MStatus undoModifyPoly(); | + | |
- | * polygon exporter | + | |
- | * class polyExporter, | + | |
+ | ===== Python vs . ===== | ||
+ | * Python vs Swift | ||
+ | * https:// | ||
- | ====== Maya in command line ====== | + | ===== Python Comment Tips ===== |
- | * launch Maya GUI interface with custom path setting and startup cmd or script at launch time, windows batch example | + | * single line comment |
- | set MAYA_MODULE_PATH=Z:/ | + | * multiple line comment <code python>''' |
- | set MAYA_SHELF_PATH=Z:/ | + | line 1 |
- | set MAYA_PRESET_PATH=Z:/ | + | line 2 |
+ | ''' | ||
+ | </code> | ||
+ | * block, section, partition <code python> | ||
- | set MAYA_PLUG_IN_PATH=Z:/ | + | ######################################## |
- | set MAYA_PLUG_IN_LIST=fbxmaya.mll; | + | # a major block of asset (40x#) |
- | + | ######################################## | |
- | set MAYA_SCRIPT_PATH=Z:/ | + | |
- | set MAYA_SCRIPT_PATH=Z:/ | + | # |
- | set MAYA_SCRIPT_PATH=Z:/ | + | # a function collection (30x-) |
+ | # | ||
- | set PYTHONPATH=Z:/ | + | # ---- section comment ---- |
- | set PYTHONPATH=Z:/ | + | |
- | " | + | # function section tip |
+ | def ________Loading_Functions________(): | ||
+ | # 8x_ | ||
+ | pass | ||
</ | </ | ||
+ | ===== Launch Python script ===== | ||
- | * process | + | * launch in windows command batch file next to it<code batch> |
+ | call python %~dp0\\MyPythonScript.py</ | ||
+ | * launch | ||
+ | set myScriptPath to path to me | ||
+ | set myScriptFolderPath to POSIX path of (container of myScriptPath as string) -- in "/" | ||
+ | set myScriptName to name of myScriptPath | ||
- | + | tell application " | |
- | **maya flags** | + | do script |
- | + | end tell | |
- | ^ -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 < | + | |
- | # active=1 if for active color</code> | + | |
- | + | ||
- | ====== 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) | + | |
</ | </ | ||
+ | ===== OS system operation ===== | ||
- | * select tool filter | + | * get user/home folder |
- | cmds.selectType(allObjects=0) # query by cmds.selectType(allObjects=1, | + | os.path.expanduser(' |
- | cmds.selectType(locator=1) | + | os.getenv(" |
+ | os.getenv(" | ||
</ | </ | ||
- | * display layer management | + | * make python code able to print unicode directly without .encode(' |
- | cmds.select(objList) | + | import codecs |
- | cmds.createDisplayLayer(name=" | + | sys.stdout = codecs.getwriter(' |
- | + | ||
- | cmds.setAttr(layer + ".displayType", | + | |
</ | </ | ||
- | * display defined curve smoother, (note only during current maya session, restart maya will back to default) | + | * get python program path<code python>import sys |
- | cmds.displaySmoothness([' | + | print(sys.executable) # the python cmd or app containing the python call path |
</ | </ | ||
- | + | | |
- | ====== Maya UI automation scripting ====== | + | os.path.dirname(ModuleName.__file__) |
- | + | ||
- | ^ 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 under pointer | + | |
- | + | ||
- | * 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<code python> | + | * get script path <code python>os.path.dirname(__file__)</ |
- | # most maya layout has a -childArray flag to get child | + | |
- | cmds.tabLayout(" | + | self.location = os.path.realpath(sys.modules[self.__class__.__module__].__file__) |
- | # get UI type | + | self.location |
- | cmds.objectType(" | + | |
- | # then continue call with the object' | + | |
- | + | ||
- | # to get parent | + | |
- | cmds.tabLayout(" | + | |
</ | </ | ||
- | * toggle maya window title bar visible and edit<code python> | + | * get class name <code python> |
- | cmds.window(' | + | * get basename of folder and file <code python> |
- | # hide title bar (true no UI by: press ctrl+space; then run this cmd) | + | * get file name without extension <code python> |
- | + | file_name | |
- | newT=" | + | import pathlib |
- | cmds.window('MayaWindow', | + | file_name_2 |
</ | </ | ||
- | * toggle outline window | + | * get expanded and un-symbolic path <code python> |
- | if cmds.window(' | + | os.path.abspath(__file__) |
- | cmds.deleteUI(' | + | os.path.realpath(__file__) |
- | else: | + | |
- | mel.eval(' | + | |
</ | </ | ||
+ | * path comparison <code python> | ||
+ | os.path.relpath(childPath, | ||
+ | os.path.commonprefix(list_of_paths) # get the common parent path of all input pathes | ||
+ | </ | ||
+ | * python code to detect platform, python version, bit <code python> | ||
+ | from sys import platform | ||
- | * ignore a set of scripting/ | + | def get_platform(): |
- | # ref: http:// | + | if platform.startswith('win'): |
- | import maya.mel as mel | + | |
- | import maya.cmds as cmds | + | elif platform.startswith('darwin'): |
- | # nextFrame | + | |
- | cmds.undoInfo(stateWithoutFlush=0) | + | return ' |
- | mel.eval('playButtonStepForward') | + | |
- | cmds.undoInfo(stateWithoutFlush=1) | + | |
- | # previousFrame (' | + | import sys |
- | cmds.undoInfo(stateWithoutFlush=0) | + | pyMode |
- | mel.eval('playButtonStepBackward' | + | print(" |
- | 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 | + | is_64bits |
- | cmds.undoInfo(stateWithoutFlush=0) | + | print(is_64bits) |
- | cmds.currentTime(cmds.findKeyframe(which=' | + | |
- | cmds.undoInfo(stateWithoutFlush=1) | + | |
</ | </ | ||
+ | * check file and path exists< | ||
+ | * scan file and folder content with pattern filtering <code python> | ||
+ | result_list = [x for x in os.listdir(scanPath) if os.path.isdir(os.path.join(scanPath, | ||
+ | # isdir for folder, isfile for file | ||
+ | cur_pattern = re.compile(' | ||
+ | result_list = [x for x in result_list if cur_pattern.match(x)] | ||
- | * group a set of scripting in undo history <code python> | + | # jpg name example |
- | cmds.undoInfo(openChunk=1) | + | jpg_list = [x.rsplit(' |
- | # your scripting block here | + | |
- | cmds.undoInfo(closeChunk=1) | + | |
</ | </ | ||
- | * get maya global variable to python | + | * rename file or folder < |
- | mel.eval(' | + | * move file (always use full path for safe, and always check source and target exist before operation)<code python> |
+ | # file move within same drive | ||
+ | os.rename("/ | ||
+ | os.replace("/ | ||
+ | # work for folder and file and cross disk drive | ||
+ | shutil.move("/ | ||
</ | </ | ||
- | + | ||
- | * toggle channel box nice and short names <code python> | + | * system cmds for each os<code python> |
- | main_cb = 'mainChannelBox' | + | os.system('move " |
- | next_state = not cmds.channelBox(main_cb, q=1, nn=1) | + | os.system(' |
- | cmds.channelBox(main_cb, e=1, ln=next_state, nn=next_state) | + | |
</ | </ | ||
+ | * cross-platform delivery from source | ||
+ | * win: frozen | ||
+ | * mac: frozen | ||
+ | * lin: packaged | ||
+ | * <code python> | ||
+ | def getSetup(): | ||
+ | if hasattr(sys, | ||
+ | return ' | ||
+ | elif is_packaged(): | ||
+ | return ' | ||
+ | return ' | ||
+ | </ | ||
+ | * change file permission <code python> | ||
+ | # ref: http:// | ||
+ | import os | ||
+ | import stat | ||
+ | os.chmod(" | ||
+ | </ | ||
+ | * windows register operation <code python> | ||
+ | PS_key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE," | ||
+ | PS_APP = _winreg.QueryValueEx(PS_key, | ||
- | * toggle script editor echo and clear echo <code python> | + | def ps_loc_win(): |
- | cmdPanel = ' | + | |
- | next_state | + | |
- | cmds.cmdScrollFieldReporter(cmdPanel, e=1, eac=next_state) | + | |
- | + | | |
- | # clear echo | + | |
- | cmds.cmdScrollFieldReporter(cmdPanel, | + | |
</ | </ | ||
- | * create script editor | + | * windows set default playback device |
- | if cmds.cmdScrollFieldExecuter(ui_name, q=1, ex=1): | + | |
- | cmds.deleteUI(ui_name) | + | |
- | mui = cmds.cmdScrollFieldExecuter(ui_name, | + | |
- | selected_text = cmds.cmdScrollFieldExecuter(mui, q=1, selectedText=1) | + | * run python script itself as admin <code python> |
- | # change selected or insert text | + | import ctypes, sys |
- | cmds.cmdScrollFieldExecuter(mui, e=1, insertText='# | + | def is_admin(): |
+ | try: | ||
+ | | ||
+ | except: | ||
+ | return False | ||
- | # set search text and search next | + | if is_admin(): |
- | cmds.cmdScrollFieldExecuter(mui, e=1, searchDown=1, | + | # Code of your program here |
- | res = cmds.cmdScrollFieldExecuter(mui, | + | else: |
- | + | # Re-run the program with admin rights | |
- | # go to line number | + | |
- | cmds.cmdScrollFieldExecuter(mui, e=1, currentLine=lineNum) | + | |
- | + | ||
- | # get script editor whole text | + | |
- | script_text = cmds.cmdScrollFieldExecuter(mui, q=1, t=1) | + | |
</ | </ | ||
- | * create script history logger | + | * run cmd with admin (3rd party module) |
- | mui = cmds.cmdScrollFieldReporter(ui_name)#, | + | # pywin32 : sourceforge.net/ |
- | + | import win32com.shell.shell as shell | |
- | # clear history | + | commands = 'echo hi' |
- | cmds.cmdScrollFieldReporter(mui, e=1, clr=1) | + | shell.ShellExecuteEx(lpVerb=' |
</ | </ | ||
- | * open component editor and show skining tab <code python> | + | * control, start, stop, restart service (3rd party module method) |
- | mel.eval(' | + | import win32serviceutil |
- | # active smooth skin tab | + | serviceName |
- | tab_name_list | + | win32serviceutil.RestartService(serviceName) |
- | for i in range(len(tab_name_list)): | + | |
- | if tab_name_list[i] == " | + | |
- | cmds.tabLayout(" | + | |
</ | </ | ||
- | | + | ==== List File and sub-folders - the Correct Way ==== |
- | for each in cmds.ls(sl=1): | + | |
- | | + | |
+ | * so compare glob, python 2.7 os.walk() | ||
+ | * scandir is at least 10x faster than old os.walk, glob is 2x faster than old os.walk by skipping dir/file type checking | ||
+ | * 132s (walk) / 62s (glob) / 12s (scandir) | ||
+ | |||
+ | <code python> | ||
+ | import glob | ||
+ | def getDirData(top, | ||
+ | res = [] | ||
+ | | ||
+ | maxGlob = "/" | ||
+ | topGlob = os.path.join( top, maxGlob ) | ||
+ | allFiles = glob.glob( topGlob ) | ||
+ | res.extend( allFiles ) | ||
+ | # | ||
+ | return res | ||
+ | |||
+ | import os | ||
+ | def getDirData( top, maxDepth=1): | ||
+ | | ||
+ | # py3 option: better with scandir, | ||
+ | # py2 has: https:// | ||
+ | top = os.path.normpath(top) | ||
+ | res = [] | ||
+ | root_len = len(top) # | ||
+ | for root,dirs,files in os.walk(top, | ||
+ | depth = root[root_len: | ||
+ | if depth < maxDepth: | ||
+ | res += [os.path.join(root, | ||
+ | res += [os.path.join(root, | ||
+ | elif depth == maxDepth: | ||
+ | res += [os.path.join(root, | ||
+ | res += [os.path.join(root, | ||
+ | dirs[:] = [] # Don't recurse any deeper | ||
+ | return res | ||
+ | |||
+ | # detail inf: https:// | ||
+ | # can use with maya 2017 by zip out _scandir.pyd and scandir.py: (scandir-1.10.0-cp27-cp27m-win_amd64.whl) https:// | ||
+ | tmp_path = r' | ||
+ | tmp_path in sys.path or sys.path.append(tmp_path) | ||
+ | import scandir | ||
+ | def getDirData(top, maxDepth=1): | ||
+ | # py3.5 has built in, only for 2.7 | ||
+ | # https:// | ||
+ | top = os.path.normpath(top) | ||
+ | res = [] | ||
+ | root_len = len(top)# | ||
+ | for root, | ||
+ | depth = root[root_len: | ||
+ | if depth < maxDepth: | ||
+ | res += [os.path.join(root, | ||
+ | res += [os.path.join(root, | ||
+ | elif depth == maxDepth: | ||
+ | res += [os.path.join(root, | ||
+ | res += [os.path.join(root, | ||
+ | dirs[:] = [] # Don't recurse any deeper | ||
+ | return res | ||
| | ||
- | cmds.hyperGraph(" | + | # test |
- | newBookmark | + | import time |
- | cmds.setAttr(newBookmark+" | + | start = time.time() |
+ | result | ||
+ | end = time.time() | ||
+ | print(' | ||
</ | </ | ||
- | * add selected node in nodeEditor and related <code python> | + | * other module option: https://github.com/ |
- | for each in cmds.ls(sl=1): | + | |
- | cmds.nodeEditor(' | + | |
- | cmds.getPanel(scriptType=" | + | ==== Copy File - the Correct Way ==== |
- | 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 | + | |
+ | * ref: http:// | ||
- | * reset hotkey < | + | * use python |
- | * create hotkey | + | import |
- | import | + | # copy: return target path, target can be file or folder |
- | # cmds.hotkey(factorySettings=1) | + | shutil.copy(' |
- | cmds.hotkey(autoSave=0) | + | shutil.copyfile(' |
- | # -- toggle select mode | + | ''' |
- | g_shiToggleMode=0 | + | 02/ |
- | def toggleSelectMode(curMode): | + | 12/ |
- | global g_shiToggleMode | + | 12/ |
- | print([g_shiToggleMode, curMode]) | + | ''' |
- | 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() | + | |
- | | + | |
- | for i in range(1,3+1): | + | shutil.copy2('d:/z_tmp/aa.jpg',' |
- | cmds.nameCommand('NC_shiToggleMode_' | + | '' |
- | cmds.hotkey(k=str(i), | + | 02/ |
+ | 02/ | ||
+ | '' | ||
+ | </ | ||
+ | * copy folder and its content <code python> | ||
+ | import shutil, errno | ||
- | # -- viewport | + | def copyFolder(src, dst): |
- | cmds.nameCommand(' | + | try: |
- | cmds.hotkey(k=' | + | |
- | # -- windows | + | |
- | cmds.nameCommand(' | + | if exc.errno == errno.ENOTDIR: |
- | cmds.hotkey(k=' | + | |
- | # -- script editor | + | else: raise |
- | cmds.nameCommand(' | + | |
- | cmds.hotkey(k=' | + | # or use this |
- | #-- hyperShade | + | distutils.dir_util.copy_tree(src_folder, tgt_folder) |
- | cmds.nameCommand(' | + | |
- | cmds.hotkey(k=' | + | |
- | #-- graph editor | + | |
- | cmds.nameCommand(' | + | |
- | cmds.hotkey(k=' | + | |
- | #-- custom | + | |
- | cmds.nameCommand(' | + | |
- | cmds.hotkey(k=' | + | |
</ | </ | ||
- | * Hotkey with Qt <code python> | + | * python more complete version for copy folder and its content |
- | # ref: http://bindpose.com/custom-global-hotkey-maya/ | + | # ref: http://blog.csdn.net/liyuan_669/article/ |
- | # qt import | + | # folder content to target folder content. created folders seams not same date, but file same date |
- | try: | + | import |
- | | + | import shutil |
- | | + | def copy_tree(src, |
- | | + | |
- | except | + | |
+ | os.makedirs(dst) | ||
+ | | ||
+ | for name in names: | ||
+ | srcname = os.path.join(src, | ||
+ | dstname = os.path.join(dst, | ||
+ | try: | ||
+ | if symlinks and os.path.islink(srcname): | ||
+ | linkto = os.readlink(srcname) | ||
+ | os.symlink(linkto, | ||
+ | elif os.path.isdir(srcname): | ||
+ | copy_tree(srcname, | ||
+ | else: | ||
+ | # perform overrides | ||
+ | if os.path.isdir(dstname): | ||
+ | os.rmdir(dstname) | ||
+ | elif os.path.isfile(dstname): | ||
+ | os.remove(dstname) | ||
+ | shutil.copy2(srcname, | ||
+ | # other case like: devices, sockets etc. | ||
+ | | ||
+ | errors.append((srcname, | ||
+ | # catch the Error from the recursive copy_tree so that we can | ||
+ | # continue with other files | ||
+ | except OSError as err: | ||
+ | errors.extend(err.args[0]) | ||
try: | try: | ||
- | | + | |
- | import shiboken2 as shiboken | + | except |
- | except | + | # can't copy file access times on Windows |
pass | pass | ||
- | import maya.cmds | + | except OSError |
- | import maya.OpenMayaUI as omui | + | |
- | hotkey = {} | + | if errors: |
- | mayaWindow = shiboken.wrapInstance(long(omui.MQtUtil.mainWindow()), QtWidgets.QWidget) | + | raise Error(errors) |
- | def shortcut_process(keyname): | + | copy_tree('E:/book', 'E:/newbook') |
- | if keyname == 'toggleWire': | + | </ |
- | if 'modelPanel' | + | * use system command to copy <code python> |
- | | + | src = 'd:/ |
- | else: | + | tgt = 'd:/ |
- | # give key event to maya | + | os.system('copy {0} {1}'.format(os.path.normpath(src),os.path.normpath(tgt)) ) |
- | hotkey[keyname].setEnabled(0) | + | # normpath will change the slash for win from unix path |
- | e = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_F, | + | |
- | QtCore.QCoreApplication.postEvent(mayaWindow , e) | + | |
- | cmds.evalDeferred(partial(hotkey[keyname].setEnabled, | + | |
- | | + | |
- | pass | + | |
- | # shortcut | + | os.system |
- | hotkey[' | + | |
- | hotkey[' | + | |
- | hotkey[' | + | |
</ | </ | ||
+ | * use system file manager to copy with system progress window (windows) <code python> | ||
+ | #ref: https:// | ||
+ | import pythoncom | ||
+ | from win32com.shell import shell, | ||
- | ====== | + | def win_copy_process(src_files, |
+ | # win32com module | ||
+ | pfo = pythoncom.CoCreateInstance(shell.CLSID_FileOperation, | ||
+ | # Respond with Yes to All for any dialog | ||
+ | # ref: http:// | ||
+ | pfo.SetOperationFlags(shellcon.FOF_NOCONFIRMATION) | ||
+ | # Set the destionation folder | ||
+ | dst = shell.SHCreateItemFromParsingName(tgt_folder, | ||
+ | for f in src_files: | ||
+ | src = shell.SHCreateItemFromParsingName(f, | ||
+ | pfo.CopyItem(src, | ||
+ | # ref: http:// | ||
+ | success | ||
+ | # ref: sdn.microsoft.com/ | ||
+ | aborted | ||
+ | return success and not aborted | ||
- | ** Python Maya API reference ** | + | file_list = [r'D:\z_tmp\aa.jpg', |
- | + | tgt_folder = r' | |
- | * Python script and Python Maya API example: | + | win_copy_process(file_list,tgt_folder) |
- | * http://cgkit.sourceforge.net/ | + | </code> |
- | * Cope with Python in Maya C++ API with MScriptUtil, for passing by reference; re-solved in API 2.0 | + | * use system |
- | * http:// | + | #ref: http://timgolden.me.uk/python/win32_how_do_i/copy-a-file.html |
- | * Maya API 2.0 for python, new python way of the redefined C++ API from Maya 2013 | + | # win32 |
- | | + | from win32com.shell import shell, shellcon |
- | + | win32file.CopyFile (filename1, filename2, 1) | |
- | ** 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:// | + | |
+ | # win32com : with windows copy dialog | ||
+ | shell.SHFileOperation ( (0, shellcon.FO_COPY, | ||
+ | # for the " | ||
+ | # shellcon.FOF_RENAMEONCOLLISION: | ||
+ | # shellcon.FOF_NOCONFIRMATION: | ||
+ | # shellcon.FOF_NOERRORUI: | ||
+ | # shellcon.FOF_SILENT: | ||
+ | # shellcon.FOF_NOCONFIRMMKDIR: | ||
+ | </ | ||
- | ===== DAG path, object and nodes ===== | + | ===== string operation |
- | **om.MDagPath**: | + | |
- | * path info for Dag node, the normal way, python/mel refering to maya objects like Transform | + | unicode(myQStringObj) # unicode output |
- | * it is from the world node to a particular object in the DAG | + | unicode(myQStringObj.toUtf8(), encoding=" |
- | + | ||
- | * string name to MDagPath | + | |
- | selectionList = om.MSelectionList() | + | |
- | selectionList.add(mesh_name) | + | |
- | mesh_dagPath = om.MDagPath() | + | |
- | selectionList.getDagPath(0, mesh_dagPath) | + | |
- | print(mesh_dagPath.partialPathName()) # if done correct, it will give same name as mesh_name | + | |
</ | </ | ||
- | * MObject | + | * ref: |
- | geo_obj.hasFn(om.MFn.kDagNode) # check if it is Dag node, then it can have a dag path | + | * UTF-8 is an encoding used to translate numbers into binary data. |
- | geo_dagNode = om.MFnDagNode(geo_obj) # now it is a DagNode, then get DagPath from DagNode | + | * Unicode |
- | print( geo_dagNode.partialPathName() ) # since DagNode also has name function | + | * text block to clean row list <code python> |
- | geo_dagPath | + | data_list |
- | geo_dagNode.getPath(geo_dagPath) | + | |
- | print( geo_dagPath.partialPathName() | + | |
</ | </ | ||
- | | + | |
- | geo_dagPath | + | |
- | geo_dagNode.getPath(geo_dagPath) | + | # coding: utf-8 |
- | print( | + | my_special_string |
+ | print(u" | ||
</ | </ | ||
+ | * remove extra space from a string, also remove end single space< | ||
+ | tidy_name = ' ' | ||
- | **om.MObject**: | + | # just remove extra space |
- | * MDagPath to MObject conversion, <code python> | + | tidy_name |
- | geo_transform_ojbect | + | tidy_name = re.sub(' |
- | geo_transform_ojbect.hasFn(om.MFn.kTransform) # true, then can safely cast to MFnTransform | + | |
</ | </ | ||
- | | + | ===== control flow operation ===== |
- | selectionList = om.MSelectionList() | + | |
- | selectionList.add(skinName) | + | |
- | skinClusterObj = om.MObject() | + | def my_fun(para1): |
- | selectionList.getDependNode(0, | + | print para1 |
- | skinClusterObj.hasFn(om.MFn.kSkinClusterFilter) # use for testing become cast to the object function | + | |
</ | </ | ||
+ | * logic and condition and loop operation <code python> | ||
+ | print tmpitem | ||
- | **MFnDagNode**: | + | if tmp > 0: |
- | * take input Object and provide function to access object as DAG Node, for its attribute and modify | + | print ' |
- | * it has same function as MDagPath like <code python> | + | elif x == 0: |
- | int = MDagPath.childCount() | + | print ' |
- | MObject = MDagPath.child(index) = MFnDagNode.child(index) | + | else: |
- | int = MDagPath.pathCount() = MFnDagNode.pathCount() | + | print ' |
- | # node only can return itself while path can return segment | + | # logic operation |
- | str = MDagPath.getPath(MDagPath, | + | x or y |
- | + | x and y | |
- | str = MDagPath.fullPathName() = MFnDagNode.fullPathName() | + | not x |
- | str = MDagPath.partialPathName() = MFnDagNode.partialPathName() | + | |
</ | </ | ||
- | * but MFnDagNode has ability to add and remove child, also duplicate, instanceCount | + | * try operation, note try is not a block, variable is same scope before try <code python> |
- | * for its matric info, it has transformationMatrix() | + | try: |
- | * but MDagPath has ability to get nearby or itself for transform MObject, cast into shape MDagPath | + | |
- | * for its matric info, it has inclusiveMatrix(), | + | except: |
- | + | pass | |
- | * str to MFnDagNode | + | # ref: https:// |
- | selectionList = om.MSelectionList() | + | |
- | selectionList.add(mesh_name) | + | |
- | mesh_dagPath | + | |
- | selectionList.getDagPath(0, | + | |
- | mesh_dagNode = om.MFnDagNode(mesh_dagPath) | + | |
- | </code> | + | |
- | * MDagPath to MFnDagNode <code python> | + | |
- | my_dagNode = om.MFnDagNode(my_dagPath) | + | |
- | </code> | + | |
- | * MObject to MFnDagNode <code python> | + | |
- | my_obj.hasFn(om.MFn.kDagNode) | + | |
- | my_dagNode = om.MFnDagNode(my_obj) | + | |
</ | </ | ||
+ | ===== data operation ===== | ||
- | <code python> | + | * data type check <code python> |
- | import maya.OpenMaya as om | + | isinstance(Vehicle(), Vehicle) # returns True |
- | selected = om.MSelectionList() | + | type(Vehicle()) == Vehicle |
- | om.MGlobal.getActiveSelectionList(selected) | + | isinstance(Truck(), Vehicle) # returns True, takes inheritance |
- | dagPath = om.MDagPath() | + | type(Truck()) == Vehicle |
- | selected.getDagPath(0,dagPath) | + | |
- | print(dagPath.fullPathName()) # current full path name | + | |
- | print(dagPath.partialPathName()) # current node name | + | |
- | # get shape count | + | # check string, both python 2,3 |
- | countUtil = om.MScriptUtil() | + | isinstance(my_data, |
- | countUtil.createFromInt(0) | + | # check list |
- | tmpIntPtr = countUtil.asUintPtr() | + | isinstance(my_data, |
- | dagPath.numberOfShapesDirectlyBelow(tmpIntPtr) | + | |
- | print(countUtil.getUint(tmpIntPtr)) | + | |
- | print(dagPath.childCount()) # another alternative method | + | |
- | # get its Shape as MDagPath | + | # check not None, note, None is its own type and has only one instance |
- | dagPath.extendToShape() | + | my_vec != None # sometime, it may cause error when = try to read certain way |
- | dagPath.extendToShapeDirectlyBelow(0) | + | my_vec is not None # this way is preferred to be error free |
- | print(dagPath.fullPathName()) # shape, if already shape, also return itself | + | isinstance(my_vec, om.MVector) # this way even better, not only check it is defined and also check its type match |
- | # get itself as Node | + | # compare type |
- | dagObj = dagPath.node() # MObject | + | if type(A) is type(B): |
- | dagObj.hasFn(om.MFn.kDagNode) # check if its DAG node, not DG node | + | print('Same Class') |
- | dagNode | + | |
- | print(dagNode.fullPathName()) | + | # note : is for same object and like pointer comparision, while == is same value comparision |
+ | </ | ||
- | # get its Transform as Node | + | * data type convertion <code python> |
- | dagObj = dagPath.transform() # MObject: return its transform MObject, if already transform, it return itself | + | str(42) |
- | dagNode = om.MFnDagNode(dagObj) | + | |
- | print(dagNode.fullPathName()) # transform node full dag path | + | |
</ | </ | ||
+ | * get datetime date <code python> | ||
+ | * get processing time <code python> | ||
+ | import time | ||
- | ===== DG nodes ===== | + | start = time.time() |
- | + | print(" | |
- | * **MFnDependencyNode**: | + | end = time.time() |
- | my_DGNode | + | print(end - start) |
- | print(my_DGNode.name()) | + | |
</ | </ | ||
- | * MFnDependencyNode also has function like attribute editor, editing all the plug/ | + | * wild char process <code python> |
+ | import glob | ||
+ | os.chdir(path) | ||
+ | for file in glob.glob(" | ||
+ | print file | ||
+ | |||
+ | tmplist=glob.glob(' | ||
- | * Conversion from other data | + | # other method: fnmatch and re |
- | * 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**: it act directly on adding nodes, making new connections, | + | * string operation <code python> |
+ | name=" | ||
+ | print name[3:-13].replace(' | ||
- | other common class: | + | len(str) # string length |
- | * **om.MPxNode**: | + | |
- | ===== MPlugs and Attributes ===== | + | "hello world" |
+ | 'Hello World' | ||
- | ref: http:// | + | # remove Whitespace on the both sides: |
+ | s = " | ||
+ | s = s.strip() | ||
- | * MPlug is just API way of calling attribute. | + | # remove Whitespace on the right side: |
- | * MPlug has network(built-in) and non-networked(user-created) ones | + | s = s.rstrip() |
- | * MPlug can be a single attribute plug, or a array/ | + | # remove Whitespace on the left side: |
- | * MPlug can have its own MPlug, as compound plug, which can be accessed with .child(); the opposite is .parent() | + | s = s.lstrip() |
- | * Array type MPlug can access its list of plug by .elementByLogicalIndex() and .elementByPhysicalIndex() method; the opposite is .array() | + | </ |
- | Array in MPlug: | + | Data Type |
- | * array are sparse. | + | * Lists []: a list of values. Each one of them is numbered, starting |
- | * Logical Index used in MEL are sparse; | + | * Tuples (): are just like lists, but you can't change their values. The values that you give it first up, are the values that you are stuck with for the rest of the program. Again, each value is numbered starting from zero, for easy reference. Example: the names of the months of the year. |
- | * Physical Index is not sparse, it range from 0-numElements()-1, while numElements may sometimes only reflect | + | * Dictionaries {'':'', |
- | * numElement only refer to data in datablock, to get update | + | |
- | print(my_arraPlugy.numElements())</ | + | |
- | === Logical Index and Physical Index of Joint in skincluster === | + | * list operation (so-called array) ([[http:// |
+ | [1,2,3] | ||
+ | len(arrayName) # size of list | ||
- | **Concept | + | # add value to all elements |
- | * 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:// | + | my_list=[1,3,5,7,8] |
- | * in addition, Maya is using the sparse array structure for array plugs(attributes) | + | new_list = [x+1 for x in my_list] # [2,4,6,8,9] |
- | * Maya will automatically resize and create new list of elements | + | |
- | * 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> | + | list.append(obj) |
- | $nameArray[5]=" | + | list.extend(list) |
- | print(" | + | list.index(obj) |
- | print $nameArray; // null null null null null objA | + | list.count(obj) # count appearance of obj |
- | print(" | + | |
- | $logical_index | + | # array to string and string to array |
- | print("\nlogical index of ' | + | sentence |
- | print("\nsize: | + | ' '.join(sentence) #' |
- | print("\n9th: | + | |
- | print(" | + | |
- | print(" | + | |
- | string $nameArray_autoNonSparse_means_noEmpty[]; | + | list=[3.1, |
- | for($each in $nameArray){ | + | ', '.join(map(str,list)) #'3.1, 121.2, 34.1' |
- | 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 | + | '3.1, 121.2, 34.1' |
- | clear($nameArray); | + | # 2nd number |
- | </ | + | '3.1, 121.2, 34.1' |
- | * so the logical index of a array is the number | + | |
- | * as shown in Maya skincluster joint list connection \\ {{graphic: | + | |
- | **The Physical index of joint in the influence | + | # list sort (modify itself) |
- | * the most obvious demonstration of physical index of joint is in the component editor - skin weight tab \\ {{graphic: | + | versionList.sort(reverse=1) # higher to lower |
- | * since " | + | versionList.sort() |
- | * 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) | + | |
- | | + | |
- | * {{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 ===== | + | str([-4, -2, 0, 2, 4]) # ' |
+ | bb_str | ||
- | * **MScriptUtil** ref: http:// | + | # list compare |
- | * Input: createFrom*() Int, Double, List+len | + | [-4, -2, 0, 2, 4] == [-4, -2, 0, 2, 3] # false |
- | * 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 ===== | + | # multiple list operation |
- | * 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 | + | wMap_zero_Jnt=[x for x in wMap_All_Jnt if x not in wMap_Jnt_tgt] |
- | # case 1: get a MVector result by rotate one Vector around another Vector by certain degree | + | |
- | res_vector | + | |
- | # 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 | + | |
- | 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 | + | a = [1, 2, 3, 4, 5] |
- | # of course, you dont need a refLoc, you can use Vector from other data as well | + | b = [9, 8, 7, 6, 5] |
- | # here locator is just a visual example | + | common |
- | refLoc | + | |
- | obj = ' | + | |
- | # grab object transform function lib | + | [i for i, j in zip(a, b) if i == j] # [5] for equal-sized lists, which order matters |
- | selectionList = om.MSelectionList() | + | |
- | selectionList.add( obj ) | + | |
- | dagPath | + | |
- | selectionList.getDagPath(0, dagPath) | + | |
- | fnTransform = om.MFnTransform(dagPath) | + | |
- | # STEP 1: align Y axis first | + | version_list_prefix=[" |
- | refLoc_normal_y | + | version_list_postfix=[" |
- | aim_vec_y | + | version_list_color=["rgb(255, 128, 128)"," |
- | obj_normal_y = getLocalVecToWorldSpaceAPI(obj, om.MVector.yAxis) | + | for each, |
- | obj_vec_y = om.MVector(*obj_normal_y) | + | print(each+" " |
- | rot_quaternion = obj_vec_y.rotateTo(aim_vec_y) | + | |
- | # absolute version | + | # list slice operation |
- | #fnTransform.setRotationQuaternion( rot_quaternion.x, | + | # n = len(L) |
- | # relative transform on top of existing obj transform | + | # item = L[index] |
- | fnTransform.rotateBy(rot_quaternion) | + | # seq = L[start: |
+ | # seq = L[start: | ||
+ | seq = L[::2] # get every other item, starting with the first | ||
+ | seq = L[1::2] # get every other item, starting with the second | ||
- | # STEP 2: align Z axis | + | seq=L[:: |
- | refLoc_normal_z = getLocalVecToWorldSpaceAPI(refLoc, om.MVector.zAxis) | + | list(reversed(seq)) # generate a list reversed |
- | aim_vec_z = om.MVector(*refLoc_normal_z) | + | L.reverse() # reverse list, it changed original list |
- | obj_normal_z = getLocalVecToWorldSpaceAPI(obj, | + | seq=L[::-2] # reverse reading list, and get every other item |
- | obj_vec_z = om.MVector(*obj_normal_z) | + | |
- | rot_quaternion | + | |
- | fnTransform.rotateBy(rot_quaternion) | + | |
- | </ | + | |
- | * get position <code python> | + | list=[1,2, |
- | #general | + | seq=list[::-2] # [6,4,2] |
- | pos = cmds.xform(obj, | + | |
- | # loc | + | # list to pair list |
- | pos = cmds.getAttr(loc+'.worldPosition')[0] # world position | + | info = ['name',' |
- | pos = cmds.pointPosition(loc) # world position | + | info_pair |
+ | # [(' | ||
+ | # list flatten, and remove duplicates | ||
+ | nestList = [[2, | ||
+ | flatList = set(sum(nestList, | ||
- | #cv - local position | + | # delete a element |
- | cmds.getAttr(' | + | a = [1,2,3,4] |
- | cmds.pointPosition(' | + | del(a[2]) # [1,2,4] |
- | # vtx - | + | # for large list |
- | cmds.getAttr(' | + | import itertools |
- | cmds.getAttr(" | + | flatList = set( list(itertools.chain.from_iterable(nestList)) ) # for same type element list, no mix of list and element like [[5],6], as it will error |
- | cmds.pointPosition(' | + | import operator |
- | cmds.setAttr(" | + | flatList |
- | cmds.setAttr(" | + | |
- | cmds.setAttr(" | + | |
- | # vtx - local position relative to pivot | + | # for any mix list |
+ | def flatFn(mainList): | ||
+ | newList = [] | ||
+ | for each in mainList: | ||
+ | if isinstance(each, | ||
+ | newList.extend( flatFn(each) ) | ||
+ | else: | ||
+ | newList.append(each) | ||
+ | return newList | ||
+ | |||
+ | flatList = set( flatFn(nestList) ) | ||
- | # 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) | ||
</ | </ | ||
+ | * dictionary operation <code python> | ||
+ | # ref: http:// | ||
+ | dict.clear() | ||
+ | dict.get(key, | ||
+ | dict.has_key(key) | ||
+ | dict.items() # a list dict's (key, value) tuple pairs | ||
+ | dict.keys() # a list of keys | ||
+ | dict.values() # a list of values | ||
+ | len(dict) # size of dict | ||
- | * get current soft selection and weight< | + | #example, obj property |
- | def getVtxSelectionSoft_OpenMaya(): | + | dict = {' |
- | # 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() | + | #example, obj list |
- | selectionObj = om.MObject() | + | dict = {' |
- | iter = om.MItSelectionList( selectionList, om.MFn.kMeshVertComponent) | + | |
- | # | + | |
- | id_list = [] | + | |
- | weights = [] | + | |
- | node_list = [] | + | |
- | while not iter.isDone(): | + | |
- | iter.getDagPath( selectionPath, selectionObj ) | + | |
- | selectionPath.pop() #Grab the parent of the shape node | + | |
- | node = selectionPath.fullPathName() | + | |
- | compFn = om.MFnSingleIndexedComponent(selectionObj) | + | |
- | getWeight = lambda i: compFn.weight(i).influence() if compFn.hasWeights() else 1.0 | + | |
- | 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> | + | # delete key |
- | def createVtxSelection_cmds(objName, | + | my_dict.pop(key_name, None) |
- | startTime = cmds.timerX() | + | |
- | | + | |
- | 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 ====== | + | * set operation <code python> |
+ | set( (" | ||
+ | # set([' | ||
- | * list of cmds in both python and mel for modeling | + | fib=set( [1, |
+ | prime=set( [2, | ||
+ | combine=fib | prime # set([1, 2, 3, 5, 7, 8, 11, 13]) | ||
+ | common=fib & prime #set([2, 3, 5, 13]) | ||
+ | diff = fib - prime # set([8, 1]) | ||
+ | fib ^ prime # combine - common #set([1, 7, 8, 11]) | ||
- | <code python> | + | # <, < |
- | # mel: DeleteHistory; | + | len(fib)=6 |
- | cmds.DeleteHistory() | + | max(fib)=13 |
+ | min(fib)=1 | ||
+ | sum(fib)=32 | ||
+ | any(set([false, | ||
+ | all(set([false, | ||
+ | set.add(new) | ||
+ | set.remove(old) | ||
+ | </ | ||
- | # show poly vertex | + | * list attribute of a object <code python> |
- | # mel: setPolygonDisplaySettings(" | + | ===== turn a file into a module ===== |
- | cmds.polyOptions(r=1, activeObjects=1, dv=1) | + | |
- | # mel: ToggleFaceNormalDisplay; | + | * a file can be a module like <code python> |
- | cmds.ToggleFaceNormalDisplay() | + | import FileName |
+ | FileName.functionName() # call function side that file | ||
+ | </ | ||
- | # mel: ToggleBackfaceGeometry; | ||
- | cmds.ToggleBackfaceGeometry() | ||
- | # mel: ReversePolygonNormals; | + | ===== turn a folder into a module ===== |
- | cmds.ReversePolygonNormals() | + | |
- | # hard edge | + | * you can turn a list of python file under same folder into a module by create a empty file under the directory: < |
- | # mel: SoftPolyEdgeElements(0); | + | * it will automatically turn the folder into a module so that you can call each file after import folder like <code python> |
- | cmds.polySoftEdge(a=0) | + | import FolderName |
+ | from FolderName import FileName | ||
+ | </ | ||
+ | * whenever you call import FolderName, the __init__py will be run-ed first, so you can put some code inside to do some pre-run task, or append the folder into python path <code python __init__.py> | ||
+ | import os, sys | ||
+ | my_path = os.path.dirname(__file__) | ||
+ | my_path in sys.path or sys.path.append(my_path) | ||
+ | </ | ||
- | # show border | + | ===== check module installed ===== |
- | # mel: ToggleBorderEdges; | + | |
- | cmds.ToggleBorderEdges() | + | |
- | # dialog to change edge width display | + | * python code <code python> |
- | # mel: ChangeEdgeWidth; | + | dir(moduleName) |
- | cmds.ChangeEdgeWidth() | + | #import moduleName |
+ | #then type moduleName to show location | ||
+ | </ | ||
+ | * check module in shell command <code bash> | ||
+ | * change current working path< | ||
+ | </ | ||
- | # show UV window | + | ===== cross python file reference===== |
- | # mel: TextureViewWindow; | + | |
- | cmds.TextureViewWindow(); | + | |
- | # mel: CenterPivot; | + | |
- | cmds.CenterPivot() | + | |
- | # turn on / off symmetric modeling | + | * import python function from another python file< |
- | ''' | + | import sys |
- | symmetricModelling -e -symmetry true; | + | sys.path.append("/ |
- | symmetricModelling -e -about | + | # method 1 |
- | symmetricModelling -e -about " | + | import sample |
- | symmetricModelling -e -axis "x" | + | sample.abcd() |
- | ''' | + | # method 2 |
- | cmds.symmetricModelling(e=1, symmetry=1) | + | from sample import abcd |
- | cmds.symmetricModelling(e=1, symmetry=0) | + | abcd() |
+ | #method 3 | ||
+ | from sample import * | ||
+ | abcd() | ||
+ | </ | ||
+ | * run another python script <code python> | ||
+ | execfile("testScript.py") | ||
+ | # python 3, that one not work, use this one | ||
+ | exec(open(" | ||
+ | </ | ||
+ | * pass parameter to a python script | ||
+ | # method 1: python myscript.py arg1 arg2 | ||
+ | import sys | ||
- | # face selection to edge border selection | + | print ' |
- | # mel: select -r `polyListComponentConversion -ff -te -bo`; | + | print ' |
- | cmds.select(cmds.polyListComponentConversion(ff=1, | + | |
- | # selection | + | # method 2 |
- | # mel: ConvertSelectionToShell; | + | import getopt, sys |
- | cmds.ConvertSelectionToShell() | + | </ |
+ | * example code for a mel file to html file process, in both module and app way | ||
+ | * run code <code bash> | ||
+ | * run in python <code python> | ||
+ | viewMel.viewMel_html(' | ||
+ | </ | ||
+ | * code <code python> | ||
+ | #view mel lib | ||
+ | #a python tool to view mel lib file functionts | ||
+ | import sys | ||
- | # selection to create layer | + | def viewMel(file): |
- | ''' | + | with open(file) as f: |
- | string $tmpname[]=`ls | + | for line in f: |
- | createDisplayLayer -name ($tmpname[0]+"_Lyr" | + | lineClean=line.strip() |
- | '' | + | if lineClean.startswith('//==') or lineClean.startswith('global proc') or lineClean.startswith('/ |
- | selected | + | print lineClean.replace(' |
- | if len(selected)>0: | + | |
- | cmds.createDisplayLayer(name=selected[0]+'_layer', | + | def viewMel_html(melFile, |
+ | h = open(htmlFile,' | ||
+ | with open(melFile) as f: | ||
+ | h.write('< | ||
+ | for line in f: | ||
+ | lineClean=line.strip() | ||
+ | if lineClean.startswith('//=='): | ||
+ | | ||
+ | elif lineClean.startswith(' | ||
+ | | ||
+ | elif lineClean.startswith('/ | ||
+ | h.write('< | ||
+ | h.write('</ | ||
+ | h.close() | ||
| | ||
- | # remove from layers | + | if __name__=="__main__": |
- | # mel: editDisplayLayerMembers -noRecurse | + | |
- | cmds.editDisplayLayerMembers(" | + | </ |
+ | ===== web link operation ===== | ||
- | #### curve ##### | + | * open url in brower <code python> |
+ | webbrowser._browsers | ||
- | # show CV | + | new = 2 # open in a new tab, if possible |
- | # mel: ToggleCVs; | + | # open a public URL, in this case, the webbrowser docs |
- | cmds.ToggleCVs() | + | url = "http:// |
+ | webbrowser.open(url,new=new) | ||
+ | # for using firefox, note, firefox needs to be default or in the system environment path | ||
+ | webbrowser.get(' | ||
+ | # open an HTML file on my own (Windows) computer | ||
+ | url = " | ||
+ | webbrowser.open(url, | ||
+ | </ | ||
- | # show editable point | + | ===== file operation ===== |
- | # mel: ToggleEditPoints; | + | * check file exists <code python> |
- | cmds.ToggleEditPoints() | + | * access file and folder< |
+ | # delete a file | ||
+ | os.remove(tmpitem) | ||
- | # rebuild curve dialog | + | # check exist of a file |
- | # mel: RebuildCurveOptions; | + | if not os.path.exists(tmpJPG): |
- | cmds.RebuildCurveOptions() | + | print tmpitem |
- | # detach surface by edge or isopam | + | # get file with a certain name pattern in a directory and all its sub-directories |
- | # mel: DetachCurve; | + | scanDir = r'D:\scanDir_start_dir' |
- | cmds.DetachCurve() | + | my_list = [] |
+ | for (dirpath, dirnames, filenames) in os.walk(scanDir): | ||
+ | my_list.extend( [ os.path.join(dirpath, | ||
- | # detach/ | + | # get file with a certain name pattern in a directory |
- | # mel: CutCurve; AttachCurve; | + | [x for x in os.listdir(scanDir) if os.path.isfile(os.path.join(scanDir, |
- | cmds.CutCurve() | + | [x for x in glob.glob(os.path.join(scanDir,' |
- | cmds.AttachCurve() | + | |
</ | </ | ||
+ | * lines to array< | ||
+ | with open(fname) as f: | ||
+ | content = f.readlines() | ||
- | ====== | + | with open(fname) as f: |
- | + | for line in f: | |
- | **Vertex check in API way** | + | #do something with line |
+ | </ | ||
+ | * remove any non-ascii words in file names <code python> | ||
+ | import os | ||
+ | import urllib | ||
+ | curdir | ||
+ | os.chdir(parentDir) | ||
+ | file_list_en | ||
+ | file_list_cn | ||
+ | for i in xrange(len(file_list_en)): | ||
+ | itemName | ||
+ | print(itemName) | ||
+ | if itemName.startswith(' | ||
+ | # convert %AA cn name to unicode | ||
+ | new_fixName1 | ||
+ | # then remove cn chars | ||
+ | new_itemName2 | ||
+ | if itemName != new_itemName2: | ||
+ | print([itemName, | ||
+ | os.rename(itemName, | ||
+ | else: | ||
+ | itemName | ||
+ | # just remove cn chars | ||
+ | new_itemName | ||
+ | if itemName != new_itemName: | ||
+ | | ||
+ | | ||
+ | |||
+ | os.chdir(curdir) | ||
+ | </ | ||
+ | | ||
+ | import linecache | ||
+ | linecache.getline('/ | ||
+ | </ | ||
+ | | ||
+ | import sys | ||
+ | import fileinput | ||
- | * poly count <code python> | + | for i, line in enumerate(fileinput.input(' |
- | vtx_cnt | + | # |
+ | if line.find(' | ||
+ | color=line.split(';' | ||
+ | color=color.split(' | ||
+ | sys.stdout.write(line.replace('</ | ||
+ | else: | ||
+ | sys.stdout.write(line) | ||
</ | </ | ||
+ | * make sure directory is created before create file in defined path <code python> | ||
+ | my_filePath = '/ | ||
+ | # method 1: these 2 lines only create last leaf folder, won't deal all the inbetween folders | ||
+ | if not os.path.isdir(os.path.dirname(my_filePath)): | ||
+ | os.mkdir(os.path.dirname(my_filePath)) | ||
+ | | ||
+ | # method 2: these 2 lines create all the needed folders forming the path | ||
+ | try: | ||
+ | os.makedirs(os.path.dirname(my_filePath)) | ||
+ | except OSError: | ||
+ | pass | ||
+ | | ||
+ | # method 3: all in one check and create if needed, like method 2 but not need try and except since it will be ok for exist | ||
+ | os.makedirs(os.path.dirname(output_path), | ||
+ | </ | ||
+ | * write python data< | ||
+ | # write raw data and read raw data | ||
+ | os.getcwd() | ||
- | * get transform to shape to vtx <code python> | + | import json |
- | selected = om.MSelectionList() | + | def writeDataFile(data, file): |
- | om.MGlobal.getActiveSelectionList(selected) | + | with open(file, ' |
- | + | json.dump(data, f) | |
- | dagPath = om.MDagPath() | + | |
- | selected.getDagPath(0,dagPath) | + | def readDataFile(file): |
- | + | with open(file) as f: | |
- | print(dagPath.fullPathName()) # transform | + | data = json.load(f) |
- | dagPath.extendToShape() # .extendToShapeDirectlyBelow() if dont want to get child of child; will error if has no shape | + | return data |
- | print(dagPath.fullPathName()) # shape, if shape, also return itself | + | |
+ | # write dictionary data and read dictionary data | ||
+ | import csv | ||
+ | def writeDictFile(dict, file): | ||
+ | w = csv.writer(open(" | ||
+ | for key, val in dict.items(): | ||
+ | w.writerow([key, val]) | ||
- | dagPath.transform() # transform | + | def readDictFile(file): |
- | + | dict = {} | |
- | mesh = om.MFnMesh(dagPath.node()) # MObject to MeshFunction | + | for key, val in csv.reader(open(file)): |
- | print(mesh.numVertices()) # vtx count | + | |
+ | return dict | ||
</ | </ | ||
+ | * UTF-8 unicode file write< | ||
+ | ############### | ||
+ | import codecs | ||
+ | txt=u"< | ||
+ | i=0 | ||
+ | for each in a: | ||
+ | i=i+1; | ||
+ | txt=txt+u"< | ||
- | **Vertex Check** | + | txt=txt+u'</ |
- | | + | with codecs.open(' |
- | # test empty geo shape | + | f.write(txt) |
- | all_geo_list = cmds.ls(type=' | + | </ |
- | zeroVtx_list | + | |
- | for each in all_geo_list: | + | for dirpath, dirnames, filenames in os.walk(srcPath): |
- | if cmds.polyEvaluate(each, v=1) == 0: | + | |
- | | + | if not os.path.isdir(to_mkdir): |
- | print(zeroVtx_list) | + | |
+ | os.startfile(tgtPath) # show result folder | ||
</ | </ | ||
- | **Face Check and Correct** | + | ===== thread operation ===== |
- | * double side face <code python> | + | * run a process or python service/app into another thread, so it wont block current interaction |
- | shape_list = cmds.ls(type='mesh' | + | def app(opt): |
- | for shape in shape_list: | + | if opt == 'Noter': |
- | | + | import threading |
+ | th = threading.Thread(target = run_noter) | ||
+ | | ||
+ | def run_noter(): | ||
+ | | ||
+ | Noter.main() | ||
</ | </ | ||
- | **Curve related** | + | ====== module operation ====== |
- | * get curve construction info <code python> | + | * How to install extra module automatically with pip |
- | def getCurveInfo(curveObj, | + | * download pip and use bellow method to manually install it, or run this line of command to get or update pip stuffs. <code python> |
- | | + | |
- | p = cmds.getAttr(curveObj+' | + | * How to install wheel package <code dos>REM pip app is at PythonAppDir/ |
- | p_new = [ [numClean(y, | + | REM make sure that wheel is in 32/64bit matching your python 32/64bit version |
- | tmpInfo = cmds.createNode( ' | + | pip install C:/ |
- | 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, | + | |
- | | + | |
- | return cmds.curve(curveObj, | + | |
- | else: | + | |
- | return cmds.curve(curveObj, | + | |
- | + | ||
- | def buildCurveInfo(curveInfo, | + | |
- | # input: dict - d, p; p ; d,p,k | + | |
- | if not isinstance(curveInfo, | + | |
- | | + | |
- | | + | |
- | 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, | + | |
</ | </ | ||
+ | * How to install extra module into seperate directory < | ||
+ | python.exe -m pip install --target=D: | ||
+ | </ | ||
+ | * How to install extra modules manually | ||
+ | - download the module source, | ||
+ | * if it has setup.py in its directory, run <code python> | ||
+ | * if it has no setup.py in its directory, or just a single folder or a python .py file, then just include its path in your python path under your system variable or run this command in python cmd line before you call <code python> | ||
+ | * **Ideally, if you put the Module Folder (not its parent distribution folder) under the Lib/ | ||
+ | * However, if you want your Python module to be at a specific location, then add it to python path environment variable. For windows advanced system setting > environment variable < | ||
+ | - distribute your python file with module, | ||
+ | * you can ask user to manually install those modules that used in your python program file | ||
+ | * or just put those dependent file or folder next to your python program file | ||
+ | * check with module info <code python> | ||
+ | # check whether a module has that attribute | ||
+ | if hasattr(QtGui, | ||
+ | print(" | ||
- | * curve_mel2py cmd convertor <code python> | + | # try get a function from a module and with fallback option |
- | #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' | + | resultFunc |
- | + | ||
- | # maya float number cleaner | + | |
- | def numClean(fnum, index=2): | + | |
- | rule = "{0:."+str(index)+" | + | |
- | cnum = float(rule.format(fnum)) | + | |
- | if cnum.is_integer(): | + | |
- | cnum = int(cnum) | + | |
- | return cnum | + | |
- | def curve_mel2py(txt): | + | # check imported module name dictionary |
- | rest, k = txt.split('-k',1) | + | if 'MyModule' in sys.modules: |
- | ks = [int(x.strip()) for x in k.split(' | + | print('yes') |
- | rest, p = rest.split('-p',1) | + | |
- | 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(' | + | |
- | | + | |
- | | + | |
</ | </ | ||
+ | * work with module using variable names <code python> | ||
+ | # import PySide | ||
+ | __import__(' | ||
- | * create NurbsCurve passing defined point list <code python> | + | # same as from PySide import QtNetwork as Net |
- | lineCurve | + | qtModeList |
- | nurbsCurve | + | qtMode |
- | cmds.delete(lineCurve) | + | Net = getattr( __import__(qtModeList[qtMode], fromlist=[' |
</ | </ | ||
+ | * import module from its parent directory <code python> | ||
+ | import os,sys | ||
+ | os.path.join(sys.path[0], | ||
+ | import my_module_at_current_file_parent_dir | ||
+ | </ | ||
+ | ===== useful module list ===== | ||
- | * **Research on API vs Cmds in Get and Set Vertex Position based on optional selection** <code python> | + | ==== useful HTML modules |
- | # ref: http:// | + | |
- | def getVtxSelection_OpenMaya(): | + | |
- | startTime | + | |
- | # normal selection | + | |
- | normalSelectionList | + | |
- | om.MGlobal.getActiveSelectionList(normalSelectionList) | + | |
- | selectionPath | + | |
- | selectionObj | + | |
- | indexList | + | |
- | 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, | + | * [[devwiki:python: |
- | | + | * < |
- | cmds.undoInfo(openChunk=1) | + | * BeautifulSoup [[http:// |
- | index_list, pos_list = pos_data | + | * Mechanize [[http://wwwsearch.sourceforge.net/ |
- | selectionList = om.MSelectionList() | + | * Scrapy [[http://scrapy.org/|site]](xpath expressions, link extractors, item loaders) |
- | selectionList.add(shapeNode) | + | * pyquery [[https:// |
- | | + | * dokuwiki: [[http:// |
- | 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() | + | |
- | | + | |
- | for i in range(len(index_list)): | + | |
- | meshPointArray.set(index_list[i], | + | |
- | mesh_fn.setPoints(meshPointArray, om.MSpace.kWorld) | + | |
- | else: # 0.53 | + | |
- | sel_index_list = getVtxSelection_OpenMaya()[1] | + | |
- | if not set(sel_index_list).issubset(set(index_list)): | + | |
- | print(' | + | |
- | return | + | |
- | meshPointArray = om.MPointArray() | + | |
- | mesh_fn.getPoints(meshPointArray, | + | |
- | for id in sel_index_list: | + | |
- | i = index_list.index(id) | + | |
- | | + | |
- | mesh_fn.setPoints(meshPointArray, | + | |
- | cmds.undoInfo(closeChunk=1) | + | |
- | totalTime = cmds.timerX(startTime=startTime) | + | |
- | print(" | + | |
- | </code> | + | |
- | ====== UV Editing ====== | + | |
+ | * embed code editor info: | ||
+ | * http:// | ||
+ | * https:// | ||
+ | * http:// | ||
+ | * http:// | ||
+ | * embed python console info: | ||
+ | * code module | ||
+ | * http:// | ||
+ | * http:// | ||
+ | * https:// | ||
+ | * http:// | ||
+ | * http:// | ||
- | ====== Material ====== | + | ==== useful automation modules |
- | | + | |
- | * material creation < | + | * winsys: admin automation for windows |
- | * change property <code python> | + | * ref: http:// |
+ | * pywinauto: https:// | ||
+ | * tutorial: | ||
+ | * DevNami - Python Pyautogui Take Screenshot : https:// | ||
+ | * SF Python - https:// | ||
+ | * https:// | ||
+ | * additional ref: | ||
+ | * https://www.youtube.com/ | ||
+ | * http://www.programcreek.com/ | ||
+ | * selenium: web browser automation | ||
+ | * the doc: http:// | ||
- | ====== Rigging ====== | + | ==== useful Graphic modules |
- | ===== delete unknown nodes ===== | + | * numpy : numerical python |
+ | * matplotlib : plot graphing tool (my note: [[devwiki: | ||
+ | * xlrd : excel read tool | ||
+ | * pandas : data structures and data analysis tools (loading datas and data operation like SQL) | ||
+ | * http:// | ||
+ | * PyQtGraph : plot graphing tool | ||
+ | * Gnuplot : plot tool | ||
+ | * ipython : interactive python and visualization tool | ||
+ | * OpenEXR | ||
+ | * IlmImf, a library that reads and writes OpenEXR images. | ||
+ | * Half, a C++ class for manipulating half values | ||
+ | * Imath, a math library with support for matrices, 2d- and 3d-transformations, | ||
+ | * exrdisplay, a sample application for viewing OpenEXR images at various exposure. | ||
+ | * wxPython : wx GUI | ||
+ | * PyQt/PySide : Qt GUI | ||
+ | * pysqlite/ | ||
+ | * Graph (node graph): https:// | ||
+ | * NetworkX (node graph big): http:// | ||
- | | + | ==== useful audio module ==== |
- | nodeList | + | |
- | # | + | |
- | def deleteIfNotReferenced( nodeList ): | + | p = pyaudio.PyAudio() |
- | if not isinstance(nodeList, | + | for i in range(p.get_device_count()): |
- | nodeList = [nodeList] | + | print p.get_device_info_by_index(i).get(' |
- | | + | |
- | if cmds.objExists(nodeToDelete) and not cmds.reference(nodeToDelete, | + | |
- | | + | |
- | if not isLocked: | + | |
- | cmds.delete( nodeToDelete | + | |
</ | </ | ||
+ | * useful Game building modules | ||
+ | * quick tutorial on python openGL game: http:// | ||
+ | ==== database and file modules ==== | ||
+ | * sqlite (built-in): | ||
+ | * additional desktop sqlite commandline tools: https:// | ||
+ | * json (built-in ascii out) | ||
+ | * cPickle (built-in binary out) | ||
+ | * encryption modules | ||
+ | * hashlib (built-in) <code python> | ||
+ | m = hashlib.md5() | ||
+ | m.update(user_input_text) | ||
+ | # a sample hash code example | ||
+ | if m.hexdigest() != ' | ||
+ | return | ||
+ | </ | ||
- | ===== common node and attributes ===== | + | * data hashing, like compress data into a string, good for big data compare and record |
+ | * hash (built-in, not need import) 64bit, take a object and get a int for quick compare | ||
+ | * hashlib (built-in, need import) 128bit+ and more option like md5, sha for more secure and digital signiture | ||
+ | ==== system info modules | ||
- | | obj_transform | .worldMatrix | + | * getpass (built-in, user name) <code python> |
+ | * glob (built-in, wild char file list) <code python> | ||
+ | # get all file named prefix variable _v001 like ma/mb file</ | ||
+ | * stat (built-in, file permission) <code python> | ||
+ | # lock down file for user, group, other readonly | ||
+ | os.chmod(filePath, | ||
+ | * time (built-in, time related) <code python> | ||
- | ===== Prefix remover ===== | + | ==== system command modules |
- | | + | |
- | * python | + | * subprocess.Popen don't block your main program |
- | mesh_list = cmds.ls('sw2_*',type='mesh',l=1) | + | </ |
- | for each_mesh in mesh_list: | + | * subprocess.call will block the main program till call finish, equals <code python> |
- | info = each_mesh.rsplit('|',1) | + | subprocess.call(r' |
- | if len(info )==2: | + | subprocess.Popen([r' |
- | cmds.rename(each_mesh, info[-1].replace('sw2_','' | + | os.system(r'notepad.exe D: |
- | else: | + | </ |
- | print(each_mesh) | + | * subprocess.check_call will raise an exception |
- | | + | * both return result |
- | obj_list | + | * Ask System to Open Folder <code python> |
- | for each_obj | + | if os.path.isdir(folderPath): |
- | | + | |
- | if len(info)==2: | + | try: |
- | | + | subprocess.check_call(['open', '--', |
- | else: | + | |
- | | + | pass # handle errors |
+ | except OSError: | ||
+ | pass # executable not found | ||
+ | | ||
+ | try: | ||
+ | subprocess.check_call(['xdg-open', | ||
+ | | ||
+ | pass # handle errors in the called executable | ||
+ | | ||
+ | pass # executable not found | ||
+ | elif sys.platform in [' | ||
+ | try: | ||
+ | subprocess.check_call(['explorer', | ||
+ | | ||
+ | pass # handle errors in the called executable | ||
+ | | ||
+ | pass # executable not found | ||
</ | </ | ||
- | + | | |
- | ===== alias attribute ===== | + | appName |
- | + | info=subprocess.Popen(' | |
- | | + | out, err = info.communicate() |
- | alias_target_weight_list | + | #out = str(out) # for py3 byte convert |
- | target_list = alias_target_weight_list[::2] # all the alias target names | + | out = out.decode(' |
- | weight_attr_list = alias_target_weight_list[1::2] # all the weight attribute list | + | result = [ x for x in out.replace('"',' |
+ | if len(result)> | ||
+ | print(' | ||
+ | else: | ||
+ | subprocess.Popen([r'D:\path_to_app\googledrivesync.exe' | ||
</ | </ | ||
+ | * ask System to open file <code python> | ||
+ | if sys.platform in [' | ||
+ | os.startfile(filePath) | ||
+ | elif sys.platform == ' | ||
+ | os.open(filePath) | ||
+ | elif sys.platform == ' | ||
+ | os.xdg-open(filePath) | ||
+ | </ | ||
+ | * launch a subprocess to run a Bat file or python file in desktop <code python> | ||
+ | subprocess.Popen([r' | ||
+ | subprocess.Popen([r' | ||
+ | # force it to open in new console window instead of inside parent console window | ||
+ | subprocess.Popen([r' | ||
+ | # if above method failed because PATH or PYTHONPATH issue, especially when call like above in a mixed python environment like Maya, | ||
+ | # you need to use a clean copy of environment, | ||
+ | env2={} | ||
+ | env2[' | ||
+ | D: | ||
+ | D: | ||
+ | D: | ||
+ | ''' | ||
+ | env2[' | ||
+ | D: | ||
+ | D: | ||
+ | D: | ||
+ | D: | ||
+ | D: | ||
+ | D: | ||
+ | D: | ||
+ | D: | ||
+ | D: | ||
+ | ''' | ||
- | ===== weight ===== | + | subprocess.Popen([r' |
- | + | subprocess.Popen([r' | |
- | * [[graphic:python: | + | </ |
+ | * subprocess a console app and get return, and optionally hide the pop up console when call < | ||
+ | # hide popup console for win platform only | ||
+ | startupinfo = subprocess.STARTUPINFO() | ||
+ | startupinfo.dwFlags | ||
+ | # startupinfo.wShowWindow = subprocess.SW_HIDE #(optional) | ||
- | * apply single vtx weight to whole geo, good for unify same weight on attachment type geos on another surface <code python> | + | info=subprocess.Popen( '{0}'.format(app_path), |
- | attach_vtx_id | + | out, err = info.communicate() |
- | cur_skin = cmds.skinCluster(cur_bones, cur_geo, tsb=1, n=cur_geo+'_skinCluster')[0] | + | out = out.decode('ascii') #str(out) # for py3 byte convert |
- | cmds.setAttr( cur_skin + ".skinningMethod" | + | |
- | vtx_weight | + | |
- | cmds.skinPercent(cur_skin, cur_geo+'.vtx[:]', tv=zip(cur_bones, | + | |
</ | </ | ||
+ | ==== string related ==== | ||
- | | + | |
- | cur_vtx_list = cmds.ls(sl=1, fl=1) | + | # capture group and non-capture group: |
- | if len(cur_vtx_list)>0: | + | # http:// |
- | if '.vtx[' | + | re.findall(' |
- | | + | re.findall(' |
- | | + | # since we only need the prefix, the Msg group is to test exisits |
</ | </ | ||
- | * select only vtx affected the defined bone <code python> | ||
- | cmds.skinCluster(cur_skin, | ||
- | </ | ||
- | ===== Blendshape ===== | ||
- | {{graphic: | + | ==== compile and de-compile module ==== |
- | | + | **cx_Freeze**: compile |
- | | + | * After compile, the only files need to do partial update each time is (exe + library.zip) |
- | | + | |
- | * 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]**: | + | |
+ | **PyInstaller**: | ||
- | * vtx general info code< | + | < |
- | cmds.getAttr(' | + | # cmd install |
+ | python -m pip install pyinstaller | ||
- | cmds.polyMoveVertex(localTranslate=(0, 0, 0)) # clear local values | + | # compile, cd to your py file first, you may need copy your supporting folder to dist path after compile |
- | </ | + | python.exe -m PyInstaller MyApp.py |
- | * blendshape per target per vtx weight info cmds way < | + | |
- | cmds.getAttr(cur_bs+' | + | |
- | # target 1 : weight per vtx | + | # no terminal version |
- | cmds.getAttr(' | + | python.exe -m PyInstaller MyApp.py --noconsole |
- | cmds.getAttr(' | + | # set app binary icon |
- | # target 2 : weight per vtx | + | python.exe -m PyInstaller MyApp.py --icon=icons/ |
- | cmds.getAttr(' | + | |
- | # set target 2: weight per vtx | + | # exclude tk and tcl library |
- | cmds.setAttr(' | + | python.exe -m PyInstaller MyApp.py --icon=icons/ |
- | # get current loaded paint weight data | + | # for mac, if failed to build on pyconfig.h |
- | cmds.getAttr(cur_bs+' | + | # - fix pyconfig.h; by cmd: sudo touch / |
+ | python.exe -m PyInstaller MyApp.py --icon=icons/ | ||
- | # get current base (the final mixing global per vtx weight) | + | python.exe -m PyInstaller MyApp.py --icon=icons/MyApp.icns -w --onefile |
- | cmds.getAttr(cur_bs+' | + | |
- | </code> | + | |
- | {{: | + | # cmd+i to check app info, and drag icns file to app/folder icon logo to set the icon |
- | * API: **MFnBlendShapeDeformer** <code python> | + | # manually turn folder into app, just add .app behind folder name, folder name must be same as main binary file |
- | 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()) | + | |
- | bs_base_obj = obj_list[0] | + | |
- | </code> | + | |
- | * API: **weight slider attribute and alias target attribute name** <code python> | + | |
- | nWeights = bs_fn.numWeights() | + | |
- | 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=[] | + | # when run result mac app on other computer, if security stop it run like "app is damaged", |
- | w_plug = bs_fn.findPlug(' | + | xattr -cr "/ |
- | 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) | + | |
</ | </ | ||
- | | + | **uncompyle2**: convert pyc to py (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()): | + | https://github.com/ |
- | 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 | + | * code example <code python># in the init file |
- | '' | + | main('/ |
- | 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> | + | ====== Python Important Modules ====== |
- | inputTargetGroupArrayPlug | + | ===== pip ===== |
- | target_weight_data = [] | + | * pip is a quick python module get and installer module |
- | for index in indexList: # i=0 | + | * check version < |
- | inputTargetGroupPlug = inputTargetGroupArrayPlug.elementByLogicalIndex(index) # inputTargetGroupPlug.name() | + | * update it before install other module, make sure you already in python' |
- | | + | # or |
- | | + | python -m pip install --upgrade pip |
- | for j in range(targetWeightsArrayPlug.numElements()): | + | |
- | | + | |
- | logicalIndex = cur_plug.logicalIndex() | + | |
- | target_weight_list[logicalIndex] = cur_plug.asFloat() | + | |
- | target_weight_data.append(target_weight_list) | + | |
</ | </ | ||
+ | ===== re ===== | ||
- | * 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** < | + | ^ all data file | < |
- | target_data = [] | + | ^ all maya file | < |
- | def geoObject_to_posList_OpenMaya(geo_object): | + | * variable name case conversion |
- | | + | |
- | | + | |
- | | + | def convert(name, |
- | while not geo_iter.isDone(): | + | |
- | | + | |
- | pos_list.append([pnt.x, pnt.y, pnt.z]) | + | #get2HTTPResponseCode > get2_HTTP_Response_Code |
- | geo_iter.next() | + | |
- | return pos_list | + | def convert2(name, gap='_'): |
- | + | | |
- | for index in indexList: # i = 0 | + | |
- | target_object_data = [] | + | |
- | + | ||
- | target_list = om.MObjectArray() | + | |
- | bs_fn.getTargets(bs_base_obj, index, target_list) | + | |
- | # target_list.length() #1, ideally one blend target only have one object | + | |
- | target_cnt = target_list.length() | + | |
- | if target_cnt > 1: | + | |
- | | + | |
- | 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) | + | |
- | bs_fn.setWeight(n, 0) | + | |
- | bs_fn.setWeight(n, 1) | + | |
- | | + | |
- | | + | |
- | | + | |
- | 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, prefix='tmp'): | + | |
- | | + | |
- | cur_loc = cmds.spaceLocator(n='loc_{0}_{1}' | + | |
- | cmds.xform(cur_loc, t=pos) | + | |
- | + | ||
- | for i,targets in enumerate(target_data): | + | |
- | pos_to_loc(targets[0], 'tgt'+str(i)) | + | |
</ | </ | ||
- | ====== Animation ====== | + | ===== requests |
- | **Import and Export animation with atom plugin** | + | |
- | * import | + | |
- | # select controls to load animation, then | + | |
- | cmds.file("D:/path_to_file/pose_001.atom" | + | * **Single Get web link content** |
+ | import requests | ||
+ | result = requests.get('http://my_site/ | ||
+ | print(result.text) | ||
+ | </ | ||
+ | * **Single post web link content with login info** <code python> | ||
+ | result = requests.post(' | ||
+ | print(result.text) | ||
</ | </ | ||
- | **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<code python> | + | with requests.Session() as session: |
- | cmds.file("D:/path_to_scene/animation_node_only_v001.ma" | + | # first time login, |
+ | reply = session.post(' | ||
+ | print(reply.text) | ||
+ | # read rest login-required pages | ||
+ | reply = session.get(' | ||
+ | print(reply.text) | ||
</ | </ | ||
- | | + | |
- | * method 2: select animated objects, then just export animation | + | print(session_object.headers) # user sending header info |
- | cmds.file(" | + | print(reply_object.headers) # server replying header info |
</ | </ | ||
- | | + | |
+ | result.content # is html in bytes | ||
+ | result.text # is html in unicode text | ||
+ | </ | ||
+ | * disable non secure cert warning <code python> | ||
+ | ===== PIL and Pillow===== | ||
+ | * PIL, Python Imaging Library (Fork) = Pillow, install the lib < | ||
+ | * python code <code python> | ||
+ | from PIL import Image, ImageDraw, ImageOps | ||
+ | </ | ||
- | **Get all Animation Curve** | + | ===== BeautifulSoup |
- | * note: it may include time value driven drive key curve, so need check< | + | |
- | for animCurve_type in [' | + | |
- | curve_list | + | |
- | + | ||
- | for each_curve in curve_list: | + | |
- | cur_connection_out | + | |
- | destinations | + | |
- | if len(destinations) | + | |
- | # optional process for unconnected animation node | + | |
- | cmds.delete(each_curve) | + | |
- | else: | + | |
- | cur_node_attr | + | |
- | cur_node, cur_attr | + | |
- | # 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 | + | * ref: https:// |
- | * general clean static animation | + | * parser is a html interprater, as not all html is clean format and tidy, some parser can auto fix some html tag miss match. |
- | cmds.delete(cur_node_attr, staticChannels=1, | + | * example code <code python> |
+ | from bs4 import BeautifulSoup | ||
+ | soup = BeautifulSoup(html, ' | ||
+ | # find all title link text | ||
+ | link_list | ||
+ | name_list = [x.string for x in link_list ] # text element extract | ||
+ | link_list[0].attrs[' | ||
</ | </ | ||
- | **Bake Animation** | ||
- | * note: make sure all channel unlocked | ||
- | * bake animation curve< | ||
- | * bake object <code python> | ||
- | ====== Animation Cache====== | + | ===== selenium |
- | * check abc node inside maya, and its cache path <code python> | + | * you need install 2 things to get it working: |
- | # get all abc node | + | - Python module: |
- | all_abc_nodes | + | - for py2.7: https:// |
- | # check current file path | + | - for py3.x: https:// |
- | for each in all_abc_nodes: | + | - system platform dependent webdriver binary: http:// |
- | print('------------------') | + | - for chrome browser automate: go above link and find 3rd binding " |
- | | + | - then you can load that drive binary into python selenium module object to start <code python> |
- | | + | from selenium import webdriver |
- | | + | driver |
+ | driver.get('http:// | ||
+ | driver.implicitly_wait(20) | ||
+ | driver.get_screenshot_as_file(r'D:\testshot.png') | ||
+ | driver.quit() | ||
</ | </ | ||
- | * to update abc cache path, (default maya only change the " | + | * get element |
- | all_abc_nodes = cmds.ls(type='AlembicNode' | + | # get css selector by right click browser' |
- | for each in all_abc_nodes: | + | driver.find_element_by_css_selector('.jsb > center:nth-child(1) > input:nth-child(1)') |
- | print(' | + | # path method |
- | print(' | + | driver.find_element_by_xpath('// |
- | cur_abc_path = cmds.getAttr(each+' | + | |
- | cmds.setAttr('{0}.fns' | + | |
</ | </ | ||
- | ====== Camera | + | * element operation |
+ | * element.get_attribute(' | ||
+ | * element.get_attribute(' | ||
+ | * element.text | ||
+ | * element.click() | ||
+ | * element_input.send_keys() | ||
+ | * element_form.submit() | ||
+ | * driven_browser.back(), | ||
- | * get 2D screen space from 3D position <code python> | + | * additional tool: |
- | import maya.OpenMayaUI as omui | + | * selenium ide firefox - a all-in-one test tool for it: https:// |
- | import maya.OpenMaya as om | + | * tutorial: http://www.seleniumeasy.com/ |
- | import maya.cmds as cmds | + | ===== PyAutoGUI ===== |
- | obj = cmds.ls(sl=1, fl=1)[0] # get first vtx or object, assume you have selected sth | + | |
- | pos = om.MPoint(obj) | + | |
- | </ | + | |
+ | * GUI cross-platform GUI interaction automation | ||
+ | * ref: https:// | ||
+ | * tutorial: https:// | ||
+ | * full list of library required in download page: https:// | ||
- | * get active viewport camera name <code python> | + | * mouse control |
- | dagPath = om.MDagPath() # dag path holder | + | * click(), doubleClick(), |
- | omui.M3dView().active3dView().getCamera(dagPath) # get info | + | * click([x,y]) |
- | cam_name | + | * moveTo(x,y [, |
+ | * moveRel(x_offset, y_offset [, | ||
+ | * dragTo(x,y [,duration=sec]) | ||
+ | * position(), size(), | ||
+ | * displayMousePosition() <code python> | ||
+ | # note, that displayMousePosition require 3rd party library | ||
+ | # * PIL (Py2.x only, adapted by Pillow) > PIL.ImageGrab > pyscreenze | ||
+ | http:// | ||
+ | https:// | ||
</ | </ | ||
- | * get viewport width and height in monitor pixel unit <code python> | + | * keyboard control |
- | omui.M3dView().active3dView().portWidth() | + | * typewrite('Text goes here', [, |
- | omui.M3dView().active3dView().portHeight() | + | * press(' |
+ | * hotkey(' | ||
+ | * note: best work with time.sleep(5) to give 5 sec for pc to respond | ||
+ | |||
+ | * image recognition | ||
+ | * pixel(x,y) # return RGB | ||
+ | * screenshot([filename]) # return PIL,Pillow Image object [and save file] | ||
+ | * locateOnScreen(imageFilename) # returns (x,y, w, h) or None | ||
+ | * linux: scrot | ||
+ | * example code <code python> | ||
+ | import pyautogui as pui | ||
+ | pos = pui.locateOnScreen(' | ||
+ | pui.click( (pos[0]+pos[2]*0.5 , pos[1]+pos[3]*0.5) | ||
</ | </ | ||
- | * Make Move tool aim to camera, or parallel to camera; Good for matchmove <code python> | + | ===== SikuliX |
- | ''' | + | |
- | ========================================== | + | |
- | 2019.05.21 | + | |
- | by Shining Ying | + | |
- | shiningdesign[with]live.com | + | |
- | make move tool aim to camera (with object/vtx selection) | + | * SikuliX automates anything you see on the screen of your desktop computer running Windows, Mac or some Linux/Unix. Link: http://sikulix.com |
- | ========================================== | + | * tutorial: https://www.youtube.com/watch?v=VdCOg1bCmGo |
- | ''' | + | |
- | import maya.cmds as cmds | + | |
- | import maya.OpenMaya as om | + | |
- | import math | + | |
- | def getLocalVecToWorldSpaceAPI(obj, | + | |
- | # ref: http://forums.cgsociety.org/ | + | |
- | # 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 | + | |
- | 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 | + | ===== pyautoit ===== |
- | #fixCam= ' | + | * a python binding for AutoIt tool, a windows only automation software |
- | fixCam = '' | + | * https:// |
- | cmds.setToolTo(' | + | |
- | cur_cam = cmds.modelEditor(cmds.playblast(activeEditor=1), q=1, camera=1) | + | * API level interaction |
- | if 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 | + | ===== pywinauto |
- | tmp_loc | + | |
- | cmds.xform(tmp_loc, | + | |
- | # align | + | |
- | alignObjectToVector(tmp_loc, | + | |
- | rot_val | + | |
- | cmds.delete(tmp_loc) | + | |
- | # back to old selection | + | |
- | cmds.select(cur_selection) | + | |
- | # set tool | + | |
- | cmds.setToolTo(' | + | |
- | cmds.manipMoveContext( ' | + | |
+ | * a mostly windows only GUI automation module, with mouse, keyboard support on linux | ||
+ | * API level interaction | ||
- | ''' | + | ===== pywin32 - win32com |
- | ========================================== | + | |
- | 2019.05.21 | + | |
- | by Shining Ying | + | |
- | shiningdesign[with]live.com | + | |
- | make move tool paralell to camera (with object/vtx selection) | + | * tip and ref: |
- | ========================================== | + | * http:// |
- | ''' | + | * http:// |
+ | * COMbrowser: win32com\client\combrowse.py | ||
- | cur_selection = cmds.ls(sl=1) | + | * install and restart computer to get dll reg refreshed < |
+ | https:// | ||
+ | (or) python -m pip install pypiwin32</ | ||
- | #fixCam= ' | + | * get windows explorer in taskbar order, return [(title, hwnd int, addressbar url, [top, |
- | fixCam = '' | + | import win32com |
- | cmds.setToolTo(' | + | from win32com import client |
- | cur_cam | + | def get_explorer_list(): |
- | if fixCam !='': | + | |
- | | + | |
- | print(' | + | win_list |
- | # loc | + | |
- | tmp_loc = cmds.spaceLocator(n=' | + | |
- | # align | + | info = [] |
- | cmds.parentConstraint(cur_cam, | + | info.append(win.LocationName) # title name |
- | rot_val = cmds.xform(tmp_loc, q=1, ro=1) | + | |
- | cmds.delete(tmp_loc) | + | info.append(win.LocationURL) # address bar url, eg. file:/// |
- | # back to old selection | + | info.append([win.Top, win.Left, win.width, win.height]) # win pixel position |
- | cmds.select(cur_selection) | + | info.append(win.visible) # visible |
+ | info.append(win) # com object | ||
+ | | ||
+ | return result_list | ||
+ | ''' | ||
+ | [' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ''' | ||
+ | </ | ||
+ | * get selected item in window explorer <code python> | ||
+ | # ref: | ||
+ | # https:// | ||
+ | # https:// | ||
- | # set tool | + | def get_selection_list(win): |
- | cmds.setToolTo(' | + | |
- | cmds.manipMoveContext( ' | + | |
+ | folderItems = win.Document.SelectedItems() | ||
+ | for i in range(folderItems.Count): | ||
+ | tmp_info | ||
+ | tmp_info.append(folderItems.Item(i).path) # C: | ||
+ | tmp_info.append(folderItems.Item(i).name) | ||
+ | tmp_info.append(folderItems.Item(i).isFolder) | ||
+ | tmp_info.append(folderItems.Item(i).size) | ||
+ | tmp_info.append(folderItems.Item(i).type) | ||
+ | item_list.append(tmp_info) | ||
+ | return item_list | ||
</ | </ | ||
- | ====== Render Related ====== | + | * explorer window COM object control <code python> |
+ | # control the window COM object to go to a location | ||
+ | win.Navigate(' | ||
+ | win.Busy # a check for still in processing | ||
- | * the ' | + | # check visibile |
- | cmds.select(' | + | win.FullScreen |
- | + | ||
- | # get current scene name | + | |
- | cur_scene = cmds.file(q=1, sn=1, shn=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(' | + | |
</ | </ | ||
- | | + | |
- | image_ext = ' | + | # get system dir and |
- | cmds.setAttr("defaultRenderGlobals.imageFormat", 32) # png | + | oShell = win32com.client.Dispatch("Wscript.Shell") |
+ | print(oShell.SpecialFolders(" | ||
- | scene_path = os.path.dirname(cmds.file(q=True, | + | # change drive letter |
- | dir_path_info = [scene_path, | + | # ref: http:// |
- | render_root_path = os.path.join(*dir_path_info ).replace(' | + | |
- | cmds.setAttr(" | + | # create windows shortcut and read it |
- | # set as name.#### | + | shortcut = shell.CreateShortCut("t:\\test.lnk") |
- | cmds.setAttr("defaultRenderGlobals.animation",1) # render multi frame | + | shortcut.Targetpath = "D:\\tmp" |
- | cmds.setAttr("defaultRenderGlobals.outFormatControl",0) # use ext | + | shortcut.save() |
- | cmds.setAttr(" | + | |
- | cmds.setAttr(" | + | |
- | cmds.setAttr(" | + | |
- | # set width | + | shortcut = shell.CreateShortCut("t:\\test.lnk") |
- | cmds.setAttr("defaultResolution.w",1920) # use padding | + | print(shortcut.Targetpath) |
- | cmds.setAttr(" | + | |
- | </ | + | |
- | * it also contains all the Deadline render job setting | + | |
- | * get absolution render output path (including dynamic changing output path) <code python> | + | # get single screen resolution and total screen |
- | cmds.renderSettings(firstImageName=1, fullPath=1) | + | # ref: https:// |
+ | import win32api | ||
+ | w=win32api.GetSystemMetrics(0) # primary screen width | ||
+ | h=win32api.GetSystemMetrics(1) # primary screen height | ||
+ | vw = win32api.GetSystemMetrics(78) # total screen area width | ||
+ | vh = win32api.GetSystemMetrics(79) # total screen area height | ||
+ | print(' | ||
+ | print(' | ||
</ | </ | ||
- | | + | ===== ctypes ===== |
- | cmds.workspace(q=1, rootDirectory=True) # root proj dir, holding the scene/ | + | |
- | cmds.workspace(q=1, dir=1) # scene folder full-path | + | |
+ | * ref: http:// | ||
+ | * my doc on windows API and DLL functions [[devwiki: | ||
+ | * (method 1) get all windows explorer windows in Z order, return [(title, class, [x,y,w,h], hwnd int)] <code python> | ||
+ | def get_win_order_all(): | ||
+ | # ctype: grab folder windows in z order, return [(title, class, [x,y,w,h], hwnd int)] | ||
+ | from ctypes import wintypes | ||
+ | order_list = [] | ||
+ | result_list = [] | ||
+ | top = ctypes.windll.user32.GetTopWindow(None) | ||
+ | if not top: | ||
+ | return order_list | ||
+ | length | ||
+ | buff = ctypes.create_unicode_buffer(length + 1) | ||
+ | ctypes.windll.user32.GetWindowTextW(top, buff, length + 1) | ||
+ | class_name | ||
+ | ctypes.windll.user32.GetClassNameA(top, | ||
+ | r=ctypes.wintypes.RECT() | ||
+ | ctypes.windll.user32.GetWindowRect(top, | ||
+ | | ||
+ | if isinstance(class_name.value, str): | ||
+ | result_list.append( [buff.value, | ||
+ | else: | ||
+ | result_list.append( [buff.value, | ||
+ | order_list.append(top) | ||
+ | while True: | ||
+ | next = ctypes.windll.user32.GetWindow(order_list[-1], 2) # win32con.GW_HWNDNEXT | ||
+ | if not next: | ||
+ | break | ||
+ | length | ||
+ | buff = ctypes.create_unicode_buffer(length + 1) | ||
+ | ctypes.windll.user32.GetWindowTextW(next, | ||
+ | class_name = ctypes.create_string_buffer(200) | ||
+ | ctypes.windll.user32.GetClassNameA(next, | ||
+ | r=ctypes.wintypes.RECT() | ||
+ | ctypes.windll.user32.GetWindowRect(next, | ||
+ | | ||
+ | if isinstance(class_name.value, | ||
+ | result_list.append( [buff.value, | ||
+ | else: | ||
+ | result_list.append( [buff.value, | ||
+ | order_list.append(next) | ||
+ | return result_list | ||
- | # scene path and scene name | + | full_list = get_win_order_all() |
- | cmds.file(q=True, sceneName=True) | + | visible_list = [x for x in full_list if ctypes.windll.user32.IsWindowVisible(x[-1])] |
- | cmds.file(q=True, sceneName=True, | + | # visible list is actually more useful, 10+ instead 100+ items |
- | # segment subpath | + | explorer_list |
- | cmds.workspace(fr=1,q=1) # list of segment sub names | + | firefox_list |
- | cmds.workspace(fr=['images', | + | |
- | cmds.workspace(fr=[' | + | |
- | cmds.workspace(fileRuleEntry='depth') # get the segment path | + | |
</ | </ | ||
- | * mel to use python write Pre-Render-Mel, | + | * (method 2) get all (visible) (windows explorer) windows in Z order, return [(title, class, hwnd as pointer)]<code python> |
- | import datetime; | + | def get_win_order_v2(): |
- | time_text = datetime.datetime.now().strftime(' | + | # ctypes |
- | import maya.cmds as cmds; | + | # ref: https:// |
- | out_path | + | # info (title, class_name, hwnd) |
- | scene_path | + | window_list = [] |
- | parent_dir = os.path.dirname(out_path); | + | # propare windows list passing |
- | info_file=parent_dir+' | + | def window_info_process(hwnd, lParam): |
- | os.path.exists(os.path.dirname(info_file)) or os.mkdir(os.path.dirname(info_file)); | + | if ctypes.windll.user32.IsWindowVisible(hwnd): |
- | f = open(info_file, | + | |
- | f.writelines([scene_path,' | + | |
- | f.close(); | + | |
- | print(' | + | |
+ | | ||
+ | t_class_name = class_name.value | ||
+ | if not isinstance(t_class_name, | ||
+ | t_class_name = class_name.value.decode(' | ||
+ | if t_class_name == 'CabinetWClass': | ||
+ | | ||
+ | | ||
+ | procObj = ctypes.WINFUNCTYPE(ctypes.c_bool, | ||
+ | # execute it | ||
+ | ctypes.windll.user32.EnumWindows(procObj(window_info_process), 0) | ||
+ | return window_list | ||
</ | </ | ||
- | * mel version | + | * (method 3) Break down ctypes hwnd, pid, process_handle into functions modules |
- | python(" | + | ''' |
- | </ | + | ref: http:// |
- | * 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%%''' | + | Window handle → process ID: GetWindowThreadProcessId |
- | # bake scene name into mel cmd, as render will create a another tmp scene file, which cause get scene name different in script | + | Process ID → process handle: OpenProcess |
- | scene_path | + | Process handle → executable |
- | pre_mel | + | Close the process handle: CloseHandle |
- | # set as per render layer instead pre scene, since each folder has different name | + | ref: http://www.mengwuji.net/forum.php?mod=viewthread& |
- | cmds.setAttr(" | + | Windows 2000 = GetModuleFileName() |
- | </ | + | Windows XP x32 = GetProcessImageFileName() |
- | * preMel: before maya kick render (postMel) | + | |
- | * preRenderLayerMel: | + | |
- | * preRenderMel: | + | |
+ | ref: | ||
+ | uisoup | ||
+ | https:// | ||
+ | https:// | ||
+ | ''' | ||
- | * defaultArnoldRenderOptions: | + | def get_win_pid(hwnd): |
- | * the arnold render setting object, contains all render setting for Arnold render | + | |
+ | ctypes.windll.user32.GetWindowThreadProcessId(hwnd, | ||
+ | return cur_pid | ||
+ | def get_process_path(pid): | ||
+ | PROCESS_QUERY_INFORMATION = 0x0400 | ||
+ | max_unicode_path = 32767 * 2 + 200 | ||
+ | file_path_buffer = ctypes.create_unicode_buffer(max_unicode_path) | ||
+ | length_path = ctypes.c_ulong(max_unicode_path) | ||
+ | pHandle = ctypes.windll.kernel32.OpenProcess(PROCESS_QUERY_INFORMATION, False, pid) # won't get admin process if not as admin | ||
+ | retval = ctypes.windll.kernel32.QueryFullProcessImageNameW(pHandle, | ||
+ | if retval == 0: | ||
+ | raise Exception(' | ||
+ | # | ||
+ | return file_path_buffer.value | ||
+ | def get_win_path(hwnd): | ||
+ | return get_process_path(get_win_pid(hwnd)) | ||
- | ====== | + | def get_win_rect(hwnd): |
+ | from ctypes import wintypes | ||
+ | r=ctypes.wintypes.RECT() | ||
+ | ctypes.windll.user32.GetWindowRect(hwnd, | ||
+ | return [r.left, | ||
+ | def get_win_title(hwnd): | ||
+ | length | ||
+ | title_buffer | ||
+ | ctypes.windll.user32.GetWindowTextW(hwnd, | ||
+ | return title_buffer.value | ||
+ | # AE CS6: AE_CApplication_11.0 | ||
+ | def get_win_class(hwnd): | ||
+ | class_name | ||
+ | ctypes.windll.user32.GetClassNameA(hwnd, | ||
+ | t_class_name | ||
+ | if not isinstance(t_class_name, | ||
+ | t_class_name | ||
+ | return t_class_name | ||
+ | def is_win_top(hwnd): | ||
+ | top_hwnd | ||
+ | return hwnd == top_hwnd | ||
- | * cross platform maya scene directory mapping and management | + | def get_win_list(visible=1, |
- | | + | |
- | | + | |
- | + | | |
- | * get mel system level operation and maya launch directory <code python> | + | |
- | import maya.mel as mel | + | if orderMethod: |
- | print(mel.eval(' | + | |
- | </ | + | |
- | + | if not top: | |
- | * Publish workflow | + | |
- | | + | |
- | * Project Dir <code python> | + | |
- | * File New <code python> | + | |
- | cmds.file(f=1,new=1) | + | while True: |
- | </ | + | |
- | * File Open <code python> | + | if not next: |
- | cmds.file(filePath, | + | break |
- | 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, f=1) | + | |
- | # get reference used file name - myfile.ma | + | |
- | cur_name = cmds.referenceQuery(cur_node, f=1, shn=1) | + | |
- | </ | + | |
- | * delete unused node <code python> | + | |
- | * checking <code python> | + | |
- | cmds.file(q=1, sn=1) # filepath | + | |
- | cmds.file(q=1, sn=1, un=1) # filepath in variable format | + | |
- | cmds.file(q=1, sn=1, shn=1) # filename | + | |
- | cmds.file(q=1, sn=1, wcn=1) # withoutCopyNumber | + | |
- | </ | + | |
- | * check file change and save <code python> | + | |
- | cmds.file(q=1, modified=1) | + | |
- | cmds.file(save=1) | + | |
- | </ | + | |
- | * saving as <code python> | + | |
- | # you need to rename first before you can save as | + | |
- | if ext.lower() == " | + | |
- | | + | |
- | | + | |
- | save_done = 1 | + | |
- | elif ext.lower() == ".mb": | + | |
- | cmds.file(rename = filePath) | + | |
- | cmds.file(f=1, save=1, type=" | + | |
- | save_done = 1 | + | |
- | </ | + | |
- | * export <code python> | + | |
- | if format_info == " | + | |
- | if self.uiList[' | + | |
- | cmds.file(filePath, f=1, type=" | + | |
- | else: | + | |
- | cmds.file(filePath, | + | |
- | else: | + | |
- | if self.uiList[' | + | |
- | cmds.file(filePath, f=1, type=" | + | |
else: | else: | ||
- | | + | |
+ | def window_info_process(hwnd, lParam): | ||
+ | visible_info | ||
+ | win_list.append( [ hwnd, visible_info ]) #hwnd: as LP_c_long | ||
+ | return True | ||
+ | # ref: http:// | ||
+ | # function 1: this will get as LP_c_long as hwnd | ||
+ | # | ||
+ | # function 2: this will get int as hwnd like other win32api function | ||
+ | procObj | ||
+ | # execute it | ||
+ | ctypes.windll.user32.EnumWindows(procObj(window_info_process), | ||
+ | if visible: | ||
+ | return [x for x in win_list if x[1]] | ||
+ | return win_list | ||
</ | </ | ||
- | * mel shelf button< | + | * ctypes window operation by hwnd< |
- | global string $gShelfTopLevel; | + | # my_hwnd can be a int or a ctypes int pointer |
- | string $currentShelf = `tabLayout -query -selectTab $gShelfTopLevel`; | + | # full function ref: http://www.jasinskionline.com/windowsapi/ref/funca.html |
- | 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 " | + | # bring window to bring |
+ | ctypes.windll.user32.SetForegroundWindow(my_hwnd) | ||
- | string $curBtn = `shelfButton -parent $currentShelf -c $cmd -mi " | + | # close window |
- | string $curPop = `popupMenu -parent $curBtn -button 1 -ctl 1`; // sh, alt, ctl | + | WM_CLOSE |
- | menuItem -p $curPop -l "A A A" -c "print A"; | + | ctypes.windll.user32.PostMessageA(my_hwnd, WM_CLOSE, 0, 0) |
- | 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 | + | |
- | shelves = cmds.tabLayout(shelf_holder, | + | |
- | cmds.shelfTabLayout(e=1, selectTabIndex=3, shelf_holder) # change active tab by index | + | |
- | cmds.shelfTabLayout(shelf_holder, e=1, selectTab = shelf_name) # change active tab by name | + | |
- | cmds.shelfLayout(shelf_name, | + | |
- | shelf_name | + | # get window x,y,w,h |
- | mel.eval(' | + | from ctypes import wintypes |
+ | r=ctypes.wintypes.RECT() | ||
+ | ctypes.windll.user32.GetWindowRect(my_hwnd, | ||
+ | print( [r.left, | ||
- | cmds.saveAllShelves(shelf_holder) # save all shelfs | + | # move window by x,y,w,h and refresh |
+ | ctypes.windll.user32.MoveWindow(my_hwnd, 0, 0, 760, 500, True) | ||
</ | </ | ||
+ | * mouse, keyboard operation <code python> | ||
+ | # get mouse position | ||
+ | class POINT(ctypes.Structure): | ||
+ | _fields_ = [(" | ||
+ | pt = POINT() | ||
+ | ctypes.windll.user32.GetCursorPos(ctypes.byref(pt)) | ||
+ | print(' | ||
- | * 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. | + | ctypes.windll.user32.SetCursorPos(1200, 850) # move cursor to 1200,850 (x,y) |
- | - maya.env method | + | ctypes.windll.user32.mouse_event(2, 0, 0, 0,0) # left mouse button down |
- | USER_SCRIPT_PATH1=C:/ | + | ctypes.windll.user32.mouse_event(4, |
- | USER_SCRIPT_PATH2=C:/ | + | |
- | MAYA_SCRIPT_PATH=$MAYA_SCRIPT_PATH; | + | |
- | MAYA_MODULE_PATH = D: | + | |
- | MAYA_PLUG_IN_PATH = D: | + | |
- | PYTHONPATH = D: | + | |
- | </ | + | |
- | - userSetup.py method | + | |
- | print(" | + | |
</ | </ | ||
- | - userSetup.mel method (load after UI loaded) (~\Documents\maya\version\scripts) | + | * get and set wallpaper in windows |
- | print(" | + | def getWallpaper(): |
- | $path1 | + | |
- | $path2 = "C:/ | + | # try: |
- | string $newScriptPath = $path1 + ";" + $path2 + ";" | + | # |
- | putenv | + | # except AttributeError, |
+ | # | ||
+ | | ||
+ | ctypes.windll.user32.SystemParametersInfoA(SPI_GETDESKWALLPAPER, | ||
+ | return wallpaper.value | ||
- | // or source other config script | + | def setWallpaper(wallpaperPath, |
- | $script1 | + | # Convert to bitmap if necessary |
- | $script2 | + | wallpaperImage = Image.open(wallpaperPath) |
- | source $script1; | + | if wallpaperImage.format != ' |
- | source $script2; | + | conversionPath = conversionPath or os.getenv(' |
+ | | ||
+ | | ||
+ | # Set wallpaper | ||
+ | wallpaperPath = ctypes.c_buffer(wallpaperPath) | ||
+ | ctypes.windll.user32.SystemParametersInfoA(win32con.SPI_SETDESKWALLPAPER, | ||
</ | </ | ||
- | - cmd or bash shell method | + | * wallpaper set (win7) the simple version |
- | // bash | + | import ctypes |
- | alias proj1=" | + | SPI_SETDESKWALLPAPER=20 |
- | // dos | + | ctypes.windll.user32.SystemParametersInfoA(SPI_SETDESKWALLPAPER, |
- | set MAYA_MODULE_PATH=R:/ | + | |
- | " | + | |
</ | </ | ||
- | + | | |
- | ===== Batch File Process Example ===== | + | |
- | + | * system operation <code python> | |
- | | + | # get screen count |
- | import maya.cmds as cmds | + | screen_cnt |
- | import maya.mel as mel | + | # get screen resolution |
- | import os | + | screen_x, screen_y |
- | root_path = r' | + | |
- | fbx_list = [x for x in os.listdir(root_path ) if x.endswith('.fbx') ] | + | |
- | 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, each_fbx), i=1) | + | |
- | | + | |
- | anim_list = cmds.ls(type=' | + | |
- | if len(anim_list)>0: | + | |
- | | + | |
- | | + | |
- | | + | |
- | new_geo = cmds.rename(mesh_list[0], | + | |
- | cmds.scale(0.1, | + | |
- | cmds.xform(new_geo, | + | |
- | cmds.makeIdentity(new_geo, | + | |
- | # material | + | |
- | | + | |
- | cmds.select(new_geo) | + | |
- | cmds.hyperShade(new_geo, | + | |
- | cmds.sets(new_geo, e=1, forceElement=mat_node+' | + | |
- | # texture | + | |
- | file_node = [x for x in cmds.ls(type=' | + | |
- | new_file = cmds.rename(file_node, prefix+str(id)+' | + | |
- | 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 ====== | ||
- | | + | **file and folder operation** |
- | mel_path | + | * show drive letter in use <code python> |
- | mel.eval('source "{0}"' | + | drive_list_data |
+ | mask_list = list( '{0:b}' | ||
+ | A_code = 65 # ascii code of letter A | ||
+ | drive_letter_in_use = [ chr(A_code+i) for i in range(len(mask_list)) if mask_list[i]==' | ||
</ | </ | ||
+ | * show file and select file in explorer (return 42)<code python> | ||
+ | my_filePath = u' | ||
+ | ctypes.windll.shell32.ShellExecuteW(None, | ||
+ | * open file property window <code python> | ||
+ | import time | ||
+ | import ctypes | ||
+ | import ctypes.wintypes | ||
- | * use python to ask maya to execute a python command in its variable scope <code python> | + | SEE_MASK_NOCLOSEPROCESS = 0x00000040 |
- | # this enable external python tool to ask maya to run a command with variable in maya's scope | + | SEE_MASK_INVOKEIDLIST |
- | cmds.python(' | + | |
- | </ | + | |
- | ====== Utility ====== | + | |
- | * open socket for external communication <code python> | + | class SHELLEXECUTEINFO(ctypes.Structure): |
- | # open port in maya | + | |
- | import maya.cmds as cmds | + | |
- | port = 7001 | + | |
- | open_already = 0 | + | |
- | try: | + | |
- | cmds.commandPort(name=":{0}".format(port), sourceType="python") | + | |
- | except: | + | |
- | | + | |
- | if open_already == 1: | + | ("nShow", |
- | print('Port already open') | + | |
+ | | ||
+ | | ||
+ | (" | ||
+ | (" | ||
+ | (" | ||
+ | (" | ||
+ | | ||
- | # send to Maya from external python | + | ShellExecuteEx |
- | import socket | + | ShellExecuteEx.restype |
- | host = '127.0.0.1' # localhost | + | |
- | port = 7001 | + | |
- | client = socket.socket(socket.AF_INET, | + | |
- | client.connect((host, | + | |
- | client.send(' | + | |
- | # close port in maya | + | sei = SHELLEXECUTEINFO() |
- | cmds.commandPort(name=":7001", close=1) | + | sei.cbSize = ctypes.sizeof(sei) |
+ | sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_INVOKEIDLIST | ||
+ | sei.lpVerb | ||
+ | sei.lpFile = ' | ||
+ | sei.nShow | ||
+ | ShellExecuteEx(ctypes.byref(sei)) | ||
+ | time.sleep(5) | ||
</ | </ | ||
- | ====== Plugin ====== | ||
- | * check and load python | + | * get clipboard data (ref: https:// |
- | if not cmds.pluginInfo(' | + | import ctypes |
- | | + | def get_clipboard_text(): |
- | </ | + | |
- | * 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> | + | ctypes.windll.user32.OpenClipboard(0) |
- | plugin_list_inUse | + | try: |
- | </ | + | if ctypes.windll.user32.IsClipboardFormatAvailable(1): |
- | * remove unknown plugin from Maya 2015 onwards <code python> | + | |
- | oldplugins | + | data_locked |
- | for plugin in oldplugins: | + | |
- | | + | value = text.value |
+ | ctypes.windll.kernel32.GlobalUnlock(data_locked) | ||
+ | | ||
+ | | ||
+ | ctypes.windll.user32.CloseClipboard() | ||
+ | # test | ||
+ | print(get_clipboard_text()) | ||
</ | </ | ||
- | ====== Maya Qt GUI Tool Development ====== | + | ===== twisted |
+ | * network communication: | ||
+ | * old version with python 2.7 32bit wheel: https:// | ||
- | * Qt python tool build guide tutorial links: | + | ===== django ===== |
- | * 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 < | + | * a python web server, install by < |
- | import struct; | + | |
- | * write PyQt4 GUI python code in Maya | + | ===== openpyxl===== |
- | - 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, | + | * a new excel reader, xlsx format, not old xls format, since now everyone use new excel version |
- | # PyQt4 windows binary link (you can install/ | + | * read excel as dictionary (1st key, rest data) <code python> |
- | # http://www.riverbankcomputing.com/ | + | def excel2Dict(filename): |
- | sys.path.append('D:\z_sys\App_CG\Python27x64\Lib\site-packages' | + | |
+ | # max_row = sheet.max_row | ||
+ | # max_column = sheet.max_column | ||
+ | wb = openpyxl.load_workbook(filename=filename, read_only=True) | ||
+ | records = [] | ||
+ | for sheet in wb.worksheets: | ||
+ | count = 0 | ||
+ | keys = [] | ||
+ | |||
+ | for row in sheet.rows: | ||
+ | temp = [] | ||
+ | for cell in row: | ||
+ | if count == 0: | ||
+ | keys.append(str(cell.value)) | ||
+ | else: | ||
+ | temp.append(str(cell.value)) | ||
+ | if count > 0: | ||
+ | records.append( dict(zip(keys, | ||
+ | else: | ||
+ | count += 1 | ||
+ | return records | ||
+ | </code> | ||
+ | * read excel as row data wo empty cell. <code python> | ||
+ | def excel2Data(filename): | ||
+ | # import openpyxl | ||
+ | wb = openpyxl.load_workbook(filename=filename, | ||
+ | | ||
+ | for sheet in wb.worksheets: | ||
+ | table_data = [] | ||
+ | for row in sheet.values: | ||
+ | row_data = [] | ||
+ | for cell in row: | ||
+ | if cell is not None: | ||
+ | row_data.append(cell) | ||
+ | if len(row_data)> | ||
+ | table_data.append(row_data) | ||
+ | sheet_data.append(table_data) | ||
+ | return sheet_data | ||
+ | </ | ||
+ | ====== Python by Reference or by Value ====== | ||
- | from PyQt4 import QtGui,QtCore</code> | + | * my experience found: "pure = assignment will not change original holder' |
- | * or use PySide | + | * more study on Pointer vs Reference: http:// |
+ | * Python variable are all reference, so pass reference to a function, the inside function holds copy of the reference, nothing outside affected, but if inside function reference modify, outside will be affected, since they all store value at same memory location(reference to that location): http:// | ||
+ | * http:// | ||
+ | * and another clear explanation of python reference vs c++ reference from c++ user by code: https:// | ||
+ | * here is my study experiment <code python> | ||
- | * explanation of above: PyQt binding vs PySide binding | + | # |
+ | # Example 1 | ||
+ | dict = {} | ||
+ | dict[' | ||
+ | for each in 'aa bb cc dd' | ||
+ | dict[' | ||
+ | list_aa = dict[' | ||
+ | for i in [1,2,3,4,5]: | ||
+ | list_aa.append(i) | ||
+ | print dict # affect both list_aa and dict | ||
+ | |||
+ | temp_list = dict[' | ||
+ | temp_list.append(22) # you are not get a copy of dict entry, but affect orginal dict as well | ||
+ | print temp_list | ||
+ | print dict # changed | ||
+ | # ref: http:// | ||
+ | temp_list2 = dict[' | ||
+ | temp_list2.append(55) | ||
+ | print temp_list2 | ||
+ | print dict # stay same | ||
- | ====== My Python Tool ====== | + | # |
+ | # Example 2 | ||
+ | locs = [ [1], [2] ] | ||
+ | for loc in locs: # passing by values !!! | ||
+ | loc = [] | ||
+ | print locs # stay same | ||
- | * shelf load python code <code python> | + | for loc in locs: |
- | import maya.cmds as cmds | + | |
- | from sys import platform | + | print locs # changed ------- because it is a operation |
- | import sys | + | |
- | import os | + | |
- | + | ||
- | def getOS(): | + | |
- | | + | |
- | if platform.startswith(' | + | |
- | return ' | + | |
- | elif platform.startswith(' | + | |
- | return ' | + | |
- | return ' | + | |
- | def getZ(): | + | # |
- | ''' | + | # Example 3 |
- | z_sys=os.getenv(' | + | a=1 |
- | | + | b=a |
- | | + | b=2 |
- | | + | print a # stay the same |
- | sys.path.append(z_sys+"/ | + | a=[1] |
- | </ | + | b=a |
+ | b=[2] | ||
+ | print a # stay the same | ||
- | ====== 3rd Party Tool ====== | + | a=[1] |
- | * poseDeform | + | b=a |
- | * http://www.tokeru.com/ | + | b.append(2) |
- | * Sculpt Inbetween Editor | + | print a # changed ------- because it is a operation |
- | * ref: http:// | + | |
- | ====== Python and Maya Qt interface ====== | + | # thus: assignment will not change original one, but operation will change original one |
- | ===== PyQt case code ===== | + | |
- | * panel creation | + | a=[1,2,3] |
- | * option: QWidget (it has panel name showing in taskbar), QDialog (not showing in taskbar) | + | b=a |
+ | c=[4,5,6] | ||
+ | b=c | ||
+ | print a # stay the same | ||
- | * layout alignment, QtCore.Qt.AlignTop, AlignRight | + | a=[1,2, [3.1,3.2,3.3] ] |
- | * layout.setAlignment(QtCore.Qt.AlignTop) | + | b=a[2] |
+ | c=[4,5,6] | ||
+ | b=c | ||
+ | print a # stay the same | ||
- | ====== Environment setup ====== | + | a=[1,2, [3.1, |
+ | b=a[2] | ||
+ | b=[4,5,6] | ||
+ | print a # stay the same | ||
- | Environment of Python in Maya | + | a={' |
- | * PYTHONPATH | + | b=a['first'] |
- | * Maya.env: <code python> | + | b=[4,5,6] |
- | import sys | + | print a # stay the same |
- | 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('win'): | + | |
- | | + | |
- | elif platform.startswith(' | + | |
- | return ' | + | |
- | return ' | + | |
- | def getZ(): | + | a={'first':[3.1,3.2,3.3] } |
- | | + | b=a['first'] |
- | # mel, about -os " | + | b[0]=4 |
- | | + | print a # changed ------- because it is a operation |
- | + | ||
- | # | + | |
- | #else $z_sys=system(" | + | |
- | + | ||
- | z_sys=os.getenv('z_sys') | + | |
- | + | ||
- | ## remove new line, mel | + | |
- | #$a=stringToStringArray($z_sys," | + | |
- | # | + | |
- | | + | |
- | # | + | |
- | z_sys = z_sys.strip() | + | a=[1,2, [3.1,3.2,3.3] ] |
+ | b=a[2][:] # fast copy | ||
+ | c=[4,5,6] | ||
+ | b=c | ||
+ | print a # stay the same | ||
- | | + | #~~~~~~~~~~~~~~~~~~~~~~~ |
- | z_sys = z_sys.replace(" | + | # example 4: |
- | # make sure system environment got defined | + | # ref: http://stackoverflow.com/ |
- | if(z_sys!="" | + | # explain: http:// |
- | print z_sys | + | def foo(a=[]): |
- | return | + | |
+ | return | ||
+ | foo() # changed every time | ||
- | </ | + | ######################################## |
- | ====== Python in mel basic ====== | + | # reference |
- | * print string and variable <code python> | + | ''' |
- | print(" | + | # ref: http:// |
- | print(" | + | Everything in Python |
- | </ | + | Every value in Python is a reference |
- | * string and interger <code python> | + | Objects cannot be values. |
- | | + | Assignment always copies the value (which is a pointer); |
- | </ | + | two such pointers can thus point to the same object. |
- | * start using mel as python <code python> | + | Objects are never copied unless you're doing something explicit to copy them. |
- | import maya.cmds | + | # ref: http:// |
- | maya.cmds.select(" | + | ???? |
- | maya.cmds.ls(sl=True) | + | ''' |
</ | </ | ||
- | * python modules <code python> | + | ====== Common Python syntax ====== |
- | import myModule | + | |
- | myModule.myFunc() | + | |
- | # reload | + | * python 101 - http:// |
- | reload(myModule) | + | |
- | </code> | + | |
- | * eval like in Python <code python> | + | ^comment |# | |
- | import maya.mel | + | ^print |print | |
- | maya.mel.eval("ls -sl") | + | ^string |var="string" |
- | </ | + | ^long string |%%varL=''' |
+ | ^raw string |r"Any dangerous Character here" | | ||
- | * python inside mel <code javascript> | + | ^os navigation | ^file operation | ^string |
- | string | + | ^os.chdir('/ |
- | size($list) | + | ^os.getcwd() | ^tfile.write(' |
- | </code> | + | ^os.ctermid() | ^tfile.close() | ^str1+str2 |concatenate ^re |regular expression ^end_remove_fixed | |
+ | ^os.uname() | ^tfile.read() | ^str(100) |string convert ^ | ^end_add_fixed | | ||
+ | ^os.listdir('/ | ||
+ | ^os.mkdir('/ | ||
+ | ^os.removedirs('/ | ||
+ | ^os.rename(src, | ||
+ | ^os.getenv(' | ||
+ | ^ | ^ | ^a? |may have one ^ | ^ | | ||
+ | ^ | ^ | ^a{1,3} |May 1~3 one ^ | ^ | | ||
+ | ^ | ^ | ^r”str” |raw string ^ | ^ | | ||
+ | ^regular expression | ^ | ^ | ^ | ^ | | ||
+ | ^ | ^ | ^ | ^ | ^ | | ||
+ | http:// | ||
- | ====== Common Python function====== | ||
- | * to use maya cmds, you need to run this code to get follow working <code python> | + | ====== |
- | ===== selection related | + | * [[http:// |
- | * select all geo from root node< | + | * http:// |
- | # method 1, filterExpand | + | * Python documentation generator |
- | childs = cmds.listRelatives(root, | + | |
- | allgeo = cmds.filterExpand(childs, | + | |
- | # method 2, naming based | + | * XML to PDF generation using Python |
- | cmds.select(root) | + | * http://www.blog.pythonlibrary.org/ |
- | cmds.select(hierarchy=1) | + | |
- | tAll=cmds.ls(sl=1, | + | |
- | allgeo=[] | + | |
- | for each in tAll: | + | |
- | if(each.endswith(' | + | |
- | allgeo.append(each) | + | |
- | # | + | |
- | </code> | + | |
- | ===== attribute related ===== | + | * Python add-on and libs |
+ | * Python image library (PIL): http:// | ||
+ | * Python EXIF.py: http:// | ||
+ | * demo of usage: http:// | ||
+ | * minimal EXIF reader and writer: http:// | ||
+ | * 50 python lib list: http:// | ||
- | * note attribute, which is not added by default, need to be added manually < | + | * a Chinese blog on python |
- | s=""" | + | |
- | 2nd line; | + | |
- | """ | + | |
- | if(cmds.objExists(' | + | |
- | cmds.addAttr(' | + | |
- | cmds.setAttr(' | + | |
- | </code> | + | |
- | ===== sets related | + | ** Python web applications ** |
+ | * https:// | ||
+ | * http:// | ||
+ | * http:// | ||
+ | ====== | ||
+ | * Python GUI option list: http:// | ||
+ | * web platform: PyJamas | ||
+ | * desktop platform: QT, wxWidget | ||
- | * empty group <code python> | + | * GUI design: |
- | * add to a organizing set<code python> | + | * wxGlade: http:// |
+ | * wxFormBuilder: | ||
- | ===== rigging related ===== | + | * IDE: |
+ | * Eric: http:// | ||
- | * findRelatedSkinCluster python version (written based on original mel version)< | + | ====== |
- | def findRelatedSkinCluster( skinObject ): | + | |
- | # | + | |
- | skinShape | + | |
- | skinShapeWithPath | + | |
- | hiddenShape | + | |
- | hiddenShapeWithPath | + | |
- | + | ||
- | cpTest | + | |
- | if len( cpTest ): | + | |
- | skinShape | + | |
- | + | ||
- | else: | + | |
- | rels = cmds.listRelatives( skinObject ) | + | |
- | for r in rels : | + | |
- | cpTest | + | |
- | 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> | + | ===== wxPython GUI concept quick guide ===== |
- | cmds.setKeyframe(testGeo+' | + | |
- | # just key the channel | + | * main build block |
- | cmds.setKeyframe(testGeo+' | + | * A Frame is a top-level window. |
- | </ | + | |
- | * delete key <code python> | ||
- | # delete all keys | ||
- | cmds.cutKey(' | ||
- | # delete some of keys | + | ===== wxWidget - wxPython ===== |
- | # 1. get key frame list | + | |
- | key_list | + | |
- | key_list | + | |
- | # current key value | + | * download: http://www.wxpython.org/ |
- | cur_v = cmds.getAttr(' | + | * youtube video tutorial: http:// |
- | </code> | + | |
- | * 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 ===== | + | * basic wxPython widget syntax and codes <code python> |
+ | # get wxWidget frame work, | ||
+ | import wx | ||
- | * emitter, particle, instance, field <code python> | + | # create class, since you are doing a App or a script |
- | # | + | |
- | # 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 | + | # inheriate a python window (so-called frame) |
- | mN_mdl_subfix='_mdl' #multDoubleLinear | + | class myApp(wx.Frame): |
+ | # constructor, | ||
+ | def __init__(self, | ||
+ | wx.Frame.__init__(self, | ||
+ | panel=wx.Panel(self) | ||
+ | |||
+ | #bitmap button | ||
+ | pic=wx.Image('img.bmp', wx.BITMAP_TYPE_BMP).ConvertToBitmap() | ||
+ | myBtn=wx.BitmapButton(panel, | ||
+ | self.Bind(wx.EVT_BUTTON, | ||
+ | myBtn.SetDefault() | ||
+ | |||
+ | | ||
+ | slider=wx.Slider(panel, | ||
+ | slider.SetTickFreq(5, | ||
+ | |||
+ | # spinner | ||
+ | spinner=wx.SpinCtrl(panel, | ||
+ | spinner.SetRange(1, | ||
+ | spinner.SetValue(10) | ||
+ | |||
+ | # checkbox | ||
+ | wx.CheckBox(panel, | ||
+ | wx.CheckBox(panel, | ||
+ | wx.CheckBox(panel, | ||
- | #---------------------- | + | |
- | # emitter and particle | + | |
- | # | + | cbox=wx.ListBox(panel,-1, |
- | mE=cmds.createNode(' | + | cbox.SetSelection(0) |
- | mP=cmds.createNode(' | + | |
- | # connect dynamic | + | # event handlers |
- | cmds.connectDynamic(mP,em=mE) | + | def btnAct(self,event): |
- | cmds.connectAttr(' | + | self.Destroy() |
- | # set initial value | + | if __name__=='__main__': |
- | e_conf={'emitterType': | + | |
- | for attr, v in e_conf.items(): | + | frame=myClass(parent=None,id=-1) # frame obj |
- | | + | frame.Show() # show frame |
- | + | app.MainLoop() # start app | |
- | # | + | |
- | # instance | + | |
- | # | + | |
- | mI=cmds.createNode(' | + | |
- | cmds.connectAttr(mP+' | + | |
- | + | ||
- | # instance Mesh | + | |
- | mInMesh=cmds.polyCylinder(n=mName+mInMesh_subfix,ax=[1,0,0]) | + | |
- | 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===== | + | * wxWidget binding for python tutorial: http:// |
- | * sin and cost function <code python> | + | * 2 Panel App Flow |
- | #----------------------------- | + | * http:// |
- | # aim vector | + | |
- | #----------------------------- | + | |
- | # 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]/ | + | ===== Common wxPython syntax |
- | ry=0 | + | |
- | if(aimDir[2]> | + | |
- | ry=math.degrees(math.atan(aimDir[0]/ | + | |
- | elif(aimDir[2]==0): # check z=0 xy panel, which rotateX=+-90 condition -90 or 90 or 0 | + | |
- | if(aimDir[0]> | + | |
- | ry=90 | + | |
- | elif(aimDir[0]< | + | |
- | ry=-90 | + | |
- | elif(aimDir[0]==0): # 0=xy panel | + | |
- | 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======= | + | * prompts <code python> |
+ | #import wx.lib.agw.multidirdialog as MDD | ||
+ | import multidirdialog as MDD | ||
+ | def showMsg(msg): | ||
+ | wx.MessageBox(msg, | ||
+ | wx.OK | wx.ICON_INFORMATION) | ||
- | * face track: | + | import wx.lib.dialogs |
- | * http:// | + | def showTxt(txt): |
- | * Sunday pipe and shaders: | + | |
- | | + | |
- | * http://www.3dg.dk/ | + | |
- | | + | |
- | | + | |
- | * python code | + | def getInput(): |
- | | + | |
+ | ask = wx.TextEntryDialog( | ||
+ | None, 'Your input:', | ||
+ | ' | ||
+ | if ask.ShowModal() == wx.ID_OK: | ||
+ | print " | ||
+ | tmpT = ask.GetValue() | ||
+ | ask.Destroy() | ||
+ | return tmpT | ||
- | * Blender shading: | + | def getFolder(): |
- | | + | |
- | | + | dialog = wx.DirDialog ( None, message = ' |
+ | if dialog.ShowModal() | ||
+ | print ' | ||
+ | tmpPath = dialog.GetPath() | ||
+ | | ||
+ | print 'No directory.' | ||
+ | dialog.Destroy() | ||
+ | return tmpPath | ||
- | ====== Angle Calculation ====== | ||
- | ===== Proof of cos(A+B) formula ===== | + | def getFolders(): |
+ | tmpPaths="" | ||
+ | ask = MDD.MultiDirDialog(None, | ||
+ | if ask.ShowModal() == wx.ID_OK: | ||
+ | tmpPaths | ||
+ | print "You chose the following file(s):" | ||
+ | for path in tmpPaths: | ||
+ | print path | ||
+ | ask.Destroy() | ||
+ | return tmpPaths | ||
- | * original reference: | + | def getFile(): |
+ | tmpPaths="" | ||
+ | wildcard = " | ||
+ | ask = wx.FileDialog(None, message=" | ||
+ | if ask.ShowModal() == wx.ID_OK: | ||
+ | tmpPaths = ask.GetPaths() | ||
+ | print " | ||
+ | for path in tmpPaths: | ||
+ | print path | ||
+ | ask.Destroy() | ||
+ | return tmpPaths | ||
- | ===== Proof of rotation matrix | + | def saveFile(): |
- | * 2D rotation of an angle from (x,y) to (x1,y1) \\ {{graphic:math_rotation_matrix_2d.png|}} | + | tmpPaths="" |
- | * Proof < | + | wildcard |
- | let A (x,y) to A1 (x1,y1) turns degree of a in respect of origin. | + | ask = wx.FileDialog(None, |
+ | if ask.ShowModal() | ||
+ | | ||
+ | print "Save file:", tmpPath | ||
+ | ask.Destroy() | ||
+ | return tmpPath | ||
+ | |||
+ | def getColor(): | ||
+ | ask = wx.ColourDialog(None) | ||
+ | | ||
+ | if ask.ShowModal() == wx.ID_OK: | ||
+ | data = ask.GetColourData() | ||
+ | print 'You selected: %s\n' % str(data.GetColour().Get(includeAlpha=False)) | ||
+ | ask.Destroy() | ||
+ | return data.GetColour().Get(includeAlpha=False) | ||
- | in Polar coordinate, | + | def getChoice(): |
- | so, A (o,R), A1 (o1,R) | + | tmpC="" |
+ | ask = wx.SingleChoiceDialog( | ||
+ | None, " | ||
+ | | ||
+ | wx.CHOICEDLG_STYLE | ||
+ | | ||
+ | if ask.ShowModal() == wx.ID_OK: | ||
+ | tmpC = ask.GetStringSelection() | ||
+ | print ' | ||
+ | ask.Destroy() | ||
+ | return tmpC | ||
- | from A1, we have | + | def getChoices(): |
- | x1=R*cos(o1) | + | tmpCs=[] |
- | y1=R*sin(o1) | + | list = [" |
+ | ask = wx.MultiChoiceDialog( None, | ||
+ | " | ||
+ | " | ||
- | as o1=o+a, resulting o1 from turn o in a degree, we have | + | if (ask.ShowModal() |
- | x1=R*cos(o1)=R*cos(o+a) | + | |
- | y1=R*sin(o1)=R*sin(o+a) | + | strings |
+ | print "You chose:" | ||
+ | | ||
+ | ask.Destroy() | ||
+ | return tmpCs | ||
+ | |||
+ | application | ||
+ | </ | ||
+ | * all dialog reference: | ||
+ | * http:// | ||
+ | * http:// | ||
- | 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. | + | ====== PyQt ====== |
- | </ | + | |
- | * thus in 3D space, we expand to have: \\ {{graphic: | + | |
- | ===== understand Matrix multiplication | + | * check [[devwiki: |
+ | ====== Python Portable for Windows ====== | ||
- | * ref: http:// | + | * since all mac and linux have python built-in |
- | ====== Vector calculation ====== | + | **Workflow on portable python** |
- | * in Maya | + | |
- | * OpenMaya MVector method | + | |
- | from maya.OpenMaya import MVector | + | * for Python3.5.2: |
+ | * for Python2.7.12: | ||
+ | - update: for PyQt and PySide, just cd to that python directory and .\python.exe -m pip install PySide or PyQt4 to install that module with pip or ./python for mac/linux | ||
+ | - < | ||
+ | * <del>https://www.riverbankcomputing.com/ | ||
+ | * < | ||
+ | * < | ||
+ | - other library: | ||
+ | * cx_Freeze | ||
- | # function: make selected points into a sphere/ | + | ====== Python Portable for Mac ====== |
- | points=cmds.ls(sl=1, fl=1) | + | * Python 2.7 is built-in in Mac at / |
- | center=cmds.ls(sl=1)[0] | + | * There are many articles on install latest python 2.7 and 3.x version with many methods, like homebrew and offical python site, while my idea is not touch any system path or system environment, |
+ | * the advantage is you choose which python to run the py script | ||
+ | * the disadvantage is you can't default python the_script.py in your command line, so you should always use a launch bash script to dynamically use which python path and open the script with it. | ||
+ | * check my App_V9.bat launch for windows: https:// | ||
- | pointVectors = [] | + | **Standard Install Python 3.8** |
- | for pnt in points: | + | * download and install python3 installer |
- | | + | * download and install command line tools for xcode latest (use normal Apple acc to login) |
- | | + | * https:// |
+ | * install PySide2 | ||
+ | * now python3 work in terminal app | ||
+ | | ||
+ | * to make command extension file runable, do bash for the command extension launcher <code bash> | ||
+ | ====== Python Cool Tricks and Notes ====== | ||
- | v_center = MVector(*cmds.pointPosition(center)) | + | |
+ | * decimal vs float for real life accounting usage: | ||
+ | * decimal wont have those float annoying issue with tons of 0s after . | ||
+ | * decimal deal number like human do instead of infinite computing accuracy | ||
+ | * https:// | ||
+ | * https:// | ||
+ | * < | ||
- | pointDistances = [] | ||
- | for v in pointVectors: | ||
- | pointDistances.append((v - v_center).length()) | ||
- | minRadius | + | * python and whatsapp msg |
+ | * ref: https:// | ||
+ | * **pywhatkit**: | ||
+ | * because it use automation, so interaction with browser can be buggy, no quick repeat sending (since automate like human action, need wait response of other app update), send group chat may send to wrong group if you also interact with browser | ||
+ | * example< | ||
+ | import pywhatkit | ||
+ | pywhatkit.sendwhatmsg_instantly(" | ||
+ | # wait 10s to ui response, close tab true after 3s, | ||
+ | # you need to close tab as whatsapp web only allow 1 window active | ||
+ | </ | ||
+ | * **selenium**: | ||
+ | * example setup and login manually for first time <code python> | ||
+ | my_drive | ||
+ | def drive_setup(): | ||
+ | from selenium import webdriver | ||
+ | from selenium.webdriver.common.keys import Keys | ||
+ | from selenium.webdriver.common.by import By | ||
+ | |||
+ | my_drive = webdriver.Chrome() | ||
+ | phone_no = " | ||
+ | my_drive.get(f' | ||
- | for pnt,v in zip(points, pointVectors): | + | def drive_msg(in_text): |
- | | + | |
- | | + | |
+ | return | ||
+ | else: | ||
+ | chat_box = my_drive.find_element(by=By.XPATH, value='/ | ||
+ | chat_box.send_keys(in_text) | ||
+ | send_button | ||
+ | send_button.click() | ||
</ | </ | ||
- | ====== Plane Intersection calcuation ====== | ||
- | * 2 planes (represented by 2 vectors) intersection line | + | ====== Python and interaction with other API ====== |
- | ====== Math tutorial links ====== | + | Telegram |
- | + | | |
- | | + | * manual: https://core.telegram.org/bots/api |
- | * http://programmedlessons.org/VectorLessons/vectorIndex.html | + | |
- | + | | |
- | | + | - now search your bot " |
- | * http:// | + | - in python, use requests to get the chat from the bot, try one more time if no result <code python> |
- | + | import requests | |
- | | + | the_key = " |
- | ====== Local space and World space and Screen space ====== | + | url = "https://api.telegram.org/bot{0}/getUpdates" |
- | + | response = requests.get(url) | |
- | | + | result |
- | + | print(result) | |
- | + | </ | |
- | ====== Fluid Calculation model ====== | + | - once you got result in json, you will find the chat id. <code python> |
- | + | # the list of all chat spec your bot received, yours is likely | |
- | * ref: http://around-the-corner.typepad.com/adn/2012/ | + | my_chat_info = result[' |
- | + | my_msg_info | |
- | ====== 3D Camera model ====== | + | # it got info of: chat, date, from, message_id, text |
- | | + | my_chat_id |
- | * stereoscopic camera: http:// | + | </ |
- | + | | |
- | ====== VR rendering in 3D====== | + | reply_text = 'hello world' |
- | + | url = "https://api.telegram.org/bot{key}/sendMessage?chat_id={id}& | |
- | | + | response = requests.get(url) |
- | * http:// | + | </code> |
- | * http://www.andrewhazelden.com/ | + | |
- | * https:// | + | result = response.json() |
- | * https:// | + | send_state = result[' |
- | | + | send_result = result[' |
- | * https:// | + | # chat (first_name, |
- | * http:// | + | </code> |
- | * http:// | + | |
- | * article | + | ====== Python and Networking related ====== |
- | * http:// | + | |
- | * http:// | + | |
- | * tool | + | * above is python interact with a REST API server, you can also use python to build your own REST API server to handle requests from other application, |
- | * http:// | + | * implement REST API into python, you may need to use some python module like Flash, FastAPI, then you start another thread from your python application to run your REST API server. |
+ | * while using Sockets method may be simpler if you just want to send some simple message between applications, | ||
+ | * using REST API allow your application to talk cross different application and difference device, and provide a safe gate to your important data handled by your application. | ||
- | * case study: | ||
- | * https:// |