feat: add StripeFiller.ConvergeStripeAngle iterative convergence
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iteratively finds the rotation angle where N copies of the pattern
|
||||
/// span the given dimension with minimal waste.
|
||||
/// Returns (angle, waste, pairCount).
|
||||
/// </summary>
|
||||
public static (double Angle, double Waste, int Count) ConvergeStripeAngle(
|
||||
List<Part> 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<Part> patternParts, double lo, double hi,
|
||||
double targetSpan, NestDirection axis)
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user