diff --git a/OpenNest.Core/CutOff.cs b/OpenNest.Core/CutOff.cs
index b7d375a..e967fbb 100644
--- a/OpenNest.Core/CutOff.cs
+++ b/OpenNest.Core/CutOff.cs
@@ -39,6 +39,17 @@ namespace OpenNest
Drawing.Program = program;
}
+ /// Measures the same trimmed, filtered segments as regeneration without building a program.
+ internal (double Length, int SegmentCount) MeasureSegments(
+ Plate plate,
+ CutOffSettings settings,
+ Dictionary cache = null
+ )
+ {
+ var segments = ComputeSegments(plate, settings, cache);
+ return (segments.Sum(s => s.End - s.Start), segments.Count);
+ }
+
private string GetName()
{
var axisChar = Axis == CutOffAxis.Vertical ? "V" : "H";
diff --git a/OpenNest.Tests/CutOffs/CutOffMeasurementTests.cs b/OpenNest.Tests/CutOffs/CutOffMeasurementTests.cs
new file mode 100644
index 0000000..3b9db02
--- /dev/null
+++ b/OpenNest.Tests/CutOffs/CutOffMeasurementTests.cs
@@ -0,0 +1,65 @@
+using System.Reflection;
+using OpenNest.CNC;
+using OpenNest.Geometry;
+
+namespace OpenNest.Tests.CutOffs;
+
+public class CutOffMeasurementTests
+{
+ [Theory]
+ [InlineData(1, false, false)]
+ [InlineData(2, false, false)]
+ [InlineData(3, false, false)]
+ [InlineData(4, false, false)]
+ [InlineData(1, true, false)]
+ [InlineData(2, true, false)]
+ [InlineData(3, true, false)]
+ [InlineData(4, true, false)]
+ [InlineData(1, false, true)]
+ [InlineData(2, false, true)]
+ [InlineData(3, false, true)]
+ [InlineData(4, false, true)]
+ public void Measurement_MatchesRegeneratedSegmentsWithoutMutatingDrawing(int quadrant, bool limited, bool excluded)
+ {
+ var plate = new Plate(60, 120) { Quadrant = quadrant };
+ var negativeX = quadrant is 2 or 3;
+ var negativeY = quadrant is 3 or 4;
+ var program = new Program();
+ program.Codes.Add(new RapidMove(0, 0));
+ program.Codes.Add(new LinearMove(20, 0));
+ program.Codes.Add(new LinearMove(20, 20));
+ program.Codes.Add(new LinearMove(0, 20));
+ program.Codes.Add(new LinearMove(0, 0));
+ plate.Parts.Add(new Part(new Drawing("measurement rectangle", program),
+ new Vector(negativeX ? -40 : 20, negativeY ? -40 : 20)));
+ var bounds = plate.BoundingBox(false);
+ var cut = new CutOff(new Vector(negativeX ? -30 : 30, 0), CutOffAxis.Vertical);
+ var settings = new CutOffSettings
+ {
+ PartClearance = excluded ? 100 : 0.5,
+ Overtravel = 2,
+ MinSegmentLength = limited ? 21 : 0,
+ CutDirection = quadrant % 2 == 0 ? CutDirection.TowardOrigin : CutDirection.AwayFromOrigin,
+ };
+ if (limited)
+ {
+ cut.StartLimit = bounds.Bottom + 5;
+ cut.EndLimit = bounds.Top - 5;
+ }
+ var cache = Plate.BuildPerimeterCache(plate);
+ var original = cut.Drawing.Program;
+ // Reflection keeps the first run behavioral-red on the unchanged production tree.
+ var method = typeof(CutOff).GetMethod("MeasureSegments", BindingFlags.Instance | BindingFlags.NonPublic);
+ Assert.NotNull(method);
+ var measured = Assert.IsType>(method.Invoke(cut, new object[] { plate, settings, cache }));
+ Assert.Same(original, cut.Drawing.Program);
+ cut.Regenerate(plate, settings, cache);
+ var length = Enumerable.Range(0, cut.Drawing.Program.Codes.Count / 2).Sum(i =>
+ System.Math.Abs(Assert.IsType(cut.Drawing.Program.Codes[2 * i + 1]).EndPoint.Y -
+ Assert.IsType(cut.Drawing.Program.Codes[2 * i]).EndPoint.Y));
+ Assert.Equal(length, measured.Item1, 10);
+ Assert.Equal(cut.Drawing.Program.Codes.Count / 2, measured.Item2);
+ if (excluded)
+ Assert.Equal((0.0, 0), measured);
+ }
+}