diff --git a/README.md b/README.md index df6e251..631db06 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,26 @@ -# rectpack [![Build Status](https://travis-ci.org/secnot/rectpack.svg?branch=master)](https://travis-ci.org/secnot/rectpack) +# SolPacker Python version [![Build Status](https://travis-ci.org/secnot/rectpack.svg?branch=master)](https://travis-ci.org/secnot/rectpack) + +Packing 3D cuboids with preset items based on rectpack +Copyright (c) 2020 - Loc Nguyen +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. Rectpack is a collection of heuristic algorithms for solving the 2D knapsack problem, @@ -23,221 +45,83 @@ pip install rectpack ``` ## Basic Usage - -Packing rectangles into a number of bins is very simple: - -```python -from rectpack import newPacker - -rectangles = [(100, 30), (40, 60), (30, 30),(70, 70), (100, 50), (30, 30)] -bins = [(300, 450), (80, 40), (200, 150)] - -packer = newPacker() - -# Add the rectangles to packing queue -for r in rectangles: - packer.add_rect(*r) - -# Add the bins where the rectangles will be placed -for b in bins: - packer.add_bin(*b) - -# Start packing -packer.pack() -``` - -Once the rectangles have been packed the results can be accessed individually - -```python -# Obtain number of bins used for packing -nbins = len(packer) - -# Index first bin -abin = packer[0] - -# Bin dimmensions (bins can be reordered during packing) -width, height = abin.width, abin.height - -# Number of rectangles packed into first bin -nrect = len(packer[0]) - -# Second bin first rectangle -rect = packer[1][0] - -# rect is a Rectangle object -x = rect.x # rectangle bottom-left x coordinate -y = rect.y # rectangle bottom-left y coordinate -w = rect.width -h = rect.height -``` - -looping over all of them - -```python -for abin in packer: - print(abin.bid) # Bin id if it has one - for rect in abin: - print(rect) -``` - -or using **rect_list()** - -```python -# Full rectangle list -all_rects = packer.rect_list() -for rect in all_rects: - b, x, y, w, h, rid = rect - -# b - Bin index -# x - Rectangle bottom-left corner x coordinate -# y - Rectangle bottom-left corner y coordinate -# w - Rectangle width -# h - Rectangle height -# rid - User asigned rectangle id or None -``` - -Lastly all the dimmension (bins and rectangles) must be integers or decimals to avoid -collisions caused by floating point rounding. If your data is floating point use -float2dec to convert float values to decimals (see float below) - - -## API - -A more detailed description of API calls: - -* class **newPacker**([, mode][, bin_algo][, pack_algo][, sort_algo][, rotation]) - Return a new packer object - * mode: Mode of operations - * PackingMode.Offline: The set of rectangles is known beforehand, packing won't - start until *pack()* is called. - * PackingMode.Online: The rectangles are unknown at the beginning of the job, and - will be packed as soon as they are added. - * bin_algo: Bin selection heuristic - * PackingBin.BNF: (Bin Next Fit) If a rectangle doesn't fit into the current bin, - close it and try next one. - * PackingBin.BFF: (Bin First Fit) Pack rectangle into the first bin it fits (without closing) - * PackingBin.BBF: (Bin Best Fit) Pack rectangle into the bin that gives best fitness. - * PackingBin.Global: For each bin pack the rectangle with the best fitness until it is full, - then continue with next bin. - * pack_algo: One of the supported packing algorithms (see list below) - * sort_algo: Rectangle sort order before packing (only for offline mode) - * SORT_NONE: Rectangles left unsorted. - * SORT_AREA: Sort by descending area. - * SORT_PERI: Sort by descending perimeter. - * SORT_DIFF: Sort by difference of rectangle sides. - * SORT_SSIDE: Sort by shortest side. - * SORT_LSIDE: Sort by longest side. - * SORT_RATIO: Sort by ration between sides. - * rotation: Enable or disable rectangle rotation. - - -* packer.**add_bin**(width, height[, count][, bid]) - Add empty bin or bins to a packer - * width: Bin width - * height: Bin height - * count: Number of bins to add, 1 by default. It's possible to add infinie bins - with *count=float("inf")* - * bid: Optional bin identifier - - -* packer.**add_rect**(width, height[, rid]) - Add rectangle to packing queue - * width: Rectangle width - * height: Rectangle height - * rid: User assigned rectangle id - - -* packer.**pack**(): - Starts packing process (only for offline mode). - - -* packer.**rect_list**(): - Returns the list of packed rectangles, each one represented by the tuple (b, x, y, w, h, rid) where: - * b: Index for the bin the rectangle was packed into - * x: X coordinate for the rectangle bottom-left corner - * y: Y coordinate for the rectangle bottom-left corner - * w: Rectangle width - * h: Rectangle height - * rid: User provided id or None - - -## Supported Algorithms - -This library implements three of the algorithms described in [1] Skyline, Maxrects, -and Guillotine, with the following variants: - -* MaxRects - * MaxRectsBl - * MaxRectsBssf - * MaxRectsBaf - * MaxRectsBlsf - - -* Skyline - * SkylineBl - * SkylineBlWm - * SkylineMwf - * SkylineMwfl - * SkylineMwfWm - * SkylineMwflWm - - -* Guillotine - * GuillotineBssfSas - * GuillotineBssfLas - * GuillotineBssfSlas - * GuillotineBssfLlas - * GuillotineBssfMaxas - * GuillotineBssfMinas - * GuillotineBlsfSas - * GuillotineBlsfLas - * GuillotineBlsfSlas - * GuillotineBlsfLlas - * GuillotineBlsfMaxas - * GuillotineBlsfMinas - * GuillotineBafSas - * GuillotineBafLas - * GuillotineBafSlas - * GuillotineBafLlas - * GuillotineBafMaxas - * GuillotineBafMinas - -I recommend to use the default algorithm unless the packing is too slow, in that -case switch to one of the Guillotine variants for example *GuillotineBssfSas*. -You can learn more about the algorithms in [1]. - -## Testing - -Rectpack is thoroughly tested, run the tests with: - -```bash -python setup.py test -``` - -or - -```bash -python -m unittest discover -``` - -## Float - -If you need to use floats just convert them to fixed-point using a Decimal type, -be carefull rounding up so the actual rectangle size is always smaller than -the conversion. Rectpack provides helper funcion **float2dec** for this task, -it accepts a number and the number of decimals to round to, and returns -the rounded Decimal. - -```python - from rectpack import float2dec, newPacker - - float_rects = [...] - dec_rects = [(float2dec(r[0], 3), float2dec(r[1], 3)) for r in float_rects] - - p = newPacker() - ... -``` - + +# -*- coding: utf-8 -*- +""" +Test 3D Bin Packing +Author: Loc Nguyen +""" +from rectpack.packer import SolPalletization +from rectpack import * + +import random +import time +import random + + +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.patches import Rectangle +from PIL import Image + +preset_cuboids =[[0,0,0,50,50,60],[50,0,0,50,50,150],[200,200,0,50,50,300]] +pack_cuiboid=[0,0,0,40,50,10] +bin_size = (300,300,450) + +#generate grid +grid_size_w=28 +grid_size_h=28 + +step_w=bin_size[0]/grid_size_w +step_h=bin_size[1]/grid_size_h + +for i in range(grid_size_w): + for j in range(grid_size_h): + if random.uniform(0,1)>0.5: + preset_cuboids.append([step_w*i,step_h*j,0,step_w,step_h,random.uniform(bin_size[2]/5, bin_size[2]*5/6) ]) + +pack3D=SolPalletization(_bin_size=bin_size) +print("Bin Size (W,H,D)=",pack3D.bin_size) +#print("free rect number=",len(pack3D.packer2D._max_rects)) +start= time.time() +success,pack_pose=pack3D.pack(preset_cuboids,pack_cuiboid,box_pose=np.identity(4),pick_pose=np.identity(4), level_num=20,display2D=True) + +end = time.time() +print("Pack time=", end - start) + +#DISPLAY by ROS scene +if success: + print("SUCCESS! pack_pose=\n",pack_pose) + file1 = open("D:\\packing.scene","w") + + # \n is placed to indicate EOL (End of Line) + file1.write("Scene Objects for ROS \n") + + file1.writelines("BIN\n\n") + file1.writelines("1\nbox\n") + file1.writelines(str(bin_size[0])+" "+str(bin_size[1])+" "+str(bin_size[2])+"\n") + file1.writelines(str(bin_size[0]/2)+" "+str(bin_size[1]/2)+" "+str(bin_size[2]/2)+"\n") + file1.writelines("0 0 0 1"+"\n") + file1.writelines("0 0 0 0"+"\n") + + file1.writelines("PACKED_CUIBOID\n\n") + file1.writelines("1\nbox\n") + file1.writelines(str(pack3D.result[3])+" "+str(pack3D.result[4])+" "+str(pack3D.result[5])+"\n") + file1.writelines(str(pack3D.result[0]+pack3D.result[3]/2)+" "+str(pack3D.result[1]+pack3D.result[4]/2)+" "+str(pack3D.result[2]+pack3D.result[5]/2)+"\n") + file1.writelines("0 0 0 1"+"\n") + file1.writelines("0 0 0 0"+"\n") + + + for i,cuboid in enumerate(preset_cuboids): + file1.writelines("cuboid "+str(i)+"\n\n") + file1.writelines("1\nbox\n") + file1.writelines(str(cuboid[3])+" "+str(cuboid[4])+" "+str(cuboid[5])+"\n") + file1.writelines(str(cuboid[0]+cuboid[3]/2)+" "+str(cuboid[1]+cuboid[4]/2)+" "+str(cuboid[2]+cuboid[5]/2)+"\n") + file1.writelines("0 0 0 1"+"\n") + file1.writelines("0 0 0 0"+"\n") + file1.close() #to change file access modes + +#=========================================== ## References [1] Jukka Jylang - A Thousand Ways to Pack the Bin - A Practical Approach to Two-Dimensional diff --git a/TEST_2DBIN_PACKER.py b/TEST_2DBIN_PACKER.py new file mode 100644 index 0000000..7dc30ac --- /dev/null +++ b/TEST_2DBIN_PACKER.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +""" +Test 2D Bin Packing +Author: +""" +from rectpack.packer import newPacker +from rectpack import * + +import random +import time + + +import matplotlib.pyplot as plt +from matplotlib.patches import Rectangle +from PIL import Image + + + +rectangles = [(100, 30), (40, 60), (30, 30),(70, 70), (100, 50), (30, 30),(55,77),(48,72)] +bins = [(150, 170)]#[(300, 450), (80, 40), (200, 150)] + +packer = newPacker(mode=PackingMode.Online, + bin_algo=PackingBin.BBF, + pack_algo=MaxRectsBssf, + sort_algo=SORT_AREA, + rotation=True) + + + +plt.figure(figsize=(10,10)) +plt.xlabel('Bin Packing 2D - SolPacker') +plt.xlim(0,bins[0][0]) +plt.ylim(0,bins[0][1]) +# Get the current reference +ax = plt.gca() + +print("display finished!") + +# Add the bins where the rectangles will be placed +for b in bins: + packer.add_bin(*b) + +count=0 +# INITIALIZE +r_list=[(89, 36),( 49, 87),(84, 39),(69, 74)] +r_preset=[(0, 0, 36, 89),(36, 0, 87, 49),(36, 49, 39, 84),(75, 49, 74, 69)] + +start= time.time() + +#Octomap +packer.add_preset_rect(*r_preset[0]) +packer.add_preset_rect(*r_preset[1]) #packer.py line 184 +packer.add_preset_rect(*r_preset[2]) +packer.add_preset_rect(*r_preset[3]) + +#Pack last item +rect=packer.add_rect(24, 36) +print("---",rect) +rect=packer.add_rect(240, 260) +print("---",rect) +rect=packer.add_rect(24, 46) +print("---",rect) +rect=packer.add_rect(24, 86) +print("---",rect) +#Display +count=0 +for abin in packer: + print(abin.bid) # Bin id if it has one + for rect in abin: + # Create a Rectangle patch + rect2D = Rectangle((rect.x,rect.y),rect.width,rect.height,linewidth=2,edgecolor=(0,0,0),facecolor=(random.uniform(0.2, 1), random.uniform(0.2, 1), random.uniform(0.2, 1))) + + # Add the patch to the Axes + ax.add_patch(rect2D) + count+=1 + ax.text(rect.x+rect.width/2,rect.y+rect.height/2, str(count), fontsize=15) + print(rect) + +end = time.time() +print("Pack time=", end - start) diff --git a/TEST_3DBIN_PACKER.py b/TEST_3DBIN_PACKER.py new file mode 100644 index 0000000..790231d --- /dev/null +++ b/TEST_3DBIN_PACKER.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +""" +Test 3D Bin Packing +Author: Loc Nguyen +""" +from rectpack.packer import SolPalletization +from rectpack import * + +import random +import time +import random + + +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.patches import Rectangle +from PIL import Image + +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + + + +preset_cuboids =[]# [[0,0,0,50,50,60],[50,0,0,50,50,150],[200,200,0,50,50,300]] +pack_cuiboid=[0,0,0,40,50,10] +bin_size = (300,300,450) + +#generate grid +grid_size_w=28 +grid_size_h=28 + +step_w=bin_size[0]/grid_size_w +step_h=bin_size[1]/grid_size_h + +for i in range(grid_size_w): + for j in range(grid_size_h): + if random.uniform(0,1)>0.5: + preset_cuboids.append([step_w*i,step_h*j,0,step_w,step_h,random.uniform(bin_size[2]/5, bin_size[2]*5/6) ]) + + + +pack3D=SolPalletization(_bin_size=bin_size) +print("Bin Size (W,H,D)=",pack3D.bin_size) +#print("free rect number=",len(pack3D.packer2D._max_rects)) +start= time.time() +success,pack_pose,pack_cuboid3D=pack3D.pack(preset_cuboids,pack_cuiboid,box_pose=np.identity(4),pick_pose=np.identity(4), level_num=20,display2D=True) + +end = time.time() +print("Pack time=", end - start) + +#DISPLAY by ROS scene +if success: + print("SUCCESS! \npack_pose=\n",pack_pose) + print("pack_cuboid3D=\n",pack_cuboid3D) + file1 = open("D:\\packing.scene","w") + + # \n is placed to indicate EOL (End of Line) + file1.write("Scene Objects for ROS \n") + + file1.writelines("BIN\n\n") + file1.writelines("1\nbox\n") + file1.writelines(str(bin_size[0])+" "+str(bin_size[1])+" "+str(bin_size[2])+"\n") + file1.writelines(str(bin_size[0]/2)+" "+str(bin_size[1]/2)+" "+str(bin_size[2]/2)+"\n") + file1.writelines("0 0 0 1"+"\n") + file1.writelines("0 0 0 0"+"\n") + + file1.writelines("PACKED_CUIBOID\n\n") + file1.writelines("1\nbox\n") + file1.writelines(str(pack_cuboid3D[3])+" "+str(pack_cuboid3D[4])+" "+str(pack_cuboid3D[5])+"\n") + file1.writelines(str(pack_cuboid3D[0]+pack_cuboid3D[3]/2)+" "+str(pack_cuboid3D[1]+pack_cuboid3D[4]/2)+" "+str(pack_cuboid3D[2]+pack_cuboid3D[5]/2)+"\n") + file1.writelines("0 0 0 1"+"\n") + file1.writelines("0 0 0 0"+"\n") + + + for i,cuboid in enumerate(preset_cuboids): + file1.writelines("cuboid "+str(i)+"\n\n") + file1.writelines("1\nbox\n") + file1.writelines(str(cuboid[3])+" "+str(cuboid[4])+" "+str(cuboid[5])+"\n") + file1.writelines(str(cuboid[0]+cuboid[3]/2)+" "+str(cuboid[1]+cuboid[4]/2)+" "+str(cuboid[2]+cuboid[5]/2)+"\n") + file1.writelines("0 0 0 1"+"\n") + file1.writelines("0 0 0 0"+"\n") + file1.close() #to change file access modes + +#=========================================== +''' +# prepare some coordinates +x, y, z = np.indices((28, 28,28)) + +# draw cuboids in the top left and bottom right corners, and a link between them +voxels = (x < 0) & (y < 0) & (z <0) + + + +for i,cuboid in enumerate(preset_cuboids): + #if i<4: + c2 = (x >= cuboid[0]/step_w) &(x <(cuboid[0]+cuboid[3])/step_w)& (y >= cuboid[1]/step_h) &(y <(cuboid[1]+cuboid[4])/step_h)& (z >= cuboid[2]*8/bin_size[2]) &(z <(cuboid[2]+cuboid[5])*8/bin_size[2]) + voxels = voxels | c2 + +# and plot everything +fig = plt.figure() +ax = fig.gca(projection='3d') +ax.voxels(voxels, edgecolor='k')#colors facecolors='y', + +plt.show() +''' +#=========================================== + \ No newline at end of file diff --git a/cube3d_display_test.py b/cube3d_display_test.py new file mode 100644 index 0000000..faae747 --- /dev/null +++ b/cube3d_display_test.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Jun 17 11:00:58 2020 + +@author: Loc +""" + +import matplotlib.pyplot as plt +import numpy as np + +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + + +# prepare some coordinates +x, y, z = np.indices((8, 8, 8)) + +# draw cuboids in the top left and bottom right corners, and a link between them +cube1 = (x < 3) & (y < 3) & (z < 3) +cube2 = (x >= 5) & (y >= 5) & (z >= 5) +link = abs(x - y) + abs(y - z) + abs(z - x) <= 2 + +# combine the objects into a single boolean array +voxels = cube1 | cube2# | link + +# set the colors of each object +colors = np.empty(voxels.shape, dtype=object) +colors[link] = 'red' +colors[cube1] = 'blue' +colors[cube2] = 'green' + +# and plot everything +fig = plt.figure() +ax = fig.gca(projection='3d') +ax.voxels(voxels, facecolors=colors, edgecolor='k') + +plt.show() \ No newline at end of file diff --git a/docs/maxrects.png b/docs/maxrects.png index 0491f05..29579f2 100644 Binary files a/docs/maxrects.png and b/docs/maxrects.png differ diff --git a/docs/skyline.png b/docs/skyline.png index c433a41..b9376ba 100644 Binary files a/docs/skyline.png and b/docs/skyline.png differ diff --git a/rectpack/maxrects.py b/rectpack/maxrects.py index 5af8d61..12136e1 100644 --- a/rectpack/maxrects.py +++ b/rectpack/maxrects.py @@ -10,9 +10,12 @@ class MaxRects(PackingAlgorithm): - + minWH=0.000000001 def __init__(self, width, height, rot=True, *args, **kwargs): super(MaxRects, self).__init__(width, height, rot, *args, **kwargs) + #self.minWH=0.000000001 + self.current_pack2D_result=None + #print("base init...") def _rect_fitness(self, max_rect, width, height): """ @@ -82,13 +85,13 @@ def _generate_splits(self, m, r): """ new_rects = [] - if r.left > m.left: + if r.left - m.left>=self.minWH:#ORIGINAL CODE: r.left > m.left new_rects.append(Rectangle(m.left, m.bottom, r.left-m.left, m.height)) - if r.right < m.right: + if m.right-r.right >=self.minWH: new_rects.append(Rectangle(r.right, m.bottom, m.right-r.right, m.height)) - if r.top < m.top: + if m.top-r.top>=self.minWH: new_rects.append(Rectangle(m.left, r.top, m.width, m.top-r.top)) - if r.bottom > m.bottom: + if r.bottom - m.bottom>=self.minWH: new_rects.append(Rectangle(m.left, m.bottom, m.width, r.bottom-m.bottom)) return new_rects @@ -169,7 +172,8 @@ def add_rect(self, width, height, rid=None): # Search best position and orientation rect, _ = self._select_position(width, height) - if not rect: + self.current_pack2D_result=rect + if not rect: return None # Subdivide all the max rectangles intersecting with the selected @@ -184,6 +188,36 @@ def add_rect(self, width, height, rid=None): self.rectangles.append(rect) return rect + def add_preset_rect(self, x,y,width, height, rid=None): + """ + Add rectangle of widthxheight dimensions. + + Arguments: + width (int, float): Rectangle width + height (int, float): Rectangle height + rid: Optional rectangle user id + + Returns: + Rectangle: Rectangle with placemente coordinates + None: If the rectangle couldn be placed. + """ + assert(width > 0 and height >0) + + # Search best position and orientation + rect =Rectangle(x,y,width, height) + + # Subdivide all the max rectangles intersecting with the selected + # rectangle. + self._split(rect) + + # Remove any max_rect contained by another + self._remove_duplicates() + + # Store and return rectangle position. + rect.rid = rid + self.rectangles.append(rect) + return rect + def reset(self): super(MaxRects, self).reset() self._max_rects = [Rectangle(0, 0, self.width, self.height)] @@ -217,13 +251,20 @@ def _select_position(self, w, h): return Rectangle(m.x, m.y, w, h), m -class MaxRectsBssf(MaxRects): +class MaxRectsBssf(MaxRects): + def print_value(): + print("=====value=123") """Best Sort Side Fit minimize short leftover side""" def _rect_fitness(self, max_rect, width, height): if width > max_rect.width or height > max_rect.height: return None - return min(max_rect.width-width, max_rect.height-height) + #Loc 20200618 replace condition + #return min(max_rect.width-width, max_rect.height-height) + #pack along Y + #return max_rect.x+max_rect.y+height/2+min(max_rect.width-width, max_rect.height-height) + #pack along X + return max_rect.x+max_rect.y+width/2+min(max_rect.width-width, max_rect.height-height) class MaxRectsBaf(MaxRects): """Best Area Fit pick maximal rectangle with smallest area diff --git a/rectpack/packer.py b/rectpack/packer.py index dba3f07..282b496 100644 --- a/rectpack/packer.py +++ b/rectpack/packer.py @@ -4,7 +4,21 @@ import itertools import collections +import time import decimal +from operator import itemgetter + + +import random +import time + +import numpy as np +from numpy.linalg import inv +from scipy.spatial.transform import Rotation as RotMat + +import matplotlib.pyplot as plt +from matplotlib.patches import Rectangle +from PIL import Image # Float to Decimal helper def float2dec(ft, decimal_digits): @@ -164,8 +178,9 @@ def add_rect(self, width, height, rid=None): fit = (b for b in fit if b[0] is not None) try: _, best_bin = min(fit, key=self.first_item) - best_bin.add_rect(width, height, rid) - return True + + return best_bin.add_rect(width, height, rid) + #return True except ValueError: pass @@ -178,10 +193,33 @@ def add_rect(self, width, height, rid=None): # _new_open_bin may return a bin that's too small, # so we have to double-check - if new_bin.add_rect(width, height, rid): - return True + return new_bin.add_rect(width, height, rid) + #if self.result2D: + # return True + def add_preset_rect(self, x, y , width, height, rid=None): + + # Try packing into open bins + fit = ((b.fitness(width, height), b) for b in self._open_bins) + fit = (b for b in fit if b[0] is not None) + try: + _, best_bin = min(fit, key=self.first_item) + best_bin.add_preset_rect(x,y,width, height, rid) + return True + except ValueError: + pass + # Try packing into one of the empty bins + while True: + # can we find an unopened bin that will hold this rect? + new_bin = self._new_open_bin(width, height, rid=rid) + if new_bin is None: + return False + + # _new_open_bin may return a bin that's too small, + # so we have to double-check + if new_bin.add_preset_rect(x,y,width, height, rid): + return True class PackerOnline(object): """ @@ -578,3 +616,163 @@ def newPacker(mode=PackingMode.Offline, return packer_class(pack_algo=pack_algo, rotation=rotation) +class SolPalletization: + """ + SolPalletization for 3D packing with point cloud + + Note: Rotation is Euler Angle following Fanuc Robot definition: rx,ry,rz + cuboid(x,y,z,w,h,d) + Arguments: + Bin Size: (Width,Height,Depth) + Preset Cuboids: [(x,y,z,w,h,d)] + Pack N Cuboids:[(w,h,d)] + Robot N Picking Pose: [(x,y,z,rx,ry,rz)] + + Returns: + Pack N Cuboids Pose:[(x,y,z,rx,ry,rz)] + """ + def __init__(self, _bin_size=(600,380,450),_bin_pose=np.identity(4), rot=True): + self.bin_size=_bin_size + self.bin_width=_bin_size[0] + self.bin_height=_bin_size[1] + self.bin_depth=_bin_size[2] + self.bin_pose=_bin_pose + def get_level_list(self, preset_cuboid=[],pack_cuboid=[], level_num=20): + search_list=[] + total_area = self.bin_width * self.bin_height + pack_cuboid_area=pack_cuboid[3]*pack_cuboid[4] + + #get list of (depth, area) for cuboid + level_list= [ [x[2]+x[5],x[3]*x[4]] for x in preset_cuboid if (x[2]+x[5])>0 and (x[2]+x[5]+pack_cuboid[5])total_area): + delete_indices.append( list(range(i,len(level_list))) ) + break #skip checking other cuboids + + #keep level only + level_list_filter = [v for i,v in enumerate(level_list) if i not in delete_indices] + + #remove level that available not fit + if len(level_list_filter)>level_num and level_num>0: + max_level=self.bin_depth#level_list_filter[0][0] + min_level=0#level_list_filter[len(level_list_filter)-1][0] + #Loop for level_num + step=(max_level-min_level)/level_num + + start_index=0 + + # search all + for i in range(-1,level_num): + group_i=[v[0] for v in level_list_filter if v[0]>min_level+i*step and v[0]<=min_level+(i+1)*step] + if (len(group_i)>0): + search_list.append(max(group_i)) + return search_list + else: + level_list_filter.sort(key=itemgetter(0)) + return [v[0] for v in level_list_filter] + + + def pack(self, preset_cuboid=[],pack_cuboid=[],box_pose=np.identity(4),pick_pose=np.identity(4),level_num=20, display2D=False): + #Get level list + level_list=self.get_level_list(preset_cuboid,pack_cuboid,level_num) + print("Number of searching level=",len(level_list)) + print("List of searching level=",level_list) + + #Start Packing + for i,level in enumerate(level_list): + start= time.time() + packer = newPacker(mode=PackingMode.Online, + bin_algo=PackingBin.BBF, + pack_algo=MaxRectsBssf,#( self.bin_width,self.bin_height), + sort_algo=SORT_AREA, + rotation=True) + packer.add_bin(self.bin_width,self.bin_height) + + + #Note: packer._pack_alg is a class name + #minWH is class attribute + packer._pack_algo.minWH= min(pack_cuboid[3],pack_cuboid[4]) + + for cuboid in preset_cuboid: + if(cuboid[2]+cuboid[5]>level): + packer.add_preset_rect(cuboid[0],cuboid[1],cuboid[3],cuboid[4]) + + print("packer._pack_algo.minWH=",packer._pack_algo.minWH) + + end_preset = time.time() + + #print("pack2d result=",packer._pack_algo.current_pack2D_result) + if (packer.add_rect(pack_cuboid[3], pack_cuboid[4])): + print("SUCCESS level=",i," depth value=",level) + current_pack2D_result=packer[0][len(packer[0])-1] + pack_cuboid3D=[current_pack2D_result.x,current_pack2D_result.y,level,current_pack2D_result.width, current_pack2D_result.height,pack_cuboid[5]] + + #pick pose in box pose coordinate + pick_pose_2_box_pose=(inv(box_pose)).dot(pick_pose) + + pack_pose=np.identity(4) + #checking rotatation of box + if abs(current_pack2D_result.width-pack_cuboid[3])<0.00001 and abs(current_pack2D_result.height-pack_cuboid[4])<0.00001: + #NOT rotated + #cuboid pose in bin coordinate + cuboid_2_bin_pose_tran= np.identity(4) + cuboid_2_bin_pose_tran[:3, 3] = [current_pack2D_result.x,current_pack2D_result.y,level] + + pack_pose=self.bin_pose.dot(cuboid_2_bin_pose_tran.dot(pick_pose_2_box_pose)) + else: + print("NOTE: Box is rotated 90 degrees!") + cuboid_2_bin_pose_tran= np.identity(4) + cuboid_2_bin_pose_tran[:3, 3] = [current_pack2D_result.x+current_pack2D_result.width,current_pack2D_result.y,level] + + cuboid_2_bin_pose_rot= np.identity(4) + cuboid_2_bin_pose_rot[:3, :3] = RotMat.from_euler('z', 90, degrees=True).as_matrix() + + pack_pose=self.bin_pose.dot(cuboid_2_bin_pose_tran.dot(cuboid_2_bin_pose_rot.dot(pick_pose_2_box_pose))) + + + #DRAWING for DEBUG + if (display2D): + plt.figure(figsize=(10,10)) + plt.xlabel('Bin Packing 2D - SolPacker') + plt.xlim(0,self.bin_width) + plt.ylim(0,self.bin_height) + # Get the current reference + ax = plt.gca() + + print("packer[0]=",packer[0]) + for abin in packer: + print("abin.bid=",abin.bid) # Bin id if it has one + for count,rect in enumerate(abin): + # Create a Rectangle patch + rect2D = Rectangle((rect.x,rect.y),rect.width,rect.height,linewidth=2,edgecolor=(0,0,0),facecolor=(random.uniform(0.2, 1), random.uniform(0.2, 1), random.uniform(0.2, 1))) + + # Add the patch to the Axes + ax.add_patch(rect2D) + + ax.text(rect.x+rect.width/2,rect.y+rect.height/2, str(count), fontsize=15) + + + return True,pack_pose,pack_cuboid3D + #print("============",packer._pack_algo.minWH) + + print("Retry Fail",i,"Preset Time (s)=", end_preset - start,"Packing 1 cuboid Time (s)=", time.time() -end_preset) + + print("PACKING FAIL!") + return False,None,None + + + + +