chiark / gitweb /
Make sure the print fits on the bed with dual-extrusion-support.
[cura.git] / Cura / util / objectScene.py
index c2944e2580d4f33f6687af49b9b143d4d4e30269..b229a70923edc67a640ad0298f224c3754245b80 100644 (file)
@@ -1,15 +1,18 @@
+__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
 import random
 import numpy
 
+from Cura.util import profile
+from Cura.util import polygon
+
 class _objectOrder(object):
        def __init__(self, order, todo):
                self.order = order
                self.todo = todo
 
 class _objectOrderFinder(object):
-       def __init__(self, scene, offset, leftToRight, frontToBack, gantryHeight):
+       def __init__(self, scene, leftToRight, frontToBack, gantryHeight):
                self._scene = scene
-               self._offset = offset - numpy.array([0.1,0.1])
                self._objs = scene.objects()
                self._leftToRight = leftToRight
                self._frontToBack = frontToBack
@@ -17,14 +20,37 @@ class _objectOrderFinder(object):
                for n in xrange(0, len(self._objs)):
                        if scene.checkPlatform(self._objs[n]):
                                initialList.append(n)
+               for n in initialList:
+                       if self._objs[n].getSize()[2] > gantryHeight and len(initialList) > 1:
+                               self.order = None
+                               return
                if len(initialList) == 0:
                        self.order = []
                        return
+
+               self._hitMap = [None] * (max(initialList)+1)
+               for a in initialList:
+                       self._hitMap[a] = [False] * (max(initialList)+1)
+                       for b in initialList:
+                               self._hitMap[a][b] = self._checkHit(a, b)
+
+               #Check if we have 2 files that overlap so that they can never be printed one at a time.
+               for a in initialList:
+                       for b in initialList:
+                               if a != b and self._hitMap[a][b] and self._hitMap[b][a]:
+                                       self.order = None
+                                       return
+
+               initialList.sort(self._objIdxCmp)
+
+               n = 0
                self._todo = [_objectOrder([], initialList)]
                while len(self._todo) > 0:
+                       n += 1
                        current = self._todo.pop()
+                       #print len(self._todo), len(current.order), len(initialList), current.order
                        for addIdx in current.todo:
-                               if not self._checkHitFor(addIdx, current.order):
+                               if not self._checkHitFor(addIdx, current.order) and not self._checkBlocks(addIdx, current.todo):
                                        todoList = current.todo[:]
                                        todoList.remove(addIdx)
                                        order = current.order[:] + [addIdx]
@@ -35,80 +61,115 @@ class _objectOrderFinder(object):
                                        self._todo.append(_objectOrder(order, todoList))
                self.order = None
 
+       def _objIdxCmp(self, a, b):
+               scoreA = sum(self._hitMap[a])
+               scoreB = sum(self._hitMap[b])
+               return scoreA - scoreB
+
        def _checkHitFor(self, addIdx, others):
                for idx in others:
-                       if self._checkHit(addIdx, idx):
+                       if self._hitMap[addIdx][idx]:
                                return True
                return False
 
-       def _checkHit(self, addIdx, idx):
-               addPos = self._scene._objectList[addIdx].getPosition()
-               addSize = self._scene._objectList[addIdx].getSize()
-               pos = self._scene._objectList[idx].getPosition()
-               size = self._scene._objectList[idx].getSize()
-
-               if self._leftToRight:
-                       if addPos[0] - addSize[0] / 2 - self._offset[0] <= pos[0] + size[0] / 2:
-                               return False
-               else:
-                       if addPos[0] + addSize[0] / 2 + self._offset[0] <= pos[0] - size[0] / 2:
-                               return False
-
-               if self._frontToBack:
-                       if addPos[1] - addSize[1] / 2 - self._offset[1] >= pos[1] + size[1] / 2:
-                               return False
-               else:
-                       if addPos[1] + addSize[1] / 2 + self._offset[1] >= pos[1] - size[1] / 2:
-                               return False
+       def _checkBlocks(self, addIdx, others):
+               for idx in others:
+                       if addIdx != idx and self._hitMap[idx][addIdx]:
+                               return True
+               return False
 
-               return True
+       #Check if printing one object will cause printhead colission with other object.
+       def _checkHit(self, addIdx, idx):
+               obj = self._scene._objectList[idx]
+               addObj = self._scene._objectList[addIdx]
+               return polygon.polygonCollision(obj._boundaryHull + obj.getPosition(), addObj._headAreaHull + addObj.getPosition())
 
 class Scene(object):
        def __init__(self):
                self._objectList = []
                self._sizeOffsets = numpy.array([0.0,0.0], numpy.float32)
                self._machineSize = numpy.array([100,100,100], numpy.float32)
