forked from LeenkxTeam/LNXSDK
		
	
		
			
	
	
		
			304 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			304 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								import collections
							 | 
						||
| 
								 | 
							
								import itertools
							 | 
						||
| 
								 | 
							
								import operator
							 | 
						||
| 
								 | 
							
								import heapq
							 | 
						||
| 
								 | 
							
								import copy
							 | 
						||
| 
								 | 
							
								from .pack_algo import PackingAlgorithm
							 | 
						||
| 
								 | 
							
								from .geometry import Point as P
							 | 
						||
| 
								 | 
							
								from .geometry import HSegment, Rectangle
							 | 
						||
| 
								 | 
							
								from .waste import WasteManager
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Skyline(PackingAlgorithm):
							 | 
						||
| 
								 | 
							
								    """ Class implementing Skyline algorithm as described by
							 | 
						||
| 
								 | 
							
								    Jukka Jylanki - A Thousand Ways to Pack the Bin (February 27, 2010)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    _skyline:  stores all the segments at the top of the skyline.
							 | 
						||
| 
								 | 
							
								    _waste: Handles all wasted sections.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, width, height, rot=True, *args, **kwargs):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        _skyline is the list used to store all the skyline segments, each 
							 | 
						||
| 
								 | 
							
								        one is a list with the format [x, y, width] where x is the x
							 | 
						||
| 
								 | 
							
								        coordinate of the left most point of the segment, y the y coordinate
							 | 
						||
| 
								 | 
							
								        of the segment, and width the length of the segment. The initial 
							 | 
						||
| 
								 | 
							
								        segment is allways [0, 0, surface_width]
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            width (int, float): 
							 | 
						||
| 
								 | 
							
								            height (int, float):
							 | 
						||
| 
								 | 
							
								            rot (bool): Enable or disable rectangle rotation
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        self._waste_management = False
							 | 
						||
| 
								 | 
							
								        self._waste = WasteManager(rot=rot)
							 | 
						||
| 
								 | 
							
								        super(Skyline, self).__init__(width, height, rot, merge=False, *args, **kwargs)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _placement_points_generator(self, skyline, width):
							 | 
						||
| 
								 | 
							
								        """Returns a generator for the x coordinates of all the placement
							 | 
						||
| 
								 | 
							
								        points on the skyline for a given rectangle.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        WARNING: In some cases could be duplicated points, but it is faster
							 | 
						||
| 
								 | 
							
								        to compute them twice than to remove them.
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            skyline (list): Skyline HSegment list
							 | 
						||
| 
								 | 
							
								            width (int, float): Rectangle width
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Returns:
							 | 
						||
| 
								 | 
							
								            generator
							 | 
						||
