def testAddManualInfluence(self): openMayaFile('simplemirror.ma') cmds.select("testMeshY") mll = MllInterface() mll.initLayers() mll.setCurrentLayer(mll.createLayer("test layer")) window = MainWindow.open() mirrorTab = window.findTab(TabMirror) initWindow = mirrorTab.execInitMirror() def selectPairAndClickOk(dialog): log.info("running inside modal dialog "+str(dialog)) dialog.sourceDropdown.setValue(2) dialog.destinationDropdown.setValue(3) dialog.chkSelfReference.setValue(False) #dialog.closeDialogWithResult(BaseDialog.BUTTON_OK) closeNextDialogWithResult(BaseDialog.BUTTON_OK) log.info("modal dialog setup") runInNextModalDialog(selectPairAndClickOk) log.info("modal dialog setup ended") initWindow.content.addPairAction.execute() self.assertEquals(str(initWindow.content.items[0]),"[M] L_joint2 <-> L_joint3", "manual pair addition failed")
class LayerData(object): ''' Intermediate data object between ngSkinTools core and importers/exporters, representing all layers info in one skin cluster. .. py:attribute:: layers a list of :py:class:`Layer` objects. .. py:attribute:: influences a list of :py:class:`InfluenceInfo` objects. Provides information about influences that were found on exported skin data, and used for influence matching when importing. ''' def __init__(self): self.layers = [] self.mll = MllInterface() self.meshInfo = MeshInfo() self.influences = [] # a map [sourceInfluenceName] -> [destinationInfluenceName] self.mirrorInfluenceAssociationOverrides = None self.skinClusterFn = None def addMirrorInfluenceAssociationOverride(self, sourceInfluence, destinationInfluence=None, selfReference=False, bidirectional=True): ''' Adds mirror influence association override, similar to UI of "Add influences association". Self reference creates a source<->source association, bidirectional means that destination->source link is added as well ''' if self.mirrorInfluenceAssociationOverrides is None: self.mirrorInfluenceAssociationOverrides = {} if selfReference: self.mirrorInfluenceAssociationOverrides[ sourceInfluence] = sourceInfluence return if destinationInfluence is None: raise MessageException("destination influence must be specified") self.mirrorInfluenceAssociationOverrides[ sourceInfluence] = destinationInfluence if bidirectional: self.mirrorInfluenceAssociationOverrides[ destinationInfluence] = sourceInfluence def addLayer(self, layer): ''' register new layer into this data object :param Layer layer: layer object to add. ''' assert isinstance(layer, Layer) self.layers.append(layer) @staticmethod def getFullNodePath(nodeName): result = cmds.ls(nodeName, l=True) if result is None or len(result) == 0: raise MessageException("node %s was not found" % nodeName) return result[0] def loadInfluenceInfo(self): self.influences = self.mll.listInfluenceInfo() def loadFrom(self, mesh): ''' loads data from actual skin cluster and prepares it for exporting. supply skin cluster or skinned mesh as an argument ''' self.mll.setCurrentMesh(mesh) meshExporter = MeshDataExporter() self.meshInfo = MeshInfo() if mesh != MllInterface.TARGET_REFERENCE_MESH: mesh, skinCluster = self.mll.getTargetInfo() meshExporter.setTransformMatrixFromNode(mesh) meshExporter.useSkinClusterInputMesh(skinCluster) self.meshInfo.verts, self.meshInfo.triangles = meshExporter.export( ) else: self.meshInfo.verts = self.mll.getReferenceMeshVerts() self.meshInfo.triangles = self.mll.getReferenceMeshTriangles() self.loadInfluenceInfo() # map LayerId->layerIndex layerIndexById = {} for index, (layerID, layerName, parentId) in enumerate(self.mll.listLayers()): layerIndexById[layerID] = index self.mirrorInfluenceAssociationOverrides = self.mll.getManualMirrorInfluences( ) if len(self.mirrorInfluenceAssociationOverrides) == 0: self.mirrorInfluenceAssociationOverrides = None layer = Layer() layer.name = layerName self.addLayer(layer) layer.opacity = self.mll.getLayerOpacity(layerID) layer.enabled = self.mll.isLayerEnabled(layerID) layer.mask = self.mll.getLayerMask(layerID) layer.dqWeights = self.mll.getDualQuaternionWeights(layerID) # transform parent ID to local index in the model if parentId is not None: layer.parent = layerIndexById[parentId] for inflName, logicalIndex in self.mll.listLayerInfluences( layerID, activeInfluences=True): if inflName == '': inflName = None influence = Influence() if inflName is not None: influence.influenceName = self.getFullNodePath(inflName) influence.logicalIndex = logicalIndex layer.addInfluence(influence) influence.weights = self.mll.getInfluenceWeights( layerID, logicalIndex) def __validate(self): numVerts = self.mll.getVertCount() def validateVertCount(count, message): if count != numVerts: raise Exception(message) for layer in self.layers: if layer.mask is not None and len(layer.mask) != 0: validateVertCount( len(layer.mask), "Invalid vertex count for mask in layer '%s': expected size is %d" % (layer.name, numVerts)) for influence in layer.influences: validateVertCount( len(influence.weights), "Invalid weights count for influence '%s' in layer '%s': expected size is %d" % (influence.influenceName, layer.name, numVerts)) if self.skinClusterFn: influence.logicalIndex = self.skinClusterFn.getLogicalInfluenceIndex( influence.influenceName) @Utils.undoable def saveTo(self, mesh): ''' saveTo(self,mesh) saves data to actual skin cluster ''' # set target to whatever was provided self.mll.setCurrentMesh(mesh) if mesh == MllInterface.TARGET_REFERENCE_MESH: self.mll.setWeightsReferenceMesh(self.meshInfo.verts, self.meshInfo.triangles) if not self.mll.getLayersAvailable(): self.mll.initLayers() if not self.mll.getLayersAvailable(): raise Exception("could not initialize layers") # is skin cluster available? if mesh != MllInterface.TARGET_REFERENCE_MESH: mesh, self.skinCluster = self.mll.getTargetInfo() self.skinClusterFn = SkinClusterFn() self.skinClusterFn.setSkinCluster(self.skinCluster) self.__validate() # set target to actual mesh self.mll.setCurrentMesh(mesh) with self.mll.batchUpdateContext(): if self.mirrorInfluenceAssociationOverrides: self.mll.setManualMirrorInfluences( self.mirrorInfluenceAssociationOverrides) # IDS of created layers, in reverse order layerIds = [] for layer in reversed(self.layers): layerId = self.mll.createLayer(name=layer.name, forceEmpty=True) layerIds.append(layerId) self.mll.setCurrentLayer(layerId) if layerId is None: raise Exception( "import failed: could not create layer '%s'" % (layer.name)) self.mll.setLayerOpacity(layerId, layer.opacity) self.mll.setLayerEnabled(layerId, layer.enabled) self.mll.setLayerMask(layerId, layer.mask) self.mll.setDualQuaternionWeights(layerId, layer.dqWeights) self.mll.setLayerWeightsBufferSize(layerId, len(layer.influences)) for influence in layer.influences: # because layer was just created and will belong to same undo chunk, disabling undo # for setting weights bit self.mll.setInfluenceWeights(layerId, influence.logicalIndex, influence.weights, undoEnabled=False) # layer.parent reference index is in normal order, therefore need to reverse it again layerIds = list(reversed(layerIds)) for index, layer in enumerate(self.layers): if layer.parent is not None: self.mll.setLayerParent(layerIds[index], layerIds[layer.parent]) # reparenting will move it to the end of the list; move it to the bottom instead self.mll.setLayerIndex(layerIds[index], 0) def __repr__(self): return "[LayerDataModel(%r)]" % self.layers def getAllInfluences(self): ''' a convenience method to retrieve a list of names of all influences used in this layer data object ''' result = set() for layer in self.layers: for influence in layer.influences: result.add(influence.influenceName) return tuple(result)
class LayerDataModel: log = getLogger("layerDataModel") # holds instance of singleton object __instance = None @staticmethod def getInstance(): ''' returns singleton instance of LayerDataModel :rtype: LayerDataModel ''' return LayerDataModel.__instance @classmethod def bindAll(cls): cls.__instance = LayerDataModel() MayaEvents.undoRedoExecuted.addHandler( cls.__instance.updateLayerAvailability) MayaEvents.nodeSelectionChanged.addHandler( cls.__instance.updateLayerAvailability) cls.__instance.updateLayerAvailability() def __init__(self): self.layerDataAvailable = None self.mll = MllInterface() self.clipboard = WeightsClipboard(self.mll) def setLayerListsUI(self, ui): self.layerListsUI = ui def getLayerListsUI(self): ''' :rtype: LayerListsUI ''' from ngSkinTools.ui import mainwindow mainWindow = mainwindow.MainWindow.getInstance() if mainWindow is None: return None return mainWindow.getLayersUI() def getSelectedLayer(self): listsUi = self.getLayerListsUI() if listsUi is None: return None return listsUi.getLayersList().getSelectedID() def getSelectedLayers(self): listsUi = self.getLayerListsUI() if listsUi is None: return [] return listsUi.getSelectedLayers() def getSelectedInfluenceIds(self): listsUi = self.getLayerListsUI() if listsUi is None: return [] return listsUi.getSelectedInfluenceIds() def updateLayerAvailability(self): ''' checks if availability of skin layers changed with the current scene selection ''' self.log.info("updating layer availability") oldValue = self.layerDataAvailable self.layerDataAvailable = self.mll.getLayersAvailable() if self.layerDataAvailable != oldValue: LayerEvents.layerAvailabilityChanged.emit() @Utils.undoable def addLayer(self, name): def guessParent(): currentLayer = self.mll.getCurrentLayer() if currentLayer is None: return None # guess layer's new parent parentsByLayerId = dict([ (layerId, parentId) for layerId, _, parentId in self.mll.listLayers() if currentLayer in (layerId, parentId) ]) # current layer is a parent? if currentLayer in parentsByLayerId.values(): return currentLayer # current layer has parent if currentLayer in parentsByLayerId.keys(): return parentsByLayerId[currentLayer] layerId = self.mll.createLayer(name) self.mll.setLayerParent(layerId, guessParent()) if layerId is None: return None LayerEvents.layerListModified.emit() self.setCurrentLayer(layerId) return layerId def removeLayer(self, layerId): self.mll.deleteLayer(layerId) LayerEvents.layerListModified.emit() LayerEvents.currentLayerChanged.emit() def setCurrentLayer(self, layerId): self.mll.setCurrentLayer(layerId) LayerEvents.currentLayerChanged.emit() def getCurrentLayer(self): return self.mll.getCurrentLayer() def attachLayerData(self): self.mll.initLayers() with self.mll.batchUpdateContext(): self.addLayer('Base Weights') self.updateLayerAvailability() selectionState.selectionInfo.dropCache() def cleanCustomNodes(self): ''' removes all custom nodes from current scene ''' # just in case we were in the middle of painting cmds.setToolTo('selectSuperContext') LayerUtils.deleteCustomNodes() # notify the rest of the world self.updateLayerAvailability() selectionState.selectionInfo.dropCache() def getLayerName(self, layerId): return mel.eval('ngSkinLayer -id {0} -q -name'.format(int(layerId))) def setLayerName(self, layerId, name): self.mll.setLayerName(layerId, name) LayerEvents.nameChanged.emit() def getLayerOpacity(self, layerId): return mel.eval('ngSkinLayer -id {0} -q -opacity'.format(layerId)) def getLayerEnabled(self, layerId): return mel.eval('ngSkinLayer -id {0} -q -enabled'.format(layerId)) def setLayerEnabled(self, layerId, enabled): cmds.ngSkinLayer(e=True, id=layerId, enabled=1 if enabled else 0) def toggleLayerEnabled(self, layerId): self.setLayerEnabled(layerId, not self.getLayerEnabled(layerId)) def getLayersCandidateFromSelection(self): ''' for given selection, returns mesh and skin cluster node names where skinLayer data is (or can be) attached. ''' return self.mll.getTargetInfo() def getLayersAvailable(self): self.updateLayerAvailability() return self.layerDataAvailable def isDqMode(self): ''' returns True if current skin cluster is operating in dual quaternion mode ''' target = self.mll.getTargetInfo() if not target: return False skinCluster = target[1] return cmds.skinCluster(skinCluster, q=True, skinMethod=True) == 2
class CopyPasteActionsTest(AdditionalAsserts, unittest.TestCase): def setUp(self): testUtils.openMayaFile("normalization.ma") self.mll = MllInterface() cmds.select("testMesh") self.mll.initLayers() self.mll.createLayer("initial weights") self.layer = self.mll.createLayer("second layer") self.mll.setCurrentLayer(self.layer) self.clipboard = WeightsClipboard(self.mll) self.newWeights = [0.5]*self.mll.getVertCount() def tearDown(self): unittest.TestCase.tearDown(self) @insideMayaOnly def testCantCopyEmptyMask(self): with self.assertRaises(MessageException, "Nothing copied"): self.mll.setCurrentPaintTarget(LayerUtils.PAINT_TARGET_MASK) self.clipboard.withCurrentLayerAndInfluence().copy() # shold not be able to paste after this with self.assertRaises(MessageException, "Nothing to paste"): self.clipboard.withCurrentLayerAndInfluence().paste(True) @insideMayaOnly def testCantPasteIfNothingIsThere(self): with self.assertRaises(MessageException, "Nothing to paste"): self.clipboard.withCurrentLayerAndInfluence().paste(True) @insideMayaOnly def testCopyMask(self): # set mask to something self.mll.setLayerMask(self.layer,self.newWeights) self.assertArraysEqual(self.mll.getLayerMask(self.layer), self.newWeights) self.mll.setCurrentPaintTarget(LayerUtils.PAINT_TARGET_MASK) self.clipboard.withCurrentLayerAndInfluence().copy() self.assertArraysEqual(self.clipboard.copiedWeights, self.newWeights) @insideMayaOnly def testPasteMaskReplace(self): self.mll.setLayerMask(self.layer,self.newWeights) self.mll.setCurrentPaintTarget(LayerUtils.PAINT_TARGET_MASK) self.clipboard.copiedWeights = self.newWeights self.clipboard.withCurrentLayerAndInfluence().paste(True) self.assertArraysEqual(self.mll.getLayerMask(self.layer), self.newWeights) @insideMayaOnly def testPasteInfluenceReplace(self): influence = 1 self.mll.setInfluenceWeights(self.layer, influence, self.newWeights) self.mll.setCurrentPaintTarget(influence) self.clipboard.copiedWeights = self.newWeights self.clipboard.withCurrentLayerAndInfluence().paste(True) self.assertArraysEqual(self.mll.getInfluenceWeights(self.layer,influence), self.newWeights)
class LayerDataModel: log = LoggerFactory.getLogger("layerDataModel") class MirrorCacheStatus: def __init__(self): self.isValid = None self.message = None self.mirrorAxis = None # holds instance of singleton object __instance = None @staticmethod def getInstance(): ''' returns singleton instance of LayerDataModel :rtype: LayerDataModel ''' if LayerDataModel.__instance is None: LayerDataModel.__instance = LayerDataModel() return LayerDataModel.__instance @staticmethod def reset(): LayerDataModel.__instance = None def __init__(self): self.layerListsUI = None # :type: LayerListsUI self.layerDataAvailable = None self.mirrorCache = self.MirrorCacheStatus() self.mll = MllInterface() self.clipboard = WeightsClipboard(self.mll) MayaEvents.undoRedoExecuted.addHandler(self.updateLayerAvailability) MayaEvents.nodeSelectionChanged.addHandler(self.updateLayerAvailability) self.updateLayerAvailability() def setLayerListsUI(self,ui): self.layerListsUI = ui def getSelectedLayer(self): if self.layerListsUI is None: return None return self.layerListsUI.getLayersList().getSelectedID() def updateLayerAvailability(self): ''' checks if availability of skin layers changed with the current scene selection ''' self.log.info("updating layer availability") oldValue = self.layerDataAvailable self.layerDataAvailable = self.mll.getLayersAvailable() if self.layerDataAvailable!=oldValue: LayerEvents.layerAvailabilityChanged.emit() self.updateMirrorCacheStatus() def updateMirrorCacheStatus(self): def setStatus(newStatus,message,axis=None): change = newStatus != self.mirrorCache.isValid or self.mirrorCache.message != message or self.mirrorCache.mirrorAxis != axis self.mirrorCache.message = message self.mirrorCache.isValid = newStatus self.mirrorCache.mirrorAxis = axis if change: self.log.info("mirror cache status changed to %s." % self.mirrorCache.message) LayerEvents.mirrorCacheStatusChanged.emit() self.log.info("updating mirror cache status") if not self.layerDataAvailable: setStatus(False,"Layer Data is not available") return try: cacheInfo = cmds.ngSkinLayer(q=True,mirrorCacheInfo=True) if cacheInfo[0]=='ok': setStatus(True,'Mirror Data Initialized',cmds.ngSkinLayer(q=True,mirrorAxis=True)) else: setStatus(False,cacheInfo[1]) except : setStatus(False,'Cache check failed') #log.error("error: "+str(err)) def addLayer(self,name): layerId = self.mll.createLayer(name) if layerId is None: return LayerEvents.layerListModified.emit() self.setCurrentLayer(layerId) def removeLayer(self,layerId): self.mll.deleteLayer(layerId) LayerEvents.layerListModified.emit() LayerEvents.currentLayerChanged.emit() def setCurrentLayer(self,layerId): self.mll.setCurrentLayer(layerId) LayerEvents.currentLayerChanged.emit() def getCurrentLayer(self): return self.mll.getCurrentLayer() def attachLayerData(self): self.mll.initLayers() self.addLayer('Base Weights') self.updateLayerAvailability() def cleanCustomNodes(self): ''' removes all custom nodes from current scene ''' LayerUtils.deleteCustomNodes() self.updateLayerAvailability() def getLayerName(self,layerId): return mel.eval('ngSkinLayer -id %d -q -name' % layerId) def setLayerName(self,layerId,name): self.mll.setLayerName(layerId,name) LayerEvents.nameChanged.emit() def getLayerOpacity(self,layerId): return mel.eval('ngSkinLayer -id %d -q -opacity' % layerId) def getLayerEnabled(self,layerId): return mel.eval('ngSkinLayer -id %d -q -enabled' % layerId) def setLayerEnabled(self,layerId,enabled): cmds.ngSkinLayer(e=True,id=layerId,enabled=1 if enabled else 0) def toggleLayerEnabled(self,layerId): self.setLayerEnabled(layerId, not self.getLayerEnabled(layerId)) def getLayersCandidateFromSelection(self): ''' for given selection, returns mesh and skin cluster node names where skinLayer data is (or can be) attached. ''' return self.mll.getTargetInfo() def getLayersAvailable(self): self.updateLayerAvailability() return self.layerDataAvailable def isDqMode(self): ''' returns True if current skin cluster is operating in dual quaternion mode ''' target = self.mll.getTargetInfo() if not target: return False skinCluster = target[1] return cmds.skinCluster(skinCluster,q=True,skinMethod=True)==2
class LayerData(object): ''' Intermediate data object between ngSkinTools core and importers/exporters, representing all layers info in one skin cluster. .. py:attribute:: layers a list of :py:class:`Layer` objects. .. py:attribute:: influences a list of :py:class:`InfluenceInfo` objects. Provides information about influences that were found on exported skin data, and used for influence matching when importing. ''' def __init__(self): self.layers = [] self.mll = MllInterface() self.meshInfo = MeshInfo() self.influences = [] # a map [sourceInfluenceName] -> [destinationInfluenceName] self.mirrorInfluenceAssociationOverrides = None self.skinClusterFn = None def addMirrorInfluenceAssociationOverride(self,sourceInfluence,destinationInfluence=None,selfReference=False,bidirectional=True): ''' Adds mirror influence association override, similar to UI of "Add influences association". Self reference creates a source<->source association, bidirectional means that destination->source link is added as well ''' if self.mirrorInfluenceAssociationOverrides is None: self.mirrorInfluenceAssociationOverrides = {} if selfReference: self.mirrorInfluenceAssociationOverrides[sourceInfluence] = sourceInfluence return if destinationInfluence is None: raise MessageException("destination influence must be specified") self.mirrorInfluenceAssociationOverrides[sourceInfluence] = destinationInfluence if bidirectional: self.mirrorInfluenceAssociationOverrides[destinationInfluence] = sourceInfluence def addLayer(self, layer): ''' register new layer into this data object :param Layer layer: layer object to add. ''' assert isinstance(layer, Layer) self.layers.append(layer) @staticmethod def getFullNodePath(nodeName): result = cmds.ls(nodeName,l=True) if result is None or len(result)==0: raise MessageException("node %s was not found" % nodeName) return result[0] def loadInfluenceInfo(self): self.influences = self.mll.listInfluenceInfo() def loadFrom(self, mesh): ''' loads data from actual skin cluster and prepares it for exporting. supply skin cluster or skinned mesh as an argument ''' self.mll.setCurrentMesh(mesh) meshExporter = MeshDataExporter() self.meshInfo = MeshInfo() if mesh!=MllInterface.TARGET_REFERENCE_MESH: mesh,skinCluster = self.mll.getTargetInfo() meshExporter.setTransformMatrixFromNode(mesh) meshExporter.useSkinClusterInputMesh(skinCluster) self.meshInfo.verts,self.meshInfo.triangles = meshExporter.export() else: self.meshInfo.verts = self.mll.getReferenceMeshVerts() self.meshInfo.triangles = self.mll.getReferenceMeshTriangles() self.loadInfluenceInfo() for layerID, layerName in self.mll.listLayers(): self.mirrorInfluenceAssociationOverrides = self.mll.getManualMirrorInfluences() if len(self.mirrorInfluenceAssociationOverrides)==0: self.mirrorInfluenceAssociationOverrides = None layer = Layer() layer.name = layerName self.addLayer(layer) layer.opacity = self.mll.getLayerOpacity(layerID) layer.enabled = self.mll.isLayerEnabled(layerID) layer.mask = self.mll.getLayerMask(layerID) layer.dqWeights = self.mll.getDualQuaternionWeights(layerID) for inflName, logicalIndex in self.mll.listLayerInfluences(layerID,activeInfluences=True): if inflName=='': inflName = None influence = Influence() if inflName is not None: influence.influenceName = self.getFullNodePath(inflName) influence.logicalIndex = logicalIndex layer.addInfluence(influence) influence.weights = self.mll.getInfluenceWeights(layerID, logicalIndex) def __validate(self): numVerts = self.mll.getVertCount() def validateVertCount(count,message): if count!=numVerts: raise Exception(message) for layer in self.layers: maskLen = len(layer.mask) if maskLen != 0: validateVertCount(maskLen, "Invalid vertex count for mask in layer '%s': expected size is %d" % (layer.name, numVerts)) for influence in layer.influences: validateVertCount(len(influence.weights), "Invalid weights count for influence '%s' in layer '%s': expected size is %d" % (influence.influenceName, layer.name, numVerts)) if self.skinClusterFn: influence.logicalIndex = self.skinClusterFn.getLogicalInfluenceIndex(influence.influenceName) @Utils.undoable def saveTo(self, mesh): ''' saveTo(self,mesh) saves data to actual skin cluster ''' # set target to whatever was provided self.mll.setCurrentMesh(mesh) if mesh==MllInterface.TARGET_REFERENCE_MESH: self.mll.setWeightsReferenceMesh(self.meshInfo.verts, self.meshInfo.triangles) if not self.mll.getLayersAvailable(): self.mll.initLayers() if not self.mll.getLayersAvailable(): raise Exception("could not initialize layers") # is skin cluster available? if mesh!=MllInterface.TARGET_REFERENCE_MESH: mesh, self.skinCluster = self.mll.getTargetInfo() self.skinClusterFn = SkinClusterFn() self.skinClusterFn.setSkinCluster(self.skinCluster) self.__validate() # set target to actual mesh self.mll.setCurrentMesh(mesh) with self.mll.batchUpdateContext(): if self.mirrorInfluenceAssociationOverrides: self.mll.setManualMirrorInfluences(self.mirrorInfluenceAssociationOverrides) for layer in reversed(self.layers): layerId = self.mll.createLayer(name=layer.name, forceEmpty=True) self.mll.setCurrentLayer(layerId) if layerId is None: raise Exception("import failed: could not create layer '%s'" % (layer.name)) self.mll.setLayerOpacity(layerId, layer.opacity) self.mll.setLayerEnabled(layerId, layer.enabled) self.mll.setLayerMask(layerId, layer.mask) self.mll.setDualQuaternionWeights(layerId, layer.dqWeights) for influence in layer.influences: self.mll.setInfluenceWeights(layerId, influence.logicalIndex, influence.weights) def __repr__(self): return "[LayerDataModel(%r)]" % self.layers def getAllInfluences(self): ''' a convenience method to retrieve a list of names of all influences used in this layer data object ''' result = set() for layer in self.layers: for influence in layer.influences: result.add(influence.influenceName) return tuple(result)
class LayerData(object): ''' Intermediate data object between ngSkinTools core and importers/exporters, representing all layers info in one skin cluster. ''' def __init__(self): #: layers list self.layers = [] self.mll = MllInterface() # a map [sourceInfluenceName] -> [destinationInfluenceName] self.mirrorInfluenceAssociationOverrides = {} def addMirrorInfluenceAssociationOverride(self, sourceInfluence, destinationInfluence=None, selfReference=False, bidirectional=True): ''' Adds mirror influence association override, similar to UI of "Add influences association". Self reference creates a source<->source association, bidirectional means that destination->source link is added as well ''' if selfReference: self.mirrorInfluenceAssociationOverrides[ sourceInfluence] = sourceInfluence return if destinationInfluence is None: raise MessageException("destination influence must be specified") self.mirrorInfluenceAssociationOverrides[ sourceInfluence] = destinationInfluence if bidirectional: self.mirrorInfluenceAssociationOverrides[ destinationInfluence] = sourceInfluence def addLayer(self, layer): ''' register new layer into this data object ''' assert isinstance(layer, Layer) self.layers.append(layer) @staticmethod def getFullNodePath(nodeName): result = cmds.ls(nodeName, l=True) if result is None or len(result) == 0: raise MessageException("node %s was not found" % nodeName) return result[0] def loadFrom(self, mesh): ''' loads data from actual skin cluster and prepares it for exporting. supply skin cluster or skinned mesh as an argument ''' self.mll.setCurrentMesh(mesh) for layerID, layerName in self.mll.listLayers(): self.mirrorInfluenceAssociationOverrides = self.mll.listManualMirrorInfluenceAssociations( ) layer = Layer() layer.name = layerName self.addLayer(layer) layer.opacity = self.mll.getLayerOpacity(layerID) layer.enabled = self.mll.isLayerEnabled(layerID) layer.mask = self.mll.getLayerMask(layerID) for inflName, logicalIndex in self.mll.listLayerInfluences( layerID): influence = Influence() influence.influenceName = self.getFullNodePath(inflName) influence.logicalIndex = logicalIndex layer.addInfluence(influence) influence.weights = self.mll.getInfluenceWeights( layerID, logicalIndex) def __validate(self): numVerts = self.mll.getVertCount() for layer in reversed(self.layers): maskLen = len(layer.mask) if maskLen != 0 and maskLen != numVerts: raise Exception( "Invalid vertex count for mask in layer '%s': expected size is %d" % (layer.name, numVerts)) for influence in layer.influences: weightsLen = len(influence.weights) if weightsLen != numVerts: raise Exception( "Invalid weights count for influence '%s' in layer '%s': expected size is %d" % (influence.influenceName, layer.name, numVerts)) influence.logicalIndex = self.skinClusterFn.getLogicalInfluenceIndex( influence.influenceName) @Utils.undoable def saveTo(self, mesh): ''' saves data to actual skin cluster ''' # set target to whatever was provided self.mll.setCurrentMesh(mesh) if not self.mll.getLayersAvailable(): self.mll.initLayers() if not self.mll.getLayersAvailable(): raise Exception("could not initialize layers") mesh, self.skinCluster = self.mll.getTargetInfo() self.skinClusterFn = SkinClusterFn() self.skinClusterFn.setSkinCluster(self.skinCluster) self.__validate() # set target to actual mesh self.mll.setCurrentMesh(mesh) with self.mll.batchUpdateContext(): for source, destination in self.mirrorInfluenceAssociationOverrides: self.mll.addManualMirrorInfluenceAssociation( source, destination) for layer in reversed(self.layers): layerId = self.mll.createLayer(name=layer.name, forceEmpty=True) self.mll.setCurrentLayer(layerId) if layerId is None: raise Exception( "import failed: could not create layer '%s'" % (layer.name)) self.mll.setLayerOpacity(layerId, layer.opacity) self.mll.setLayerEnabled(layerId, layer.enabled) self.mll.setLayerMask(layerId, layer.mask) for influence in layer.influences: self.mll.setInfluenceWeights(layerId, influence.logicalIndex, influence.weights) def __repr__(self): return "[LayerDataModel(%r)]" % self.layers def getAllInfluences(self): ''' a convenience method to retrieve a list of names of all influences used in this layer data object ''' result = set() for layer in self.layers: for influence in layer.influences: result.add(influence.influenceName) return tuple(result)
class LayerData(object): ''' Intermediate data object between ngSkinTools core and importers/exporters, representing all layers info in one skin cluster. ''' def __init__(self): #: layers list self.layers = [] self.mll = MllInterface() # a map [sourceInfluenceName] -> [destinationInfluenceName] self.mirrorInfluenceAssociationOverrides = {} def addMirrorInfluenceAssociationOverride(self,sourceInfluence,destinationInfluence=None,selfReference=False,bidirectional=True): ''' Adds mirror influence association override, similar to UI of "Add influences association". Self reference creates a source<->source association, bidirectional means that destination->source link is added as well ''' if selfReference: self.mirrorInfluenceAssociationOverrides[sourceInfluence] = sourceInfluence return if destinationInfluence is None: raise MessageException("destination influence must be specified") self.mirrorInfluenceAssociationOverrides[sourceInfluence] = destinationInfluence if bidirectional: self.mirrorInfluenceAssociationOverrides[destinationInfluence] = sourceInfluence def addLayer(self, layer): ''' register new layer into this data object ''' assert isinstance(layer, Layer) self.layers.append(layer) @staticmethod def getFullNodePath(nodeName): result = cmds.ls(nodeName,l=True) if result is None or len(result)==0: raise MessageException("node %s was not found" % nodeName) return result[0] def loadFrom(self, mesh): ''' loads data from actual skin cluster and prepares it for exporting. supply skin cluster or skinned mesh as an argument ''' self.mll.setCurrentMesh(mesh) for layerID, layerName in self.mll.listLayers(): self.mirrorInfluenceAssociationOverrides = self.mll.listManualMirrorInfluenceAssociations() layer = Layer() layer.name = layerName self.addLayer(layer) layer.opacity = self.mll.getLayerOpacity(layerID) layer.enabled = self.mll.isLayerEnabled(layerID) layer.mask = self.mll.getLayerMask(layerID) for inflName, logicalIndex in self.mll.listLayerInfluences(layerID): influence = Influence() influence.influenceName = self.getFullNodePath(inflName) influence.logicalIndex = logicalIndex layer.addInfluence(influence) influence.weights = self.mll.getInfluenceWeights(layerID, logicalIndex) def __validate(self): numVerts = self.mll.getVertCount() for layer in reversed(self.layers): maskLen = len(layer.mask) if maskLen != 0 and maskLen != numVerts: raise Exception("Invalid vertex count for mask in layer '%s': expected size is %d" % (layer.name, numVerts)) for influence in layer.influences: weightsLen = len(influence.weights) if weightsLen != numVerts: raise Exception("Invalid weights count for influence '%s' in layer '%s': expected size is %d" % (influence.influenceName, layer.name, numVerts)) influence.logicalIndex = self.skinClusterFn.getLogicalInfluenceIndex(influence.influenceName) @Utils.undoable def saveTo(self, mesh): ''' saves data to actual skin cluster ''' # set target to whatever was provided self.mll.setCurrentMesh(mesh) if not self.mll.getLayersAvailable(): self.mll.initLayers() if not self.mll.getLayersAvailable(): raise Exception("could not initialize layers") mesh, self.skinCluster = self.mll.getTargetInfo() self.skinClusterFn = SkinClusterFn() self.skinClusterFn.setSkinCluster(self.skinCluster) self.__validate() # set target to actual mesh self.mll.setCurrentMesh(mesh) with self.mll.batchUpdateContext(): for source,destination in self.mirrorInfluenceAssociationOverrides.iteritems(): self.mll.addManualMirrorInfluenceAssociation(source, destination) for layer in reversed(self.layers): layerId = self.mll.createLayer(name=layer.name, forceEmpty=True) self.mll.setCurrentLayer(layerId) if layerId is None: raise Exception("import failed: could not create layer '%s'" % (layer.name)) self.mll.setLayerOpacity(layerId, layer.opacity) self.mll.setLayerEnabled(layerId, layer.enabled) self.mll.setLayerMask(layerId, layer.mask) for influence in layer.influences: self.mll.setInfluenceWeights(layerId, influence.logicalIndex, influence.weights) def __repr__(self): return "[LayerDataModel(%r)]" % self.layers def getAllInfluences(self): ''' a convenience method to retrieve a list of names of all influences used in this layer data object ''' result = set() for layer in self.layers: for influence in layer.influences: result.add(influence.influenceName) return tuple(result)
class LayerDataModel: log = LoggerFactory.getLogger("layerDataModel") class MirrorCacheStatus: def __init__(self): self.isValid = None self.message = None self.mirrorAxis = None # holds instance of singleton object __instance = None @staticmethod def getInstance(): ''' returns singleton instance of LayerDataModel :rtype: LayerDataModel ''' if LayerDataModel.__instance is None: LayerDataModel.__instance = LayerDataModel() return LayerDataModel.__instance @staticmethod def reset(): LayerDataModel.__instance = None def __init__(self): self.layerListsUI = None # :type: LayerListsUI self.layerDataAvailable = None self.mirrorCache = self.MirrorCacheStatus() self.mll = MllInterface() self.clipboard = WeightsClipboard(self.mll) MayaEvents.undoRedoExecuted.addHandler(self.updateLayerAvailability) MayaEvents.nodeSelectionChanged.addHandler( self.updateLayerAvailability) self.updateLayerAvailability() def setLayerListsUI(self, ui): self.layerListsUI = ui def getSelectedLayer(self): if self.layerListsUI is None: return None return self.layerListsUI.getLayersList().getSelectedID() def getSelectedLayers(self): if self.layerListsUI is None: return [] return self.layerListsUI.getSelectedLayers() def getSelectedInfluenceIds(self): if self.layerListsUI is None: return [] return self.layerListsUI.getSelectedInfluenceIds() def updateLayerAvailability(self): ''' checks if availability of skin layers changed with the current scene selection ''' self.log.info("updating layer availability") oldValue = self.layerDataAvailable self.layerDataAvailable = self.mll.getLayersAvailable() if self.layerDataAvailable != oldValue: LayerEvents.layerAvailabilityChanged.emit() self.updateMirrorCacheStatus() def updateMirrorCacheStatus(self): def setStatus(newStatus, message, axis=None): change = newStatus != self.mirrorCache.isValid or self.mirrorCache.message != message or self.mirrorCache.mirrorAxis != axis self.mirrorCache.message = message self.mirrorCache.isValid = newStatus self.mirrorCache.mirrorAxis = axis if change: self.log.info("mirror cache status changed to %s." % self.mirrorCache.message) LayerEvents.mirrorCacheStatusChanged.emit() self.log.info("updating mirror cache status") if not self.layerDataAvailable: setStatus(False, "Layer Data is not available") return try: cacheInfo = cmds.ngSkinLayer(q=True, mirrorCacheInfo=True) if cacheInfo[0] == 'ok': setStatus(True, 'Mirror Data Initialized', cmds.ngSkinLayer(q=True, mirrorAxis=True)) else: setStatus(False, cacheInfo[1]) except: setStatus(False, 'Cache check failed') #log.error("error: "+str(err)) def addLayer(self, name): layerId = self.mll.createLayer(name) if layerId is None: return LayerEvents.layerListModified.emit() self.setCurrentLayer(layerId) def removeLayer(self, layerId): self.mll.deleteLayer(layerId) LayerEvents.layerListModified.emit() LayerEvents.currentLayerChanged.emit() def setCurrentLayer(self, layerId): self.mll.setCurrentLayer(layerId) LayerEvents.currentLayerChanged.emit() def getCurrentLayer(self): return self.mll.getCurrentLayer() def attachLayerData(self): self.mll.initLayers() self.addLayer('Base Weights') self.updateLayerAvailability() def cleanCustomNodes(self): ''' removes all custom nodes from current scene ''' LayerUtils.deleteCustomNodes() self.updateLayerAvailability() def getLayerName(self, layerId): return mel.eval('ngSkinLayer -id %d -q -name' % layerId) def setLayerName(self, layerId, name): self.mll.setLayerName(layerId, name) LayerEvents.nameChanged.emit() def getLayerOpacity(self, layerId): return mel.eval('ngSkinLayer -id %d -q -opacity' % layerId) def getLayerEnabled(self, layerId): return mel.eval('ngSkinLayer -id %d -q -enabled' % layerId) def setLayerEnabled(self, layerId, enabled): cmds.ngSkinLayer(e=True, id=layerId, enabled=1 if enabled else 0) def toggleLayerEnabled(self, layerId): self.setLayerEnabled(layerId, not self.getLayerEnabled(layerId)) def getLayersCandidateFromSelection(self): ''' for given selection, returns mesh and skin cluster node names where skinLayer data is (or can be) attached. ''' return self.mll.getTargetInfo() def getLayersAvailable(self): self.updateLayerAvailability() return self.layerDataAvailable def isDqMode(self): ''' returns True if current skin cluster is operating in dual quaternion mode ''' target = self.mll.getTargetInfo() if not target: return False skinCluster = target[1] return cmds.skinCluster(skinCluster, q=True, skinMethod=True) == 2