1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
9 numpy.seterr(all='ignore')
11 def convexHull(pointList):
12 def _isRightTurn((p, q, r)):
13 sum1 = q[0]*r[1] + p[0]*q[1] + r[0]*p[1]
14 sum2 = q[0]*p[1] + r[0]*q[1] + p[0]*r[1]
23 unique[(int(p[0]),int(p[1]))] = 1
25 points = unique.keys()
28 # Build upper half of the hull.
29 upper = [points[0], points[1]]
32 while len(upper) > 2 and not _isRightTurn(upper[-3:]):
35 # Build lower half of the hull.
37 lower = [points[0], points[1]]
40 while len(lower) > 2 and not _isRightTurn(lower[-3:]):
47 return numpy.array(upper + lower, numpy.float32) - numpy.array([0.0,0.0], numpy.float32)
49 class printableObject(object):
50 def __init__(self, originFilename):
51 self._originFilename = originFilename
52 if originFilename is None:
55 self._name = os.path.basename(originFilename)
57 self._name = os.path.splitext(self._name)[0]
59 self._position = numpy.array([0.0, 0.0])
60 self._matrix = numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)
61 self._transformedMin = None
62 self._transformedMax = None
63 self._transformedSize = None
64 self._boundaryCircleSize = None
65 self._drawOffset = None
69 ret = printableObject(self._originFilename)
70 ret._matrix = self._matrix.copy()
71 ret._transformedMin = self._transformedMin.copy()
72 ret._transformedMax = self._transformedMax.copy()
73 ret._transformedSize = self._transformedSize.copy()
74 ret._boundaryCircleSize = self._boundaryCircleSize
75 ret._boundaryHull = self._boundaryHull.copy()
76 ret._drawOffset = self._drawOffset.copy()
77 for m in self._meshList[:]:
79 m2.vertexes = m.vertexes
80 m2.vertexCount = m.vertexCount
87 self._meshList.append(m)
90 def _postProcessAfterLoad(self):
91 for m in self._meshList:
94 if numpy.max(self.getSize()) > 10000.0:
95 for m in self._meshList:
98 if numpy.max(self.getSize()) < 1.0:
99 for m in self._meshList:
103 def applyMatrix(self, m):
107 def processMatrix(self):
108 self._transformedMin = numpy.array([999999999999,999999999999,999999999999], numpy.float64)
109 self._transformedMax = numpy.array([-999999999999,-999999999999,-999999999999], numpy.float64)
110 self._boundaryCircleSize = 0
112 hull = numpy.zeros((0, 2), numpy.float32)
113 for m in self._meshList:
114 transformedVertexes = m.getTransformedVertexes()
115 hull = convexHull(numpy.concatenate((transformedVertexes[:,0:2], hull), 0))
116 transformedMin = transformedVertexes.min(0)
117 transformedMax = transformedVertexes.max(0)
118 for n in xrange(0, 3):
119 self._transformedMin[n] = min(transformedMin[n], self._transformedMin[n])
120 self._transformedMax[n] = max(transformedMax[n], self._transformedMax[n])
122 #Calculate the boundary circle
123 transformedSize = transformedMax - transformedMin
124 center = transformedMin + transformedSize / 2.0
125 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)
126 self._boundaryCircleSize = max(self._boundaryCircleSize, boundaryCircleSize)
127 self._transformedSize = self._transformedMax - self._transformedMin
128 self._drawOffset = (self._transformedMax + self._transformedMin) / 2
129 self._drawOffset[2] = self._transformedMin[2]
130 self._boundaryHull = hull - self._drawOffset[0:2]
131 self._transformedMax -= self._drawOffset
132 self._transformedMin -= self._drawOffset
136 def getOriginFilename(self):
137 return self._originFilename
138 def getPosition(self):
139 return self._position
140 def setPosition(self, newPos):
141 self._position = newPos
145 def getMaximum(self):
146 return self._transformedMax
147 def getMinimum(self):
148 return self._transformedMin
150 return self._transformedSize
151 def getDrawOffset(self):
152 return self._drawOffset
153 def getBoundaryCircle(self):
154 return self._boundaryCircleSize
156 def mirror(self, axis):
157 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
158 matrix[axis][axis] = -1
159 self.applyMatrix(numpy.matrix(matrix, numpy.float64))
163 numpy.linalg.norm(self._matrix[::,0].getA().flatten()),
164 numpy.linalg.norm(self._matrix[::,1].getA().flatten()),
165 numpy.linalg.norm(self._matrix[::,2].getA().flatten())], numpy.float64);
167 def setScale(self, scale, axis, uniform):
168 currentScale = numpy.linalg.norm(self._matrix[::,axis].getA().flatten())
169 scale /= currentScale
173 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
175 matrix = [[1.0,0,0], [0, 1.0, 0], [0, 0, 1.0]]
176 matrix[axis][axis] = scale
177 self.applyMatrix(numpy.matrix(matrix, numpy.float64))
179 def setSize(self, size, axis, uniform):
180 scale = self.getSize()[axis]
185 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
187 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
188 matrix[axis][axis] = scale
189 self.applyMatrix(numpy.matrix(matrix, numpy.float64))
191 def resetScale(self):
192 x = 1/numpy.linalg.norm(self._matrix[::,0].getA().flatten())
193 y = 1/numpy.linalg.norm(self._matrix[::,1].getA().flatten())
194 z = 1/numpy.linalg.norm(self._matrix[::,2].getA().flatten())
195 self.applyMatrix(numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64))
197 def resetRotation(self):
198 x = numpy.linalg.norm(self._matrix[::,0].getA().flatten())
199 y = numpy.linalg.norm(self._matrix[::,1].getA().flatten())
200 z = numpy.linalg.norm(self._matrix[::,2].getA().flatten())
201 self._matrix = numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
205 transformedVertexes = self._meshList[0].getTransformedVertexes()
206 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
209 for v in transformedVertexes:
210 diff = v - minZvertex
211 len = math.sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])
214 dot = (diff[2] / len)
220 rad = -math.atan2(dotV[1], dotV[0])
221 self._matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
222 rad = -math.asin(dotMin)
223 self._matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
226 transformedVertexes = self._meshList[0].getTransformedVertexes()
227 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
230 for v in transformedVertexes:
231 diff = v - minZvertex
232 len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
235 dot = (diff[2] / len)
242 rad = math.asin(dotMin)
244 rad = -math.asin(dotMin)
245 self.applyMatrix(numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64))
247 def scaleUpTo(self, size):
248 vMin = self._transformedMin
249 vMax = self._transformedMax
251 scaleX1 = (size[0] / 2 - self._position[0]) / ((vMax[0] - vMin[0]) / 2)
252 scaleY1 = (size[1] / 2 - self._position[1]) / ((vMax[1] - vMin[1]) / 2)
253 scaleX2 = (self._position[0] + size[0] / 2) / ((vMax[0] - vMin[0]) / 2)
254 scaleY2 = (self._position[1] + size[1] / 2) / ((vMax[1] - vMin[1]) / 2)
255 scaleZ = size[2] / (vMax[2] - vMin[2])
256 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
258 self.applyMatrix(numpy.matrix([[scale,0,0],[0,scale,0],[0,0,scale]], numpy.float64))
260 #Split splits an object with multiple meshes into different objects, where each object is a part of the original mesh that has
261 # connected faces. This is useful to split up plate STL files.
262 def split(self, callback):
264 for oriMesh in self._meshList:
265 ret += oriMesh.split(callback)
268 def canStoreAsSTL(self):
269 return len(self._meshList) < 2
271 #getVertexIndexList returns an array of vertexes, and an integer array for each mesh in this object.
272 # the integer arrays are indexes into the vertex array for each triangle in the model.
273 def getVertexIndexList(self):
277 for m in self._meshList:
278 verts = m.getTransformedVertexes(True)
280 for idx in xrange(0, len(verts)):
282 hashNr = int(v[0] * 100) | int(v[1] * 100) << 10 | int(v[2] * 100) << 20
284 if hashNr in vertexMap:
285 for idx2 in vertexMap[hashNr]:
286 if numpy.linalg.norm(v - vertexList[idx2]) < 0.001:
289 vIdx = len(vertexList)
290 vertexMap[hashNr] = [vIdx]
292 meshIdxList.append(vIdx)
293 meshList.append(numpy.array(meshIdxList, numpy.int32))
294 return numpy.array(vertexList, numpy.float32), meshList
297 def __init__(self, obj):
303 def _addFace(self, x0, y0, z0, x1, y1, z1, x2, y2, z2):
305 self.vertexes[n][0] = x0
306 self.vertexes[n][1] = y0
307 self.vertexes[n][2] = z0
309 self.vertexes[n][0] = x1
310 self.vertexes[n][1] = y1
311 self.vertexes[n][2] = z1
313 self.vertexes[n][0] = x2
314 self.vertexes[n][1] = y2
315 self.vertexes[n][2] = z2
316 self.vertexCount += 3
318 def _prepareFaceCount(self, faceNumber):
319 #Set the amount of faces before loading data in them. This way we can create the numpy arrays before we fill them.
320 self.vertexes = numpy.zeros((faceNumber*3, 3), numpy.float32)
321 self.normal = numpy.zeros((faceNumber*3, 3), numpy.float32)
324 def _calculateNormals(self):
325 #Calculate the normals
326 tris = self.vertexes.reshape(self.vertexCount / 3, 3, 3)
327 normals = numpy.cross( tris[::,1 ] - tris[::,0] , tris[::,2 ] - tris[::,0] )
328 lens = numpy.sqrt( normals[:,0]**2 + normals[:,1]**2 + normals[:,2]**2 )
333 n = numpy.zeros((self.vertexCount / 3, 9), numpy.float32)
337 self.normal = n.reshape(self.vertexCount, 3)
338 self.invNormal = -self.normal
340 def _vertexHash(self, idx):
341 v = self.vertexes[idx]
342 return int(v[0] * 100) | int(v[1] * 100) << 10 | int(v[2] * 100) << 20
344 def _idxFromHash(self, map, idx):
345 vHash = self._vertexHash(idx)
347 if numpy.linalg.norm(self.vertexes[i] - self.vertexes[idx]) < 0.001:
350 def getTransformedVertexes(self, applyOffsets = False):
352 pos = self._obj._position.copy()
354 pos[2] = self._obj.getSize()[2] / 2
355 offset = self._obj._drawOffset.copy()
356 offset[2] += self._obj.getSize()[2] / 2
357 return (numpy.matrix(self.vertexes, copy = False) * numpy.matrix(self._obj._matrix, numpy.float32)).getA() - offset + pos
358 return (numpy.matrix(self.vertexes, copy = False) * numpy.matrix(self._obj._matrix, numpy.float32)).getA()
360 def split(self, callback):
364 for idx in xrange(0, self.vertexCount):
366 callback(idx * 100 / self.vertexCount)
367 vHash = self._vertexHash(idx)
368 if vHash not in vertexMap:
369 vertexMap[vHash] = []
370 vertexMap[vHash].append(idx)
371 vertexToFace.append([])
374 for idx in xrange(0, self.vertexCount, 3):
376 callback(idx * 100 / self.vertexCount)
377 f = [self._idxFromHash(vertexMap, idx), self._idxFromHash(vertexMap, idx+1), self._idxFromHash(vertexMap, idx+2)]
378 vertexToFace[f[0]].append(idx / 3)
379 vertexToFace[f[1]].append(idx / 3)
380 vertexToFace[f[2]].append(idx / 3)
385 for idx in xrange(0, len(faceList)):
391 while len(todoList) > 0:
393 meshFaceList.append(idx)
394 for n in xrange(0, 3):
395 for i in vertexToFace[faceList[idx][n]]:
400 obj = printableObject(self._obj.getOriginFilename())
401 obj._matrix = self._obj._matrix.copy()
403 m._prepareFaceCount(len(meshFaceList))
404 for idx in meshFaceList:
405 m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][0]]
407 m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][1]]
409 m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][2]]
411 obj._postProcessAfterLoad()