Skip to content

Stock Cut Pattern Documentation

Overview

The Stock Cut Pattern module (stock.cut.pattern) represents an optimal cutting pattern generated by the solver. Each pattern defines how to cut a single stock piece into multiple smaller pieces to fulfill orders, including waste calculations.


Model Information

Model Name: stock.cut.pattern
Display Name: (Not explicitly defined)
Key Fields: None (no unique constraint defined)

Features

  • ❌ Audit logging enabled (_audit_log)
  • ❌ Multi-company support (company_id)
  • ❌ Full-text content search (_content_search)
  • ❌ Unique key constraint

Understanding Cutting Patterns

A cutting pattern defines one way to cut a stock piece. Each pattern can be repeated multiple times to fulfill order requirements efficiently.

Pattern Structure

Stock Width: 300mm
Repeat: 5 times

Cuts per stock piece:
├─ Width 100mm × 2 pieces
├─ Width 50mm × 1 piece
└─ Waste: 100mm

Total output (5 repetitions):
├─ Width 100mm: 2 × 5 = 10 pieces
├─ Width 50mm: 1 × 5 = 5 pieces
└─ Total waste: 100mm × 5 = 500mm

Field Reference

Header Fields

Field Type Required Description
cut_id Many2One Parent cutting operation (on_delete="cascade")
stock_width Decimal Width of the stock material being cut
num Integer Number of times to repeat this cutting pattern

Cut Definition Fields

Field Type Required Description
width1 Decimal Width of first cut type
qty1 Decimal Quantity of first cut type per stock piece
width2 Decimal Width of second cut type (optional)
qty2 Decimal Quantity of second cut type per stock piece
width3 Decimal Width of third cut type (optional)
qty3 Decimal Quantity of third cut type per stock piece
width4 Decimal Width of fourth cut type (optional)
qty4 Decimal Quantity of fourth cut type per stock piece
width5 Decimal Width of fifth cut type (optional)
qty5 Decimal Quantity of fifth cut type per stock piece

Computed Fields

Field Type Description
total_cut Decimal Total width of all cuts per stock piece
waste Decimal Waste per stock piece (stock_width - total_cut)
total_waste Decimal Total waste for all repetitions (waste × num)

Pattern Capacity

Each pattern supports up to 5 different cut widths per stock piece. This limitation is built into the model structure with fields width1-5 and qty1-5.

Example of pattern using all 5 cuts:

{
    "stock_width": 500.0,
    "num": 10,
    "width1": 100.0, "qty1": 1,
    "width2": 80.0,  "qty2": 2,
    "width3": 75.0,  "qty3": 1,
    "width4": 50.0,  "qty4": 2,
    "width5": 40.0,  "qty5": 1,
    # Total cut: 100 + 160 + 75 + 100 + 40 = 475
    # Waste: 500 - 475 = 25 per piece
}


Computed Fields Functions

get_waste(ids, context)

Calculates cutting efficiency metrics for each pattern using multi-field computation.

Calculated Fields: 1. total_cut: Sum of all cuts per stock piece

total_cut = (width1 × qty1) + (width2 × qty2) + ... + (width5 × qty5)

  1. waste: Unused material per stock piece

    waste = stock_width - total_cut
    

  2. total_waste: Total waste across all repetitions

    total_waste = waste × num
    

Returns: Dictionary mapping pattern ID to dict with three computed values

Example:

# Pattern: Stock 300, repeated 5 times
# Cuts: 100×2, 80×1
# 
# total_cut = (100 × 2) + (80 × 1) = 280
# waste = 300 - 280 = 20
# total_waste = 20 × 5 = 100

pattern = get_model("stock.cut.pattern").browse(pattern_id)
print(f"Cut: {pattern.total_cut}")        # 280
print(f"Waste: {pattern.waste}")          # 20
print(f"Total Waste: {pattern.total_waste}")  # 100


API Methods

1. Create Pattern

Method: create(vals, context)

Creates a new cutting pattern record. Typically called by the solver rather than manually.

