Example #1
0
	def exportABC(self, dccMesh, abcMesh, js, world=False, pBar=None):
		# export the data to alembic
		if dccMesh is None:
			dccMesh = self.mesh

		shapeDict = {i.name:i for i in self.simplex.shapes}
		shapes = [shapeDict[i] for i in js["shapes"]]
		faces, counts = self._exportABCFaces(dccMesh)
		schema = abcMesh.getSchema()

		if pBar is not None:
			pBar.show()
			pBar.setMaximum(len(shapes))

		with disconnected(self.shapeNode) as cnx:
			shapeCnx = cnx[self.shapeNode]
			for v in shapeCnx.itervalues():
				cmds.setAttr(v, 0.0)
			for i, shape in enumerate(shapes):
				if pBar is not None:
					pBar.setValue(i)
					QApplication.processEvents()
					if pBar.wasCanceled():
						return
				cmds.setAttr(shape.thing, 1.0)
				verts = self._exportABCVertices(dccMesh, world=world)
				abcSample = OPolyMeshSchemaSample(verts, faces, counts)
				schema.set(abcSample)
				cmds.setAttr(shape.thing, 0.0)
Example #2
0
def _pyblish_action(name, is_reset=True):

    if nuke.value('root.name', None):
        Window.dock()

    window_ = Window.instance
    assert isinstance(window_, Window)
    controller = window_.controller
    assert isinstance(controller, control.Controller)

    start = getattr(window_, name)
    signal = {'publish': controller.was_published,
              'validate': controller.was_validated}[name]

    finish_event = multiprocessing.Event()
    _after_signal(signal, finish_event.set)

    if is_reset:
        # Run after reset finish.
        _after_signal(controller.was_reset, start)
        window_.reset()
    else:
        # Run directly.
        start()

    # Wait finish.
    while (not finish_event.is_set()
           or controller.is_running):
        QApplication.processEvents()
Example #3
0
	def exportABC(self, dccMesh, abcMesh, js, pBar=None):
		# dccMesh doesn't work in XSI, so just ignore it
		# export the data to alembic
		shapeDict = {i.name:i for i in self.simplex.shapes}
		shapes = [shapeDict[i] for i in js["shapes"]]
		faces, counts = self._exportABCFaces(self.mesh)
		schema = abcMesh.getSchema()

		if pBar is not None:
			pBar.show()
			pBar.setMaximum(len(shapes))

		#deactivate evaluation above modeling to insure no deformations are present
		dcc.xsi.DeactivateAbove("%s.modelingmarker" %self.mesh.ActivePrimitive, True)

		for i, shape in enumerate(shapes):
			if pBar is not None:
				pBar.setValue(i)
				QApplication.processEvents()
				if pBar.wasCanceled():
					return
			verts = self._exportABCVertices(self.mesh, shape)
			abcSample = OPolyMeshSchemaSample(verts, faces, counts)
			schema.set(abcSample)

		dcc.xsi.DeactivateAbove("%s.modelingmarker" %self.mesh.ActivePrimitive, "")
Example #4
0
def _writeSimplex(oarch, name, jsString, faces, counts, newShapes, pBar=None):
    ''' Separate the writer from oarch creation so garbage
	collection *hopefully* works as expected
	'''
    par = OXform(oarch.getTop(), name)
    props = par.getSchema().getUserProperties()
    prop = OStringProperty(props, "simplex")
    prop.setValue(str(jsString))
    abcMesh = OPolyMesh(par, name)
    schema = abcMesh.getSchema()

    if pBar is not None:
        pBar.setLabelText('Writing Corrected Simplex')
        pBar.setMaximum(len(newShapes))

    for i, newShape in enumerate(newShapes):
        if pBar is not None:
            pBar.setValue(i)
            QApplication.processEvents()
        else:
            print "Writing {0: 3d} of {1}\r".format(i, len(newShapes)),

        verts = mkSampleVertexPoints(newShape)
        abcSample = OPolyMeshSchemaSample(verts, faces, counts)
        schema.set(abcSample)
    if pBar is None:
        print "Writing {0: 3d} of {1}".format(len(newShapes), len(newShapes))
Example #5
0
def readAndApplyCorrectives(inPath, namePath, refPath, outPath, pBar=None):
    '''
	Read the provided files, apply the correctives, then output a new file

	Arguments:
		inPath: The input .smpx file
		namePath: A file correlating the shape names, and the reference
			indices. Separated by ; with one entry per line
		refPath: The reference matrices per point of deformation.
			Created by npArray.dump(refPath)
		outPath: The output .smpx filepath
	'''

    if pBar is not None:
        pBar.setLabelText("Reading reference data")
        QApplication.processEvents()

    jsString, simplex, solver, allShapePts, restPts = loadSimplex(inPath)
    with open(namePath, 'r') as f:
        nr = f.read()
    nr = [i.split(';') for i in nr.split('\n') if i]
    names, refIdxs = zip(*nr)
    refIdxs = map(int, refIdxs)
    refs = np.load(refPath)
    simplex = Simplex()
    simplex.loadJSON(jsString)

    shapeByName = {i.name: i for i in simplex.shapes}
    shapes = [shapeByName[n] for n in names]
    newPts = applyCorrectives(simplex, allShapePts, restPts, solver, shapes,
                              refIdxs, refs, pBar)
    writeSimplex(inPath, outPath, newPts, pBar=pBar)
    print "DONE"
Example #6
0
def outputCorrectiveReferences(outNames, outRefs, simplex, mesh, poses, sliders, pBar=None):
	'''
	Output the proper files for an external corrective application

	Arguments:
		outNames: The filepath for the output shape and reference indices
		outRefs: The filepath for the deformation references
		simplex: A simplex system
		mesh: The mesh object to deform
		poses: Lists of parameter/value pairs. Each list corresponds to a slider
		sliders: The simplex sliders that correspond to the poses
	'''
	refs, shapes, refIdxs = buildCorrectiveReferences(mesh, simplex, poses, sliders, pBar)

	if pBar is not None:
		pBar.setLabelText('Writing Names')
		QApplication.processEvents()
	nameWrite = ['{};{}'.format(s.name, r) for s, r, in zip(shapes, refIdxs)]
	with open(outNames, 'w') as f:
		f.write('\n'.join(nameWrite))

	if pBar is not None:
		pBar.setLabelText('Writing References')
		QApplication.processEvents()
	refs.dump(outRefs)
