chiark / gitweb /
Scale down/up really small or really big meshes.
[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 import os
7
8 import numpy
9 numpy.seterr(all='ignore')
10
11 class printableObject(object):
12         def __init__(self, originFilename):
13                 self._originFilename = originFilename
14                 if originFilename is None:
15                         self._name = 'None'
16                 else:
17                         self._name = os.path.basename(originFilename)
18                 if '.' in self._name:
19                         self._name = os.path.splitext(self._name)[0]
20                 self._meshList = []
21                 self._position = numpy.array([0.0, 0.0])
22                 self._matrix = numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)
23                 self._transformedMin = None
24                 self._transformedMax = None
25                 self._transformedSize = None
26                 self._boundaryCircleSize = None
27                 self._drawOffset = None
28                 self._loadAnim = None
29
30         def copy(self):
31                 ret = printableObject(self._originFilename)
32                 ret._matrix = self._matrix.copy()
33                 ret._transformedMin = self._transformedMin.copy()
34                 ret._transformedMax = self._transformedMax.copy()
35                 ret._transformedSize = self._transformedSize.copy()
36                 ret._boundaryCircleSize = self._boundaryCircleSize
37                 ret._drawOffset = self._drawOffset.copy()
38                 for m in self._meshList[:]:
39                         m2 = ret._addMesh()
40                         m2.vertexes = m.vertexes
41                         m2.vertexCount = m.vertexCount
42                         m2.vbo = m.vbo
43                         m2.vbo.incRef()
44                 return ret
45
46         def _addMesh(self):
47                 m = mesh(self)
48                 self._meshList.append(m)
49                 return m
50
51         def _postProcessAfterLoad(self):
52                 for m in self._meshList:
53                         m._calculateNormals()
54                 self.processMatrix()
55                 if numpy.max(self.getSize()) > 10000.0:
56                         for m in self._meshList:
57                                 m.vertexes /= 1000.0
58                         self.processMatrix()
59                 if numpy.max(self.getSize()) < 1.0:
60                         for m in self._meshList:
61                                 m.vertexes *= 1000.0
62                         self.processMatrix()
63
64         def applyMatrix(self, m):
65                 self._matrix *= m
66                 self.processMatrix()
67
68         def processMatrix(self):
69                 self._transformedMin = numpy.array([999999999999,999999999999,999999999999], numpy.float64)
70                 self._transformedMax = numpy.array([-999999999999,-999999999999,-999999999999], numpy.float64)
71                 self._boundaryCircleSize = 0
72
73                 for m in self._meshList:
74                         transformedVertexes = m.getTransformedVertexes()
75                         transformedMin = transformedVertexes.min(0)
76                         transformedMax = transformedVertexes.max(0)
77                         for n in xrange(0, 3):
78                                 self._transformedMin[n] = min(transformedMin[n], self._transformedMin[n])
79                                 self._transformedMax[n] = max(transformedMax[n], self._transformedMax[n])
80
81                         #Calculate the boundary circle
82                         transformedSize = transformedMax - transformedMin
83                         center = transformedMin + transformedSize / 2.0
84                         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)
85                         self._boundaryCircleSize = max(self._boundaryCircleSize, boundaryCircleSize)
86                 self._transformedSize = self._transformedMax - self._transformedMin
87                 self._drawOffset = (self._transformedMax + self._transformedMin) / 2
88                 self._drawOffset[2] = self._transformedMin[2]
89                 self._transformedMax -= self._drawOffset
90                 self._transformedMin -= self._drawOffset
91
92         def getName(self):
93                 return self._name
94         def getOriginFilename(self):
95                 return self._originFilename
96         def getPosition(self):
97                 return self._position
98         def setPosition(self, newPos):
99                 self._position = newPos
100         def getMatrix(self):
101                 return self._matrix
102
103         def getMaximum(self):
104                 return self._transformedMax
105         def getMinimum(self):
106                 return self._transformedMin
107         def getSize(self):
108                 return self._transformedSize
109         def getDrawOffset(self):
110                 return self._drawOffset
111         def getBoundaryCircle(self):
112                 return self._boundaryCircleSize
113
114         def mirror(self, axis):
115                 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
116                 matrix[axis][axis] = -1
117                 self.applyMatrix(numpy.matrix(matrix, numpy.float64))
118
119         def getScale(self):
120                 return numpy.array([
121                         numpy.linalg.norm(self._matrix[::,0].getA().flatten()),
122                         numpy.linalg.norm(self._matrix[::,1].getA().flatten()),
123                         numpy.linalg.norm(self._matrix[::,2].getA().flatten())], numpy.float64);
124
125         def setScale(self, scale, axis, uniform):
126                 currentScale = numpy.linalg.norm(self._matrix[::,axis].getA().flatten())
127                 scale /= currentScale
128                 if scale == 0:
129                         return
130                 if uniform:
131                         matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
132                 else:
133                         matrix = [[1.0,0,0], [0, 1.0, 0], [0, 0, 1.0]]
134                         matrix[axis][axis] = scale
135                 self.applyMatrix(numpy.matrix(matrix, numpy.float64))
136
137         def setSize(self, size, axis, uniform):
138                 scale = self.getSize()[axis]
139                 scale = size / scale
140                 if scale == 0:
141                         return
142                 if uniform:
143                         matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
144                 else:
145                         matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
146                         matrix[axis][axis] = scale
147                 self.applyMatrix(numpy.matrix(matrix, numpy.float64))
148
149         def resetScale(self):
150                 x = 1/numpy.linalg.norm(self._matrix[::,0].getA().flatten())
151                 y = 1/numpy.linalg.norm(self._matrix[::,1].getA().flatten())
152                 z = 1/numpy.linalg.norm(self._matrix[::,2].getA().flatten())
153                 self.applyMatrix(numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64))
154
155         def resetRotation(self):
156                 x = numpy.linalg.norm(self._matrix[::,0].getA().flatten())
157                 y = numpy.linalg.norm(self._matrix[::,1].getA().flatten())
158                 z = numpy.linalg.norm(self._matrix[::,2].getA().flatten())
159                 self._matrix = numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
160                 self.processMatrix()
161
162         def layFlat(self):
163                 transformedVertexes = self._meshList[0].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[0] * diff[0] + 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                 rad = -math.atan2(dotV[1], dotV[0])
179                 self._matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
180                 rad = -math.asin(dotMin)
181                 self._matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
182
183
184                 transformedVertexes = self._meshList[0].getTransformedVertexes()
185                 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
186                 dotMin = 1.0
187                 dotV = None
188                 for v in transformedVertexes:
189                         diff = v - minZvertex
190                         len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
191                         if len < 5:
192                                 continue
193                         dot = (diff[2] / len)
194                         if dotMin > dot:
195                                 dotMin = dot
196                                 dotV = diff
197                 if dotV is None:
198                         return
199                 if dotV[1] < 0:
200                         rad = math.asin(dotMin)
201                 else:
202                         rad = -math.asin(dotMin)
203                 self.applyMatrix(numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64))
204
205         def scaleUpTo(self, size):
206                 vMin = self._transformedMin
207                 vMax = self._transformedMax
208
209                 scaleX1 = (size[0] / 2 - self._position[0]) / ((vMax[0] - vMin[0]) / 2)
210                 scaleY1 = (size[1] / 2 - self._position[1]) / ((vMax[1] - vMin[1]) / 2)
211                 scaleX2 = (self._position[0] + size[0] / 2) / ((vMax[0] - vMin[0]) / 2)
212                 scaleY2 = (self._position[1] + size[1] / 2) / ((vMax[1] - vMin[1]) / 2)
213                 scaleZ = size[2] / (vMax[2] - vMin[2])
214                 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
215                 if scale > 0:
216                         self.applyMatrix(numpy.matrix([[scale,0,0],[0,scale,0],[0,0,scale]], numpy.float64))
217
218         #Split splits an object with multiple meshes into different objects, where each object is a part of the original mesh that has
219         # connected faces. This is useful to split up plate STL files.
220         def split(self, callback):
221                 ret = []
222                 for oriMesh in self._meshList:
223                         ret += oriMesh.split(callback)
224                 return ret
225
226         def canStoreAsSTL(self):
227                 return len(self._meshList) < 2
228
229         #getVertexIndexList returns an array of vertexes, and an integer array for each mesh in this object.
230         # the integer arrays are indexes into the vertex array for each triangle in the model.
231         def getVertexIndexList(self):
232                 vertexMap = {}
233                 vertexList = []
234                 meshList = []
235                 for m in self._meshList:
236                         verts = m.getTransformedVertexes(True)
237                         meshIdxList = []
238                         for idx in xrange(0, len(verts)):
239                                 v = verts[idx]
240                                 hashNr = int(v[0] * 100) | int(v[1] * 100) << 10 | int(v[2] * 100) << 20
241                                 vIdx = None
242                                 if hashNr in vertexMap:
243                                         for idx2 in vertexMap[hashNr]:
244                                                 if numpy.linalg.norm(v - vertexList[idx2]) < 0.001:
245                                                         vIdx = idx2
246                                 if vIdx is None:
247                                         vIdx = len(vertexList)
248                                         vertexMap[hashNr] = [vIdx]
249                                         vertexList.append(v)
250                                 meshIdxList.append(vIdx)
251                         meshList.append(numpy.array(meshIdxList, numpy.int32))
252                 return numpy.array(vertexList, numpy.float32), meshList
253
254 class mesh(object):
255         def __init__(self, obj):
256                 self.vertexes = None
257                 self.vertexCount = 0
258                 self.vbo = None
259                 self._obj = obj
260
261         def _addFace(self, x0, y0, z0, x1, y1, z1, x2, y2, z2):
262                 n = self.vertexCount
263                 self.vertexes[n][0] = x0
264                 self.vertexes[n][1] = y0
265                 self.vertexes[n][2] = z0
266                 n += 1
267                 self.vertexes[n][0] = x1
268                 self.vertexes[n][1] = y1
269                 self.vertexes[n][2] = z1
270                 n += 1
271                 self.vertexes[n][0] = x2
272                 self.vertexes[n][1] = y2
273                 self.vertexes[n][2] = z2
274                 self.vertexCount += 3
275         
276         def _prepareFaceCount(self, faceNumber):
277                 #Set the amount of faces before loading data in them. This way we can create the numpy arrays before we fill them.
278                 self.vertexes = numpy.zeros((faceNumber*3, 3), numpy.float32)
279                 self.normal = numpy.zeros((faceNumber*3, 3), numpy.float32)
280                 self.vertexCount = 0
281
282         def _calculateNormals(self):
283                 #Calculate the normals
284                 tris = self.vertexes.reshape(self.vertexCount / 3, 3, 3)
285                 normals = numpy.cross( tris[::,1 ] - tris[::,0]  , tris[::,2 ] - tris[::,0] )
286                 lens = numpy.sqrt( normals[:,0]**2 + normals[:,1]**2 + normals[:,2]**2 )
287                 normals[:,0] /= lens
288                 normals[:,1] /= lens
289                 normals[:,2] /= lens
290                 
291                 n = numpy.zeros((self.vertexCount / 3, 9), numpy.float32)
292                 n[:,0:3] = normals
293                 n[:,3:6] = normals
294                 n[:,6:9] = normals
295                 self.normal = n.reshape(self.vertexCount, 3)
296                 self.invNormal = -self.normal
297
298         def _vertexHash(self, idx):
299                 v = self.vertexes[idx]
300                 return int(v[0] * 100) | int(v[1] * 100) << 10 | int(v[2] * 100) << 20
301
302         def _idxFromHash(self, map, idx):
303                 vHash = self._vertexHash(idx)
304                 for i in map[vHash]:
305                         if numpy.linalg.norm(self.vertexes[i] - self.vertexes[idx]) < 0.001:
306                                 return i
307
308         def getTransformedVertexes(self, applyOffsets = False):
309                 if applyOffsets:
310                         pos = self._obj._position.copy()
311                         pos.resize((3))
312                         pos[2] = self._obj.getSize()[2] / 2
313                         offset = self._obj._drawOffset.copy()
314                         offset[2] += self._obj.getSize()[2] / 2
315                         return (numpy.matrix(self.vertexes, copy = False) * numpy.matrix(self._obj._matrix, numpy.float32)).getA() - offset + pos
316                 return (numpy.matrix(self.vertexes, copy = False) * numpy.matrix(self._obj._matrix, numpy.float32)).getA()
317
318         def split(self, callback):
319                 vertexMap = {}
320
321                 vertexToFace = []
322                 for idx in xrange(0, self.vertexCount):
323                         if (idx % 100) == 0:
324                                 callback(idx * 100 / self.vertexCount)
325                         vHash = self._vertexHash(idx)
326                         if vHash not in vertexMap:
327                                 vertexMap[vHash] = []
328                         vertexMap[vHash].append(idx)
329                         vertexToFace.append([])
330
331                 faceList = []
332                 for idx in xrange(0, self.vertexCount, 3):
333                         if (idx % 100) == 0:
334                                 callback(idx * 100 / self.vertexCount)
335                         f = [self._idxFromHash(vertexMap, idx), self._idxFromHash(vertexMap, idx+1), self._idxFromHash(vertexMap, idx+2)]
336                         vertexToFace[f[0]].append(idx / 3)
337                         vertexToFace[f[1]].append(idx / 3)
338                         vertexToFace[f[2]].append(idx / 3)
339                         faceList.append(f)
340
341                 ret = []
342                 doneSet = set()
343                 for idx in xrange(0, len(faceList)):
344                         if idx in doneSet:
345                                 continue
346                         doneSet.add(idx)
347                         todoList = [idx]
348                         meshFaceList = []
349                         while len(todoList) > 0:
350                                 idx = todoList.pop()
351                                 meshFaceList.append(idx)
352                                 for n in xrange(0, 3):
353                                         for i in vertexToFace[faceList[idx][n]]:
354                                                 if not i in doneSet:
355                                                         doneSet.add(i)
356                                                         todoList.append(i)
357
358                         obj = printableObject(self._obj.getOriginFilename())
359                         obj._matrix = self._obj._matrix.copy()
360                         m = obj._addMesh()
361                         m._prepareFaceCount(len(meshFaceList))
362                         for idx in meshFaceList:
363                                 m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][0]]
364                                 m.vertexCount += 1
365                                 m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][1]]
366                                 m.vertexCount += 1
367                                 m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][2]]
368                                 m.vertexCount += 1
369                         obj._postProcessAfterLoad()
370                         ret.append(obj)
371                 return ret