chiark / gitweb /
Correctly size the bed for dual-extrusion-support prints.
[cura.git] / Cura / util / objectScene.py
1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
2 import random
3 import numpy
4 from Cura.util import profile
5
6 class _objectOrder(object):
7         def __init__(self, order, todo):
8                 self.order = order
9                 self.todo = todo
10
11 class _objectOrderFinder(object):
12         def __init__(self, scene, offset, leftToRight, frontToBack, gantryHeight):
13                 self._scene = scene
14                 self._offset = offset - numpy.array([0.1,0.1])
15                 self._objs = scene.objects()
16                 self._leftToRight = leftToRight
17                 self._frontToBack = frontToBack
18                 initialList = []
19                 for n in xrange(0, len(self._objs)):
20                         if scene.checkPlatform(self._objs[n]):
21                                 initialList.append(n)
22                 for n in initialList:
23                         if self._objs[n].getSize()[2] > gantryHeight and len(initialList) > 1:
24                                 self.order = None
25                                 return
26                 if len(initialList) == 0:
27                         self.order = []
28                         return
29
30
31                 self._hitMap = [None] * (max(initialList)+1)
32                 for a in initialList:
33                         self._hitMap[a] = [False] * (max(initialList)+1)
34                         for b in initialList:
35                                 self._hitMap[a][b] = self._checkHit(a, b)
36
37                 initialList.sort(self._objIdxCmp)
38
39                 n = 0
40                 self._todo = [_objectOrder([], initialList)]
41                 while len(self._todo) > 0:
42                         n += 1
43                         current = self._todo.pop()
44                         #print len(self._todo), len(current.order), len(initialList), current.order
45                         for addIdx in current.todo:
46                                 if not self._checkHitFor(addIdx, current.order) and not self._checkBlocks(addIdx, current.todo):
47                                         todoList = current.todo[:]
48                                         todoList.remove(addIdx)
49                                         order = current.order[:] + [addIdx]
50                                         if len(todoList) == 0:
51                                                 self._todo = None
52                                                 self.order = order
53                                                 return
54                                         self._todo.append(_objectOrder(order, todoList))
55                 self.order = None
56
57         def _objIdxCmp(self, a, b):
58                 scoreA = sum(self._hitMap[a])
59                 scoreB = sum(self._hitMap[b])
60                 return scoreA - scoreB
61
62         def _checkHitFor(self, addIdx, others):
63                 for idx in others:
64                         if self._hitMap[addIdx][idx]:
65                                 return True
66                 return False
67
68         def _checkBlocks(self, addIdx, others):
69                 for idx in others:
70                         if addIdx != idx and self._hitMap[idx][addIdx]:
71                                 return True
72                 return False
73
74         #Check if printing one object will cause printhead colission with other object.
75         def _checkHit(self, addIdx, idx):
76                 addPos = self._scene._objectList[addIdx].getPosition()
77                 addSize = self._scene._objectList[addIdx].getSize()
78                 pos = self._scene._objectList[idx].getPosition()
79                 size = self._scene._objectList[idx].getSize()
80
81                 if self._leftToRight:
82                         if addPos[0] - addSize[0] / 2 - self._offset[0] >= pos[0] + size[0] / 2:
83                                 return False
84                 else:
85                         if addPos[0] + addSize[0] / 2 + self._offset[0] <= pos[0] - size[0] / 2:
86                                 return False
87
88                 if self._frontToBack:
89                         if addPos[1] - addSize[1] / 2 - self._offset[1] >= pos[1] + size[1] / 2:
90                                 return False
91                 else:
92                         if addPos[1] + addSize[1] / 2 + self._offset[1] <= pos[1] - size[1] / 2:
93                                 return False
94
95                 return True
96
97 class Scene(object):
98         def __init__(self):
99                 self._objectList = []
100                 self._sizeOffsets = numpy.array([0.0,0.0], numpy.float32)
101                 self._machineSize = numpy.array([100,100,100], numpy.float32)
102                 self._headSizeOffsets = numpy.array([18.0,18.0], numpy.float32)
103                 self._extruderOffset = [numpy.array([0,0], numpy.float32)] * 4
104
105                 #Print order variables
106                 self._leftToRight = False
107                 self._frontToBack = True
108                 self._gantryHeight = 60
109         # Physical (square) machine size.
110         def setMachineSize(self, machineSize):
111                 self._machineSize = machineSize
112
113         # Size offsets are offsets caused by brim, skirt, etc.
114         def setSizeOffsets(self, sizeOffsets):
115                 self._sizeOffsets = sizeOffsets
116
117         #size of the printing head.
118         def setHeadSize(self, xMin, xMax, yMin, yMax, gantryHeight):
119                 self._leftToRight = xMin < xMax
120                 self._frontToBack = yMin < yMax
121                 self._headSizeOffsets[0] = min(xMin, xMax)
122                 self._headSizeOffsets[1] = min(yMin, yMax)
123                 self._gantryHeight = gantryHeight
124
125         def setExtruderOffset(self, extruderNr, offsetX, offsetY):
126                 self._extruderOffset[extruderNr] = numpy.array([offsetX, offsetY], numpy.float32)
127
128         def getObjectExtend(self):
129                 return self._sizeOffsets + self._headSizeOffsets
130
131         def objects(self):
132                 return self._objectList
133
134         #Add new object to print area
135         def add(self, obj):
136                 self._findFreePositionFor(obj)
137                 self._objectList.append(obj)
138                 self.pushFree()
139                 if numpy.max(obj.getSize()[0:2]) > numpy.max(self._machineSize[0:2]) * 2.5:
140                         scale = numpy.max(self._machineSize[0:2]) * 2.5 / numpy.max(obj.getSize()[0:2])
141                         matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
142                         obj.applyMatrix(numpy.matrix(matrix, numpy.float64))
143
144         def remove(self, obj):
145                 self._objectList.remove(obj)
146
147         #Dual(multiple) extrusion merge
148         def merge(self, obj1, obj2):
149                 self.remove(obj2)
150                 obj1._meshList += obj2._meshList
151                 for m in obj2._meshList:
152                         m._obj = obj1
153                 obj1.processMatrix()
154                 obj1.setPosition((obj1.getPosition() + obj2.getPosition()) / 2)
155                 self.pushFree()
156
157         def pushFree(self):
158                 n = 1000
159                 while self._pushFree():
160                         n -= 1
161                         if n < 0:
162                                 return
163
164         def arrangeAll(self):
165                 oldList = self._objectList
166                 self._objectList = []
167                 for obj in oldList:
168                         obj.setPosition(numpy.array([0,0], numpy.float32))
169                         self.add(obj)
170
171         def centerAll(self):
172                 minPos = numpy.array([9999999,9999999], numpy.float32)
173                 maxPos = numpy.array([-9999999,-9999999], numpy.float32)
174                 for obj in self._objectList:
175                         pos = obj.getPosition()
176                         size = obj.getSize()
177                         minPos[0] = min(minPos[0], pos[0] - size[0] / 2)
178                         minPos[1] = min(minPos[1], pos[1] - size[1] / 2)
179                         maxPos[0] = max(maxPos[0], pos[0] + size[0] / 2)
180                         maxPos[1] = max(maxPos[1], pos[1] + size[1] / 2)
181                 offset = -(maxPos + minPos) / 2
182                 for obj in self._objectList:
183                         obj.setPosition(obj.getPosition() + offset)
184
185         def printOrder(self):
186                 order = _objectOrderFinder(self, self._headSizeOffsets + self._sizeOffsets, self._leftToRight, self._frontToBack, self._gantryHeight).order
187                 return order
188
189         def _pushFree(self):
190                 for a in self._objectList:
191                         for b in self._objectList:
192                                 if not self._checkHit(a, b):
193                                         continue
194                                 posDiff = a.getPosition() - b.getPosition()
195                                 if posDiff[0] == 0.0 and posDiff[1] == 0.0:
196                                         posDiff[1] = 1.0
197                                 if abs(posDiff[0]) > abs(posDiff[1]):
198                                         axis = 0
199                                 else:
200                                         axis = 1
201                                 aPos = a.getPosition()
202                                 bPos = b.getPosition()
203                                 center = (aPos[axis] + bPos[axis]) / 2
204                                 distance = (a.getSize()[axis] + b.getSize()[axis]) / 2 + 0.1 + self._sizeOffsets[axis] + self._headSizeOffsets[axis]
205                                 if posDiff[axis] < 0:
206                                         distance = -distance
207                                 aPos[axis] = center + distance / 2
208                                 bPos[axis] = center - distance / 2
209                                 a.setPosition(aPos)
210                                 b.setPosition(bPos)
211                                 return True
212                 return False
213
214         #Check if two objects are hitting each-other (+ head space).
215         def _checkHit(self, a, b):
216                 if a == b:
217                         return False
218                 posDiff = a.getPosition() - b.getPosition()
219                 if abs(posDiff[0]) < (a.getSize()[0] + b.getSize()[0]) / 2 + self._sizeOffsets[0] + self._headSizeOffsets[0]:
220                         if abs(posDiff[1]) < (a.getSize()[1] + b.getSize()[1]) / 2 + self._sizeOffsets[1] + self._headSizeOffsets[1]:
221                                 return True
222                 return False
223
224         def checkPlatform(self, obj):
225                 p = obj.getPosition()
226                 s = obj.getSize()[0:2] / 2 + self._sizeOffsets
227                 offsetLeft = 0.0
228                 offsetRight = 0.0
229                 offsetBack = 0.0
230                 offsetFront = 0.0
231                 extruderCount = len(obj._meshList)
232                 if profile.getProfileSetting('support_dual_extrusion') == 'Second extruder' and profile.getProfileSetting('support') != 'None':
233                         extruderCount = max(extruderCount, 2)
234                 for n in xrange(1, extruderCount):
235                         if offsetLeft < self._extruderOffset[n][0]:
236                                 offsetLeft = self._extruderOffset[n][0]
237                         if offsetRight > self._extruderOffset[n][0]:
238                                 offsetRight = self._extruderOffset[n][0]
239                         if offsetFront < self._extruderOffset[n][1]:
240                                 offsetFront = self._extruderOffset[n][1]
241                         if offsetBack > self._extruderOffset[n][1]:
242                                 offsetBack = self._extruderOffset[n][1]
243                 boundLeft = -self._machineSize[0] / 2 + offsetLeft
244                 boundRight = self._machineSize[0] / 2 + offsetRight
245                 boundFront = -self._machineSize[1] / 2 + offsetFront
246                 boundBack = self._machineSize[1] / 2 + offsetBack
247                 if p[0] - s[0] < boundLeft:
248                         return False
249                 if p[0] + s[0] > boundRight:
250                         return False
251                 if p[1] - s[1] < boundFront:
252                         return False
253                 if p[1] + s[1] > boundBack:
254                         return False
255
256                 #Do clip Check for UM2.
257                 machine = profile.getMachineSetting('machine_type')
258                 if machine == "ultimaker2":
259                         #lowerRight clip check
260                         if p[0] - s[0] < boundLeft + 25 and p[1] - s[1] < boundFront + 10:
261                                 return False
262                         #UpperRight
263                         if p[0] - s[0] < boundLeft + 25 and p[1] + s[1] > boundBack - 10:
264                                 return False
265                         #LowerLeft
266                         if p[0] + s[0] > boundRight - 25 and p[1] - s[1] < boundFront + 10:
267                                 return False
268                         #UpperLeft
269                         if p[0] + s[0] > boundRight - 25 and p[1] + s[1] > boundBack - 10:
270                                 return False
271                 return True
272
273         def _findFreePositionFor(self, obj):
274                 posList = []
275                 for a in self._objectList:
276                         p = a.getPosition()
277                         s = (a.getSize()[0:2] + obj.getSize()[0:2]) / 2 + self._sizeOffsets + self._headSizeOffsets
278                         posList.append(p + s * ( 1.0, 1.0))
279                         posList.append(p + s * ( 0.0, 1.0))
280                         posList.append(p + s * (-1.0, 1.0))
281                         posList.append(p + s * ( 1.0, 0.0))
282                         posList.append(p + s * (-1.0, 0.0))
283                         posList.append(p + s * ( 1.0,-1.0))
284                         posList.append(p + s * ( 0.0,-1.0))
285                         posList.append(p + s * (-1.0,-1.0))
286
287                 best = None
288                 bestDist = None
289                 for p in posList:
290                         obj.setPosition(p)
291                         ok = True
292                         for a in self._objectList:
293                                 if self._checkHit(a, obj):
294                                         ok = False
295                                         break
296                         if not ok:
297                                 continue
298                         dist = numpy.linalg.norm(p)
299                         if not self.checkPlatform(obj):
300                                 dist *= 3
301                         if best is None or dist < bestDist:
302                                 best = p
303                                 bestDist = dist
304                 if best is not None:
305                         obj.setPosition(best)