Example #7
0
    def buildFromAbc(self, thing, abcPath, pBar=None):
        """ Load a system from an exported
		abc file onto the current system """
        if pBar is not None:
            pBar.show()
            pBar.setMaximum(100)
            pBar.setValue(0)
            pBar.setLabelText("Loading Smpx")
            QApplication.processEvents()

        iarch = IArchive(str(abcPath))  # because alembic hates unicode
        try:
            top = iarch.getTop()
            par = top.children[0]
            par = IXform(top, par.getName())
            abcMesh = par.children[0]
            abcMesh = IPolyMesh(par, abcMesh.getName())

            systemSchema = par.getSchema()
            props = systemSchema.getUserProperties()
            prop = props.getProperty("simplex")
            jsString = prop.getValue()
            js = json.loads(jsString)

            system = self.buildFromDict(thing, js, True, pBar)
            self.DCC.loadABC(abcMesh, js, pBar)
        finally:
            del iarch

        return system
 def setUp(self):
     try:
         self.qtApp = QApplication([])
     except RuntimeError:
         self.qtApp = QApplication.instance()
     self.gui = SimpleGui()
     QApplication.processEvents()
Example #9
0
def autoCrawlMeshes(orderMesh,
                    shapeMesh,
                    skipMismatchedIslands=False,
                    pBar=None):
    """
	Crawl both the order and shape meshes using my heuristics to find
	any matching islands
	"""
    if pBar is not None:
        pBar.setLabelText("Finding Islands")
        pBar.setValue(66)
        QApplication.processEvents()

    fmg = fullMatchCoProcess(orderMesh,
                             shapeMesh,
                             skipMismatchedIslands=skipMismatchedIslands)
    matches = []
    errors = {}
    matchCount = 0
    check = 0
    found = False
    try:
        # Execute the generator up to the first yield, and get the data from it
        sm, curIdx = fmg.send(None)  #Send nothing the first time
        idxErrors = []
        while True:
            if pBar is not None:
                pBar.setLabelText("Crawling iteration {0}".format(check))
                pBar.setValue(0)
                QApplication.processEvents()
            check += 1
            try:
                print
                print "Checking Vertex Match", zip(*sm)
                match = matchByTopology(orderMesh,
                                        shapeMesh,
                                        sm,
                                        matchedNum=matchCount,
                                        vertNum=len(orderMesh.vertArray),
                                        pBar=pBar)
            except TopologyMismatch as err:
                idxErrors.append(str(err))
            else:
                matches.append(match)
                found = True
                matchCount += len(match)
            # send the return value into the generator,
            # execute up until the next yield and get the data from it
            sm, idx = fmg.send(found)
            if curIdx != idx:
                if not found:
                    errors[curIdx] = idxErrors
                idxErrors = []
                curIdx = idx
            found = False
    except StopIteration:
        if not found:
            raise TopologyMismatch("No Match Found")
    return matches
Example #10
0
def uvTransfer(
    srcFaces,
    srcUvFaces,
    srcVerts,
    srcUvs,
    tarFaces,
    tarUvFaces,
    tarVerts,
    tarUvs,
    tol=0.0001,
    pBar=None,
):
    """A helper function that transfers pre-loaded data.
        The source data will be transferred onto the tar data

    Parameters
    ----------
    srcFaces : [[int, ...], ...]
        The source vertex face list
    srcUvFaces : [[int, ...], ...]
        The source uv face list
    srcUvs : np.array
        The source UV positions
    tarFaces : [[int, ...], ...]
        The target vertex face list
    tarUvFaces : [[int, ...], ...]
        The target uv face list
    tarUvs : np.array
        The Target UV Positions
    srcVerts : np.array
        The source Vertex positions
    tarVerts : np.array
        The target Vertex positions
    tol : float
        A small tolerance value, defaulting to the global EPS
    pBar : QProgressDialog, optional
        An optional progress dialog

    Returns
    -------
    : np.array
        The new target vert positions

    """
    corr = getVertCorrelation(srcUvFaces,
                              srcUvs,
                              tarFaces,
                              tarUvFaces,
                              tarUvs,
                              tol=tol,
                              pBar=pBar)
    if pBar is not None:
        pBar.setValue(0)
        pBar.setLabelText("Apply Transfer")
        from Qt.QtWidgets import QApplication

        QApplication.processEvents()

    return applyTransfer(srcVerts, srcFaces, corr, len(tarVerts))
Example #11
0
    def buildFromDict(self, thing, simpDict, create, pBar=None):
        if pBar is not None:
            pBar.setLabelText("Build Structure")
            QApplication.processEvents()

        simp = Simplex(simpDict["systemName"])
        simp.loadDefinition(simpDict)
        self.initSimplex(simp)
        self.DCC.loadNodes(simp, thing, create=create, pBar=pBar)
        self.DCC.loadConnections(simp, create=create, pBar=pBar)
Example #12
0
	def loadABC(self, abcMesh, js, pBar=None):
		# UGH, I *REALLY* hate that this is faster
		# But if I want to be "pure" about it, I should just bite the bullet
		# and do the direct alembic manipulation in C++
		if pBar is not None:
			pBar.setValue(0)
			pBar.setLabelText("Building Stuff")

		abcPath = str(abcMesh.getArchive())

		abcNode = cmds.createNode('AlembicNode')
		cmds.setAttr(abcNode + ".abc_File", abcPath, type="string")
		cmds.setAttr(abcNode + ".speed", 24)
		shapes = js["shapes"]
		shapeDict = {i.name:i for i in self.simplex.shapes}

		importHead = cmds.polySphere(name='importHead', constructionHistory=False)[0]
		importHeadShape = [i for i in cmds.listRelatives(importHead, shapes=True)][0]

		cmds.connectAttr(abcNode+".outPolyMesh[0]", importHeadShape+".inMesh")
		vertCount = cmds.polyEvaluate(importHead, vertex=True) # force update
		cmds.disconnectAttr(abcNode+".outPolyMesh[0]", importHeadShape+".inMesh")

		importBS = cmds.blendShape(self.mesh, importHead)[0]
		cmds.blendShape(importBS, edit=True, weight=[(0, 1.0)])
		# Maybe get shapeNode from self.mesh??
		cmds.disconnectAttr(self.mesh+'.worldMesh[0]', importBS+'.inputTarget[0].inputTargetGroup[0].inputTargetItem[6000].inputGeomTarget')
		importOrig = [i for i in cmds.listRelatives(importHead, shapes=True) if i.endswith('Orig')][0]
		cmds.connectAttr(abcNode+".outPolyMesh[0]", importOrig+".inMesh")

		if pBar is not None:
			pBar.show()
			pBar.setMaximum(len(shapes))
			longName = max(shapes, key=len)
			pBar.setValue(1)
			pBar.setLabelText("Loading:\n{0}".format("_"*len(longName)))

		for i, shapeName in enumerate(shapes):
			if pBar is not None:
				pBar.setValue(i)
				pBar.setLabelText("Loading:\n{0}".format(shapeName))
				QApplication.processEvents()
				if pBar.wasCanceled():
					return
			index = self._getShapeIndex(shapeDict[shapeName])
			cmds.setAttr(abcNode + ".time", i)

			outAttr = "{0}.worldMesh[0]".format(importHead)
			tgn = "{0}.inputTarget[0].inputTargetGroup[{1}]".format(self.shapeNode, index)
			inAttr = "{0}.inputTargetItem[6000].inputGeomTarget".format(tgn)

			cmds.connectAttr(outAttr, inAttr, force=True)
			cmds.disconnectAttr(outAttr, inAttr)
		cmds.delete(abcNode)
		cmds.delete(importHead)
