From cc2b3d1b814b0bcf42154b2147c822c215f01cff Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Tue, 20 Oct 2026 07:36:01 -0400 Subject: [PATCH] test(posts): post the CI Fiber sample's own lead-ins and match it line for line The sample regression fixture now carries each part's pierce, lead-in and cut moves from the Cincinnati machine program as its saved cutting program. A new test posts those saved programs without reassigning lead-ins and requires the program body to equal the sample exactly: L2/L4 macro, G41/G42 side, lead-ins and every cut move (21,911 lines). Mutation checks: dropping the largest-contour perimeter rule from the kerf fix fails it at the first perimeter (L4 posted as L2), and forcing G41 fails it at the first perimeter's G42. The existing tests still replace the saved programs with OpenNest's own lead-ins first; their comment now says so instead of describing the sample's leads wrongly. --- .../CIFiberSampleRegressionTests.cs | 57 +++++++++++++++++-- docs/cincinnati-ci-fiber-post-output.md | 8 ++- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/OpenNest.Tests/CincinnatiCIFiber/CIFiberSampleRegressionTests.cs b/OpenNest.Tests/CincinnatiCIFiber/CIFiberSampleRegressionTests.cs index 66ac9b6a..069a4f61 100644 --- a/OpenNest.Tests/CincinnatiCIFiber/CIFiberSampleRegressionTests.cs +++ b/OpenNest.Tests/CincinnatiCIFiber/CIFiberSampleRegressionTests.cs @@ -20,6 +20,11 @@ namespace OpenNest.Tests.CincinnatiCIFiber; /// 12992-4SS nest (same part + layout as the sample NC) and compares contour /// counts, macro counts and perimeter geometry with 12992-4SS_NEST.nc. /// +/// Each placed part in the fixture carries the sample's own pierce, lead-in +/// and cut moves as its saved cutting program (not locked). Posting those +/// saved programs must reproduce the sample move for move; the other tests +/// replace them with OpenNest's own lead-ins first. +/// /// The fixture lives outside the repository (/srv/shared). Configure it in /// OpenNest.Tests/test-config.json: /// { "CIFiberSampleNcPath": "/srv/shared/.../12992-4SS_NEST.nc", @@ -48,10 +53,11 @@ public class CIFiberSampleRegressionTests var plate = nest.Plates[0]; plate.CuttingParameters = new CuttingParameters { - // The sample cuts clean holes with a short perpendicular linear - // lead (pierce ~0.079 outside the contour), including the circles - // (ArcCircleLeadIn). TF5200 13.2.4.1 requires a LINEAR first - // motion block after G41/G42, so holes must have linear leads. + // OpenNest's own short perpendicular linear leads, including the + // circles (ArcCircleLeadIn). TF5200 13.2.4.1 requires a LINEAR + // first motion block after G41/G42, so holes must have linear + // leads. (The sample itself pierces holes at their centre and + // uses 0.125 in perimeter leads; see the saved-lead-in test.) InternalLeadIn = new LineLeadIn { Length = 0.075, ApproachAngle = 90 }, ExternalLeadIn = new LineLeadIn { Length = 0.075, ApproachAngle = 90 }, ArcCircleLeadIn = new LineLeadIn { Length = 0.075, ApproachAngle = 90 }, @@ -96,6 +102,49 @@ public class CIFiberSampleRegressionTests return result; } + /// + /// The program body from the restart label through the last part: every + /// line except the per-part source-path comment, which names the + /// original CAM part file. + /// + private static List ProgramBody(string nc) + { + var lines = nc.Replace("\r\n", "\n").Split('\n').ToList(); + var start = lines.IndexOf("N0:"); + var end = lines.LastIndexOf("( PART END )"); + Assert.True(start >= 0 && end > start, "No N0: ... ( PART END ) body found"); + return lines + .Skip(start) + .Take(end - start + 1) + .Where(line => !line.StartsWith("( PART:", StringComparison.Ordinal)) + .ToList(); + } + + [SkippableFact] + public void Post_ReconstructedNest_SavedSampleLeadIns_ReproduceSampleProgram() + { + var fixture = ResolveFixture(); + Skip.If(fixture == null, "CI Fiber fixtures not configured in test-config.json"); + + // Post the saved programs as they are: no lead-in assignment. + var reader = new NestReader(fixture.Value.Nest); + var nest = reader.Read(); + Assert.Empty(reader.Warnings); + Assert.All(nest.Plates.SelectMany(plate => plate.Parts), part => Assert.True(part.HasManualLeadIns)); + + var expected = ProgramBody(File.ReadAllText(fixture.Value.Nc)); + var actual = ProgramBody(PostNest(nest)); + + // Pierce points, L2/L4 macro, G41/G42 side, lead-ins and every cut + // move line up with the machine program exactly. + Assert.Equal(expected.Count, actual.Count); + for (var i = 0; i < expected.Count; i++) + Assert.True( + expected[i] == actual[i], + $"Line {i} after N0: differs: sample '{expected[i]}', posted '{actual[i]}'" + ); + } + [SkippableFact] public void Post_ReconstructedNest_IsIdenticalAfterSavedLeadInsReload() { diff --git a/docs/cincinnati-ci-fiber-post-output.md b/docs/cincinnati-ci-fiber-post-output.md index aff3bb86..73eb4f1b 100644 --- a/docs/cincinnati-ci-fiber-post-output.md +++ b/docs/cincinnati-ci-fiber-post-output.md @@ -100,7 +100,13 @@ validated and saved only when OK is pressed. cover a square with a hole, kerf side for either winding, coordinate transforms/formatting, lead-in rejection, cut-off sequencing and output, table bounds, suppression/scribe handling, and -multi-sheet output in both modes. Run: +multi-sheet output in both modes. +[Sample regression tests](../OpenNest.Tests/CincinnatiCIFiber/CIFiberSampleRegressionTests.cs) +post a nest rebuilt from a Cincinnati machine sample, whose parts carry the +sample's own pierce, lead-in and cut moves. Posted unchanged, the program body +must match the sample line for line (macro, G41/G42 side, lead-ins and cuts). +The fixture stays outside the repository and the tests skip when +`test-config.json` does not point at it. Run: ```sh dotnet test OpenNest.Tests/OpenNest.Tests.csproj --filter FullyQualifiedName~CincinnatiCIFiber