Parameters:

vals = {
    "cut_id": 1,                    # Required: parent cutting operation
    "stock_width": 300.0,           # Required: stock material width
    "num": 5,                       # Required: repeat count
    "width1": 100.0,                # Required: first cut width
    "qty1": 2,                      # Required: first cut quantity
    "width2": 80.0,                 # Optional: second cut width
    "qty2": 1,                      # Optional: second cut quantity
    # ... up to width5/qty5
}

Returns: int - New pattern ID

Example:

# Manually create a pattern (typically done by solver)
pattern_id = get_model("stock.cut.pattern").create({
    "cut_id": cut_id,
    "stock_width": 300.0,
    "num": 10,
    "width1": 100.0,
    "qty1": 2,
    "width2": 50.0,
    "qty2": 1,
})


Model Relationship Description
stock.cut Many2One Parent cutting operation
stock.cut.order Related Orders that this pattern helps fulfill
stock.cut.stock Related Stock materials used in this pattern

Common Use Cases

Use Case 1: Analyze Pattern Efficiency

# Calculate efficiency for each pattern
cut = get_model("stock.cut").browse(cut_id)

for pattern in cut.patterns:
    efficiency = (pattern.total_cut / pattern.stock_width) * 100
    waste_pct = (pattern.waste / pattern.stock_width) * 100

    print(f"Pattern: Stock {pattern.stock_width}mm × {pattern.num}")
    print(f"  Efficiency: {efficiency:.1f}%")
    print(f"  Waste: {waste_pct:.1f}% ({pattern.waste}mm per piece)")
    print(f"  Total waste: {pattern.total_waste}mm")
    print()

Use Case 2: Identify Best Patterns

# Find patterns with lowest waste percentage
cut = get_model("stock.cut").browse(cut_id)

patterns_with_efficiency = [
    {
        "pattern": p,
        "waste_pct": (p.waste / p.stock_width) * 100 if p.stock_width else 0
    }
    for p in cut.patterns
]

# Sort by waste percentage
sorted_patterns = sorted(patterns_with_efficiency, key=lambda x: x["waste_pct"])

print("Most Efficient Patterns:")
for item in sorted_patterns[:3]:
    p = item["pattern"]
    print(f"Stock {p.stock_width}: {item['waste_pct']:.2f}% waste ({p.waste}mm)")

Use Case 3: Generate Cutting Instructions

# Create human-readable cutting instructions
def generate_cutting_instructions(cut_id):
    cut = get_model("stock.cut").browse(cut_id)

    instructions = []
    for i, pattern in enumerate(cut.patterns, 1):
        instruction = f"Pattern {i}: Cut {pattern.num} pieces of {pattern.stock_width}mm stock\n"

        cuts = []
        for j in range(1, 6):  # Check width1-5
            width = getattr(pattern, f"width{j}")
            qty = getattr(pattern, f"qty{j}")
            if width and qty:
                cuts.append(f"  - {qty}× {width}mm")

        instruction += "\n".join(cuts)
        instruction += f"\n  Waste: {pattern.waste}mm per piece"
        instructions.append(instruction)

    return "\n\n".join(instructions)

print(generate_cutting_instructions(cut_id))

Use Case 4: Calculate Material Requirements

# Determine how much stock material is needed
cut = get_model("stock.cut").browse(cut_id)

stock_requirements = {}
for pattern in cut.patterns:
    stock_requirements.setdefault(pattern.stock_width, 0)
    stock_requirements[pattern.stock_width] += pattern.num

print("Stock Material Requirements:")
for width, qty in stock_requirements.items():
    print(f"  {width}mm: {qty} pieces")

Understanding Multi-Field Computation

The get_waste function uses multi-field computation (function_multi=True) to efficiently calculate three related values in a single database query:

_fields = {
    "total_cut": fields.Decimal("Unit Cut", function="get_waste", function_multi=True),
    "waste": fields.Decimal("Unit Waste", function="get_waste", function_multi=True),
    "total_waste": fields.Decimal("Total Waste", function="get_waste", function_multi=True),
}

