chiark / gitweb /
a5fd0f039bfbd0d5a4b5e44ae2b1c903bb98d8d5
[cura.git] / Cura / util / mesh.py
1 from __future__ import absolute_import
2
3 import time
4 import math
5
6 import numpy
7 numpy.seterr(all='ignore')
8
9 class printableObject(object):
10         def __init__(self):
11                 self._meshList = []
12                 self._position = numpy.array([0.0, 0.0])
13                 self._matrix = numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)
14                 self._transformedMin = None
15                 self._transformedMax = None
16                 self._transformedSize = None
17                 self._boundaryCircleSize = None
18                 self._drawOffset = None
19                 self._loadAnim = None
20
21         def copy(self):
22                 ret = printableObject()
23                 ret._matrix = self._matrix.copy()
24                 ret._transformedMin = self._transformedMin.copy()
25                 ret._transformedMax = self._transformedMin.copy()
26                 ret._transformedSize = self._transformedSize.copy()
27                 ret._boundaryCircleSize = self._boundaryCircleSize
28                 ret._drawOffset = self._drawOffset.copy()
29                 for m in self._meshList[:]:
30                         m2 = ret._addMesh()
31                         m2.vertexes = m.vertexes
32                         m2.vertexCount = m.vertexCount
33                         m2.vbo = m.vbo
34                         m2.vbo.incRef()
35                 return ret
36
37         def _addMesh(self):
38                 m = mesh(self)
39                 self._meshList.append(m)
40                 return m
41
42         def _postProcessAfterLoad(self):
43                 for m in self._meshList:
44                         m._calculateNormals()
45                 self.processMatrix()
46
47         def applyMatrix(self, m):
48                 self._matrix *= m
49                 self.processMatrix()
50
51         def processMatrix(self):
52                 self._transformedMin = numpy.array([999999999999,999999999999,999999999999], numpy.float64)
53                 self._transformedMax = numpy.array([-999999999999,-999999999999,-999999999999], numpy.float64)
54                 self._boundaryCircleSize = 0
55
56                 for m in self._meshList:
57                         transformedVertexes = m.getTransformedVertexes()
58                         transformedMin = transformedVertexes.min(0)
59                         transformedMax = transformedVertexes.max(0)
60                         for n in xrange(0, 3):
61                                 self._transformedMin[n] = min(transformedMin[n], self._transformedMin[n])
62                                 self._transformedMax[n] = max(transformedMax[n], self._transformedMax[n])
63
64                         #Calculate the boundary circle
65                         transformedSize = transformedMax - transformedMin
66                         center = transformedMin + transformedSize / 2.0
67                         boundaryCircleSize = round(math.sqrt(numpy.max(((transformedVertexes[::,0] - center[0]) * (transformedVertexes[::,0] - center[0])) + ((transformedVertexes[::,1] - center[1]) * (transformedVertexes[::,1] - center[1])) + ((transformedVertexes[::,2] - center[2]) * (transformedVertexes[::,2] - center[2])))), 3)
68                         self._boundaryCircleSize = max(self._boundaryCircleSize, boundaryCircleSize)
69                 self._transformedSize = self._transformedMax - self._transformedMin
70                 self._drawOffset = (self._transformedMax + self._transformedMin) / 2
71                 self._drawOffset[2] = self._transformedMin[2]
72                 self._transformedMax -= self._drawOffset
73                 self._transformedMin -= self._drawOffset
74
75         def getPosition(self):
76                 return self._position
77         def setPosition(self, newPos):
78                 self._position = newPos
79         def getMatrix(self):
80                 return self._matrix
81
82         def getMaximum(self):
83                 return self._transformedMax
84         def getMinimum(self):
85                 return self._transformedMin
86         def getSize(self):
87                 return self._transformedSize
88         def getDrawOffset(self):
89                 return self._drawOffset
90         def getBoundaryCircle(self):
91                 return self._boundaryCircleSize
92
93         def mirror(self, axis):
94                 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
95                 matrix[axis][axis] = -1
96                 self.applyMatrix(numpy.matrix(matrix, numpy.float64))
97
98         def getScale(self):
99                 return numpy.array([
100                         numpy.linalg.norm(self._matrix[::,0].getA().flatten()),
101                         numpy.linalg.norm(self._matrix[::,1].getA().flatten()),
102                         numpy.linalg.norm(self._matrix[::,2].getA().flatten())], numpy.float64);
103
104         def setScale(self, scale, axis, uniform):
105                 currentScale = numpy.linalg.norm(self._matrix[::,axis].getA().flatten())
106                 scale /= currentScale
107                 if scale == 0:
108                         return
109                 if uniform:
110                         matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
111                 else:
112                         matrix = [[1.0,0,0], [0, 1.0, 0], [0, 0, 1.0]]
113                         matrix[axis][axis] = scale
114                 self.applyMatrix(numpy.matrix(matrix, numpy.float64))
115
116         def setSize(self, size, axis, uniform):
117                 scale = self.getSize()[axis]
118                 scale = size / scale
119                 if scale == 0:
120                         return
121                 if uniform:
122                         matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
123                 else:
124                         matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
125                         matrix[axis][axis] = scale
126                 self.applyMatrix(numpy.matrix(matrix, numpy.float64))
127
128         def resetScale(self):
129                 x = 1/numpy.linalg.norm(self._matrix[::,0].getA().flatten())
130                 y = 1/numpy.linalg.norm(self._matrix[::,1].getA().flatten())
131                 z = 1/numpy.linalg.norm(self._matrix[::,2].getA().flatten())
132                 self.applyMatrix(numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64))
133
134         def resetRotation(self):
135                 x = numpy.linalg.norm(self._matrix[::,0].getA().flatten())
136                 y = numpy.linalg.norm(self._matrix[::,1].getA().flatten())
137                 z = numpy.linalg.norm(self._matrix[::,2].getA().flatten())
138                 self._matrix = numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
139                 self.processMatrix()
140
141         def layFlat(self):
142                 transformedVertexes = self.getTransformedVertexes()
143                 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
144                 dotMin = 1.0
145                 dotV = None
146                 for v in transformedVertexes:
147                         diff = v - minZvertex
148                         len = math.sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])
149                         if len < 5:
150                                 continue
151                         dot = (diff[2] / len)
152                         if dotMin > dot:
153                                 dotMin = dot
154                                 dotV = diff
155                 if dotV is None:
156                         return
157                 rad = -math.atan2(dotV[1], dotV[0])
158                 self._matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
159                 rad = -math.asin(dotMin)
160                 self._matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
161
162
163                 transformedVertexes = self.getTransformedVertexes()
164                 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
165                 dotMin = 1.0
166                 dotV = None
167                 for v in transformedVertexes:
168                         diff = v - minZvertex
169                         len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
170                         if len < 5:
171                                 continue
172                         dot = (diff[2] / len)
173                         if dotMin > dot:
174                                 dotMin = dot
175                                 dotV = diff
176                 if dotV is None:
177                         return
178                 if dotV[1] < 0:
179                         rad = math.asin(dotMin)
180                 else:
181                         rad = -math.asin(dotMin)
182                 self.applyMatrix(numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64))
183
184         def scaleUpTo(self, size):
185                 vMin = self._transformedMin
186                 vMax = self._transformedMax
187
188                 scaleX1 = (size[0] / 2 - self._position[0]) / ((vMax[0] - vMin[0]) / 2)
189                 scaleY1 = (size[1] / 2 - self._position[1]) / ((vMax[1] - vMin[1]) / 2)
190                 scaleX2 = (self._position[0] + size[0] / 2) / ((vMax[0] - vMin[0]) / 2)
191                 scaleY2 = (self._position[1] + size[1] / 2) / ((vMax[1] - vMin[1]) / 2)
192                 scaleZ = size[2] / (vMax[2] - vMin[2])
193                 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
194                 if scale > 0:
195                         self.applyMatrix(numpy.matrix([[scale,0,0],[0,scale,0],[0,0,scale]], numpy.float64))
196
197         def split(self, callback):
198                 ret = []
199                 for oriMesh in self._meshList:
200                         ret += oriMesh.split(callback)
201                 return ret
202
203 class mesh(object):
204         def __init__(self, obj):
205                 self.vertexes = None
206                 self.vertexCount = 0
207                 self.vbo = None
208                 self._obj = obj
209
210         def _addFace(self, x0, y0, z0, x1, y1, z1, x2, y2, z2):
211                 n = self.vertexCount
212                 self.vertexes[n][0] = x0
213                 self.vertexes[n][1] = y0
214                 self.vertexes[n][2] = z0
215                 n += 1
216                 self.vertexes[n][0] = x1
217                 self.vertexes[n][1] = y1
218                 self.vertexes[n][2] = z1
219                 n += 1
220                 self.vertexes[n][0] = x2
221                 self.vertexes[n][1] = y2
222                 self.vertexes[n][2] = z2
223                 self.vertexCount += 3
224         
225         def _prepareFaceCount(self, faceNumber):
226                 #Set the amount of faces before loading data in them. This way we can create the numpy arrays before we fill them.
227                 self.vertexes = numpy.zeros((faceNumber*3, 3), numpy.float32)
228                 self.normal = numpy.zeros((faceNumber*3, 3), numpy.float32)
229                 self.vertexCount = 0
230
231         def _calculateNormals(self):
232                 #Calculate the normals
233                 tris = self.vertexes.reshape(self.vertexCount / 3, 3, 3)
234                 normals = numpy.cross( tris[::,1 ] - tris[::,0]  , tris[::,2 ] - tris[::,0] )
235                 lens = numpy.sqrt( normals[:,0]**2 + normals[:,1]**2 + normals[:,2]**2 )
236                 normals[:,0] /= lens
237                 normals[:,1] /= lens
238                 normals[:,2] /= lens
239                 
240                 n = numpy.zeros((self.vertexCount / 3, 9), numpy.float32)
241                 n[:,0:3] = normals
242                 n[:,3:6] = normals
243                 n[:,6:9] = normals
244                 self.normal = n.reshape(self.vertexCount, 3)
245                 self.invNormal = -self.normal
246
247         def _vertexHash(self, idx):
248                 v = self.vertexes[idx]
249                 return int(v[0] * 100) | int(v[1] * 100) << 10 | int(v[2] * 100) << 20
250
251         def _idxFromHash(self, map, idx):
252                 vHash = self._vertexHash(idx)
253                 for i in map[vHash]:
254                         if numpy.linalg.norm(self.vertexes[i] - self.vertexes[idx]) < 0.001:
255                                 return i
256
257         def getTransformedVertexes(self, applyOffsets = False):
258                 if applyOffsets:
259                         pos = self._obj._position.copy()
260                         pos.resize((3))
261                         pos[2] = self._obj.getSize()[2] / 2
262                         offset = self._obj._drawOffset.copy()
263                         offset[2] += self._obj.getSize()[2] / 2
264                         return (numpy.matrix(self.vertexes, copy = False) * numpy.matrix(self._obj._matrix, numpy.float32)).getA() - offset + pos
265                 return (numpy.matrix(self.vertexes, copy = False) * numpy.matrix(self._obj._matrix, numpy.float32)).getA()
266
267         def split(self, callback):
268                 vertexMap = {}
269
270                 vertexToFace = []
271                 for idx in xrange(0, self.vertexCount):
272                         if (idx % 100) == 0:
273                                 callback(idx * 100 / self.vertexCount)
274                         vHash = self._vertexHash(idx)
275                         if vHash not in vertexMap:
276                                 vertexMap[vHash] = []
277                         vertexMap[vHash].append(idx)
278                         vertexToFace.append([])
279
280                 faceList = []
281                 for idx in xrange(0, self.vertexCount, 3):
282                         if (idx % 100) == 0:
283                                 callback(idx * 100 / self.vertexCount)
284                         f = [self._idxFromHash(vertexMap, idx), self._idxFromHash(vertexMap, idx+1), self._idxFromHash(vertexMap, idx+2)]
285                         vertexToFace[f[0]].append(idx / 3)
286                         vertexToFace[f[1]].append(idx / 3)
287                         vertexToFace[f[2]].append(idx / 3)
288                         faceList.append(f)
289
290                 ret = []
291                 doneSet = set()
292                 for idx in xrange(0, len(faceList)):
293                         if idx in doneSet:
294                                 continue
295                         doneSet.add(idx)
296                         todoList = [idx]
297                         meshFaceList = []
298                         while len(todoList) > 0:
299                                 idx = todoList.pop()
300                                 meshFaceList.append(idx)
301                                 for n in xrange(0, 3):
302                                         for i in vertexToFace[faceList[idx][n]]:
303                                                 if not i in doneSet:
304                                                         doneSet.add(i)
305                                                         todoList.append(i)
306
307                         obj = printableObject()
308                         obj._matrix = self._obj._matrix.copy()
309                         m = obj._addMesh()
310                         m._prepareFaceCount(len(meshFaceList))
311                         for idx in meshFaceList:
312                                 m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][0]]
313                                 m.vertexCount += 1
314                                 m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][1]]
315                                 m.vertexCount += 1
316                                 m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][2]]
317                                 m.vertexCount += 1
318                         obj._postProcessAfterLoad()
319                         ret.append(obj)
320                 return ret