forked from LeenkxTeam/LNXSDK
		
	
		
			
	
	
		
			581 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			581 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								from .maxrects import MaxRectsBssf
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import operator
							 | 
						||
| 
								 | 
							
								import itertools
							 | 
						||
| 
								 | 
							
								import collections
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import decimal
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# Float to Decimal helper
							 | 
						||
| 
								 | 
							
								def float2dec(ft, decimal_digits):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Convert float (or int) to Decimal (rounding up) with the
							 | 
						||
| 
								 | 
							
								    requested number of decimal digits.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Arguments:
							 | 
						||
| 
								 | 
							
								        ft (float, int): Number to convert
							 | 
						||
| 
								 | 
							
								        decimal (int): Number of digits after decimal point
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Return:
							 | 
						||
| 
								 | 
							
								        Decimal: Number converted to decima
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    with decimal.localcontext() as ctx:
							 | 
						||
| 
								 | 
							
								        ctx.rounding = decimal.ROUND_UP
							 | 
						||
| 
								 | 
							
								        places = decimal.Decimal(10)**(-decimal_digits)
							 | 
						||
| 
								 | 
							
								        return decimal.Decimal.from_float(float(ft)).quantize(places)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# Sorting algos for rectangle lists
							 | 
						||
| 
								 | 
							
								SORT_AREA  = lambda rectlist: sorted(rectlist, reverse=True, 
							 | 
						||
| 
								 | 
							
								        key=lambda r: r[0]*r[1]) # Sort by area
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SORT_PERI  = lambda rectlist: sorted(rectlist, reverse=True, 
							 | 
						||
| 
								 | 
							
								        key=lambda r: r[0]+r[1]) # Sort by perimeter
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SORT_DIFF  = lambda rectlist: sorted(rectlist, reverse=True, 
							 | 
						||
| 
								 | 
							
								        key=lambda r: abs(r[0]-r[1])) # Sort by Diff
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SORT_SSIDE = lambda rectlist: sorted(rectlist, reverse=True, 
							 | 
						||
| 
								 | 
							
								        key=lambda r: (min(r[0], r[1]), max(r[0], r[1]))) # Sort by short side
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SORT_LSIDE = lambda rectlist: sorted(rectlist, reverse=True, 
							 | 
						||
| 
								 | 
							
								        key=lambda r: (max(r[0], r[1]), min(r[0], r[1]))) # Sort by long side
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SORT_RATIO = lambda rectlist: sorted(rectlist, reverse=True,
							 | 
						||
| 
								 | 
							
								        key=lambda r: r[0]/r[1]) # Sort by side ratio
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SORT_NONE = lambda rectlist: list(rectlist) # Unsorted
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class BinFactory(object):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, width, height, count, pack_algo, *args, **kwargs):
							 | 
						||
| 
								 | 
							
								        self._width = width
							 | 
						||
| 
								 | 
							
								        self._height = height
							 | 
						||
| 
								 | 
							
								        self._count = count
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        self._pack_algo = pack_algo
							 | 
						||
| 
								 | 
							
								        self._algo_kwargs = kwargs
							 | 
						||
| 
								 | 
							
								        self._algo_args = args
							 | 
						||
| 
								 | 
							
								        self._ref_bin = None # Reference bin used to calculate fitness
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        self._bid = kwargs.get("bid", None)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _create_bin(self):
							 | 
						||
| 
								 | 
							
								        return self._pack_algo(self._width, self._height, *self._algo_args, **self._algo_kwargs)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def is_empty(self):
							 | 
						||
| 
								 | 
							
								        return self._count<1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def fitness(self, width, height):
							 | 
						||
| 
								 | 
							
								        if not self._ref_bin:
							 | 
						||
| 
								 | 
							
								            self._ref_bin = self._create_bin()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return self._ref_bin.fitness(width, height)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def fits_inside(self, width, height):
							 | 
						||
| 
								 | 
							
								        # Determine if rectangle widthxheight will fit into empty bin
							 | 
						||
| 
								 | 
							
								        if not self._ref_bin:
							 | 
						||
| 
								 | 
							
								            self._ref_bin = self._create_bin()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return self._ref_bin._fits_surface(width, height)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def new_bin(self):
							 | 
						||
| 
								 | 
							
								        if self._count > 0:
							 | 
						||
| 
								 | 
							
								            self._count -= 1
							 | 
						||
