Last active
July 27, 2023 06:38
-
-
Save hamidzr/5ddf6fd96b67e5fa371e121d5c651309 to your computer and use it in GitHub Desktop.
plan and calculate how a list of items can fit in a 2d space. 2d binpacking
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from typing import NamedTuple, List | |
from rectpack import newPacker | |
import argparse | |
import re | |
import matplotlib.pyplot as plt | |
import matplotlib.patches as patches | |
import dataclasses | |
Item = NamedTuple("Item", [("name", str), ("width", float), ("depth", float)]) | |
@dataclasses.dataclass | |
class Rect(): | |
rid: str # name of the item | |
x: float | |
y: float | |
width: float | |
height: float | |
def convert_to_feet(measurement): | |
value, unit = measurement[:-2], measurement[-2:] | |
value = float(value) | |
if unit == 'in': | |
value /= 12 | |
return value | |
def solve_packing_problem(storage_width, storage_depth, items: List[Item]) -> List[List[Rect]]: | |
packer = newPacker() | |
for item in items: | |
packer.add_rect(*item[1:], item[0]) | |
packer.add_bin(storage_width, storage_depth) | |
packer.pack() | |
packed_rectangles = [] | |
for abin in packer: | |
rects: List[Rect] = [] | |
for rect in abin.rect_list(): | |
x, y, width, height, rid = rect | |
rects.append(Rect(rid, x, y, width, height)) | |
packed_rectangles.append(rects) | |
return packed_rectangles | |
def pad_item(item: Item, padding: float) -> Item: | |
new_name = item.name | |
return Item(new_name, item.width + padding, item.depth + padding) | |
def visualize_packing(packed_rectangles: List[List[Rect]], storage_width, storage_depth): | |
fig, ax = plt.subplots() | |
for bin_rectangles in packed_rectangles: | |
for rect in bin_rectangles: | |
ax.add_patch(patches.Rectangle((rect.x, rect.y), rect.width, rect.height, edgecolor='black', facecolor='gray', alpha=0.5)) | |
# optionally add the item name as text to the center of the rectangle | |
rotation = 0 if rect.width >= rect.height else 90 | |
ax.text(rect.x + rect.width/2, rect.y + rect.height/2, rect.rid, color='black', ha='center', va='center', rotation=rotation) | |
ax.set_xlim([0, storage_width]) | |
ax.set_ylim([0, storage_depth]) | |
ax.set_xlabel('Width') | |
ax.set_ylabel('Depth') | |
ax.set_title('Arrangement of items in the storage bin') | |
ax.set_aspect('equal') | |
plt.savefig('arrangement.png') | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser(description='Calculate the best arrangement of items in a storage unit.') | |
parser.add_argument('--storage', type=float, nargs=2, required=True, help='The width and depth of the storage unit') | |
parser.add_argument('--items', type=str, required=True, help='Path to a file containing the items. Each line in the file should represent an item and be formatted as name,width,depth.') | |
# arg for padding in ft | |
parser.add_argument('--padding', type=float, default=1/12, help='Padding to add to each item in ft') | |
args = parser.parse_args() | |
storage_width, storage_depth = args.storage | |
items = [] | |
with open(args.items, 'r') as file: | |
for line in file: | |
# assume # is comment | |
line = re.sub(r'#.*', '', line) | |
if not line.strip(): | |
continue | |
name, width, depth = [part.strip() for part in line.strip().split(',')] | |
item = Item(name, convert_to_feet(width), convert_to_feet(depth)) | |
item = pad_item(item, args.padding) | |
items.append(item) | |
packed_rectangles = solve_packing_problem(storage_width, storage_depth, items) | |
visualize_packing(packed_rectangles, storage_width, storage_depth) | |
no_fit = [item.name for item in items if item.name not in [rect.rid for rect in packed_rectangles[0]]] | |
if no_fit: | |
print('The following items did not fit') | |
print(', '.join(no_fit)) | |
exit(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
sample