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