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