mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-30 13:01:07 -04:00
fix(geometry): let parts push off a frame whose contour misses closing by 1e-6
PEP-rounded drawings can leave a contour 1e-6 short of closing. ShapeBuilder chains it as a loop at Tolerance.ChainTolerance, but the slide classifier re-recovered loops at 1e-7, so the whole part became unresolved and every contact with it blocked. Parts placed in its cutout at exactly the part spacing could not be pushed (Y/X keys) in any direction, even away from the wall. Recover loops with the same tolerance ShapeBuilder uses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c6b6678a2f
commit
b8e8cc387d
3 files changed
+40
-2
No files matched your search
@@ -41,6 +41,11 @@ namespace OpenNest.Geometry
|
||||
// the joint's tangents; otherwise the neighbour reads as a second boundary there.
|
||||
internal const double JointTolerance = 1e-6;
|
||||
|
||||
// Loops are recovered with ShapeBuilder's chaining tolerance: a source contour that
|
||||
// closes within it (PEP rounding leaves 1e-6 gaps) is a loop everywhere else, and an
|
||||
// unrecovered loop leaves every contact with the whole part unresolved and blocking.
|
||||
internal const double ChainTolerance = Tolerance.ChainTolerance;
|
||||
|
||||
private readonly List<Entity> entities;
|
||||
private readonly int[] loopOf;
|
||||
private readonly int[] previous;
|
||||
@@ -373,7 +378,7 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
}
|
||||
|
||||
private static bool Near(Vector a, Vector b) => a.DistanceTo(b) <= IncidenceTolerance;
|
||||
private static bool Near(Vector a, Vector b) => a.DistanceTo(b) <= ChainTolerance;
|
||||
|
||||
private static double SignedArea(List<Entity> entities, int first, int last)
|
||||
{
|
||||
|
||||
@@ -387,6 +387,39 @@ namespace OpenNest.Tests.Fill
|
||||
AssertClearance(moving, frame, spacing);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(0.25)]
|
||||
public void Push_RestingInHoleOfFrameWithUnclosedSourceContour_SlidesAwayFromFloor(
|
||||
double spacing
|
||||
)
|
||||
{
|
||||
// PEP-rounded contours can miss closing by 1e-6, inside ShapeBuilder's chaining
|
||||
// tolerance. The slide classifier must chain them the same way, or the frame's
|
||||
// material sides are unknown and the floor contact blocks every direction.
|
||||
var program = new OpenNest.CNC.Program();
|
||||
program.Codes.Add(new OpenNest.CNC.RapidMove(new Vector(0, 0)));
|
||||
program.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(60, 0)));
|
||||
program.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(60, 60)));
|
||||
program.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(0, 60)));
|
||||
program.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(0, 0.000001)));
|
||||
program.Codes.Add(new OpenNest.CNC.RapidMove(new Vector(10, 10)));
|
||||
program.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(50, 10)));
|
||||
program.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(50, 50)));
|
||||
program.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(10, 50)));
|
||||
program.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(10, 10)));
|
||||
var frame = new Part(new Drawing("frame", program));
|
||||
var moving = MakeRectPart(10 + spacing, 10 + spacing, 5, 5);
|
||||
var plate = new Plate(100, 100) { PartSpacing = spacing };
|
||||
plate.Parts.Add(frame);
|
||||
plate.Parts.Add(moving);
|
||||
|
||||
var distance = Compactor.Push(new List<Part> { moving }, plate, PushDirection.Up);
|
||||
|
||||
Assert.Equal(35 - 2 * spacing, distance, 7);
|
||||
Assert.Equal(50 - spacing, moving.BoundingBox.Top, 7);
|
||||
}
|
||||
|
||||
private static void AssertClearance(Part moving, Part obstacle, double spacing)
|
||||
{
|
||||
var clearance = double.MaxValue;
|
||||
|
||||
@@ -6,7 +6,7 @@ For initially non-overlapping closed material boundaries, a directional slide st
|
||||
|
||||
`SpatialQuery.DirectionalDistance` overloads and CPU best-fit batches use the same event sources and contact resolver. Events carry both contact points in their initial world frames, rather than only a snapped distance. The classifier uses closed-loop material sectors, native-curve containment for hole depth, and the curvature of the supporting boundary at a tangential contact. A full-circle arc has no physical corner at its seam. Raw ray helpers remain first-touch primitives, not material-aware slide queries.
|
||||
|
||||
Open/incomplete chains and ambiguous contacts conservatively block. This is not an overlap-repair operation or a general replacement for layout validation. Caller-provided contact topology must represent the same boundaries and offsets as the query. Prepared geometry must not be mutated; prepare a classifier before sharing it between parallel queries. Edge-array queries still sort their arrays, and recover loop order from private copies before classifying contacts.
|
||||
Open/incomplete chains and ambiguous contacts conservatively block. Loops are recovered with ShapeBuilder's `Tolerance.ChainTolerance`, so a source contour that closes within it (PEP-rounded drawings can miss by 1e-6) stays a closed loop; a stricter closure test would leave the whole part unresolved and freeze any part resting against it. This is not an overlap-repair operation or a general replacement for layout validation. Caller-provided contact topology must represent the same boundaries and offsets as the query. Prepared geometry must not be mutated; prepare a classifier before sharing it between parallel queries. Edge-array queries still sort their arrays, and recover loop order from private copies before classifying contacts.
|
||||
|
||||
## Callers
|
||||
|
||||
|
||||
Reference in new issue
Block a user