Example #13
0
def buildFullShapes(simplex, shapes, allPts, solver, restPts, pBar=None):
    '''
	Given shape inputs, build the full output shape from the deltas
	We use shapes here because a shape implies both the progression
	and the value of the inputs (with a little figuring)
	'''
    ###########################################
    # Manipulate all the input lists and caches
    indexBySlider = {s: i for i, s in enumerate(simplex.sliders)}
    indexByShape = {s: i for i, s in enumerate(simplex.shapes)}
    floaters = set(simplex.getFloatingShapes())
    floatIdxs = set([indexByShape[s] for s in floaters])

    shapeDict = {}
    for item in itertools.chain(simplex.sliders, simplex.combos):
        for pair in item.prog.pairs:
            if not pair.shape.isRest:
                shapeDict[pair.shape] = (item, pair.value)

    ######################
    # Actually do the work
    vecByShape = {}  # store this for later use
    ptsByShape = {}

    if pBar is not None:
        pBar.setMaximum(len(shapes))
        pBar.setValue(0)
        QApplication.processEvents()

    for i, shape in enumerate(shapes):
        if pBar is not None:
            pBar.setValue(i)
            QApplication.processEvents()
        else:
            print "Building {0} of {1}\r".format(i + 1, len(shapes)),

        item, value = shapeDict[shape]
        inVec = _buildSolverInputs(simplex, item, value, indexBySlider)
        outVec = solver.solve(inVec)
        if shape not in floaters:
            for fi in floatIdxs:
                outVec[fi] = 0.0
        outVec = np.array(outVec)
        outVec[np.where(np.isclose(outVec, 0))] = 0
        outVec[np.where(np.isclose(outVec, 1))] = 1
        vecByShape[shape] = outVec
        pts = np.dot(outVec, allPts.transpose((1, 0, 2)))
        ptsByShape[shape] = pts + restPts
    if pBar is None:
        print

    return ptsByShape, vecByShape
Example #14
0
    def from_file(cls, file, **data):
        """Construct from file path.

        Args:
            file (str): qml file path.
            data (dict, optional): Data will be used in qml as `DATA`.
        """

        ret = Notify()
        context = ret.rootContext()
        context.setContextProperty('DATA', data)
        context.setContextProperty('VIEW', ret)
        ret.setSource(QUrl.fromLocalFile(file))
        QApplication.processEvents()
        return ret
Example #15
0
def exportUnsub(inPath, outPath, newFaces, kept, pBar=None):
	''' Export the unsubdivided simplex '''
	iarch = IArchive(str(inPath)) # because alembic hates unicode
	top = iarch.getTop()
	ixfo = IXform(top, top.children[0].getName())

	iprops = ixfo.getSchema().getUserProperties()
	iprop = iprops.getProperty("simplex")
	jsString = iprop.getValue()
	imesh = IPolyMesh(ixfo, ixfo.children[0].getName())

	verts = getSampleArray(imesh)
	verts = verts[:, kept]

	indices = []
	counts = []
	for f in newFaces:
		indices.extend(f)
		counts.append(len(f))

	abcCounts = mkArray(IntArray, counts)
	abcIndices = mkArray(IntArray, indices)

	# `False` for HDF5 `True` for Ogawa
	oarch = OArchive(str(outPath), False)
	oxfo = OXform(oarch.getTop(), ixfo.getName())
	oprops = oxfo.getSchema().getUserProperties()
	oprop = OStringProperty(oprops, "simplex")
	oprop.setValue(str(jsString))
	omesh = OPolyMesh(oxfo, imesh.getName())
	osch = omesh.getSchema()

	if pBar is not None:
		pBar.setValue(0)
		pBar.setMaximum(len(verts))
		pBar.setLabelText("Exporting Unsubdivided Shapes")
		QApplication.processEvents()

	for i, v in enumerate(verts):
		if pBar is not None:
			pBar.setValue(i)
			QApplication.processEvents()
		else:
			print "Exporting Unsubdivided Shape {0: <4}\r".format(i+1),
		sample = OPolyMeshSchemaSample(mkSampleVertexPoints(v), abcIndices, abcCounts)
		osch.set(sample)
	if pBar is None:
		print "Exporting Unsubdivided Shape {0: <4}".format(len(verts))
Example #16
0
    def split(self, pBar=None):
        self.setDefaultComboProgFalloffs()
        for falloff in self.falloffs:
            newsliders = []
            newcombos = []
            splitList = []
            for slider in self.sliders:
                sp = slider.split(falloff)
                if sp is None:
                    newsliders.append(slider)
                else:
                    newsliders.extend(sp)
                    splitList.append((slider, sp))

            for combo in self.combos:
                newcombos.extend(combo.split(falloff, splitList))

            self.sliders = newsliders[:]
            self.combos = newcombos[:]

        # Dig through sliders and combos to find the freshly split shapes and progressions
        self._resetShapes()

        self._split = True
        if self._smpx:
            rest = self.restShape._verts
            for fo in self.falloffs:
                fo.setVerts(rest)

            if pBar is not None:
                pBar.setMaximum(len(self.shapes))
                pBar.setValue(0)
                pBar.setLabelText("Splitting Shapes")
                QApplication.processEvents()

            for i, shape in enumerate(self.shapes):
                if pBar is not None:
                    pBar.setValue(i)
                    QApplication.processEvents()
                else:
                    msg = "Splitting Shape {0} of {1}: {2}".format(
                        i + 1, len(self.shapes), shape.name).ljust(120)
                    print '{0}\r'.format(msg),

                shape.applyWeights(rest)
            if pBar is None:
                print
Example #17
0
	def toSMPX(self, path, pBar=None):
		defDict = self.toJSON()
		jsString = json.dumps(defDict)

		# `False` for HDF5 `True` for Ogawa
		arch = OArchive(str(path), False) # alembic does not like unicode filepaths
		try:
			par = OXform(arch.getTop(), str(self.name))
			props = par.getSchema().getUserProperties()
			prop = OStringProperty(props, "simplex")
			prop.setValue(str(jsString))
			mesh = OPolyMesh(par, str(self.name))

			faces = Int32TPTraits.arrayType(len(self._faces))
			for i, f in enumerate(self._faces):
				faces[i] = f

			counts = Int32TPTraits.arrayType(len(self._counts))
			for i, c in enumerate(self._counts):
				counts[i] = c

			schema = mesh.getSchema()

			if pBar is not None:
				pBar.setMaximum(len(self.shapes))
				pBar.setValue(0)
				pBar.setLabelText("Exporting Split Shapes")
				QApplication.processEvents()

			for i, shape in enumerate(self.shapes):
				if pBar is not None:
					pBar.setValue(i)
					QApplication.processEvents()
				else:
					print "Exporting Shape {0} of {1}\r".format(i+1, len(self.shapes)),
				verts = shape.toSMPX()
				abcSample = OPolyMeshSchemaSample(verts, faces, counts)
				schema.set(abcSample)
			if pBar is None:
				print
		except:
			raise

		finally:
			del arch