| 
								 | 
							
								            return self._create_bin()
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __eq__(self, other):
							 | 
						||
| 
								 | 
							
								        return self._width*self._height == other._width*other._height
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __lt__(self, other):
							 | 
						||
| 
								 | 
							
								        return self._width*self._height < other._width*other._height
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __str__(self):
							 | 
						||
| 
								 | 
							
								        return "Bin: {} {} {}".format(self._width, self._height, self._count)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class PackerBNFMixin(object):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    BNF (Bin Next Fit): Only one open bin at a time.  If the rectangle
							 | 
						||
| 
								 | 
							
								    doesn't fit, close the current bin and go to the next.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def add_rect(self, width, height, rid=None):
							 | 
						||
| 
								 | 
							
								        while True:
							 | 
						||
| 
								 | 
							
								            # if there are no open bins, try to open a new one
							 | 
						||
| 
								 | 
							
								            if len(self._open_bins)==0:
							 | 
						||
| 
								 | 
							
								                # can we find an unopened bin that will hold this rect?
							 | 
						||
| 
								 | 
							
								                new_bin = self._new_open_bin(width, height, rid=rid)
							 | 
						||
| 
								 | 
							
								                if new_bin is None:
							 | 
						||
| 
								 | 
							
								                    return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # we have at least one open bin, so check if it can hold this rect
							 | 
						||
| 
								 | 
							
								            rect = self._open_bins[0].add_rect(width, height, rid=rid)
							 | 
						||
| 
								 | 
							
								            if rect is not None:
							 | 
						||
| 
								 | 
							
								                return rect
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # since the rect doesn't fit, close this bin and try again
							 | 
						||
| 
								 | 
							
								            closed_bin = self._open_bins.popleft()
							 | 
						||
| 
								 | 
							
								            self._closed_bins.append(closed_bin)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class PackerBFFMixin(object):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    BFF (Bin First Fit): Pack rectangle in first bin it fits
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								 
							 | 
						||
| 
								 | 
							
								    def add_rect(self, width, height, rid=None):
							 | 
						||
| 
								 | 
							
								        # see if this rect will fit in any of the open bins
							 | 
						||
| 
								 | 
							
								        for b in self._open_bins:
							 | 
						||
| 
								 | 
							
								            rect = b.add_rect(width, height, rid=rid)
							 | 
						||
| 
								 | 
							
								            if rect is not None:
							 | 
						||
| 
								 | 
							
								                return rect
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        while True:
							 | 
						||
| 
								 | 
							
								            # can we find an unopened bin that will hold this rect?
							 | 
						||
| 
								 | 
							
								            new_bin = self._new_open_bin(width, height, rid=rid)
							 | 
						||
| 
								 | 
							
								            if new_bin is None:
							 | 
						||
| 
								 | 
							
								                return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # _new_open_bin may return a bin that's too small,
							 | 
						||
| 
								 | 
							
								            # so we have to double-check
							 | 
						||
| 
								 | 
							
								            rect = new_bin.add_rect(width, height, rid=rid)
							 | 
						||
| 
								 | 
							
								            if rect is not None:
							 | 
						||
| 
								 | 
							
								                return rect
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class PackerBBFMixin(object):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    BBF (Bin Best Fit): Pack rectangle in bin that gives best fitness
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # only create this getter once
							 | 
						||
| 
								 | 
							
								    first_item = operator.itemgetter(0)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def add_rect(self, width, height, rid=None):
							 | 
						||
| 
								 | 
							
								 
							 | 
						||
| 
								 | 
							
								        # Try packing into open bins
							 | 
						||
| 
								 | 
							
								        fit = ((b.fitness(width, height),  b) for b in self._open_bins)
							 | 
						||
| 
								 | 
							
								        fit = (b for b in fit if b[0] is not None)
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            _, best_bin = min(fit, key=self.first_item)
							 | 
						||
| 
								 | 
							
								            best_bin.add_rect(width, height, rid)
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								        except ValueError:
							 | 
						||
| 
								 | 
							
								            pass    
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Try packing into one of the empty bins
							 | 
						||
| 
								 | 
							
								        while True:
							 | 
						||
| 
								 | 
							
								            # can we find an unopened bin that will hold this rect?
							 | 
						||
| 
								 | 
							
								            new_bin = self._new_open_bin(width, height, rid=rid)
							 | 
						||
| 
								 | 
							
								            if new_bin is None:
							 | 
						||
