Skip to content

Instantly share code, notes, and snippets.

@hamidzr
Last active July 27, 2023 06:38
Show Gist options
  • Select an option

  • Save hamidzr/5ddf6fd96b67e5fa371e121d5c651309 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown
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