using OpenNest.CNC; using OpenNest.CNC.CuttingPlanning; using OpenNest.Diagnostics; using OpenNest.Engine.CuttingPlanning; using OpenNest.Geometry; using static OpenNest.Tests.CommonLines.CommonLinePlanningTests; namespace OpenNest.Tests.CommonLines; /// /// A Z-shaped common line (docs/common-line-cutting.md): two stepped bars, the second turned half /// a turn, interlock one kerf apart along three straight legs. The legs' spans chain at their /// corners into one shared path. The part cut first starts its perimeter on it, entered along its /// first leg from the scrap, and cuts the whole Z straight into the rest of its perimeter. /// public class CommonLineZTests { private const double K = CommonLinePlanningTests.Kerf; private const double Length = 12; private const double Step = 5; private const double Low = 0.5; private const double High = 1; // A bar Length long, Low tall for its first Step and High tall after it. private static Drawing SteppedBar() { var program = new Program(); program.Codes.Add(new RapidMove(0, 0)); program.Codes.Add(new LinearMove(0, Low)); program.Codes.Add(new LinearMove(Step, Low)); program.Codes.Add(new LinearMove(Step, High)); program.Codes.Add(new LinearMove(Length, High)); program.Codes.Add(new LinearMove(Length, 0)); program.Codes.Add(new LinearMove(0, 0)); return new Drawing("Terminal", program); } // The bar at (x, y), and its half-turned copy interlocked over its low end, offset from one // kerf apart by (dx, dy). private static (Plate Plate, Part A, Part B) Pair(double dx = 0, double dy = 0, bool join = true) { var plate = new Plate(48, 120) { PartSpacing = 0.25 }; var drawing = SteppedBar(); var a = new Part(drawing, new Vector(2, 2)); var b = new Part(drawing); b.Rotate(System.Math.PI); b.Offset(2 + Step - K + dx - b.Location.X, 2 + High + K + dy - b.Location.Y); plate.Parts.Add(a); plate.Parts.Add(b); if (join) plate.JoinCommonLine([a, b], K); return (plate, a, b); } [Fact] public void Spans_OfTheZ_ChainIntoOnePathThroughItsCorners() { var (plate, _, _) = Pair(); var check = CommonLineGeometry.Check(plate.CommonLines[0]); Assert.True(check.IsIntact); Assert.Equal(3, check.Spans.Count); var path = Assert.Single(CommonLineGeometry.Paths(check.Spans, K)); Assert.Equal(3, path.Spans.Count); Assert.Equal(4, path.Points.Count); // The corners lie where the legs' centerlines meet, half a kerf off both parts. var corners = path.Points.Skip(1).Take(2).OrderBy(p => p.X).ToArray(); Assert.Equal(2 - K / 2, corners[0].X, 9); Assert.Equal(2 + Low + K / 2, corners[0].Y, 9); Assert.Equal(2 + Step - K / 2, corners[1].X, 9); Assert.Equal(2 + Low + K / 2, corners[1].Y, 9); Assert.Equal(Step + 2 * (Low - K / 2), path.Length, 9); } [Fact] public void SeparateStraightSpans_StayPathsOfTheirOwn() { var plate = Row(3); var check = CommonLineGeometry.Check(plate.CommonLines[0]); var paths = CommonLineGeometry.Paths(check.Spans, K); Assert.Equal(2, paths.Count); Assert.All(paths, path => Assert.Equal(2, path.Points.Count)); } [Fact] public void Join_ClosesEveryLegOfTheZ() { var (plate, a, b) = Pair(dx: 0.06, dy: 0.1, join: false); var result = CommonLineJoiner.Join(plate, [a, b], K, 1); Assert.True(result.Succeeded, result.Failure); var check = CommonLineGeometry.Check(result.Group); Assert.True(check.IsIntact); Assert.Equal(3, check.Spans.Count); Assert.Single(CommonLineGeometry.Paths(check.Spans, K)); } [Fact] public void Plan_TheFirstPartStartsOnTheZAndRunsStraightIntoItsPerimeter() { var (plate, a, _) = Pair(); var result = Plan(plate); Assert.True(result.Status == CuttingPlanStatus.Ready, Describe(result)); Assert.True(result.IndependentlyReplayed); Assert.DoesNotContain(result.ProposedOrder.SelectMany(Motions), m => m.Layer == LayerType.CommonLine); var owner = result.ProposedOrder[0]; Assert.Same(a, owner.SourcePart); var motions = Motions(owner).ToList(); // One lead-in, up the Z's first leg from below, then its three legs in a row. var lead = Assert.Single(motions, m => m.Layer == LayerType.Leadin); Assert.Equal(2, lead.Start!.Value.X, 9); Assert.Equal(2, lead.End.X, 9); Assert.Equal(2, lead.End.Y, 9); var cuts = motions.Where(IsCut).ToList(); Assert.Equal(new Vector(2, 2 + Low), cuts[0].End); Assert.Equal(new Vector(2 + Step, 2 + Low), cuts[1].End); Assert.Equal(new Vector(2 + Step, 2 + High), cuts[2].End); Assert.Equal(0, cuts[^1].End.DistanceTo(lead.End), 9); Assert.DoesNotContain(motions, m => m.Layer == LayerType.Leadout); // The partner cuts only its remainder. var partner = Motions(result.ProposedOrder[1]).Where(IsCut).Sum(m => m.Length); Assert.True(partner < 2 * (Length + High) - (Step + 2 * Low) + 2 * K); } private static bool IsCut(ExecutionMotion m) => !m.Rapid && m.Layer is LayerType.Cut or LayerType.Display; [Fact] public void Members_CutTheirPerimeterExactlyOnce_TheZByTheFirstPart() { var (plate, _, _) = Pair(); var order = CommonLineCutting.Order(plate.CommonLines[0], CommonLineGeometry.Check(plate.CommonLines[0])); var result = Plan(plate); Assert.True(result.Status == CuttingPlanStatus.Ready, Describe(result)); foreach (var proposal in result.ProposedOrder) { var part = proposal.SourcePart; var cutBefore = order.PathsCutBefore(part).ToArray(); var marked = CommonLineCutting.MarkSharedSpans(part.Program, part.Location, cutBefore, K); // The partner's Z is one tab run: its legs join at its own corners. var layers = marked.Codes.OfType().Select(m => m.Layer).ToList(); Assert.Equal(cutBefore.Length, layers.Where((layer, i) => layer == LayerType.Tab && layers[(i + layers.Count - 1) % layers.Count] != LayerType.Tab).Count()); var material = LeadMaterialSnapshot.Capture(marked, part.Location, default, sharedSpans: true); Assert.True(material.IsComplete, material.Reason); var planned = ExecutionMotionReader.Read(proposal.CopyProgram(), part.Location, null, default); Assert.True(ContourProgramVerifier.Verify(planned, material, null, default)); } } [Fact] public void Applied_PassesPrePostReview() { var nest = new Nest("Z"); var (plate, _, _) = Pair(); plate.CuttingParameters = Parameters(); nest.Plates.Add(plate); var result = Plan(plate); Assert.True(result.Status == CuttingPlanStatus.Ready, Describe(result)); Assert.Equal(CuttingCommitStatus.Applied, CuttingPlanService.Apply([result]).Status); var findings = PostVerificationAnalyzer.Analyze(nest).Findings; Assert.DoesNotContain(findings, f => f.Kind is PostVerificationKind.CommonLine or PostVerificationKind.Incomplete or PostVerificationKind.RapidCrossing or PostVerificationKind.MissingLeadIn or PostVerificationKind.PartSpacing); } }