forked from LeenkxTeam/LNXSDK
		
	
		
			
	
	
		
			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))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 |