mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-10 20:14:13 -04:00
fix(cutting): accept tangent line-arc joints in material capture
Plan Cutting refused ordinary filleted parts with "Native contact query is numerically uncertain." Material capture checks every curve pair of a ring, adjacent ones included. Where a line meets a tangent arc at their shared vertex, rounding can drop the tangent root of the native line/circle quadratic; the exact ray cast from the line's far end then reached the vertex, which is already recorded as an endpoint contact, and was read as a contact the native query missed. Rounded rectangles rotated off-axis were refused 1037 times in 1080 before this change and 0 times after. The exactness rays now stop short of a line endpoint the other curve already contains (half-way from each end when both are contained), so together they still cover every other point of the line and any unrecorded contact still refuses. The native kernel and tolerances are unchanged. Regressions: four tangent-fillet rings and a rotated filleted part planned through CuttingPlanBatch (red before, green after); a line ending inside a small circle's contact band stays uncertain in both directions. Mutations that treat either endpoint as always contained, drop the start ray, or count recorded endpoints as native contacts all fail. Project Memory: 41880037
This commit is contained in:
1 parent
bec4db21a8
commit
18a61717fe
3 files changed
+108
-15
No files matched your search
@@ -221,20 +221,10 @@ internal static class PostVerificationGeometry
|
||||
points.Add(point);
|
||||
// Existing exact line/ray contact semantics guard native queries which
|
||||
// suppress very short or nearly parallel intersections. Uncertainty refuses.
|
||||
if (Center is null && !SameSupport(other))
|
||||
{
|
||||
var direction = (End - Start) * (1 / Length);
|
||||
if ((other.ContactAfterStart(Start, direction, Length)
|
||||
|| other.ContactAfterStart(End, direction * -1, Length)) && nativeContactCount == 0)
|
||||
throw new NotSupportedException("Native contact query is numerically uncertain.");
|
||||
}
|
||||
if (other.Center is null && Center.HasValue)
|
||||
{
|
||||
var direction = (other.End - other.Start) * (1 / other.Length);
|
||||
if ((ContactAfterStart(other.Start, direction, other.Length)
|
||||
|| ContactAfterStart(other.End, direction * -1, other.Length)) && nativeContactCount == 0)
|
||||
throw new NotSupportedException("Native contact query is numerically uncertain.");
|
||||
}
|
||||
if (Center is null && !SameSupport(other) && nativeContactCount == 0 && ExactLineContact(this, other))
|
||||
throw new NotSupportedException("Native contact query is numerically uncertain.");
|
||||
if (other.Center is null && Center.HasValue && nativeContactCount == 0 && ExactLineContact(other, this))
|
||||
throw new NotSupportedException("Native contact query is numerically uncertain.");
|
||||
return points;
|
||||
|
||||
static bool InteriorWitness(Vector point, Curve curve) => curve.Contains(point)
|
||||
@@ -242,6 +232,25 @@ internal static class PostVerificationGeometry
|
||||
|| (point.DistanceTo(curve.Start) > Epsilon && point.DistanceTo(curve.End) > Epsilon));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether exact ray semantics find <paramref name="curve"/> touching <paramref name="line"/>
|
||||
/// anywhere except at a line endpoint the curve contains, which is already a recorded contact.
|
||||
/// Rays from both ends together cover the whole line. A ray stops short of a contained
|
||||
/// endpoint (both stop half-way when both endpoints are contained): a line meeting a
|
||||
/// tangent arc at their shared vertex is otherwise rediscovered there as a contact the
|
||||
/// native query missed, because rounding can drop the tangent root of its quadratic.
|
||||
/// </summary>
|
||||
private static bool ExactLineContact(Curve line, Curve curve)
|
||||
{
|
||||
var direction = (line.End - line.Start) * (1 / line.Length);
|
||||
var startRecorded = curve.Contains(line.Start);
|
||||
var endRecorded = curve.Contains(line.End);
|
||||
var forward = endRecorded ? startRecorded ? line.Length / 2 : 0 : line.Length;
|
||||
var backward = startRecorded ? endRecorded ? line.Length / 2 : 0 : line.Length;
|
||||
return (forward > 0 && curve.ContactAfterStart(line.Start, direction, forward))
|
||||
|| (backward > 0 && curve.ContactAfterStart(line.End, direction * -1, backward));
|
||||
}
|
||||
|
||||
|
||||
internal static Curve Create(Vector start, Vector end, Vector? center, bool clockwise)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.CNC.CuttingPlanning;
|
||||
using OpenNest.Diagnostics;
|
||||
using OpenNest.Engine.CuttingPlanning;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Tests.CuttingPlanning;
|
||||
@@ -67,4 +68,84 @@ public class NativeContactRegressionTests
|
||||
Assert.False(snapshot.IsComplete);
|
||||
Assert.NotNull(snapshot.Reason);
|
||||
}
|
||||
|
||||
// A line meeting a fillet arc tangentially at their shared vertex: rounding can drop the
|
||||
// tangent root of the native line/circle quadratic, and the exact ray from the line's far end
|
||||
// then rediscovered the already-recorded vertex as a missed contact.
|
||||
[Theory]
|
||||
[InlineData(0.125, 7, 0, 0)]
|
||||
[InlineData(1.0, 33, 55.6, 4.8)]
|
||||
[InlineData(0.5, 61, 110.3, 57.1)]
|
||||
[InlineData(0.25, 80, 0, 0)]
|
||||
public void TangentFilletRing_CertifiesAsSimpleMaterial(double radius, double degrees, double x, double y)
|
||||
{
|
||||
var snapshot = LeadMaterialSnapshot.Capture(
|
||||
RoundedRectangle(7, 3, radius, degrees * System.Math.PI / 180, new Vector(x, y)), Vector.Zero);
|
||||
|
||||
Assert.True(snapshot.IsComplete, snapshot.Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RotatedFilletedPart_PlansReadyOnAPlate()
|
||||
{
|
||||
var plate = new Nest().CreatePlate();
|
||||
plate.Size = new Size(100, 100);
|
||||
plate.Parts.Add(new Part(new Drawing("filleted",
|
||||
RoundedRectangle(7, 3, 1, 33 * System.Math.PI / 180, Vector.Zero)), new Vector(20, 20)));
|
||||
|
||||
var proposal = CuttingPlanBatch.Capture([plate], ExplicitContourTests.Parameters(), false).Plan();
|
||||
|
||||
Assert.True(proposal.CanApply, string.Join(Environment.NewLine, proposal.Describe("in")));
|
||||
}
|
||||
|
||||
// The line ends 0.0000015 above a radius-0.003 circle: inside the circle's exact contact band,
|
||||
// where the native query finds nothing. That end is not a recorded contact, so the ray that
|
||||
// reaches it from the other end must still run.
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public void LineEndingInsideACircleContactBand_StaysUncertain(bool reversed)
|
||||
{
|
||||
const double radius = 0.003;
|
||||
var near = new Vector(0, radius + 0.0000015);
|
||||
var far = new Vector(-0.01, near.Y);
|
||||
var line = reversed
|
||||
? PostVerificationGeometry.Curve.Create(near, far, null, false)
|
||||
: PostVerificationGeometry.Curve.Create(far, near, null, false);
|
||||
var circle = PostVerificationGeometry.Curve.Create(new Vector(radius, 0), new Vector(radius, 0), Vector.Zero, false);
|
||||
Assert.False(circle.Contains(near));
|
||||
|
||||
Assert.Throws<NotSupportedException>(() => line.Contacts(circle, out _));
|
||||
Assert.Throws<NotSupportedException>(() => circle.Contacts(line, out _));
|
||||
}
|
||||
|
||||
private static Program RoundedRectangle(double width, double height, double radius, double angle, Vector at)
|
||||
{
|
||||
var cos = System.Math.Cos(angle);
|
||||
var sin = System.Math.Sin(angle);
|
||||
Vector Place(double x, double y) => new(at.X + x * cos - y * sin, at.Y + x * sin + y * cos);
|
||||
var program = new Program();
|
||||
var start = Place(radius, 0);
|
||||
program.MoveTo(start.X, start.Y);
|
||||
void Line(double x, double y)
|
||||
{
|
||||
var end = Place(x, y);
|
||||
program.LineTo(end.X, end.Y);
|
||||
}
|
||||
void Fillet(double x, double y, double cx, double cy)
|
||||
{
|
||||
var end = Place(x, y);
|
||||
var center = Place(cx, cy);
|
||||
program.ArcTo(end.X, end.Y, center.X, center.Y, RotationType.CCW);
|
||||
}
|
||||
Line(width - radius, 0);
|
||||
Fillet(width, radius, width - radius, radius);
|
||||
Line(width, height - radius);
|
||||
Fillet(width - radius, height, width - radius, height - radius);
|
||||
Line(radius, height);
|
||||
Fillet(0, height - radius, radius, height - radius);
|
||||
Line(0, radius);
|
||||
Fillet(radius, 0, radius, radius);
|
||||
return program;
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,10 @@ lead-in and lead-out line/arc paths must stay in target scrap and avoid other
|
||||
placed material; holes in other parts remain scrap. Tangent/coincident contacts
|
||||
outside the genuine target contour joint and numerically uncertain queries refuse.
|
||||
Material capture supports a simple closed perimeter minus disjoint, non-nested
|
||||
holes; unsupported topology is not a bounding-box approximation.
|
||||
holes; unsupported topology is not a bounding-box approximation. A line meeting a
|
||||
tangent arc at a shared vertex, such as a fillet, is an ordinary joint: an exact
|
||||
contact that the native query rounds away is not uncertain at a line endpoint the
|
||||
other curve already touches, while a contact anywhere else on the line still refuses.
|
||||
|
||||
Candidates rank by actual modeled rapid distance with stable source/contour/entry
|
||||
ordinals. Hash values and drawing names are not tie breakers. The expansion budget
|
||||
|
||||
Reference in new issue
Block a user