| 
								 | 
							
								                return False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # _new_open_bin may return a bin that's too small,
							 | 
						||
| 
								 | 
							
								            # so we have to double-check
							 | 
						||
| 
								 | 
							
								            if new_bin.add_rect(width, height, rid):
							 | 
						||
| 
								 | 
							
								                return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class PackerOnline(object):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Rectangles are packed as soon are they are added
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, pack_algo=MaxRectsBssf, rotation=True):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            pack_algo (PackingAlgorithm): What packing algo to use
							 | 
						||
| 
								 | 
							
								            rotation (bool): Enable/Disable rectangle rotation
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        self._rotation = rotation
							 | 
						||
| 
								 | 
							
								        self._pack_algo = pack_algo
							 | 
						||
| 
								 | 
							
								        self.reset()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __iter__(self):
							 | 
						||
| 
								 | 
							
								        return itertools.chain(self._closed_bins, self._open_bins)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __len__(self):
							 | 
						||
| 
								 | 
							
								        return len(self._closed_bins)+len(self._open_bins)
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    def __getitem__(self, key):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Return bin in selected position. (excluding empty bins)
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if not isinstance(key, int):
							 | 
						||
| 
								 | 
							
								            raise TypeError("Indices must be integers")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        size = len(self)  # avoid recalulations
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if key < 0:
							 | 
						||
| 
								 | 
							
								            key += size
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if not 0 <= key < size:
							 | 
						||
| 
								 | 
							
								            raise IndexError("Index out of range")
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        if key < len(self._closed_bins):
							 | 
						||
| 
								 | 
							
								            return self._closed_bins[key]
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return self._open_bins[key-len(self._closed_bins)]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _new_open_bin(self, width=None, height=None, rid=None):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Extract the next empty bin and append it to open bins
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Returns:
							 | 
						||
| 
								 | 
							
								            PackingAlgorithm: Initialized empty packing bin.
							 | 
						||
| 
								 | 
							
								            None: No bin big enough for the rectangle was found
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        factories_to_delete = set() #
							 | 
						||
| 
								 | 
							
								        new_bin = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for key, binfac in self._empty_bins.items():
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # Only return the new bin if the rect fits.
							 | 
						||
| 
								 | 
							
								            # (If width or height is None, caller doesn't know the size.)
							 | 
						||
| 
								 | 
							
								            if not binfac.fits_inside(width, height):
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								           
							 | 
						||
| 
								 | 
							
								            # Create bin and add to open_bins
							 | 
						||
| 
								 | 
							
								            new_bin = binfac.new_bin()
							 | 
						||
| 
								 | 
							
								            if new_bin is None:
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								            self._open_bins.append(new_bin)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # If the factory was depleted mark for deletion
							 | 
						||
| 
								 | 
							
								            if binfac.is_empty():
							 | 
						||
| 
								 | 
							
								                factories_to_delete.add(key)
							 | 
						||
| 
								 | 
							
								       
							 | 
						||
| 
								 | 
							
								            break
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Delete marked factories
							 | 
						||
| 
								 | 
							
								        for f in factories_to_delete:
							 | 
						||
| 
								 | 
							
								            del self._empty_bins[f]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return new_bin 
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def add_bin(self, width, height, count=1, **kwargs):
							 | 
						||
| 
								 | 
							
								        # accept the same parameters as PackingAlgorithm objects
							 | 
						||
| 
								 | 
							
								        kwargs['rot'] = self._rotation
							 | 
						||
| 
								 | 
							
								        bin_factory = BinFactory(width, height, count, self._pack_algo, **kwargs)
							 | 
						||
| 
								 | 
							
								        self._empty_bins[next(self._bin_count)] = bin_factory
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def rect_list(self):
							 | 
						||
| 
								 | 
							
								        rectangles = []
							 | 
						||
| 
								 | 
							
								        bin_count = 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for abin in self:
							 | 
						||
| 
								 | 
							
								            for rect in abin:
							 | 
						||
| 
								 | 
							
								                rectangles.append((bin_count, rect.x, rect.y, rect.width, rect.height, rect.rid))
							 | 
						||
| 
								 | 
							
								            bin_count += 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return rectangles
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def bin_list(self):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Return a list of the dimmensions of the bins in use, that is closed
							 | 
						||
