diff --git a/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs b/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs index a67cac0..6d7bed5 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs @@ -36,6 +36,9 @@ namespace OpenNest.CNC.CuttingStrategy var leadIn = SelectLeadIn(contourType); var leadOut = SelectLeadOut(contourType); + if (contourType == ContourType.ArcCircle && entity is Circle circle) + leadIn = ClampLeadInForCircle(leadIn, circle, closestPt, normal); + result.Codes.AddRange(leadIn.Generate(closestPt, normal, winding)); var reindexed = cutout.ReindexAt(closestPt, entity); result.Codes.AddRange(ConvertShapeToMoves(reindexed, closestPt)); @@ -153,6 +156,50 @@ namespace OpenNest.CNC.CuttingStrategy return area >= 0 ? RotationType.CCW : RotationType.CW; } + private LeadIn ClampLeadInForCircle(LeadIn leadIn, Circle circle, Vector contourPoint, double normalAngle) + { + if (leadIn is NoLeadIn || Parameters.PierceClearance <= 0) + return leadIn; + + var piercePoint = leadIn.GetPiercePoint(contourPoint, normalAngle); + var maxRadius = circle.Radius - Parameters.PierceClearance; + if (maxRadius <= 0) + return leadIn; + + var distFromCenter = piercePoint.DistanceTo(circle.Center); + if (distFromCenter <= maxRadius) + return leadIn; + + // Compute max distance from contourPoint toward piercePoint that stays + // inside a circle of radius maxRadius centered at circle.Center. + // Solve: |contourPoint + t*d - center|^2 = maxRadius^2 + var currentDist = contourPoint.DistanceTo(piercePoint); + if (currentDist < Math.Tolerance.Epsilon) + return leadIn; + + var dx = (piercePoint.X - contourPoint.X) / currentDist; + var dy = (piercePoint.Y - contourPoint.Y) / currentDist; + var vx = contourPoint.X - circle.Center.X; + var vy = contourPoint.Y - circle.Center.Y; + + var b = 2.0 * (vx * dx + vy * dy); + var c = vx * vx + vy * vy - maxRadius * maxRadius; + var discriminant = b * b - 4.0 * c; + + if (discriminant < 0) + return leadIn; + + var t = (-b + System.Math.Sqrt(discriminant)) / 2.0; + if (t <= 0) + return leadIn; + + var scale = t / currentDist; + if (scale >= 1.0) + return leadIn; + + return leadIn.Scale(scale); + } + private LeadIn SelectLeadIn(ContourType contourType) { return contourType switch diff --git a/OpenNest.Core/CNC/CuttingStrategy/CuttingParameters.cs b/OpenNest.Core/CNC/CuttingStrategy/CuttingParameters.cs index 3f7323c..a48a7d8 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/CuttingParameters.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/CuttingParameters.cs @@ -21,6 +21,8 @@ namespace OpenNest.CNC.CuttingStrategy public LeadIn ArcCircleLeadIn { get; set; } = new NoLeadIn(); public LeadOut ArcCircleLeadOut { get; set; } = new NoLeadOut(); + public double PierceClearance { get; set; } = 0.0625; + public Tab TabConfig { get; set; } public bool TabsEnabled { get; set; } diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/ArcLeadIn.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/ArcLeadIn.cs index 0779ae4..cdb6c32 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/ArcLeadIn.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/ArcLeadIn.cs @@ -32,5 +32,8 @@ namespace OpenNest.CNC.CuttingStrategy arcCenterX + Radius * System.Math.Cos(contourNormalAngle), arcCenterY + Radius * System.Math.Sin(contourNormalAngle)); } + + public override LeadIn Scale(double factor) => + new ArcLeadIn { Radius = Radius * factor }; } } diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/CleanHoleLeadIn.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/CleanHoleLeadIn.cs index 884341a..ef79235 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/CleanHoleLeadIn.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/CleanHoleLeadIn.cs @@ -45,5 +45,8 @@ namespace OpenNest.CNC.CuttingStrategy arcStartX + LineLength * System.Math.Cos(lineAngle), arcStartY + LineLength * System.Math.Sin(lineAngle)); } + + public override LeadIn Scale(double factor) => + new CleanHoleLeadIn { LineLength = LineLength * factor, ArcRadius = ArcRadius * factor, Kerf = Kerf }; } } diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LeadIn.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LeadIn.cs index bc9eff4..3dc0323 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LeadIn.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LeadIn.cs @@ -9,5 +9,7 @@ namespace OpenNest.CNC.CuttingStrategy RotationType winding = RotationType.CW); public abstract Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle); + + public virtual LeadIn Scale(double factor) => this; } } diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineArcLeadIn.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineArcLeadIn.cs index 136dada..1afde38 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineArcLeadIn.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineArcLeadIn.cs @@ -45,5 +45,8 @@ namespace OpenNest.CNC.CuttingStrategy arcStartX + LineLength * System.Math.Cos(lineAngle), arcStartY + LineLength * System.Math.Sin(lineAngle)); } + + public override LeadIn Scale(double factor) => + new LineArcLeadIn { LineLength = LineLength * factor, ArcRadius = ArcRadius * factor, ApproachAngle = ApproachAngle }; } } diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLeadIn.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLeadIn.cs index 261c872..cfea720 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLeadIn.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLeadIn.cs @@ -28,5 +28,8 @@ namespace OpenNest.CNC.CuttingStrategy contourStartPoint.X + Length * System.Math.Cos(approachAngle), contourStartPoint.Y + Length * System.Math.Sin(approachAngle)); } + + public override LeadIn Scale(double factor) => + new LineLeadIn { Length = Length * factor, ApproachAngle = ApproachAngle }; } } diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLineLeadIn.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLineLeadIn.cs index fdfda0b..3125876 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLineLeadIn.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLineLeadIn.cs @@ -40,5 +40,8 @@ namespace OpenNest.CNC.CuttingStrategy midX + Length1 * System.Math.Cos(firstAngle), midY + Length1 * System.Math.Sin(firstAngle)); } + + public override LeadIn Scale(double factor) => + new LineLineLeadIn { Length1 = Length1 * factor, ApproachAngle1 = ApproachAngle1, Length2 = Length2 * factor, ApproachAngle2 = ApproachAngle2 }; } } diff --git a/OpenNest/Actions/ActionLeadIn.cs b/OpenNest/Actions/ActionLeadIn.cs index 87084af..51ebcb2 100644 --- a/OpenNest/Actions/ActionLeadIn.cs +++ b/OpenNest/Actions/ActionLeadIn.cs @@ -2,6 +2,7 @@ using OpenNest.CNC.CuttingStrategy; using OpenNest.Controls; using OpenNest.Converters; using OpenNest.Geometry; +using OpenNest.Math; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; @@ -148,6 +149,35 @@ namespace OpenNest.Actions if (leadIn == null) return; + // Clamp lead-in for circle contours so it stays inside the hole + if (snapContourType == ContourType.ArcCircle && snapEntity is Circle snapCircle + && parameters.PierceClearance > 0) + { + var pierceCheck = leadIn.GetPiercePoint(snapPoint, snapNormal); + var distFromCenter = pierceCheck.DistanceTo(snapCircle.Center); + var maxRadius = snapCircle.Radius - parameters.PierceClearance; + if (maxRadius > 0 && distFromCenter > maxRadius) + { + var currentDist = snapPoint.DistanceTo(pierceCheck); + if (currentDist > Tolerance.Epsilon) + { + var dx = (pierceCheck.X - snapPoint.X) / currentDist; + var dy = (pierceCheck.Y - snapPoint.Y) / currentDist; + var vx = snapPoint.X - snapCircle.Center.X; + var vy = snapPoint.Y - snapCircle.Center.Y; + var b = 2.0 * (vx * dx + vy * dy); + var c = vx * vx + vy * vy - maxRadius * maxRadius; + var disc = b * b - 4.0 * c; + if (disc >= 0) + { + var t = (-b + System.Math.Sqrt(disc)) / 2.0; + if (t > 0 && t < currentDist) + leadIn = leadIn.Scale(t / currentDist); + } + } + } + } + // Get the pierce point (in local space) var piercePoint = leadIn.GetPiercePoint(snapPoint, snapNormal); var worldPierce = TransformToWorld(piercePoint);