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