Example #18
0
    def set_frameless_enabled(self, frameless=False):
        """
        Enables/Disables frameless mode or OS system default
        :param frameless: bool
        """

        from tpDcc.managers import tools

        tool_inst = tools.ToolsManager().get_tool_by_plugin_instance(
            self._window)
        if not tool_inst:
            return

        offset = QPoint()

        if self._window.docked():
            rect = self._window.rect()
            pos = self._window.mapToGlobal(QPoint(-10, -10))
            rect.setWidth(rect.width() + 21)
            self._window.close()
        else:
            rect = self.window().rect()
            pos = self.window().pos()
            offset = QPoint(3, 15)
            self.window().close()

        tool_inst._launch(launch_frameless=frameless)

        new_tool = tool_inst.latest_tool()

        QTimer.singleShot(
            0,
            lambda: new_tool.window().setGeometry(pos.x() + offset.x(),
                                                  pos.y() + offset.y(),
                                                  rect.width(), rect.height()))
        new_tool.framelessChanged.emit(frameless)
        QApplication.processEvents()

        return new_tool
Example #19
0
def uvTransfer(srcFaces,
               srcUvFaces,
               srcVerts,
               srcUvs,
               tarFaces,
               tarUvFaces,
               tarVerts,
               tarUvs,
               tol=0.0001,
               pBar=None):
    ''' A helper function that transfers pre-loaded data.
	The source data will be transferred onto the tar data

	Arguments:
		srcFaces(list): A face list of the source verts
		srcUvFaces(list): A face list of the source uvs
		srcUvs(np.array): The source uvs
		tarFaces(list): A face list of the target verts
		tarUvFaces(list): A face list of the target uvs
		tarUvs(np.array): The target uvs

	Returns:
		out(np.array): The target vert positions

	'''
    corr = getVertCorrelation(srcUvFaces,
                              srcUvs,
                              tarFaces,
                              tarUvFaces,
                              tarUvs,
                              tol=tol,
                              pBar=pBar)
    if pBar is not None:
        pBar.setValue(0)
        pBar.setLabelText("Apply Transfer")
        from Qt.QtWidgets import QApplication
        QApplication.processEvents()

    return applyTransfer(srcVerts, srcFaces, corr, len(tarVerts))
Example #20
0
	def _checkAllShapeValidity(self, pPairs, dataRef, errorOnMissing=False, pBar=None):
		''' Check shapes to see if they exist, and either gather the missing files, or
		Load the proper data onto the shapes
		'''
		propByName = {i.Name: i for i in list(self.shapeCluster.Properties)}

		if pBar is not None:
			pBar.setMaximum(len(pPairs))
			if errorOnMissing:
				pBar.setLabelText("Checking Validity")
			else:
				pBar.setLabelText("Checking For Missing Shapes")
			pBar.setValue(0)
			QApplication.processEvents()

		# Keep the set ordered, but make a set for quick checking
		missingNameSet = set()
		missingNames = []
		seen = set()

		for i, pp in enumerate(pPairs):
			shape = pp.shape
			if shape.name in seen:
				continue
			seen.add(shape.name)

			if pBar is not None:
				pBar.setValue(i)
				QApplication.processEvents()

			if shapeNamePrefix + shape.name not in propByName:
				if errorOnMissing:
					raise RuntimeError("Missing shape: {}".format(shape.name))
				else:
					if shape.name not in missingNameSet:
						missingNameSet.add(shape.name)
						missingNames.append(shape.name)
		return missingNames
Example #21
0
	def loadABC(self, abcMesh, js, pBar=False):
		shapes = js["shapes"]

		if pBar is not None:
			pBar.show()
			pBar.setMaximum(len(shapes))
			longName = max(shapes, key=len)
			pBar.setValue(1)
			pBar.setLabelText("Loading:\n{0}".format("_"*len(longName)))
			QApplication.processEvents()

		# Load the alembic via geometry
		preAtc = self.mesh.model.Properties('alembic_timecontrol')
		loader = dcc.io.abcImport(
			abcMesh.getArchive().getName(),
			parentNode=self.mesh.parent()
		)
		postAtc = self.mesh.model.Properties('alembic_timecontrol')
		loader = loader[0]
		loader.name = 'Loader'
		loader.Properties("Visibility").viewvis = False
		dcc.xsi.FreezeModeling(loader)
		dcc.xsi.DeleteObj("{0}.polymsh.alembic_polymesh.Expression".format(loader.FullName))

		tVal = "{0}.polymsh.alembic_polymesh.time".format(loader.FullName)
		dcc.xsi.SetValue(tVal, 0)

		rester = dcc.xsi.Duplicate(loader, 1,
			dcc.constants.siCurrentHistory, dcc.constants.siSharedParent,
			dcc.constants.siNoGrouping, dcc.constants.siNoProperties,
			dcc.constants.siNoAnimation, dcc.constants.siNoConstraints,
			dcc.constants.siNoSelection)
		rester = rester[0]
		rester.Name = 'Rester'
		rester.Properties("Visibility").viewvis = False

		dcc.xsi.FreezeObj(rester)
		matcher = self._matchDelta(loader, rester)

		cluster = self.shapeCluster.FullName
		#"{0}.polymsh.cls.Face_Shapes".format(self.mesh.FullName)

		for i, shapeName in enumerate(shapes):
			if pBar is not None:
				pBar.setValue(i)
				pBar.setLabelText("Loading:\n{0}".format(shapeName))
				QApplication.processEvents()
				if pBar.wasCanceled():
					return
			dcc.xsi.SetValue(tVal, i)
			dcc.xsi.ReplaceShapeKey("{0}.{1}".format(cluster, shapeName))

		matcher.delete()
		dcc.xsi.DeleteObj(loader)
		dcc.xsi.DeleteObj(rester)
		self.deleteShapeCombiner()
		if preAtc is None:
			dcc.xsi.DeleteObj(postAtc)

		# Force a rebuild of the Icetree
		self.recreateShapeNode()

		if pBar is not None:
			pBar.setValue(len(shapes))
Example #22
0
	def loadConnections(self, simp, create=True, multiplier=1, pBar=None):
		if pBar is not None:
			pBar.setLabelText("Loading Connections")
			QApplication.processEvents()

		# Build sliders on ctrl property
		for slider in simp.sliders:
			sliderParam = self.inProp.Parameters(slider.name)
			if not sliderParam:
				if not create:
					raise RuntimeError("Slider {0} not found with creation turned off".format(slider.name))
				self.createSlider(slider.name, slider, rebuildOp=False, multiplier=multiplier)
			else:
				slider.thing = sliderParam

		# Build/create any shapes and their icetree structures
		dataRef = self.getDataReferences(self.shapeNode)
		pPairs = set()
		for i in [simp.combos, simp.sliders]:
			for c in i:
				for p in c.prog.pairs:
					pPairs.add(p)

		if not pPairs:
			shape = simp.buildRestShape()
			if create:
				self.createRawShape(shape.name, shape, dataReferences=dataRef, deleteCombiner=False)

		# Gather all the missing shapes to create in one go
		toMake = []
		makeChecker = set()
		for i, pp in enumerate(pPairs):
			shape = pp.shape
			shapeCheck = self.checkShapeValidity(shape, dataReferences=dataRef)
			if not shapeCheck:
				if not create:
					raise RuntimeError("Shape {0} not found with creation turned off".format(shape.name))

				if shape.name not in makeChecker:
					toMake.append(shape.name)
					makeChecker.add(shape.name)

		if toMake:
			# Make 1 master duplicate and store it as a shapekey
			# This ensures that all the shapes are created on the correct cluster
			dups = dcc.xsi.Duplicate(self.mesh, 1,
				dcc.constants.siCurrentHistory, dcc.constants.siSharedParent,
				dcc.constants.siNoGrouping, dcc.constants.siNoProperties,
				dcc.constants.siNoAnimation, dcc.constants.siNoConstraints,
				dcc.constants.siSetSelection)
			dup = dups[0]
			dup.Name = "__Simplex_Master_Dup"
			dup.Properties("Visibility").viewvis = False
			newShape = dcc.xsi.StoreShapeKey(self.shapeCluster, dup.Name,
				dcc.constants.siShapeObjectReferenceMode,
				1, 0, 0, dcc.constants.siShapeContentPrimaryShape, False)
			dcc.xsi.FreezeObj(newShape)
			dcc.xsi.DeleteObj(dup)
			sks = [newShape]

			if len(toMake) > 1:
				# If there are more shapes to make, then we make them by
				# duplicating the shapes from the mixer. This is easily
				# 50x faster than creating shapes via any other method
				if pBar is not None:
					pBar.setMaximum(len(toMake))
					pBar.setValue(0)
					pBar.setLabelText("Creating Empty Shapes")
					QApplication.processEvents()

				mixShape = '{0}.{1}'.format(newShape.model.mixer.FullName, newShape.Name)
				# This may fail on stupid-dense meshes.
				# Maybe add a pointCount vs. chunk size heuristic?
				chunk = 20
				for i in range(0, len(toMake[1:]), chunk):
					if pBar is not None:
						pBar.setValue(i)
						QApplication.processEvents()

					curSize = min(len(toMake)-1, i+chunk) - i
					# Can't duplicate more than 1 at a time, otherwise we get memory issues
					dupShapes = dcc.xsi.Duplicate(mixShape, curSize,
						dcc.constants.siCurrentHistory, dcc.constants.siSharedParent,
						dcc.constants.siShareGrouping, dcc.constants.siNoProperties,
						dcc.constants.siDuplicateAnimation, dcc.constants.siShareConstraints,
						dcc.constants.siNoSelection)
					for dd in dupShapes:
						cs = dcc.xsi.GetValue('{0}.{1}'.format(self.shapeCluster.FullName, dd.Name))
						sks.append(cs)

			if pBar is not None:
				pBar.setLabelText("Naming Shapes")
				pBar.setValue(0)
				QApplication.processEvents()

			with self.noShapeNode():
				for i, (sk, name) in enumerate(zip(sks, toMake)):
					if pBar is not None:
						pBar.setValue(i)
						QApplication.processEvents()
					sk.Name = name

		if pBar is not None:
			pBar.setMaximum(len(pPairs))
			pBar.setLabelText("Checking Validity")
			pBar.setValue(0)
			QApplication.processEvents()

		dataRef = self.getDataReferences(self.shapeNode)
		seen = set()

		for i, pp in enumerate(pPairs):
			if pp.shape.name in seen:
				continue
			seen.add(pp.shape.name)
			if pBar is not None:
				pBar.setValue(i)
				QApplication.processEvents()

			shape = pp.shape
			s = self.shapeCluster.Properties(shape.name)
			shapeCheck = self.checkShapeValidity(shape, dataReferences=dataRef)
			if shapeCheck:
				shapeNodes = self.getShapeIceNodes(s, dataRef)
				shape.thing = [s] + shapeNodes
			else:
				raise RuntimeError("Missing shape: {}".format(shape.name))

		self.freezeAllShapes()
		self.deleteShapeCombiner()

		#connect the operator after building shapes
		self.resetShapeIndexes()

		if create:
			self.rebuildSliderNode()
Example #23
0
def simplexUvTransfer(srcSmpxPath,
                      tarPath,
                      outPath,
                      srcUvPath=None,
                      tol=0.0001,
                      pBar=None):
    """ Transfer a simplex system onto a mesh through UV space

	Arguments:
		srcSmpxPath (str): The path to the source .smpx file
		tarPath (str): The path to the mesh to recieve the blendshapes
		outPath (str): The .smpx path that will be written
		srcUvPath (str): If the .smpx file doesn't have UV's, then the UV's
			from this mesh wil be used. Defaults to None
		tol (float): The tolerance for checking if a UV is outside of a poly
		pBar (QProgressDialog): Optional progress bar
	"""
    if pBar is not None:
        pBar.setLabelText("Loading Source Mesh")
        from Qt.QtWidgets import QApplication
        QApplication.processEvents()

    srcUvPath = srcUvPath or srcSmpxPath
    if srcUvPath.endswith('.abc') or srcUvPath.endswith('.smpx'):
        src = Mesh.loadAbc(srcUvPath, ensureWinding=False)
    elif srcUvPath.endswith('.obj'):
        src = Mesh.loadObj(srcUvPath, ensureWinding=False)
    srcVerts = abc.getSampleArray(abc.getMesh(srcSmpxPath))

    if pBar is not None:
        pBar.setLabelText("Loading Target Mesh")
        from Qt.QtWidgets import QApplication
        QApplication.processEvents()
    if tarPath.endswith('.abc'):
        tar = Mesh.loadAbc(tarPath, ensureWinding=False)
    elif tarPath.endswith('.obj'):
        tar = Mesh.loadObj(tarPath, ensureWinding=False)

    srcFaces = src.faceVertArray
    srcUvFaces = src.uvFaceMap['default']
    srcUvs = np.array(src.uvMap['default'])

    tarFaces = tar.faceVertArray
    tarUvFaces = tar.uvFaceMap['default']
    tarUvs = np.array(tar.uvMap['default'])
    oldTarVerts = np.array(tar.vertArray)

    corr = getVertCorrelation(srcUvFaces,
                              srcUvs,
                              tarFaces,
                              tarUvFaces,
                              tarUvs,
                              tol=tol,
                              pBar=pBar)
    tarVerts = applyTransfer(srcVerts, srcFaces, corr, len(oldTarVerts))

    # Apply as a delta
    deltas = tarVerts - tarVerts[0][None, ...]
    writeVerts = oldTarVerts[None, ...] + deltas

    jsString, name = _loadJSString(srcSmpxPath)
    oarch = OArchive(str(outPath), OGAWA)  # false for HDF5
    abc.buildAbc(oarch,
                 writeVerts,
                 tarFaces,
                 uvs=tarUvs,
                 uvFaces=tarUvFaces,
                 name=name,
                 shapeSuffix='',
                 propDict=dict(simplex=jsString))