| 
								 | 
							
								        """ 
							 | 
						||
| 
								 | 
							
								        skyline_r = skyline[-1].right
							 | 
						||
| 
								 | 
							
								        skyline_l = skyline[0].left
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Placements using skyline segment left point
							 | 
						||
| 
								 | 
							
								        ppointsl = (s.left for s in skyline if s.left+width <= skyline_r)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Placements using skyline segment right point
							 | 
						||
| 
								 | 
							
								        ppointsr = (s.right-width for s in skyline if s.right-width >= skyline_l)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Merge positions
							 | 
						||
| 
								 | 
							
								        return heapq.merge(ppointsl, ppointsr)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _generate_placements(self, width, height):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Generate a list with 
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            skyline (list): SkylineHSegment list
							 | 
						||
| 
								 | 
							
								            width (number):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Returns:
							 | 
						||
| 
								 | 
							
								            tuple (Rectangle, fitness):
							 | 
						||
| 
								 | 
							
								                Rectangle: Rectangle in valid position
							 | 
						||
| 
								 | 
							
								                left_skyline: Index for the skyline under the rectangle left edge.
							 | 
						||
| 
								 | 
							
								                right_skyline: Index for the skyline under the rectangle right edte.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        skyline = self._skyline
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        points = collections.deque()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        left_index = right_index = 0 # Left and right side skyline index
							 | 
						||
| 
								 | 
							
								        support_height = skyline[0].top
							 | 
						||
| 
								 | 
							
								        support_index = 0 
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								        placements = self._placement_points_generator(skyline, width)
							 | 
						||
| 
								 | 
							
								        for p in placements:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # If Rectangle's right side changed segment, find new support
							 | 
						||
| 
								 | 
							
								            if p+width > skyline[right_index].right:
							 | 
						||
| 
								 | 
							
								                for right_index in range(right_index+1, len(skyline)):
							 | 
						||
| 
								 | 
							
								                    if skyline[right_index].top >= support_height:
							 | 
						||
| 
								 | 
							
								                        support_index = right_index
							 | 
						||
| 
								 | 
							
								                        support_height = skyline[right_index].top
							 | 
						||
| 
								 | 
							
								                    if p+width <= skyline[right_index].right:
							 | 
						||
| 
								 | 
							
								                        break
							 | 
						||
| 
								 | 
							
								                
							 | 
						||
| 
								 | 
							
								            # If left side changed segment.
							 | 
						||
| 
								 | 
							
								            if p >= skyline[left_index].right:
							 | 
						||
| 
								 | 
							
								                left_index +=1
							 | 
						||
| 
								 | 
							
								           
							 | 
						||
| 
								 | 
							
								            # Find new support if the previous one was shifted out.
							 | 
						||
| 
								 | 
							
								            if support_index < left_index:
							 | 
						||
| 
								 | 
							
								                support_index = left_index
							 | 
						||
| 
								 | 
							
								                support_height = skyline[left_index].top
							 | 
						||
| 
								 | 
							
								                for i in range(left_index, right_index+1):
							 | 
						||
| 
								 | 
							
								                    if skyline[i].top >= support_height:
							 | 
						||
| 
								 | 
							
								                        support_index = i
							 | 
						||
| 
								 | 
							
								                        support_height = skyline[i].top
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # Add point if there is enought room at the top
							 | 
						||
| 
								 | 
							
								            if support_height+height <= self.height:
							 | 
						||
| 
								 | 
							
								                points.append((Rectangle(p, support_height, width, height),\
							 | 
						||
| 
								 | 
							
								                    left_index, right_index))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return points
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _merge_skyline(self, skylineq, segment):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            skylineq (collections.deque):
							 | 
						||
| 
								 | 
							
								            segment (HSegment):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if len(skylineq) == 0:
							 | 
						||
| 
								 | 
							
								            skylineq.append(segment)
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if skylineq[-1].top == segment.top:
							 | 
						||
| 
								 | 
							
								            s = skylineq[-1]
							 | 
						||
| 
								 | 
							
								            skylineq[-1] = HSegment(s.start, s.length+segment.length)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            skylineq.append(segment)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _add_skyline(self, rect):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            seg (Rectangle):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        skylineq = collections.deque([]) # Skyline after adding new one
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        for sky in self._skyline:
							 | 
						||
| 
								 | 
							
								            if sky.right <= rect.left or sky.left >= rect.right:
							 | 
						||
| 
								 | 
							
								                self._merge_skyline(skylineq, sky)
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if sky.left < rect.left and sky.right > rect.left:
							 | 
						||
| 
								 | 
							
								                # Skyline section partially under segment left
							 | 
						||
| 
								 | 
							
								                self._merge_skyline(skylineq, 
							 | 
						||
| 
								 | 
							
								                        HSegment(sky.start, rect.left-sky.left))
							 | 
						||
| 
								 | 
							
								                sky = HSegment(P(rect.left, sky.top), sky.right-rect.left)
							 | 
						||
| 
								 | 
							
								            
							 | 
						||
| 
								 | 
							
								            if sky.left < rect.right:
							 | 
						||
| 
								 | 
							
								                if sky.left == rect.left:
							 | 
						||
| 
								 | 
							
								                    self._merge_skyline(skylineq, 
							 | 
						||
| 
								 | 
							
								                        HSegment(P(rect.left, rect.top), rect.width))
							 | 
						||
| 
								 | 
							
								                # Skyline section partially under segment right
							 | 
						||
| 
								 | 
							
								                if sky.right > rect.right:
							 | 
						||
| 
								 | 
							
								                    self._merge_skyline(skylineq, 
							 | 
						||
| 
								 | 
							
								                        HSegment(P(rect.right, sky.top), sky.right-rect.right))
							 | 
						||
| 
								 | 
							
								                    sky = HSegment(sky.start, rect.right-sky.left)
							 | 
						||
| 
								 | 
							
								            
							 | 
						||
| 
								 | 
							
								            if sky.left >= rect.left and sky.right <= rect.right:
							 | 
						||
| 
								 | 
							
								                # Skyline section fully under segment, account for wasted space
							 | 
						||
| 
								 | 
							
								                if self._waste_management and sky.top < rect.bottom:
							 | 
						||
| 
								 | 
							
								                    self._waste.add_waste(sky.left, sky.top, 
							 | 
						||
| 
								 | 
							
								                        sky.length, rect.bottom - sky.top)
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                # Segment
							 | 
						||
| 
								 | 
							
								                self._merge_skyline(skylineq, sky)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Aaaaand ..... Done
							 | 
						||
| 
								 | 
							
								        self._skyline = list(skylineq)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _rect_fitness(self, rect, left_index, right_index):
							 | 
						||
| 
								 | 
							
								        return rect.top
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _select_position(self, width, height):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Search for the placement with the bes fitness for the rectangle.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Returns:
							 | 
						||
| 
								 | 
							
								            tuple (Rectangle, fitness) - Rectangle placed in the fittest position
							 | 
						||
| 
								 | 
							
								            None - Rectangle couldn't be placed
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        positions = self._generate_placements(width, height)
							 | 
						||
| 
								 | 
							
								        if self.rot and width != height:
							 | 
						||
| 
								 | 
							
								            positions += self._generate_placements(height, width)
							 | 
						||
| 
								 | 
							
								        if not positions:
							 | 
						||
| 
								 | 
							
								            return None, None
							 | 
						||
| 
								 | 
							
								        return min(((p[0], self._rect_fitness(*p))for p in positions), 
							 | 
						||
| 
								 | 
							
								                key=operator.itemgetter(1))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def fitness(self, width, height):
							 | 
						||
| 
								 | 
							
								        """Search for the best fitness 
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        assert(width > 0 and height >0)
							 | 
						||
