关于 pallet-loading
这是帮助优化托盘装载的技能,面向仓储、包装和运输规划场景。它结合箱体尺寸、重量、堆叠与稳定性限制,生成装载方案并检查重心和抗压要求。
主要功能
- 01生成单品及交错堆叠布局
- 02优化混合 SKU 托盘组合
- 03检查托盘尺寸与重量限制
- 04分析重心稳定性与箱体抗压
- 05纳入高度、悬垂及运输条件
适用场景
- 01提高单托盘装箱数量
- 02减少混合 SKU 订单所需托盘数
- 03检查装载方案的稳定性与承重
- 04按运输高度或重量限制规划托盘
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ kishorkukreja/ awesome-supply-chain --skill pallet-loading安装后在 Claude Code 中输入/pallet-loading确认已加载。
详细介绍
托盘装载优化
你是托盘装载与优化方面的专家。你的目标是帮助高效地将箱子、货箱和物品装载到托盘上,同时最大限度地提高空间利用率,确保货物稳定、满足重量限制并符合运输要求。
初步评估
优化托盘装载之前,请先了解:
-
托盘规格
- 托盘类型?(GMA/CHEP 48x40、Euro 47.2x39.4、自定义)
- 托盘尺寸:长 x 宽 x 高
- 托盘承重能力?(通常为 2,500-4,600 lbs)
- 托盘自重?(通常为 30-70 lbs)
- 是否允许超出托盘边缘?(通常为 0-3 英寸)
-
待装载物品
- 有多少件物品/货箱?尺寸是多少?
- 物品重量及重量分布
- 是否有堆叠限制?(堆叠上限、抗压强度)
- 是否有需要特殊摆放的易碎物品?
- 尺寸是否相同,还是包含多个 SKU?
-
装载限制
- 托盘最大高度?(通常为 72"、84"、96")
- 是否有重量分布要求?
- 是否要求采用交错堆叠方式?
- 使用拉伸膜、收缩膜还是捆扎带?
- 是否需要防滑垫片?
-
运输要求
- 通过卡车还是铁路运输?
- 是否会堆叠托盘?(双层堆叠)
- 叉车需要从哪些方向作业?
- 是否有装卸平台高度限制?
- 运输时长及搬运情况?
-
优化目标
- 最大限度增加每个托盘的货箱数?
- 尽量减少托盘数量?
- 确保稳定性(尽量减少超出托盘边缘的部分)?
- 优化仓库空间利用?
托盘装载框架
标准托盘尺寸
北美:
- GMA/CHEP:48" x 40"(1219mm x 1016mm)——最常见
- 42" x 42" ——电信、油漆行业
- 48" x 48" ——圆桶、散装材料
- 40" x 48" ——军用、水泥行业
欧洲:
- EUR/EUR1:47.2" x 31.5"(1200mm x 800mm)——欧洲标准
- EUR2:47.2" x 23.6"(1200mm x 600mm)
- EUR3:39.4" x 47.2"(1000mm x 1200mm)
- EUR6:31.5" x 47.2"(800mm x 1200mm)
亚洲:
- 43.3" x 43.3"(1100mm x 1100mm)——亚洲标准
- 39.4" x 39.4"(1000mm x 1000mm)
ISO 标准:
- ISO 1:47.2" x 39.4"(1200mm x 1000mm)
- ISO 2:39.4" x 47.2"(1000mm x 1200mm)
关键概念
1. 托盘堆放模式
- 每层箱子的排列方式
- 决定稳定性和效率
- 常见模式:方形排列、行列排列、风车式排列、分行排列
2. 交错堆叠
- 不同层之间的箱子相互错开(旋转 90°)
- 提高稳定性
- 降低垮塌风险
3. 柱式堆叠
- 箱子直接上下对齐
- 不进行交错堆叠
- 稳定性较低,但有时是必需的
4. 超出托盘边缘
- 箱子超出托盘边缘
- 通常限制在 2-3 英寸
- 会降低稳定性并增加损坏风险
5. 抗压强度
- 箱子可承受的上方重量
- 是确定堆叠高度的关键因素
- 会随时间推移及湿度变化而下降
数学建模
托盘装载问题
目标: 最大限度增加装载箱数,或尽量减少托盘数量
变量:
- x_i、y_i = 托盘上第 i 个箱子的位置
- z_i = 第 i 个箱子所在的层数
- o_i = 第 i 个箱子的摆放方向(0-5)
- pattern_j = 第 j 层使用的托盘堆放模式
约束条件:
- 托盘尺寸:箱子必须位于托盘边界内(允许超出边缘的范围除外)
- 不重叠:同一层中的箱子不得重叠
- 重量:总重量 ≤ 托盘承重能力
- 稳定性:下方支撑充足,重心合理
- 堆叠高度:高度 ≤ 允许的最大值
- 抗压强度:每个箱子承受的重量 ≤ 其抗压强度
算法与求解方法
堆放模式生成方法
简单方形排列
def generate_block_pattern(box_dims, pallet_dims, allow_overhang=2):
"""
Generate simple block pattern for single box size
Calculates optimal orientation and layout
Parameters:
- box_dims: (length, width, height)
- pallet_dims: (length, width, max_height)
- allow_overhang: maximum overhang in inches
Returns pattern with max boxes per layer
"""
box_l, box_w, box_h = box_dims
pallet_l, pallet_w, pallet_h = pallet_dims
best_pattern = None
max_boxes = 0
# Try both orientations
orientations = [
(box_l, box_w, box_h, 0), # Original
(box_w, box_l, box_h, 1) # Rotated 90°
]
for l, w, h, rotation in orientations:
# Calculate how many fit in each direction
effective_pallet_l = pallet_l + 2 * allow_overhang
effective_pallet_w = pallet_w + 2 * allow_overhang
boxes_length = int(effective_pallet_l // l)
boxes_width = int(effective_pallet_w // w)
boxes_per_layer = boxes_length * boxes_width
if boxes_per_layer > max_boxes:
max_boxes = boxes_per_layer
# Calculate actual overhang
actual_overhang_l = (boxes_length * l - pallet_l) / 2
actual_overhang_w = (boxes_width * w - pallet_w) / 2
best_pattern = {
'boxes_per_layer': boxes_per_layer,
'boxes_length': boxes_length,
'boxes_width': boxes_width,
'rotation': rotation,
'box_dims': (l, w, h),
'overhang': (actual_overhang_l, actual_overhang_w),
'positions': []
}
# Generate positions
for i in range(boxes_length):
for j in range(boxes_width):
x = i * l - actual_overhang_l
y = j * w - actual_overhang_w
best_pattern['positions'].append((x, y, l, w))
return best_pattern
# Example
box = (12, 10, 8) # inches
pallet = (48, 40, 72) # GMA pallet
pattern = generate_block_pattern(box, pallet, allow_overhang=2)
print(f"Boxes per layer: {pattern['boxes_per_layer']}")
print(f"Layout: {pattern['boxes_length']} x {pattern['boxes_width']}")
print(f"Overhang: {pattern['overhang']}")
交错堆叠模式生成器
def generate_interlocking_pattern(box_dims, pallet_dims, num_layers):
"""
Generate interlocking pattern for stability
Alternates orientation between layers
Parameters:
- box_dims: (length, width, height)
- pallet_dims: (length, width, max_height)
- num_layers: number of layers to generate
Returns multi-layer interlocking pattern
"""
box_l, box_w, box_h = box_dims
pallet_l, pallet_w, pallet_h = pallet_dims
patterns = []
for layer in range(num_layers):
# Alternate orientation by layer
if layer % 2 == 0:
# Layer 0, 2, 4, ... : original orientation
pattern = generate_block_pattern(
(box_l, box_w, box_h),
(pallet_l, pallet_w, pallet_h)
)
else:
# Layer 1, 3, 5, ... : rotated orientation
pattern = generate_block_pattern(
(box_w, box_l, box_h),
(pallet_l, pallet_w, pallet_h)
)
# Add z-coordinate (layer height)
z = layer * box_h
pattern['layer'] = layer
pattern['z'] = z
patterns.append(pattern)
total_boxes = sum(p['boxes_per_layer'] for p in patterns)
return {
'num_layers': num_layers,
'total_boxes': total_boxes,
'total_height': num_layers * box_h,
'layers': patterns
}
# Example
box = (16, 12, 10)
pallet = (48, 40, 72)
max_layers = int(pallet[2] // box[2]) # 7 layers fit
interlock = generate_interlocking_pattern(box, pallet, max_layers)
print(f"Total boxes: {interlock['total_boxes']}")
print(f"Height: {interlock['total_height']} inches")
多 SKU 堆放模式优化
from pulp import *
def optimize_mixed_sku_pallet(items, pallet_dims, max_weight):
"""
Optimize pallet loading with mixed SKUs
Uses integer programming to select best combination
Parameters:
- items: list of dicts with 'id', 'dims', 'weight', 'quantity'
- pallet_dims: (length, width, max_height)
- max_weight: weight capacity
Returns optimal loading pattern
"""
pallet_l, pallet_w, pallet_h = pallet_dims
# Pre-calculate patterns for each item type
item_patterns = {}
for item in items:
item_id = item['id']
dims = item['dims']
pattern = generate_block_pattern(dims, pallet_dims)
item_patterns[item_id] = pattern
# Create optimization problem
prob = LpProblem("Pallet_Loading", LpMaximize)
# Decision variables: how many of each item to load
x = LpVariable.dicts("load",
[item['id'] for item in items],
lowBound=0,
cat='Integer')
# Objective: maximize total value/volume loaded
prob += lpSum([
x[item['id']] * item['dims'][0] * item['dims'][1] * item['dims'][2]
for item in items
]), "Total_Volume"
# Weight constraint
prob += lpSum([
x[item['id']] * item['weight']
for item in items
]) <= max_weight, "Weight_Capacity"
# Quantity constraint
for item in items:
prob += x[item['id']] <= item['quantity'], f"Quantity_{item['id']}"
# Height constraint (simplified - assumes stacking)
# This is a simplification; full solution needs layer-by-layer check
prob += lpSum([
x[item['id']] * item['dims'][2]
for item in items
]) <= pallet_h * 10, "Height_Limit" # Rough approximation
# Solve
prob.solve(PULP_CBC_CMD(msg=0))
# Extract solution
solution = {
'status': LpStatus[prob.status],
'items_loaded': {},
'total_weight': 0,
'total_volume': 0
}
for item in items:
qty = int(x[item['id']].varValue) if x[item['id']].varValue else 0
if qty > 0:
solution['items_loaded'][item['id']] = qty
solution['total_weight'] += qty * item['weight']
volume = item['dims'][0] * item['dims'][1] * item['dims'][2]
solution['total_volume'] += qty * volume
return solution
稳定性分析
重心检查
import numpy as np
def check_center_of_gravity(loaded_items, pallet_dims):
"""
Calculate center of gravity and check stability
Parameters:
- loaded_items: list of dicts with 'position' (x,y,z), 'dims', 'weight'
- pallet_dims: (length, width, height)
Returns COG coordinates and stability score
"""
if not loaded_items:
return None
total_weight = sum(item['weight'] for item in loaded_items)
# Calculate weighted average position
cog_x = sum(
(item['position'][0] + item['dims'][0]/2) * item['weight']
for item in loaded_items
) / total_weight
cog_y = sum(
(item['position'][1] + item['dims'][1]/2) * item['weight']
for item in loaded_items
) / total_weight
cog_z = sum(
(item['position'][2] + item['dims'][2]/2) * item['weight']
for item in loaded_items
) / total_weight
# Check if COG is within pallet bounds
pallet_center_x = pallet_dims[0] / 2
pallet_center_y = pallet_dims[1] / 2
offset_x = abs(cog_x - pallet_center_x)
offset_y = abs(cog_y - pallet_center_y)
# Stability score (lower offset = higher stability)
# Good: offset < 10% of pallet dimension
max_offset_x = pallet_dims[0] * 0.1
max_offset_y = pallet_dims[1] * 0.1
stability_x = max(0, 100 * (1 - offset_x / max_offset_x))
stability_y = max(0, 100 * (1 - offset_y / max_offset_y))
stability_score = min(stability_x, stability_y)
return {
'cog': (cog_x, cog_y, cog_z),
'pallet_center': (pallet_center_x, pallet_center_y),
'offset': (offset_x, offset_y),
'stability_score': stability_score,
'is_stable': stability_score > 70
}
# Example
items = [
{'position': (0, 0, 0), 'dims': (12, 10, 8), 'weight': 25},
{'position': (12, 0, 0), 'dims': (12, 10, 8), 'weight': 25},
{'position': (0, 10, 0), 'dims': (12, 10, 8), 'weight': 25},
{'position': (12, 10, 0), 'dims': (12, 10, 8), 'weight': 25},
]
pallet = (48, 40, 72)
cog_result = check_center_of_gravity(items, pallet)
print(f"COG: {cog_result['cog']}")
print(f"Stability Score: {cog_result['stability_score']:.1f}")
print(f"Stable: {cog_result['is_stable']}")
抗压强度检查
def check_compression_strength(layers, box_compression_strength):
"""
Verify each box can support weight above it
Parameters:
- layers: list of layer patterns (bottom to top)
- box_compression_strength: max weight box can support (lbs)
Returns safety analysis
"""
issues = []
safe = True
for layer_idx in range(len(layers) - 1):
layer = layers[layer_idx]
# Calculate weight above this layer
weight_above = 0
for upper_layer_idx in range(layer_idx + 1, len(layers)):
upper_layer = layers[upper_layer_idx]
weight_above += sum(
item['weight'] for item in upper_layer['items']
)
# Check each box in this layer
num_boxes_in_layer = len(layer['items'])
weight_per_box = weight_above / num_boxes_in_layer if num_boxes_in_layer > 0 else 0
if weight_per_box > box_compression_strength:
issues.append({
'layer': layer_idx,
'weight_per_box': weight_per_box,
'max_allowed': box_compression_strength,
'safety_factor': weight_per_box / box_compression_strength
})
safe = False
return {
'safe': safe,
'issues': issues,
'max_safe_layers': len(layers) if safe else layer_idx
}
完整托盘优化器
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
class PalletOptimizer:
"""
Comprehensive Pallet Loading Optimizer
Handles single and mixed SKU loading with stability checks
"""
def __init__(self, pallet_type='GMA', custom_dims=None,
weight_capacity=2500, max_height=72):
"""
Initialize pallet optimizer
Parameters:
- pallet_type: 'GMA', 'EUR', 'EUR1', or 'custom'
- custom_dims: (length, width) if pallet_type='custom'
- weight_capacity: max weight in lbs
- max_height: max height in inches
"""
# Standard pallet dimensions (inches)
pallet_dims = {
'GMA': (48, 40),
'CHEP': (48, 40),
'EUR': (47.2, 31.5),
'EUR1': (47.2, 31.5),
'EUR2': (47.2, 23.6),
'ISO1': (47.2, 39.4),
'ISO2': (39.4, 47.2)
}
if pallet_type == 'custom':
if custom_dims is None:
raise ValueError("Must provide custom_dims for custom pallet")
self.pallet_dims = (*custom_dims, max_height)
else:
dims = pallet_dims.get(pallet_type, (48, 40))
self.pallet_dims = (*dims, max_height)
self.pallet_type = pallet_type
self.weight_capacity = weight_capacity
self.items = []
self.solution = None
def add_item(self, length, width, height, weight,
quantity=1, item_id=None, compression_strength=None,
fragile=False):
"""Add item type to be loaded"""
if item_id is None:
item_id = f"SKU_{len(self.items)}"
# Default compression strength (stack limit)
if compression_strength is None:
compression_strength = weight * 10 # Conservative estimate
self.items.append({
'id': item_id,
'dims': (length, width, height),
'weight': weight,
'quantity': quantity,
'compression_strength': compression_strength,
'fragile': fragile
})
def optimize_single_sku(self, allow_overhang=2, interlock=True):
"""
Optimize loading for single SKU
Parameters:
- allow_overhang: max overhang in inches
- interlock: use interlocking pattern
Returns optimal pallet configuration
"""
if len(self.items) != 1:
raise ValueError("Use optimize_single_sku only for single SKU")
item = self.items[0]
box_dims = item['dims']
box_weight = item['weight']
available_qty = item['quantity']
# Calculate max layers by height
max_layers_height = int(self.pallet_dims[2] // box_dims[2])
# Calculate max layers by weight
max_layers_weight = int(self.weight_capacity //
(box_weight * 100)) # rough estimate
# Calculate max layers by compression
max_layers_compression = int(item['compression_strength'] / box_weight) + 1
# Take minimum
max_layers = min(max_layers_height, max_layers_weight,
max_layers_compression, 15) # Cap at 15 for safety
if interlock:
pattern = generate_interlocking_pattern(
box_dims, self.pallet_dims, max_layers
)
else:
# Simple block stacking
base_pattern = generate_block_pattern(
box_dims, self.pallet_dims, allow_overhang
)
pattern = {
'num_layers': max_layers,
'total_boxes': base_pattern['boxes_per_layer'] * max_layers,
'layers': [base_pattern] * max_layers
}
# Check if we have enough quantity
if pattern['total_boxes'] > available_qty:
# Reduce layers to fit available quantity
boxes_per_layer = pattern['layers'][0]['boxes_per_layer']
actual_layers = int(available_qty // boxes_per_layer)
pattern['num_layers'] = actual_layers
pattern['total_boxes'] = boxes_per_layer * actual_layers
# Calculate weight
total_weight = pattern['total_boxes'] * box_weight
# Check stability
loaded_items = []
for layer_idx in range(pattern['num_layers']):
z = layer_idx * box_dims[2]
layer_pattern = pattern['layers'][layer_idx % len(pattern['layers'])]
for pos in layer_pattern['positions']:
loaded_items.append({
'position': (pos[0], pos[1], z),
'dims': box_dims,
'weight': box_weight
})
cog = check_center_of_gravity(loaded_items, self.pallet_dims)
self.solution = {
'pattern': pattern,
'total_boxes': pattern['total_boxes'],
'total_weight': total_weight,
'total_height': pattern['num_layers'] * box_dims[2],
'num_layers': pattern['num_layers'],
'utilization': self.calculate_utilization(pattern, box_dims),
'stability': cog,
'loaded_items': loaded_items
}
return self.solution
def optimize_mixed_sku(self):
"""
Optimize loading for mixed SKUs
Uses layer-by-layer heuristic
"""
if len(self.items) < 2:
raise ValueError("Use optimize_mixed_sku for multiple SKUs")
# Sort items by volume (largest first)
sorted_items = sorted(self.items,
key=lambda x: x['dims'][0] * x['dims'][1] * x['dims'][2],
reverse=True)
loaded_items = []
current_height = 0
total_weight = 0
# Load layer by layer
while current_height < self.pallet_dims[2]:
# Select item for this layer
layer_item = None
for item in sorted_items:
if item['quantity'] > 0:
# Check if item fits
if current_height + item['dims'][2] <= self.pallet_dims[2]:
layer_item = item
break
if layer_item is None:
break # No more items fit
# Generate pattern for this layer
layer_pattern = generate_block_pattern(
layer_item['dims'],
self.pallet_dims
)
boxes_to_load = min(
layer_pattern['boxes_per_layer'],
layer_item['quantity']
)
# Check weight
if total_weight + boxes_to_load * layer_item['weight'] > self.weight_capacity:
# Reduce boxes to fit weight
boxes_to_load = int((self.weight_capacity - total_weight) / layer_item['weight'])
if boxes_to_load == 0:
break
# Load boxes
for i, pos in enumerate(layer_pattern['positions'][:boxes_to_load]):
loaded_items.append({
'item_id': layer_item['id'],
'position': (pos[0], pos[1], current_height),
'dims': layer_item['dims'],
'weight': layer_item['weight']
})
layer_item['quantity'] -= boxes_to_load
total_weight += boxes_to_load * layer_item['weight']
current_height += layer_item['dims'][2]
# Calculate metrics
cog = check_center_of_gravity(loaded_items, self.pallet_dims)
self.solution = {
'total_boxes': len(loaded_items),
'total_weight': total_weight,
'total_height': current_height,
'loaded_items': loaded_items,
'stability': cog
}
return self.solution
def calculate_utilization(self, pattern, box_dims):
"""Calculate volume utilization percentage"""
box_volume = box_dims[0] * box_dims[1] * box_dims[2]
total_box_volume = pattern['total_boxes'] * box_volume
pallet_volume = (self.pallet_dims[0] * self.pallet_dims[1] *
pattern['num_layers'] * box_dims[2])
return (total_box_volume / pallet_volume * 100) if pallet_volume > 0 else 0
def visualize_3d(self, save_path=None):
"""3D visualization of pallet load"""
if self.solution is None:
raise ValueError("No solution to visualize. Run optimize first.")
fig = plt.figure(figsize=(12, 10))
ax = fig.add_subplot(111, projection='3d')
# Draw pallet base
pallet_l, pallet_w, pallet_h = self.pallet_dims
self._draw_pallet_base(ax, pallet_l, pallet_w)
# Draw loaded items
colors = plt.cm.tab20(np.linspace(0, 1, 20))
for idx, item in enumerate(self.solution['loaded_items']):
pos = item['position']
dims = item['dims']
color = colors[idx % 20]
self._draw_box_3d(ax, pos, dims, color, alpha=0.7)
# Draw COG
if self.solution.get('stability'):
cog = self.solution['stability']['cog']
ax.scatter([cog[0]], [cog[1]], [cog[2]],
c='red', s=200, marker='*',
label='Center of Gravity')
ax.set_xlabel('Length (in)')
ax.set_ylabel('Width (in)')
ax.set_zlabel('Height (in)')
ax.set_title(f'Pallet Load: {self.solution["total_boxes"]} boxes\n'
f'Weight: {self.solution["total_weight"]:.0f} lbs | '
f'Height: {self.solution["total_height"]:.1f} in')
ax.legend()
# Set equal aspect
max_dim = max(pallet_l, pallet_w, self.solution['total_height'])
ax.set_xlim(-5, max_dim + 5)
ax.set_ylim(-5, max_dim + 5)
ax.set_zlim(0, max_dim + 5)
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
plt.show()
def _draw_pallet_base(self, ax, length, width):
"""Draw pallet base"""
vertices = np.array([
[0, 0, 0], [length, 0, 0], [length, width, 0], [0, width, 0]
])
face = [vertices]
pallet_face = Poly3DCollection(face, facecolors='brown',
linewidths=2, edgecolors='black',
alpha=0.3)
ax.add_collection3d(pallet_face)
def _draw_box_3d(self, ax, position, dimensions, color, alpha=0.7):
"""Draw 3D box"""
x, y, z = position
l, w, h = dimensions
vertices = np.array([
[x, y, z], [x+l, y, z], [x+l, y+w, z], [x, y+w, z],
[x, y, z+h], [x+l, y, z+h], [x+l, y+w, z+h], [x, y+w, z+h]
])
faces = [
[vertices[0], vertices[1], vertices[5], vertices[4]],
[vertices[2], vertices[3], vertices[7], vertices[6]],
[vertices[0], vertices[3], vertices[7], vertices[4]],
[vertices[1], vertices[2], vertices[6], vertices[5]],
[vertices[0], vertices[1], vertices[2], vertices[3]],
[vertices[4], vertices[5], vertices[6], vertices[7]]
]
face_collection = Poly3DCollection(faces, facecolors=color,
linewidths=0.5, edgecolors='black',
alpha=alpha)
ax.add_collection3d(face_collection)
def visualize_top_view(self, layer_index=0, save_path=None):
"""2D top-down view of specific layer"""
if self.solution is None:
raise ValueError("No solution to visualize")
fig, ax = plt.subplots(figsize=(10, 8))
# Draw pallet outline
pallet_l, pallet_w = self.pallet_dims[:2]
pallet_rect = patches.Rectangle((0, 0), pallet_l, pallet_w,
linewidth=2, edgecolor='black',
facecolor='lightgray', alpha=0.3)
ax.add_patch(pallet_rect)
# Filter items for this layer
if 'pattern' in self.solution:
# Single SKU
if layer_index < len(self.solution['pattern']['layers']):
layer = self.solution['pattern']['layers'][layer_index]
box_dims = self.items[0]['dims']
for pos in layer['positions']:
rect = patches.Rectangle(
(pos[0], pos[1]), pos[2], pos[3],
linewidth=1, edgecolor='blue',
facecolor='lightblue', alpha=0.7
)
ax.add_patch(rect)
# Add dimensions label
cx = pos[0] + pos[2]/2
cy = pos[1] + pos[3]/2
ax.text(cx, cy, f'{pos[2]:.0f}x{pos[3]:.0f}',
ha='center', va='center', fontsize=8)
ax.set_xlim(-5, pallet_l + 5)
ax.set_ylim(-5, pallet_w + 5)
ax.set_aspect('equal')
ax.set_xlabel('Length (in)')
ax.set_ylabel('Width (in)')
ax.set_title(f'Pallet Top View - Layer {layer_index + 1}')
ax.grid(True, alpha=0.3)
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
plt.show()
def print_solution(self):
"""Print solution summary"""
if self.solution is None:
print("No solution available")
return
print("=" * 70)
print("PALLET LOADING SOLUTION")
print("=" * 70)
print(f"Pallet type: {self.pallet_type}")
print(f"Pallet dimensions: {self.pallet_dims[0]} x {self.pallet_dims[1]} x {self.pallet_dims[2]} in")
print(f"Weight capacity: {self.weight_capacity} lbs")
print()
print(f"Total boxes loaded: {self.solution['total_boxes']}")
print(f"Total weight: {self.solution['total_weight']:.1f} lbs "
f"({self.solution['total_weight']/self.weight_capacity*100:.1f}% of capacity)")
print(f"Total height: {self.solution['total_height']:.1f} in "
f"({self.solution['total_height']/self.pallet_dims[2]*100:.1f}% of max)")
if 'utilization' in self.solution:
print(f"Volume utilization: {self.solution['utilization']:.1f}%")
if self.solution.get('stability'):
stab = self.solution['stability']
print()
print("Stability Analysis:")
print(f" Center of Gravity: ({stab['cog'][0]:.1f}, {stab['cog'][1]:.1f}, {stab['cog'][2]:.1f})")
print(f" Offset from center: ({stab['offset'][0]:.1f}, {stab['offset'][1]:.1f}) in")
print(f" Stability Score: {stab['stability_score']:.1f}/100")
print(f" Status: {'STABLE' if stab['is_stable'] else 'UNSTABLE - REVIEW REQUIRED'}")
# Example usage
if __name__ == "__main__":
# Example 1: Single SKU optimization
print("Example 1: Single SKU Pallet Loading")
print("-" * 50)
pallet = PalletOptimizer(pallet_type='GMA', weight_capacity=2500, max_height=72)
# Add boxes (12x10x8 inches, 25 lbs each, 100 available)
pallet.add_item(length=12, width=10, height=8, weight=25,
quantity=100, item_id='BOX_A',
compression_strength=250)
# Optimize
solution = pallet.optimize_single_sku(interlock=True)
# Print results
pallet.print_solution()
# Visualize
print("\nGenerating 3D visualization...")
pallet.visualize_3d()
print("\nGenerating top view...")
pallet.visualize_top_view(layer_index=0)
工具与库
Python 库
palletizing - 托盘码放模式生成 py3dbp - 可用于托盘装载 rectpack - 矩形装箱(用于生成层)
商业软件
- CAPE PACK:专业托盘化软件
- TOPS Pro:托盘优化
- PalletStacking:在线托盘计算器
- LoadCalc:装载规划软件
- Pallet Design System (PDS):NWPCA 软件
在线工具
- PackEx:免费托盘计算器
- PalletConfigr:托盘码放模式生成器
- Boxologic:免费 3D 装箱工具
常见挑战与解决方案
挑战:托盘利用率低(<75%)
问题:
- 箱子无法高效装载
- 箱子之间有较大间隙
- 垂直空间浪费
解决方案:
- 尝试不同的摆放方向(旋转 90°)
- 使用交错码放模式
- 在同一托盘上混合装载尺寸相近的 SKU
- 考虑使用更适合紧密排列的定制箱体尺寸
- 用较小物品填补间隙
挑战:装载不稳定
问题:
- 托盘在运输过程中倾斜
- 箱子移位或倒塌
- 悬垂过多
解决方案:
- 将悬垂限制在 <3 英寸
- 使用交错码放模式
- 将重物放在底部中央
- 检查重心计算结果
- 在各层之间使用滑托板
- 正确缠绕拉伸膜
- 加装护角板以提供支撑
挑战:压缩损坏
问题:
- 底层箱子被压坏
- 货堆在存储过程中倒塌
- 瓦楞纸箱受潮后强度下降
解决方案:
- 检查抗压强度规格
- 根据 ECT 等级限制堆叠高度
- 对强度较弱的箱子采用柱式堆码
- 加装顶部盖板
- 控制存储环境的湿度
- 底层使用强度更高的箱子
挑战:混合 SKU 的复杂性
问题:
- 不同尺寸的箱子难以搭配装载
- 高度差异导致不稳定
- 难以生成高效的码放模式
解决方案:
- 在同一层上组合高度相近的箱子
- 采用逐层处理的方法
- 将最大、最重的箱子放在底部
- 用填充材料填补小间隙
- 尺寸差异很大时,考虑使用两个托盘
挑战:运输限制
问题:
- 托盘对卡车来说太高
- 对装卸码头设备来说太重
- 无法通过门口
解决方案:
- 设置最大高度限制(通常为 72" 或 84")
- 遵守重量限制(叉车承载能力为 3,000-5,000 lbs)
- 优化前检查门口的净空高度
- 考虑是否支持双层堆叠
- 根据特定卡车或铁路车厢的尺寸进行规划
输出格式
托盘装载报告
摘要:
- 托盘类型:GMA (48" x 40")
- 已装载箱数:84 箱
- 总重量:2,100 lbs
- 总高度:64 英寸
- 利用率:89.5%
- 稳定性:通过(COG 偏移:1.2")
码放模式:
第 1 层(底层):
- 模式:4 x 5 = 20 箱
- 方向:12"L x 10"W
- 层高:8"
- 层重量:500 lbs
第 2 层:
- 模式:5 x 4 = 20 箱(旋转 90°)
- 方向:10"L x 12"W
- 层高:16"
- 层重量:500 lbs
第 3 层:
- 模式:4 x 5 = 20 箱
- 方向:12"L x 10"W
- 层高:24"
- 层重量:500 lbs
第 4 层:
- 模式:5 x 4 = 20 箱(旋转 90°)
- 方向:10"L x 12"W
- 层高:32"
- 层重量:500 lbs
第 5 层(顶层):
- 模式:2 x 2 = 4 箱
- 方向:12"L x 10"W
- 层高:40"
- 层重量:100 lbs
稳定性分析:
- 重心:(24.0", 20.0", 20.5")
- 与中心的偏移:水平 1.2"
- 状态:稳定 ✓
- 建议:缠绕 3-4 圈拉伸膜
运输说明:
- 装载后立即缠绕拉伸膜
- 使用四向进叉方式
- 可双层堆叠(重量允许)
- 最大堆叠高度:2 层(总高 128")
- 在四个侧面均贴上标签
需要询问的问题
如果需要更多背景信息:
- 您使用的托盘尺寸是多少?(GMA 48x40、Euro,还是定制尺寸?)
- 箱子的尺寸和重量是多少?
- 每个托盘需要装多少个箱子?
- 是否有最大高度限制?
- 箱子能否旋转,或在同一托盘上混装?
- 是否需要考虑稳定性或抗压强度?
- 运输方式是什么?(卡车、铁路、集装箱)
- 是否需要采用交错排列以提高稳定性?
相关技能
- 3d-bin-packing:适用于一般的 3D 装箱问题
- container-loading-optimization:用于将托盘装入集装箱
- vehicle-loading-optimization:用于托盘装载卡车
- 2d-bin-packing:用于生成层级排列方案
- load-building-optimization:用于订单履约和托盘装载
- warehouse-slotting-optimization:用于优化托盘存储
评论
支持 Markdown