Example #24
0
def simplexUvTransfer(srcSmpxPath,
                      tarPath,
                      outPath,
                      srcUvPath=None,
                      tol=0.0001,
                      pBar=None):
    """ Transfer a simplex system onto a mesh through UV space

	Parameters
	----------
	srcSmpxPath : str
		The path to the source .smpx file
	tarPath : str
		The path to the mesh to recieve the blendshapes
	outPath : str
		The .smpx path that will be written
	srcUvPath : str
		If the .smpx file doesn't have UV's, then the UV's
		from this mesh wil be used. Defaults to None
	tol : float
		The tolerance for checking if a UV is outside of a poly
	pBar : QProgressDialog
		Optional progress bar

	"""
    if np is None:
        raise RuntimeError(
            "UV Transfer requires Numpy. It is currently unavailable")

    if pBar is not None:
        pBar.setLabelText("Loading Source Mesh")
        from Qt.QtWidgets import QApplication
        QApplication.processEvents()

    srcUvPath = srcUvPath or srcSmpxPath
    if srcUvPath.endswith('.abc') or srcUvPath.endswith('.smpx'):
        src = Mesh.loadAbc(srcUvPath, ensureWinding=False)
    elif srcUvPath.endswith('.obj'):
        src = Mesh.loadObj(srcUvPath, ensureWinding=False)

    if pBar is not None:
        pBar.setLabelText("Loading Target Mesh")
        from Qt.QtWidgets import QApplication
        QApplication.processEvents()
    if tarPath.endswith('.abc'):
        tar = Mesh.loadAbc(tarPath, ensureWinding=False)
    elif tarPath.endswith('.obj'):
        tar = Mesh.loadObj(tarPath, ensureWinding=False)

    jsString, _, srcVerts, _, _, _ = readSmpx(srcSmpxPath)
    js = json.loads(jsString)
    name = js['systemName']

    srcFaces = src.faceVertArray
    srcUvFaces = src.uvFaceMap['default']
    srcUvs = np.array(src.uvMap['default'])

    tarFaces = tar.faceVertArray
    tarUvFaces = tar.uvFaceMap['default']
    tarUvs = np.array(tar.uvMap['default'])
    oldTarVerts = np.array(tar.vertArray)

    corr = getVertCorrelation(srcUvFaces,
                              srcUvs,
                              tarFaces,
                              tarUvFaces,
                              tarUvs,
                              tol=tol,
                              pBar=pBar)
    tarVerts = applyTransfer(srcVerts, srcFaces, corr, len(oldTarVerts))

    # Apply as a delta
    deltas = tarVerts - tarVerts[0][None, ...]
    writeVerts = oldTarVerts[None, ...] + deltas

    buildSmpx(outPath,
              writeVerts,
              tarFaces,
              jsString,
              name,
              uvs=tarUvs,
              uvFaces=tarUvFaces,
              ogawa=OGAWA)
Example #25
0
def process_ui_events():
    """
    Processes all events currently in the application events queue
    """

    QApplication.processEvents()
Example #26
0
def applyCorrectives(simplex,
                     allShapePts,
                     restPts,
                     solver,
                     shapes,
                     refIdxs,
                     references,
                     pBar=None):
    '''
	Loop over the shapes and references, apply them, and return a new np.array
	of shape points

	simplex: Simplex system
	allShapePts: deltas per shape
	restPts: The rest point positions
	solver: The Python Simplex solver object
	shapes: The simplex shape objects we care about
	refIdxs: The reference index per shape
	references: A list of matrix-per-points
	'''
    # The rule of thumb is "THE SHAPE IS ALWAYS A DELTA"

    if pBar is not None:
        pBar.setLabelText("Inverting References")
        pBar.setValue(0)
        pBar.setMaximum(len(references))
        QApplication.processEvents()
    else:
        print "Inverting References"

    inverses = []
    for i, r in enumerate(references):
        if pBar is not None:
            pBar.setValue(i)
            QApplication.processEvents()
        inverses.append(invertAll(r))

    if pBar is not None:
        pBar.setLabelText("Extracting Uncorrected Shapes")
        QApplication.processEvents()
    else:
        print "Building Full Shapes"
    ptsByShape, vecByShape = buildFullShapes(simplex, shapes, allShapePts,
                                             solver, restPts, pBar)

    if pBar is not None:
        pBar.setLabelText("Correcting")
        QApplication.processEvents()
    else:
        print "Correcting"
    newPtsByShape = {}
    for shape, refIdx in zip(shapes, refIdxs):
        inv = inverses[refIdx]
        pts = ptsByShape[shape]
        newPts = applyReference(pts, inv)
        newPtsByShape[shape] = newPts

    newShapePts = collapseFullShapes(simplex, allShapePts, newPtsByShape,
                                     vecByShape, pBar)
    newShapePts = newShapePts + restPts[None, ...]

    return newShapePts
Example #27
0
def collapseFullShapes(simplex, allPts, ptsByShape, vecByShape, pBar=None):
    '''
	Given a set of shapes that are full-on shapes (not just deltas)
	Collapse them back into deltas in the simplex shape list
	'''
    #######################
    # Manipulate all the input lists and caches
    #indexBySlider = {s: i for i, s in enumerate(simplex.sliders)}
    indexByShape = {s: i for i, s in enumerate(simplex.shapes)}
    floaters = set(simplex.getFloatingShapes())
    #floatIdxs = set([indexByShape[s] for s in floaters])
    newPts = np.copy(allPts)

    # Order the combos by depth, and split out the floaters
    allDFirst = sorted(simplex.combos[:], key=lambda x: len(x.pairs))
    dFirst, dFloat = [], []
    for c in allDFirst:
        app = dFloat if c.isFloating() else dFirst
        app.append(c)

    # first do the sliders
    for item in simplex.sliders:
        for pair in item.prog.pairs:
            if pair.shape in ptsByShape:
                idx = indexByShape[pair.shape]
                newPts[idx] = ptsByShape[pair.shape]

    # Get the max number of iterations
    mxcount = 0
    for c in itertools.chain(dFirst, dFloat):
        for pair in c.prog.pairs:
            if pair.shape in ptsByShape:
                mxcount += 1

    if pBar is not None:
        pBar.setValue(0)
        pBar.setMaximum(mxcount)
        pBar.setLabelText("Building Corrected Deltas")
        QApplication.processEvents()

    # Then go through all the combos in order
    vcount = 0
    for c in itertools.chain(dFirst, dFloat):

        for pair in c.prog.pairs:
            if pair.shape in ptsByShape:
                if pBar is not None:
                    pBar.setValue(vcount)
                    QApplication.processEvents()
                else:
                    print "Collapsing {0} of {1}\r".format(
                        vcount + 1, mxcount),
                vcount += 1

                idx = indexByShape[pair.shape]
                outVec = vecByShape[pair.shape]
                outVec[
                    idx] = 0.0  # turn off the influence of the current shape
                comboBase = np.dot(outVec, newPts.transpose((1, 0, 2)))
                comboSculpt = ptsByShape[pair.shape]
                newPts[idx] = comboSculpt - comboBase
    if pBar is None:
        print

    return newPts