| 
								 | 
							
								        or open containing at least one rectangle
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return [(b.width, b.height) for b in self]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def validate_packing(self):
							 | 
						||
| 
								 | 
							
								        for b in self:
							 | 
						||
| 
								 | 
							
								            b.validate_packing()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def reset(self): 
							 | 
						||
| 
								 | 
							
								        # Bins fully packed and closed.
							 | 
						||
| 
								 | 
							
								        self._closed_bins = collections.deque()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Bins ready to pack rectangles
							 | 
						||
| 
								 | 
							
								        self._open_bins = collections.deque()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # User provided bins not in current use
							 | 
						||
| 
								 | 
							
								        self._empty_bins = collections.OrderedDict() # O(1) deletion of arbitrary elem
							 | 
						||
| 
								 | 
							
								        self._bin_count = itertools.count()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Packer(PackerOnline):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Rectangles aren't packed untils pack() is called
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, pack_algo=MaxRectsBssf, sort_algo=SORT_NONE, 
							 | 
						||
| 
								 | 
							
								            rotation=True):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        super(Packer, self).__init__(pack_algo=pack_algo, rotation=rotation)
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        self._sort_algo = sort_algo
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # User provided bins and Rectangles
							 | 
						||
| 
								 | 
							
								        self._avail_bins = collections.deque()
							 | 
						||
| 
								 | 
							
								        self._avail_rect = collections.deque()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Aux vars used during packing
							 | 
						||
| 
								 | 
							
								        self._sorted_rect = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def add_bin(self, width, height, count=1, **kwargs):
							 | 
						||
| 
								 | 
							
								        self._avail_bins.append((width, height, count, kwargs))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def add_rect(self, width, height, rid=None):
							 | 
						||
| 
								 | 
							
								        self._avail_rect.append((width, height, rid))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _is_everything_ready(self):
							 | 
						||
| 
								 | 
							
								        return self._avail_rect and self._avail_bins
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def pack(self):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.reset()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if not self._is_everything_ready():
							 | 
						||
| 
								 | 
							
								            # maybe we should throw an error here?
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Add available bins to packer
							 | 
						||
| 
								 | 
							
								        for b in self._avail_bins:
							 | 
						||
| 
								 | 
							
								            width, height, count, extra_kwargs = b
							 | 
						||
| 
								 | 
							
								            super(Packer, self).add_bin(width, height, count, **extra_kwargs)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # If enabled sort rectangles
							 | 
						||
| 
								 | 
							
								        self._sorted_rect = self._sort_algo(self._avail_rect)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Start packing
							 | 
						||
| 
								 | 
							
								        for r in self._sorted_rect:
							 | 
						||
| 
								 | 
							
								            super(Packer, self).add_rect(*r)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								 
							 | 
						||
| 
								 | 
							
								class PackerBNF(Packer, PackerBNFMixin):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    BNF (Bin Next Fit): Only one open bin, if rectangle doesn't fit
							 | 
						||
| 
								 | 
							
								    go to next bin and close current one.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class PackerBFF(Packer, PackerBFFMixin):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    BFF (Bin First Fit): Pack rectangle in first bin it fits
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								class PackerBBF(Packer, PackerBBFMixin):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    BBF (Bin Best Fit): Pack rectangle in bin that gives best fitness
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    pass 
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class PackerOnlineBNF(PackerOnline, PackerBNFMixin):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    BNF Bin Next Fit Online variant
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    pass 
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class PackerOnlineBFF(PackerOnline, PackerBFFMixin):
							 | 
						||
| 
								 | 
							
								    """ 
							 | 
						||
| 
								 | 
							
								    BFF Bin First Fit Online variant
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class PackerOnlineBBF(PackerOnline, PackerBBFMixin):
							 | 
						||
| 
								 | 
							
								    """ 
							 | 
						||
| 
								 | 
							
								    BBF Bin Best Fit Online variant
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class PackerGlobal(Packer, PackerBNFMixin):
							 | 
						||
