diff --git a/OpenNest.Engine/Fill/StripeFiller.cs b/OpenNest.Engine/Fill/StripeFiller.cs index eab8473..71012d4 100644 --- a/OpenNest.Engine/Fill/StripeFiller.cs +++ b/OpenNest.Engine/Fill/StripeFiller.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Threading; using OpenNest.Engine.Strategies; using OpenNest.Geometry; using OpenNest.Math; @@ -75,6 +76,57 @@ public class StripeFiller return bestAngle; } + /// + /// Iteratively finds the rotation angle where N copies of the pattern + /// span the given dimension with minimal waste. + /// Returns (angle, waste, pairCount). + /// + public static (double Angle, double Waste, int Count) ConvergeStripeAngle( + List patternParts, double sheetSpan, double spacing, + NestDirection axis, CancellationToken token = default) + { + var currentAngle = 0.0; + var bestWaste = double.MaxValue; + var bestAngle = 0.0; + var bestCount = 0; + var tolerance = sheetSpan * 0.001; + + for (var iteration = 0; iteration < MaxConvergenceIterations; iteration++) + { + token.ThrowIfCancellationRequested(); + + var rotated = FillHelpers.BuildRotatedPattern(patternParts, currentAngle); + var pairSpan = GetDimension(rotated.BoundingBox, axis); + + if (pairSpan + spacing <= 0) + break; + + var n = (int)System.Math.Floor((sheetSpan + spacing) / (pairSpan + spacing)); + if (n <= 0) + break; + + var usedSpan = n * (pairSpan + spacing) - spacing; + var remaining = sheetSpan - usedSpan; + + if (remaining < bestWaste) + { + bestWaste = remaining; + bestAngle = currentAngle; + bestCount = n; + } + + if (remaining <= tolerance) + break; + + var delta = remaining / n; + var targetSpan = pairSpan + delta; + + currentAngle = FindAngleForTargetSpan(patternParts, targetSpan, axis); + } + + return (bestAngle, bestWaste, bestCount); + } + private static double BisectForTarget( List patternParts, double lo, double hi, double targetSpan, NestDirection axis) diff --git a/OpenNest.Tests/Strategies/StripeFillerTests.cs b/OpenNest.Tests/Strategies/StripeFillerTests.cs index 425285d..02a669a 100644 --- a/OpenNest.Tests/Strategies/StripeFillerTests.cs +++ b/OpenNest.Tests/Strategies/StripeFillerTests.cs @@ -60,4 +60,36 @@ public class StripeFillerTests Assert.True(angle >= 0 && angle <= System.Math.PI / 2); } + + [Fact] + public void ConvergeStripeAngle_ReducesWaste() + { + var pattern = MakeRectPattern(20, 10); + var (angle, waste, count) = StripeFiller.ConvergeStripeAngle( + pattern.Parts, 120.0, 0.5, NestDirection.Horizontal); + + Assert.True(count >= 5, $"Expected at least 5 pairs, got {count}"); + Assert.True(waste < 18.0, $"Expected waste < 18, got {waste:F2}"); + } + + [Fact] + public void ConvergeStripeAngle_HandlesExactFit() + { + var pattern = MakeRectPattern(10, 5); + var (angle, waste, count) = StripeFiller.ConvergeStripeAngle( + pattern.Parts, 100.0, 0.0, NestDirection.Horizontal); + + Assert.Equal(10, count); + Assert.True(waste < 0.2, $"Expected near-zero waste, got {waste:F2}"); + } + + [Fact] + public void ConvergeStripeAngle_Vertical() + { + var pattern = MakeRectPattern(10, 20); + var (angle, waste, count) = StripeFiller.ConvergeStripeAngle( + pattern.Parts, 120.0, 0.5, NestDirection.Vertical); + + Assert.True(count >= 5, $"Expected at least 5 pairs, got {count}"); + } }