def toSMPX(self, path): defDict = self.toJSON() jsString = json.dumps(defDict) arch = OArchive(str(path)) # 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() for shape in self.shapes: verts = shape.toSMPX() abcSample = OPolyMeshSchemaSample(verts, faces, counts) schema.set(abcSample) except: raise finally: del arch
def buildAlembicArchiveData(path, name, jsString, ogawa): ''' Set up an output alembic archive with a mesh ready for writing Parameters ---------- path : str The output file path name : str The name of the system jsString : str The simplex definition string ogawa : bool Whether to open in Ogawa (True) or HDF5 (False) mode Returns ------- : OArchive The opened alembic output archive : OPolyMesh The mesh to write the shape data to ''' arch = OArchive(str(path), ogawa) par, props, abcMesh = [None] * 3 try: par = OXform(arch.getTop(), str(name)) props = par.getSchema().getUserProperties() writeStringProperty(props, 'simplex', jsString, ogawa) abcMesh = OPolyMesh(par, str(name)) except Exception: arch, par, props, abcMesh = [None] * 4 raise return arch, abcMesh
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 exportABC(self, path): defDict = self.simplex.buildDefinition() jsString = json.dumps(defDict) arch = OArchive(str(path)) # 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)) self.DCC.exportABC(mesh, defDict) finally: del arch
def mismatchedTransfer(sourcePath, matchPath, outPath, invertMatch=False): ''' Transfer shape data from the sourcePath using the numpy int array at matchPath to make the final output at outPath ''' print "Loading Simplex" sourceArch = IArchive(str(sourcePath)) # because alembic hates unicode sourceShapes = getShapes(sourceArch) jsString = loadJSString(sourceArch) sFaces, counts = getMesh(sourceArch) sFaces = np.array(sFaces) print "Loading Correspondence" c = np.load(matchPath) c = c[c[:, 0].argsort()].T[1] ci = c.argsort() if invertMatch: ci, c = c, ci print "Reordering" targetShapes = sourceShapes[:, c, :] faces = mkSampleIntArray(ci[sFaces]) print "Writing" oarch = OArchive(str(outPath)) # alembic does not like unicode filepaths try: _writeSimplex(oarch, 'Face', jsString, faces, counts, targetShapes) finally: del oarch gc.collect()
def extractExternal(self, path, dccMesh): # Extract shapes from an arbitrary mesh based on the current simplex defDict = self.simplex.buildDefinition() jsString = json.dumps(defDict) arch = OArchive(str(path)) # 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)) abcMesh = OPolyMesh(par, str(self.name)) self.DCC.exportABC(dccMesh, abcMesh, defDict) finally: del arch
def writeSimplex(inPath, outPath, newShapes, name='Face', pBar=None): '''Write a simplex file with new shapes Parameters ---------- inPath : str The input .smpx file path outPath : str The output .smpx file path newShapes : np.array A numpy array of shapes to write name : str The name of the new system pBar : QProgressDialog, optional An optional progress dialog Returns ------- ''' if not os.path.isfile(str(inPath)): raise IOError("File does not exist: " + str(inPath)) iarch = IArchive(str(inPath)) jsString = loadJSString(iarch) faces, counts = loadMesh(iarch) del iarch oarch = OArchive(str(outPath), OGAWA) # alembic does not like unicode filepaths try: _writeSimplex(oarch, name, jsString, faces, counts, newShapes, pBar) finally: del oarch gc.collect()
def hdf5Convert(inPath, outPath, ogawa=False): '''Load and parse all the data from a simplex file Parameters ---------- inPath : str The input .smpx file path outPath : str The output .smpx file path ogawa : bool Whether to write out in Ogawa format. Defaults False Returns ------- ''' if not os.path.isfile(str(inPath)): raise IOError("File does not exist: " + str(inPath)) iarch = IArchive(str(inPath)) jsString = loadJSString(iarch) shapes = loadSmpx(iarch) faces, counts = loadMesh(iarch) del iarch oarch = OArchive(str(outPath), ogawa) # alembic does not like unicode filepaths try: _writeSimplex(oarch, 'Face', jsString, faces, counts, shapes) finally: del oarch gc.collect()
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 _exportUnsub(outPath, xfoName, meshName, jsString, faces, verts, uvFaces, uvs, pBar=None): oarch = OArchive(str(outPath), OGAWA) # False for HDF5 oxfo = OXform(oarch.getTop(), xfoName) oprops = oxfo.getSchema().getUserProperties() oprop = OStringProperty(oprops, "simplex") oprop.setValue(str(jsString)) omesh = OPolyMesh(oxfo, meshName) osch = omesh.getSchema() if pBar is not None: pBar.setValue(0) pBar.setMaximum(len(verts)) pBar.setLabelText("Exporting Unsubdivided Shapes") QApplication.processEvents() abcIndices = mkSampleIntArray(list(chain.from_iterable(faces))) abcCounts = mkSampleIntArray(map(len, faces)) kwargs = {} if uvs is not None: # Alembic doesn't use None as the placeholder for un-passed kwargs # So I don't have to deal with that, I only set the kwarg dict if the uvs exist uvidx = None if uvFaces is None else list(chain.from_iterable(uvFaces)) uvSample = mkUvSample(uvs, uvidx) kwargs['iUVs'] = uvSample for i, v in enumerate(verts): pbPrint(pBar, "Exporting Unsubdivided Shape", i) sample = OPolyMeshSchemaSample(mkSampleVertexPoints(v), abcIndices, abcCounts, **kwargs) osch.set(sample) pbPrint(pBar, "Done")
def writeSimplex(inPath, outPath, newShapes, name='Face', pBar=None): ''' Write a simplex file with new shapes ''' iarch = IArchive(str(inPath)) jsString = loadJSString(iarch) faces, counts = loadMesh(iarch) del iarch oarch = OArchive(str(outPath)) # alembic does not like unicode filepaths try: _writeSimplex(oarch, name, jsString, faces, counts, newShapes, pBar) finally: del oarch gc.collect()
def hdf5Convert(inPath, outPath): ''' Load and parse all the data from a simplex file ''' iarch = IArchive(str(inPath)) jsString = loadJSString(iarch) shapes = loadSmpx(iarch) faces, counts = loadMesh(iarch) del iarch oarch = OArchive(str(outPath), False) # alembic does not like unicode filepaths try: _writeSimplex(oarch, 'Face', jsString, faces, counts, shapes) finally: del oarch gc.collect()
def reorderSimplexPoints(sourcePath, matchPath, outPath, invertMatch=False): '''Transfer shape data from the sourcePath using the numpy int array at matchPath to make the final output at outPath Parameters ---------- sourcePath : str The source .smpx file path matchPath : str The new vert order dumped from numpy outPath : str The new output .smpx path invertMatch : bool Whether to directly apply the match from matchPath, or whether to invert it Returns ------- ''' print "Loading Simplex" if not os.path.isfile(str(sourcePath)): raise IOError("File does not exist: " + str(sourcePath)) sourceArch = IArchive(str(sourcePath)) # because alembic hates unicode sourceShapes = getShapes(sourceArch) jsString = loadJSString(sourceArch) sFaces, counts = getMesh(sourceArch) sFaces = np.array(sFaces) print "Loading Correspondence" c = np.load(matchPath) c = c[c[:, 0].argsort()].T[1] ci = c.argsort() if invertMatch: ci, c = c, ci print "Reordering" targetShapes = sourceShapes[:, c, :] faces = mkSampleIntArray(ci[sFaces]) print "Writing" oarch = OArchive(str(outPath), OGAWA) # alembic does not like unicode filepaths try: _writeSimplex(oarch, 'Face', jsString, faces, counts, targetShapes) finally: del oarch gc.collect()
def hdf5Convert(inPath, outPath, ogawa=False): ''' Load and parse all the data from a simplex file ''' if not os.path.isfile(str(inPath)): raise IOError("File does not exist: " + str(inPath)) iarch = IArchive(str(inPath)) jsString = loadJSString(iarch) shapes = loadSmpx(iarch) faces, counts = loadMesh(iarch) del iarch oarch = OArchive(str(outPath), ogawa) # alembic does not like unicode filepaths try: _writeSimplex(oarch, 'Face', jsString, faces, counts, shapes) finally: del oarch gc.collect()
def expandedExportAbc(path, mesh, master, clients=()): '''Export the alembic by re-building the deltas from all of the full shapes This is required for delta-mushing a system, because the sum of mushed shapes is not the same as the mushed sum-of-shapes Parameters ---------- path : str Output path for the .smpx mesh : str The maya shape node name master : Simplex The master simplex system clients : [Simplex, ...] The client simplex systems Returns ------- ''' # Convert clients to a list if need be if not clients: clients = [] elif not isinstance(clients, list): if isinstance(clients, tuple): clients = list(clients) else: clients = [clients] faces, counts, uvs = getAbcFaces(mesh) shapeArray = buildShapeArray(mesh, master, clients) # export the data to alembic arch = OArchive(str(path), OGAWA) # alembic does not like unicode filepaths try: _exportAbc(arch, master, shapeArray, faces, counts, uvs) finally: del arch
def buildAbc(outPath, points, faces, faceCounts=None, uvs=None, uvFaces=None, normals=None, normFaces=None, name='polymsh', shapeSuffix='Shape', transformSuffix='', propDict=None): ''' Build a single-mesh alembic file from all of the non-alembic raw data Parameters ---------- outPath: str or OArchive The output path for the alembic file points: list or ndarray The list or array of points. Single multiple frames supported faces: list A list of lists of face indices, or a flattened list of indices. If flat, then faceCounts must be provided faceCounts: list A list of the number of vertices per face. Defaults to None uvs: list or ndarray The Uvs for this mesh. Defaults to None uvFaces: list A list of lists of face indices, or a flattened list of indices. If flat, then faceCounts must be provided. Defaults to None normals: list or ndarray The Normals for this mesh. Defaults to None normFaces: list A list of lists of face indices, or a flattened list of indices. If flat, then faceCounts must be provided. Defaults to None name: str The name to give this mesh. Defaults to "polymsh" shapeSuffix: str The suffix to add to the shape of this mesh. Defaults to "Shape" transformSuffix: str The suffix to add to the transform of this mesh. Defaults to "" propDict: dict A dictionary of properties to add to the xform object ''' if faceCounts is None: # All the faces are in list-of-list format # put them in index-count format faceCounts, faces = _flattenFaces(faces) if uvFaces is not None: _, uvFaces = _flattenFaces(uvFaces) if normFaces is not None: _, normFaces = _flattenFaces(normFaces) faceCounts = mkSampleIntArray(faceCounts) faces = mkSampleIntArray(faces) if uvFaces is not None and uvs is not None: uvs = mkUvSample(uvs, indexes=uvFaces) if normFaces is not None and normals is not None: normals = mkNormalSample(normals, indexes=normFaces) if isinstance(outPath, basestring): oarch = OArchive(str(outPath), False) #False for HDF5 else: oarch = outPath parent = oarch.getTop() opar = OXform(parent, name + transformSuffix) if propDict: props = opar.getSchema().getUserProperties() for k, v in propDict.iteritems(): prop = OStringProperty(props, str(k)) prop.setValue(str(v)) omesh = OPolyMesh(opar, name + shapeSuffix) if np is not None: points = np.array(points) if len(points.shape) == 2: points = points[None, ...] else: if not isinstance(points[0][0], (list, tuple)): points = [points] sch = omesh.getSchema() for frame in points: abcFrame = mkSampleVertexPoints(frame) setAlembicSample(sch, abcFrame, faces, faceCounts, uvs=uvs, normals=normals) return oarch
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 buildAbc(outPath, points, faces, faceCounts=None, uvs=None, uvFaces=None, normals=None, normFaces=None, name='polymsh', shapeSuffix='Shape', transformSuffix='', propDict=None, ogawa=True, pBar=None): ''' Build a single-mesh alembic file from all of the non-alembic raw data Parameters ---------- outPath: str The output path for the alembic file points: list or ndarray The list or array of points. Single multiple frames supported faces: list A list of lists of face indices, or a flattened list of indices. If flat, then faceCounts must be provided faceCounts: list A list of the number of vertices per face. Defaults to None uvs: list or ndarray The Uvs for this mesh. Defaults to None uvFaces: list A list of lists of face indices, or a flattened list of indices. If flat, then faceCounts must be provided. Defaults to None normals: list or ndarray The Normals for this mesh. Defaults to None normFaces: list A list of lists of face indices, or a flattened list of indices. If flat, then faceCounts must be provided. Defaults to None name: str The name to give this mesh. Defaults to "polymsh" shapeSuffix: str The suffix to add to the shape of this mesh. Defaults to "Shape" transformSuffix: str The suffix to add to the transform of this mesh. Defaults to "" propDict: dict A dictionary of properties to add to the xform object ogawa : bool Whether to write to the Ogawa (True) or HDF5 (False) backend pBar : QProgressDialog, optional An optional progress dialog ''' if faceCounts is None: # All the faces are in list-of-list format # put them in index-count format faceCounts, faces = flattenFaces(faces) if uvFaces is not None: _, uvFaces = flattenFaces(uvFaces) if normFaces is not None: _, normFaces = flattenFaces(normFaces) faceCounts = mkSampleIntArray(faceCounts) faces = mkSampleIntArray(faces) if not isinstance(uvs, OV2fGeomParamSample): if uvFaces is not None and uvs is not None: uvs = mkUvSample(uvs, indexes=uvFaces) if not isinstance(normals, ON3fGeomParamSample): if normFaces is not None and normals is not None: normals = mkNormalSample(normals, indexes=normFaces) oarch = OArchive(str(outPath), ogawa) parent, opar, props, omesh, sch = None, None, None, None, None try: parent = oarch.getTop() opar = OXform(parent, str(name+transformSuffix)) if propDict: props = opar.getSchema().getUserProperties() for k, v in propDict.iteritems(): writeStringProperty(props, str(k), str(v), ogawa) omesh = OPolyMesh(opar, str(name+shapeSuffix)) if np is not None: points = np.array(points) if len(points.shape) == 2: points = points[None, ...] else: if not isinstance(points[0][0], (list, tuple)): points = [points] sch = omesh.getSchema() for i, frame in enumerate(points): pbPrint(pBar, message="Exporting Shape", val=i, maxVal=len(points)) abcFrame = mkSampleVertexPoints(frame) setAlembicSample(sch, abcFrame, faceCounts, faces, uvs=uvs, normals=normals) pbPrint(pBar, message="Done Exporting") finally: # Make sure all this gets deleted so the file is freed del parent, opar, props, omesh, sch