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}");
+ }
}