Example #28
0
def buildCorrectiveReferences(mesh, simplex, poses, sliders, pBar=None):
	'''
	Take correlated poses and sliders, and expand down the
	simplex combo tree, building references for each required shape

	Inputs:
		simplex <SimplexSystem> : A simplex system
		solver <PySimplex> : An instantiated simplex value solver
		poses <Prop/Value pair lists> : Different rig poses
		shapes <SimplexShapes> : Shape objects correlated to the poses
	'''
	# cache the pose search

	# Pre-cache the combo search
	allCombosBySliderValue = {}
	for c in simplex.combos:
		for p in c.pairs:
			allCombosBySliderValue.setdefault((p.slider, p.value), []).append(c)

	# This is only my subset set of downstreams
	# Get the downstreams by slider and value
	sliderValuesByCombo = {}
	for slider in sliders:
		for p in slider.prog.pairs:
			combos = allCombosBySliderValue.get((slider, p.value), [])
			for combo in combos:
				sliderValuesByCombo.setdefault(combo, []).append((slider, p.value))

	#out = []
	refCache = {}
	refs, shapes, refIdxs = [], [], []

	# get the slider outputs
	if pBar is not None:
		pBar.setLabelText("Building Shape References")
		pBar.setValue(0)
		mv = 0
		for slider in sliders:
			for p in slider.prog.pairs:
				if not p.shape.isRest:
					mv += 1
		pBar.setMaximum(mv)
		QApplication.processEvents()

	poseBySlider = {}
	for slider, pose in zip(sliders, poses):
		poseBySlider[slider] = pose
		for p in slider.prog.pairs:
			if not p.shape.isRest:
				if pBar is not None:
					pBar.setValue(pBar.value())
					QApplication.processEvents()
				cacheKey = frozenset([(slider, p.value)])
				if cacheKey in refCache:
					idx = refCache[cacheKey]
					refIdxs.append(idx)
				else:
					ref = getRefForPoses(mesh, [pose], p.value)
					refIdxs.append(len(refs))
					refCache[cacheKey] = len(refs)
					refs.append(ref)
				shapes.append(p.shape)

	# Get the combo outputs
	if pBar is not None:
		pBar.setLabelText("Building Combo References")
		pBar.setValue(0)
		mv = 0
		for combo in sliderValuesByCombo.iterkeys():
			for p in combo.prog.pairs:
				if not p.shape.isRest:
					mv += 1
		pBar.setMaximum(mv)
		QApplication.processEvents()

	for combo, sliderVals in sliderValuesByCombo.iteritems():
		#components = frozenset(sliderVals)
		poses = [poseBySlider[s] for s, _ in sliderVals]
		for p in combo.prog.pairs:
			if not p.shape.isRest:
				if pBar is not None:
					pBar.setValue(pBar.value())
					QApplication.processEvents()

				cacheKey = frozenset(sliderVals)
				if cacheKey in refCache:
					idx = refCache[cacheKey]
					refIdxs.append(idx)
				else:
					ref = getRefForPoses(mesh, poses, p.value)
					refIdxs.append(len(refs))
					refCache[cacheKey] = len(refs)
					refs.append(ref)
				shapes.append(p.shape)

	return np.array(refs), shapes, refIdxs
Example #29
0
	def loadNodes(self, simp, thing, create=True, pBar=None):
		"""
		Create a new system based on the simplex tree
		Build any DCC objects that are missing if create=True
		Raises a runtime error if missing objects are found and
		create=False
		"""
		self.name = simp.name
		self.mesh = thing

		# find/build the shapeCluster
		if pBar is not None:
			pBar.setLabelText("Loading Nodes")
			QApplication.processEvents()

		shapeCluster = thing.ActivePrimitive.Geometry.Clusters("Shape")
		if not shapeCluster:
			shapeCluster = thing.ActivePrimitive.Geometry.Clusters("%s_Shapes" %self.name)
		if not shapeCluster:
			if not create:
				raise RuntimeError("Shape cluster not found with creation turned off")
			self.shapeCluster = dcc.xsi.CreateCluster("%s.pnt[*]" %thing.FullName)[0]
			self.shapeCluster.Name = "%s_Shapes" %self.name
			dcc.xsi.SelectObj(thing)
		else:
			self.shapeCluster = shapeCluster

		# find/build the Icetree
		shapeTree = thing.ActivePrimitive.ICETrees("%s_IceTree" %self.name)
		if not shapeTree:
			if not create:
				raise RuntimeError("Simplex shape ICETree not found with creation turned off")
			self.shapeTree = dcc.ice.ICETree(None, self.mesh, "%s_IceTree" %self.name,
									dcc.constants.siConstructionModePrimaryShape)
		else:
			self.shapeTree = dcc.ice.ICETree(shapeTree)

		#find/build the Slider compound in the Icetree
		sliderComp = None
		for node in self.shapeTree.compoundNodes:
			if node.name == "SliderArray":
				sliderComp = node
				break
		if not sliderComp:
			if not create:
				raise RuntimeError("Slider array compound not found in ICETree with creation turned off")
			sliderArray = self.shapeTree.addNode("BuildArray")
			sliderComp = self.shapeTree.createCompound([sliderArray])
			sliderComp.rename("SliderArray")
			sliderComp.exposePort(sliderArray.outputPorts["array"])
		self.sliderNode = sliderComp

		#find/build the simplex node in the Icetree
		ops = DCC.getSimplexOperatorsOnObjectByName(thing, self.name)
		if not ops:
			if not create:
				raise RuntimeError("Simplex Operator not found with create turned off")
			op = dcc.ice.ICENode(dcc.xsi.AddICENode("SimplexNode", self.shapeTree.fullName))
			op.inputPorts["Sliders"].connect(self.sliderNode.outputPorts["Array"])
			setter = self.shapeTree.addSetDataNode("Self._%s_SimplexVector" %self.name)
			setter.Value.connect(op.outputPorts["Weights"])
			self.shapeTree.connect(setter.Execute, 1)
			self.op = op
		else:
			self.op = ops

		#find/build the shape compound in the Icetree
		shapeNode = None
		for node in self.shapeTree.compoundNodes:
			if node.name == "ShapeCompound":
				shapeNode = node
				break
		if not shapeNode:
			if not create:
				raise RuntimeError("Shape compound not found in ICETree with creation turned off")
			# addNode = self.shapeTree.addNode("Add")
			# passNode = self.shapeTree.addNode("PassThrough")

			# getPointNode = self.shapeTree.addGetDataNode("Self.PointPosition")
			# port = DCC.firstAvailablePort(addNode)
			# getPointNode.value.connect(port)

			# shapeCompound = self.shapeTree.createCompound([addNode,passNode,getPointNode])
			# shapeCompound.rename("ShapeCompound")
			# shapeCompound.exposePort(addNode.outputPorts["result"])
			# shapeCompound.exposePort(passNode.inputPorts["in"])

			setter = self.shapeTree.addSetDataNode("Self.PointPosition")
			#shapeCompound.outputPorts["Result"].connect(setter.inputPorts["Value"])
			self.shapeTree.connect(setter.Execute)

			shapeCompound = self.rebuildShapeNode(simp)

			getter = self.shapeTree.addGetDataNode("Self._%s_SimplexVector" %self.name)
			getter.value.connect(shapeCompound.inputPorts["In"])

			setter.Value.connect(shapeCompound.Result)

			self.shapeNode = shapeCompound
		else:
			self.shapeNode = shapeNode

		# find/build the input and output properties
		inProp = thing.Properties("%s_inProperty" %self.name)
		if not inProp:
			if not create:
				raise RuntimeError("Control parameters not found with creation turned off")
			self.inProp = self.mesh.AddProperty("CustomProperty", False, "%s_inProperty" %self.name)
		else:
			self.inProp = inProp
