Skip to content

Instantly share code, notes, and snippets.

@hamidzr
Last active July 27, 2023 06:38
Show Gist options
  • Save hamidzr/5ddf6fd96b67e5fa371e121d5c651309 to your computer and use it in GitHub Desktop.
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
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)
@hamidzr
Copy link
Author

hamidzr commented Jul 27, 2023

arrangement
sample

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment