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