369 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			369 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|  | from .pack_algo import PackingAlgorithm | ||
|  | from .geometry import Rectangle | ||
|  | import itertools | ||
|  | import operator | ||
|  | 
 | ||
|  | 
 | ||
|  | class Guillotine(PackingAlgorithm): | ||
|  |     """Implementation of several variants of Guillotine packing algorithm
 | ||
|  |      | ||
|  |     For a more detailed explanation of the algorithm used, see: | ||
|  |     Jukka Jylanki - A Thousand Ways to Pack the Bin (February 27, 2010) | ||
|  |     """
 | ||
|  |     def __init__(self, width, height, rot=True, merge=True, *args, **kwargs): | ||
|  |         """
 | ||
|  |         Arguments: | ||
|  |             width (int, float): | ||
|  |             height (int, float): | ||
|  |             merge (bool): Optional keyword argument | ||
|  |         """
 | ||
|  |         self._merge = merge | ||
|  |         super(Guillotine, self).__init__(width, height, rot, *args, **kwargs) | ||
|  |          | ||
|  | 
 | ||
|  |     def _add_section(self, section): | ||
|  |         """Adds a new section to the free section list, but before that and if 
 | ||
|  |         section merge is enabled, tries to join the rectangle with all existing  | ||
|  |         sections, if successful the resulting section is again merged with the  | ||
|  |         remaining sections until the operation fails. The result is then  | ||
|  |         appended to the list. | ||
|  | 
 | ||
|  |         Arguments: | ||
|  |             section (Rectangle): New free section. | ||
|  |         """
 | ||
|  |         section.rid = 0      | ||
|  |         plen = 0 | ||
|  | 
 | ||
|  |         while self._merge and self._sections and plen != len(self._sections): | ||
|  |             plen = len(self._sections) | ||
|  |             self._sections = [s for s in self._sections if not section.join(s)] | ||
|  |         self._sections.append(section) | ||
|  | 
 | ||
|  | 
 | ||
|  |     def _split_horizontal(self, section, width, height): | ||
|  |         """For an horizontal split the rectangle is placed in the lower
 | ||
|  |         left corner of the section (section's xy coordinates), the top | ||
|  |         most side of the rectangle and its horizontal continuation, | ||
|  |         marks the line of division for the split. | ||
|  |         +-----------------+ | ||
|  |         |                 | | ||
|  |         |                 | | ||
|  |         |                 | | ||
|  |         |                 | | ||
|  |         +-------+---------+ | ||
|  |         |#######|         | | ||
|  |         |#######|         | | ||
|  |         |#######|         | | ||
|  |         +-------+---------+ | ||
|  |         If the rectangle width is equal to the the section width, only one | ||
|  |         section is created over the rectangle. If the rectangle height is | ||
|  |         equal to the section height, only one section to the right of the | ||
|  |         rectangle is created. If both width and height are equal, no sections | ||
|  |         are created. | ||
|  |         """
 | ||
|  |         # First remove the section we are splitting so it doesn't  | ||
|  |         # interfere when later we try to merge the resulting split | ||
|  |         # rectangles, with the rest of free sections. | ||
|  |         #self._sections.remove(section) | ||
|  | 
 | ||
|  |         # Creates two new empty sections, and returns the new rectangle. | ||
|  |         if height < section.height: | ||
|  |             self._add_section(Rectangle(section.x, section.y+height, | ||
|  |                 section.width, section.height-height)) | ||
|  | 
 | ||
|  |         if width < section.width: | ||
|  |             self._add_section(Rectangle(section.x+width, section.y, | ||
|  |                 section.width-width, height)) | ||
|  | 
 | ||
|  | 
 | ||
|  |     def _split_vertical(self, section, width, height): | ||
|  |         """For a vertical split the rectangle is placed in the lower
 | ||
|  |         left corner of the section (section's xy coordinates), the | ||
|  |         right most side of the rectangle and its vertical continuation, | ||
|  |         marks the line of division for the split. | ||
|  |         +-------+---------+ | ||
|  |         |       |         | | ||
|  |         |       |         | | ||
|  |         |       |         | | ||
|  |         |       |         | | ||
|  |         +-------+         | | ||
|  |         |#######|         | | ||
|  |         |#######|         | | ||
|  |         |#######|         | | ||
|  |         +-------+---------+ | ||
|  |         If the rectangle width is equal to the the section width, only one | ||
|  |         section is created over the rectangle. If the rectangle height is | ||
|  |         equal to the section height, only one section to the right of the | ||
|  |         rectangle is created. If both width and height are equal, no sections | ||
|  |         are created. | ||
|  |         """
 | ||
