2 The printableObject module contains a printableObject class,
3 which is used to represent a single object that can be printed.
4 A single object can have 1 or more meshes which represent different sections for multi-material extrusion.
6 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
13 numpy.seterr(all='ignore')
15 from Cura.util import polygon
17 class printableObject(object):
19 A printable object is an object that can be printed and is on the build platform.
20 It contains 1 or more Meshes. Where more meshes are used for multi-extrusion.
22 Each object has a 3x3 transformation matrix to rotate/scale the object.
23 This object also keeps track of the 2D boundary polygon used for object collision in the objectScene class.
25 def __init__(self, originFilename):
26 self._originFilename = originFilename
27 if originFilename is None:
30 self._name = os.path.basename(originFilename)
32 self._name = os.path.splitext(self._name)[0]
34 self._position = numpy.array([0.0, 0.0])
35 self._matrix = numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)
36 self._transformedMin = None
37 self._transformedMax = None
38 self._transformedSize = None
39 self._boundaryCircleSize = None
40 self._drawOffset = None
41 self._boundaryHull = None
42 self._printAreaExtend = numpy.array([[-1,-1],[ 1,-1],[ 1, 1],[-1, 1]], numpy.float32)
43 self._headAreaExtend = numpy.array([[-1,-1],[ 1,-1],[ 1, 1],[-1, 1]], numpy.float32)
44 self._headMinSize = numpy.array([1, 1], numpy.float32)
45 self._printAreaHull = None
46 self._headAreaHull = None
47 self._headAreaMinHull = None
52 ret = printableObject(self._originFilename)
53 ret._matrix = self._matrix.copy()
54 ret._transformedMin = self._transformedMin.copy()
55 ret._transformedMax = self._transformedMax.copy()
56 ret._transformedSize = self._transformedSize.copy()
57 ret._boundaryCircleSize = self._boundaryCircleSize
58 ret._boundaryHull = self._boundaryHull.copy()
59 ret._printAreaExtend = self._printAreaExtend.copy()
60 ret._printAreaHull = self._printAreaHull.copy()
61 ret._drawOffset = self._drawOffset.copy()
62 for m in self._meshList[:]:
64 m2.vertexes = m.vertexes
65 m2.vertexCount = m.vertexCount
72 self._meshList.append(m)
75 def _postProcessAfterLoad(self):
76 for m in self._meshList:
79 if numpy.max(self.getSize()) > 10000.0:
80 for m in self._meshList:
83 if numpy.max(self.getSize()) < 1.0:
84 for m in self._meshList:
88 def applyMatrix(self, m):
92 def processMatrix(self):
93 self._transformedMin = numpy.array([999999999999,999999999999,999999999999], numpy.float64)
94 self._transformedMax = numpy.array([-999999999999,-999999999999,-999999999999], numpy.float64)
95 self._boundaryCircleSize = 0
97 hull = numpy.zeros((0, 2), numpy.int)
98 for m in self._meshList:
99 transformedVertexes = m.getTransformedVertexes()
100 hull = polygon.convexHull(numpy.concatenate((numpy.rint(transformedVertexes[:,0:2]).astype(int), hull), 0))
101 transformedMin = transformedVertexes.min(0)
102 transformedMax = transformedVertexes.max(0)
103 for n in xrange(0, 3):
104 self._transformedMin[n] = min(transformedMin[n], self._transformedMin[n])
105 self._transformedMax[n] = max(transformedMax[n], self._transformedMax[n])
107 #Calculate the boundary circle
108 transformedSize = transformedMax - transformedMin
109 center = transformedMin + transformedSize / 2.0
110 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)
111 self._boundaryCircleSize = max(self._boundaryCircleSize, boundaryCircleSize)
112 self._transformedSize = self._transformedMax - self._transformedMin
113 self._drawOffset = (self._transformedMax + self._transformedMin) / 2
114 self._drawOffset[2] = self._transformedMin[2]
115 self._transformedMax -= self._drawOffset
116 self._transformedMin -= self._drawOffset
118 self._boundaryHull = polygon.minkowskiHull((hull.astype(numpy.float32) - self._drawOffset[0:2]), numpy.array([[-1,-1],[-1,1],[1,1],[1,-1]],numpy.float32))
119 self._printAreaHull = polygon.minkowskiHull(self._boundaryHull, self._printAreaExtend)
120 self.setHeadArea(self._headAreaExtend, self._headMinSize)
124 def getOriginFilename(self):
125 return self._originFilename
126 def getPosition(self):
127 return self._position
128 def setPosition(self, newPos):
129 self._position = newPos
133 def getMaximum(self):
134 return self._transformedMax
135 def getMinimum(self):
136 return self._transformedMin
138 return self._transformedSize
139 def getDrawOffset(self):
140 return self._drawOffset
141 def getBoundaryCircle(self):
142 return self._boundaryCircleSize
144 def setPrintAreaExtends(self, poly):
145 self._printAreaExtend = poly
146 self._printAreaHull = polygon.minkowskiHull(self._boundaryHull, self._printAreaExtend)
148 self.setHeadArea(self._headAreaExtend, self._headMinSize)
150 def setHeadArea(self, poly, minSize):
151 self._headAreaExtend = poly
152 self._headMinSize = minSize
153 self._headAreaHull = polygon.minkowskiHull(self._printAreaHull, self._headAreaExtend)
154 pMin = numpy.min(self._printAreaHull, 0) - self._headMinSize
155 pMax = numpy.max(self._printAreaHull, 0) + self._headMinSize
156 square = numpy.array([pMin, [pMin[0], pMax[1]], pMax, [pMax[0], pMin[1]]], numpy.float32)
157 self._headAreaMinHull = polygon.clipConvex(self._headAreaHull, square)
159 def mirror(self, axis):
160 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
161 matrix[axis][axis] = -1
162 self.applyMatrix(numpy.matrix(matrix, numpy.float64))
166 numpy.linalg.norm(self._matrix[::,0].getA().flatten()),
167 numpy.linalg.norm(self._matrix[::,1].getA().flatten()),
168 numpy.linalg.norm(self._matrix[::,2].getA().flatten())], numpy.float64);
170 def setScale(self, scale, axis, uniform):
171 currentScale = numpy.linalg.norm(self._matrix[::,axis].getA().flatten())
172 scale /= currentScale
176 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
178 matrix = [[1.0,0,0], [0, 1.0, 0], [0, 0, 1.0]]
179 matrix[axis][axis] = scale
180 self.applyMatrix(numpy.matrix(matrix, numpy.float64))
182 def setSize(self, size, axis, uniform):
183 scale = self.getSize()[axis]
188 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
190 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
191 matrix[axis][axis] = scale
192 self.applyMatrix(numpy.matrix(matrix, numpy.float64))
194 def resetScale(self):
195 x = 1/numpy.linalg.norm(self._matrix[::,0].getA().flatten())
196 y = 1/numpy.linalg.norm(self._matrix[::,1].getA().flatten())
197 z = 1/numpy.linalg.norm(self._matrix[::,2].getA().flatten())
198 self.applyMatrix(numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64))
200 def resetRotation(self):
201 x = numpy.linalg.norm(self._matrix[::,0].getA().flatten())
202 y = numpy.linalg.norm(self._matrix[::,1].getA().flatten())
203 z = numpy.linalg.norm(self._matrix[::,2].getA().flatten())
204 self._matrix = numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
208 transformedVertexes = self._meshList[0].getTransformedVertexes()
209 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
212 for v in transformedVertexes:
213 diff = v - minZvertex
214 len = math.sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])
217 dot = (diff[2] / len)
223 rad = -math.atan2(dotV[1], dotV[0])
224 self._matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
225 rad = -math.asin(dotMin)
226 self._matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
229 transformedVertexes = self._meshList[0].getTransformedVertexes()
230 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
233 for v in transformedVertexes:
234 diff = v - minZvertex
235 len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
238 dot = (diff[2] / len)
245 rad = math.asin(dotMin)
247 rad = -math.asin(dotMin)
248 self.applyMatrix(numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64))
250 def scaleUpTo(self, size):
251 vMin = self._transformedMin
252 vMax = self._transformedMax
254 scaleX1 = (size[0] / 2 - self._position[0]) / ((vMax[0] - vMin[0]) / 2)
255 scaleY1 = (size[1] / 2 - self._position[1]) / ((vMax[1] - vMin[1]) / 2)
256 scaleX2 = (self._position[0] + size[0] / 2) / ((vMax[0] - vMin[0]) / 2)
257 scaleY2 = (self._position[1] + size[1] / 2) / ((vMax[1] - vMin[1]) / 2)
258 scaleZ = size[2] / (vMax[2] - vMin[2])
259 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
261 self.applyMatrix(numpy.matrix([[scale,0,0],[0,scale,0],[0,0,scale]], numpy.float64))
263 #Split splits an object with multiple meshes into different objects, where each object is a part of the original mesh that has
264 # connected faces. This is useful to split up plate STL files.
265 def split(self, callback):
267 for oriMesh in self._meshList:
268 ret += oriMesh.split(callback)
271 def canStoreAsSTL(self):
272 return len(self._meshList) < 2
274 #getVertexIndexList returns an array of vertexes, and an integer array for each mesh in this object.
275 # the integer arrays are indexes into the vertex array for each triangle in the model.
276 def getVertexIndexList(self):
280 for m in self._meshList:
281 verts = m.getTransformedVertexes(True)
283 for idx in xrange(0, len(verts)):
285 hashNr = int(v[0] * 100) | int(v[1] * 100) << 10 | int(v[2] * 100) << 20
287 if hashNr in vertexMap:
288 for idx2 in vertexMap[hashNr]:
289 if numpy.linalg.norm(v - vertexList[idx2]) < 0.001:
292 vIdx = len(vertexList)
293 vertexMap[hashNr] = [vIdx]
295 meshIdxList.append(vIdx)
296 meshList.append(numpy.array(meshIdxList, numpy.int32))
297 return numpy.array(vertexList, numpy.float32), meshList
301 A mesh is a list of 3D triangles build from vertexes. Each triangle has 3 vertexes.
303 A "VBO" can be associated with this object, which is used for rendering this object.
305 def __init__(self, obj):
311 def _addFace(self, x0, y0, z0, x1, y1, z1, x2, y2, z2):
313 self.vertexes[n][0] = x0
314 self.vertexes[n][1] = y0
315 self.vertexes[n][2] = z0
317 self.vertexes[n][0] = x1
318 self.vertexes[n][1] = y1
319 self.vertexes[n][2] = z1
321 self.vertexes[n][0] = x2
322 self.vertexes[n][1] = y2
323 self.vertexes[n][2] = z2
324 self.vertexCount += 3
326 def _prepareFaceCount(self, faceNumber):
327 #Set the amount of faces before loading data in them. This way we can create the numpy arrays before we fill them.
328 self.vertexes = numpy.zeros((faceNumber*3, 3), numpy.float32)
329 self.normal = numpy.zeros((faceNumber*3, 3), numpy.float32)
332 def _calculateNormals(self):
333 #Calculate the normals
334 tris = self.vertexes.reshape(self.vertexCount / 3, 3, 3)
335 normals = numpy.cross( tris[::,1 ] - tris[::,0] , tris[::,2 ] - tris[::,0] )
336 lens = numpy.sqrt( normals[:,0]**2 + normals[:,1]**2 + normals[:,2]**2 )
341 n = numpy.zeros((self.vertexCount / 3, 9), numpy.float32)
345 self.normal = n.reshape(self.vertexCount, 3)
346 self.invNormal = -self.normal
348 def _vertexHash(self, idx):
349 v = self.vertexes[idx]
350 return int(v[0] * 100) | int(v[1] * 100) << 10 | int(v[2] * 100) << 20
352 def _idxFromHash(self, map, idx):
353 vHash = self._vertexHash(idx)
355 if numpy.linalg.norm(self.vertexes[i] - self.vertexes[idx]) < 0.001:
358 def getTransformedVertexes(self, applyOffsets = False):
360 pos = self._obj._position.copy()
362 pos[2] = self._obj.getSize()[2] / 2
363 offset = self._obj._drawOffset.copy()
364 offset[2] += self._obj.getSize()[2] / 2
365 return (numpy.matrix(self.vertexes, copy = False) * numpy.matrix(self._obj._matrix, numpy.float32)).getA() - offset + pos
366 return (numpy.matrix(self.vertexes, copy = False) * numpy.matrix(self._obj._matrix, numpy.float32)).getA()
368 def split(self, callback):
372 for idx in xrange(0, self.vertexCount):
374 callback(idx * 100 / self.vertexCount)
375 vHash = self._vertexHash(idx)
376 if vHash not in vertexMap:
377 vertexMap[vHash] = []
378 vertexMap[vHash].append(idx)
379 vertexToFace.append([])
382 for idx in xrange(0, self.vertexCount, 3):
384 callback(idx * 100 / self.vertexCount)
385 f = [self._idxFromHash(vertexMap, idx), self._idxFromHash(vertexMap, idx+1), self._idxFromHash(vertexMap, idx+2)]
386 vertexToFace[f[0]].append(idx / 3)
387 vertexToFace[f[1]].append(idx / 3)
388 vertexToFace[f[2]].append(idx / 3)
393 for idx in xrange(0, len(faceList)):
399 while len(todoList) > 0:
401 meshFaceList.append(idx)
402 for n in xrange(0, 3):
403 for i in vertexToFace[faceList[idx][n]]:
408 obj = printableObject(self._obj.getOriginFilename())
409 obj._matrix = self._obj._matrix.copy()
411 m._prepareFaceCount(len(meshFaceList))
412 for idx in meshFaceList:
413 m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][0]]
415 m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][1]]
417 m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][2]]
419 obj._postProcessAfterLoad()