diff --git a/OpenNest.Engine.Tests/Fill/SortStripsTests.cs b/OpenNest.Engine.Tests/Fill/SortStripsTests.cs new file mode 100644 index 0000000..40bce6e --- /dev/null +++ b/OpenNest.Engine.Tests/Fill/SortStripsTests.cs @@ -0,0 +1,46 @@ +using OpenNest.Engine.Fill; +using OpenNest.Geometry; + +namespace OpenNest.Engine.Tests.Fill; + +public class SortStripsTests +{ + private static Part MakeRectPart(double x, double y, double w, double h) + { + var pgm = new OpenNest.CNC.Program(); + pgm.Codes.Add(new OpenNest.CNC.RapidMove(new Vector(0, 0))); + pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(w, 0))); + pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(w, h))); + pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(0, h))); + pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(0, 0))); + var drawing = new Drawing("rect", pgm); + return new Part(drawing, new Vector(x, y)); + } + + [Fact] + public void SortColumnsByHeight_NonUniformGaps_DoesNotExceedOriginalSpan() + { + // Three columns with non-uniform gaps between them (5, then 1) and heights + // ordered so the sort-by-height pass must reorder them (tallest first, then + // shortest, then medium). The tallest column's original position leaves a + // 5-unit gap to its neighbor; that single sampled gap must not get replayed + // as the spacing for the whole staircase once it's no longer the leading pair. + var tall = MakeRectPart(0, 0, 10, 30); // Left 0-10, gap of 5 to next + var shortCol = MakeRectPart(15, 0, 5, 5); // Left 15-20, gap of 1 to next + var medium = MakeRectPart(21, 0, 20, 15); // Left 21-41 + + var originalRight = new[] { tall, shortCol, medium }.Max(p => p.BoundingBox.Right); + var originalLeft = new[] { tall, shortCol, medium }.Min(p => p.BoundingBox.Left); + var originalSpan = originalRight - originalLeft; + + var parts = new List { tall, shortCol, medium }; + IterativeShrinkFiller.SortColumnsByHeight(parts, spacing: 1.0); + + var newRight = parts.Max(p => p.BoundingBox.Right); + var newLeft = parts.Min(p => p.BoundingBox.Left); + var newSpan = newRight - newLeft; + + Assert.True(newSpan <= originalSpan + 1e-9, + $"Resequenced columns must not exceed the original footprint: original span {originalSpan}, new span {newSpan}"); + } +} diff --git a/OpenNest.Engine/Fill/IterativeShrinkFiller.cs b/OpenNest.Engine/Fill/IterativeShrinkFiller.cs index 790148c..2669804 100644 --- a/OpenNest.Engine/Fill/IterativeShrinkFiller.cs +++ b/OpenNest.Engine/Fill/IterativeShrinkFiller.cs @@ -219,8 +219,10 @@ namespace OpenNest.Engine.Fill if (strips.Count <= 1) return; - var gap = stripMin(strips[1]) - stripMax(strips[0]); - + // Use the required clearance as the inter-strip gap, not a gap sampled from one + // original pair: actual placement gaps vary for irregular/mixed-size geometry, and + // replaying a larger sampled gap across every reordered pair can push the trailing + // strip past the original (already plate-fitted) footprint. strips.Sort((a, b) => sortMetric(a).CompareTo(sortMetric(b))); var pos = primaryEdge(parts[0].BoundingBox); @@ -236,7 +238,7 @@ namespace OpenNest.Engine.Fill part.Offset(offset); } - pos = stripMax(s) + gap; + pos = stripMax(s) + spacing; } parts.Clear(); diff --git a/OpenNest.Engine/OpenNest.Engine.csproj b/OpenNest.Engine/OpenNest.Engine.csproj index d873f47..f9ac952 100644 --- a/OpenNest.Engine/OpenNest.Engine.csproj +++ b/OpenNest.Engine/OpenNest.Engine.csproj @@ -6,6 +6,7 @@ +