2025-01-22 16:18:30 +01:00

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