|  |         # When a section is split, depending on the rectangle size  | ||
|  |         # two, one, or no new sections will be created.  | ||
|  |         if height < section.height: | ||
|  |             self._add_section(Rectangle(section.x, section.y+height, | ||
|  |                 width, section.height-height)) | ||
|  |          | ||
|  |         if width < section.width: | ||
|  |             self._add_section(Rectangle(section.x+width, section.y, | ||
|  |                 section.width-width, section.height)) | ||
|  |          | ||
|  | 
 | ||
|  |     def _split(self, section, width, height): | ||
|  |         """
 | ||
|  |         Selects the best split for a section, given a rectangle of dimmensions | ||
|  |         width and height, then calls _split_vertical or _split_horizontal,  | ||
|  |         to do the dirty work. | ||
|  |         | ||
|  |         Arguments: | ||
|  |             section (Rectangle): Section to split | ||
|  |             width (int, float): Rectangle width | ||
|  |             height (int, float): Rectangle height | ||
|  |         """
 | ||
|  |         raise NotImplementedError | ||
|  | 
 | ||
|  | 
 | ||
|  |     def _section_fitness(self, section, width, height): | ||
|  |         """The subclass for each one of the Guillotine selection methods,
 | ||
|  |         BAF, BLSF.... will override this method, this is here only | ||
|  |         to asure a valid value return if the worst happens. | ||
|  |         """
 | ||
|  |         raise NotImplementedError | ||
|  | 
 | ||
|  |     def _select_fittest_section(self, w, h): | ||
|  |         """Calls _section_fitness for each of the sections in free section 
 | ||
|  |         list. Returns the section with the minimal fitness value, all the rest  | ||
|  |         is boilerplate to make the fitness comparison, to rotatate the rectangles, | ||
|  |         and to take into account when _section_fitness returns None because  | ||
|  |         the rectangle couldn't be placed. | ||
|  | 
 | ||
|  |         Arguments: | ||
|  |             w (int, float): Rectangle width | ||
|  |             h (int, float): Rectangle height | ||
|  | 
 | ||
|  |         Returns: | ||
|  |             (section, was_rotated): Returns the tuple  | ||
|  |                 section (Rectangle): Section with best fitness | ||
|  |                 was_rotated (bool): The rectangle was rotated  | ||
|  |         """
 | ||
|  |         fitn = ((self._section_fitness(s, w, h), s, False) for s in self._sections  | ||
|  |                 if self._section_fitness(s, w, h) is not None) | ||
|  |         fitr = ((self._section_fitness(s, h, w), s, True) for s in self._sections  | ||
|  |                 if self._section_fitness(s, h, w) is not None) | ||
|  | 
 | ||
|  |         if not self.rot: | ||
|  |             fitr = [] | ||
|  | 
 | ||
|  |         fit = itertools.chain(fitn, fitr) | ||
|  |          | ||
|  |         try: | ||
|  |             _, sec, rot = min(fit, key=operator.itemgetter(0)) | ||
|  |         except ValueError: | ||
|  |             return None, None | ||
|  | 
 | ||
|  |         return sec, rot | ||
|  | 
 | ||
|  | 
 | ||
|  |     def add_rect(self, width, height, rid=None):      | ||
|  |         """
 | ||
|  |         Add rectangle of widthxheight dimensions. | ||
|  | 
 | ||
|  |         Arguments: | ||
|  |             width (int, float): Rectangle width | ||
|  |             height (int, float): Rectangle height | ||
|  |             rid: Optional rectangle user id | ||
|  | 
 | ||
|  |         Returns: | ||
|  |             Rectangle: Rectangle with placemente coordinates | ||
|  |             None: If the rectangle couldn be placed. | ||
|  |         """
 | ||
|  |         assert(width > 0 and height >0) | ||
|  | 
 | ||
