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)
 | 
						|
 | 
						|
 |