mcpField
P
pallet-loading

pallet-loading

github.com/kishorkukrejaその他8447 回インストール3 週間前に更新
リポジトリ

pallet-loading について

倉庫、梱包、輸送計画でのパレット積載を最適化するスキルです。箱の寸法や重量、積み重ね、安定性の制約を考慮し、重心と圧縮強度の確認方法も扱います。

主な機能

  1. 01単一SKUと交互積みパターンの生成
  2. 02混載SKUのパレット構成の最適化
  3. 03パレット寸法と重量制限の確認
  4. 04重心安定性と圧縮強度の分析
  5. 05高さ・はみ出し・輸送条件への対応

活用シーン

  1. 01パレット当たりのケース数の増加
  2. 02混載SKU注文で必要なパレット数の削減
  3. 03積載の安定性と重量支持の確認
  4. 04輸送時の高さ・重量制限に合わせた計画

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/kishorkukreja/awesome-supply-chain --skill pallet-loading

インストール後、Claude Code で/pallet-loadingを入力して読み込まれたか確認します。

詳細

パレット積載の最適化

パレット積載と最適化の専門家として、積載率を最大化し、荷崩れを防ぎ、重量制限を守り、輸送要件を満たしながら、箱、ケース、商品をパレットに効率よく積載できるよう支援します。

初期評価

パレット積載を最適化する前に、以下を確認してください。

  1. パレットの仕様

    • パレットの種類は何ですか?(GMA/CHEP 48x40、Euro 47.2x39.4、特注など)
    • パレットの寸法:長さ x 幅 x 高さ
    • パレットの耐荷重はどのくらいですか?(通常2,500~4,600 lbs)
    • パレット自体の重量はどのくらいですか?(通常30~70 lbs)
    • はみ出しは許容されますか?(通常0~3インチ)
  2. 積載する商品

    • 商品/ケースはいくつありますか?寸法はどのくらいですか?
    • 商品の重量と重量配分
    • 積み重ねに関する制約はありますか?(積み重ね上限、圧縮強度)
    • 特別な配置が必要な壊れやすい商品はありますか?
    • すべて同じサイズですか、それともSKUが混在していますか?
  3. 積載の制約

    • パレットの最大高さはどのくらいですか?(多くの場合72"、84"、96")
    • 重量配分の要件はありますか?
    • かみ合わせる積み方が必要ですか?
    • ストレッチフィルム、シュリンクフィルム、またはバンド掛けを使用しますか?
    • 滑り止めシートは必要ですか?
  4. 輸送要件

    • トラック輸送ですか、それとも鉄道輸送ですか?
    • パレットを積み重ねますか?(二段積み)
    • フォークリフトでアクセスする方向はどちらですか?
    • 積み込みドックの高さに制約はありますか?
    • 輸送時間と荷役作業はどのようなものですか?
  5. 最適化の目標

    • 1パレットあたりのケース数を最大化しますか?
    • パレット数を最小化しますか?
    • 安定性を確保しますか(はみ出しを最小化)?
    • 倉庫スペースを最適化しますか?

パレット積載のフレームワーク

標準パレットサイズ

北米:

  • 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に使用するパレットパターン

制約条件:

  1. パレット寸法:箱がパレットの範囲内に収まること(はみ出し許容範囲を含む)
  2. 重なりなし:同じ段の箱同士が重ならないこと
  3. 重量:総重量 ≤ パレットの耐荷重
  4. 安定性:下から十分に支えられ、重心が適切であること
  5. 積み重ね高さ:高さ ≤ 許容最大値
  6. 圧縮:各箱にかかる重量 ≤ その箱の圧縮強度

アルゴリズムと解法

パターン生成方法

シンプルなブロックパターン

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 inches に制限します
  • インターロッキングパターンを使用します
  • 重い商品を下段の中央に置きます
  • 重心の計算を確認します
  • 層の間にスリップシートを使用します
  • ストレッチフィルムを適切に巻きます
  • 支えとしてコーナーボードを追加します
課題:圧縮による損傷

問題:

  • 下段の箱が潰れる
  • 保管中に積み重ねが崩れる
  • 湿度によって段ボール箱が弱くなる

解決策:

  • 圧縮強度の仕様を確認します
  • ECT 評価に基づいて積み重ねの高さを制限します
  • 強度の低い箱には縦積みを使用します
  • 荷物の上部に天板を追加します
  • 保管場所の湿度を管理します
  • 下段には強度の高い箱を使用します
課題:複数 SKU の複雑さ

問題:

  • サイズの異なる箱をうまく一緒に梱包できない
  • 高さの違いにより不安定になる
  • 効率的なパターンを生成しにくい

解決策:

  • 同じ層には高さの近い箱をまとめます
  • 層ごとに積み付ける方法を使用します
  • 最も大きい箱、重い箱を下段に置きます
  • 緩衝材で小さな隙間を埋めます
  • サイズが大きく異なる場合は、2枚のパレットを使用することを検討します
課題:輸送上の制約

問題:

  • パレットがトラックに積める高さを超えている
  • 荷役設備の耐荷重を超えている
  • ドアを通過できない

解決策:

  • 最大高さの制約を設定します(通常は 72" または 84")
  • 重量制限を守ります(フォークリフトの積載能力は 3,000~5,000 lbs)
  • 最適化の前にドアの有効寸法を確認します
  • 2段積みが可能か検討します
  • 使用するトラック/貨車の寸法に合わせて計画します

出力形式

パレット積載レポート

概要:

  • パレットタイプ:GMA(48" x 40")
  • 積載箱数:84 ケース
  • 総重量:2,100 lbs
  • 全高:64 inches
  • 積載効率: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段積み可能(重量の条件を満たしています)
  • 最大積み重ね高さ:2段(合計 128")
  • 4面すべてにラベルを貼ります

確認する質問

より詳しい情報が必要な場合:

  1. どのパレットサイズを使用していますか?(GMA 48x40、ユーロパレット、カスタムサイズなど)
  2. 箱の寸法と重量を教えてください。
  3. パレットごとに何箱積載する必要がありますか?
  4. 高さ制限はありますか?
  5. 箱の向きを変えたり、同じパレットに異なる種類の箱を混載したりできますか?
  6. 安定性や圧縮強度に関する懸念はありますか?
  7. 輸送方法は何ですか?(トラック、鉄道、コンテナ)
  8. 安定性を高めるために、かみ合わせパターンが必要ですか?

関連スキル

  • 3d-bin-packing:一般的な3D梱包問題向け
  • container-loading-optimization:コンテナへのパレット積載向け
  • vehicle-loading-optimization:パレットを使ったトラックへの積載向け
  • 2d-bin-packing:段積みパターンの生成向け
  • load-building-optimization:受注処理とパレット積み付け向け
  • warehouse-slotting-optimization:パレットの保管場所の最適化向け

コメント