Example #30
0
def matchByTopology(orderMesh,
                    shapeMesh,
                    vertexPairs,
                    matchedNum=None,
                    vertNum=None,
                    symmetry=False,
                    pBar=None):
    """ Match the topology of two meshes with different vert orders

	Provide a 1:1 vertex index match between two meshes that don't
	necessarily have the same vertex order.
	At minimum, 3 vertex pairs around a single poly are required.

	The algorithm simultaneously performs two different types of
	"grow verts" operations on each mesh and each vertex keeps track
	of where it was grown from, and how.
	New matching verts will be grown from known matching verts in the
	same way.

	Example:
	Starting with two meshes and a "matched" vertex selection on two meshes
	I'm calling M1 and M2

	Say:
	v6 on M1 is an edge away from (v3 ,v5) and a face away from (v3 ,v5,v4)
	v9 on M2 is an edge away from (v13,v2) and a face away from (v13,v2,v6)
	And we know going in that: M1.v3=M2.v13, M1.v5=M2.v2, M1.v4=M2.v6

	Then we can say M1.v6=M2.v9 becaue if we substitute all of our known
	matches, we can see that the two vertices are equivalent

	Args:
		M1: A Mesh object
		M2: A second Mesh object
		vertPairs: A List of 2-Tuples of vertex indices
			that are known matches
		matchedNum: The total number of vertices matched up to this point
		vertNum: The total number of vertices in the mesh, for
			percentageDone purposes. Default: None
		symmetry: Boolean value indicating whether the vertPairs
			are mirrored indices on the same mesh. Default: False

	Returns:
		A list of (Vertex,Vertex) pairs that define 1:1 matches

	"""
    orderVerts, shapeVerts = zip(*vertexPairs)
    orderVertsGrow = set(orderVerts)
    shapeVertsGrow = set(shapeVerts)
    orderVerts = set(orderVerts)
    shapeVerts = set(shapeVerts)
    centerVerts = set()

    orderToShape = {s: t for s, t in vertexPairs}

    if vertNum is not None:
        vertNum = float(vertNum)  #just for percentage reporting
        if symmetry:
            vertNum /= 2

    if not matchedNum:
        matchedNum = 0

    updated = True
    #counter = 0
    while updated:
        updated = False
        if vertNum is not None:
            percent = (len(orderVerts) + matchedNum) / vertNum * 100
            if pBar is None:
                print "\rPercentage processed: {0:.2f}%".format(percent),
            else:
                pBar.setValue(int(percent))
                QApplication.processEvents()

        # Grow the vert selection along edges and save as a set
        # Grow the vert selection along faces and save as another set
        # Remove already selected verts from both lists
        #
        # The dicts are structured like this:
        #	 {vertex: (orderVertTuple), ...}

        if symmetry:
            # A fun little hack that allows me to treat the left and
            # right hand sides of a model with symmetrical topology as
            # two different meshes to match
            allSet = orderVerts | shapeVerts | centerVerts
            lAllSet = allSet
            rAllSet = allSet
        else:
            lAllSet = orderVerts
            rAllSet = shapeVerts

        orderEdgeDict, orderFaceDict, orderVertsGrow = growTracked(
            orderMesh, orderVertsGrow, lAllSet)
        shapeEdgeDict, shapeFaceDict, shapeVertsGrow = growTracked(
            shapeMesh, shapeVertsGrow, rAllSet)

        # if a key has a *unique* (face & edge) value
        # we can match it to the shape
        #
        # so flip the dicts and key off of *BOTH* values
        # simultaneously
        orderEFDict = flipMultiDict(orderEdgeDict, orderFaceDict)
        shapeEFDict = flipMultiDict(shapeEdgeDict, shapeFaceDict)

        # Then, if the swapped dict's value only has 1 item
        # it is a uniquely identified vertex and can be matched
        for orderKey, orderValue in orderEFDict.iteritems():
            if len(orderValue) == 1:
                edgeKey = frozenset(orderToShape[i] for i in orderKey[0])
                faceKey = frozenset(orderToShape[i] for i in orderKey[1])

                try:
                    shapeValue = shapeEFDict[(edgeKey, faceKey)]
                except KeyError:
                    area = list(edgeKey) + list(faceKey)
                    area = list(set(area))
                    vp = vertexPairs[:]
                    iidx, jidx = zip(*vp)
                    m = 'Order {0} to Shape {1}: Match produced no results: Check this order area {2}'.format(
                        iidx, jidx, area)
                    #if vertNum is not None and pBar is None:
                    #print #clear the percentage value
                    raise TopologyMismatch(m)

                if len(shapeValue) != 1:
                    #if vertNum is not None and pBar is None:
                    #print #clear the percentage value
                    raise TopologyMismatch('Match produced multiple results')

                orderVert = orderValue[0]
                shapeVert = shapeValue[0]
                if (shapeVert == orderVert) and symmetry:
                    centerVerts.add(shapeVert)
                else:
                    orderToShape[orderVert] = shapeVert
                    orderVerts.add(orderVert)
                    orderVertsGrow.add(orderVert)
                    shapeVerts.add(shapeVert)
                    shapeVertsGrow.add(shapeVert)

                #pair = (orderVert, shapeVert)
                updated = True

    if vertNum is not None and pBar is None:
        print  #clear the percentage value

    #if vertNum is not None:
    #print "\n"
    return [(k, v) for k, v in orderToShape.iteritems()]