2 The objectScene module contain a objectScene class,
3 this class contains a group of printableObjects that are located on the build platform.
5 The objectScene handles the printing order of these objects, and if they collide.
7 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
11 from Cura.util import profile
12 from Cura.util import polygon
14 class _objectOrder(object):
16 Internal object used by the _objectOrderFinder to keep track of a possible order in which to print objects.
18 def __init__(self, order, todo):
20 :param order: List of indexes in which to print objects, ordered by printing order.
21 :param todo: List of indexes which are not yet inserted into the order list.
26 class _objectOrderFinder(object):
28 Internal object used by the Scene class to figure out in which order to print objects.
30 def __init__(self, scene, leftToRight, frontToBack, gantryHeight):
32 self._objs = scene.objects()
33 self._leftToRight = leftToRight
34 self._frontToBack = frontToBack
36 for n in xrange(0, len(self._objs)):
37 if scene.checkPlatform(self._objs[n]):
39 if len(initialList) == 1:
40 self.order = initialList
43 if self._objs[n].getSize()[2] > gantryHeight and len(initialList) > 1:
46 if len(initialList) == 0:
50 self._hitMap = [None] * (max(initialList)+1)
52 self._hitMap[a] = [False] * (max(initialList)+1)
54 self._hitMap[a][b] = self._checkHit(a, b)
56 #Check if we have 2 files that overlap so that they can never be printed one at a time.
59 if a != b and self._hitMap[a][b] and self._hitMap[b][a]:
63 initialList.sort(self._objIdxCmp)
66 self._todo = [_objectOrder([], initialList)]
67 while len(self._todo) > 0:
69 current = self._todo.pop()
70 #print len(self._todo), len(current.order), len(initialList), current.order
71 for addIdx in current.todo:
72 if not self._checkHitFor(addIdx, current.order) and not self._checkBlocks(addIdx, current.todo):
73 todoList = current.todo[:]
74 todoList.remove(addIdx)
75 order = current.order[:] + [addIdx]
76 if len(todoList) == 0:
80 self._todo.append(_objectOrder(order, todoList))
83 def _objIdxCmp(self, a, b):
84 scoreA = sum(self._hitMap[a])
85 scoreB = sum(self._hitMap[b])
86 return scoreA - scoreB
88 def _checkHitFor(self, addIdx, others):
90 if self._hitMap[addIdx][idx]:
94 def _checkBlocks(self, addIdx, others):
96 if addIdx != idx and self._hitMap[idx][addIdx]:
100 #Check if printing one object will cause printhead colission with other object.
101 def _checkHit(self, addIdx, idx):
102 obj = self._scene._objectList[idx]
103 addObj = self._scene._objectList[addIdx]
104 return polygon.polygonCollision(obj._boundaryHull + obj.getPosition(), addObj._headAreaHull + addObj.getPosition())
108 The scene class keep track of an collection of objects on a build platform and their state.
109 It can figure out in which order to print them (if any) and if an object can be printed at all.
111 def __init__(self, sceneView=None):
112 self._objectList = []
113 self._sizeOffsets = numpy.array([0.0,0.0], numpy.float32)
114 self._machineSize = numpy.array([100,100,100], numpy.float32)
115 self._headSizeOffsets = numpy.array([18.0,18.0], numpy.float32)
116 self._minExtruderCount = None
117 self._extruderOffset = [numpy.array([0,0], numpy.float32)] * 4
119 #Print order variables
120 self._leftToRight = False
121 self._frontToBack = True
122 self._gantryHeight = 60
123 self._oneAtATime = True
125 self._lastOneAtATime = False
126 self._lastResultOneAtATime = True
127 self._sceneView = sceneView
129 # update the physical machine dimensions
130 def updateMachineDimensions(self):
131 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
132 self._machinePolygons = profile.getMachineSizePolygons()
133 self.updateHeadSize()
135 # Size offsets are offsets caused by brim, skirt, etc.
136 def updateSizeOffsets(self, force=False):
137 newOffsets = numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32)
138 minExtruderCount = profile.minimalExtruderCount()
139 if not force and numpy.array_equal(self._sizeOffsets, newOffsets) and self._minExtruderCount == minExtruderCount:
141 self._sizeOffsets = newOffsets
142 self._minExtruderCount = minExtruderCount
144 extends = [numpy.array([[-newOffsets[0],-newOffsets[1]],[ newOffsets[0],-newOffsets[1]],[ newOffsets[0], newOffsets[1]],[-newOffsets[0], newOffsets[1]]], numpy.float32)]
145 for n in xrange(1, 4):
146 headOffset = numpy.array([[0, 0], [-profile.getMachineSettingFloat('extruder_offset_x%d' % (n)), -profile.getMachineSettingFloat('extruder_offset_y%d' % (n))]], numpy.float32)
147 extends.append(polygon.minkowskiHull(extends[n-1], headOffset))
148 if minExtruderCount > 1:
149 extends[0] = extends[1]
151 for obj in self._objectList:
152 obj.setPrintAreaExtends(extends[len(obj._meshList) - 1])
154 #size of the printing head.
155 def updateHeadSize(self, obj = None):
156 xMin = profile.getMachineSettingFloat('extruder_head_size_min_x')
157 xMax = profile.getMachineSettingFloat('extruder_head_size_max_x')
158 yMin = profile.getMachineSettingFloat('extruder_head_size_min_y')
159 yMax = profile.getMachineSettingFloat('extruder_head_size_max_y')
160 gantryHeight = profile.getMachineSettingFloat('extruder_head_size_height')
161 objectSink = profile.getProfileSettingFloat("object_sink")
162 if profile.getPreference('startMode') == 'Simple':
163 objectSink = float(profile.settingsDictionary["object_sink"].getDefault())
165 self._leftToRight = xMin < xMax
166 self._frontToBack = yMin < yMax
167 self._headSizeOffsets[0] = min(xMin, xMax)
168 self._headSizeOffsets[1] = min(yMin, yMax)
169 self._gantryHeight = gantryHeight
171 printOneAtATime = profile.getPreference('oneAtATime') == 'True'
172 self._oneAtATime = self._gantryHeight > 0 and printOneAtATime
174 if not self._lastOneAtATime:
175 #print mode was changed by user. We need to reset that value to test with current scene content
176 self._lastResultOneAtATime = True
178 for objIter in self._objectList:
179 if objIter.getSize()[2] - objectSink > self._gantryHeight:
180 self._oneAtATime = False
181 if self._lastResultOneAtATime:
183 self._sceneView.notification.message("Object must be shorter than {}mm for this printer/tool head. Reduce object size or swap to \"All at once\" mode. ".format(self._gantryHeight))
186 self._lastResultOneAtATime = self._oneAtATime
187 self._lastOneAtATime = printOneAtATime
189 headArea = numpy.array([[-xMin,-yMin],[ xMax,-yMin],[ xMax, yMax],[-xMin, yMax]], numpy.float32)
192 for obj in self._objectList:
193 obj.setHeadArea(headArea, self._headSizeOffsets)
195 obj.setHeadArea(headArea, self._headSizeOffsets)
197 def isOneAtATime(self):
198 return self._oneAtATime
200 def setExtruderOffset(self, extruderNr, offsetX, offsetY):
201 self._extruderOffset[extruderNr] = numpy.array([offsetX, offsetY], numpy.float32)
204 return self._objectList
206 #Add new object to print area
207 def add(self, obj, positionOnly=False):
208 if not positionOnly and numpy.max(obj.getSize()[0:2]) > numpy.max(self._machineSize[0:2]) * 2.5:
209 scale = numpy.max(self._machineSize[0:2]) * 2.5 / numpy.max(obj.getSize()[0:2])
210 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
211 obj.applyMatrix(numpy.matrix(matrix, numpy.float64))
212 self._findFreePositionFor(obj)
213 self._objectList.append(obj)
214 self.updateHeadSize(obj)
215 self.updateSizeOffsets(True)
218 def remove(self, obj):
219 self._objectList.remove(obj)
221 #Dual(multiple) extrusion merge
222 def merge(self, obj1, obj2):
224 obj1._meshList += obj2._meshList
225 for m in obj2._meshList:
228 obj1.setPosition((obj1.getPosition() + obj2.getPosition()) / 2)
231 def pushFree(self, staticObj = None):
232 if staticObj is None:
233 for obj in self._objectList:
236 if not self.checkPlatform(staticObj):
239 for obj in self._objectList:
240 if obj == staticObj or not self.checkPlatform(obj):
243 v = polygon.polygonCollisionPushVector(obj._headAreaMinHull + obj.getPosition(), staticObj._boundaryHull + staticObj.getPosition())
245 v = polygon.polygonCollisionPushVector(obj._boundaryHull + obj.getPosition(), staticObj._boundaryHull + staticObj.getPosition())
248 obj.setPosition(obj.getPosition() + v * 1.01)
253 def arrangeAll(self, positionOnly=False):
254 oldList = self._objectList
255 self._objectList = []
257 obj.setPosition(numpy.array([0,0], numpy.float32))
258 self.add(obj, positionOnly)
261 minPos = numpy.array([9999999,9999999], numpy.float32)
262 maxPos = numpy.array([-9999999,-9999999], numpy.float32)
263 for obj in self._objectList:
264 pos = obj.getPosition()
266 minPos[0] = min(minPos[0], pos[0] - size[0] / 2)
267 minPos[1] = min(minPos[1], pos[1] - size[1] / 2)
268 maxPos[0] = max(maxPos[0], pos[0] + size[0] / 2)
269 maxPos[1] = max(maxPos[1], pos[1] + size[1] / 2)
270 offset = -(maxPos + minPos) / 2
271 for obj in self._objectList:
272 obj.setPosition(obj.getPosition() + offset)
274 def printOrder(self):
276 order = _objectOrderFinder(self, self._leftToRight, self._frontToBack, self._gantryHeight).order
281 #Check if two objects are hitting each-other (+ head space).
282 def _checkHit(self, a, b):
286 return polygon.polygonCollision(a._headAreaMinHull + a.getPosition(), b._boundaryHull + b.getPosition())
288 return polygon.polygonCollision(a._boundaryHull + a.getPosition(), b._boundaryHull + b.getPosition())
290 def checkPlatform(self, obj):
291 objectSink = profile.getProfileSettingFloat("object_sink")
292 if profile.getPreference('startMode') == 'Simple':
293 objectSink = float(profile.settingsDictionary["object_sink"].getDefault())
295 area = obj._printAreaHull + obj.getPosition()
296 if obj.getSize()[2] - objectSink > self._machineSize[2]:
298 if not polygon.fullInside(area, self._machinePolygons[0]):
300 #Check the "no go zones"
301 for poly in self._machinePolygons[1:]:
302 if polygon.polygonCollision(poly, area):
306 def _findFreePositionFor(self, obj):
308 for a in self._objectList:
311 s = (a.getSize()[0:2] + obj.getSize()[0:2]) / 2 + self._sizeOffsets + self._headSizeOffsets + numpy.array([4,4], numpy.float32)
313 s = (a.getSize()[0:2] + obj.getSize()[0:2]) / 2 + numpy.array([4,4], numpy.float32)
314 posList.append(p + s * ( 1.0, 1.0))
315 posList.append(p + s * ( 0.0, 1.0))
316 posList.append(p + s * (-1.0, 1.0))
317 posList.append(p + s * ( 1.0, 0.0))
318 posList.append(p + s * (-1.0, 0.0))
319 posList.append(p + s * ( 1.0,-1.0))
320 posList.append(p + s * ( 0.0,-1.0))
321 posList.append(p + s * (-1.0,-1.0))
328 for a in self._objectList:
329 if self._checkHit(a, obj):
334 dist = numpy.linalg.norm(p)
335 if not self.checkPlatform(obj):
337 if best is None or dist < bestDist:
341 obj.setPosition(best)