Software Engineering
Software Engineering
Smart Palletizer: Optimizing Warehouse Packing with 3D Bin-Packing
Smart Palletizer: Optimizing Warehouse Packing with 3D Bin-Packing
Smart Palletizer: Optimizing Warehouse Packing with 3D Bin-Packing
May 12, 2025
|
8
min read

The Challenge: Optimizing Pallet Packaging for Logistics
We were approached by a client in the warehousing industry facing a persistent and costly issue:
Boxes were packed manually, leading to poor space usage and frequent errors
Pallet space was underused often by 15 – 30%, driving up shipping volume
Weight distribution was uneven, raising safety and compliance concerns
Manual adjustments consumed hours daily, slowing operations and causing stress
These inefficiencies weren’t just operational .They were eating into margins, increasing shipping costs, and putting unnecessary pressure on the team.
Our Solution: A Desktop Tool for Heuristic-Based Pallet Packing and Visualization
To solve this, we developed a cross-platform Qt application that automates every step from intelligently grouping boxes to generating optimized, layer-by-layer pallet diagrams and exportable packing reports.
User loads a file (Excel .xlsx or JSON) containing all box entries.
Automated grouping by size assigns each box to the best candidate pallet templates.
Parallel packing threads run the Py3DBP engine across all pallet types, then a custom pooling manager redistributes items for maximal fill and balanced loads.
Real-time 3D animation shows a spinning “loading” pallet during computation, then an interactive 3D view of the final arrangement switchable between front/right/back/left/top perspectives.
Layer-by-layer 2D diagrams are generated and saved into a multi-page PDF, complete with box IDs labeled at their true coordinates.
One-click exports to PDF, PNG or CSV create all necessary shipping documentation, with direct links to each pallet’s detailed view.
With this tool, our client eliminated layout errors and manual guesswork, gained instant visibility into 3D and 2D packing plans, and significantly reduced the number of pallets shipped resulting in substantial cost savings on every delivery.
Below is a screenshot of the desktop application in action, showcasing how the optimized packing layout is visualized for both operators and planners.

Workflow Overview
This diagram illustrates the three core stages of the Pallet Optimization Tool:

Diagram Type: UML Activity Diagram
Key Code Snippets
Grouping & JSON Preparation (utils.py)
def group_by_size(items): """ Groups items by their (length, width, height). Returns a dict mapping size tuples to lists of items. """ … def assign_pallets_to_groups(groups): """ Returns a dict mapping each size-tuple to the best-fitting PalletType code. """ …
Multi‑Pallet Packing Solver (solver.py)
# solver.py def solve_multi_pallet_py3dbp(boxes, pallet: PalletType, tol=0): """ Given a list of box dicts and a PalletType, spins up one Bin per box to allow maximal packing, runs the 3D bin‑packing solver, and returns only the filled bins. """
Pooled Bin Manager & Redistribution (pooled_bin_manager.py)
# pooled_bin_manager.py class PooledBinManager: def __init__(self, bins, tol, callback=lambda msg: None): """ …. def redistribute(self): """Repeatedly merge and repack until no more improvements.""" # single-pass merges
GUI Integration & 3D Visualization (mainwindow.py)
def draw_loading_cube(ax, l, w, h): verts = [ [(0,0,0),(l,0,0),(l,w,0),(0,w,0)], [(0,0,h),(l,0,h),(l,w,h),(0,w,h)], # other faces... ] cube = Poly3DCollection(verts, alpha=0.2) ax.add_collection3d(cube) ax.set_box_aspect((l,w,h)) def draw_boxes(ax, boxes): for idx, (x,y,z,l,w,h,_) in enumerate(boxes): faces = [ [(x,y,z),(x+l,y,z),(x+l,y+w,z),(x,y+w,z)], # other faces... ] poly = Poly3DCollection(faces, alpha=0.8) ax.add_collection3d(poly)
Loading Data & Grouping (mainwindow.py)
# mainwindow.py # Triggered when user selects an Excel file def load_excel(self): self.load_excel_action.setEnabled(False) path, _ = QFileDialog.getOpenFileName(self, 'Open Boxes xlsx', '', 'Excel Files (*.xlsx)') if not path: self.load_excel_action.setEnabled(True) return try: self.loaded_boxes = json.loads(excel_to_json(path)) except Exception as e: QMessageBox.critical(self, 'Error', f'Failed to load data: {e}') self.load_excel_action.setEnabled(True) return self.file_label.setText(f'Imported file: {os.path.basename(path)}') self.prepare_loaded() # Triggered when user selects a JSON file def load_json(self): self.load_action.setEnabled(False) path, _ = QFileDialog.getOpenFileName(self, 'Open Boxes JSON', '', 'JSON Files (*.json)') if not path: self.load_action.setEnabled(True) return try: with open(path) as f: self.loaded_boxes = json.load(f) except Exception as e: QMessageBox.critical(self, 'Error', f'Failed to load JSON: {e}') self.load_action.setEnabled(True) return self.file_label.setText(f'Imported file: {os.path.basename(path)}') self.prepare_loaded()
Redistributing Unfitted Items (pooled_bin_manager.py)
# pooled_bin_manager.py class PooledBinManager: ... def redistribute(self) -> Tuple[int, List[Bin], List[Any]]: """ Repeatedly redistribute and merge items until no further improvements. Returns (total_moved, final_bins, unplaceable_items). """ # 1) perform single-pass merges until stable while True: moved = self._single_pass() self.total_moved += moved if moved == 0: break …. return self.total_moved, self.bins, unplaceable
Exporting Packing Plans for the Warehouse Floor
To make the optimized pallet configurations actionable for logistics teams, we implemented a PDF export feature directly within the desktop application. This allows warehouse workers to receive clear, step-by-step visual instructions on how to pack each pallet ensuring accurate, efficient, and error-free execution on the ground.
Why It Matters
Shipping optimization is only as good as its execution. Our export functionality bridges the gap between digital planning and physical operations by providing printable packing plans that detail exactly how each pallet should be assembled layer by layer, box by box.
How It Works
For every pallet, the system automatically generates a PDF file (e.g.,
pallet_1.pdf
) that includes:One page per Z-layer of the pallet
A bounding box showing the maximum pallet footprint (X and Y)
Precise box positions and dimensions
Box identifiers clearly labeled at the center of each item
A structured title for each page, such as:
Pallet 2 – Layer 3 (Z = 120.0 mm)
This format ensures that logistics workers can immediately understand the layout and reproduce it with accuracy.
# Called when user selects an export format def export_results(self, format: str): filters = {'pdf': 'PDF Files (*.pdf)', 'png': 'PNG Files (*.png)', 'csv': 'CSV Files (*.csv)'} path, _ = QFileDialog.getSaveFileName(self, 'Export Results', '', filters[format]) …
Visual Preview :
Here is a sample output showing one layer of a pallet:
The Challenge: Optimizing Pallet Packaging for Logistics
We were approached by a client in the warehousing industry facing a persistent and costly issue:
Boxes were packed manually, leading to poor space usage and frequent errors
Pallet space was underused often by 15 – 30%, driving up shipping volume
Weight distribution was uneven, raising safety and compliance concerns
Manual adjustments consumed hours daily, slowing operations and causing stress
These inefficiencies weren’t just operational .They were eating into margins, increasing shipping costs, and putting unnecessary pressure on the team.
Our Solution: A Desktop Tool for Heuristic-Based Pallet Packing and Visualization
To solve this, we developed a cross-platform Qt application that automates every step from intelligently grouping boxes to generating optimized, layer-by-layer pallet diagrams and exportable packing reports.
User loads a file (Excel .xlsx or JSON) containing all box entries.
Automated grouping by size assigns each box to the best candidate pallet templates.
Parallel packing threads run the Py3DBP engine across all pallet types, then a custom pooling manager redistributes items for maximal fill and balanced loads.
Real-time 3D animation shows a spinning “loading” pallet during computation, then an interactive 3D view of the final arrangement switchable between front/right/back/left/top perspectives.
Layer-by-layer 2D diagrams are generated and saved into a multi-page PDF, complete with box IDs labeled at their true coordinates.
One-click exports to PDF, PNG or CSV create all necessary shipping documentation, with direct links to each pallet’s detailed view.
With this tool, our client eliminated layout errors and manual guesswork, gained instant visibility into 3D and 2D packing plans, and significantly reduced the number of pallets shipped resulting in substantial cost savings on every delivery.
Below is a screenshot of the desktop application in action, showcasing how the optimized packing layout is visualized for both operators and planners.

Workflow Overview
This diagram illustrates the three core stages of the Pallet Optimization Tool:

Diagram Type: UML Activity Diagram
Key Code Snippets
Grouping & JSON Preparation (utils.py)
def group_by_size(items): """ Groups items by their (length, width, height). Returns a dict mapping size tuples to lists of items. """ … def assign_pallets_to_groups(groups): """ Returns a dict mapping each size-tuple to the best-fitting PalletType code. """ …
Multi‑Pallet Packing Solver (solver.py)
# solver.py def solve_multi_pallet_py3dbp(boxes, pallet: PalletType, tol=0): """ Given a list of box dicts and a PalletType, spins up one Bin per box to allow maximal packing, runs the 3D bin‑packing solver, and returns only the filled bins. """
Pooled Bin Manager & Redistribution (pooled_bin_manager.py)
# pooled_bin_manager.py class PooledBinManager: def __init__(self, bins, tol, callback=lambda msg: None): """ …. def redistribute(self): """Repeatedly merge and repack until no more improvements.""" # single-pass merges
GUI Integration & 3D Visualization (mainwindow.py)
def draw_loading_cube(ax, l, w, h): verts = [ [(0,0,0),(l,0,0),(l,w,0),(0,w,0)], [(0,0,h),(l,0,h),(l,w,h),(0,w,h)], # other faces... ] cube = Poly3DCollection(verts, alpha=0.2) ax.add_collection3d(cube) ax.set_box_aspect((l,w,h)) def draw_boxes(ax, boxes): for idx, (x,y,z,l,w,h,_) in enumerate(boxes): faces = [ [(x,y,z),(x+l,y,z),(x+l,y+w,z),(x,y+w,z)], # other faces... ] poly = Poly3DCollection(faces, alpha=0.8) ax.add_collection3d(poly)
Loading Data & Grouping (mainwindow.py)
# mainwindow.py # Triggered when user selects an Excel file def load_excel(self): self.load_excel_action.setEnabled(False) path, _ = QFileDialog.getOpenFileName(self, 'Open Boxes xlsx', '', 'Excel Files (*.xlsx)') if not path: self.load_excel_action.setEnabled(True) return try: self.loaded_boxes = json.loads(excel_to_json(path)) except Exception as e: QMessageBox.critical(self, 'Error', f'Failed to load data: {e}') self.load_excel_action.setEnabled(True) return self.file_label.setText(f'Imported file: {os.path.basename(path)}') self.prepare_loaded() # Triggered when user selects a JSON file def load_json(self): self.load_action.setEnabled(False) path, _ = QFileDialog.getOpenFileName(self, 'Open Boxes JSON', '', 'JSON Files (*.json)') if not path: self.load_action.setEnabled(True) return try: with open(path) as f: self.loaded_boxes = json.load(f) except Exception as e: QMessageBox.critical(self, 'Error', f'Failed to load JSON: {e}') self.load_action.setEnabled(True) return self.file_label.setText(f'Imported file: {os.path.basename(path)}') self.prepare_loaded()
Redistributing Unfitted Items (pooled_bin_manager.py)
# pooled_bin_manager.py class PooledBinManager: ... def redistribute(self) -> Tuple[int, List[Bin], List[Any]]: """ Repeatedly redistribute and merge items until no further improvements. Returns (total_moved, final_bins, unplaceable_items). """ # 1) perform single-pass merges until stable while True: moved = self._single_pass() self.total_moved += moved if moved == 0: break …. return self.total_moved, self.bins, unplaceable
Exporting Packing Plans for the Warehouse Floor
To make the optimized pallet configurations actionable for logistics teams, we implemented a PDF export feature directly within the desktop application. This allows warehouse workers to receive clear, step-by-step visual instructions on how to pack each pallet ensuring accurate, efficient, and error-free execution on the ground.
Why It Matters
Shipping optimization is only as good as its execution. Our export functionality bridges the gap between digital planning and physical operations by providing printable packing plans that detail exactly how each pallet should be assembled layer by layer, box by box.
How It Works
For every pallet, the system automatically generates a PDF file (e.g.,
pallet_1.pdf
) that includes:One page per Z-layer of the pallet
A bounding box showing the maximum pallet footprint (X and Y)
Precise box positions and dimensions
Box identifiers clearly labeled at the center of each item
A structured title for each page, such as:
Pallet 2 – Layer 3 (Z = 120.0 mm)
This format ensures that logistics workers can immediately understand the layout and reproduce it with accuracy.
# Called when user selects an export format def export_results(self, format: str): filters = {'pdf': 'PDF Files (*.pdf)', 'png': 'PNG Files (*.png)', 'csv': 'CSV Files (*.csv)'} path, _ = QFileDialog.getSaveFileName(self, 'Export Results', '', filters[format]) …
Visual Preview :
Here is a sample output showing one layer of a pallet:
The Challenge: Optimizing Pallet Packaging for Logistics
We were approached by a client in the warehousing industry facing a persistent and costly issue:
Boxes were packed manually, leading to poor space usage and frequent errors
Pallet space was underused often by 15 – 30%, driving up shipping volume
Weight distribution was uneven, raising safety and compliance concerns
Manual adjustments consumed hours daily, slowing operations and causing stress
These inefficiencies weren’t just operational .They were eating into margins, increasing shipping costs, and putting unnecessary pressure on the team.
Our Solution: A Desktop Tool for Heuristic-Based Pallet Packing and Visualization
To solve this, we developed a cross-platform Qt application that automates every step from intelligently grouping boxes to generating optimized, layer-by-layer pallet diagrams and exportable packing reports.
User loads a file (Excel .xlsx or JSON) containing all box entries.
Automated grouping by size assigns each box to the best candidate pallet templates.
Parallel packing threads run the Py3DBP engine across all pallet types, then a custom pooling manager redistributes items for maximal fill and balanced loads.
Real-time 3D animation shows a spinning “loading” pallet during computation, then an interactive 3D view of the final arrangement switchable between front/right/back/left/top perspectives.
Layer-by-layer 2D diagrams are generated and saved into a multi-page PDF, complete with box IDs labeled at their true coordinates.
One-click exports to PDF, PNG or CSV create all necessary shipping documentation, with direct links to each pallet’s detailed view.
With this tool, our client eliminated layout errors and manual guesswork, gained instant visibility into 3D and 2D packing plans, and significantly reduced the number of pallets shipped resulting in substantial cost savings on every delivery.
Below is a screenshot of the desktop application in action, showcasing how the optimized packing layout is visualized for both operators and planners.

Workflow Overview
This diagram illustrates the three core stages of the Pallet Optimization Tool:

Diagram Type: UML Activity Diagram
Key Code Snippets
Grouping & JSON Preparation (utils.py)
def group_by_size(items): """ Groups items by their (length, width, height). Returns a dict mapping size tuples to lists of items. """ … def assign_pallets_to_groups(groups): """ Returns a dict mapping each size-tuple to the best-fitting PalletType code. """ …
Multi‑Pallet Packing Solver (solver.py)
# solver.py def solve_multi_pallet_py3dbp(boxes, pallet: PalletType, tol=0): """ Given a list of box dicts and a PalletType, spins up one Bin per box to allow maximal packing, runs the 3D bin‑packing solver, and returns only the filled bins. """
Pooled Bin Manager & Redistribution (pooled_bin_manager.py)
# pooled_bin_manager.py class PooledBinManager: def __init__(self, bins, tol, callback=lambda msg: None): """ …. def redistribute(self): """Repeatedly merge and repack until no more improvements.""" # single-pass merges
GUI Integration & 3D Visualization (mainwindow.py)
def draw_loading_cube(ax, l, w, h): verts = [ [(0,0,0),(l,0,0),(l,w,0),(0,w,0)], [(0,0,h),(l,0,h),(l,w,h),(0,w,h)], # other faces... ] cube = Poly3DCollection(verts, alpha=0.2) ax.add_collection3d(cube) ax.set_box_aspect((l,w,h)) def draw_boxes(ax, boxes): for idx, (x,y,z,l,w,h,_) in enumerate(boxes): faces = [ [(x,y,z),(x+l,y,z),(x+l,y+w,z),(x,y+w,z)], # other faces... ] poly = Poly3DCollection(faces, alpha=0.8) ax.add_collection3d(poly)
Loading Data & Grouping (mainwindow.py)
# mainwindow.py # Triggered when user selects an Excel file def load_excel(self): self.load_excel_action.setEnabled(False) path, _ = QFileDialog.getOpenFileName(self, 'Open Boxes xlsx', '', 'Excel Files (*.xlsx)') if not path: self.load_excel_action.setEnabled(True) return try: self.loaded_boxes = json.loads(excel_to_json(path)) except Exception as e: QMessageBox.critical(self, 'Error', f'Failed to load data: {e}') self.load_excel_action.setEnabled(True) return self.file_label.setText(f'Imported file: {os.path.basename(path)}') self.prepare_loaded() # Triggered when user selects a JSON file def load_json(self): self.load_action.setEnabled(False) path, _ = QFileDialog.getOpenFileName(self, 'Open Boxes JSON', '', 'JSON Files (*.json)') if not path: self.load_action.setEnabled(True) return try: with open(path) as f: self.loaded_boxes = json.load(f) except Exception as e: QMessageBox.critical(self, 'Error', f'Failed to load JSON: {e}') self.load_action.setEnabled(True) return self.file_label.setText(f'Imported file: {os.path.basename(path)}') self.prepare_loaded()
Redistributing Unfitted Items (pooled_bin_manager.py)
# pooled_bin_manager.py class PooledBinManager: ... def redistribute(self) -> Tuple[int, List[Bin], List[Any]]: """ Repeatedly redistribute and merge items until no further improvements. Returns (total_moved, final_bins, unplaceable_items). """ # 1) perform single-pass merges until stable while True: moved = self._single_pass() self.total_moved += moved if moved == 0: break …. return self.total_moved, self.bins, unplaceable
Exporting Packing Plans for the Warehouse Floor
To make the optimized pallet configurations actionable for logistics teams, we implemented a PDF export feature directly within the desktop application. This allows warehouse workers to receive clear, step-by-step visual instructions on how to pack each pallet ensuring accurate, efficient, and error-free execution on the ground.
Why It Matters
Shipping optimization is only as good as its execution. Our export functionality bridges the gap between digital planning and physical operations by providing printable packing plans that detail exactly how each pallet should be assembled layer by layer, box by box.
How It Works
For every pallet, the system automatically generates a PDF file (e.g.,
pallet_1.pdf
) that includes:One page per Z-layer of the pallet
A bounding box showing the maximum pallet footprint (X and Y)
Precise box positions and dimensions
Box identifiers clearly labeled at the center of each item
A structured title for each page, such as:
Pallet 2 – Layer 3 (Z = 120.0 mm)
This format ensures that logistics workers can immediately understand the layout and reproduce it with accuracy.
# Called when user selects an export format def export_results(self, format: str): filters = {'pdf': 'PDF Files (*.pdf)', 'png': 'PNG Files (*.png)', 'csv': 'CSV Files (*.csv)'} path, _ = QFileDialog.getSaveFileName(self, 'Export Results', '', filters[format]) …
Visual Preview :
Here is a sample output showing one layer of a pallet: