import maya.cmds as cmds
import maya.mel as mel
from ngSkinTools import log
from ngSkinTools.utils import MessageException
from ngSkinTools.layerUtils import NamedPaintTarget
from itertools import izip
from _functools import partial
class MirrorDirection:
DIRECTION_NEGATIVETOPOSITIVE = 0;
DIRECTION_POSITIVETONEGATIVE = 1;
DIRECTION_GUESS = 2;
DIRECTION_FLIP = 3;
[docs]class PaintMode:
'''
Constants for paint mode
'''
PAINTMODE_REPLACE = 1
PAINTMODE_ADD = 2
PAINTMODE_SCALE = 3
PAINTMODE_SMOOTH = 4
PAINTMODE_SHARPEN = 5
[docs]class MllInterface(object):
'''
A wrapper object to call functionality from ngSkinLayer command.
Most operations operate on current selection, or on target mesh that
was set in advance. All edit operations are undoable.
Example usage:
.. code-block:: python
from ngSkinTools.mllInterface import MllInterface
mll = MllInterface()
mll.setCurrentMesh('myMesh')
mll.initLayers()
id = mll.createLayer('initial weights')
mll.setInfluenceWeights(id,0,[0.0,0.0,1.0,1.0])
...
'''
TARGET_REFERENCE_MESH = 'ngSkinTools#TargetRefMesh'
def __init__(self,mesh=None):
self.log = log.getLogger("MllInterface")
self.setCurrentMesh(mesh)
[docs] def setCurrentMesh(self,mesh):
'''
Set mesh we'll be working on with in this wrapper. Use None to operate on current selection instead.
:param str mesh: mesh node name/path
'''
self.mesh = mesh
[docs] def initLayers(self):
'''
initializes layer data node setup for target mesh
'''
self.ngSkinLayerCmd(lda=True)
[docs] def getLayersAvailable(self):
'''
returns true if layer data is available for target mesh
:rtype: bool
'''
try:
result = self.ngSkinLayerCmd(q=True,lda=True)
return result
except Exception,err:
return False
[docs] def getCurrentLayer(self):
'''
get layer that is marked as selected on MLL side; current layer is used for many things, for example, as a paint target.
:rtype: int
'''
result = self.ngSkinLayerCmd(q=True,cl=True)
if result==0:
return None
return result
[docs] def getTargetInfo(self):
'''
Returns a tuple with mesh and skin cluster node names where skinLayer data
is (or can be) attached.
If current mesh (or selection) is not suitable for attaching layers,
returns None
:rtype: (str,str)
'''
try:
result = self.ngSkinLayerCmd(q=True,ldt=True)
if len(result)==2:
return result
except MessageException,err:
raise err
except:
return None
return None
[docs] def getVertCount(self):
'''
For initialized layer info, returns number of vertices layer manager sees in the mesh.
This might be different to actual vertex count in the mesh, if mesh has post-skin cluster mesh
modifiers (as vertex merge or smooth)
:rtype: int
'''
return self.ngSkinLayerCmd(q=True,vertexCount=True)
[docs] def getLayerName(self,layerId):
'''
get layer name by ID
:param int layerId: layer ID
:rtype: str
'''
return self.ngSkinLayerCmdMel("-id {0} -q -name".format(int(layerId)))
[docs] def setLayerName(self,layerId,name):
'''
set layer name by ID
'''
self.ngSkinLayerCmd(e=True,id=int(layerId),name=name)
[docs] def getLayerOpacity(self,layerId):
'''
Returns layer opacity as float between ``0.0`` and ``1.0``
'''
return float(self.ngSkinLayerCmdMel('-id {0} -q -opacity'.format(int(layerId))))
[docs] def setLayerOpacity(self,layerId,opacity):
'''
Set opacity for given layer. Use values between ``0.0`` and ``1.0``
'''
self.ngSkinLayerCmd(e=True,id=int(layerId),opacity=opacity)
[docs] def isLayerEnabled(self,layerId):
'''
Returns ``True``, if layer on/off flag is turned on
'''
return bool(self.ngSkinLayerCmdMel('-id {0} -q -enabled'.format(int(layerId))))
[docs] def setLayerEnabled(self,layerId,enabled):
'''
Turn layer on/off. Use ``True`` / ``False`` for 'enabled' value.
'''
self.ngSkinLayerCmd(e=True,id=int(layerId),enabled=enabled)
[docs] def listLayers(self):
'''
returns iterator to layer list; each element is a tuple: ``(layer ID, layer name)``
'''
layers = self.ngSkinLayerCmd(q=True,listLayers=True)
argsPerLayer = 3
for i in xrange(len(layers)/argsPerLayer):
# layerID, layerName, identation level
parent = int(layers[i*argsPerLayer+2])
if parent==0:
parent = None
yield int(layers[i*argsPerLayer]),layers[i*argsPerLayer+1],parent
[docs] def listLayerInfluences(self,layerId=None,activeInfluences=True):
'''
returns iterator to layer influences. each element is a tuple ``(influence name,influence logical index)``
'''
cmd = ''
if layerId is not None:
cmd += '-id {0}'.format(layerId)
cmd += '-q -listLayerInfluences'
if activeInfluences:
cmd+= " -activeInfluences"
influences = self.ngSkinLayerCmdMel(cmd)
if influences is None:
return []
return zip(influences[0::2],map(int, influences[1::2]))
def __asTypeList(self,_type,result):
if result is None:
return []
return map(_type,result)
def __asFloatList(self,result):
return self.__asTypeList(float, result)
def __asIntList(self,result):
return self.__asTypeList(int, result)
def __floatListAsString(self,floatList):
'''
returns empty string for None and []
otherwise, returns a list of floats, comma delimited
'''
if not floatList:
return ""
def formatFloat(value):
return str(value)
return ",".join(map(formatFloat, floatList))
def __intListAsString(self,values):
return ",".join(map(str,values))
def setLayerParent(self,layerId,parentLayerId):
if parentLayerId is None:
parentLayerId = 0
self.ngSkinLayerCmd(e=True,id=int(layerId),parent=int(parentLayerId))
def getLayerParent(self,layerId):
result = self.ngSkinLayerCmdMel('-id {0} -q -parent'.format(layerId))
if result == 0:
return None
return result
[docs] def getLayerMask(self,layerId):
'''
returns layer mask weights as float list. if mask is not initialized, returns empty list
'''
return self.getInfluenceWeights(layerId, NamedPaintTarget.MASK);
[docs] def setLayerMask(self,layerId,weights):
'''
Set mask for given layer. Supply float list for weights, e.g. ``[0.0,1.0,0.6]``.
Supply empty list to set mask into uninitialized state.
'''
self.setInfluenceWeights(layerId, NamedPaintTarget.MASK, weights)
def hasDqBlendTarget(self,layerId):
return self.ngSkinLayerCmdMel('-id {0} -paintTarget "{1}" -q -hasPaintTarget '.format(layerId,NamedPaintTarget.DUAL_QUATERNION))!=0
[docs] def getDualQuaternionWeights(self,layerId):
'''
returns layer DQ weights as float list. if DQ weights are not painted for this layer, returns empty list
'''
return self.getInfluenceWeights(layerId, NamedPaintTarget.DUAL_QUATERNION);
[docs] def setDualQuaternionWeights(self,layerId,weights):
'''
Set dual-quaternion weights for given layer. Supply float list for weights, e.g. ``[0.0,1.0,0.6]``.
Supply empty list to set into uninitialized state.
'''
self.setInfluenceWeights(layerId, NamedPaintTarget.DUAL_QUATERNION, weights)
[docs] def getInfluenceWeights(self,layerId,influence):
'''
returns influence weights as float list.
:param influence: either a logical influence index or named influences "mask", "dq"
'''
return self.__asFloatList(self.ngSkinLayerCmdMel('-id {0} -paintTarget {1} -q -w '.format(layerId,influence)))
[docs] def setInfluenceWeights(self,layerId,influence,weights,undoEnabled=True):
'''
Set weights for given influence in a layer. Provide weights as float list; vertex count should match result of :py:meth:`~.getVertCount`
If weight values are higher than 1.0, they will be capped at 1.0.
'''
self.ngSkinLayerCmd(e=True,id=int(layerId),paintTarget=influence,w=self.__floatListAsString(weights),undoEnabled=undoEnabled)
[docs] def snapshotLayerWeights(self,layerId):
'''
remembers current weights and restores them after undo; redo restores weights that were there before first undo
'''
self.ngSkinLayerCmd(e=True,id=int(layerId),createWeightsSnapshot=True)
[docs] def setLayerWeightsBufferSize(self,layerId,numInfluences):
'''
sets layer weights buffer size no less than X influences
'''
self.ngSkinLayerCmd(e=True,id=int(layerId),wbs=numInfluences)
def ngSkinLayerCmd(self,*args,**kwargs):
#import inspect
#import sys
#sys.__stderr__.write("\n".join(["stack: {1} ({2} in {3})".format(*i) for i in inspect.stack()])+"\n")
if self.mesh is not None:
if self.mesh==self.TARGET_REFERENCE_MESH:
kwargs['targetReferenceMesh'] = True
else:
args = (self.mesh,)+args
# self.log.info("ngSkinLayer %r %r",args,kwargs)
return cmds.ngSkinLayer(*args,**kwargs)
def ngSkinLayerCmdMel(self,melCmd):
melCmd = "ngSkinLayer "+melCmd
if self.mesh is not None:
if self.mesh==MllInterface.TARGET_REFERENCE_MESH:
melCmd += " -targetReferenceMesh"
else:
melCmd += " " + self.mesh
self.log.info(melCmd)
return mel.eval(melCmd)
[docs] def createLayer(self,name,forceEmpty=False):
'''
creates new layer with given name and returns it's ID; when forceEmpty flag is set to true,
layer weights will not be populated from skin cluster.
:return: layer ID
:rtype: int
'''
return self.ngSkinLayerCmd(name=name,add=True,forceEmpty=forceEmpty)
[docs] def deleteLayer(self,layerId):
'''
Deletes given layer in target mesh
'''
self.ngSkinLayerCmd(rm=True,id=int(layerId))
[docs] def setCurrentLayer(self,layerId):
'''
Set current layer
:param int layerId: layerId earlier obtained via createLayer; can be None, will equate to having to current layer
'''
if layerId is None:
layerId = 0
return self.ngSkinLayerCmd(cl=int(layerId))
[docs] def setCurrentPaintTarget(self,paintTarget):
'''
universal way to set current paint target.
:param paintTarget: influence index or named paint target: ``mask``, ``dq``;
'''
self.ngSkinLayerCmd(pt=paintTarget)
[docs] def getCurrentPaintTarget(self):
'''
if there is a named target selected, returns "mask" or "dq";
if no target is selected, returns ``None``;
otherwise returns influence index
'''
return self.ngSkinLayerCmd(q=True,pt=True)
def getPaintTargetPath(self):
return self.ngSkinLayerCmd(q=True,paintTargetPath=True)
[docs] def getMirrorAxis(self):
'''
Get axis that is used in the mirror operation. Can be one of: 'x', 'y', 'z', or 'undefined'
'''
result = self.ngSkinLayerCmd(q=True,mirrorAxis=True)
if result=='undefined':
return None
return result
[docs] def mirrorLayerWeights(self,layerId,mirrorWidth=0.0,mirrorLayerWeights=True,mirrorLayerMask=True,mirrorDualQuaternion=True,mirrorDirection=MirrorDirection.DIRECTION_POSITIVETONEGATIVE):
'''
Mirror weights in a layer.
:param int layerId: id of a target layer
:param float mirrorWidth: width of a seam axis
:param bool mirrorLayerMask: should mask be mirrored?
:param bool mirrorLayerWeights: should weights be mirrored?
:param bool mirrorDualQuaternion: should dual quaternion blend weights be mirrored?
:param int mirrorDirection: direction to mirror; use MirrorDirection constants for reference.
'''
self.ngSkinLayerCmd(
id = layerId,
mirrorWidth=mirrorWidth,
mirrorLayerWeights=mirrorLayerWeights,
mirrorLayerMask=mirrorLayerMask,
mirrorLayerDq=mirrorDualQuaternion,
mirrorDirection=mirrorDirection
)
[docs] def beginDataUpdate(self):
'''
starts batch data update mode, putting layer data into suspended state - certain
internal updates are switched off, making multiple layer data changes like setLayerWeights
or setLayerOpacity run faster; updates will take place when endDataUpdate is called.
begin..endDataUpdate() pairs can be stacked (e.g. methods inside begin..end can call begin..end
themselves) - updates will resume only when most outer pair finishes executing.
'''
self.ngSkinLayerCmd(beginDataUpdate=True)
[docs] def endDataUpdate(self):
'''
end batch update.
'''
self.ngSkinLayerCmd(endDataUpdate=True)
[docs] def batchUpdateContext(self):
'''
a helper method to use in a "with" statement, e.g.:
.. code-block:: python
with mll.batchUpdateContext():
mll.setLayerWeights(...)
mll.setLayerOpacity(...)
this is the same as:
.. code-block:: python
mll.beginDataUpdate()
try:
mll.setLayerWeights(...)
mll.setLayerOpacity(...)
finally:
mll.endDataUpdate()
'''
return BatchUpdateContext(self)
[docs] def setWeightsReferenceMesh(self,vertices,triangles):
'''
create an in-memory reference mesh with layer manager initialized;
this mesh and layer info can be accessed later by setting target mesh to
MllInterface.TARGET_REFERENCE_MESH.
:param list vertices: a float array, listing x y z for first vertex, then second, etc;
:param list triangles: an int array, listing vertex IDs for first triangle, then second, etc.
'''
self.ngSkinLayerCmd(e=True,
referenceMeshVertices=self.__floatListAsString(vertices),
referenceMeshTriangles=self.__intListAsString(triangles)
)
[docs] def getReferenceMeshVerts(self):
'''
:return: a list of floats, where each three values is a vertex XYZ for reference mesh vertices
'''
return self.ngSkinLayerCmd(q=True,
referenceMeshVertices=True)
[docs] def getReferenceMeshTriangles(self):
'''
:return: a list of integers, where each three values describe mesh vert IDs that make up a triangle in the reference mesh
'''
return self.ngSkinLayerCmd(q=True,
referenceMeshTriangles=True)
[docs] def getInfluenceLimitPerVertex(self):
'''
:return: current value of "influence limit per vertex" setting for this mesh. ``0`` is returned when there's no limit.
'''
return self.ngSkinLayerCmd(q=True,influenceLimitPerVertex=True)
[docs] def setInfluenceLimitPerVertex(self,limit=None):
'''
Set max number of influences permitted for this mesh/skin cluster. This limit will
be applied as a post-processing filter, before writting data to skin cluster.
See ngSkinTools user manual for more info on this mechanic.
:param int limit: max number of influences allowed; provide ``0`` or ``None`` to remove the limit.
'''
if limit is None:
limit=0
self.ngSkinLayerCmd(e=True,influenceLimitPerVertex=limit)
[docs] def listInfluenceIndexes(self):
'''
:return: list of logical indexes for all influences in this skin cluster
'''
return self.ngSkinLayerCmd(q=True,influenceIndexes=True)
[docs] def listInfluencePaths(self):
'''
:return: list of influence names for all influences in this skin cluster
'''
return self.ngSkinLayerCmd(q=True,influencePaths=True)
[docs] def listInfluencePivots(self):
'''
Returns coordinates for influence pivots for all influences in this skin cluster;
:return: a list where each element is 3-float tuple, e.g. ```[(1.0,2.0,3.0),(0,0,0),...]```
'''
influencePivots = self.ngSkinLayerCmd(q=True,influencePivots=True)
return zip(influencePivots[0::3],influencePivots[1::3],influencePivots[2::3])
[docs] def listInfluenceInfo(self):
'''
Returns a list of influences in this skin cluster, where each influence
is represented as :py:class:`.importExport.InfluenceInfo`.
:rtype: List[ngSkinTools.importExport.InfluenceInfo]
'''
from ngSkinTools.importExport import InfluenceInfo
influenceIndexes = self.listInfluenceIndexes()
if not influenceIndexes:
return []
influences = []
influencePaths = self.listInfluencePaths()
influencePivots = self.listInfluencePivots()
for index,path,pivot in izip(influenceIndexes,influencePaths,influencePivots):
influence = InfluenceInfo()
influence.pivot = pivot
influence.path = path
influence.logicalIndex = index
influences.append(influence)
return influences
@staticmethod
def influencesMapToList(influencesMapping):
return ','.join(str(k)+","+str(v) for (k,v) in influencesMapping.items())
[docs] def transferWeights(self,targetMesh,influencesMapping,vertexTransferMode=None):
'''
Transfer weights from self.mesh onto the targetMesh. Layers are always appended to the list
of existing layers;
if you wish to replace old layers, delete them after the transfer (doing so before
might deform the mesh and mess up vertex transfer associations).
:param str targetMesh: a mesh to perform transfer to
:param dict influencesMapping: a dictionary of [source influence logical ID]->destination influence logical ID
:param str vertexTransferMode: "closestPoint","uvSpace" or "vertexId"
'''
call = partial(self.ngSkinLayerCmd,targetMesh,transferSkinData=True,
influencesMapping=self.influencesMapToList(influencesMapping))
if vertexTransferMode is not None:
call = partial(call,vertexTransferMode=vertexTransferMode)
call()
def setManualMirrorInfluences(self,sourceDestinationMap):
self.ngSkinLayerCmd(manualInfluenceMappings=self.influencesMapToList(sourceDestinationMap))
def getManualMirrorInfluences(self):
values = self.ngSkinLayerCmd(q=True,manualInfluenceMappings=True)
if values is None:
return {}
return dict(zip(values[0::2],values[1::2]))
[docs] def layerMergeDown(self,layerId):
'''
Merges given layer the layer below it.
:param int layerId: layer to be merged
'''
self.ngSkinLayerCmd(e=True,layerId=int(layerId),layerMergeDown=True)
def getLayerIndex(self,layerId):
return self.ngSkinLayerCmdMel("-id {0} -q -layerIndex".format(layerId))
def setLayerIndex(self,layerId,layerIndex):
self.ngSkinLayerCmd(e=True, id=int(layerId), layerIndex=int(layerIndex))
[docs] def pruneWeights(self,layerId=None,threshold=0.01):
'''
Remove weights in influence weights lower than provided threshold;
upscale remaining weights, preserving transparency of the layer.
:param int layerId: layer to be processed; if not provided, will use currently active layer
:param float threshold: weights below this value will be set to 0
'''
self.ngSkinLayerCmd(e=True,layerId=int(layerId),pruneWeights=True,pruneWeightsThreshold=threshold)
[docs] def pruneMask(self,layerId=None,threshold=0.01):
'''
remove weights in layer mask lower than provided threshold;
'''
self.ngSkinLayerCmd(e=True,layerId=int(layerId),pruneMask=True,pruneWeightsThreshold=threshold)
def setPruneWeightsFilter(self,threshold):
self.ngSkinLayerCmd(e=True,pruneWeightsFilterThreshold=threshold)
def getPruneWeightsFilter(self):
return self.ngSkinLayerCmd(q=True,pruneWeightsFilterThreshold=True)
def floodPaint(self):
self.ngSkinLayerCmd(paintFlood=True)
[docs] def setPaintMode(self,mode,intensity):
'''
Sets paint mode (replace,add,..) and it's intensity
:param mode: use :py:class:`PaintMode` constants for that
'''
cmds.ngSkinLayer(paintOperation=mode,paintIntensity=intensity)
def getVertexSelectionWeights(self):
return self.ngSkinLayerCmd(q=True,vertexSelectionWeights=True)
def cacheIndexedColors(self):
for i in range(30):
c = cmds.colorIndex(i+1,q=True)
self.ngSkinLayerCmd(indexedColorIndex=(i+1),indexedColorValue=self.__floatListAsString(c))
def setPaintOptionRedistributeWeight(self,value):
return cmds.ngSkinToolsPaintToolCmd(redistributeRemovedWeight=value)
def getPaintOptionRedistributeWeight(self):
return cmds.ngSkinToolsPaintToolCmd(q=True, redistributeRemovedWeight=True)
[docs]class BatchUpdateContext:
'''
A helper class for MllInterface.batchUpdateContext() method, helping
implement "with" statement setup/teardown functionality
'''
def __init__(self,mll):
self.mll = mll
def __enter__(self):
self.mll.beginDataUpdate()
return self.mll
def __exit__(self, _type, value, traceback):
self.mll.endDataUpdate()