| 
								 | 
							
								        if width > max(self.width, self.height) or\
							 | 
						||
| 
								 | 
							
								            height > max(self.height, self.width):
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # If there is room in wasted space, FREE PACKING!!
							 | 
						||
| 
								 | 
							
								        if self._waste_management:
							 | 
						||
| 
								 | 
							
								            if self._waste.fitness(width, height) is not None:
							 | 
						||
| 
								 | 
							
								                return 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Get best fitness segment, for normal rectangle, and for
							 | 
						||
| 
								 | 
							
								        # rotated rectangle if rotation is enabled.
							 | 
						||
| 
								 | 
							
								        rect, fitness = self._select_position(width, height)
							 | 
						||
| 
								 | 
							
								        return fitness
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def add_rect(self, width, height, rid=None):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Add new rectangle
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        assert(width > 0 and height > 0)
							 | 
						||
| 
								 | 
							
								        if width > max(self.width, self.height) or\
							 | 
						||
| 
								 | 
							
								            height > max(self.height, self.width):
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        rect = None
							 | 
						||
| 
								 | 
							
								        # If Waste managment is enabled, first try to place the rectangle there
							 | 
						||
| 
								 | 
							
								        if self._waste_management:
							 | 
						||
| 
								 | 
							
								            rect = self._waste.add_rect(width, height, rid)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Get best possible rectangle position
							 | 
						||
| 
								 | 
							
								        if not rect:
							 | 
						||
