diff --git a/OpenNest.Engine.Tests/NestingEngines/BarPackerTests.cs b/OpenNest.Engine.Tests/NestingEngines/BarPackerTests.cs new file mode 100644 index 00000000..25aba0b4 --- /dev/null +++ b/OpenNest.Engine.Tests/NestingEngines/BarPackerTests.cs @@ -0,0 +1,31 @@ +using System.Linq; +using System.Threading; +using OpenNest.Engine.NestingEngines.FlatBar; + +namespace OpenNest.Engine.Tests.NestingEngines; + +public class BarPackerTests +{ + [Fact] + public void SwapOptimizationStopsWhenOnlyIdenticalCopiesRemain() + { + // One bar holds [6, 1.5, 1.5] with 0.99 free; two 1.5 copies are left over. Trading a + // placed copy for an unplaced one changes nothing, but a trial charges the copy's + // self-nesting gain again and looked 0.01 fuller, so the swap used to repeat forever. + var items = new[] + { + new BarItem(0, 6, 0, 0), + new BarItem(1, 1.5, 0, 0.01), + new BarItem(1, 1.5, 0, 0.01), + new BarItem(1, 1.5, 0, 0.01), + new BarItem(1, 1.5, 0, 0.01), + }; + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + var plan = BarPacker.Pack(items, [10.0], 1, timeout.Token); + + var bar = Assert.Single(plan.Bars); + Assert.Equal(new[] { 0, 1, 1 }, bar.Items.Select(i => i.Type).OrderBy(t => t)); + Assert.Equal(2, plan.Unplaced.Count); + } +} diff --git a/OpenNest.Engine/NestingEngines/FlatBar/BarPacker.cs b/OpenNest.Engine/NestingEngines/FlatBar/BarPacker.cs index e9132956..96dd12e2 100644 --- a/OpenNest.Engine/NestingEngines/FlatBar/BarPacker.cs +++ b/OpenNest.Engine/NestingEngines/FlatBar/BarPacker.cs @@ -174,7 +174,13 @@ internal static class BarPacker trial.Items.Add(next); } - if (trial.Remaining < before) + // Judge the swap on the merged run: the trial charges gains again for types the + // run already holds, so trading a copy for an identical one looks fuller and the + // swap would repeat forever. + var merged = new PackedSegment(segment.Capacity); + merged.Items.AddRange(segment.Items); + merged.Items.AddRange(trial.Items); + if (merged.Remaining < before) { remaining.Add(removed); segment.Items.AddRange(trial.Items);