chiark / gitweb /
f7388b2a29e0dc05f52c12c2fa82ad132f9b0013
[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, leftToRight, frontToBack, gantryHeight):
15                 self._scene = scene
16                 self._objs = scene.objects()
17                 self._leftToRight = leftToRight
18                 self._frontToBack = frontToBack
19                 initialList = []
20                 for n in xrange(0, len(self._objs)):
21                         if scene.checkPlatform(self._objs[n]):
22                                 initialList.append(n)
23                 for n in initialList:
24                         if self._objs[n].getSize()[2] > gantryHeight and len(initialList) > 1:
25                                 self.order = None
26                                 return
27                 if len(initialList) == 0:
28                         self.order = []
29                         return
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                 #Check if we have 2 files that overlap so that they can never be printed one at a time.
38                 for a in initialList:
39                         for b in initialList:
40                                 if a != b and self._hitMap[a][b] and self._hitMap[b][a]:
41                                         self.order = None
42                                         return
43
44                 initialList.sort(self._objIdxCmp)
45
46                 n = 0
47                 self._todo = [_objectOrder([], initialList)]
48                 while len(self._todo) > 0:
49                         n += 1
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:
58                                                 self._todo = None
59                                                 self.order = order
60                                                 return
61                                         self._todo.append(_objectOrder(order, todoList))
62                 self.order = None
63
64         def _objIdxCmp(self, a, b):
65                 scoreA = sum(self._hitMap[a])
66                 scoreB = sum(self._hitMap[b])
67                 return scoreA - scoreB
68
69         def _checkHitFor(self, addIdx, others):
70                 for idx in others:
71                         if self._hitMap[addIdx][idx]:
72                                 return True
73                 return False
74
75         def _checkBlocks(self, addIdx, others):
76                 for idx in others:
77                         if addIdx != idx and self._hitMap[idx][addIdx]:
78                                 return True
79                 return False
80
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())
86
87 class Scene(object):
88         def __init__(self):
89                 self._objectList = []
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._extruderOffset = [numpy.array([0,0], numpy.float32)] * 4
94
95                 #Print order variables
96                 self._leftToRight = False
97                 self._frontToBack = True
98                 self._gantryHeight = 60
99                 self._oneAtATime = True
100
101         # Physical (square) machine size.
102         def setMachineSize(self, machineSize):
103                 self._machineSize = machineSize
104
105         # Size offsets are offsets caused by brim, skirt, etc.
106         def updateSizeOffsets(self, force=False):
107                 newOffsets = numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32)
108                 if not force and numpy.array_equal(self._sizeOffsets, newOffsets):
109                         return
110                 self._sizeOffsets = newOffsets
111
112                 extends = numpy.array([[-newOffsets[0],-newOffsets[1]],[ newOffsets[0],-newOffsets[1]],[ newOffsets[0], newOffsets[1]],[-newOffsets[0], newOffsets[1]]], numpy.float32)
113                 for obj in self._objectList:
114                         obj.setPrintAreaExtends(extends)
115
116         #size of the printing head.
117         def setHeadSize(self, xMin, xMax, yMin, yMax, gantryHeight):
118                 self._leftToRight = xMin < xMax
119                 self._frontToBack = yMin < yMax
120                 self._headSizeOffsets[0] = min(xMin, xMax)
121                 self._headSizeOffsets[1] = min(yMin, yMax)
122                 self._gantryHeight = gantryHeight
123                 self._oneAtATime = self._gantryHeight > 0
124
125                 headArea = numpy.array([[-xMin,-yMin],[ xMax,-yMin],[ xMax, yMax],[-xMin, yMax]], numpy.float32)
126                 for obj in self._objectList:
127                         obj.setHeadArea(headArea, self._headSizeOffsets)
128
129         def setExtruderOffset(self, extruderNr, offsetX, offsetY):
130                 self._extruderOffset[extruderNr] = numpy.array([offsetX, offsetY], numpy.float32)
131
132         def objects(self):
133                 return self._objectList
134
135         #Add new object to print area
136         def add(self, obj):
137                 self._findFreePositionFor(obj)
138                 self._objectList.append(obj)
139                 self.pushFree()
140                 if numpy.max(obj.getSize()[0:2]) > numpy.max(self._machineSize[0:2]) * 2.5:
141                         scale = numpy.max(self._machineSize[0:2]) * 2.5 / numpy.max(obj.getSize()[0:2])
142                         matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
143                         obj.applyMatrix(numpy.matrix(matrix, numpy.float64))
144                 self.updateSizeOffsets(True)
145
146         def remove(self, obj):
147                 self._objectList.remove(obj)
148
149         #Dual(multiple) extrusion merge
150         def merge(self, obj1, obj2):
151                 self.remove(obj2)
152                 obj1._meshList += obj2._meshList
153                 for m in obj2._meshList:
154                         m._obj = obj1
155                 obj1.processMatrix()
156                 obj1.setPosition((obj1.getPosition() + obj2.getPosition()) / 2)
157                 self.pushFree()
158
159         def pushFree(self):
160                 n = 1000
161                 while self._pushFree():
162                         n -= 1
163                         if n < 0:
164                                 return
165
166         def arrangeAll(self):
167                 oldList = self._objectList
168                 self._objectList = []
169                 for obj in oldList:
170                         obj.setPosition(numpy.array([0,0], numpy.float32))
171                         self.add(obj)
172
173         def centerAll(self):
174                 minPos = numpy.array([9999999,9999999], numpy.float32)
175                 maxPos = numpy.array([-9999999,-9999999], numpy.float32)
176                 for obj in self._objectList:
177                         pos = obj.getPosition()
178                         size = obj.getSize()
179                         minPos[0] = min(minPos[0], pos[0] - size[0] / 2)
180                         minPos[1] = min(minPos[1], pos[1] - size[1] / 2)
181                         maxPos[0] = max(maxPos[0], pos[0] + size[0] / 2)
182                         maxPos[1] = max(maxPos[1], pos[1] + size[1] / 2)
183                 offset = -(maxPos + minPos) / 2
184                 for obj in self._objectList:
185                         obj.setPosition(obj.getPosition() + offset)
186
187         def printOrder(self):
188                 if self._oneAtATime:
189                         order = _objectOrderFinder(self, self._leftToRight, self._frontToBack, self._gantryHeight).order
190                 else:
191                         order = None
192                 return order
193
194         def _pushFree(self):
195                 for a in self._objectList:
196                         for b in self._objectList:
197                                 if a == b:
198                                         continue
199                                 if self._oneAtATime:
200                                         v = polygon.polygonCollisionPushVector(a._headAreaMinHull + a.getPosition(), b._boundaryHull + b.getPosition())
201                                 else:
202                                         v = polygon.polygonCollisionPushVector(a._boundaryHull + a.getPosition(), b._boundaryHull + b.getPosition())
203                                 if type(v) is bool:
204                                         continue
205                                 a.setPosition(a.getPosition() + v / 2.0)
206                                 b.setPosition(b.getPosition() - v / 2.0)
207                                 return True
208                 return False
209
210         #Check if two objects are hitting each-other (+ head space).
211         def _checkHit(self, a, b):
212                 if a == b:
213                         return False
214                 if self._oneAtATime:
215                         return polygon.polygonCollision(a._headAreaMinHull + a.getPosition(), b._boundaryHull + b.getPosition())
216                 else:
217                         return polygon.polygonCollision(a._boundaryHull + a.getPosition(), b._boundaryHull + b.getPosition())
218
219         def checkPlatform(self, obj):
220                 p = obj.getPosition()
221                 s = obj.getSize()[0:2] / 2 + self._sizeOffsets
222                 offsetLeft = 0.0
223                 offsetRight = 0.0
224                 offsetBack = 0.0
225                 offsetFront = 0.0
226                 extruderCount = len(obj._meshList)
227                 if profile.getProfileSetting('support_dual_extrusion') == 'Second extruder' and profile.getProfileSetting('support') != 'None':
228                         extruderCount = max(extruderCount, 2)
229                 for n in xrange(1, extruderCount):
230                         if offsetLeft < self._extruderOffset[n][0]:
231                                 offsetLeft = self._extruderOffset[n][0]
232                         if offsetRight > self._extruderOffset[n][0]:
233                                 offsetRight = self._extruderOffset[n][0]
234                         if offsetFront < self._extruderOffset[n][1]:
235                                 offsetFront = self._extruderOffset[n][1]
236                         if offsetBack > self._extruderOffset[n][1]:
237                                 offsetBack = self._extruderOffset[n][1]
238                 boundLeft = -self._machineSize[0] / 2 + offsetLeft
239                 boundRight = self._machineSize[0] / 2 + offsetRight
240                 boundFront = -self._machineSize[1] / 2 + offsetFront
241                 boundBack = self._machineSize[1] / 2 + offsetBack
242                 if p[0] - s[0] < boundLeft:
243                         return False
244                 if p[0] + s[0] > boundRight:
245                         return False
246                 if p[1] - s[1] < boundFront:
247                         return False
248                 if p[1] + s[1] > boundBack:
249                         return False
250
251                 #Do clip Check for UM2.
252                 machine = profile.getMachineSetting('machine_type')
253                 if machine == "ultimaker2":
254                         #lowerRight clip check
255                         if p[0] - s[0] < boundLeft + 25 and p[1] - s[1] < boundFront + 10:
256                                 return False
257                         #UpperRight
258                         if p[0] - s[0] < boundLeft + 25 and p[1] + s[1] > boundBack - 10:
259                                 return False
260                         #LowerLeft
261                         if p[0] + s[0] > boundRight - 25 and p[1] - s[1] < boundFront + 10:
262                                 return False
263                         #UpperLeft
264                         if p[0] + s[0] > boundRight - 25 and p[1] + s[1] > boundBack - 10:
265                                 return False
266                 return True
267
268         def _findFreePositionFor(self, obj):
269                 posList = []
270                 for a in self._objectList:
271                         p = a.getPosition()
272                         s = (a.getSize()[0:2] + obj.getSize()[0:2]) / 2 + self._sizeOffsets + self._headSizeOffsets
273                         posList.append(p + s * ( 1.0, 1.0))
274                         posList.append(p + s * ( 0.0, 1.0))
275                         posList.append(p + s * (-1.0, 1.0))
276                         posList.append(p + s * ( 1.0, 0.0))
277                         posList.append(p + s * (-1.0, 0.0))
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
282                 best = None
283                 bestDist = None
284                 for p in posList:
285                         obj.setPosition(p)
286                         ok = True
287                         for a in self._objectList:
288                                 if self._checkHit(a, obj):
289                                         ok = False
290                                         break
291                         if not ok:
292                                 continue
293                         dist = numpy.linalg.norm(p)
294                         if not self.checkPlatform(obj):
295                                 dist *= 3
296                         if best is None or dist < bestDist:
297                                 best = p
298                                 bestDist = dist
299                 if best is not None:
300                         obj.setPosition(best)