forked from LeenkxTeam/LNXSDK
		
	
		
			
	
	
		
			245 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			245 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								from .pack_algo import PackingAlgorithm
							 | 
						||
| 
								 | 
							
								from .geometry import Rectangle
							 | 
						||
| 
								 | 
							
								import itertools
							 | 
						||
| 
								 | 
							
								import collections
							 | 
						||
| 
								 | 
							
								import operator
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								first_item = operator.itemgetter(0)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class MaxRects(PackingAlgorithm):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, width, height, rot=True, *args, **kwargs):
							 | 
						||
| 
								 | 
							
								        super(MaxRects, self).__init__(width, height, rot, *args, **kwargs)
							 | 
						||
| 
								 | 
							
								   
							 | 
						||
| 
								 | 
							
								    def _rect_fitness(self, max_rect, width, height):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            max_rect (Rectangle): Destination max_rect
							 | 
						||
| 
								 | 
							
								            width (int, float): Rectangle width
							 | 
						||
| 
								 | 
							
								            height (int, float): Rectangle height
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Returns:
							 | 
						||
| 
								 | 
							
								            None: Rectangle couldn't be placed into max_rect
							 | 
						||
| 
								 | 
							
								            integer, float: fitness value 
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if width <= max_rect.width and height <= max_rect.height:
							 | 
						||
| 
								 | 
							
								            return 0
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _select_position(self, w, h): 
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Find max_rect with best fitness for placing a rectangle
							 | 
						||
| 
								 | 
							
								        of dimentsions w*h
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            w (int, float): Rectangle width
							 | 
						||
| 
								 | 
							
								            h (int, float): Rectangle height
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Returns:
							 | 
						||
| 
								 | 
							
								            (rect, max_rect)
							 | 
						||
| 
								 | 
							
								            rect (Rectangle): Placed rectangle or None if was unable.
							 | 
						||
| 
								 | 
							
								            max_rect (Rectangle): Maximal rectangle were rect was placed
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if not self._max_rects:
							 | 
						||
| 
								 | 
							
								            return None, None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Normal rectangle
							 | 
						||
| 
								 | 
							
								        fitn = ((self._rect_fitness(m, w, h), w, h, m) for m in self._max_rects 
							 | 
						||
| 
								 | 
							
								                if self._rect_fitness(m, w, h) is not None)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Rotated rectangle
							 | 
						||
| 
								 | 
							
								        fitr = ((self._rect_fitness(m, h, w), h, w, m) for m in self._max_rects 
							 | 
						||
| 
								 | 
							
								                if self._rect_fitness(m, h, w) is not None)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if not self.rot:
							 | 
						||
| 
								 | 
							
								            fitr = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        fit = itertools.chain(fitn, fitr)
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            _, w, h, m = min(fit, key=first_item)
							 | 
						||
| 
								 | 
							
								        except ValueError:
							 | 
						||
| 
								 | 
							
								            return None, None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return Rectangle(m.x, m.y, w, h), m
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _generate_splits(self, m, r):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        When a rectangle is placed inside a maximal rectangle, it stops being one
							 | 
						||
| 
								 | 
							
								        and up to 4 new maximal rectangles may appear depending on the placement.
							 | 
						||
| 
								 | 
							
								        _generate_splits calculates them.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            m (Rectangle): max_rect rectangle
							 | 
						||
| 
								 | 
							
								            r (Rectangle): rectangle placed
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Returns:
							 | 
						||
| 
								 | 
							
								            list : list containing new maximal rectangles or an empty list
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        new_rects = []
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        if r.left > m.left:
							 | 
						||
| 
								 | 
							
								            new_rects.append(Rectangle(m.left, m.bottom, r.left-m.left, m.height))
							 | 
						||
| 
								 | 
							
								        if r.right < m.right:
							 | 
						||
| 
								 | 
							
								            new_rects.append(Rectangle(r.right, m.bottom, m.right-r.right, m.height))
							 | 
						||
| 
								 | 
							
								        if r.top < m.top:
							 | 
						||
| 
								 | 
							
								            new_rects.append(Rectangle(m.left, r.top, m.width, m.top-r.top))
							 | 
						||
| 
								 | 
							
								        if r.bottom > m.bottom:
							 | 
						||
| 
								 | 
							
								            new_rects.append(Rectangle(m.left, m.bottom, m.width, r.bottom-m.bottom))
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        return new_rects
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _split(self, rect):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Split all max_rects intersecting the rectangle rect into up to
							 | 
						||
| 
								 | 
							
								        4 new max_rects.
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            rect (Rectangle): Rectangle
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Returns:
							 | 
						||
| 
								 | 
							
								            split (Rectangle list): List of rectangles resulting from the split
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        max_rects = collections.deque()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for r in self._max_rects:
							 | 
						||
| 
								 | 
							
								            if r.intersects(rect):
							 | 
						||
| 
								 | 
							
								                max_rects.extend(self._generate_splits(r, rect))
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                max_rects.append(r)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Add newly generated max_rects
							 | 
						||
