class DuplicateLayersTest(AdditionalAsserts, unittest.TestCase): def setUp(self): unittest.TestCase.setUp(self) self.mll = MllInterface() self.setup = DuplicateLayers() self.setup.setMllInterface(self.mll) testUtils.openMayaFile("influence transfer.mb") self.mll.setCurrentMesh('sourceMesh') self.mll.initLayers() def testLayerName(self): self.assertEquals("layer1 copy",self.setup.createLayerName("layer1")) self.assertEquals("layer1 copy(2)",self.setup.createLayerName("layer1 copy")) self.assertEquals("layer1 copy(1000001)",self.setup.createLayerName("layer1 copy(1000000)")) @insideMayaOnly def testDuplicateOneLayer(self): id = self.mll.createLayer("layer1 copy") self.setup.addLayer(id) self.setup.execute() newId = self.setup.duplicateIds[0] self.assertEqual(self.mll.getLayerName(newId),'layer1 copy(2)') self.assertEqual(self.mll.isLayerEnabled(newId),True) self.assertAlmostEqual(self.mll.getLayerOpacity(newId),1.0) self.assertFloatArraysEqual(self.mll.getLayerMask(newId), []) self.assertFloatArraysEqual(self.mll.getInfluenceWeights(id, 1), self.mll.getInfluenceWeights(newId, 1)) @insideMayaOnly def testLayerOrder(self): id1 = self.mll.createLayer("layer1") id2 = self.mll.createLayer("layer2") def layerNames(): return [a[1] for a in self.mll.listLayers()] self.assertArraysEqual(layerNames(), ["layer2","layer1"]) self.setup.addLayer(id1) self.setup.addLayer(id2) self.setup.execute() # order respects "newer layers on top" self.assertArraysEqual(layerNames(), ["layer2 copy","layer1 copy","layer2","layer1"]) # individual indexes in copy array respect indexes in the original array self.assertEquals(self.mll.getLayerName(self.setup.duplicateIds[0]),"layer1 copy") self.assertEquals(self.mll.getLayerName(self.setup.duplicateIds[1]),"layer2 copy")
def testAddInfluence(self): self.importer.setSourceFromMesh("sourceMesh") self.importer.setDestinationFromMesh("destinationMesh") self.importer.addInfluence("joint3") influences = self.importer.listInfluencesDiff() self.assertArraysEqual(influences, ["|joint1"]) skinCluster = SkinClusterFn() skinCluster.setSkinCluster(self.importer.destinationSkinCluster) influenceLogicalIndex = skinCluster.getLogicalInfluenceIndex("joint3") mll = MllInterface() mll.setCurrentMesh("destinationMesh") mll.initLayers() layerId = mll.createLayer("test") weights = mll.getInfluenceWeights(layerId, influenceLogicalIndex) print weights self.assertFloatArraysEqual(weights, [0.0]*25)
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 DuplicateLayersTest(AdditionalAsserts, unittest.TestCase): def setUp(self): unittest.TestCase.setUp(self) self.mll = MllInterface() self.setup = DuplicateLayers() self.setup.setMllInterface(self.mll) testUtils.openMayaFile("influence transfer.mb") self.mll.setCurrentMesh('sourceMesh') self.mll.initLayers() def testLayerName(self): self.assertEquals("layer1 copy", self.setup.createLayerName("layer1")) self.assertEquals("layer1 copy(2)", self.setup.createLayerName("layer1 copy")) self.assertEquals("layer1 copy(1000001)", self.setup.createLayerName("layer1 copy(1000000)")) @insideMayaOnly def testDuplicateOneLayer(self): id = self.mll.createLayer("layer1 copy") self.setup.addLayer(id) self.setup.execute() newId = self.setup.duplicateIds[0] self.assertEqual(self.mll.getLayerName(newId), 'layer1 copy(2)') self.assertEqual(self.mll.isLayerEnabled(newId), True) self.assertAlmostEqual(self.mll.getLayerOpacity(newId), 1.0) self.assertFloatArraysEqual(self.mll.getLayerMask(newId), []) self.assertFloatArraysEqual(self.mll.getInfluenceWeights(id, 1), self.mll.getInfluenceWeights(newId, 1)) @insideMayaOnly def testLayerOrder(self): id1 = self.mll.createLayer("layer1") id2 = self.mll.createLayer("layer2") def layerNames(): return [a[1] for a in self.mll.listLayers()] self.assertArraysEqual(layerNames(), ["layer2", "layer1"]) self.setup.addLayer(id1) self.setup.addLayer(id2) self.setup.execute() # order respects "newer layers on top" self.assertArraysEqual( layerNames(), ["layer2 copy", "layer1 copy", "layer2", "layer1"]) # individual indexes in copy array respect indexes in the original array self.assertEquals(self.mll.getLayerName(self.setup.duplicateIds[0]), "layer1 copy") self.assertEquals(self.mll.getLayerName(self.setup.duplicateIds[1]), "layer2 copy")
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 UseNgBrush(object): """ setup for custom brush operations, a new instance is created on every brush stroke """ def __init__(self, surface_name): self.surface = surface_name self.stroke_id = None self.mode = 1 self.value = 1 self.volumeThreshold = -0.1 self.mll = MllInterface() self.selection_name = cmds.ls(selection=True) self.mesh = cmds.listRelatives(self.selection_name[0], shapes=True) self.ngs_layer_id = self.mll.getCurrentLayer() self.ngs_influence = self.mll.getCurrentPaintTarget() self.ngs_weight_list = self.mll.getInfluenceWeights( self.ngs_layer_id, self.ngs_influence) self.max_value = max(self.ngs_weight_list) self.min_value = min(self.ngs_weight_list) def stroke_initialize(self): """ this function is executed before each brush stroke """ cmds.undoInfo(openChunk=True, undoName="paint stroke") get_stroke_id = ngLayerPaintCtxInitialize(self.mesh[0]) self.stroke_id = int(get_stroke_id.split(" ")[1]) cmds.ngSkinLayer(paintOperation=self.mode, paintIntensity=self.value) self.stroke_update() return self.surface, self.stroke_id def stroke_finalize(self): """ this function is executed after each brush stroke """ if self.stroke_id: cmds.ngLayerPaintCtxFinalize(self.stroke_id) self.stroke_id = None cmds.undoInfo(closeChunk=True) def stroke_update(self): """ updates certain attributes for the brush instance """ # self.mll = MllInterface() # self.selection_name = cmds.ls(selection=True) # self.mesh = cmds.listRelatives(self.selection_name[0], shapes=True) # self.ngs_layer_id = self.mll.getCurrentLayer() # self.ngs_influence = self.mll.getCurrentPaintTarget() # self.ngs_weight_list = self.mll.getInfluenceWeights(self.ngs_layer_id, self.ngs_influence) # self.max_value = max(self.ngs_weight_list) # self.min_value = min(self.ngs_weight_list) self.volumeThreshold = cmds.floatSlider("volume_slider", q=True, value=True) def contrast_paint(self, vert_id, value): """ sharpens the edge of the active weight map """ vertex_weight = self.ngs_weight_list[vert_id] if not self.max_value > vertex_weight > self.min_value: return avg_value = (self.max_value + self.min_value) / 2 normalized_range = self.max_value - self.min_value dist_value = vertex_weight - avg_value modifier = norm.cdf(dist_value, loc=0, scale=(0.1 * normalized_range)) - vertex_weight contrast_weight = vertex_weight + (modifier * value) contrast_weight = max(self.min_value, min(contrast_weight, self.max_value)) cmds.ngSkinLayer(paintIntensity=contrast_weight) cmds.ngLayerPaintCtxSetValue(self.stroke_id, vert_id, 1) def conceal_paint(self, vert_id, value): """ smooth operation that only lowers weight values """ vertex_weight = self.ngs_weight_list[vert_id] if vertex_weight <= self.min_value: return vertex_name = '{}.vtx[{}]'.format(self.selection_name[0], vert_id) area_vertices = get_surrounding_verts(vertex_name) weight_list = [self.ngs_weight_list[int(x)] for x in area_vertices] weight_avg = sum(weight_list) / float(len(weight_list)) min_avg = min(weight_list) threshold = 0.01 / (value / 0.1) if weight_avg >= self.max_value and abs(weight_avg - vertex_weight) <= threshold: return weight_diff = abs(weight_avg - vertex_weight) conceal_weight = vertex_weight * (1 - (weight_diff * value)) conceal_weight = max(conceal_weight, min_avg) cmds.ngSkinLayer(paintIntensity=conceal_weight) cmds.ngLayerPaintCtxSetValue(self.stroke_id, vert_id, 1) def spread_paint(self, vert_id, value): """ smooth operation that only increases weight values """ vertex_weight = self.ngs_weight_list[vert_id] if vertex_weight >= self.max_value: return vertex_name = '.'.join([self.selection_name[0], 'vtx[%d]' % vert_id]) area_vertices = get_surrounding_verts(vertex_name) weight_list = [self.ngs_weight_list[int(x)] for x in area_vertices] weight_avg = sum(weight_list) / float(len(weight_list)) max_avg = max(weight_list) threshold = 0.01 / (value / 0.1) if weight_avg <= self.min_value and abs(weight_avg - vertex_weight) <= threshold: return weight_diff = abs(weight_avg - vertex_weight) spread_weight = vertex_weight + (weight_diff * value) spread_weight = min(spread_weight, max_avg) cmds.ngSkinLayer(paintIntensity=spread_weight) cmds.ngLayerPaintCtxSetValue(self.stroke_id, vert_id, 1) def gain_paint(self, vert_id, value): """ increases existing weight values but preserves empty weights """ vertex_weight = self.ngs_weight_list[vert_id] if vertex_weight == 0: return gain_weight = vertex_weight + (vertex_weight * value) gain_weight = min(gain_weight, 1) cmds.ngSkinLayer(paintIntensity=gain_weight) cmds.ngLayerPaintCtxSetValue(self.stroke_id, vert_id, 1) def volume_equalize(self, vert_id, value): """ i.e a volumetric match operation with a falloff. applies weight values inside the brush radius onto other vertices inside a spherical volume """ origin_vertex = '.'.join([self.selection_name[0], 'vtx[%s]']) % vert_id vertex_weight = self.ngs_weight_list[vert_id] v1 = cmds.pointPosition(origin_vertex) for i in range(len(self.ngs_weight_list)): target_weight = self.ngs_weight_list[i] if target_weight == vertex_weight: continue if i == vert_id: continue target_vertex = '.'.join([self.selection_name[0], 'vtx[%s]']) % i v2 = cmds.pointPosition(target_vertex) target_distance = sqrt((pow((v1[0] - v2[0]), 2)) + (pow((v1[1] - v2[1]), 2)) + (pow((v1[2] - v2[2]), 2))) if target_distance > (self.volumeThreshold * value): continue falloff = (self.volumeThreshold - target_distance) / self.volumeThreshold eq_weight = target_weight - (( (target_weight - vertex_weight) * value) * falloff) cmds.ngSkinLayer(paintIntensity=eq_weight) cmds.ngLayerPaintCtxSetValue(self.stroke_id, i, 1)
class MapOperations(object): """ class that contains operations applied to the entire mesh """ def __init__(self): self.mll = MllInterface() self.selection_name = [] self.ngs_layer_id = -1 self.ngs_influence = None self.ngs_weight_list = [] self.ngs_vert_count = -1 self.max_value = -1.0 self.min_value = -1.0 def get_data(self): self.selection_name = cmds.ls(selection=True) self.ngs_layer_id = self.mll.getCurrentLayer() self.ngs_influence = self.mll.getCurrentPaintTarget() self.ngs_weight_list = self.mll.getInfluenceWeights( self.ngs_layer_id, self.ngs_influence) self.ngs_vert_count = self.mll.getVertCount() self.max_value = max(self.ngs_weight_list) self.min_value = min(self.ngs_weight_list) def grow_map(self, intensity): """ pushes the border of the active weight map outwards """ self.get_data() new_weight_list = [] for i in range(self.ngs_vert_count): vertex_weight = self.ngs_weight_list[i] if vertex_weight >= self.max_value: new_weight_list.append(vertex_weight) continue vertex_name = '.'.join([self.selection_name[0], 'vtx[%d]' % i]) area_vertices = get_surrounding_verts(vertex_name) weight_list = [self.ngs_weight_list[int(x)] for x in area_vertices] max_avg = max(weight_list) if max_avg <= self.min_value: new_weight_list.append(vertex_weight) continue grow_weight = vertex_weight + (abs(vertex_weight - max_avg) * intensity) grow_weight = min(grow_weight, self.max_value) new_weight_list.append(grow_weight) self.mll.setInfluenceWeights(self.ngs_layer_id, self.ngs_influence, new_weight_list) def shrink_map(self, intensity): """ pulls the border of the active weight map inwards """ self.get_data() new_weight_list = [] for i in range(self.ngs_vert_count): vertex_weight = self.ngs_weight_list[i] if vertex_weight <= self.min_value: new_weight_list.append(vertex_weight) continue vertex_name = '.'.join([self.selection_name[0], 'vtx[%d]' % i]) area_vertices = get_surrounding_verts(vertex_name) weight_list = [self.ngs_weight_list[int(x)] for x in area_vertices] min_avg = min(weight_list) if min_avg >= self.max_value: new_weight_list.append(vertex_weight) continue shrink_weight = vertex_weight - (abs(vertex_weight - min_avg) * intensity) shrink_weight = max(shrink_weight, self.min_value) new_weight_list.append(shrink_weight) self.mll.setInfluenceWeights(self.ngs_layer_id, self.ngs_influence, new_weight_list) def conceal_map(self, intensity): """ smooth operation for the active map by only lowering values """ self.get_data() new_weight_list = [] threshold = 0.01 / (intensity / 0.1) for i in range(self.ngs_vert_count): vertex_weight = self.ngs_weight_list[i] if vertex_weight <= self.min_value: new_weight_list.append(vertex_weight) continue vertex_name = '.'.join([self.selection_name[0], 'vtx[%d]' % i]) area_vertices = get_surrounding_verts(vertex_name) weight_list = [self.ngs_weight_list[int(x)] for x in area_vertices] weight_avg = sum(weight_list) / float(len(weight_list)) min_avg = min(weight_list) if weight_avg >= self.max_value and abs(weight_avg - vertex_weight) < threshold: new_weight_list.append(vertex_weight) continue weight_diff = abs(weight_avg - vertex_weight) conceal_weight = vertex_weight * (1 - (weight_diff * intensity)) conceal_weight = max(conceal_weight, min_avg) new_weight_list.append(conceal_weight) self.mll.setInfluenceWeights(self.ngs_layer_id, self.ngs_influence, new_weight_list) def spread_map(self, intensity): """ smooth operation for the active map by only increasing values """ self.get_data() new_weight_list = [] threshold = 0.01 / (intensity / 0.1) for i in range(self.ngs_vert_count): vertex_weight = self.ngs_weight_list[i] if vertex_weight >= self.max_value: new_weight_list.append(vertex_weight) continue vertex_name = '.'.join([self.selection_name[0], 'vtx[%d]' % i]) area_vertices = get_surrounding_verts(vertex_name) weight_list = [self.ngs_weight_list[int(x)] for x in area_vertices] weight_avg = sum(weight_list) / float(len(weight_list)) max_avg = max(weight_list) if weight_avg <= self.min_value and abs(weight_avg - vertex_weight) < threshold: new_weight_list.append(vertex_weight) continue weight_diff = abs(weight_avg - vertex_weight) spread_weight = vertex_weight + weight_diff * intensity spread_weight = min(spread_weight, max_avg) new_weight_list.append(spread_weight) self.mll.setInfluenceWeights(self.ngs_layer_id, self.ngs_influence, new_weight_list) def gain_map(self, intensity): """ 'reverse scale' tool that only increases weight values above 0 """ self.get_data() new_weight_list = [] for i in range(self.ngs_vert_count): vertex_weight = self.ngs_weight_list[i] if vertex_weight == 0: new_weight_list.append(vertex_weight) continue gain_weight = vertex_weight + (vertex_weight * intensity) gain_weight = min(gain_weight, 1) new_weight_list.append(gain_weight) self.mll.setInfluenceWeights(self.ngs_layer_id, self.ngs_influence, new_weight_list) def contrast_map(self, intensity): """ sharpens the edge of the active weight map """ self.get_data() new_weight_list = [] avg_value = (self.max_value + self.min_value) / 2 normalized_range = self.max_value - self.min_value for i in range(self.ngs_vert_count): vertex_weight = self.ngs_weight_list[i] if not self.max_value > vertex_weight > self.min_value: new_weight_list.append(vertex_weight) continue dist_value = vertex_weight - avg_value modifier = norm.cdf(dist_value, loc=0, scale=(0.1 * normalized_range)) - vertex_weight contrast_weight = vertex_weight + modifier * intensity contrast_weight = max(self.min_value, min(contrast_weight, self.max_value)) new_weight_list.append(contrast_weight) self.mll.setInfluenceWeights(self.ngs_layer_id, self.ngs_influence, new_weight_list)
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 VariousImportScenarios(AdditionalAsserts, unittest.TestCase): def setUp(self): self.targetMesh = 'testMesh' self.numVerts = 169 # number of verts in our mesh self.mll = MllInterface() unittest.TestCase.setUp(self) openMayaFile('simplemirror.ma') cmds.hide("|y_axis") cmds.showHidden("|x_axis") self.mll.setCurrentMesh(self.targetMesh) self.mll.initLayers() self.mll.createLayer("original weights") self.model = ie.LayerData() self.layer = ie.Layer() self.layer.opacity = 1.0 self.layer.enabled = True self.layer.mask = [0.0]*self.numVerts self.layer.mask[1] = 0.9 self.layer.mask[5] = 0.5566 self.layer.name = "imported layer" self.model.addLayer(self.layer) self.infl = ie.Influence() self.layer.addInfluence(self.infl) self.infl.influenceName = "x_axis|root|R_joint1" self.infl.logicalIndex = 666 # use nonsense value self.infl.weights = [0.1]*self.numVerts self.infl.weights[3] = 0.688 self.infl.weights[4] = 0.345 @decorators.insideMayaOnly def testSave(self): self.model.saveTo(self.targetMesh) layers = list(self.mll.listLayers()) id,name = layers[0] self.assertEquals("imported layer", name) self.assertFloatArraysEqual(self.mll.getLayerMask(id), self.layer.mask) self.assertFloatArraysEqual(self.mll.getInfluenceWeights(id, self.infl.logicalIndex), self.infl.weights) @decorators.insideMayaOnly def testEmptyMask(self): self.layer.mask = [] self.model.saveTo(self.targetMesh) @decorators.insideMayaOnly def testInvalidVertCount(self): self.infl.weights = self.infl.weights[:-1] with self.assertRaises(Exception,"Invalid weights count for influence 'x_axis|root|R_joint1' in layer 'imported layer': expected size is 169"): self.model.saveTo(self.targetMesh) @decorators.insideMayaOnly def testInvalidMaskCount(self): self.layer.mask = self.layer.mask[:-1] with self.assertRaises(Exception,"Invalid vertex count for mask in layer 'imported layer': expected size is 169"): self.model.saveTo(self.targetMesh) @decorators.insideMayaOnly def testInvalidMesh(self): with self.assertRaises(Exception,"could not initialize layers"): self.model.saveTo("persp") @decorators.insideMayaOnly def testUninitializedLayers(self): ''' if layers on mesh were uninitialized, just initialize layers as usual, and do not fail ''' LayerUtils.deleteCustomNodes() self.model.saveTo(self.targetMesh) @decorators.insideMayaOnly def testMissingInfluence(self): self.infl.influenceName = "something_is_missing_here" with self.assertRaises(MessageException,"Could not find influence 'something_is_missing_here' in skinCluster1"): self.model.saveTo(self.targetMesh)
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 MllInterfaceTest(AdditionalAsserts, unittest.TestCase): def setUp(self): unittest.TestCase.setUp(self) self.mll = MllInterface() @insideMayaOnly def testAccessForNoSelection(self): testUtils.openMayaFile("normalization.ma") cmds.select(cl=True) self.mll.setCurrentMesh(None) self.assertEquals(None, self.mll.getTargetInfo()) @insideMayaOnly def testInvalidInfluenceIndex(self): testUtils.openMayaFile("normalization.ma") self.mll.setCurrentMesh('testMesh') self.mll.initLayers() layerId = self.mll.createLayer("initial weights") weights = [0.0] * self.mll.getVertCount() with self.assertRaises( errorMessage="skin cluster does not have logical influence 666" ): self.mll.setInfluenceWeights(layerId, 666, weights) @insideMayaOnly def testAccess(self): # test the same for specified mesh, or None for current selection for mesh in ('testMesh', None): testUtils.openMayaFile("normalization.ma") self.mll.setCurrentMesh(mesh) if mesh is None: cmds.select('testMesh') self.mll.initLayers() layerId = self.mll.createLayer("initial weights") self.assertNotEqual(None, layerId) self.assertEqual("initial weights", self.mll.getLayerName(layerId)) self.assertEqual(16, self.mll.getVertCount(), "Invalid vertex count detected") self.assertEqual(1, self.mll.getLayerOpacity(layerId)) self.assertEqual(True, self.mll.isLayerEnabled(layerId)) self.assertEqual([], self.mll.getLayerMask(layerId)) self.assertFloatArraysEqual( self.mll.getInfluenceWeights(layerId, 1), [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ]) @insideMayaOnly def testOverwriteWeightsCrashesMaya(self): testUtils.openMayaFile("genericSkinnedMesh.mb") self.mll.setCurrentMesh("mesh|pPlane1") self.mll.initLayers() layerId = self.mll.createLayer("initial weights", forceEmpty=True) weights = [1.0] * self.mll.getVertCount() weights[0] = 0.7 weights[1] = 0.5 weights[2] = 0.3 self.mll.setInfluenceWeights(layerId, 0, weights) self.assertFloatArraysEqual( self.mll.getInfluenceWeights(layerId, 0)[0:3], [0.7, 0.5, 0.3]) self.mll.setInfluenceWeights(layerId, 1, weights) self.assertFloatArraysEqual(weights, self.mll.getInfluenceWeights(layerId, 1)) # previous influence should have some of it's value substracted self.assertFloatArraysEqual( self.mll.getInfluenceWeights(layerId, 0)[0:3], [0.3, 0.5, 0.3]) @insideMayaOnly def testAddManualMirrorInfluences(self): testUtils.openMayaFile("genericSkinnedMesh.mb") self.mll.setCurrentMesh("mesh|pPlane1") self.mll.initLayers() layerId = self.mll.createLayer("initial weights") result = self.mll.listManualMirrorInfluenceAssociations() self.assertArraysEqual(result, []) self.mll.addManualMirrorInfluenceAssociation("L_joint1", "R_joint1") self.mll.addManualMirrorInfluenceAssociation("R_joint1", "L_joint1") result = self.mll.listManualMirrorInfluenceAssociations() self.assertDictionariesEqual(result, { "L_joint1": "R_joint1", "R_joint1": "L_joint1" }) @insideMayaOnly def testSetMaxInfluencePerVertex(self): testUtils.openMayaFile("genericSkinnedMesh.mb") self.mll.setCurrentMesh("mesh|pPlane1") self.mll.initLayers() layerId = self.mll.createLayer("initial weights") self.assertEqual(self.mll.getInfluenceLimitPerVertex(), 0, "initial limit per vertex should be zero") self.mll.setInfluenceLimitPerVertex(10) self.assertEqual(self.mll.getInfluenceLimitPerVertex(), 10, "was not able to set limit to the desired value") self.mll.setInfluenceLimitPerVertex(2) self.assertEqual(self.mll.getInfluenceLimitPerVertex(), 2, "was not able to set limit to the desired value")
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 Equalize(object): def __init__(self): self.mll = MllInterface() self.ngs_vert_count = -1 self.ngs_layer_id = -1 self.intensity = -1 self.ngs_influence = None self.vert_weight_dict = {} self.vert_weight_list = [] self.ngs_weight_dict = {} self.ngs_weight_list = [] self.new_weight_list = [] self.selection_vert = [] self.vertex_list = [] self.id_list = [] self.modes = { "max": self.vert_max, "min": self.vert_min, "avg": self.vert_avg, "first": self.vert_first, "last": self.vert_last } def get_data(self, check): self.ngs_weight_dict = {} self.vert_weight_dict = {} self.selection_vert = cmds.ls(os=True) if not self.selection_vert: return False if not check: self.ngs_influence = [("selected influence:", self.mll.getCurrentPaintTarget())] else: self.ngs_influence = self.mll.listLayerInfluences( layerId=None, activeInfluences=True) self.mll = MllInterface() self.ngs_layer_id = self.mll.getCurrentLayer() self.ngs_vert_count = self.mll.getVertCount() self.vertex_list = cmds.ls(self.selection_vert, flatten=True) self.id_list = list( map(lambda x: x[x.find("[") + 1:x.find("]")], self.vertex_list)) for influences in self.ngs_influence: influence_weights = self.mll.getInfluenceWeights( self.ngs_layer_id, influences[1]) vert_weights = [influence_weights[int(i)] for i in self.id_list] self.ngs_weight_dict[influences] = influence_weights self.vert_weight_dict[influences] = vert_weights return True def set_vert(self, mode, intensity): self.intensity = intensity for influences in self.ngs_influence: self.new_weight_list = [] self.ngs_weight_list = self.ngs_weight_dict[influences] self.vert_weight_list = self.vert_weight_dict[influences] self.modes[mode]() self.mll.setInfluenceWeights(self.ngs_layer_id, influences[1], self.new_weight_list) def vert_max(self): for i in range(self.ngs_vert_count): vertex_weight = self.ngs_weight_list[i] if str(i) not in self.id_list or vertex_weight == max( self.vert_weight_list): self.new_weight_list.append(vertex_weight) continue new_weight = vertex_weight + ( (max(self.vert_weight_list) - vertex_weight) * self.intensity) self.new_weight_list.append(new_weight) def vert_min(self): for i in range(self.ngs_vert_count): vertex_weight = self.ngs_weight_list[i] if str(i) not in self.id_list or vertex_weight == min( self.vert_weight_list): self.new_weight_list.append(vertex_weight) continue new_weight = vertex_weight - ( (vertex_weight - min(self.vert_weight_list)) * self.intensity) self.new_weight_list.append(new_weight) def vert_avg(self): average = sum(self.vert_weight_list) / float(len( self.vert_weight_list)) for i in range(self.ngs_vert_count): vertex_weight = self.ngs_weight_list[i] if str(i) not in self.id_list or vertex_weight == average: self.new_weight_list.append(vertex_weight) continue new_weight = vertex_weight - ( (vertex_weight - average) * self.intensity) self.new_weight_list.append(new_weight) def vert_first(self): first = self.vert_weight_list[0] for i in range(self.ngs_vert_count): vertex_weight = self.ngs_weight_list[i] if str(i) not in self.id_list or vertex_weight == first: self.new_weight_list.append(vertex_weight) continue new_weight = vertex_weight - ( (vertex_weight - first) * self.intensity) self.new_weight_list.append(new_weight) def vert_last(self): last = self.vert_weight_list[-1] for i in range(self.ngs_vert_count): vertex_weight = self.ngs_weight_list[i] if str(i) not in self.id_list or vertex_weight == last: self.new_weight_list.append(vertex_weight) continue new_weight = vertex_weight - ( (vertex_weight - last) * self.intensity) self.new_weight_list.append(new_weight)
class MllInterfaceTest(AdditionalAsserts, unittest.TestCase): def setUp(self): unittest.TestCase.setUp(self) self.mll = MllInterface() @insideMayaOnly def testAccessForNoSelection(self): testUtils.openMayaFile("normalization.ma") cmds.select(cl=True) self.mll.setCurrentMesh(None) self.assertEquals(None,self.mll.getTargetInfo()) @insideMayaOnly def testInvalidInfluenceIndex(self): testUtils.openMayaFile("normalization.ma") self.mll.setCurrentMesh('testMesh') self.mll.initLayers() layerId = self.mll.createLayer("initial weights") weights = [0.0]*self.mll.getVertCount() with self.assertRaises(errorMessage="skin cluster does not have logical influence 666"): self.mll.setInfluenceWeights(layerId, 666, weights) @insideMayaOnly def testAccess(self): # test the same for specified mesh, or None for current selection for mesh in ('testMesh',None): testUtils.openMayaFile("normalization.ma") self.mll.setCurrentMesh(mesh) if mesh is None: cmds.select('testMesh') self.mll.initLayers() layerId = self.mll.createLayer("initial weights") self.assertNotEqual(None, layerId) self.assertEqual("initial weights",self.mll.getLayerName(layerId)) self.assertEqual(16, self.mll.getVertCount(), "Invalid vertex count detected") self.assertEqual(1, self.mll.getLayerOpacity(layerId)) self.assertEqual(True, self.mll.isLayerEnabled(layerId)) self.assertEqual([], self.mll.getLayerMask(layerId)) self.assertFloatArraysEqual(self.mll.getInfluenceWeights(layerId,1),[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]) @insideMayaOnly def testOverwriteWeightsCrashesMaya(self): testUtils.openMayaFile("genericSkinnedMesh.mb") self.mll.setCurrentMesh("mesh|pPlane1") self.mll.initLayers() layerId = self.mll.createLayer("initial weights",forceEmpty=True) weights = [1.0]*self.mll.getVertCount() weights[0] = 0.7 weights[1] = 0.5 weights[2] = 0.3 self.mll.setInfluenceWeights(layerId, 0, weights) self.assertFloatArraysEqual(self.mll.getInfluenceWeights(layerId, 0)[0:3], [0.7,0.5,0.3]) self.mll.setInfluenceWeights(layerId, 1, weights) self.assertFloatArraysEqual(weights,self.mll.getInfluenceWeights(layerId, 1)) # previous influence should have some of it's value substracted self.assertFloatArraysEqual(self.mll.getInfluenceWeights(layerId, 0)[0:3], [0.3,0.5,0.3]) @insideMayaOnly def testAddManualMirrorInfluences(self): testUtils.openMayaFile("genericSkinnedMesh.mb") self.mll.setCurrentMesh("mesh|pPlane1") self.mll.initLayers() layerId = self.mll.createLayer("initial weights") result = self.mll.listManualMirrorInfluenceAssociations() self.assertArraysEqual(result, []) self.mll.addManualMirrorInfluenceAssociation("L_joint1", "R_joint1") self.mll.addManualMirrorInfluenceAssociation("R_joint1", "L_joint1") result = self.mll.listManualMirrorInfluenceAssociations() self.assertDictionariesEqual(result, {"L_joint1":"R_joint1","R_joint1":"L_joint1"}) @insideMayaOnly def testSetMaxInfluencePerVertex(self): testUtils.openMayaFile("genericSkinnedMesh.mb") self.mll.setCurrentMesh("mesh|pPlane1") self.mll.initLayers() layerId = self.mll.createLayer("initial weights") self.assertEqual(self.mll.getInfluenceLimitPerVertex(),0,"initial limit per vertex should be zero") self.mll.setInfluenceLimitPerVertex(10) self.assertEqual(self.mll.getInfluenceLimitPerVertex(),10,"was not able to set limit to the desired value") self.mll.setInfluenceLimitPerVertex(2) self.assertEqual(self.mll.getInfluenceLimitPerVertex(),2,"was not able to set limit to the desired value")
class VariousImportScenarios(AdditionalAsserts, unittest.TestCase): def setUp(self): self.targetMesh = 'testMesh' self.numVerts = 169 # number of verts in our mesh self.mll = MllInterface() unittest.TestCase.setUp(self) openMayaFile('simplemirror.ma') cmds.hide("|y_axis") cmds.showHidden("|x_axis") self.mll.setCurrentMesh(self.targetMesh) self.mll.initLayers() self.mll.createLayer("original weights") self.model = ie.LayerData() self.layer = ie.Layer() self.layer.opacity = 1.0 self.layer.enabled = True self.layer.mask = [0.0] * self.numVerts self.layer.mask[1] = 0.9 self.layer.mask[5] = 0.5566 self.layer.name = "imported layer" self.model.addLayer(self.layer) self.infl = ie.Influence() self.layer.addInfluence(self.infl) self.infl.influenceName = "x_axis|root|R_joint1" self.infl.logicalIndex = 666 # use nonsense value self.infl.weights = [0.1] * self.numVerts self.infl.weights[3] = 0.688 self.infl.weights[4] = 0.345 @decorators.insideMayaOnly def testSave(self): self.model.saveTo(self.targetMesh) layers = list(self.mll.listLayers()) id, name = layers[0] self.assertEquals("imported layer", name) self.assertFloatArraysEqual(self.mll.getLayerMask(id), self.layer.mask) self.assertFloatArraysEqual( self.mll.getInfluenceWeights(id, self.infl.logicalIndex), self.infl.weights) @decorators.insideMayaOnly def testEmptyMask(self): self.layer.mask = [] self.model.saveTo(self.targetMesh) @decorators.insideMayaOnly def testInvalidVertCount(self): self.infl.weights = self.infl.weights[:-1] with self.assertRaises( Exception, "Invalid weights count for influence 'x_axis|root|R_joint1' in layer 'imported layer': expected size is 169" ): self.model.saveTo(self.targetMesh) @decorators.insideMayaOnly def testInvalidMaskCount(self): self.layer.mask = self.layer.mask[:-1] with self.assertRaises( Exception, "Invalid vertex count for mask in layer 'imported layer': expected size is 169" ): self.model.saveTo(self.targetMesh) @decorators.insideMayaOnly def testInvalidMesh(self): with self.assertRaises(Exception, "could not initialize layers"): self.model.saveTo("persp") @decorators.insideMayaOnly def testUninitializedLayers(self): ''' if layers on mesh were uninitialized, just initialize layers as usual, and do not fail ''' LayerUtils.deleteCustomNodes() self.model.saveTo(self.targetMesh) @decorators.insideMayaOnly def testMissingInfluence(self): self.infl.influenceName = "something_is_missing_here" with self.assertRaises( MessageException, "Could not find influence 'something_is_missing_here' in skinCluster1" ): self.model.saveTo(self.targetMesh)