-
-
Save tbttfox/9ca775bf629c7a1285c27c8d9d961bca to your computer and use it in GitHub Desktop.
| from maya import OpenMaya as om | |
| import numpy as np | |
| from ctypes import c_float, c_double, c_int, c_uint | |
| _CONVERT_DICT = { | |
| om.MPointArray: (float, 4, c_double, om.MScriptUtil.asDouble4Ptr), | |
| om.MFloatPointArray: (float, 4, c_float , om.MScriptUtil.asFloat4Ptr), | |
| om.MVectorArray: (float, 3, c_double, om.MScriptUtil.asDouble3Ptr), | |
| om.MFloatVectorArray: (float, 3, c_float , om.MScriptUtil.asFloat3Ptr), | |
| om.MDoubleArray: (float, 1, c_double, om.MScriptUtil.asDoublePtr), | |
| om.MFloatArray: (float, 1, c_float , om.MScriptUtil.asFloatPtr), | |
| om.MIntArray: (int , 1, c_int , om.MScriptUtil.asIntPtr), | |
| om.MUintArray: (int , 1, c_uint , om.MScriptUtil.asUintPtr), | |
| } | |
| def _swigConnect(mArray, count, util): | |
| ''' | |
| Use an MScriptUtil to build SWIG array that we can read from and write to. | |
| Make sure to get the MScriptUtil from outside this function, otherwise | |
| it may be garbage collected | |
| The _CONVERT_DICT holds {mayaType: (pyType, numComps, cType, ptrType)} where | |
| pyType: The type that is used to fill the MScriptUtil array. | |
| numComps: The number of components. So a double4Ptr would be 4 | |
| cType: The ctypes type used to read the data | |
| ptrType: An unbound method on MScriptUtil to cast the pointer to the correct type | |
| I can still call that unbound method by manually passing the usually-implicit | |
| self argument (which will be an instance of MScriptUtil) | |
| ''' | |
| pyTyp, comps, ctp, ptrTyp = _CONVERT_DICT[type(mArray)] | |
| cc = (count * comps) | |
| util.createFromList([pyTyp()] * cc, cc) | |
| #passing util as 'self' to call the unbound method | |
| ptr = ptrTyp(util) | |
| mArray.get(ptr) | |
| if comps == 1: | |
| cdata = ctp * count | |
| else: | |
| # Multiplication follows some strange rules here | |
| # I would expect (ctype*3)*N to be an Nx3 array (ctype*3 repeated N times) | |
| # However, it gets converted to a 3xN array | |
| cdata = (ctp * comps) * count | |
| # int(ptr) gives the memory address | |
| cta = cdata.from_address(int(ptr)) | |
| # This makes numpy look at the same memory as the ctypes array | |
| # so we can both read from and write to that data through numpy | |
| npArray = np.ctypeslib.as_array(cta) | |
| return npArray, ptr | |
| def mayaToNumpy(mArray): | |
| ''' Convert a maya array to a numpy array | |
| Parameters | |
| ---------- | |
| ary : MArray | |
| The maya array to convert to a numpy array | |
| Returns | |
| ------- | |
| : np.array : | |
| A numpy array that contains the data from mArray | |
| ''' | |
| util = om.MScriptUtil() | |
| count = mArray.length() | |
| npArray, _ = _swigConnect(mArray, count, util) | |
| return np.copy(npArray) | |
| def numpyToMaya(ary, mType): | |
| ''' Convert a numpy array to a specific maya type array | |
| Parameters | |
| ---------- | |
| ary : np.array | |
| The numpy array to convert to a maya array | |
| mType : type | |
| The maya type to convert to out of: MPointArray, MFloatPointArray, MVectorArray, | |
| MFloatVectorArray, MDoubleArray, MFloatArray, MIntArray, MUintArray | |
| Returns | |
| ------- | |
| : mType : | |
| An array of the provided type that contains the data from ary | |
| ''' | |
| util = om.MScriptUtil() | |
| # Add a little shape checking | |
| comps = _CONVERT_DICT[mType][1] | |
| if comps == 1: | |
| if len(ary.shape) != 1: | |
| raise ValueError("Numpy array must be 1D to convert to the given maya type") | |
| else: | |
| if len(ary.shape) != 2: | |
| raise ValueError("Numpy array must be 2D to convert to the given maya type") | |
| if ary.shape[1] != comps: | |
| msg = "Numpy array must have the proper shape. Dimension 2 has size {0}, but needs size {1}" | |
| raise ValueError(msg.format(ary.shape[1], comps)) | |
| count = ary.shape[0] | |
| mArray = mType(count) | |
| npArray, ptr = _swigConnect(mArray, count, util) | |
| np.copyto(npArray, ary) | |
| return mType(ptr, count) | |
| _NTYPE_DICT={ | |
| om.MFnNumericData.kInvalid: (om.MDataHandle.asDouble, om.MDataHandle.setDouble), | |
| om.MFnNumericData.kFloat: (om.MDataHandle.asDouble, om.MDataHandle.setDouble), | |
| om.MFnNumericData.kDouble: (om.MDataHandle.asDouble, om.MDataHandle.setDouble), | |
| om.MFnNumericData.kByte: (om.MDataHandle.asInt, om.MDataHandle.setInt), | |
| om.MFnNumericData.kChar: (om.MDataHandle.asChar, om.MDataHandle.setChar), | |
| om.MFnNumericData.kShort: (om.MDataHandle.asShort, om.MDataHandle.setShort), | |
| om.MFnNumericData.kInt: (om.MDataHandle.asInt, om.MDataHandle.setInt), | |
| #om.MFnNumericData.kInt64: (om.MDataHandle.asInt, om.MDataHandle.setInt64), | |
| om.MFnNumericData.kAddr: (om.MDataHandle.asInt, om.MDataHandle.setInt), | |
| om.MFnNumericData.kLong: (om.MDataHandle.asInt, om.MDataHandle.setInt), | |
| om.MFnNumericData.kBoolean: (om.MDataHandle.asBool, om.MDataHandle.setBool), | |
| om.MFnNumericData.k2Short: (om.MDataHandle.asShort2, om.MDataHandle.set2Short), | |
| om.MFnNumericData.k2Long: (om.MDataHandle.asInt2, om.MDataHandle.set2Int), | |
| om.MFnNumericData.k2Int: (om.MDataHandle.asInt2, om.MDataHandle.set2Int), | |
| om.MFnNumericData.k3Short: (om.MDataHandle.asShort3, om.MDataHandle.set3Short), | |
| om.MFnNumericData.k3Long: (om.MDataHandle.asInt3, om.MDataHandle.set3Int), | |
| om.MFnNumericData.k3Int: (om.MDataHandle.asInt3, om.MDataHandle.set3Int), | |
| om.MFnNumericData.k2Float: (om.MDataHandle.asFloat2, om.MDataHandle.set2Float), | |
| om.MFnNumericData.k2Double: (om.MDataHandle.asDouble2, om.MDataHandle.set2Double), | |
| om.MFnNumericData.k3Float: (om.MDataHandle.asFloat3, om.MDataHandle.set3Float), | |
| om.MFnNumericData.k3Double: (om.MDataHandle.asDouble3, om.MDataHandle.set3Double), | |
| } | |
| _DTYPE_DICT = { | |
| om.MFn.kPointArrayData: (om.MFnPointArrayData, om.MPointArray), | |
| om.MFn.kDoubleArrayData: (om.MFnDoubleArrayData, om.MDoubleArray), | |
| om.MFn.kFloatArrayData: (om.MFnFloatArrayData, om.MFloatArray), | |
| om.MFn.kIntArrayData: (om.MFnIntArrayData, om.MIntArray), | |
| om.MFn.kUInt64ArrayData: (om.MFnUInt64ArrayData, om.MPointArray), | |
| om.MFn.kVectorArrayData: (om.MFnVectorArrayData, om.MVectorArray), | |
| } | |
| def getNumpyAttr(attrName): | |
| ''' Read attribute data directly from the plugs into numpy | |
| This function will read most numeric data types directly into numpy arrays | |
| However, some simple data types (floats, vectors, etc...) have api accessors | |
| that return python tuples. These will not be turned into numpy arrays. | |
| And really, if you're getting simple data like that, just use cmds.getAttr | |
| Parameters | |
| ---------- | |
| attrName : str or om.MPlug | |
| The name of the attribute to get (For instance "pSphere2.translate", or "group1.pim[0]") | |
| Or the MPlug itself | |
| Returns | |
| ------- | |
| : object : | |
| The numerical data from the provided plug. A np.array, float, int, or tuple | |
| ''' | |
| if isinstance(attrName, basestring): | |
| sl = om.MSelectionList() | |
| sl.add(attrName) | |
| plug = om.MPlug() | |
| sl.getPlug(0, plug) | |
| elif isinstance(attrName, om.MPlug): | |
| plug = attrName | |
| #First just check if the data is numeric | |
| mdh = plug.asMDataHandle() | |
| if mdh.isNumeric(): | |
| # So, at this point, you should really just use getattr | |
| ntype = mdh.numericType() | |
| if ntype in _NTYPE_DICT: | |
| return _NTYPE_DICT[ntype][0](mdh) | |
| elif ntype == om.MFnNumericData.k4Double: | |
| NotImplementedError("Haven't implemented double4 access yet") | |
| else: | |
| raise RuntimeError("I don't know how to access data from the given attribute") | |
| else: | |
| # The data is more complex than a simple number. | |
| try: | |
| pmo = plug.asMObject() | |
| except RuntimeError: | |
| # raise a more descriptive error. And make sure to actually print the plug name | |
| raise RuntimeError("I don't know how to access data from the given attribute") | |
| apiType = pmo.apiType() | |
| # A list of types that I can just pass to mayaToNumpy | |
| if apiType in _DTYPE_DICT: | |
| fn, dtype = _DTYPE_DICT[apiType] | |
| fnPmo = fn(pmo) | |
| ary = fnPmo.array() | |
| return mayaToNumpy(ary) | |
| elif apiType == om.MFn.kComponentListData: | |
| fnPmo = om.MFnComponentListData(pmo) | |
| mirs = [] | |
| mir = om.MIntArray() | |
| for attrIndex in xrange(fnPmo.length()): | |
| fnEL = om.MFnSingleIndexedComponent(fnPmo[attrIndex]) | |
| fnEL.getElements(mir) | |
| mirs.append(mayaToNumpy(mir)) | |
| return np.concatenate(mirs) | |
| elif apiType == om.MFn.kMatrixData: | |
| fnPmo = om.MFnMatrixData(pmo) | |
| mat = fnPmo.matrix() | |
| return mayaToNumpy(mat) | |
| else: | |
| apiTypeStr = pmo.apiTypeStr() | |
| raise NotImplementedError("I don't know how to handle {0} yet".format(apiTypeStr)) | |
| raise NotImplementedError("Fell all the way through") | |
| def setNumpyAttr(attrName, value): | |
| ''' Write a numpy array directly into a maya plug | |
| This function will handle most numeric plug types. | |
| But for single float, individual point, etc.. types, consider using cmds.setAttr | |
| THIS DOES NOT SUPPORT UNDO | |
| Parameters | |
| ---------- | |
| attrName : str or om.MPlug | |
| The name of the attribute to get (For instance "pSphere2.translate", or "group1.pim[0]") | |
| Or the MPlug itself | |
| value : int, float, tuple, np.array | |
| The correctly typed value to set on the attribute | |
| ''' | |
| if isinstance(attrName, basestring): | |
| sl = om.MSelectionList() | |
| sl.add(attrName) | |
| plug = om.MPlug() | |
| sl.getPlug(0, plug) | |
| elif isinstance(attrName, om.MPlug): | |
| plug = attrName | |
| else: | |
| raise ValueError("Data must be string or MPlug. Got {0}".format(type(attrName))) | |
| #First just check if the data is numeric | |
| mdh = plug.asMDataHandle() | |
| if mdh.isNumeric(): | |
| # So, at this point, you should really just use setattr | |
| ntype = mdh.numericType() | |
| if ntype in _NTYPE_DICT: | |
| _NTYPE_DICT[ntype][1](mdh, *value) | |
| plug.setMObject(mdh.data()) | |
| elif ntype == om.MFnNumericData.k4Double: | |
| NotImplementedError("Haven't implemented double4 access yet") | |
| else: | |
| raise RuntimeError("I don't know how to set data on the given attribute") | |
| else: | |
| # The data is more complex than a simple number. | |
| try: | |
| pmo = plug.asMObject() | |
| except RuntimeError: | |
| # raise a more descriptive error. And make sure to actually print the plug name | |
| raise RuntimeError("I don't know how to access data from the given attribute") | |
| apiType = pmo.apiType() | |
| if apiType in _DTYPE_DICT: | |
| # build the pointArrayData | |
| fnType, mType = _DTYPE_DICT[apiType] | |
| fn = fnType() | |
| mPts = numpyToMaya(value, mType) | |
| dataObj = fn.create(mPts) | |
| plug.setMObject(dataObj) | |
| return | |
| elif apiType == om.MFn.kComponentListData: | |
| fnCompList = om.MFnComponentListData() | |
| compList = fnCompList.create() | |
| fnIdx = om.MFnSingleIndexedComponent() | |
| idxObj = fnIdx.create(om.MFn.kMeshVertComponent) | |
| mIdxs = numpyToMaya(value, om.MIntArray) | |
| fnIdx.addElements(mIdxs) | |
| fnCompList.add(idxObj) | |
| plug.setMObject(compList) | |
| return | |
| else: | |
| apiTypeStr = pmo.apiTypeStr() | |
| raise NotImplementedError("I don't know how to handle {0} yet".format(apiTypeStr)) | |
| raise NotImplementedError("WTF? How did you get here??") | |
| ################################################################################ | |
| def test(): | |
| import time | |
| from maya import cmds | |
| meshName = 'pSphere1' | |
| bsName = 'blendShape1' | |
| meshIdx = 0 | |
| bsIdx = 0 | |
| # A quick test showing how to build a numpy array | |
| # containing the deltas for a shape on a blendshape node | |
| numVerts = cmds.polyEvaluate(meshName, vertex=True) | |
| baseAttr = '{0}.it[{1}].itg[{2}].iti[6000]'.format(bsName, meshIdx, bsIdx) | |
| inPtAttr = baseAttr + '.inputPointsTarget' | |
| inCompAttr = baseAttr + '.inputComponentsTarget' | |
| start = time.time() | |
| points = getNumpyAttr(inPtAttr) | |
| idxs = getNumpyAttr(inCompAttr) | |
| ret = np.zeros((numVerts, 4)) | |
| ret[idxs] = points | |
| end = time.time() | |
| print "IDXS", idxs.shape | |
| print "OUT", points.shape | |
| print "RET", ret.shape | |
| print "TOOK", end - start | |
| if __name__ == "__main__": | |
| test() |
The one from this link works in 2018 and after
https://forums.autodesk.com/t5/maya-programming/numpy-1-13-1-scipy-0-19-1-for-maya-2018/td-p/7362541
Also, you can install pip for mayapy.exe, and then install numpy. From a windows command prompt, navigate to the maya bin folder then do this:
mayapy.exe -m ensurepip --upgrade
mayapy.exe -m pip install <path-to-that-numpy-wheel>
Thanks! I actually saw this link before, but there is no more numpy wheel in that Google Drive folder, only a numexpr wheel :/
I ended up finding another site which pointed to a repo with Maya-compatible packages, here's the procedure:
enabling pip for Maya:
see https://discourse.techart.online/t/numpy-1-13-1-scipy-0-19-1-for-maya-2018/9121/11
and https://forums.autodesk.com/t5/maya-programming/numpy-1-13-1-scipy-0-19-1-for-maya-2018/td-p/7362541
- Get pip to run on MayaPY: form an elevated command prompt navigate to mayapy folder and run:
mayapy -m ensurepip
- Update pip:
mayapy -m pip install --upgrade pip
mayapy -m pip install --upgrade setuptools
installing numpy/scipy:
pip install -i https://pypi.anaconda.org/carlkl/simple numpy
pip install -i https://pypi.anaconda.org/carlkl/simple scipy
... But there is a numpy wheel in that google drive folder from the autodesk link. I checked before I posted. It was down for a while, but he re-uploaded it about 3 months ago according to the last message in that thread.
Also the stuff on the anaconda link is latest 1.11.0
Hi thanks for sharing these scripts they are very useful!
I am encountering a problem trying to get the targetWeights attribute from a blendshape target.
baseAttr = '{0}.inputTarget[{1}].inputTargetGroup[{2}].targetWeights'.format(bsName, meshIdx, bsIdx)
I only get one value out of that. Is this one of those circumstances where you advise to use the getAttr cmds?
Thank you!
Short answer: Yep, use cmds.getAttr
Longer answer:
See the input points target returns a "typed" type and "False" for multi
attr = 'inputTarget.inputTargetGroup.inputTargetItem.inputPointsTarget'
cmds.attributeQuery(attr.split('.')[-1], node='blendShape1', attributeType=True) # returns "typed"
cmds.attributeQuery(attr.split('.')[-1], node='blendShape1', multi=True) # returns "false"
However, the targetWeights will return "Float" and "True"
attr = 'inputTarget.inputTargetGroup.targetWeights'
cmds.attributeQuery(attr.split('.')[-1], node='blendShape1', attributeType=True) # returns "float"
cmds.attributeQuery(attr.split('.')[-1], node='blendShape1', multi=True) # returns "True"
This library handles when the type of the single attribute is some kind of arrayType (like MFloatArray or MPointArray) which is reported as a "typed" attributeType.
However, targetWeights is not a single attribute, it's a multi-attribute. So you'd have to loop over (in this case) the vertex indices with a baseAttr like this:
baseAttr = '{0}.inputTarget[{1}].inputTargetGroup[{2}].targetWeights[{3}]'.format(bsName, meshIdx, bsIdx, vertIdx)
And I'm 99% sure that'd be slower than just using cmds.getAttr
Thanks for the answer and the detailed explanation!
I wonder if there is anything faster than getAttr. I am not having the best performances using it.
Cheers
Hey, can I ask where you got a Maya-compatible numpy? any chance you could share a build, for Maya 2020 + Win64?