MeshData.py 5.44 KB
Newer Older
1
2
from pyqtgraph.Qt import QtGui

Luke Campagnola's avatar
Luke Campagnola committed
3
4
5
6
7
8
9
10
11
12
class MeshData(object):
    """
    Class for storing 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
    """

Luke Campagnola's avatar
Luke Campagnola committed
13
    def __init__(self):
14
15
16
17
18
19
20
21
22
23
        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
Luke Campagnola's avatar
Luke Campagnola committed
24
25
26
27
28
29
30
31
32
33
34
35
        
    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:
36
            self._setUnindexedFaces(faces)
Luke Campagnola's avatar
Luke Campagnola committed
37
        else:
38
39
40
            self._setIndexedFaces(faces, vertexes)
    
    
Luke Campagnola's avatar
Luke Campagnola committed
41
42
    def _setUnindexedFaces(self, faces):
        verts = {}
43
44
45
46
47
        self._faces = []
        self._vertexes = []
        self._vertexFaces = []
        self._faceNormals = None
        self._vertexNormals = None
Luke Campagnola's avatar
Luke Campagnola committed
48
49
50
        for face in faces:
            inds = []
            for pt in face:
51
                pt2 = tuple([round(x*1e14) for x in pt])  ## quantize to be sure that nearly-identical points will be merged
Luke Campagnola's avatar
Luke Campagnola committed
52
53
                index = verts.get(pt2, None)
                if index is None:
54
55
56
                    self._vertexes.append(QtGui.QVector3D(*pt))
                    self._vertexFaces.append([])
                    index = len(self._vertexes)-1
Luke Campagnola's avatar
Luke Campagnola committed
57
                    verts[pt2] = index
58
                self._vertexFaces[index].append(len(self._faces))
Luke Campagnola's avatar
Luke Campagnola committed
59
                inds.append(index)
60
            self._faces.append(tuple(inds))
Luke Campagnola's avatar
Luke Campagnola committed
61
62
    
    def _setIndexedFaces(self, faces, vertexes):
63
64
65
66
67
68
        self._vertexes = [QtGui.QVector3D(*v) for v in vertexes]
        self._faces = faces
        self._edges = None
        self._vertexFaces = None
        self._faceNormals = None
        self._vertexNormals = None
Luke Campagnola's avatar
Luke Campagnola committed
69

70
    def vertexFaces(self):
Luke Campagnola's avatar
Luke Campagnola committed
71
72
73
        """
        Return list mapping each vertex index to a list of face indexes that use the vertex.
        """
74
75
76
        if self._vertexFaces is None:
            self._vertexFaces = [[]] * len(self._vertexes)
            for i, face in enumerate(self._faces):
Luke Campagnola's avatar
Luke Campagnola committed
77
                for ind in face:
78
79
80
81
                    if len(self._vertexFaces[ind]) == 0:
                        self._vertexFaces[ind] = []  ## need a unique/empty list to fill
                    self._vertexFaces[ind].append(i)
        return self._vertexFaces
Luke Campagnola's avatar
Luke Campagnola committed
82
        
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
    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):
Luke Campagnola's avatar
Luke Campagnola committed
102
103
104
        """
        Computes and stores normal of each face.
        """
105
106
107
        if self._faceNormals is None:
            self._faceNormals = []
            for i, face in enumerate(self._faces):
Luke Campagnola's avatar
Luke Campagnola committed
108
                ## compute face normal
109
110
111
112
                pts = [self._vertexes[vind] for vind in face]
                norm = QtGui.QVector3D.crossProduct(pts[1]-pts[0], pts[2]-pts[0]).normalized()
                self._faceNormals.append(norm)
        return self._faceNormals
Luke Campagnola's avatar
Luke Campagnola committed
113
    
114
    def vertexNormals(self):
Luke Campagnola's avatar
Luke Campagnola committed
115
116
117
118
        """
        Assigns each vertex the average of its connected face normals.
        If face normals have not been computed yet, then generateFaceNormals will be called.
        """
119
120
121
122
123
124
        if self._vertexNormals is None:
            faceNorms = self.faceNormals()
            vertFaces = self.vertexFaces()
            self._vertexNormals = []
            for vindex in xrange(len(self._vertexes)):
                #print vertFaces[vindex]
Luke Campagnola's avatar
Luke Campagnola committed
125
                norms = [faceNorms[findex] for findex in vertFaces[vindex]]
126
127
128
129
130
131
132
133
134
135
136
137
                norm = QtGui.QVector3D()
                for fn in norms:
                    norm += fn
                norm.normalize()
                self._vertexNormals.append(norm)
        return self._vertexNormals
        
    def vertexColors(self):
        return self._vertexColors
        
    def faceColors(self):
        return self._faceColors
Luke Campagnola's avatar
Luke Campagnola committed
138
        
139
140
    def edgeColors(self):
        return self._edgeColors
Luke Campagnola's avatar
Luke Campagnola committed
141
142
        
    def reverseNormals(self):
Luke Campagnola's avatar
Luke Campagnola committed
143
144
145
146
147
148
149
150
151
152
153
154
        """
        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