|  |         # Obtain the best section to place the rectangle. | ||
|  |         section, rotated = self._select_fittest_section(width, height) | ||
|  |         if not section: | ||
|  |             return None | ||
|  |          | ||
|  |         if rotated: | ||
|  |             width, height = height, width | ||
|  |          | ||
|  |         # Remove section, split and store results | ||
|  |         self._sections.remove(section) | ||
|  |         self._split(section, width, height) | ||
|  |         | ||
|  |         # Store rectangle in the selected position | ||
|  |         rect = Rectangle(section.x, section.y, width, height, rid) | ||
|  |         self.rectangles.append(rect) | ||
|  |         return rect | ||
|  | 
 | ||
|  |     def fitness(self, width, height): | ||
|  |         """
 | ||
|  |         In guillotine algorithm case, returns the min of the fitness of all  | ||
|  |         free sections, for the given dimension, both normal and rotated | ||
|  |         (if rotation enabled.) | ||
|  |         """
 | ||
|  |         assert(width > 0 and height > 0) | ||
|  | 
 | ||
|  |         # Get best fitness section. | ||
|  |         section, rotated = self._select_fittest_section(width, height) | ||
|  |         if not section: | ||
|  |             return None | ||
|  |          | ||
|  |         # Return fitness of returned section, with correct dimmensions if the | ||
|  |         # the rectangle was rotated. | ||
|  |         if rotated: | ||
|  |             return self._section_fitness(section, height, width) | ||
|  |         else: | ||
|  |             return self._section_fitness(section, width, height) | ||
|  | 
 | ||
|  |     def reset(self): | ||
|  |         super(Guillotine, self).reset() | ||
|  |         self._sections = [] | ||
|  |         self._add_section(Rectangle(0, 0, self.width, self.height)) | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | class GuillotineBaf(Guillotine): | ||
|  |     """Implements Best Area Fit (BAF) section selection criteria for 
 | ||
|  |     Guillotine algorithm. | ||
|  |     """
 | ||
|  |     def _section_fitness(self, section, width, height): | ||
|  |         if width > section.width or height > section.height: | ||
|  |             return None | ||
|  |         return section.area()-width*height | ||
|  | 
 | ||
|  | 
 | ||
|  | class GuillotineBlsf(Guillotine): | ||
|  |     """Implements Best Long Side Fit (BLSF) section selection criteria for 
 | ||
|  |     Guillotine algorithm. | ||
|  |     """
 | ||
|  |     def _section_fitness(self, section, width, height): | ||
|  |         if width > section.width or height > section.height: | ||
|  |             return None | ||
|  |         return max(section.width-width, section.height-height) | ||
|  | 
 | ||
|  | 
 | ||
|  | class GuillotineBssf(Guillotine): | ||
|  |     """Implements Best Short Side Fit (BSSF) section selection criteria for 
 | ||
|  |     Guillotine algorithm. | ||
|  |     """
 | ||
|  |     def _section_fitness(self, section, width, height): | ||
|  |         if width > section.width or height > section.height: | ||
|  |             return None | ||
|  |         return min(section.width-width, section.height-height) | ||
|  | 
 | ||
|  | 
 | ||
|  | class GuillotineSas(Guillotine): | ||
|  |     """Implements Short Axis Split (SAS) selection rule for Guillotine 
 | ||
|  |     algorithm. | ||
|  |     """
 | ||
|  |     def _split(self, section, width, height): | ||
|  |         if section.width < section.height: | ||
|  |             return self._split_horizontal(section, width, height) | ||
|  |         else: | ||
|  |             return self._split_vertical(section, width, height) | ||
|  |          | ||
|  | 
 | ||
|  | 
 | ||
|  | class GuillotineLas(Guillotine): | ||
|  |     """Implements Long Axis Split (LAS) selection rule for Guillotine 
 | ||
|  |     algorithm. | ||
|  |     """
 | ||
|  |     def _split(self, section, width, height): | ||
|  |         if section.width >= section.height: | ||
|  |             return self._split_horizontal(section, width, height) | ||
|  |         else: | ||
|  |             return self._split_vertical(section, width, height) | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | class GuillotineSlas(Guillotine): | ||
|  |     """Implements Short Leftover Axis Split (SLAS) selection rule for 
 | ||
|  |     Guillotine algorithm. | ||
|  |     """
 | ||
