I have been using Py3dbp to pack some items in a container and I got it to work with a Gui so when I select an item in a list in the Gui, the item is highlighted in the container as such:
Now I need to add a walking room, or an empty space in the first container so that a person can walk into the container like this:
What I need to happen is when I turn the walking room to True, the red box is there and the packer or bin class checks for that dimension, and packs the items around the walking room in the same order and priority that the packer class already has set up. I have tried a lot of different ways for the item loading to look at the dimensions but nothing seems to work as I have floating items or items overlapping the walking room.
Here is the Packer class:
class Packer:
def __init__(self):
''' '''
self.bins = []
self.items = []
self.unfit_items = []
self.total_items = 0
self.binding = []
# self.apex = []
def addBin(self, bin):
''' '''
return self.bins.append(bin)
def addItem(self, item):
''' '''
self.total_items = len(self.items) + 1
return self.items.append(item)
def pack2Bin(self, bin, item,fix_point,check_stable,support_surface_ratio):
''' pack item to bin '''
fitted = False
bin.fix_point = fix_point
bin.check_stable = check_stable
bin.support_surface_ratio = support_surface_ratio
# first put item on (0,0,0) , if corner exist ,first add corner in box.
if bin.corner != 0 and not bin.items:
corner_lst = bin.addCorner()
for i in range(len(corner_lst)) :
bin.putCorner(i,corner_lst[i])
elif not bin.items:
response = bin.putItem(item, item.position)
if not response:
bin.unfitted_items.append(item)
return
for axis in range(0, 3):
items_in_bin = bin.items
for ib in items_in_bin:
pivot = [0, 0, 0]
w, h, d = ib.getDimension()
if axis == Axis.WIDTH:
pivot = [ib.position[0] + w,ib.position[1],ib.position[2]]
elif axis == Axis.HEIGHT:
pivot = [ib.position[0],ib.position[1] + h,ib.position[2]]
elif axis == Axis.DEPTH:
pivot = [ib.position[0],ib.position[1],ib.position[2] + d]
if bin.putItem(item, pivot, axis):
fitted = True
break
if fitted:
break
if not fitted:
bin.unfitted_items.append(item)
#########
# walking_room_x = bin.width / 4
# walking_room_y = bin.height / 2
# walking_room_z = bin.depth
# # Iterate over the walking room dimensions
# for x in range(int(walking_room_x), int(walking_room_x) + 24):
# for y in range(int(walking_room_y), int(walking_room_y) + 24):
# for z in range(int(walking_room_z)):
# # Check if the item fits in the walking room and does not intersect with other items
# item.position = [x, y, z]
# if self.isItemFittingInWalkingRoom(item, bin.items):
# # If the item fits, pack it based on the order set in the Packer class
# self.packItemInOrder(bin, item, fix_point, check_stable, support_surface_ratio)
# return
# def isItemFittingInWalkingRoom(self, item, items_in_bin):
# # Check if the item fits within the bin excluding the walking room area
# item_width, item_height, item_depth = item.getDimension()
# for existing_item in items_in_bin:
# # Check for intersection with existing items
# if intersect(existing_item, item):
# return False
# # Access bin dimensions from the first bin in the list (you may need to adapt this based on your logic)
# if not self.bins:
# return False
# bin_width = self.bins[0].width
# bin_height = self.bins[0].height
# # Check if the item fits in the bin excluding the walking room
# if (
# 0 <= item.position[0] < bin_width and
# 0 <= item.position[1] < bin_height and
# 0 <= item.position[2] < item_depth and
# item.position[0] + item_width <= bin_width - int(bin_width / 4) and
# item.position[1] + item_height <= bin_height - int(bin_height / 2) and
# item.position[2] + item_depth <= bin_depth
# ):
# return True
# else:
# return False
######
def packItemInOrder(self, bin, item, fix_point, check_stable, support_surface_ratio):
# Logic to pack the item based on the order set in the Packer class
for axis in range(0, 3):
items_in_bin = bin.items
for ib in items_in_bin:
pivot = [0, 0, 0]
w, h, d = ib.getDimension()
if axis == Axis.WIDTH:
pivot = [ib.position[0] + w, ib.position[1], ib.position[2]]
elif axis == Axis.HEIGHT:
pivot = [ib.position[0], ib.position[1] + h, ib.position[2]]
elif axis == Axis.DEPTH:
pivot = [ib.position[0], ib.position[1], ib.position[2] + d]
if bin.putItem(item, pivot, axis):
return
#######
def sortBinding(self,bin):
''' sorted by binding '''
b,front,back = [],[],[]
for i in range(len(self.binding)):
b.append([])
for item in self.items:
if item.name in self.binding[i]:
b[i].append(item)
elif item.name not in self.binding:
if len(b[0]) == 0 and item not in front:
front.append(item)
elif item not in back and item not in front:
back.append(item)
min_c = min([len(i) for i in b])
sort_bind =[]
for i in range(min_c):
for j in range(len(b)):
sort_bind.append(b[j][i])
for i in b:
for j in i:
if j not in sort_bind:
self.unfit_items.append(j)
self.items = front + sort_bind + back
return
def putOrder(self):
'''Arrange the order of items '''
r = []
for i in self.bins:
# open top container
if i.put_type == 2:
i.items.sort(key=lambda item: item.position[0], reverse=False)
i.items.sort(key=lambda item: item.position[1], reverse=False)
i.items.sort(key=lambda item: item.position[2], reverse=False)
# general container
elif i.put_type == 1:
i.items.sort(key=lambda item: item.position[1], reverse=False)
i.items.sort(key=lambda item: item.position[2], reverse=False)
i.items.sort(key=lambda item: item.position[0], reverse=False)
else :
pass
return
def gravityCenter(self,bin):
'''
Deviation Of Cargo gravity distribution
'''
w = int(bin.width)
h = int(bin.height)
d = int(bin.depth)
area1 = [set(range(0,w//2+1)),set(range(0,h//2+1)),0]
area2 = [set(range(w//2+1,w+1)),set(range(0,h//2+1)),0]
area3 = [set(range(0,w//2+1)),set(range(h//2+1,h+1)),0]
area4 = [set(range(w//2+1,w+1)),set(range(h//2+1,h+1)),0]
area = [area1,area2,area3,area4]
for i in bin.items:
x_st = int(i.position[0])
y_st = int(i.position[1])
if i.rotation_type == 0:
x_ed = int(i.position[0] + i.width)
y_ed = int(i.position[1] + i.height)
elif i.rotation_type == 1:
x_ed = int(i.position[0] + i.height)
y_ed = int(i.position[1] + i.width)
elif i.rotation_type == 2:
x_ed = int(i.position[0] + i.height)
y_ed = int(i.position[1] + i.depth)
elif i.rotation_type == 3:
x_ed = int(i.position[0] + i.depth)
y_ed = int(i.position[1] + i.height)
elif i.rotation_type == 4:
x_ed = int(i.position[0] + i.depth)
y_ed = int(i.position[1] + i.width)
elif i.rotation_type == 5:
x_ed = int(i.position[0] + i.width)
y_ed = int(i.position[1] + i.depth)
x_set = set(range(x_st,int(x_ed)+1))
y_set = set(range(y_st,y_ed+1))
# cal gravity distribution
for j in range(len(area)):
if x_set.issubset(area[j][0]) and y_set.issubset(area[j][1]) :
area[j][2] += int(i.weight)
break
# include x and !include y
elif x_set.issubset(area[j][0]) == True and y_set.issubset(area[j][1]) == False and len(y_set & area[j][1]) != 0 :
y = len(y_set & area[j][1]) / (y_ed - y_st) * int(i.weight)
area[j][2] += y
if j >= 2 :
area[j-2][2] += (int(i.weight) - x)
else :
area[j+2][2] += (int(i.weight) - y)
break
# include y and !include x
elif x_set.issubset(area[j][0]) == False and y_set.issubset(area[j][1]) == True and len(x_set & area[j][0]) != 0 :
x = len(x_set & area[j][0]) / (x_ed - x_st) * int(i.weight)
area[j][2] += x
if j >= 2 :
area[j-2][2] += (int(i.weight) - x)
else :
area[j+2][2] += (int(i.weight) - x)
break
# !include x and !include y
elif x_set.issubset(area[j][0])== False and y_set.issubset(area[j][1]) == False and len(y_set & area[j][1]) != 0 and len(x_set & area[j][0]) != 0 :
all = (y_ed - y_st) * (x_ed - x_st)
y = len(y_set & area[0][1])
y_2 = y_ed - y_st - y
x = len(x_set & area[0][0])
x_2 = x_ed - x_st - x
area[0][2] += x * y / all * int(i.weight)
area[1][2] += x_2 * y / all * int(i.weight)
area[2][2] += x * y_2 / all * int(i.weight)
area[3][2] += x_2 * y_2 / all * int(i.weight)
break
r = [area[0][2],area[1][2],area[2][2],area[3][2]]
result = []
for i in r :
result.append(round(i / sum(r) * 100,2))
return result
def pack(self, bigger_first=False,distribute_items=True,fix_point=True,check_stable=True,support_surface_ratio=0.75,binding=[],number_of_decimals=DEFAULT_NUMBER_OF_DECIMALS):
'''pack master func '''
# set decimals
for bin in self.bins:
bin.formatNumbers(number_of_decimals)
for item in self.items:
item.formatNumbers(number_of_decimals)
# add binding attribute
self.binding = binding
# Bin : sorted by volumn
self.bins.sort(key=lambda bin: bin.getVolume(), reverse=bigger_first)
# Item : sorted by volumn -> sorted by loadbear -> sorted by level -> binding
self.items.sort(key=lambda item: item.getVolume(), reverse=bigger_first)
# self.items.sort(key=lambda item: item.getMaxArea(), reverse=bigger_first)
self.items.sort(key=lambda item: item.loadbear, reverse=True)
self.items.sort(key=lambda item: item.level, reverse=False)
# sorted by binding
if binding != []:
self.sortBinding(bin)
for idx,bin in enumerate(self.bins):
# pack item to bin
for item in self.items:
self.pack2Bin(bin, item, fix_point, check_stable, support_surface_ratio)
if binding != []:
# resorted
self.items.sort(key=lambda item: item.getVolume(), reverse=bigger_first)
self.items.sort(key=lambda item: item.loadbear, reverse=True)
self.items.sort(key=lambda item: item.level, reverse=False)
# clear bin
bin.items = []
bin.unfitted_items = self.unfit_items
bin.fit_items = np.array([[0,bin.width,0,bin.height,0,0]])
# repacking
for item in self.items:
self.pack2Bin(bin, item,fix_point,check_stable,support_surface_ratio)
# Deviation Of Cargo Gravity Center
self.bins[idx].gravity = self.gravityCenter(bin)
if distribute_items :
for bitem in bin.items:
no = bitem.partno
for item in self.items :
if item.partno == no :
self.items.remove(item)
break
# put order of items
self.putOrder()
if self.items != []:
self.unfit_items = copy.deepcopy(self.items)
self.items = []
# for item in self.items.copy():
# if item in bin.unfitted_items:
# self.items.remove(item)
And here is the Bin Class:
class Bin:
def __init__(self, partno, WHD, max_weight,corner=0,put_type=1):
''' '''
self.partno = partno
self.width = WHD[0]
self.height = WHD[1]
self.depth = WHD[2]
self.max_weight = max_weight
self.corner = corner
self.items = []
self.fit_items = np.array([[0,WHD[0],0,WHD[1],0,0]])
self.unfitted_items = []
self.number_of_decimals = DEFAULT_NUMBER_OF_DECIMALS
self.fix_point = False
self.check_stable = False
self.support_surface_ratio = 0
self.put_type = put_type
# used to put gravity distribution
self.gravity = []
def formatNumbers(self, number_of_decimals):
''' '''
self.width = set2Decimal(self.width, number_of_decimals)
self.height = set2Decimal(self.height, number_of_decimals)
self.depth = set2Decimal(self.depth, number_of_decimals)
self.max_weight = set2Decimal(self.max_weight, number_of_decimals)
self.number_of_decimals = number_of_decimals
def string(self):
''' '''
return "%s(%sx%sx%s, max_weight:%s) vol(%s)" % (
self.partno, self.width, self.height, self.depth, self.max_weight,
self.getVolume()
)
def getVolume(self):
''' '''
return set2Decimal(
self.width * self.height * self.depth, self.number_of_decimals
)
def getTotalWeight(self):
''' '''
total_weight = 0
for item in self.items:
total_weight += item.weight
return set2Decimal(total_weight, self.number_of_decimals)
def putItem(self, item, pivot,axis=None):
''' put item in bin '''
#####
####
fit = False
valid_item_position = item.position
item.position = pivot
rotate = RotationType.ALL if item.updown == True else RotationType.Notupdown
for i in range(0, len(rotate)):
item.rotation_type = i
dimension = item.getDimension()
# rotatate
######
# # Ensure the item does not exceed the bin boundaries
# if (
# pivot[0] + dimension[0] > self.width or
# pivot[1] + dimension[1] > self.height or
# pivot[2] + dimension[2] > self.depth
# ):
# continue
# fit = True
# # Check if the item intersects with the walking room boundaries
# walking_room_x = self.width / 4
# walking_room_y = self.height / 2
# walking_room_z = self.depth
# if (
# pivot[0] < walking_room_x or pivot[0] + dimension[0] > walking_room_x + 24 or
# pivot[1] < walking_room_y or pivot[1] + dimension[1] > walking_room_y + 24 or
# pivot[2] + dimension[2] > walking_room_z
# ):
# fit = False
# break
#####
if (
self.width < pivot[0] + dimension[0] or
self.height < pivot[1] + dimension[1] or
self.depth < pivot[2] + dimension[2]
):
continue
fit = True
for current_item_in_bin in self.items:
if intersect(current_item_in_bin, item):
fit = False
break
if fit:
# cal total weight
if self.getTotalWeight() + item.weight > self.max_weight:
print("Too Heavy for container: ", self.item.partno)
fit = False
return fit
# fix point float prob
if self.fix_point == True :
[w,h,d] = dimension
[x,y,z] = [float(pivot[0]),float(pivot[1]),float(pivot[2])]
for i in range(3):
# fix height
y = self.checkHeight([x,x+float(w),y,y+float(h),z,z+float(d)])
# fix width
x = self.checkWidth([x,x+float(w),y,y+float(h),z,z+float(d)])
# fix depth
z = self.checkDepth([x,x+float(w),y,y+float(h),z,z+float(d)])
# check stability on item
# rule :
# 1. Define a support ratio, if the ratio below the support surface does not exceed this ratio, compare the second rule.
# 2. If there is no support under any vertices of the bottom of the item, then fit = False.
if self.check_stable == True :
# Cal the surface area of item.
# item_area_lower = int(dimension[0] * dimension[1])
item_area_lower = dimension[0] * dimension[1]
# Cal the surface area of the underlying support.
support_area_upper = 0
for i in self.fit_items:
# Verify that the lower support surface area is greater than the upper support surface area * support_surface_ratio.
if z == i[5] :
area = len(set([ j for j in range(int(x),int(x+int(w)))]) & set([ j for j in range(int(i[0]),int(i[1]))])) * \
len(set([ j for j in range(int(y),int(y+int(h)))]) & set([ j for j in range(int(i[2]),int(i[3]))]))
support_area_upper += area
# If not , get four vertices of the bottom of the item.
if support_area_upper / item_area_lower < self.support_surface_ratio :
four_vertices = [[x,y],[x+float(w),y],[x,y+float(h)],[x+float(w),y+float(h)]]
# If any vertices is not supported, fit = False.
c = [False,False,False,False]
for i in self.fit_items:
if z == i[5] :
for jdx,j in enumerate(four_vertices) :
if (i[0] <= j[0] <= i[1]) and (i[2] <= j[1] <= i[3]) :
c[jdx] = True
if False in c :
item.position = valid_item_position
fit = False
return fit
self.fit_items = np.append(self.fit_items,np.array([[x,x+float(w),y,y+float(h),z,z+float(d)]]),axis=0)
item.position = [set2Decimal(x),set2Decimal(y),set2Decimal(z)]
if fit :
self.items.append(copy.deepcopy(item))
# indented line
else :
item.position = valid_item_position
return fit
else :
item.position = valid_item_position
return fit
def checkDepth(self,unfix_point):
''' fix item position z '''
z_ = [[0,0],[float(self.depth),float(self.depth)]]
for j in self.fit_items:
# creat x set
x_bottom = set([i for i in range(int(j[0]),int(j[1]))])
x_top = set([i for i in range(int(unfix_point[0]),int(unfix_point[1]))])
# creat y set
y_bottom = set([i for i in range(int(j[2]),int(j[3]))])
y_top = set([i for i in range(int(unfix_point[2]),int(unfix_point[3]))])
# find intersection on x set and y set.
if len(x_bottom & x_top) != 0 and len(y_bottom & y_top) != 0 :
z_.append([float(j[4]),float(j[5])])
top_depth = unfix_point[5] - unfix_point[4]
# find diff set on z_.
z_ = sorted(z_, key = lambda z_ : z_[1])
for j in range(len(z_)-1):
if z_[j+1][0] -z_[j][1] >= top_depth:
return z_[j][1]
return unfix_point[4]
def checkWidth(self,unfix_point):
''' fix item position x '''
x_ = [[0,0],[float(self.width),float(self.width)]]
for j in self.fit_items:
# creat z set
z_bottom = set([i for i in range(int(j[4]),int(j[5]))])
z_top = set([i for i in range(int(unfix_point[4]),int(unfix_point[5]))])
# creat y set
y_bottom = set([i for i in range(int(j[2]),int(j[3]))])
y_top = set([i for i in range(int(unfix_point[2]),int(unfix_point[3]))])
# find intersection on z set and y set.
if len(z_bottom & z_top) != 0 and len(y_bottom & y_top) != 0 :
x_.append([float(j[0]),float(j[1])])
top_width = unfix_point[1] - unfix_point[0]
# find diff set on x_bottom and x_top.
x_ = sorted(x_,key = lambda x_ : x_[1])
for j in range(len(x_)-1):
if x_[j+1][0] -x_[j][1] >= top_width:
return x_[j][1]
return unfix_point[0]
def checkHeight(self,unfix_point):
'''fix item position y '''
y_ = [[0,0],[float(self.height),float(self.height)]]
for j in self.fit_items:
# creat x set
x_bottom = set([i for i in range(int(j[0]),int(j[1]))])
x_top = set([i for i in range(int(unfix_point[0]),int(unfix_point[1]))])
# creat z set
z_bottom = set([i for i in range(int(j[4]),int(j[5]))])
z_top = set([i for i in range(int(unfix_point[4]),int(unfix_point[5]))])
# find intersection on x set and z set.
if len(x_bottom & x_top) != 0 and len(z_bottom & z_top) != 0 :
y_.append([float(j[2]),float(j[3])])
top_height = unfix_point[3] - unfix_point[2]
# find diff set on y_bottom and y_top.
y_ = sorted(y_,key = lambda y_ : y_[1])
for j in range(len(y_)-1):
if y_[j+1][0] -y_[j][1] >= top_height:
return y_[j][1]
return unfix_point[2]
def addCorner(self):
'''add container coner '''
if self.corner != 0 :
corner = set2Decimal(self.corner)
corner_list = []
for i in range(8):
a = Item(
partno='corner{}'.format(i),
name='corner',
typeof='cube',
WHD=(corner,corner,corner),
weight=0,
level=0,
loadbear=0,
updown=True,
color='#000000'
# color='#fa0202'
)
corner_list.append(a)
return corner_list
def putCorner(self,info,item):
'''put coner in bin '''
fit = False
x = set2Decimal(self.width - self.corner)
y = set2Decimal(self.height - self.corner)
z = set2Decimal(self.depth - self.corner)
pos = [[0,0,0],[0,0,z],[0,y,z],[0,y,0],[x,y,0],[x,0,0],[x,0,z],[x,y,z]]
item.position = pos[info]
self.items.append(item)
corner = [float(item.position[0]),float(item.position[0])+float(self.corner),float(item.position[1]),float(item.position[1])+float(self.corner),float(item.position[2]),float(item.position[2])+float(self.corner)]
self.fit_items = np.append(self.fit_items,np.array([corner]),axis=0)
return
def clearBin(self):
''' clear item which in bin '''
self.items = []
self.fit_items = np.array([[0,self.width,0,self.height,0,0]])
return
I have commented out the areas I generally work in to try to get the dimensions lined up. This script has become quite large so not sure if I need to add the entire thing but any ideas or related issues would help out a lot.
In my mind, Logic should be get bin/container size, determine if and where the walking room dimensions will be, adjust how to pack items in the order the Packer has set and remove the items that will not fit bin, plot items in the Painter (a class using matplotlib-3d) and add to Gui


