183 lines
6.6 KiB
Python
183 lines
6.6 KiB
Python
from pyqtgraph.Qt import QtGui
|
|
import pyqtgraph.functions as fn
|
|
|
|
class MeshData(object):
|
|
"""
|
|
Class for storing and operating on 3D mesh data. May contain:
|
|
|
|
- list of vertex locations
|
|
- list of edges
|
|
- list of triangles
|
|
- colors per vertex, edge, or tri
|
|
- normals per vertex or tri
|
|
"""
|
|
|
|
def __init__(self):
|
|
self._vertexes = []
|
|
self._edges = None
|
|
self._faces = []
|
|
self._vertexFaces = None ## maps vertex ID to a list of face IDs
|
|
self._vertexNormals = None
|
|
self._faceNormals = None
|
|
self._vertexColors = None
|
|
self._edgeColors = None
|
|
self._faceColors = None
|
|
self._meshColor = (1, 1, 1, 0.1) # default color to use if no face/edge/vertex colors are given
|
|
|
|
def setFaces(self, faces, vertexes=None):
|
|
"""
|
|
Set the faces in this data set.
|
|
Data may be provided either as an Nx3x3 list of floats (9 float coordinate values per face)::
|
|
|
|
faces = [ [(x, y, z), (x, y, z), (x, y, z)], ... ]
|
|
|
|
or as an Nx3 list of ints (vertex integers) AND an Mx3 list of floats (3 float coordinate values per vertex)::
|
|
|
|
faces = [ (p1, p2, p3), ... ]
|
|
vertexes = [ (x, y, z), ... ]
|
|
|
|
"""
|
|
|
|
if vertexes is None:
|
|
self._setUnindexedFaces(faces)
|
|
else:
|
|
self._setIndexedFaces(faces, vertexes)
|
|
|
|
def setMeshColor(self, color):
|
|
"""Set the color of the entire mesh. This removes any per-face or per-vertex colors."""
|
|
color = fn.Color(color)
|
|
self._meshColor = color.glColor()
|
|
self._vertexColors = None
|
|
self._faceColors = None
|
|
|
|
def _setUnindexedFaces(self, faces):
|
|
verts = {}
|
|
self._faces = []
|
|
self._vertexes = []
|
|
self._vertexFaces = []
|
|
self._faceNormals = None
|
|
self._vertexNormals = None
|
|
for face in faces:
|
|
inds = []
|
|
for pt in face:
|
|
pt2 = tuple([round(x*1e14) for x in pt]) ## quantize to be sure that nearly-identical points will be merged
|
|
index = verts.get(pt2, None)
|
|
if index is None:
|
|
self._vertexes.append(QtGui.QVector3D(*pt))
|
|
self._vertexFaces.append([])
|
|
index = len(self._vertexes)-1
|
|
verts[pt2] = index
|
|
self._vertexFaces[index].append(len(self._faces))
|
|
inds.append(index)
|
|
self._faces.append(tuple(inds))
|
|
|
|
def _setIndexedFaces(self, faces, vertexes):
|
|
self._vertexes = [QtGui.QVector3D(*v) for v in vertexes]
|
|
self._faces = faces
|
|
self._edges = None
|
|
self._vertexFaces = None
|
|
self._faceNormals = None
|
|
self._vertexNormals = None
|
|
|
|
def vertexFaces(self):
|
|
"""
|
|
Return list mapping each vertex index to a list of face indexes that use the vertex.
|
|
"""
|
|
if self._vertexFaces is None:
|
|
self._vertexFaces = [[]] * len(self._vertexes)
|
|
for i, face in enumerate(self._faces):
|
|
for ind in face:
|
|
if len(self._vertexFaces[ind]) == 0:
|
|
self._vertexFaces[ind] = [] ## need a unique/empty list to fill
|
|
self._vertexFaces[ind].append(i)
|
|
return self._vertexFaces
|
|
|
|
def __iter__(self):
|
|
"""Iterate over all faces, yielding a list of three tuples [(position, normal, color), ...] for each face."""
|
|
vnorms = self.vertexNormals()
|
|
vcolors = self.vertexColors()
|
|
for i in range(len(self._faces)):
|
|
face = []
|
|
for j in [0,1,2]:
|
|
vind = self._faces[i][j]
|
|
pos = self._vertexes[vind]
|
|
norm = vnorms[vind]
|
|
if vcolors is None:
|
|
color = self._meshColor
|
|
else:
|
|
color = vcolors[vind]
|
|
face.append((pos, norm, color))
|
|
yield face
|
|
|
|
|
|
def faceNormals(self):
|
|
"""
|
|
Computes and stores normal of each face.
|
|
"""
|
|
if self._faceNormals is None:
|
|
self._faceNormals = []
|
|
for i, face in enumerate(self._faces):
|
|
## compute face normal
|
|
pts = [self._vertexes[vind] for vind in face]
|
|
norm = QtGui.QVector3D.crossProduct(pts[1]-pts[0], pts[2]-pts[0])
|
|
norm = norm / norm.length() ## don't use .normalized(); doesn't work for small values.
|
|
self._faceNormals.append(norm)
|
|
return self._faceNormals
|
|
|
|
def vertexNormals(self):
|
|
"""
|
|
Assigns each vertex the average of its connected face normals.
|
|
If face normals have not been computed yet, then generateFaceNormals will be called.
|
|
"""
|
|
if self._vertexNormals is None:
|
|
faceNorms = self.faceNormals()
|
|
vertFaces = self.vertexFaces()
|
|
self._vertexNormals = []
|
|
for vindex in xrange(len(self._vertexes)):
|
|
#print vertFaces[vindex]
|
|
norms = [faceNorms[findex] for findex in vertFaces[vindex]]
|
|
norm = QtGui.QVector3D()
|
|
for fn in norms:
|
|
norm += fn
|
|
norm = norm / norm.length() ## don't use .normalize(); doesn't work for small values.
|
|
self._vertexNormals.append(norm)
|
|
return self._vertexNormals
|
|
|
|
def vertexColors(self):
|
|
return self._vertexColors
|
|
|
|
def faceColors(self):
|
|
return self._faceColors
|
|
|
|
def edgeColors(self):
|
|
return self._edgeColors
|
|
|
|
#def reverseNormals(self):
|
|
#"""
|
|
#Reverses the direction of all normal vectors.
|
|
#"""
|
|
#pass
|
|
|
|
#def generateEdgesFromFaces(self):
|
|
#"""
|
|
#Generate a set of edges by listing all the edges of faces and removing any duplicates.
|
|
#Useful for displaying wireframe meshes.
|
|
#"""
|
|
#pass
|
|
|
|
def save(self):
|
|
"""Serialize this mesh to a string appropriate for disk storage"""
|
|
import pickle
|
|
names = ['_vertexes', '_edges', '_faces', '_vertexFaces', '_vertexNormals', '_faceNormals', '_vertexColors', '_edgeColors', '_faceColors', '_meshColor']
|
|
state = {n:getattr(self, n) for n in names}
|
|
return pickle.dumps(state)
|
|
|
|
def restore(self, state):
|
|
"""Restore the state of a mesh previously saved using save()"""
|
|
import pickle
|
|
state = pickle.loads(state)
|
|
for k in state:
|
|
setattr(self, k, state[k])
|
|
|
|
|
|
|