fix: consolidate tail plates by upgrading instead of creating new plates
Three fixes to TryUpgradeOrNewPlate and a new post-pass: 1. Change ShouldUpgrade from < to <= so upgrade wins when costs are tied (e.g., all zero) — previously 0 < 0 was always false 2. Guard against "upgrades" that shrink a dimension — when options are sorted by cost and costs are equal, the next option may have a smaller length despite higher width (e.g., 72x96 after 60x144) 3. Revert plate size when upgrade fill fails — the plate was being resized before confirming parts fit, leaving it at the wrong size 4. Add TryConsolidateTailPlates post-pass: after all nesting, find the lowest-utilization new plate and try to absorb its parts into another plate via upgrade. Eliminates wasteful tail plates (e.g., a 48x96 plate at 21% util for 2 parts that fit in upgraded space). Real nest file: 6 plates → 5 plates, all 43 parts placed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -160,7 +160,7 @@ namespace OpenNest
|
|||||||
|
|
||||||
return new UpgradeDecision
|
return new UpgradeDecision
|
||||||
{
|
{
|
||||||
ShouldUpgrade = upgradeCost < netNewCost,
|
ShouldUpgrade = upgradeCost <= netNewCost,
|
||||||
UpgradeCost = upgradeCost,
|
UpgradeCost = upgradeCost,
|
||||||
NewPlateCost = netNewCost,
|
NewPlateCost = netNewCost,
|
||||||
};
|
};
|
||||||
@@ -292,6 +292,9 @@ namespace OpenNest
|
|||||||
CreateSharedPlates(leftovers);
|
CreateSharedPlates(leftovers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_plateOptions != null && _plateOptions.Count > 0 && !_token.IsCancellationRequested)
|
||||||
|
TryConsolidateTailPlates();
|
||||||
|
|
||||||
foreach (var item in sorted.Where(i => i.Quantity > 0))
|
foreach (var item in sorted.Where(i => i.Quantity > 0))
|
||||||
result.UnplacedItems.Add(item);
|
result.UnplacedItems.Add(item);
|
||||||
|
|
||||||
@@ -473,6 +476,12 @@ namespace OpenNest
|
|||||||
for (var i = currentIdx + 1; i < sortedOptions.Count; i++)
|
for (var i = currentIdx + 1; i < sortedOptions.Count; i++)
|
||||||
{
|
{
|
||||||
var upgradeOption = sortedOptions[i];
|
var upgradeOption = sortedOptions[i];
|
||||||
|
|
||||||
|
// Only consider options that are at least as large in both dimensions.
|
||||||
|
if (upgradeOption.Width < currentOption.Width - Tolerance.Epsilon
|
||||||
|
|| upgradeOption.Length < currentOption.Length - Tolerance.Epsilon)
|
||||||
|
continue;
|
||||||
|
|
||||||
var smallestNew = sortedOptions.FirstOrDefault(o =>
|
var smallestNew = sortedOptions.FirstOrDefault(o =>
|
||||||
{
|
{
|
||||||
var ww = o.Width - _template.EdgeSpacing.Left - _template.EdgeSpacing.Right;
|
var ww = o.Width - _template.EdgeSpacing.Left - _template.EdgeSpacing.Right;
|
||||||
@@ -490,6 +499,9 @@ namespace OpenNest
|
|||||||
|
|
||||||
if (decision.ShouldUpgrade)
|
if (decision.ShouldUpgrade)
|
||||||
{
|
{
|
||||||
|
var oldSize = pr.Plate.Size;
|
||||||
|
var oldChosenSize = pr.ChosenSize;
|
||||||
|
|
||||||
pr.Plate.Size = new Size(upgradeOption.Width, upgradeOption.Length);
|
pr.Plate.Size = new Size(upgradeOption.Width, upgradeOption.Length);
|
||||||
pr.ChosenSize = upgradeOption;
|
pr.ChosenSize = upgradeOption;
|
||||||
|
|
||||||
@@ -497,6 +509,10 @@ namespace OpenNest
|
|||||||
|
|
||||||
if (remainingArea.Count > 0 && FillAndPlace(pr, remainingArea[0], item) > 0)
|
if (remainingArea.Count > 0 && FillAndPlace(pr, remainingArea[0], item) > 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
// Revert if nothing was placed.
|
||||||
|
pr.Plate.Size = oldSize;
|
||||||
|
pr.ChosenSize = oldChosenSize;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -504,5 +520,78 @@ namespace OpenNest
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TryConsolidateTailPlates()
|
||||||
|
{
|
||||||
|
var activePlates = _platePool.Where(p => p.Parts.Count > 0 && p.IsNew).ToList();
|
||||||
|
if (activePlates.Count < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var sortedOptions = _plateOptions.OrderBy(o => o.Cost).ToList();
|
||||||
|
|
||||||
|
// Try to absorb the smallest-utilization new plate into another plate via upgrade.
|
||||||
|
var donor = activePlates.OrderBy(p => p.Plate.Utilization()).First();
|
||||||
|
var donorParts = donor.Parts.ToList();
|
||||||
|
|
||||||
|
foreach (var target in activePlates)
|
||||||
|
{
|
||||||
|
if (target == donor || target.ChosenSize == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var currentOption = target.ChosenSize;
|
||||||
|
|
||||||
|
// Try each larger option that doesn't shrink any dimension.
|
||||||
|
foreach (var upgradeOption in sortedOptions.Where(o =>
|
||||||
|
o.Width >= currentOption.Width - Tolerance.Epsilon
|
||||||
|
&& o.Length >= currentOption.Length - Tolerance.Epsilon
|
||||||
|
&& (o.Width > currentOption.Width + Tolerance.Epsilon
|
||||||
|
|| o.Length > currentOption.Length + Tolerance.Epsilon)))
|
||||||
|
{
|
||||||
|
var oldSize = target.Plate.Size;
|
||||||
|
var oldChosenSize = target.ChosenSize;
|
||||||
|
|
||||||
|
target.Plate.Size = new Size(upgradeOption.Width, upgradeOption.Length);
|
||||||
|
target.ChosenSize = upgradeOption;
|
||||||
|
|
||||||
|
var remnants = RemnantFinder.FromPlate(target.Plate).FindRemnants();
|
||||||
|
if (remnants.Count == 0)
|
||||||
|
{
|
||||||
|
target.Plate.Size = oldSize;
|
||||||
|
target.ChosenSize = oldChosenSize;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to pack all donor parts into the remnant space.
|
||||||
|
var engine = NestEngineRegistry.Create(target.Plate);
|
||||||
|
var tempItems = donorParts
|
||||||
|
.GroupBy(p => p.BaseDrawing.Name)
|
||||||
|
.Select(g => new NestItem
|
||||||
|
{
|
||||||
|
Drawing = g.First().BaseDrawing,
|
||||||
|
Quantity = g.Count(),
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var placed = engine.PackArea(remnants[0], tempItems, _progress, _token);
|
||||||
|
|
||||||
|
if (placed.Count >= donorParts.Count)
|
||||||
|
{
|
||||||
|
// All donor parts fit — absorb them.
|
||||||
|
target.Plate.Parts.AddRange(placed);
|
||||||
|
target.Parts.AddRange(placed);
|
||||||
|
|
||||||
|
foreach (var p in donorParts)
|
||||||
|
donor.Plate.Parts.Remove(p);
|
||||||
|
donor.Parts.Clear();
|
||||||
|
_platePool.Remove(donor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Didn't fit all parts — revert.
|
||||||
|
target.Plate.Size = oldSize;
|
||||||
|
target.ChosenSize = oldChosenSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user