1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
5 from Cura.util import profile
6 from Cura.util import polygon
8 class _objectOrder(object):
9 def __init__(self, order, todo):
13 class _objectOrderFinder(object):
14 def __init__(self, scene, leftToRight, frontToBack, gantryHeight):
16 self._objs = scene.objects()
17 self._leftToRight = leftToRight
18 self._frontToBack = frontToBack
20 for n in xrange(0, len(self._objs)):
21 if scene.checkPlatform(self._objs[n]):
24 if self._objs[n].getSize()[2] > gantryHeight and len(initialList) > 1:
27 if len(initialList) == 0:
31 self._hitMap = [None] * (max(initialList)+1)
33 self._hitMap[a] = [False] * (max(initialList)+1)
35 self._hitMap[a][b] = self._checkHit(a, b)
37 #Check if we have 2 files that overlap so that they can never be printed one at a time.
40 if a != b and self._hitMap[a][b] and self._hitMap[b][a]:
44 initialList.sort(self._objIdxCmp)
47 self._todo = [_objectOrder([], initialList)]
48 while len(self._todo) > 0:
50 current = self._todo.pop()
51 #print len(self._todo), len(current.order), len(initialList), current.order
52 for addIdx in current.todo:
53 if not self._checkHitFor(addIdx, current.order) and not self._checkBlocks(addIdx, current.todo):
54 todoList = current.todo[:]
55 todoList.remove(addIdx)
56 order = current.order[:] + [addIdx]
57 if len(todoList) == 0:
61 self._todo.append(_objectOrder(order, todoList))
64 def _objIdxCmp(self, a, b):
65 scoreA = sum(self._hitMap[a])
66 scoreB = sum(self._hitMap[b])
67 return scoreA - scoreB
69 def _checkHitFor(self, addIdx, others):
71 if self._hitMap[addIdx][idx]:
75 def _checkBlocks(self, addIdx, others):
77 if addIdx != idx and self._hitMap[idx][addIdx]:
81 #Check if printing one object will cause printhead colission with other object.
82 def _checkHit(self, addIdx, idx):
83 obj = self._scene._objectList[idx]
84 addObj = self._scene._objectList[addIdx]
85 return polygon.polygonCollision(obj._boundaryHull + obj.getPosition(), addObj._headAreaHull + addObj.getPosition())
90 self._sizeOffsets = numpy.array([0.0,0.0], numpy.float32)
91 self._machineSize = numpy.array([100,100,100], numpy.float32)
92 self._headSizeOffsets = numpy.array([18.0,18.0], numpy.float32)
93 self._minExtruderCount = None
94 self._extruderOffset = [numpy.array([0,0], numpy.float32)] * 4
96 #Print order variables
97 self._leftToRight = False
98 self._frontToBack = True
99 self._gantryHeight = 60
100 self._oneAtATime = True
102 # update the physical machine dimensions
103 def updateMachineDimensions(self):
104 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
105 self._machinePolygons = profile.getMachineSizePolygons()
106 self.updateHeadSize()
108 # Size offsets are offsets caused by brim, skirt, etc.
109 def updateSizeOffsets(self, force=False):
110 newOffsets = numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32)
111 minExtruderCount = profile.minimalExtruderCount()
112 if not force and numpy.array_equal(self._sizeOffsets, newOffsets) and self._minExtruderCount == minExtruderCount:
114 self._sizeOffsets = newOffsets
115 self._minExtruderCount = minExtruderCount
117 extends = [numpy.array([[-newOffsets[0],-newOffsets[1]],[ newOffsets[0],-newOffsets[1]],[ newOffsets[0], newOffsets[1]],[-newOffsets[0], newOffsets[1]]], numpy.float32)]
118 for n in xrange(1, 4):
119 headOffset = numpy.array([[0, 0], [-profile.getMachineSettingFloat('extruder_offset_x%d' % (n)), -profile.getMachineSettingFloat('extruder_offset_y%d' % (n))]], numpy.float32)
120 extends.append(polygon.minkowskiHull(extends[n-1], headOffset))
121 if minExtruderCount > 1:
122 extends[0] = extends[1]
124 for obj in self._objectList:
125 obj.setPrintAreaExtends(extends[len(obj._meshList) - 1])
127 #size of the printing head.
128 def updateHeadSize(self, obj = None):
129 xMin = profile.getMachineSettingFloat('extruder_head_size_min_x')
130 xMax = profile.getMachineSettingFloat('extruder_head_size_max_x')
131 yMin = profile.getMachineSettingFloat('extruder_head_size_min_y')
132 yMax = profile.getMachineSettingFloat('extruder_head_size_max_y')
133 gantryHeight = profile.getMachineSettingFloat('extruder_head_size_height')
135 self._leftToRight = xMin < xMax
136 self._frontToBack = yMin < yMax
137 self._headSizeOffsets[0] = min(xMin, xMax)
138 self._headSizeOffsets[1] = min(yMin, yMax)
139 self._gantryHeight = gantryHeight
140 self._oneAtATime = self._gantryHeight > 0
142 headArea = numpy.array([[-xMin,-yMin],[ xMax,-yMin],[ xMax, yMax],[-xMin, yMax]], numpy.float32)
145 for obj in self._objectList:
146 obj.setHeadArea(headArea, self._headSizeOffsets)
148 obj.setHeadArea(headArea, self._headSizeOffsets)
150 def setExtruderOffset(self, extruderNr, offsetX, offsetY):
151 self._extruderOffset[extruderNr] = numpy.array([offsetX, offsetY], numpy.float32)
154 return self._objectList
156 #Add new object to print area
158 if numpy.max(obj.getSize()[0:2]) > numpy.max(self._machineSize[0:2]) * 2.5:
159 scale = numpy.max(self._machineSize[0:2]) * 2.5 / numpy.max(obj.getSize()[0:2])
160 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
161 obj.applyMatrix(numpy.matrix(matrix, numpy.float64))
162 self._findFreePositionFor(obj)
163 self._objectList.append(obj)
164 self.updateHeadSize(obj)
165 self.updateSizeOffsets(True)
168 def remove(self, obj):
169 self._objectList.remove(obj)
171 #Dual(multiple) extrusion merge
172 def merge(self, obj1, obj2):
174 obj1._meshList += obj2._meshList
175 for m in obj2._meshList:
178 obj1.setPosition((obj1.getPosition() + obj2.getPosition()) / 2)
183 while self._pushFree():
188 def arrangeAll(self):
189 oldList = self._objectList
190 self._objectList = []
192 obj.setPosition(numpy.array([0,0], numpy.float32))
196 minPos = numpy.array([9999999,9999999], numpy.float32)
197 maxPos = numpy.array([-9999999,-9999999], numpy.float32)
198 for obj in self._objectList:
199 pos = obj.getPosition()
201 minPos[0] = min(minPos[0], pos[0] - size[0] / 2)
202 minPos[1] = min(minPos[1], pos[1] - size[1] / 2)
203 maxPos[0] = max(maxPos[0], pos[0] + size[0] / 2)
204 maxPos[1] = max(maxPos[1], pos[1] + size[1] / 2)
205 offset = -(maxPos + minPos) / 2
206 for obj in self._objectList:
207 obj.setPosition(obj.getPosition() + offset)
209 def printOrder(self):
211 order = _objectOrderFinder(self, self._leftToRight, self._frontToBack, self._gantryHeight).order
217 for a in self._objectList:
218 for b in self._objectList:
219 if a == b or not self.checkPlatform(a) or not self.checkPlatform(b):
222 v = polygon.polygonCollisionPushVector(a._headAreaMinHull + a.getPosition(), b._boundaryHull + b.getPosition())
224 v = polygon.polygonCollisionPushVector(a._boundaryHull + a.getPosition(), b._boundaryHull + b.getPosition())
227 a.setPosition(a.getPosition() + v * 0.4)
228 b.setPosition(b.getPosition() - v * 0.6)
232 #Check if two objects are hitting each-other (+ head space).
233 def _checkHit(self, a, b):
237 return polygon.polygonCollision(a._headAreaMinHull + a.getPosition(), b._boundaryHull + b.getPosition())
239 return polygon.polygonCollision(a._boundaryHull + a.getPosition(), b._boundaryHull + b.getPosition())
241 def checkPlatform(self, obj):
242 area = obj._printAreaHull + obj.getPosition()
243 if not polygon.fullInside(area, self._machinePolygons[0]):
245 #Check the "no go zones"
246 for poly in self._machinePolygons[1:]:
247 if polygon.polygonCollision(poly, area):
251 def _findFreePositionFor(self, obj):
253 for a in self._objectList:
256 s = (a.getSize()[0:2] + obj.getSize()[0:2]) / 2 + self._sizeOffsets + self._headSizeOffsets + numpy.array([3,3], numpy.float32)
258 s = (a.getSize()[0:2] + obj.getSize()[0:2]) / 2 + numpy.array([3,3], numpy.float32)
259 posList.append(p + s * ( 1.0, 1.0))
260 posList.append(p + s * ( 0.0, 1.0))
261 posList.append(p + s * (-1.0, 1.0))
262 posList.append(p + s * ( 1.0, 0.0))
263 posList.append(p + s * (-1.0, 0.0))
264 posList.append(p + s * ( 1.0,-1.0))
265 posList.append(p + s * ( 0.0,-1.0))
266 posList.append(p + s * (-1.0,-1.0))
273 for a in self._objectList:
274 if self._checkHit(a, obj):
279 dist = numpy.linalg.norm(p)
280 if not self.checkPlatform(obj):
282 if best is None or dist < bestDist:
286 obj.setPosition(best)