Files
CutList/CutList.Core/Nesting/BestFitEngine.cs
AJ Isaacs b19ecf3610 refactor: Redesign nesting engines with pipeline pattern and add exhaustive search
- Rename Result to PackResult to avoid confusion with Result<T>
- Add PackingRequest as immutable configuration replacing mutable engine state
- Add PackingStrategy enum (AdvancedFit, BestFit, Exhaustive)
- Implement pipeline pattern for composable packing steps
- Rewrite AdvancedFitEngine as stateless using pipeline
- Rewrite BestFitEngine as stateless
- Add ExhaustiveFitEngine with symmetry breaking for optimal solutions
  - Tries all bin assignments to find minimum bins
  - Falls back to AdvancedFit for >20 items
  - Configurable threshold via constructor
- Update IEngine/IEngineFactory interfaces for new pattern
- Add strategy parameter to MCP tools

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 15:16:40 -05:00

86 lines
2.5 KiB
C#

namespace CutList.Core.Nesting
{
/// <summary>
/// Best-Fit Decreasing bin packing engine.
/// Places each item in the bin with the least remaining space that can still fit it.
/// This is a stateless engine - all state is passed via PackingRequest.
/// </summary>
public class BestFitEngine : IEngine
{
/// <summary>
/// Packs items into bins using the Best-Fit Decreasing algorithm.
/// </summary>
public PackResult Pack(PackingRequest request)
{
var result = new PackResult();
var items = request.Items.OrderByDescending(i => i.Length).ToList();
var bins = new List<Bin>();
// Filter oversized items
var oversizedItems = items.Where(i => i.Length > request.StockLength).ToList();
foreach (var item in oversizedItems)
{
items.Remove(item);
result.AddItemNotUsed(item);
}
// Pack remaining items using best-fit
foreach (var item in items)
{
if (!TryFindBestBin(bins, item.Length, out var bestBin))
{
if (bins.Count < request.MaxBinCount)
{
bestBin = CreateBin(request);
bins.Add(bestBin);
}
}
if (bestBin != null)
{
bestBin.AddItem(item);
}
else
{
result.AddItemNotUsed(item);
}
}
// Sort bins by utilization
var sortedBins = bins
.OrderByDescending(b => b.Utilization)
.ThenBy(b => b.Items.Count);
result.AddBins(sortedBins);
return result;
}
private static Bin CreateBin(PackingRequest request)
{
return new Bin(request.StockLength)
{
Spacing = request.Spacing
};
}
private static bool TryFindBestBin(IEnumerable<Bin> bins, double length, out Bin? found)
{
found = null;
foreach (var bin in bins)
{
if (bin.RemainingLength < length)
continue;
if (found == null || bin.RemainingLength < found.RemainingLength)
{
found = bin;
}
}
return found != null;
}
}
}