This is a basic application to calculate how to cut pieces from stock. It can be used to cut lumber or iron rods, for your wood/iron projects. It's simple but useful. It's vibe coded, cause i wanted to calculate the cuts for a project of mine and didn't have the time to program it my self. I tested it with other online tools and gives the same results. This can either mean that the script is OK or also the other tools were vibe coded :P
Anyway... here it is.
#!/usr/bin/python3
import sys
from itertools import combinations
from collections import defaultdict
def get_positive_float(prompt):
"""Get a positive float from user input"""
while True:
try:
value = float(input(prompt))
if value >= 0:
return value
print("Please enter a positive number.")
except ValueError:
print("Invalid input. Please enter a number.")
def get_positive_int(prompt):
"""Get a positive integer from user input"""
while True:
try:
value = int(input(prompt))
if value >= 0:
return value
print("Please enter a positive integer.")
except ValueError:
print("Invalid input. Please enter an integer.")
def find_best_cut_pattern(stock_length, pieces_to_cut, kerf=0):
"""
Find the best combination of pieces that fit in one stock length.
Uses dynamic programming approach for optimal packing.
"""
# Convert pieces to list of (length, id) for tracking
piece_list = []
for length, quantity in pieces_to_cut.items():
for i in range(quantity):
piece_list.append((length, length))
# Sort by length descending for better packing
piece_list.sort(reverse=True)
# Dynamic programming to find best combination
# dp[i] = (max pieces, min waste, list of piece indices)
n = len(piece_list)
best_result = None
best_waste = float('inf')
best_count = 0
# Try different combinations using recursive approach with memoization
def try_combinations(index, current_length, current_pieces):
nonlocal best_result, best_waste, best_count
if current_length > stock_length:
return
# Check if this is better than current best
waste = stock_length - current_length
count = len(current_pieces)
# Prefer more pieces, then less waste
if count > best_count or (count == best_count and waste < best_waste):
best_count = count
best_waste = waste
best_result = current_pieces.copy()
if index >= n:
return
# Try including current piece
if current_length + piece_list[index][0] + kerf <= stock_length:
current_pieces.append(index)
try_combinations(index + 1, current_length + piece_list[index][0] + kerf, current_pieces)
current_pieces.pop()
# Try skipping current piece
try_combinations(index + 1, current_length, current_pieces)
try_combinations(0, 0, [])
if best_result is None:
return None, piece_list
# Return the lengths of pieces in best combination
selected_pieces = [piece_list[i][0] for i in best_result]
return selected_pieces, piece_list
def calculate_cutting_plan(stock_length, pieces_needed, kerf=0):
"""
Calculate the optimal cutting plan.
Returns list of cuts for each stock piece.
"""
# Make a copy of pieces needed
remaining_pieces = pieces_needed.copy()
cutting_plan = []
while any(qty > 0 for qty in remaining_pieces.values()):
# Find best combination for one stock piece
pieces_available = {k: v for k, v in remaining_pieces.items() if v > 0}
if not pieces_available:
break
# Try to find best pattern
best_pattern = find_optimal_pattern(stock_length, pieces_available, kerf)
if not best_pattern:
# Can't fit any more pieces
break
# Remove used pieces from remaining
for piece_length in best_pattern:
remaining_pieces[piece_length] -= 1
cutting_plan.append(best_pattern)
return cutting_plan, remaining_pieces
def find_optimal_pattern(stock_length, pieces_available, kerf=0):
"""
Find the optimal pattern of cuts for a single stock piece.
Uses a greedy approach with lookahead.
"""
# Convert to list of (length, quantity)
pieces_list = [(length, qty) for length, qty in pieces_available.items()]
pieces_list.sort(reverse=True) # Sort by length descending
best_pattern = []
best_waste = stock_length
best_count = 0
# Try greedy approach - always take the longest piece that fits
def greedy_fill(remaining_length, available_pieces):
pattern = []
remaining = available_pieces.copy()
# Keep taking pieces while they fit
changed = True
while changed:
changed = False
for length in sorted(remaining.keys(), reverse=True):
if remaining[length] > 0 and length + kerf <= remaining_length:
pattern.append(length)
remaining[length] -= 1
remaining_length -= (length + kerf)
changed = True
break
return pattern
# Try different starting pieces
for start_length in sorted(pieces_available.keys(), reverse=True):
if pieces_available[start_length] > 0 and start_length + kerf <= stock_length:
# Create a temporary copy with one less of start piece
temp_available = pieces_available.copy()
temp_available[start_length] -= 1
# Greedily fill the rest
pattern = [start_length]
remaining_length = stock_length - start_length - kerf
rest_pattern = greedy_fill(remaining_length, temp_available)
pattern.extend(rest_pattern)
waste = stock_length - sum(pattern) - kerf * len(pattern)
count = len(pattern)
# Choose pattern with most pieces, then least waste
if count > best_count or (count == best_count and waste < best_waste):
best_count = count
best_waste = waste
best_pattern = pattern
return best_pattern if best_pattern else None
def display_results(stock_length, cutting_plan, remaining_pieces, kerf):
"""Display the cutting plan in a readable format"""
print("\n" + "="*60)
print("OPTIMAL CUTTING PLAN")
print("="*60)
if not cutting_plan:
print("No cuts can be made with the given stock length!")
return
total_stock_used = len(cutting_plan)
total_waste = 0
total_pieces = 0
print(f"\nStock length: {stock_length}m")
if kerf > 0:
print(f"Kerf (blade width): {kerf}m")
print(f"\nNumber of {stock_length}m pieces needed: {total_stock_used}")
print("-"*60)
for i, cuts in enumerate(cutting_plan, 1):
cuts_used = sum(cuts)
kerf_total = kerf * len(cuts)
waste = stock_length - cuts_used - kerf_total
total_waste += waste
total_pieces += len(cuts)
cuts_str = " + ".join(f"{c}m" for c in cuts)
print(f"Piece {i}: {cuts_str}")
print(f" Used: {cuts_used}m, Waste: {waste:.3f}m, Pieces: {len(cuts)}")
print("-"*60)
print(f"\nSUMMARY:")
print(f" Total {stock_length}m pieces needed: {total_stock_used}")
print(f" Total pieces cut: {total_pieces}")
print(f" Total material used: {total_pieces * 0 + sum(sum(cuts) for cuts in cutting_plan):.3f}m")
print(f" Total waste: {total_waste:.3f}m")
print(f" Waste percentage: {(total_waste / (total_stock_used * stock_length) * 100):.2f}%")
if remaining_pieces and any(qty > 0 for qty in remaining_pieces.values()):
print(f"\nWARNING: Some pieces could not be cut:")
for length, qty in remaining_pieces.items():
if qty > 0:
print(f" - {qty} piece(s) of {length}m")
print("="*60)
def main():
print("="*60)
print("LUMBER CUTTING OPTIMIZER")
print("="*60)
print("\nThis program calculates the optimal way to cut lumber")
print("to minimize waste.\n")
# Get stock length
stock_length = get_positive_float("Enter the length of stock pieces (e.g., 6 for 6m): ")
# Get kerf (optional)
use_kerf = input("\nDo you want to account for blade width (kerf)? (y/n): ").lower().strip()
kerf = 0
if use_kerf == 'y':
kerf = get_positive_float("Enter kerf/blade width (e.g., 0.003 for 3mm): ")
# Get pieces needed
pieces_needed = defaultdict(float)
print("\nEnter the pieces you need to cut.")
print("(Enter 0 for length when done)\n")
while True:
length = get_positive_float("Enter piece length (or 0 to finish): ")
if length == 0:
break
if length > stock_length:
print(f" Warning: {length}m is longer than stock length {stock_length}m!")
continue
quantity = get_positive_int(f"Enter quantity of {length}m pieces: ")
pieces_needed[length] += quantity
if not pieces_needed:
print("No pieces entered. Exiting.")
return
# Show input summary
print("\n" + "-"*40)
print("Pieces to cut:")
total_pieces_needed = 0
total_length_needed = 0
for length, qty in sorted(pieces_needed.items(), reverse=True):
print(f" {int(qty)} x {length}m = {qty * length:.3f}m")
total_pieces_needed += qty
total_length_needed += qty * length
print(f"\nTotal pieces: {int(total_pieces_needed)}")
print(f"Total length needed: {total_length_needed:.3f}m")
print(f"Minimum stock pieces needed: {int(total_length_needed / stock_length) + 1}")
# Calculate cutting plan
print("\nCalculating optimal cutting plan...")
cutting_plan, remaining = calculate_cutting_plan(stock_length, dict(pieces_needed), kerf)
# Display results
display_results(stock_length, cutting_plan, remaining, kerf)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\nProgram interrupted. Goodbye!")
sys.exit(0)


