def shapeMatchIndexes(self, indexes): # make a dict of name:object sel = DCC.getSelectedObjects() if not sel: return mesh = sel[0] pairs = coerceIndexToChildType(indexes, ProgPair) pairs = [i.model().itemFromIndex(i) for i in pairs] pairs = makeUnique([i for i in pairs if not i.shape.isRest]) # Set up the progress bar pBar = QProgressDialog("Matching Shapes", "Cancel", 0, 100, self) pBar.setMaximum(len(pairs)) # Do the extractions for pair in pairs: c = pair.prog.controller c.connectShape(pair.shape, mesh=mesh) # ProgressBar pBar.setValue(pBar.value() + 1) pBar.setLabelText("Matching:\n{0}".format(pair.shape.name)) QApplication.processEvents() if pBar.wasCanceled(): return pBar.close()
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} shapeNames = js['shapes'] if js['encodingVersion'] > 1: shapeNames = [i['name'] for i in shapeNames] shapes = [shapeDict[i] for i in shapeNames] schema = abcMesh.getSchema() if pBar is not None: pBar.show() pBar.setMaximum(len(shapes)) spacerName = '_' * max(map(len, shapeNames)) pBar.setLabelText('Exporting:\n{0}'.format(spacerName)) QApplication.processEvents() for i, shape in enumerate(shapes): if pBar is not None: pBar.setLabelText('Exporting:\n{0}'.format(shape.name)) pBar.setValue(i) QApplication.processEvents() if pBar.wasCanceled(): return verts = mkSampleVertexPoints(self._shapes[shape.name]) if self._uvs is not None: # Alembic doesn't allow for self._uvs=None for some reason abcSample = OPolyMeshSchemaSample(verts, self._faces, self._counts, self._uvs) else: abcSample = OPolyMeshSchemaSample(verts, self._faces, self._counts) schema.set(abcSample)
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) 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 _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 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 shapeIndexExtract(self, indexes, live=None): # Create meshes that are possibly live-connected to the shapes if live is None: live = self.uiLiveShapeConnectionACT.isChecked() pairs = coerceIndexToChildType(indexes, ProgPair) pairs = [i.model().itemFromIndex(i) for i in pairs] pairs = makeUnique([i for i in pairs if not i.shape.isRest]) pairs.sort(key=lambda x: naturalSortKey(x.shape.name)) # Set up the progress bar pBar = QProgressDialog("Extracting Shapes", "Cancel", 0, 100, self) pBar.setMaximum(len(pairs)) # Do the extractions offset = 10 for pair in pairs: c = pair.prog.controller c.extractShape(pair.shape, live=live, offset=offset) offset += 5 # ProgressBar pBar.setValue(pBar.value() + 1) pBar.setLabelText("Extracting:\n{0}".format(pair.shape.name)) QApplication.processEvents() if pBar.wasCanceled(): return pBar.close()
def exportUnsub(inPath, outPath, newFaces, kept, shapePrefix=None, 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() if shapePrefix is not None: d = json.loads(jsString) if d['encodingVersion'] > 1: for shape in d['shapes']: shape['name'] = shapePrefix + shape['name'] else: d['shapes'] = [shapePrefix + i for i in d['shapes']] jsString = json.dumps(d) 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 buildFullShapes(simplex, shapeObjs, shapes, 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(shapeObjs)) pBar.setValue(0) QApplication.processEvents() flatShapes = shapes.reshape((len(shapes), -1)) for i, shape in enumerate(shapeObjs): if pBar is not None: pBar.setValue(i) QApplication.processEvents() else: print "Building {0} of {1}\r".format(i+1, len(shapeObjs)), 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, flatShapes) pts = pts.reshape((-1, 3)) ptsByShape[shape] = pts + restPts if pBar is None: print return ptsByShape, vecByShape
def importSystemFromFile(self): if self._currentObject is None: impTypes = ['smpx'] else: impTypes = ['smpx', 'json'] if blurdev is None: pref = QSettings("Blur", "Simplex3") defaultPath = str(toPyObject(pref.value('systemImport', os.path.join(os.path.expanduser('~'))))) path = self.fileDialog("Import Template", defaultPath, impTypes, save=False) if not path: return pref.setValue('systemImport', os.path.dirname(path)) pref.sync() else: # Blur Prefs pref = blurdev.prefs.find('tools/simplex3') defaultPath = pref.restoreProperty('systemImport', os.path.join(os.path.expanduser('~'))) path = self.fileDialog("Import Template", defaultPath, impTypes, save=False) if not path: return pref.recordProperty('systemImport', os.path.dirname(path)) pref.save() pBar = QProgressDialog("Loading Shapes", "Cancel", 0, 100, self) pBar.show() QApplication.processEvents() # TODO: Come up with a better list of possibilites for loading # simplex files, and make the appropriate methods on the Simplex if path.endswith('.smpx'): newSystem = Simplex.buildSystemFromSmpx(path, self._currentObject, sliderMul=self._sliderMul, pBar=pBar) elif path.endswith('.json'): newSystem = Simplex.buildSystemFromJson(path, self._currentObject, sliderMul=self._sliderMul, pBar=pBar) with signalsBlocked(self.uiCurrentSystemCBOX): self.loadObject(newSystem.DCC.mesh) idx = self.uiCurrentSystemCBOX.findText(self._currentObjectName) if idx >= 0: self.uiCurrentSystemCBOX.setCurrentIndex(idx) else: self.uiCurrentSystemCBOX.addItem(newSystem.name) self.uiCurrentSystemCBOX.setCurrentIndex(self.uiCurrentSystemCBOX.count()-1) self.setSystem(newSystem) pBar.close()
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 shapeConnectFromSelection(self): if self.simplex is None: return # make a dict of name:object sel = DCC.getSelectedObjects() selDict = {} for s in sel: name = DCC.getObjectName(s) if name.endswith("_Extract"): nn = name.rsplit("_Extract", 1)[0] selDict[nn] = s pairDict = {} for p in self.simplex.progs: for pp in p.pairs: pairDict[pp.shape.name] = pp # get all common names selKeys = set(selDict.iterkeys()) pairKeys = set(pairDict.iterkeys()) common = selKeys & pairKeys # get those items pairs = [pairDict[i] for i in common] # Set up the progress bar pBar = QProgressDialog("Connecting Shapes", "Cancel", 0, 100, self) pBar.setMaximum(len(pairs)) # Do the extractions for pair in pairs: c = pair.prog.controller c.connectShape(pair.shape, delete=True) # ProgressBar pBar.setValue(pBar.value() + 1) pBar.setLabelText("Connecting:\n{0}".format(pair.shape.name)) QApplication.processEvents() if pBar.wasCanceled(): return pBar.close()
def shapeConnectIndexes(self, indexes): pairs = coerceIndexToChildType(indexes, ProgPair) pairs = [i.model().itemFromIndex(i) for i in pairs] pairs = makeUnique([i for i in pairs if not i.shape.isRest]) # Set up the progress bar pBar = QProgressDialog("Connecting Shapes", "Cancel", 0, 100, self) pBar.setMaximum(len(pairs)) # Do the extractions for pair in pairs: c = pair.prog.controller c.connectShape(pair.shape, delete=True) # ProgressBar pBar.setValue(pBar.value() + 1) pBar.setLabelText("Extracting:\n{0}".format(pair.shape.name)) QApplication.processEvents() if pBar.wasCanceled(): return pBar.close()
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 importObjFolder(self, folder): ''' Import all objs from a user selected folder ''' if not os.path.isdir(folder): QMessageBox.warning(self, 'Warning', 'Folder does not exist') return paths = os.listdir(folder) paths = [i for i in paths if i.endswith('.obj')] if not paths: QMessageBox.warning(self, 'Warning', 'Folder does not contain any .obj files') return shapeDict = {shape.name: shape for shape in self.simplex.shapes} inPairs = {} for path in paths: shapeName = os.path.splitext(os.path.basename(path))[0] shape = shapeDict.get(shapeName) if shape is not None: inPairs[shapeName] = path else: sfx = "_Extract" if shapeName.endswith(sfx): shapeName = shapeName[:-len(sfx)] shape = shapeDict.get(shapeName) if shape is not None: inPairs[shapeName] = path sliderMasters, comboMasters = {}, {} for masters in [self.simplex.sliders, self.simplex.combos]: for master in masters: for pp in master.prog.pairs: shape = shapeDict.get(pp.shape.name) if shape is not None: if shape.name in inPairs: if isinstance(master, Slider): sliderMasters[shape.name] = master if isinstance(master, Combo): comboMasters[shape.name] = master comboDepth = {} for k, v in comboMasters.iteritems(): depth = len(v.pairs) comboDepth.setdefault(depth, {})[k] = v pBar = QProgressDialog("Loading from Mesh", "Cancel", 0, len(comboMasters) + len(sliderMasters), self) pBar.show() for shapeName, slider in sliderMasters.iteritems(): pBar.setValue(pBar.value() + 1) pBar.setLabelText("Loading Obj :\n{0}".format(shapeName)) QApplication.processEvents() if pBar.wasCanceled(): return path = inPairs[shapeName] mesh = self.simplex.DCC.importObj(os.path.join(folder, path)) shape = shapeDict[shapeName] self.simplex.DCC.connectShape(shape, mesh=mesh, live=False, delete=True) for depth in sorted(comboDepth.keys()): for shapeName, combo in comboDepth[depth].iteritems(): pBar.setValue(pBar.value() + 1) pBar.setLabelText("Loading Obj :\n{0}".format(shapeName)) QApplication.processEvents() if pBar.wasCanceled(): return path = inPairs[shapeName] mesh = self.simplex.DCC.importObj(os.path.join(folder, path)) shape = shapeDict[shapeName] self.simplex.DCC.connectComboShape(combo, shape, mesh=mesh, live=False, delete=True) pBar.close()