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)
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()
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, "")
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))
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"
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)
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()
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
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))
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)
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)
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
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
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))
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
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
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
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))
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
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))
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()
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))
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)
def process_ui_events(): """ Processes all events currently in the application events queue """ QApplication.processEvents()
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
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
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
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
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()]