Rename SawCut library to CutList.Core
Rename the core library project from SawCut to CutList.Core for consistent branding across the solution. This includes: - Rename project folder and .csproj file - Update namespace from SawCut to CutList.Core - Update all using statements and project references Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
253
CutList.Core/Nesting/AdvancedFitEngine.cs
Normal file
253
CutList.Core/Nesting/AdvancedFitEngine.cs
Normal file
@@ -0,0 +1,253 @@
|
||||
using CutList.Core.Nesting;
|
||||
using CutList.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
|
||||
namespace CutList.Core.Nesting
|
||||
{
|
||||
public class AdvancedFitEngine : IEngine
|
||||
{
|
||||
public AdvancedFitEngine()
|
||||
{
|
||||
Bins = new List<Bin>();
|
||||
}
|
||||
|
||||
public double StockLength { get; set; }
|
||||
|
||||
public double Spacing { get; set; }
|
||||
|
||||
public int MaxBinCount { get; set; } = int.MaxValue;
|
||||
|
||||
private List<BinItem> Items { get; set; }
|
||||
|
||||
private List<Bin> Bins { get; set; }
|
||||
|
||||
public Result Pack(List<BinItem> items)
|
||||
{
|
||||
if (StockLength <= 0)
|
||||
throw new Exception("Stock length must be greater than 0");
|
||||
|
||||
Items = items.OrderByDescending(i => i.Length).ToList();
|
||||
|
||||
var result = new Result();
|
||||
var itemsTooLarge = Items.Where(i => i.Length > StockLength).ToList();
|
||||
result.AddItemsNotUsed(itemsTooLarge);
|
||||
|
||||
Items.RemoveAll(item => itemsTooLarge.Contains(item));
|
||||
|
||||
CreateBins();
|
||||
|
||||
var finalItemsTooLarge = Items.Where(i => i.Length > StockLength).ToList();
|
||||
result.AddItemsNotUsed(finalItemsTooLarge);
|
||||
result.AddBins(Bins);
|
||||
|
||||
foreach (var bin in result.Bins)
|
||||
{
|
||||
foreach (var item in bin.Items)
|
||||
{
|
||||
Items.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
result.AddItemsNotUsed(Items);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void CreateBins()
|
||||
{
|
||||
while (Items.Count > 0 && CanAddMoreBins())
|
||||
{
|
||||
var bin = new Bin(StockLength)
|
||||
{
|
||||
Spacing = Spacing
|
||||
};
|
||||
|
||||
FillBin(bin);
|
||||
|
||||
while (TryImprovePacking(bin))
|
||||
{
|
||||
}
|
||||
|
||||
bin.SortItems((a, b) =>
|
||||
{
|
||||
int comparison = b.Length.CompareTo(a.Length);
|
||||
return comparison != 0 ? comparison : a.Length.CompareTo(b.Length);
|
||||
});
|
||||
|
||||
Bins.Add(bin);
|
||||
|
||||
CreateDuplicateBins(bin);
|
||||
}
|
||||
|
||||
Bins = Bins
|
||||
.OrderByDescending(b => b.Utilization)
|
||||
.ThenBy(b => b.Items.Count)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private bool CanAddMoreBins()
|
||||
{
|
||||
if (MaxBinCount == -1)
|
||||
return true;
|
||||
|
||||
if (Bins.Count < MaxBinCount)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void FillBin(Bin bin)
|
||||
{
|
||||
for (int i = 0; i < Items.Count; i++)
|
||||
{
|
||||
if (bin.RemainingLength >= Items[i].Length)
|
||||
{
|
||||
bin.AddItem(Items[i]);
|
||||
Items.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateDuplicateBins(Bin originalBin)
|
||||
{
|
||||
// Count how many times the bin can be duplicated
|
||||
int duplicateCount = GetDuplicateCount(originalBin);
|
||||
|
||||
for (int i = 0; i < duplicateCount; i++)
|
||||
{
|
||||
if (!CanAddMoreBins())
|
||||
break;
|
||||
|
||||
var newBin = new Bin(originalBin.Length)
|
||||
{
|
||||
Spacing = Spacing
|
||||
};
|
||||
|
||||
foreach (var item in originalBin.Items)
|
||||
{
|
||||
var newItem = Items.FirstOrDefault(a => a.Length == item.Length);
|
||||
newBin.AddItem(newItem);
|
||||
Items.Remove(newItem);
|
||||
}
|
||||
|
||||
Bins.Add(newBin);
|
||||
}
|
||||
}
|
||||
|
||||
private int GetDuplicateCount(Bin bin)
|
||||
{
|
||||
int count = int.MaxValue;
|
||||
|
||||
foreach (var item in bin.Items.GroupBy(i => i.Length))
|
||||
{
|
||||
int availableCount = Items.Count(i => i.Length == item.Key);
|
||||
count = Math.Min(count, availableCount / item.Count());
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private bool TryImprovePacking(Bin bin)
|
||||
{
|
||||
if (bin.Items.Count == 0)
|
||||
return false;
|
||||
|
||||
if (Items.Count < 2)
|
||||
return false;
|
||||
|
||||
var lengthGroups = GroupItemsByLength(bin.Items);
|
||||
|
||||
var shortestLengthItemAvailable = Items.Min(i => i.Length);
|
||||
|
||||
foreach (var group in lengthGroups)
|
||||
{
|
||||
var minRemainingLength = bin.RemainingLength;
|
||||
var firstItem = group.Items.FirstOrDefault();
|
||||
bin.RemoveItem(firstItem);
|
||||
|
||||
for (int i = 0; i < Items.Count; i++)
|
||||
{
|
||||
var item1 = Items[i];
|
||||
|
||||
if (Items[i].Length > bin.RemainingLength)
|
||||
continue;
|
||||
|
||||
var bin2 = new Bin(bin.RemainingLength);
|
||||
bin2.Spacing = bin.Spacing;
|
||||
bin2.AddItem(item1);
|
||||
|
||||
for (int j = i + 1; j < Items.Count; j++)
|
||||
{
|
||||
if (bin2.RemainingLength < shortestLengthItemAvailable)
|
||||
break;
|
||||
|
||||
var item2 = Items[j];
|
||||
|
||||
if (item2.Length > bin2.RemainingLength)
|
||||
continue;
|
||||
|
||||
bin2.AddItem(item2);
|
||||
}
|
||||
|
||||
if (bin2.RemainingLength < minRemainingLength)
|
||||
{
|
||||
Items.Add(firstItem);
|
||||
bin.AddItems(bin2.Items);
|
||||
|
||||
foreach (var item in bin2.Items)
|
||||
{
|
||||
Items.Remove(item);
|
||||
}
|
||||
|
||||
// improvement made
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bin.AddItem(firstItem);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<LengthGroup> GroupItemsByLength(IEnumerable<BinItem> items)
|
||||
{
|
||||
var groups = new List<LengthGroup>();
|
||||
var groupMap = new Dictionary<double, LengthGroup>();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (!groupMap.TryGetValue(item.Length, out var group))
|
||||
{
|
||||
group = new LengthGroup
|
||||
{
|
||||
Length = item.Length,
|
||||
Items = new List<BinItem>()
|
||||
};
|
||||
groupMap[item.Length] = group;
|
||||
groups.Add(group);
|
||||
}
|
||||
group.Items.Add(item);
|
||||
}
|
||||
|
||||
groups.Sort((a, b) => b.Length.CompareTo(a.Length));
|
||||
if (groups.Count > 0)
|
||||
{
|
||||
groups.RemoveAt(0); // Remove the largest length group
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
}
|
||||
|
||||
internal class LengthGroup
|
||||
{
|
||||
public double Length { get; set; }
|
||||
public List<BinItem> Items { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
111
CutList.Core/Nesting/BestFitEngine.cs
Normal file
111
CutList.Core/Nesting/BestFitEngine.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
|
||||
namespace CutList.Core.Nesting
|
||||
{
|
||||
public class BestFitEngine : IEngine
|
||||
{
|
||||
public double StockLength { get; set; }
|
||||
|
||||
public double Spacing { get; set; }
|
||||
|
||||
public int MaxBinCount { get; set; } = int.MaxValue;
|
||||
|
||||
private List<BinItem> Items { get; set; }
|
||||
|
||||
public Result Pack(List<BinItem> items)
|
||||
{
|
||||
if (StockLength <= 0)
|
||||
throw new Exception("Stock length must be greater than 0");
|
||||
|
||||
Items = items.OrderByDescending(i => i.Length).ToList();
|
||||
|
||||
var result = new Result();
|
||||
var itemsTooLarge = Items.Where(i => i.Length > StockLength).ToList();
|
||||
result.AddItemsNotUsed(itemsTooLarge);
|
||||
|
||||
foreach (var item in itemsTooLarge)
|
||||
{
|
||||
Items.Remove(item);
|
||||
}
|
||||
|
||||
var bins = GetBins();
|
||||
result.AddBins(bins);
|
||||
|
||||
foreach (var bin in bins)
|
||||
{
|
||||
foreach (var item in bin.Items)
|
||||
{
|
||||
Items.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
result.AddItemsNotUsed(Items);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Bin> GetBins()
|
||||
{
|
||||
var bins = new List<Bin>();
|
||||
|
||||
foreach (var item in Items)
|
||||
{
|
||||
Bin best_bin;
|
||||
|
||||
if (!FindBin(bins.ToArray(), item.Length, out best_bin))
|
||||
{
|
||||
if (item.Length > StockLength)
|
||||
continue;
|
||||
|
||||
if (bins.Count < MaxBinCount)
|
||||
{
|
||||
best_bin = CreateBin();
|
||||
bins.Add(best_bin);
|
||||
}
|
||||
}
|
||||
|
||||
if (best_bin != null)
|
||||
best_bin.AddItem(item);
|
||||
|
||||
|
||||
}
|
||||
|
||||
return bins
|
||||
.OrderByDescending(b => b.Utilization)
|
||||
.ThenBy(b => b.Items.Count)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private Bin CreateBin()
|
||||
{
|
||||
var length = StockLength;
|
||||
|
||||
return new Bin(length)
|
||||
{
|
||||
Spacing = Spacing
|
||||
};
|
||||
}
|
||||
|
||||
private static bool FindBin(IEnumerable<Bin> bins, double length, out Bin found)
|
||||
{
|
||||
found = null;
|
||||
|
||||
foreach (var bin in bins)
|
||||
{
|
||||
if (bin.RemainingLength < length)
|
||||
continue;
|
||||
|
||||
if (found == null)
|
||||
found = bin;
|
||||
|
||||
if (bin.RemainingLength < found.RemainingLength)
|
||||
found = bin;
|
||||
}
|
||||
|
||||
return (found != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
CutList.Core/Nesting/EngineFactory.cs
Normal file
19
CutList.Core/Nesting/EngineFactory.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace CutList.Core.Nesting
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of IEngineFactory that creates AdvancedFitEngine instances.
|
||||
/// Can be extended to support different engine types based on configuration.
|
||||
/// </summary>
|
||||
public class EngineFactory : IEngineFactory
|
||||
{
|
||||
public IEngine CreateEngine(double stockLength, double spacing, int maxBinCount)
|
||||
{
|
||||
return new AdvancedFitEngine
|
||||
{
|
||||
StockLength = stockLength,
|
||||
Spacing = spacing,
|
||||
MaxBinCount = maxBinCount
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
9
CutList.Core/Nesting/IEngine.cs
Normal file
9
CutList.Core/Nesting/IEngine.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CutList.Core.Nesting
|
||||
{
|
||||
public interface IEngine
|
||||
{
|
||||
Result Pack(List<BinItem> items);
|
||||
}
|
||||
}
|
||||
18
CutList.Core/Nesting/IEngineFactory.cs
Normal file
18
CutList.Core/Nesting/IEngineFactory.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace CutList.Core.Nesting
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory interface for creating bin packing engines.
|
||||
/// Allows for dependency injection and testing without hard-coded engine types.
|
||||
/// </summary>
|
||||
public interface IEngineFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a configured engine instance for bin packing.
|
||||
/// </summary>
|
||||
/// <param name="stockLength">The length of stock bins</param>
|
||||
/// <param name="spacing">The spacing/kerf between items</param>
|
||||
/// <param name="maxBinCount">Maximum number of bins to create</param>
|
||||
/// <returns>A configured IEngine instance</returns>
|
||||
IEngine CreateEngine(double stockLength, double spacing, int maxBinCount);
|
||||
}
|
||||
}
|
||||
68
CutList.Core/Nesting/MultiBinEngine.cs
Normal file
68
CutList.Core/Nesting/MultiBinEngine.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace CutList.Core.Nesting
|
||||
{
|
||||
public class MultiBinEngine : IEngine
|
||||
{
|
||||
private readonly IEngineFactory _engineFactory;
|
||||
|
||||
public MultiBinEngine() : this(new EngineFactory())
|
||||
{
|
||||
}
|
||||
|
||||
public MultiBinEngine(IEngineFactory engineFactory)
|
||||
{
|
||||
_engineFactory = engineFactory ?? throw new ArgumentNullException(nameof(engineFactory));
|
||||
_bins = new List<MultiBin>();
|
||||
}
|
||||
|
||||
private readonly List<MultiBin> _bins;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the read-only collection of bins.
|
||||
/// Use SetBins() to configure bins for packing.
|
||||
/// </summary>
|
||||
public IReadOnlyList<MultiBin> Bins => _bins.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the bins to use for packing.
|
||||
/// </summary>
|
||||
public void SetBins(IEnumerable<MultiBin> bins)
|
||||
{
|
||||
_bins.Clear();
|
||||
if (bins != null)
|
||||
{
|
||||
_bins.AddRange(bins);
|
||||
}
|
||||
}
|
||||
|
||||
public double Spacing { get; set; }
|
||||
|
||||
public Result Pack(List<BinItem> items)
|
||||
{
|
||||
var bins = _bins
|
||||
.Where(b => b.Length > 0)
|
||||
.OrderBy(b => b.Priority)
|
||||
.ThenBy(b => b.Length)
|
||||
.ToList();
|
||||
|
||||
var result = new Result();
|
||||
var remainingItems = new List<BinItem>(items);
|
||||
|
||||
foreach (var bin in bins)
|
||||
{
|
||||
var engine = _engineFactory.CreateEngine(bin.Length, Spacing, bin.Quantity);
|
||||
var r = engine.Pack(remainingItems);
|
||||
|
||||
result.AddBins(r.Bins);
|
||||
remainingItems = r.ItemsNotUsed.ToList();
|
||||
}
|
||||
|
||||
result.AddItemsNotUsed(remainingItems);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
CutList.Core/Nesting/Result.cs
Normal file
40
CutList.Core/Nesting/Result.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CutList.Core.Nesting
|
||||
{
|
||||
public class Result
|
||||
{
|
||||
private readonly List<BinItem> _itemsNotUsed;
|
||||
private readonly List<Bin> _bins;
|
||||
|
||||
public Result()
|
||||
{
|
||||
_itemsNotUsed = new List<BinItem>();
|
||||
_bins = new List<Bin>();
|
||||
}
|
||||
|
||||
public IReadOnlyList<BinItem> ItemsNotUsed => _itemsNotUsed;
|
||||
|
||||
public IReadOnlyList<Bin> Bins => _bins;
|
||||
|
||||
public void AddItemNotUsed(BinItem item)
|
||||
{
|
||||
_itemsNotUsed.Add(item);
|
||||
}
|
||||
|
||||
public void AddItemsNotUsed(IEnumerable<BinItem> items)
|
||||
{
|
||||
_itemsNotUsed.AddRange(items);
|
||||
}
|
||||
|
||||
public void AddBin(Bin bin)
|
||||
{
|
||||
_bins.Add(bin);
|
||||
}
|
||||
|
||||
public void AddBins(IEnumerable<Bin> bins)
|
||||
{
|
||||
_bins.AddRange(bins);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user