| 
								 | 
							
								        self._max_rects = list(max_rects)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _remove_duplicates(self):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Remove every maximal rectangle contained by another one.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        contained = set()
							 | 
						||
| 
								 | 
							
								        for m1, m2 in itertools.combinations(self._max_rects, 2):
							 | 
						||
| 
								 | 
							
								            if m1.contains(m2):
							 | 
						||
| 
								 | 
							
								                contained.add(m2)
							 | 
						||
| 
								 | 
							
								            elif m2.contains(m1):
							 | 
						||
| 
								 | 
							
								                contained.add(m1)
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        # Remove from max_rects
							 | 
						||
| 
								 | 
							
								        self._max_rects = [m for m in self._max_rects if m not in contained]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def fitness(self, width, height): 
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Metric used to rate how much space is wasted if a rectangle is placed.
							 | 
						||
| 
								 | 
							
								        Returns a value greater or equal to zero, the smaller the value the more 
							 | 
						||
| 
								 | 
							
								        'fit' is the rectangle. If the rectangle can't be placed, returns None.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            width (int, float): Rectangle width
							 | 
						||
| 
								 | 
							
								            height (int, float): Rectangle height
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Returns:
							 | 
						||
| 
								 | 
							
								            int, float: Rectangle fitness 
							 | 
						||
| 
								 | 
							
								            None: Rectangle can't be placed
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        assert(width > 0 and height > 0)
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        rect, max_rect = self._select_position(width, height)
							 | 
						||
| 
								 | 
							
								        if rect is None:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Return fitness
							 | 
						||
| 
								 | 
							
								        return self._rect_fitness(max_rect, rect.width, rect.height)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    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)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Search best position and orientation
							 | 
						||
| 
								 | 
							
								        rect, _ = self._select_position(width, height)
							 | 
						||
| 
								 | 
							
								        if not rect:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        # Subdivide all the max rectangles intersecting with the selected 
							 | 
						||
| 
								 | 
							
								        # rectangle.
							 | 
						||
| 
								 | 
							
								        self._split(rect)
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								        # Remove any max_rect contained by another 
							 | 
						||
| 
								 | 
							
								        self._remove_duplicates()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Store and return rectangle position.
							 | 
						||
| 
								 | 
							
								        rect.rid = rid
							 | 
						||
| 
								 | 
							
								        self.rectangles.append(rect)
							 | 
						||
| 
								 | 
							
								        return rect
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def reset(self):
							 | 
						||
| 
								 | 
							
								        super(MaxRects, self).reset()
							 | 
						||
| 
								 | 
							
								        self._max_rects = [Rectangle(0, 0, self.width, self.height)]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class MaxRectsBl(MaxRects):
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    def _select_position(self, w, h): 
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Select the position where the y coordinate of the top of the rectangle
							 | 
						||
| 
								 | 
							
								        is lower, if there are severtal pick the one with the smallest x 
							 | 
						||
| 
								 | 
							
								        coordinate
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        fitn = ((m.y+h, m.x, w, h, m) for m in self._max_rects 
							 | 
						||
| 
								 | 
							
								                if self._rect_fitness(m, w, h) is not None)
							 | 
						||
| 
								 | 
							
								        fitr = ((m.y+w, m.x, h, w, m) for m in self._max_rects 
							 | 
						||
| 
								 | 
							
								                if self._rect_fitness(m, h, w) is not None)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if not self.rot:
							 | 
						||
| 
								 | 
							
								            fitr = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        fit = itertools.chain(fitn, fitr)
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            _, _, w, h, m = min(fit, key=first_item)
							 | 
						||
| 
								 | 
							
								        except ValueError:
							 | 
						||
| 
								 | 
							
								            return None, None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return Rectangle(m.x, m.y, w, h), m
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class MaxRectsBssf(MaxRects):
							 | 
						||
| 
								 | 
							
								    """Best Sort Side Fit minimize short leftover side"""
							 | 
						||
| 
								 | 
							
								    def _rect_fitness(self, max_rect, width, height):
							 | 
						||
| 
								 | 
							
								        if width > max_rect.width or height > max_rect.height:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return min(max_rect.width-width, max_rect.height-height)
							 | 
						||
| 
								 | 
							
								           
							 | 
						||
| 
								 | 
							
								class MaxRectsBaf(MaxRects):
							 | 
						||
| 
								 | 
							
								    """Best Area Fit pick maximal rectangle with smallest area
							 | 
						||
| 
								 | 
							
								    where the rectangle can be placed"""
							 | 
						||
| 
								 | 
							
								    def _rect_fitness(self, max_rect, width, height):
							 | 
						||
| 
								 | 
							
								        if width > max_rect.width or height > max_rect.height:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        return (max_rect.width*max_rect.height)-(width*height)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class MaxRectsBlsf(MaxRects):
							 | 
						||
| 
								 | 
							
								    """Best Long Side Fit minimize long leftover side"""
							 | 
						||
| 
								 | 
							
								    def _rect_fitness(self, max_rect, width, height):
							 | 
						||
| 
								 | 
							
								        if width > max_rect.width or height > max_rect.height:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return max(max_rect.width-width, max_rect.height-height)
							 |