This approach: - ✅ Reduces database queries (3 fields with 1 function call) - ✅ Ensures consistency across related calculations - ✅ Improves performance when accessing multiple computed fields


Waste Calculation Deep Dive

Per-Piece Waste

# Example pattern:
stock_width = 300mm
width1=100mm, qty1=2  # Uses 200mm
width2=50mm,  qty2=1  # Uses 50mm
# Total cut = 250mm
# Waste = 300mm - 250mm = 50mm per piece

Total Waste Across Repetitions

# Same pattern, repeated 10 times:
waste_per_piece = 50mm
num_repetitions = 10
total_waste = 50mm × 10 = 500mm

Efficiency Percentage

efficiency = (total_cut / stock_width) × 100
efficiency = (250 / 300) × 100 = 83.33%

waste_percentage = 100 - efficiency = 16.67%

Best Practices

1. Minimize Pattern Count

# Bad: Many patterns with few repetitions
patterns = [
    {"stock_width": 300, "num": 1, ...},
    {"stock_width": 300, "num": 1, ...},
    {"stock_width": 300, "num": 1, ...},
]

# Good: Fewer patterns with more repetitions
patterns = [
    {"stock_width": 300, "num": 10, ...},
    {"stock_width": 250, "num": 5, ...},
]

2. Validate Pattern Feasibility

def validate_pattern(pattern):
    """Ensure pattern is physically feasible"""

    # Check total cut doesn't exceed stock width
    if pattern.total_cut > pattern.stock_width:
        raise Exception(f"Total cut {pattern.total_cut} exceeds stock width {pattern.stock_width}")

    # Check for negative waste
    if pattern.waste < 0:
        raise Exception(f"Pattern has negative waste: {pattern.waste}")

    # Warn about excessive waste
    waste_pct = (pattern.waste / pattern.stock_width) * 100
    if waste_pct > 20:
        print(f"Warning: Pattern has {waste_pct:.1f}% waste")

3. Optimize for Production

Patterns with similar cuts across multiple stock widths may indicate opportunities to standardize stock sizes.


Performance Tips

1. Access Computed Fields Efficiently

# Good: Compute fields are cached per query
pattern = get_model("stock.cut.pattern").browse(pattern_id)
cut = pattern.total_cut      # Triggers get_waste()
waste = pattern.waste         # Uses cached result
total = pattern.total_waste   # Uses cached result

# Bad: Multiple separate queries
for pattern_id in pattern_ids:
    pattern = get_model("stock.cut.pattern").browse(pattern_id)
    print(pattern.waste)  # Individual query each time

2. Bulk Pattern Operations

# Good: Process all patterns at once
patterns = get_model("stock.cut.pattern").browse(pattern_ids)
for p in patterns:
    print(f"{p.stock_width}: {p.waste}")

# Bad: Individual lookups
for pattern_id in pattern_ids:
    p = get_model("stock.cut.pattern").browse(pattern_id)
    print(f"{p.stock_width}: {p.waste}")

Troubleshooting

"Negative waste calculated"

Cause: Total cut width exceeds stock width (data integrity issue)
Solution: - Verify cut widths and quantities - Check solver output for errors - Recalculate patterns using solve()

"Pattern waste seems too high"

Cause: Inefficient cutting pattern or mismatched stock/order sizes
Solution: - Review order widths and stock widths for compatibility - Consider adding intermediate stock sizes - Adjust order quantities to improve optimization

"Computed fields not updating"

Cause: Fields are cached and not automatically recalculated
Solution: - Re-browse the record to trigger recalculation - Patterns are typically immutable once created; delete and recreate if needed


Version History

Last Updated: October 2025
Model Version: stock_cut_pattern.py
Framework: Netforce


Additional Resources

  • Stock Cut Documentation: stock.cut
  • Stock Cut Order Documentation: stock.cut.order
  • Stock Cut Stock Documentation: stock.cut.stock

This documentation is generated for developer onboarding and reference purposes.