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