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
-
waste: Unused material per stock piece
-
total_waste: Total waste across all repetitions
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,
})
Related Models¶
| 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.