149 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			149 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import heapq # heapq.heappush, heapq.heappop
 | |
| from .packer import newPacker, PackingMode, PackingBin, SORT_LSIDE
 | |
| from .skyline import SkylineBlWm
 | |
| 
 | |
| 
 | |
| 
 | |
| class Enclose(object):
 | |
| 
 | |
|     def __init__(self, rectangles=[], max_width=None, max_height=None, rotation=True):
 | |
|         """
 | |
|         Arguments:
 | |
|             rectangles (list): Rectangle to be enveloped
 | |
|                 [(width1, height1), (width2, height2), ...]
 | |
|             max_width (number|None): Enveloping rectangle max allowed width.
 | |
|             max_height (number|None): Enveloping rectangle max allowed height.
 | |
|             rotation (boolean): Enable/Disable rectangle rotation.
 | |
|         """
 | |
|         # Enclosing rectangle max width
 | |
|         self._max_width = max_width
 | |
| 
 | |
|         # Encloseing rectangle max height
 | |
|         self._max_height = max_height
 | |
| 
 | |
|         # Enable or disable rectangle rotation
 | |
|         self._rotation = rotation
 | |
| 
 | |
|         # Default packing algorithm
 | |
|         self._pack_algo = SkylineBlWm
 | |
|         
 | |
|         # rectangles to enclose [(width, height), (width, height, ...)]
 | |
|         self._rectangles = []
 | |
|         for r in rectangles:
 | |
|             self.add_rect(*r)
 | |
| 
 | |
|     def _container_candidates(self):
 | |
|         """Generate container candidate list 
 | |
|         
 | |
|         Returns:
 | |
|             tuple list: [(width1, height1), (width2, height2), ...] 
 | |
|         """
 | |
|         if not self._rectangles:
 | |
|             return []
 | |
| 
 | |
|         if self._rotation:
 | |
|             sides = sorted(side for rect in self._rectangles for side in rect)
 | |
|             max_height = sum(max(r[0], r[1]) for r in self._rectangles)
 | |
|             min_width = max(min(r[0], r[1]) for r in self._rectangles)
 | |
|             max_width = max_height
 | |
|         else:
 | |
|             sides = sorted(r[0] for r in self._rectangles)
 | |
|             max_height = sum(r[1] for r in self._rectangles)
 | |
|             min_width = max(r[0] for r in self._rectangles)
 | |
|             max_width = sum(sides)
 | |
|         
 | |
|         if self._max_width and self._max_width < max_width:
 | |
|             max_width = self._max_width
 | |
| 
 | |
|         if self._max_height and self._max_height < max_height:
 | |
|             max_height = self._max_height
 | |
| 
 | |
|         assert(max_width>min_width)
 | |
|  
 | |
|         # Generate initial container widths
 | |
|         candidates = [max_width, min_width]
 | |
| 
 | |
|         width = 0
 | |
|         for s in reversed(sides):
 | |
|             width += s
 | |
|             candidates.append(width)
 | |
|         
 | |
|         width = 0
 | |
|         for s in sides:
 | |
|             width += s
 | |
|             candidates.append(width)
 | |
| 
 | |
|         candidates.append(max_width)
 | |
|         candidates.append(min_width)
 | |
|       
 | |
|         # Remove duplicates and widths too big or small
 | |
|         seen = set()
 | |
|         seen_add = seen.add
 | |
|         candidates = [x for x in candidates if not(x in seen or seen_add(x))] 
 | |
|         candidates = [x for x in candidates if not(x>max_width or x<min_width)]
 | |
|         
 | |
|         # Remove candidates too small to fit all the rectangles
 | |
|         min_area = sum(r[0]*r[1] for r in self._rectangles)
 | |
|         return [(c, max_height) for c in candidates if c*max_height>=min_area]
 | |
|    
 | |
|     def _refine_candidate(self, width, height):
 | |
|         """
 | |
|         Use bottom-left packing algorithm to find a lower height for the 
 | |
|         container.
 | |
| 
 | |
|         Arguments:
 | |
|             width
 | |
|             height
 | |
| 
 | |
|         Returns:
 | |
|             tuple (width, height, PackingAlgorithm):
 | |
|         """
 | |
|         packer = newPacker(PackingMode.Offline, PackingBin.BFF, 
 | |
|                 pack_algo=self._pack_algo, sort_algo=SORT_LSIDE,
 | |
|                 rotation=self._rotation)
 | |
|         packer.add_bin(width, height)
 | |
|        
 | |
|         for r in self._rectangles:
 | |
|             packer.add_rect(*r)
 | |
| 
 | |
|         packer.pack()
 | |
| 
 | |
|         # Check all rectangles where packed
 | |
|         if len(packer[0]) != len(self._rectangles):
 | |
|             return None
 | |
| 
 | |
|         # Find highest rectangle
 | |
|         new_height = max(packer[0], key=lambda x: x.top).top
 | |
|         return(width, new_height, packer)
 | |
| 
 | |
|     def generate(self):
 | |
|     
 | |
|         # Generate initial containers
 | |
|         candidates = self._container_candidates()
 | |
|         if not candidates:
 | |
|             return None
 | |
| 
 | |
|         # Refine candidates and return the one with the smaller area
 | |
|         containers = [self._refine_candidate(*c) for c in candidates]
 | |
|         containers = [c for c in containers if c]
 | |
|         if not containers:
 | |
|             return None
 | |
| 
 | |
|         width, height, packer = min(containers, key=lambda x: x[0]*x[1])
 | |
| 
 | |
|         packer.width = width
 | |
|         packer.height = height
 | |
|         return packer
 | |
| 
 | |
|     def add_rect(self, width, height):
 | |
|         """
 | |
|         Add anoter rectangle to be enclosed
 | |
| 
 | |
|         Arguments:
 | |
|             width (number): Rectangle width
 | |
|             height (number): Rectangle height
 | |
|         """
 | |
|         self._rectangles.append((width, height))
 | |
| 
 | |
| 
 |