Duplicate bins when possible in AdvancedFitEngine
This commit is contained in:
@@ -9,6 +9,11 @@ namespace SawCut.Nesting
|
|||||||
{
|
{
|
||||||
public class AdvancedFitEngine : IEngine
|
public class AdvancedFitEngine : IEngine
|
||||||
{
|
{
|
||||||
|
public AdvancedFitEngine()
|
||||||
|
{
|
||||||
|
Bins = new List<Bin>();
|
||||||
|
}
|
||||||
|
|
||||||
public double StockLength { get; set; }
|
public double StockLength { get; set; }
|
||||||
|
|
||||||
public double Spacing { get; set; }
|
public double Spacing { get; set; }
|
||||||
@@ -17,6 +22,8 @@ namespace SawCut.Nesting
|
|||||||
|
|
||||||
private List<BinItem> Items { get; set; }
|
private List<BinItem> Items { get; set; }
|
||||||
|
|
||||||
|
private List<Bin> Bins { get; set; }
|
||||||
|
|
||||||
public Result Pack(List<BinItem> items)
|
public Result Pack(List<BinItem> items)
|
||||||
{
|
{
|
||||||
if (StockLength <= 0)
|
if (StockLength <= 0)
|
||||||
@@ -31,8 +38,10 @@ namespace SawCut.Nesting
|
|||||||
|
|
||||||
Items.RemoveAll(item => result.ItemsNotUsed.Contains(item));
|
Items.RemoveAll(item => result.ItemsNotUsed.Contains(item));
|
||||||
|
|
||||||
|
CreateBins();
|
||||||
|
|
||||||
result.ItemsNotUsed = Items.Where(i => i.Length > StockLength).ToList();
|
result.ItemsNotUsed = Items.Where(i => i.Length > StockLength).ToList();
|
||||||
result.Bins = GetBins();
|
result.Bins = Bins;
|
||||||
|
|
||||||
foreach (var bin in result.Bins)
|
foreach (var bin in result.Bins)
|
||||||
{
|
{
|
||||||
@@ -47,51 +56,101 @@ namespace SawCut.Nesting
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Bin> GetBins()
|
private void CreateBins()
|
||||||
{
|
{
|
||||||
var bins = new List<Bin>();
|
while (Items.Count > 0 && CanAddMoreBins())
|
||||||
|
|
||||||
while (Items.Count > 0 && (MaxBinCount == -1 || bins.Count < MaxBinCount))
|
|
||||||
{
|
{
|
||||||
var bin = new Bin(StockLength);
|
var bin = new Bin(StockLength)
|
||||||
bin.Spacing = Spacing;
|
{
|
||||||
|
Spacing = Spacing
|
||||||
|
};
|
||||||
|
|
||||||
FillBin(bin);
|
FillBin(bin);
|
||||||
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
while (TryImprovePacking(bin))
|
while (TryImprovePacking(bin))
|
||||||
{
|
{
|
||||||
count++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bin.Items.Sort((a, b) =>
|
||||||
|
{
|
||||||
|
int comparison = b.Length.CompareTo(a.Length);
|
||||||
|
return comparison != 0 ? comparison : a.Length.CompareTo(b.Length);
|
||||||
|
});
|
||||||
|
|
||||||
bin.Items = bin.Items.OrderByDescending(i => i.Length).ToList();
|
Bins.Add(bin);
|
||||||
|
|
||||||
bins.Add(bin);
|
CreateDuplicateBins(bin);
|
||||||
}
|
}
|
||||||
|
|
||||||
return bins
|
Bins = Bins
|
||||||
.OrderByDescending(b => b.Utilization)
|
.OrderByDescending(b => b.Utilization)
|
||||||
.ThenBy(b => b.Items.Count)
|
.ThenBy(b => b.Items.Count)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool CanAddMoreBins()
|
||||||
|
{
|
||||||
|
if (MaxBinCount == -1)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (Bins.Count < MaxBinCount)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void FillBin(Bin bin)
|
private void FillBin(Bin bin)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < Items.Count; i++)
|
for (int i = 0; i < Items.Count; i++)
|
||||||
{
|
{
|
||||||
var item = Items[i];
|
if (bin.RemainingLength >= Items[i].Length)
|
||||||
|
{
|
||||||
if (bin.RemainingLength >= item.Length)
|
bin.Items.Add(Items[i]);
|
||||||
bin.Items.Add(item);
|
Items.RemoveAt(i);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var item in bin.Items)
|
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++)
|
||||||
{
|
{
|
||||||
Items.Remove(item);
|
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.Items.Add(item);
|
||||||
|
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)
|
private bool TryImprovePacking(Bin bin)
|
||||||
{
|
{
|
||||||
if (bin.Items.Count == 0)
|
if (bin.Items.Count == 0)
|
||||||
@@ -100,18 +159,14 @@ namespace SawCut.Nesting
|
|||||||
if (Items.Count < 2)
|
if (Items.Count < 2)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var lengthGroups = bin.Items
|
var lengthGroups = GroupItemsByLength(bin.Items);
|
||||||
.OrderByDescending(i => i.Length)
|
|
||||||
.GroupBy(i => i.Length)
|
|
||||||
.Skip(1);
|
|
||||||
|
|
||||||
var minItemLength = Items.Min(i => i.Length);
|
var shortestLengthItemAvailable = Items.Min(i => i.Length);
|
||||||
|
|
||||||
foreach (var group in lengthGroups)
|
foreach (var group in lengthGroups)
|
||||||
{
|
{
|
||||||
var minRemainingLength = bin.RemainingLength;
|
var minRemainingLength = bin.RemainingLength;
|
||||||
|
var firstItem = group.Items.FirstOrDefault();
|
||||||
var firstItem = group.First();
|
|
||||||
bin.Items.Remove(firstItem);
|
bin.Items.Remove(firstItem);
|
||||||
|
|
||||||
for (int i = 0; i < Items.Count; i++)
|
for (int i = 0; i < Items.Count; i++)
|
||||||
@@ -127,7 +182,7 @@ namespace SawCut.Nesting
|
|||||||
|
|
||||||
for (int j = i + 1; j < Items.Count; j++)
|
for (int j = i + 1; j < Items.Count; j++)
|
||||||
{
|
{
|
||||||
if (bin2.RemainingLength < minItemLength)
|
if (bin2.RemainingLength < shortestLengthItemAvailable)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
var item2 = Items[j];
|
var item2 = Items[j];
|
||||||
@@ -158,6 +213,41 @@ namespace SawCut.Nesting
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<LengthGroup> GroupItemsByLength(List<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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user