| 
								 | 
							
								    """ 
							 | 
						||
| 
								 | 
							
								    GLOBAL: For each bin pack the rectangle with the best fitness.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    first_item = operator.itemgetter(0)
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    def __init__(self, pack_algo=MaxRectsBssf, rotation=True):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        super(PackerGlobal, self).__init__(pack_algo=pack_algo,
							 | 
						||
| 
								 | 
							
								            sort_algo=SORT_NONE, rotation=rotation)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _find_best_fit(self, pbin):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Return best fitness rectangle from rectangles packing _sorted_rect list
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            pbin (PackingAlgorithm): Packing bin
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Returns:
							 | 
						||
| 
								 | 
							
								            key of the rectangle with best fitness
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        fit = ((pbin.fitness(r[0], r[1]), k) for k, r in self._sorted_rect.items())
							 | 
						||
| 
								 | 
							
								        fit = (f for f in fit if f[0] is not None)
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            _, rect = min(fit, key=self.first_item)
							 | 
						||
| 
								 | 
							
								            return rect
							 | 
						||
| 
								 | 
							
								        except ValueError:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _new_open_bin(self, remaining_rect):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Extract the next bin where at least one of the rectangles in
							 | 
						||
| 
								 | 
							
								        rem
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            remaining_rect (dict): rectangles not placed yet
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Returns:
							 | 
						||
| 
								 | 
							
								            PackingAlgorithm: Initialized empty packing bin.
							 | 
						||
| 
								 | 
							
								            None: No bin big enough for the rectangle was found
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        factories_to_delete = set() #
							 | 
						||
| 
								 | 
							
								        new_bin = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for key, binfac in self._empty_bins.items():
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # Only return the new bin if at least one of the remaining 
							 | 
						||
| 
								 | 
							
								            # rectangles fit inside.
							 | 
						||
| 
								 | 
							
								            a_rectangle_fits = False
							 | 
						||
| 
								 | 
							
								            for _, rect in remaining_rect.items():
							 | 
						||
| 
								 | 
							
								                if binfac.fits_inside(rect[0], rect[1]):
							 | 
						||
| 
								 | 
							
								                    a_rectangle_fits = True
							 | 
						||
| 
								 | 
							
								                    break
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if not a_rectangle_fits:
							 | 
						||
| 
								 | 
							
								                factories_to_delete.add(key)
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								           
							 | 
						||
| 
								 | 
							
								            # Create bin and add to open_bins
							 | 
						||
| 
								 | 
							
								            new_bin = binfac.new_bin()
							 | 
						||
| 
								 | 
							
								            if new_bin is None:
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								            self._open_bins.append(new_bin)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # If the factory was depleted mark for deletion
							 | 
						||
| 
								 | 
							
								            if binfac.is_empty():
							 | 
						||
| 
								 | 
							
								                factories_to_delete.add(key)
							 | 
						||
| 
								 | 
							
								       
							 | 
						||
| 
								 | 
							
								            break
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Delete marked factories
							 | 
						||
| 
								 | 
							
								        for f in factories_to_delete:
							 | 
						||
| 
								 | 
							
								            del self._empty_bins[f]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return new_bin 
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def pack(self):
							 | 
						||
| 
								 | 
							
								       
							 | 
						||
| 
								 | 
							
								        self.reset()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if not self._is_everything_ready():
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        # Add available bins to packer
							 | 
						||
| 
								 | 
							
								        for b in self._avail_bins:
							 | 
						||
| 
								 | 
							
								            width, height, count, extra_kwargs = b
							 | 
						||
| 
								 | 
							
								            super(Packer, self).add_bin(width, height, count, **extra_kwargs)
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								        # Store rectangles into dict for fast deletion
							 | 
						||
| 
								 | 
							
								        self._sorted_rect = collections.OrderedDict(
							 | 
						||
| 
								 | 
							
								                enumerate(self._sort_algo(self._avail_rect)))
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        # For each bin pack the rectangles with lowest fitness until it is filled or
							 | 
						||
| 
								 | 
							
								        # the rectangles exhausted, then open the next bin where at least one rectangle 
							 | 
						||
| 
								 | 
							
								        # will fit and repeat the process until there aren't more rectangles or bins 
							 | 
						||
| 
								 | 
							
								        # available.
							 | 
						||
| 
								 | 
							
								        while len(self._sorted_rect) > 0:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # Find one bin where at least one of the remaining rectangles fit
							 | 
						||
| 
								 | 
							
								            pbin = self._new_open_bin(self._sorted_rect)
							 | 
						||
| 
								 | 
							
								            if pbin is None:
							 | 
						||
| 
								 | 
							
								                break
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # Pack as many rectangles as possible into the open bin
							 | 
						||
| 
								 | 
							
								            while True:
							 | 
						||
| 
								 | 
							
								              
							 | 
						||
| 
								 | 
							
								                # Find 'fittest' rectangle
							 | 
						||
| 
								 | 
							
								                best_rect_key = self._find_best_fit(pbin)
							 | 
						||
| 
								 | 
							
								                if best_rect_key is None:
							 | 
						||
| 
								 | 
							
								                    closed_bin = self._open_bins.popleft()
							 | 
						||
| 
								 | 
							
								                    self._closed_bins.append(closed_bin)
							 | 
						||
| 
								 | 
							
								                    break # None of the remaining rectangles can be packed in this bin
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                best_rect = self._sorted_rect[best_rect_key]
							 | 
						||
| 
								 | 
							
								                del self._sorted_rect[best_rect_key]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                PackerBNFMixin.add_rect(self, *best_rect)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# Packer factory
							 | 
						||
| 
								 | 
							
								class Enum(tuple): 
							 | 
						||
| 
								 | 
							
								    __getattr__ = tuple.index
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								PackingMode = Enum(["Online", "Offline"])
							 | 
						||
| 
								 | 
							
								PackingBin = Enum(["BNF", "BFF", "BBF", "Global"])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def newPacker(mode=PackingMode.Offline, 
							 | 
						||
| 
								 | 
							
								         bin_algo=PackingBin.BBF, 
							 | 
						||
| 
								 | 
							
								        pack_algo=MaxRectsBssf,
							 | 
						||
| 
								 | 
							
								        sort_algo=SORT_AREA, 
							 | 
						||
| 
								 | 
							
								        rotation=True):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Packer factory helper function
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Arguments:
							 | 
						||
| 
								 | 
							
								        mode (PackingMode): Packing mode
							 | 
						||
| 
								 | 
							
								            Online: Rectangles are packed as soon are they are added
							 | 
						||
| 
								 | 
							
								            Offline: Rectangles aren't packed untils pack() is called
							 | 
						||
| 
								 | 
							
								        bin_algo (PackingBin): Bin selection heuristic
							 | 
						||
| 
								 | 
							
								        pack_algo (PackingAlgorithm): Algorithm used
							 | 
						||
| 
								 | 
							
								        rotation (boolean): Enable or disable rectangle rotation. 
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Returns:
							 | 
						||
| 
								 | 
							
								        Packer: Initialized packer instance.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    packer_class = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Online Mode
							 | 
						||
| 
								 | 
							
								    if mode == PackingMode.Online:
							 | 
						||
| 
								 | 
							
								        sort_algo=None
							 | 
						||
| 
								 | 
							
								        if bin_algo == PackingBin.BNF:
							 | 
						||
| 
								 | 
							
								            packer_class = PackerOnlineBNF
							 | 
						||
| 
								 | 
							
								        elif bin_algo == PackingBin.BFF:
							 | 
						||
| 
								 | 
							
								            packer_class = PackerOnlineBFF
							 | 
						||
| 
								 | 
							
								        elif bin_algo == PackingBin.BBF:
							 | 
						||
| 
								 | 
							
								            packer_class = PackerOnlineBBF
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            raise AttributeError("Unsupported bin selection heuristic")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Offline Mode
							 | 
						||
| 
								 | 
							
								    elif mode == PackingMode.Offline:
							 | 
						||
| 
								 | 
							
								        if bin_algo == PackingBin.BNF:
							 | 
						||
| 
								 | 
							
								            packer_class = PackerBNF
							 | 
						||
| 
								 | 
							
								        elif bin_algo == PackingBin.BFF:
							 | 
						||
| 
								 | 
							
								            packer_class = PackerBFF
							 | 
						||
| 
								 | 
							
								        elif bin_algo == PackingBin.BBF:
							 | 
						||
| 
								 | 
							
								            packer_class = PackerBBF
							 | 
						||
| 
								 | 
							
								        elif bin_algo == PackingBin.Global:
							 | 
						||
| 
								 | 
							
								            packer_class = PackerGlobal
							 | 
						||
| 
								 | 
							
								            sort_algo=None
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            raise AttributeError("Unsupported bin selection heuristic")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        raise AttributeError("Unknown packing mode.")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if sort_algo:
							 | 
						||
| 
								 | 
							
								        return packer_class(pack_algo=pack_algo, sort_algo=sort_algo, 
							 | 
						||
| 
								 | 
							
								            rotation=rotation)
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        return packer_class(pack_algo=pack_algo, rotation=rotation)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 |