| 
								 | 
							
								            rect, _ = self._select_position(width, height)
							 | 
						||
| 
								 | 
							
								            if rect:
							 | 
						||
| 
								 | 
							
								                self._add_skyline(rect)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if rect is None:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        # Store rectangle, and recalculate skyline
							 | 
						||
| 
								 | 
							
								        rect.rid = rid
							 | 
						||
| 
								 | 
							
								        self.rectangles.append(rect)
							 | 
						||
| 
								 | 
							
								        return rect
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def reset(self):
							 | 
						||
| 
								 | 
							
								        super(Skyline, self).reset()
							 | 
						||
| 
								 | 
							
								        self._skyline = [HSegment(P(0, 0), self.width)]
							 | 
						||
| 
								 | 
							
								        self._waste.reset()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class SkylineWMixin(Skyline):
							 | 
						||
| 
								 | 
							
								    """Waste managment mixin"""
							 | 
						||
| 
								 | 
							
								    def __init__(self, width, height, *args, **kwargs):
							 | 
						||
| 
								 | 
							
								        super(SkylineWMixin, self).__init__(width, height, *args, **kwargs)
							 | 
						||
| 
								 | 
							
								        self._waste_management = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class SkylineMwf(Skyline):
							 | 
						||
| 
								 | 
							
								    """Implements Min Waste fit heuristic, minimizing the area wasted under the
							 | 
						||
| 
								 | 
							
								    rectangle.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    def _rect_fitness(self, rect, left_index, right_index):
							 | 
						||
| 
								 | 
							
								        waste = 0
							 | 
						||
| 
								 | 
							
								        for seg in self._skyline[left_index:right_index+1]:
							 | 
						||
| 
								 | 
							
								            waste +=\
							 | 
						||
| 
								 | 
							
								                (min(rect.right, seg.right)-max(rect.left, seg.left)) *\
							 | 
						||
| 
								 | 
							
								                (rect.bottom-seg.top)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return waste
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _rect_fitnes2s(self, rect, left_index, right_index):
							 | 
						||
| 
								 | 
							
								        waste = ((min(rect.right, seg.right)-max(rect.left, seg.left)) for seg in self._skyline[left_index:right_index+1])
							 | 
						||
| 
								 | 
							
								        return sum(waste)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class SkylineMwfl(Skyline):
							 | 
						||
| 
								 | 
							
								    """Implements Min Waste fit with low profile heuritic, minimizing the area
							 | 
						||
| 
								 | 
							
								    wasted below the rectangle, at the same time it tries to keep the height
							 | 
						||
| 
								 | 
							
								    minimal.
							 | 
						||
| 
								 | 
							
								    """ 
							 | 
						||
| 
								 | 
							
								    def _rect_fitness(self, rect, left_index, right_index):
							 | 
						||
| 
								 | 
							
								        waste = 0
							 | 
						||
| 
								 | 
							
								        for seg in self._skyline[left_index:right_index+1]:
							 | 
						||
| 
								 | 
							
								            waste +=\
							 | 
						||
| 
								 | 
							
								                (min(rect.right, seg.right)-max(rect.left, seg.left)) *\
							 | 
						||
| 
								 | 
							
								                (rect.bottom-seg.top)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return waste*self.width*self.height+rect.top
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class SkylineBl(Skyline):
							 | 
						||
| 
								 | 
							
								    """Implements Bottom Left heuristic, the best fit option is that which
							 | 
						||
| 
								 | 
							
								    results in which the top side of the rectangle lies at the bottom-most 
							 | 
						||
| 
								 | 
							
								    position.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    def _rect_fitness(self, rect, left_index, right_index):
							 | 
						||
| 
								 | 
							
								        return rect.top
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class SkylineBlWm(SkylineBl, SkylineWMixin):
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class SkylineMwfWm(SkylineMwf, SkylineWMixin):
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class SkylineMwflWm(SkylineMwfl, SkylineWMixin):
							 | 
						||
| 
								 | 
							
								    pass
							 |