feat: add pierce clearance clamping for circle contour lead-ins
Scales down lead-ins that would place the pierce point too close to the opposite wall of small holes. Uses quadratic solve to find the maximum safe distance inside a clearance-reduced radius. Adds Scale() method to all LeadIn types and applies clamping in both the strategy and the interactive preview. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user