|  |     def _split(self, section, width, height): | ||
|  |         if section.width-width < section.height-height: | ||
|  |             return self._split_horizontal(section, width, height) | ||
|  |         else: | ||
|  |             return self._split_vertical(section, width, height) | ||
|  |          | ||
|  | 
 | ||
|  | 
 | ||
|  | class GuillotineLlas(Guillotine): | ||
|  |     """Implements Long Leftover Axis Split (LLAS) selection rule for 
 | ||
|  |     Guillotine algorithm. | ||
|  |     """
 | ||
|  |     def _split(self, section, width, height): | ||
|  |         if section.width-width >= section.height-height: | ||
|  |             return self._split_horizontal(section, width, height) | ||
|  |         else: | ||
|  |             return self._split_vertical(section, width, height) | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | class GuillotineMaxas(Guillotine): | ||
|  |     """Implements Max Area Axis Split (MAXAS) selection rule for Guillotine
 | ||
|  |     algorithm. Maximize the larger area == minimize the smaller area. | ||
|  |     Tries to make the rectangles more even-sized. | ||
|  |     """
 | ||
|  |     def _split(self, section, width, height): | ||
|  |         if width*(section.height-height) <= height*(section.width-width): | ||
|  |             return self._split_horizontal(section, width, height) | ||
|  |         else: | ||
|  |             return self._split_vertical(section, width, height) | ||
|  |          | ||
|  | 
 | ||
|  | 
 | ||
|  | class GuillotineMinas(Guillotine): | ||
|  |     """Implements Min Area Axis Split (MINAS) selection rule for Guillotine 
 | ||
|  |     algorithm.  | ||
|  |     """
 | ||
|  |     def _split(self, section, width, height): | ||
|  |         if width*(section.height-height) >= height*(section.width-width): | ||
|  |             return self._split_horizontal(section, width, height) | ||
|  |         else: | ||
|  |             return self._split_vertical(section, width, height) | ||
|  |         | ||
|  | 
 | ||
|  | 
 | ||
|  | # Guillotine algorithms GUILLOTINE-RECT-SPLIT, Selecting one | ||
|  | # Axis split, and one selection criteria. | ||
|  | class GuillotineBssfSas(GuillotineBssf, GuillotineSas): | ||
|  |     pass | ||
|  | class GuillotineBssfLas(GuillotineBssf, GuillotineLas): | ||
|  |     pass | ||
|  | class GuillotineBssfSlas(GuillotineBssf, GuillotineSlas): | ||
|  |     pass | ||
|  | class GuillotineBssfLlas(GuillotineBssf, GuillotineLlas): | ||
|  |     pass | ||
|  | class GuillotineBssfMaxas(GuillotineBssf, GuillotineMaxas): | ||
|  |     pass | ||
|  | class GuillotineBssfMinas(GuillotineBssf, GuillotineMinas): | ||
|  |     pass | ||
|  | class GuillotineBlsfSas(GuillotineBlsf, GuillotineSas): | ||
|  |     pass | ||
|  | class GuillotineBlsfLas(GuillotineBlsf, GuillotineLas): | ||
|  |     pass | ||
|  | class GuillotineBlsfSlas(GuillotineBlsf, GuillotineSlas): | ||
|  |     pass | ||
|  | class GuillotineBlsfLlas(GuillotineBlsf, GuillotineLlas): | ||
|  |     pass | ||
|  | class GuillotineBlsfMaxas(GuillotineBlsf, GuillotineMaxas): | ||
|  |     pass | ||
|  | class GuillotineBlsfMinas(GuillotineBlsf, GuillotineMinas): | ||
|  |     pass | ||
|  | class GuillotineBafSas(GuillotineBaf, GuillotineSas): | ||
|  |     pass | ||
|  | class GuillotineBafLas(GuillotineBaf, GuillotineLas): | ||
|  |     pass | ||
|  | class GuillotineBafSlas(GuillotineBaf, GuillotineSlas): | ||
|  |     pass | ||
|  | class GuillotineBafLlas(GuillotineBaf, GuillotineLlas): | ||
|  |     pass | ||
|  | class GuillotineBafMaxas(GuillotineBaf, GuillotineMaxas): | ||
|  |     pass | ||
|  | class GuillotineBafMinas(GuillotineBaf, GuillotineMinas): | ||
|  |     pass | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 |