diff --git a/OpenNest.Tests/CincinnatiCIFiber/CIFiberPostProcessorTests.cs b/OpenNest.Tests/CincinnatiCIFiber/CIFiberPostProcessorTests.cs index 5ec93e8..d4a1138 100644 --- a/OpenNest.Tests/CincinnatiCIFiber/CIFiberPostProcessorTests.cs +++ b/OpenNest.Tests/CincinnatiCIFiber/CIFiberPostProcessorTests.cs @@ -222,6 +222,35 @@ public class CIFiberPostProcessorTests Assert.Contains("13.2.4.1", ex.Message); } + [Fact] + public void Post_CutOff_PostsLastWithoutCompensationOrLeadIn() + { + var nest = MakeSquareWithHoleNest(); + var plate = nest.Plates[0]; + + // Vertical cut-off at X=20: a bare rapid + line, no lead-in. + var cutOff = new CutOff(new Vector(20, 0), CutOffAxis.Vertical); + cutOff.Regenerate(plate, new CutOffSettings()); + plate.Parts.Insert(0, new Part(cutOff.Drawing, Vector.Zero)); + + var lines = Lines(Post(nest)); + + // Cut-off follows the real part despite being first in plate order. + var partEnd = lines.IndexOf("( PART END )"); + var cutOffPart = lines.IndexOf("( Part #2 )"); + Assert.True(partEnd >= 0 && cutOffPart > partEnd); + + var n3 = lines.IndexOf("N3:"); + Assert.True(n3 > cutOffPart); + Assert.Equal("/L \"L0\"", lines[n3 + 1]); + Assert.Equal("V.E.R3=3", lines[n3 + 2]); + Assert.StartsWith("G0X20Y", lines[n3 + 3]); + Assert.Equal("/L \"L4\"", lines[n3 + 4]); + Assert.Equal("/L \"L6\"", lines[n3 + 5]); + Assert.StartsWith("G1X20Y", lines[n3 + 6]); + Assert.Equal("/L \"ZHSOFF\"", lines[n3 + 7]); + } + [Fact] public void Post_ValidatesTableSize() { diff --git a/Posts/OpenNest.Posts.CincinnatiCIFiber/CIFiberProgramWriter.cs b/Posts/OpenNest.Posts.CincinnatiCIFiber/CIFiberProgramWriter.cs index 42c974d..8afb783 100644 --- a/Posts/OpenNest.Posts.CincinnatiCIFiber/CIFiberProgramWriter.cs +++ b/Posts/OpenNest.Posts.CincinnatiCIFiber/CIFiberProgramWriter.cs @@ -135,8 +135,14 @@ namespace OpenNest.Posts.CincinnatiCIFiber private int WriteSheet(Plate plate, TextWriter w, int contourNumber) { + // Cut-offs run last: severing the sheet first would free the + // skeleton before the parts are cut (matches the CL post). + var ordered = plate + .Parts.Where(p => !p.BaseDrawing.IsCutOff) + .Concat(plate.Parts.Where(p => p.BaseDrawing.IsCutOff)); + var partNumber = 0; - foreach (var part in plate.Parts) + foreach (var part in ordered) { partNumber++; contourNumber = WritePart(part, partNumber, w, contourNumber); @@ -158,10 +164,14 @@ namespace OpenNest.Posts.CincinnatiCIFiber if (_config.SkipScribe) contours = contours.Where(c => !IsScribeContour(c)).ToList(); + var isCutOff = part.BaseDrawing.IsCutOff; foreach (var contour in contours) { contourNumber++; - WriteContour(contour, contourNumber, w); + if (isCutOff) + WriteCutOffContour(contour, contourNumber, w); + else + WriteContour(contour, contourNumber, w); } CIFiberFormatter.Line(w, "( PART END )"); @@ -227,6 +237,38 @@ namespace OpenNest.Posts.CincinnatiCIFiber CIFiberFormatter.Line(w, SkippableLine(_config.LayerCutEnd)); } + /// + /// Cut-offs are open straight lines with no lead-in: the line is the + /// beam centreline (CutOffSettings clearance already allows for kerf) + /// and an open line has no inside/outside, so no G41/G42 is selected + /// and the ยง13.2.4.1 linear-lead-in rule does not apply. The exterior + /// lead layer still runs so the pierce sequence matches a perimeter. + /// + private void WriteCutOffContour(CIFiberContour contour, int contourNumber, TextWriter w) + { + CIFiberFormatter.Line(w, $"N{contourNumber}:"); + CIFiberFormatter.Line(w, SkippableLine(_config.LayerCancel)); + CIFiberFormatter.Line(w, $"V.E.R3={contourNumber}"); + CIFiberFormatter.Line(w, $"G0X{Fmt(contour.Pierce.X)}Y{Fmt(contour.Pierce.Y)}"); + CIFiberFormatter.Line(w, SkippableLine(_config.LayerExteriorLeadin)); + CIFiberFormatter.Line(w, SkippableLine(_config.LayerCut)); + + var prev = contour.Pierce; + if (contour.LeadIn != null) + { + CIFiberFormatter.Line(w, FormatMotion(contour.LeadIn, prev)); + prev = contour.LeadIn.EndPoint; + } + + foreach (var motion in contour.LeadInExtra.Concat(contour.Cuts)) + { + CIFiberFormatter.Line(w, FormatMotion(motion, prev)); + prev = motion.EndPoint; + } + + CIFiberFormatter.Line(w, SkippableLine(_config.LayerCutEnd)); + } + private string ResolvePartComment(Part part) { if (!string.IsNullOrWhiteSpace(_config.PartComment)) diff --git a/docs/cincinnati-ci-fiber-post-output.md b/docs/cincinnati-ci-fiber-post-output.md index 968fdbd..be5756a 100644 --- a/docs/cincinnati-ci-fiber-post-output.md +++ b/docs/cincinnati-ci-fiber-post-output.md @@ -26,6 +26,12 @@ uses the Cincinnati machine-sample convention, not a generic TF5200 laser API: - Tail: `/L "L0"`, `L PROGRAMEND.NC`, `M50`, `M30`, and `%`. - Motion endpoints are sheet-absolute XY. Arc `I`/`J` are offsets from the arc start, matching the G162 convention; the post does not explicitly emit G162. +- Sheet cut-offs post after every part on their sheet. Each segment is an open + line with no lead-in: rapid to its start, `/L "L4"`, `/L "L6"`, the `G1`, + and `/L "ZHSOFF"`. No `G41`/`G42` is selected, because the line is the beam + centreline and has no inside or outside. Whether the `L4` macro runs + correctly without a following lead-in move has not been confirmed on the + machine. - Hole subprogram geometry is inlined. Suppressed moves and, by default, wholly scribe contours are omitted. - Files use UTF-8 without a BOM and CRLF lines. Default accuracy is three decimal @@ -58,6 +64,7 @@ The post is named for the machine family; table size belongs in configuration. [Output-contract tests](../OpenNest.Tests/CincinnatiCIFiber/CIFiberPostProcessorTests.cs) cover a square with a hole, coordinate transforms/formatting, lead-in rejection, +cut-off ordering and output, table bounds, and suppression/scribe handling. Run: ```sh