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 leadIn = SelectLeadIn(contourType);
|
||||||
var leadOut = SelectLeadOut(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));
|
result.Codes.AddRange(leadIn.Generate(closestPt, normal, winding));
|
||||||
var reindexed = cutout.ReindexAt(closestPt, entity);
|
var reindexed = cutout.ReindexAt(closestPt, entity);
|
||||||
result.Codes.AddRange(ConvertShapeToMoves(reindexed, closestPt));
|
result.Codes.AddRange(ConvertShapeToMoves(reindexed, closestPt));
|
||||||
@@ -153,6 +156,50 @@ namespace OpenNest.CNC.CuttingStrategy
|
|||||||
return area >= 0 ? RotationType.CCW : RotationType.CW;
|
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)
|
private LeadIn SelectLeadIn(ContourType contourType)
|
||||||
{
|
{
|
||||||
return contourType switch
|
return contourType switch
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ namespace OpenNest.CNC.CuttingStrategy
|
|||||||
public LeadIn ArcCircleLeadIn { get; set; } = new NoLeadIn();
|
public LeadIn ArcCircleLeadIn { get; set; } = new NoLeadIn();
|
||||||
public LeadOut ArcCircleLeadOut { get; set; } = new NoLeadOut();
|
public LeadOut ArcCircleLeadOut { get; set; } = new NoLeadOut();
|
||||||
|
|
||||||
|
public double PierceClearance { get; set; } = 0.0625;
|
||||||
|
|
||||||
public Tab TabConfig { get; set; }
|
public Tab TabConfig { get; set; }
|
||||||
public bool TabsEnabled { get; set; }
|
public bool TabsEnabled { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -32,5 +32,8 @@ namespace OpenNest.CNC.CuttingStrategy
|
|||||||
arcCenterX + Radius * System.Math.Cos(contourNormalAngle),
|
arcCenterX + Radius * System.Math.Cos(contourNormalAngle),
|
||||||
arcCenterY + Radius * System.Math.Sin(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),
|
arcStartX + LineLength * System.Math.Cos(lineAngle),
|
||||||
arcStartY + LineLength * System.Math.Sin(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);
|
RotationType winding = RotationType.CW);
|
||||||
|
|
||||||
public abstract Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle);
|
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),
|
arcStartX + LineLength * System.Math.Cos(lineAngle),
|
||||||
arcStartY + LineLength * System.Math.Sin(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.X + Length * System.Math.Cos(approachAngle),
|
||||||
contourStartPoint.Y + Length * System.Math.Sin(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),
|
midX + Length1 * System.Math.Cos(firstAngle),
|
||||||
midY + Length1 * System.Math.Sin(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.Controls;
|
||||||
using OpenNest.Converters;
|
using OpenNest.Converters;
|
||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
|
using OpenNest.Math;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
@@ -148,6 +149,35 @@ namespace OpenNest.Actions
|
|||||||
if (leadIn == null)
|
if (leadIn == null)
|
||||||
return;
|
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)
|
// Get the pierce point (in local space)
|
||||||
var piercePoint = leadIn.GetPiercePoint(snapPoint, snapNormal);
|
var piercePoint = leadIn.GetPiercePoint(snapPoint, snapNormal);
|
||||||
var worldPierce = TransformToWorld(piercePoint);
|
var worldPierce = TransformToWorld(piercePoint);
|
||||||
|
|||||||
Reference in New Issue
Block a user