From f17db1d2f9596655a7a550613849ff4b8266e28a Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 12 Mar 2026 23:07:31 -0400 Subject: [PATCH 1/5] feat: add LeadIn hierarchy (7 classes) Co-Authored-By: Claude Sonnet 4.6 --- .../CNC/CuttingStrategy/LeadIns/ArcLeadIn.cs | 36 ++++++++++++++ .../LeadIns/CleanHoleLeadIn.cs | 49 +++++++++++++++++++ .../CNC/CuttingStrategy/LeadIns/LeadIn.cs | 13 +++++ .../CuttingStrategy/LeadIns/LineArcLeadIn.cs | 49 +++++++++++++++++++ .../CNC/CuttingStrategy/LeadIns/LineLeadIn.cs | 32 ++++++++++++ .../CuttingStrategy/LeadIns/LineLineLeadIn.cs | 44 +++++++++++++++++ .../CNC/CuttingStrategy/LeadIns/NoLeadIn.cs | 22 +++++++++ 7 files changed, 245 insertions(+) create mode 100644 OpenNest.Core/CNC/CuttingStrategy/LeadIns/ArcLeadIn.cs create mode 100644 OpenNest.Core/CNC/CuttingStrategy/LeadIns/CleanHoleLeadIn.cs create mode 100644 OpenNest.Core/CNC/CuttingStrategy/LeadIns/LeadIn.cs create mode 100644 OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineArcLeadIn.cs create mode 100644 OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLeadIn.cs create mode 100644 OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLineLeadIn.cs create mode 100644 OpenNest.Core/CNC/CuttingStrategy/LeadIns/NoLeadIn.cs diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/ArcLeadIn.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/ArcLeadIn.cs new file mode 100644 index 0000000..e76c9ce --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/ArcLeadIn.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using OpenNest.Geometry; + +namespace OpenNest.CNC.CuttingStrategy +{ + public class ArcLeadIn : LeadIn + { + public double Radius { get; set; } + + public override List Generate(Vector contourStartPoint, double contourNormalAngle, + RotationType winding = RotationType.CW) + { + var piercePoint = GetPiercePoint(contourStartPoint, contourNormalAngle); + + var arcCenter = new Vector( + contourStartPoint.X + Radius * System.Math.Cos(contourNormalAngle), + contourStartPoint.Y + Radius * System.Math.Sin(contourNormalAngle)); + + return new List + { + new RapidMove(piercePoint), + new ArcMove(contourStartPoint, arcCenter, winding) + }; + } + + public override Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle) + { + var arcCenterX = contourStartPoint.X + Radius * System.Math.Cos(contourNormalAngle); + var arcCenterY = contourStartPoint.Y + Radius * System.Math.Sin(contourNormalAngle); + + return new Vector( + arcCenterX + Radius * System.Math.Cos(contourNormalAngle), + arcCenterY + Radius * System.Math.Sin(contourNormalAngle)); + } + } +} diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/CleanHoleLeadIn.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/CleanHoleLeadIn.cs new file mode 100644 index 0000000..d30f0e6 --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/CleanHoleLeadIn.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using OpenNest.Geometry; +using OpenNest.Math; + +namespace OpenNest.CNC.CuttingStrategy +{ + public class CleanHoleLeadIn : LeadIn + { + public double LineLength { get; set; } + public double ArcRadius { get; set; } + public double Kerf { get; set; } + + public override List Generate(Vector contourStartPoint, double contourNormalAngle, + RotationType winding = RotationType.CW) + { + var piercePoint = GetPiercePoint(contourStartPoint, contourNormalAngle); + + var arcCenterX = contourStartPoint.X + ArcRadius * System.Math.Cos(contourNormalAngle); + var arcCenterY = contourStartPoint.Y + ArcRadius * System.Math.Sin(contourNormalAngle); + var arcCenter = new Vector(arcCenterX, arcCenterY); + + var lineAngle = contourNormalAngle + Angle.ToRadians(135.0); + var arcStart = new Vector( + arcCenterX + ArcRadius * System.Math.Cos(lineAngle), + arcCenterY + ArcRadius * System.Math.Sin(lineAngle)); + + return new List + { + new RapidMove(piercePoint), + new LinearMove(arcStart), + new ArcMove(contourStartPoint, arcCenter, winding) + }; + } + + public override Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle) + { + var arcCenterX = contourStartPoint.X + ArcRadius * System.Math.Cos(contourNormalAngle); + var arcCenterY = contourStartPoint.Y + ArcRadius * System.Math.Sin(contourNormalAngle); + + var lineAngle = contourNormalAngle + Angle.ToRadians(135.0); + var arcStartX = arcCenterX + ArcRadius * System.Math.Cos(lineAngle); + var arcStartY = arcCenterY + ArcRadius * System.Math.Sin(lineAngle); + + return new Vector( + arcStartX + LineLength * System.Math.Cos(lineAngle), + arcStartY + LineLength * System.Math.Sin(lineAngle)); + } + } +} diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LeadIn.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LeadIn.cs new file mode 100644 index 0000000..83be504 --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LeadIn.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using OpenNest.Geometry; + +namespace OpenNest.CNC.CuttingStrategy +{ + public abstract class LeadIn + { + public abstract List Generate(Vector contourStartPoint, double contourNormalAngle, + RotationType winding = RotationType.CW); + + public abstract Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle); + } +} diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineArcLeadIn.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineArcLeadIn.cs new file mode 100644 index 0000000..a816593 --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineArcLeadIn.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using OpenNest.Geometry; +using OpenNest.Math; + +namespace OpenNest.CNC.CuttingStrategy +{ + public class LineArcLeadIn : LeadIn + { + public double LineLength { get; set; } + public double ApproachAngle { get; set; } = 135.0; + public double ArcRadius { get; set; } + + public override List Generate(Vector contourStartPoint, double contourNormalAngle, + RotationType winding = RotationType.CW) + { + var piercePoint = GetPiercePoint(contourStartPoint, contourNormalAngle); + + var arcCenterX = contourStartPoint.X + ArcRadius * System.Math.Cos(contourNormalAngle); + var arcCenterY = contourStartPoint.Y + ArcRadius * System.Math.Sin(contourNormalAngle); + var arcCenter = new Vector(arcCenterX, arcCenterY); + + var lineAngle = contourNormalAngle + Angle.ToRadians(ApproachAngle); + var arcStart = new Vector( + arcCenterX + ArcRadius * System.Math.Cos(lineAngle), + arcCenterY + ArcRadius * System.Math.Sin(lineAngle)); + + return new List + { + new RapidMove(piercePoint), + new LinearMove(arcStart), + new ArcMove(contourStartPoint, arcCenter, winding) + }; + } + + public override Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle) + { + var arcCenterX = contourStartPoint.X + ArcRadius * System.Math.Cos(contourNormalAngle); + var arcCenterY = contourStartPoint.Y + ArcRadius * System.Math.Sin(contourNormalAngle); + + var lineAngle = contourNormalAngle + Angle.ToRadians(ApproachAngle); + var arcStartX = arcCenterX + ArcRadius * System.Math.Cos(lineAngle); + var arcStartY = arcCenterY + ArcRadius * System.Math.Sin(lineAngle); + + return new Vector( + arcStartX + LineLength * System.Math.Cos(lineAngle), + arcStartY + LineLength * System.Math.Sin(lineAngle)); + } + } +} diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLeadIn.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLeadIn.cs new file mode 100644 index 0000000..e7922cf --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLeadIn.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using OpenNest.Geometry; +using OpenNest.Math; + +namespace OpenNest.CNC.CuttingStrategy +{ + public class LineLeadIn : LeadIn + { + public double Length { get; set; } + public double ApproachAngle { get; set; } = 90.0; + + public override List Generate(Vector contourStartPoint, double contourNormalAngle, + RotationType winding = RotationType.CW) + { + var piercePoint = GetPiercePoint(contourStartPoint, contourNormalAngle); + + return new List + { + new RapidMove(piercePoint), + new LinearMove(contourStartPoint) + }; + } + + public override Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle) + { + var approachAngle = contourNormalAngle + Angle.ToRadians(ApproachAngle); + return new Vector( + contourStartPoint.X + Length * System.Math.Cos(approachAngle), + contourStartPoint.Y + Length * System.Math.Sin(approachAngle)); + } + } +} diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLineLeadIn.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLineLeadIn.cs new file mode 100644 index 0000000..f5700cb --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLineLeadIn.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using OpenNest.Geometry; +using OpenNest.Math; + +namespace OpenNest.CNC.CuttingStrategy +{ + public class LineLineLeadIn : LeadIn + { + public double Length1 { get; set; } + public double ApproachAngle1 { get; set; } = 90.0; + public double Length2 { get; set; } + public double ApproachAngle2 { get; set; } = 90.0; + + public override List Generate(Vector contourStartPoint, double contourNormalAngle, + RotationType winding = RotationType.CW) + { + var piercePoint = GetPiercePoint(contourStartPoint, contourNormalAngle); + + var secondAngle = contourNormalAngle + Angle.ToRadians(ApproachAngle1); + var midPoint = new Vector( + contourStartPoint.X + Length2 * System.Math.Cos(secondAngle), + contourStartPoint.Y + Length2 * System.Math.Sin(secondAngle)); + + return new List + { + new RapidMove(piercePoint), + new LinearMove(midPoint), + new LinearMove(contourStartPoint) + }; + } + + public override Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle) + { + var secondAngle = contourNormalAngle + Angle.ToRadians(ApproachAngle1); + var midX = contourStartPoint.X + Length2 * System.Math.Cos(secondAngle); + var midY = contourStartPoint.Y + Length2 * System.Math.Sin(secondAngle); + + var firstAngle = secondAngle + Angle.ToRadians(ApproachAngle2); + return new Vector( + midX + Length1 * System.Math.Cos(firstAngle), + midY + Length1 * System.Math.Sin(firstAngle)); + } + } +} diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/NoLeadIn.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/NoLeadIn.cs new file mode 100644 index 0000000..7100487 --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/NoLeadIn.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using OpenNest.Geometry; + +namespace OpenNest.CNC.CuttingStrategy +{ + public class NoLeadIn : LeadIn + { + public override List Generate(Vector contourStartPoint, double contourNormalAngle, + RotationType winding = RotationType.CW) + { + return new List + { + new RapidMove(contourStartPoint) + }; + } + + public override Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle) + { + return contourStartPoint; + } + } +} From b112f70f6aeef16b7e8f596dc3a26e75ae42b010 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 12 Mar 2026 23:12:09 -0400 Subject: [PATCH 2/5] feat: add LeadOut hierarchy (5 classes) Co-Authored-By: Claude Opus 4.6 --- .../CuttingStrategy/LeadOuts/ArcLeadOut.cs | 27 +++++++++++++++++++ .../CNC/CuttingStrategy/LeadOuts/LeadOut.cs | 11 ++++++++ .../CuttingStrategy/LeadOuts/LineLeadOut.cs | 26 ++++++++++++++++++ .../LeadOuts/MicrotabLeadOut.cs | 16 +++++++++++ .../CNC/CuttingStrategy/LeadOuts/NoLeadOut.cs | 14 ++++++++++ 5 files changed, 94 insertions(+) create mode 100644 OpenNest.Core/CNC/CuttingStrategy/LeadOuts/ArcLeadOut.cs create mode 100644 OpenNest.Core/CNC/CuttingStrategy/LeadOuts/LeadOut.cs create mode 100644 OpenNest.Core/CNC/CuttingStrategy/LeadOuts/LineLeadOut.cs create mode 100644 OpenNest.Core/CNC/CuttingStrategy/LeadOuts/MicrotabLeadOut.cs create mode 100644 OpenNest.Core/CNC/CuttingStrategy/LeadOuts/NoLeadOut.cs diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/ArcLeadOut.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/ArcLeadOut.cs new file mode 100644 index 0000000..95d8724 --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/ArcLeadOut.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using OpenNest.Geometry; + +namespace OpenNest.CNC.CuttingStrategy +{ + public class ArcLeadOut : LeadOut + { + public double Radius { get; set; } + + public override List Generate(Vector contourEndPoint, double contourNormalAngle, + RotationType winding = RotationType.CW) + { + var arcCenterX = contourEndPoint.X + Radius * System.Math.Cos(contourNormalAngle); + var arcCenterY = contourEndPoint.Y + Radius * System.Math.Sin(contourNormalAngle); + var arcCenter = new Vector(arcCenterX, arcCenterY); + + var endPoint = new Vector( + arcCenterX + Radius * System.Math.Cos(contourNormalAngle + System.Math.PI / 2), + arcCenterY + Radius * System.Math.Sin(contourNormalAngle + System.Math.PI / 2)); + + return new List + { + new ArcMove(endPoint, arcCenter, winding) + }; + } + } +} diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/LeadOut.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/LeadOut.cs new file mode 100644 index 0000000..6915c5f --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/LeadOut.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using OpenNest.Geometry; + +namespace OpenNest.CNC.CuttingStrategy +{ + public abstract class LeadOut + { + public abstract List Generate(Vector contourEndPoint, double contourNormalAngle, + RotationType winding = RotationType.CW); + } +} diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/LineLeadOut.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/LineLeadOut.cs new file mode 100644 index 0000000..c72847b --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/LineLeadOut.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using OpenNest.Geometry; +using OpenNest.Math; + +namespace OpenNest.CNC.CuttingStrategy +{ + public class LineLeadOut : LeadOut + { + public double Length { get; set; } + public double ApproachAngle { get; set; } = 90.0; + + public override List Generate(Vector contourEndPoint, double contourNormalAngle, + RotationType winding = RotationType.CW) + { + var overcutAngle = contourNormalAngle + Angle.ToRadians(ApproachAngle); + var endPoint = new Vector( + contourEndPoint.X + Length * System.Math.Cos(overcutAngle), + contourEndPoint.Y + Length * System.Math.Sin(overcutAngle)); + + return new List + { + new LinearMove(endPoint) + }; + } + } +} diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/MicrotabLeadOut.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/MicrotabLeadOut.cs new file mode 100644 index 0000000..13dc799 --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/MicrotabLeadOut.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using OpenNest.Geometry; + +namespace OpenNest.CNC.CuttingStrategy +{ + public class MicrotabLeadOut : LeadOut + { + public double GapSize { get; set; } = 0.03; + + public override List Generate(Vector contourEndPoint, double contourNormalAngle, + RotationType winding = RotationType.CW) + { + return new List(); + } + } +} diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/NoLeadOut.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/NoLeadOut.cs new file mode 100644 index 0000000..8a45cc8 --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/NoLeadOut.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using OpenNest.Geometry; + +namespace OpenNest.CNC.CuttingStrategy +{ + public class NoLeadOut : LeadOut + { + public override List Generate(Vector contourEndPoint, double contourNormalAngle, + RotationType winding = RotationType.CW) + { + return new List(); + } + } +} From 459738e37366d69d72d0a5147365c35e559c8abd Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 12 Mar 2026 23:13:31 -0400 Subject: [PATCH 3/5] feat: add Tab hierarchy (4 classes) Co-Authored-By: Claude Opus 4.6 --- .../CNC/CuttingStrategy/Tabs/BreakerTab.cs | 34 ++++++++++++++++++ .../CNC/CuttingStrategy/Tabs/MachineTab.cs | 20 +++++++++++ .../CNC/CuttingStrategy/Tabs/NormalTab.cs | 36 +++++++++++++++++++ OpenNest.Core/CNC/CuttingStrategy/Tabs/Tab.cs | 16 +++++++++ 4 files changed, 106 insertions(+) create mode 100644 OpenNest.Core/CNC/CuttingStrategy/Tabs/BreakerTab.cs create mode 100644 OpenNest.Core/CNC/CuttingStrategy/Tabs/MachineTab.cs create mode 100644 OpenNest.Core/CNC/CuttingStrategy/Tabs/NormalTab.cs create mode 100644 OpenNest.Core/CNC/CuttingStrategy/Tabs/Tab.cs diff --git a/OpenNest.Core/CNC/CuttingStrategy/Tabs/BreakerTab.cs b/OpenNest.Core/CNC/CuttingStrategy/Tabs/BreakerTab.cs new file mode 100644 index 0000000..3921491 --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/Tabs/BreakerTab.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using OpenNest.Geometry; + +namespace OpenNest.CNC.CuttingStrategy +{ + public class BreakerTab : Tab + { + public double BreakerDepth { get; set; } + public double BreakerLeadInLength { get; set; } + public double BreakerAngle { get; set; } + + public override List Generate( + Vector tabStartPoint, Vector tabEndPoint, double contourNormalAngle, + RotationType winding = RotationType.CW) + { + var codes = new List(); + + if (TabLeadOut != null) + codes.AddRange(TabLeadOut.Generate(tabStartPoint, contourNormalAngle, winding)); + + var scoreAngle = contourNormalAngle + System.Math.PI; + var scoreEnd = new Vector( + tabStartPoint.X + BreakerDepth * System.Math.Cos(scoreAngle), + tabStartPoint.Y + BreakerDepth * System.Math.Sin(scoreAngle)); + codes.Add(new LinearMove(scoreEnd)); + codes.Add(new RapidMove(tabEndPoint)); + + if (TabLeadIn != null) + codes.AddRange(TabLeadIn.Generate(tabEndPoint, contourNormalAngle, winding)); + + return codes; + } + } +} diff --git a/OpenNest.Core/CNC/CuttingStrategy/Tabs/MachineTab.cs b/OpenNest.Core/CNC/CuttingStrategy/Tabs/MachineTab.cs new file mode 100644 index 0000000..b6e5fbd --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/Tabs/MachineTab.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using OpenNest.Geometry; + +namespace OpenNest.CNC.CuttingStrategy +{ + public class MachineTab : Tab + { + public int MachineTabId { get; set; } + + public override List Generate( + Vector tabStartPoint, Vector tabEndPoint, double contourNormalAngle, + RotationType winding = RotationType.CW) + { + return new List + { + new RapidMove(tabEndPoint) + }; + } + } +} diff --git a/OpenNest.Core/CNC/CuttingStrategy/Tabs/NormalTab.cs b/OpenNest.Core/CNC/CuttingStrategy/Tabs/NormalTab.cs new file mode 100644 index 0000000..de3e16c --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/Tabs/NormalTab.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using OpenNest.Geometry; + +namespace OpenNest.CNC.CuttingStrategy +{ + public class NormalTab : Tab + { + public double CutoutMinWidth { get; set; } + public double CutoutMinHeight { get; set; } + public double CutoutMaxWidth { get; set; } + public double CutoutMaxHeight { get; set; } + + public override List Generate( + Vector tabStartPoint, Vector tabEndPoint, double contourNormalAngle, + RotationType winding = RotationType.CW) + { + var codes = new List(); + + if (TabLeadOut != null) + codes.AddRange(TabLeadOut.Generate(tabStartPoint, contourNormalAngle, winding)); + + codes.Add(new RapidMove(tabEndPoint)); + + if (TabLeadIn != null) + codes.AddRange(TabLeadIn.Generate(tabEndPoint, contourNormalAngle, winding)); + + return codes; + } + + public bool AppliesToCutout(double cutoutWidth, double cutoutHeight) + { + return cutoutWidth >= CutoutMinWidth && cutoutWidth <= CutoutMaxWidth + && cutoutHeight >= CutoutMinHeight && cutoutHeight <= CutoutMaxHeight; + } + } +} diff --git a/OpenNest.Core/CNC/CuttingStrategy/Tabs/Tab.cs b/OpenNest.Core/CNC/CuttingStrategy/Tabs/Tab.cs new file mode 100644 index 0000000..504eec5 --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/Tabs/Tab.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using OpenNest.Geometry; + +namespace OpenNest.CNC.CuttingStrategy +{ + public abstract class Tab + { + public double Size { get; set; } = 0.03; + public LeadIn TabLeadIn { get; set; } + public LeadOut TabLeadOut { get; set; } + + public abstract List Generate( + Vector tabStartPoint, Vector tabEndPoint, double contourNormalAngle, + RotationType winding = RotationType.CW); + } +} From ac7d90ae17bec51ce2808b543c4c2d24dc3ffc1a Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 12 Mar 2026 23:13:36 -0400 Subject: [PATCH 4/5] feat: add CuttingParameters and configuration classes Co-Authored-By: Claude Opus 4.6 --- .../CuttingStrategy/AssignmentParameters.cs | 9 ++++++ .../CNC/CuttingStrategy/ContourType.cs | 9 ++++++ .../CNC/CuttingStrategy/CuttingParameters.cs | 30 +++++++++++++++++++ .../CNC/CuttingStrategy/SequenceParameters.cs | 27 +++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 OpenNest.Core/CNC/CuttingStrategy/AssignmentParameters.cs create mode 100644 OpenNest.Core/CNC/CuttingStrategy/ContourType.cs create mode 100644 OpenNest.Core/CNC/CuttingStrategy/CuttingParameters.cs create mode 100644 OpenNest.Core/CNC/CuttingStrategy/SequenceParameters.cs diff --git a/OpenNest.Core/CNC/CuttingStrategy/AssignmentParameters.cs b/OpenNest.Core/CNC/CuttingStrategy/AssignmentParameters.cs new file mode 100644 index 0000000..cfd48ba --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/AssignmentParameters.cs @@ -0,0 +1,9 @@ +namespace OpenNest.CNC.CuttingStrategy +{ + public class AssignmentParameters + { + public SequenceMethod Method { get; set; } = SequenceMethod.Advanced; + public string Preference { get; set; } = "ILAT"; + public double MinGeometryLength { get; set; } = 0.01; + } +} diff --git a/OpenNest.Core/CNC/CuttingStrategy/ContourType.cs b/OpenNest.Core/CNC/CuttingStrategy/ContourType.cs new file mode 100644 index 0000000..9205913 --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/ContourType.cs @@ -0,0 +1,9 @@ +namespace OpenNest.CNC.CuttingStrategy +{ + public enum ContourType + { + External, + Internal, + ArcCircle + } +} diff --git a/OpenNest.Core/CNC/CuttingStrategy/CuttingParameters.cs b/OpenNest.Core/CNC/CuttingStrategy/CuttingParameters.cs new file mode 100644 index 0000000..3f7323c --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/CuttingParameters.cs @@ -0,0 +1,30 @@ +namespace OpenNest.CNC.CuttingStrategy +{ + public class CuttingParameters + { + public int Id { get; set; } + + public string MachineName { get; set; } + public string MaterialName { get; set; } + public string Grade { get; set; } + public double Thickness { get; set; } + + public double Kerf { get; set; } + public double PartSpacing { get; set; } + + public LeadIn ExternalLeadIn { get; set; } = new NoLeadIn(); + public LeadOut ExternalLeadOut { get; set; } = new NoLeadOut(); + + public LeadIn InternalLeadIn { get; set; } = new LineLeadIn { Length = 0.125, ApproachAngle = 90 }; + public LeadOut InternalLeadOut { get; set; } = new NoLeadOut(); + + public LeadIn ArcCircleLeadIn { get; set; } = new NoLeadIn(); + public LeadOut ArcCircleLeadOut { get; set; } = new NoLeadOut(); + + public Tab TabConfig { get; set; } + public bool TabsEnabled { get; set; } + + public SequenceParameters Sequencing { get; set; } = new SequenceParameters(); + public AssignmentParameters Assignment { get; set; } = new AssignmentParameters(); + } +} diff --git a/OpenNest.Core/CNC/CuttingStrategy/SequenceParameters.cs b/OpenNest.Core/CNC/CuttingStrategy/SequenceParameters.cs new file mode 100644 index 0000000..910d333 --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/SequenceParameters.cs @@ -0,0 +1,27 @@ +namespace OpenNest.CNC.CuttingStrategy +{ + // Values match PEP Technology's numbering scheme (value 6 intentionally skipped) + public enum SequenceMethod + { + RightSide = 1, + LeastCode = 2, + Advanced = 3, + BottomSide = 4, + EdgeStart = 5, + LeftSide = 7, + RightSideAlt = 8 + } + + public class SequenceParameters + { + public SequenceMethod Method { get; set; } = SequenceMethod.Advanced; + public double SmallCutoutWidth { get; set; } = 1.5; + public double SmallCutoutHeight { get; set; } = 1.5; + public double MediumCutoutWidth { get; set; } = 8.0; + public double MediumCutoutHeight { get; set; } = 8.0; + public double DistanceMediumSmall { get; set; } + public bool AlternateRowsColumns { get; set; } = true; + public bool AlternateCutoutsWithinRowColumn { get; set; } = true; + public double MinDistanceBetweenRowsColumns { get; set; } = 0.25; + } +} From 441628eff25adf166697bd2f2f0f893d3ed2362d Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 12 Mar 2026 23:15:38 -0400 Subject: [PATCH 5/5] feat: add ContourCuttingStrategy orchestrator Exit point from plate quadrant, nearest-neighbor cutout sequencing via ShapeProfile + ClosestPointTo, contour type detection, and normal angle computation. Co-Authored-By: Claude Opus 4.6 --- .../CuttingStrategy/ContourCuttingStrategy.cs | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs diff --git a/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs b/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs new file mode 100644 index 0000000..6882f3f --- /dev/null +++ b/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs @@ -0,0 +1,177 @@ +using System.Collections.Generic; +using OpenNest.Geometry; + +namespace OpenNest.CNC.CuttingStrategy +{ + public class ContourCuttingStrategy + { + public CuttingParameters Parameters { get; set; } + + public Program Apply(Program partProgram, Plate plate) + { + var exitPoint = GetExitPoint(plate); + var entities = partProgram.ToGeometry(); + var profile = new ShapeProfile(entities); + + // Find closest point on perimeter from exit point + var perimeterPoint = profile.Perimeter.ClosestPointTo(exitPoint, out var perimeterEntity); + + // Chain cutouts by nearest-neighbor from perimeter point, then reverse + // so farthest cutouts are cut first, nearest-to-perimeter cut last + var orderedCutouts = SequenceCutouts(profile.Cutouts, perimeterPoint); + orderedCutouts.Reverse(); + + // Build output program: cutouts first (farthest to nearest), perimeter last + var result = new Program(); + var currentPoint = exitPoint; + + foreach (var cutout in orderedCutouts) + { + var contourType = DetectContourType(cutout); + var closestPt = cutout.ClosestPointTo(currentPoint, out var entity); + var normal = ComputeNormal(closestPt, entity, contourType); + var winding = DetermineWinding(cutout); + + var leadIn = SelectLeadIn(contourType); + var leadOut = SelectLeadOut(contourType); + + result.Codes.AddRange(leadIn.Generate(closestPt, normal, winding)); + // Contour re-indexing: split shape entities at closestPt so cutting + // starts there, convert to ICode, and add to result.Codes + throw new System.NotImplementedException("Contour re-indexing not yet implemented"); + result.Codes.AddRange(leadOut.Generate(closestPt, normal, winding)); + + currentPoint = closestPt; + } + + // Perimeter last + { + var perimeterPt = profile.Perimeter.ClosestPointTo(currentPoint, out perimeterEntity); + var normal = ComputeNormal(perimeterPt, perimeterEntity, ContourType.External); + var winding = DetermineWinding(profile.Perimeter); + + var leadIn = SelectLeadIn(ContourType.External); + var leadOut = SelectLeadOut(ContourType.External); + + result.Codes.AddRange(leadIn.Generate(perimeterPt, normal, winding)); + throw new System.NotImplementedException("Contour re-indexing not yet implemented"); + result.Codes.AddRange(leadOut.Generate(perimeterPt, normal, winding)); + } + + return result; + } + + private Vector GetExitPoint(Plate plate) + { + var w = plate.Size.Width; + var l = plate.Size.Length; + + return plate.Quadrant switch + { + 1 => new Vector(0, 0), // Q1 TopRight origin -> exit BottomLeft + 2 => new Vector(w, 0), // Q2 TopLeft origin -> exit BottomRight + 3 => new Vector(w, l), // Q3 BottomLeft origin -> exit TopRight + 4 => new Vector(0, l), // Q4 BottomRight origin -> exit TopLeft + _ => new Vector(0, 0) + }; + } + + private List SequenceCutouts(List cutouts, Vector startPoint) + { + var remaining = new List(cutouts); + var ordered = new List(); + var currentPoint = startPoint; + + while (remaining.Count > 0) + { + var nearest = remaining[0]; + var nearestPt = nearest.ClosestPointTo(currentPoint); + var nearestDist = nearestPt.DistanceTo(currentPoint); + + for (var i = 1; i < remaining.Count; i++) + { + var pt = remaining[i].ClosestPointTo(currentPoint); + var dist = pt.DistanceTo(currentPoint); + if (dist < nearestDist) + { + nearest = remaining[i]; + nearestPt = pt; + nearestDist = dist; + } + } + + ordered.Add(nearest); + remaining.Remove(nearest); + currentPoint = nearestPt; + } + + return ordered; + } + + private ContourType DetectContourType(Shape cutout) + { + if (cutout.Entities.Count == 1 && cutout.Entities[0] is Circle) + return ContourType.ArcCircle; + + return ContourType.Internal; + } + + private double ComputeNormal(Vector point, Entity entity, ContourType contourType) + { + double normal; + + if (entity is Line line) + { + // Perpendicular to line direction + var tangent = line.EndPoint.AngleFrom(line.StartPoint); + normal = tangent + Math.Angle.HalfPI; + } + else if (entity is Arc arc) + { + // Radial direction from center to point + normal = point.AngleFrom(arc.Center); + } + else if (entity is Circle circle) + { + normal = point.AngleFrom(circle.Center); + } + else + { + normal = 0; + } + + // For internal contours, flip the normal (point into scrap) + if (contourType == ContourType.Internal || contourType == ContourType.ArcCircle) + normal += System.Math.PI; + + return Math.Angle.NormalizeRad(normal); + } + + private RotationType DetermineWinding(Shape shape) + { + // Use signed area: positive = CCW, negative = CW + var area = shape.Area(); + return area >= 0 ? RotationType.CCW : RotationType.CW; + } + + private LeadIn SelectLeadIn(ContourType contourType) + { + return contourType switch + { + ContourType.ArcCircle => Parameters.ArcCircleLeadIn ?? Parameters.InternalLeadIn, + ContourType.Internal => Parameters.InternalLeadIn, + _ => Parameters.ExternalLeadIn + }; + } + + private LeadOut SelectLeadOut(ContourType contourType) + { + return contourType switch + { + ContourType.ArcCircle => Parameters.ArcCircleLeadOut ?? Parameters.InternalLeadOut, + ContourType.Internal => Parameters.InternalLeadOut, + _ => Parameters.ExternalLeadOut + }; + } + } +}