using System.Text; using OpenNest.CNC; using OpenNest.CNC.CuttingPlanning; using OpenNest.Engine.CuttingPlanning; using OpenNest.Posts.Cincinnati; using OpenNest.Posts.CincinnatiCIFiber; using OpenNest.Posts.GravographIS; namespace OpenNest.Tests.CommonLines; /// /// Posting common lines cut once (docs/common-line-cutting.md): the part cut first is a normal /// compensated perimeter that starts on its common line; the CI Fiber post writes each remainder /// in the contour form, compensated on its perimeter's side; posts that cannot write remainders /// refuse the nest. /// public class CommonLinePostTests { private static Nest PlannedPair() { var nest = new Nest("N26-295") { Thickness = 0.1875, Material = new Material("Mild Steel") }; var plate = CommonLinePlanningTests.Row(2); nest.Plates.Add(plate); var result = CommonLinePlanningTests.Plan(plate); Assert.True(result.Status == CuttingPlanStatus.Ready, CommonLinePlanningTests.Describe(result)); Assert.Equal(CuttingCommitStatus.Applied, CuttingPlanService.Apply([result]).Status); return nest; } private static string Body(string program) { var lines = program.Split("\r\n"); var start = Array.IndexOf(lines, "N0:") + 1; var end = Array.LastIndexOf(lines, "/L \"L0\""); return string.Join("\n", lines[start..end]); } [Fact] public void CIFiber_WritesTheCommonLineAsThePerimetersStart_AndTheRemainderOnItsPerimetersSide() { var nest = PlannedPair(); var post = new CIFiberPostProcessor(new CIFiberPostConfig { ConfigurationName = "CI FIBER 4020" }); using var stream = new MemoryStream(); post.Post(nest, stream); // Part 1 enters below the notch straight up its shared edge (x 8, compensated G42 onto the // centerline), cuts the common line and carries on round its perimeter, closing where it // began. Part 2's remainder is pierced in that kerf and compensated G42 as its // counterclockwise perimeter would be. const string golden = """ ( Sheet number - 1 ) ( Part #1 ) ( PART:PT42 ) V.E.R4=1 N1: /L "L0" V.E.R3=1 G0X8Y2.13 /L "L4" G42 G1X8Y2.38 /L "L6" G1X8Y14.06 G1X7.62Y14.44 G1X2.38Y14.44 G1X2Y14.06 G1X2Y2.38 G1X2.38Y2 G1X7.62Y2 G1X8Y2.38 /L "ZHSOFF" ( PART END ) ( Part #2 ) ( PART:PT42 ) V.E.R4=2 N2: /L "L0" V.E.R3=2 G0X8.006Y2.63 /L "L4" G42 G1X8.012Y2.38 /L "L6" G1X8.392Y2 G1X13.632Y2 G1X14.012Y2.38 G1X14.012Y14.06 G1X13.632Y14.44 G1X8.392Y14.44 G1X8.012Y14.06 /L "ZHSOFF" ( PART END ) """; Assert.Equal(golden.ReplaceLineEndings("\n"), Body(Encoding.UTF8.GetString(stream.ToArray()))); } [Fact] public void CIFiber_CompensatesAClockwisePerimetersRemaindersG41() { // Imported drawings are wound clockwise: the same pair drawn that way. var drawing = CommonLinePlanningTests.Pt42(); var reversed = new Program(); var points = drawing.Program.Codes.OfType().Select(m => m.EndPoint).Reverse().ToArray(); reversed.Codes.Add(new RapidMove(points[0])); foreach (var point in points.Skip(1)) reversed.Codes.Add(new LinearMove(point)); var nest = new Nest("N26-295") { Thickness = 0.1875, Material = new Material("Mild Steel") }; var plate = CommonLinePlanningTests.Row(2, drawing: new Drawing("PT42", reversed)); nest.Plates.Add(plate); var result = CommonLinePlanningTests.Plan(plate); Assert.True(result.Status == CuttingPlanStatus.Ready, CommonLinePlanningTests.Describe(result)); Assert.Equal(CuttingCommitStatus.Applied, CuttingPlanService.Apply([result]).Status); using var stream = new MemoryStream(); new CIFiberPostProcessor(new CIFiberPostConfig()).Post(nest, stream); var lines = Body(Encoding.UTF8.GetString(stream.ToArray())).Split("\n"); Assert.Equal(2, lines.Count(line => line == "G41")); Assert.DoesNotContain("G42", lines); } [Fact] public void PostsThatCannotWriteSinglePassCommonLines_RefuseTheNest() { var nest = PlannedPair(); using var stream = new MemoryStream(); var cincinnati = Assert.Throws(() => new CincinnatiPostProcessor().Post(nest, stream)); var gravograph = Assert.Throws(() => new GravographISPostProcessor().Post(nest, stream)); Assert.Contains("cannot write single-pass common lines", cincinnati.Message); Assert.Contains("Cut Common Lines Twice", gravograph.Message); Assert.Equal(0, stream.Length); } [Fact] public void Host_RefusesAPostWithoutSupport_AndPassesOneWithIt() { var nest = PlannedPair(); var refused = Assert.Throws(() => CommonLineCuts.EnsurePostable(nest, new NoCommonLinePost())); Assert.Contains("Plate 1, part 2 cuts a common line once", refused.Message); CommonLineCuts.EnsurePostable(nest, new CIFiberPostProcessor()); // Cut twice, nothing is single-pass, so any post may write it. nest.Plates[0].CutCommonLinesTwice = true; var again = CommonLinePlanningTests.Plan(nest.Plates[0]); Assert.Equal(CuttingCommitStatus.Applied, CuttingPlanService.Apply([again]).Status); CommonLineCuts.EnsurePostable(nest, new NoCommonLinePost()); } private sealed class NoCommonLinePost : IPostProcessor { public string Name => "Plain"; public string Author => "Tests"; public string Description => "A post that knows nothing of common lines."; public void Post(Nest nest, Stream outputStream) => throw new NotSupportedException(); public void Post(Nest nest, string outputFile) => throw new NotSupportedException(); } }