-               self._headOffsets = numpy.array([18.0,18.0], numpy.float32)
+               self._headSizeOffsets = numpy.array([18.0,18.0], numpy.float32)
+               self._minExtruderCount = None
+               self._extruderOffset = [numpy.array([0,0], numpy.float32)] * 4
+
+               #Print order variables
                self._leftToRight = False
                self._frontToBack = True
                self._gantryHeight = 60
+               self._oneAtATime = True
+
+       # update the physical machine dimensions
+       def updateMachineDimensions(self):
+               self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
+               self._machinePolygons = profile.getMachineSizePolygons()
+               self.setHeadSize(profile.getMachineSettingFloat('extruder_head_size_min_x'), profile.getMachineSettingFloat('extruder_head_size_max_x'), profile.getMachineSettingFloat('extruder_head_size_min_y'), profile.getMachineSettingFloat('extruder_head_size_max_y'), profile.getMachineSettingFloat('extruder_head_size_height'))
+
+       # Size offsets are offsets caused by brim, skirt, etc.
+       def updateSizeOffsets(self, force=False):
+               newOffsets = numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32)
+               minExtruderCount = profile.minimalExtruderCount()
+               if not force and numpy.array_equal(self._sizeOffsets, newOffsets) and self._minExtruderCount == minExtruderCount:
+                       return
+               self._sizeOffsets = newOffsets
+               self._minExtruderCount = minExtruderCount
 
-       def setMachineSize(self, machineSize):
-               self._machineSize = machineSize
+               extends = [numpy.array([[-newOffsets[0],-newOffsets[1]],[ newOffsets[0],-newOffsets[1]],[ newOffsets[0], newOffsets[1]],[-newOffsets[0], newOffsets[1]]], numpy.float32)]
+               for n in xrange(1, 4):
+                       headOffset = numpy.array([[0, 0], [-profile.getMachineSettingFloat('extruder_offset_x%d' % (n)), -profile.getMachineSettingFloat('extruder_offset_y%d' % (n))]], numpy.float32)
+                       extends.append(polygon.minkowskiHull(extends[n-1], headOffset))
+               if minExtruderCount > 1:
+                       extends[0] = extends[1]
 
-       def setSizeOffsets(self, sizeOffsets):
-               self._sizeOffsets = sizeOffsets
+               for obj in self._objectList:
+                       obj.setPrintAreaExtends(extends[len(obj._meshList) - 1])
 
+       #size of the printing head.
        def setHeadSize(self, xMin, xMax, yMin, yMax, gantryHeight):
                self._leftToRight = xMin < xMax
                self._frontToBack = yMin < yMax
-               self._headOffsets[0] = min(xMin, xMax)
-               self._headOffsets[1] = min(yMin, yMax)
+               self._headSizeOffsets[0] = min(xMin, xMax)
+               self._headSizeOffsets[1] = min(yMin, yMax)
                self._gantryHeight = gantryHeight
+               self._oneAtATime = self._gantryHeight > 0
+
+               headArea = numpy.array([[-xMin,-yMin],[ xMax,-yMin],[ xMax, yMax],[-xMin, yMax]], numpy.float32)
+
+               for obj in self._objectList:
+                       obj.setHeadArea(headArea, self._headSizeOffsets)
 
-       def getObjectExtend(self):
-               return self._sizeOffsets + self._headOffsets
+       def setExtruderOffset(self, extruderNr, offsetX, offsetY):
+               self._extruderOffset[extruderNr] = numpy.array([offsetX, offsetY], numpy.float32)
 
        def objects(self):
                return self._objectList
 
+       #Add new object to print area
        def add(self, obj):
+               if numpy.max(obj.getSize()[0:2]) > numpy.max(self._machineSize[0:2]) * 2.5:
+                       scale = numpy.max(self._machineSize[0:2]) * 2.5 / numpy.max(obj.getSize()[0:2])
+                       matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
+                       obj.applyMatrix(numpy.matrix(matrix, numpy.float64))
                self._findFreePositionFor(obj)
                self._objectList.append(obj)
+               self.updateSizeOffsets(True)
                self.pushFree()
 
        def remove(self, obj):
                self._objectList.remove(obj)
 
+       #Dual(multiple) extrusion merge
        def merge(self, obj1, obj2):
                self.remove(obj2)
                obj1._meshList += obj2._meshList
+               for m in obj2._meshList:
+                       m._obj = obj1
                obj1.processMatrix()
                obj1.setPosition((obj1.getPosition() + obj2.getPosition()) / 2)
                self.pushFree()
 
        def pushFree(self):
-               n = 1000
+               n = 10
                while self._pushFree():
                        n -= 1
                        if n < 0:
@@ -136,65 +197,55 @@ class Scene(object):
                        obj.setPosition(obj.getPosition() + offset)
 
        def printOrder(self):
-               order = _objectOrderFinder(self, self._headOffsets + self._sizeOffsets, self._leftToRight, self._frontToBack, self._gantryHeight).order
-               if order is None:
-                       print "ODD! Cannot find out proper printing order!!!"
-                       for obj in self._objectList:
-                               print obj.getPosition(), obj.getSize()
+               if self._oneAtATime:
+                       order = _objectOrderFinder(self, self._leftToRight, self._frontToBack, self._gantryHeight).order
+               else:
+                       order = None
                return order
 
        def _pushFree(self):
                for a in self._objectList:
                        for b in self._objectList:
-                               if not self._checkHit(a, b):
+                               if a == b or not self.checkPlatform(a) or not self.checkPlatform(b):
                                        continue
-                               posDiff = a.getPosition() - b.getPosition()
-                               if posDiff[0] == 0.0 and posDiff[1] == 0.0:
-                                       posDiff[1] = 1.0
-                               if abs(posDiff[0]) > abs(posDiff[1]):
-                                       axis = 0
+                               if self._oneAtATime:
+                                       v = polygon.polygonCollisionPushVector(a._headAreaMinHull + a.getPosition(), b._boundaryHull + b.getPosition())
                                else:
-                                       axis = 1
-                               aPos = a.getPosition()
-                               bPos = b.getPosition()
-                               center = (aPos[axis] + bPos[axis]) / 2
-                               distance = (a.getSize()[axis] + b.getSize()[axis]) / 2 + 0.1 + self._sizeOffsets[axis] + self._headOffsets[axis]
-                               if posDiff[axis] < 0:
-                                       distance = -distance
-                               aPos[axis] = center + distance / 2
-                               bPos[axis] = center - distance / 2
-                               a.setPosition(aPos)
-                               b.setPosition(bPos)
+                                       v = polygon.polygonCollisionPushVector(a._boundaryHull + a.getPosition(), b._boundaryHull + b.getPosition())
+                               if type(v) is bool:
+                                       continue
+                               a.setPosition(a.getPosition() + v * 0.4)
+                               b.setPosition(b.getPosition() - v * 0.6)
                                return True
                return False
 
+       #Check if two objects are hitting each-other (+ head space).
        def _checkHit(self, a, b):
                if a == b:
                        return False
-               posDiff = a.getPosition() - b.getPosition()
-               if abs(posDiff[0]) < (a.getSize()[0] + b.getSize()[0]) / 2 + self._sizeOffsets[0] + self._headOffsets[0]:
-                       if abs(posDiff[1]) < (a.getSize()[1] + b.getSize()[1]) / 2 + self._sizeOffsets[1] + self._headOffsets[1]:
-                               return True
-               return False
+               if self._oneAtATime:
+                       return polygon.polygonCollision(a._headAreaMinHull + a.getPosition(), b._boundaryHull + b.getPosition())
+               else:
+                       return polygon.polygonCollision(a._boundaryHull + a.getPosition(), b._boundaryHull + b.getPosition())
 
        def checkPlatform(self, obj):
-               p = obj.getPosition()
-               s = obj.getSize()[0:2] / 2 + self._sizeOffsets
-               if p[0] - s[0] < -self._machineSize[0] / 2:
-                       return False
-               if p[0] + s[0] > self._machineSize[0] / 2:
-                       return False
-               if p[1] - s[1] < -self._machineSize[1] / 2:
-                       return False
-               if p[1] + s[1] > self._machineSize[1] / 2:
+               area = obj._printAreaHull + obj.getPosition()
+               if not polygon.fullInside(area, self._machinePolygons[0]):
                        return False
+               #Check the "no go zones"
+               for poly in self._machinePolygons[1:]:
+                       if polygon.polygonCollision(poly, area):
+                               return False
                return True
 
        def _findFreePositionFor(self, obj):
                posList = []
                for a in self._objectList:
                        p = a.getPosition()
-                       s = (a.getSize()[0:2] + obj.getSize()[0:2]) / 2 + self._sizeOffsets + self._headOffsets
+                       if self._oneAtATime:
+                               s = (a.getSize()[0:2] + obj.getSize()[0:2]) / 2 + self._sizeOffsets + self._headSizeOffsets + numpy.array([3,3], numpy.float32)
+                       else:
+                               s = (a.getSize()[0:2] + obj.getSize()[0:2]) / 2 + numpy.array([3,3], numpy.float32)
                        posList.append(p + s * ( 1.0, 1.0))
                        posList.append(p + s * ( 0.0, 1.0))
                        posList.append(p + s * (-1.0, 1.0))