mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-15 03:32:12 -04:00
fix(clamps): flatten part material conservatively for tiny clamps
Fine inscribed flattening still let a native arc cut 6.2e-7 into a 1e-5 clamp unseen: less than one chord's sag, so the layout check, interactive fill and Check Overlaps all called it clear. Tiny-clamp checks now circumscribe perimeter arcs and inscribe cutout arcs (lines stay exact), so the polygons hold every bit of real material. Any real overlap is reported; the cost is a possible report within the tolerance of an arc, never a false clear. The analyzer builds the same polygons instead of a second OverlapMaterial read. PM 407f5eb7-329f-41e4-947e-64ad52aa4034 (delta review s6-delta-review.md)
This commit is contained in:
1 parent
1bcead4b3a
commit
9df9a802cd
5 files changed
+45
-30
No files matched your search
@@ -10,13 +10,7 @@ namespace OpenNest.Diagnostics;
|
||||
/// <summary>Request-local, validated single-outer material. Never exposed to report consumers.</summary>
|
||||
internal sealed record OverlapMaterial(Polygon Outer, List<Polygon> Holes)
|
||||
{
|
||||
internal static OverlapMaterial Read(List<Entity> entities, CancellationToken cancellationToken) =>
|
||||
Read(entities, cancellationToken, PlateOverlapAnalyzer.ChordTolerance);
|
||||
|
||||
/// <summary>As <see cref="Read(List{Entity}, CancellationToken)"/> with a caller-chosen
|
||||
/// chord tolerance (a finer one for keep-outs smaller than the default).</summary>
|
||||
internal static OverlapMaterial Read(List<Entity> entities, CancellationToken cancellationToken,
|
||||
double chordTolerance)
|
||||
internal static OverlapMaterial Read(List<Entity> entities, CancellationToken cancellationToken)
|
||||
{
|
||||
// ShapeBuilder can reverse entities while chaining. Own a fresh copy for each analysis.
|
||||
var shapes = ShapeBuilder.GetShapes(entities.Select(entity => entity.Clone()));
|
||||
@@ -27,7 +21,7 @@ internal sealed record OverlapMaterial(Polygon Outer, List<Polygon> Holes)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ValidateChain(shape);
|
||||
var polygon = shape.ToPolygonWithTolerance(chordTolerance);
|
||||
var polygon = shape.ToPolygonWithTolerance(PlateOverlapAnalyzer.ChordTolerance);
|
||||
// The analytic chain was validated above. Normalize its sampled seam (e.g.
|
||||
// sin(2*pi) is not exactly zero), rather than adding a spurious microscopic edge.
|
||||
if (polygon.IsClosed())
|
||||
|
||||
@@ -280,11 +280,14 @@ public static class PlateOverlapAnalyzer
|
||||
// (negligibly small) clamp rectangle as the overlap fragment.
|
||||
if (KeepOutOverlap.NeedsToleranceFreeTest(zone))
|
||||
{
|
||||
// Re-read the native contours flattened finely enough for this clamp:
|
||||
// the shared 0.001 chords can hide material between an arc and its chord.
|
||||
var fine = OverlapMaterial
|
||||
.Read(part.Input.Source.Entities, cancellationToken, KeepOutOverlap.FlatteningTolerance(zone))
|
||||
.Transform(part.Input.Rotation, part.Input.Location);
|
||||
// Re-read the native contours conservatively and finely enough for
|
||||
// this clamp: the shared 0.001 chords can hide material between an
|
||||
// arc and its chord.
|
||||
var profile = new ShapeProfile(part.Input.Source.Entities.Select(e => e.Clone()).ToList());
|
||||
if (profile.Perimeter == null)
|
||||
throw new ArgumentException("Drawing has no closed material contour.");
|
||||
var (outer, holes) = KeepOutOverlap.ConservativePolygons(profile, zone);
|
||||
var fine = new OverlapMaterial(outer, holes).Transform(part.Input.Rotation, part.Input.Location);
|
||||
if (KeepOutOverlap.MaterialEnters(fine.Outer, fine.Holes, zone))
|
||||
findings.Add(TinyClampFinding(part, clamp));
|
||||
continue;
|
||||
|
||||
@@ -21,30 +21,38 @@ public static class KeepOutOverlap
|
||||
|
||||
/// <summary>
|
||||
/// Chord tolerance for flattening real part material against <paramref name="keepOut"/>:
|
||||
/// a quarter of its smaller side (at most <see cref="ToleranceFreeBelow"/>), so the
|
||||
/// sliver between an arc and its chord can never hold the whole rectangle - any real
|
||||
/// overlap then shows up as a polygon edge through it or its centre inside.
|
||||
/// a quarter of its smaller side, at most <see cref="ToleranceFreeBelow"/>.
|
||||
/// </summary>
|
||||
public static double FlatteningTolerance(Box keepOut) =>
|
||||
System.Math.Min(ToleranceFreeBelow, System.Math.Min(keepOut.Length, keepOut.Width) / 4);
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="MaterialEnters"/> for a part's native contours (lines kept exactly, arcs
|
||||
/// flattened at <see cref="FlatteningTolerance"/>), translated by <paramref name="offset"/>.
|
||||
/// Never fed through a grid-rounding offset or a coarse flattening.
|
||||
/// A part's native contours flattened for a tiny keep-out, never through a grid-rounding
|
||||
/// offset: lines exact, arcs at <see cref="FlatteningTolerance"/>, perimeter arcs
|
||||
/// circumscribed and cutout arcs inscribed, so the polygons hold all real material and
|
||||
/// any real overlap is found. The price is a possible report within that tolerance of
|
||||
/// an arc, never a false clear.
|
||||
/// </summary>
|
||||
public static (Polygon Perimeter, List<Polygon> Holes) ConservativePolygons(ShapeProfile profile, Box keepOut)
|
||||
{
|
||||
var tolerance = FlatteningTolerance(keepOut);
|
||||
var perimeter = profile.Perimeter.ToPolygonWithTolerance(tolerance, circumscribe: true);
|
||||
var holes = new List<Polygon>(profile.Cutouts.Count);
|
||||
foreach (var cutout in profile.Cutouts)
|
||||
holes.Add(cutout.ToPolygonWithTolerance(tolerance));
|
||||
return (perimeter, holes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="MaterialEnters"/> on <see cref="ConservativePolygons"/>, translated by
|
||||
/// <paramref name="offset"/>.
|
||||
/// </summary>
|
||||
public static bool ProfileEnters(ShapeProfile profile, Vector offset, Box keepOut)
|
||||
{
|
||||
var tolerance = FlatteningTolerance(keepOut);
|
||||
var perimeter = profile.Perimeter.ToPolygonWithTolerance(tolerance);
|
||||
var (perimeter, holes) = ConservativePolygons(profile, keepOut);
|
||||
perimeter.Offset(offset);
|
||||
var holes = new List<Polygon>(profile.Cutouts.Count);
|
||||
foreach (var cutout in profile.Cutouts)
|
||||
{
|
||||
var hole = cutout.ToPolygonWithTolerance(tolerance);
|
||||
foreach (var hole in holes)
|
||||
hole.Offset(offset);
|
||||
holes.Add(hole);
|
||||
}
|
||||
return MaterialEnters(perimeter, holes, keepOut);
|
||||
}
|
||||
|
||||
|
||||
@@ -223,6 +223,8 @@ public class TinyClampZoneTests
|
||||
{ "rect-2.00004", new Box(2.000032, 1, 5e-6, Tiny), true },
|
||||
{ "disk", DiskBox(1.9998), true }, // inside the true arc, outside its 0.001 chords
|
||||
{ "disk", DiskBox(2.0002), false }, // just outside the true arc
|
||||
// The arc cuts a corner off the zone, 6.2e-7 deep: less than a fine chord's sag.
|
||||
{ "disk", new Box(11.935615871667656, 10.503377293956166, Tiny, Tiny), true },
|
||||
};
|
||||
|
||||
private static Program MaterialProgram(string name) => name switch
|
||||
|
||||
@@ -220,6 +220,7 @@ public class PlateOverlapAnalyzerClampZoneTests
|
||||
[InlineData("rect", 8.00005, 0.5, false)]
|
||||
[InlineData("disk", 1.9998, 0, true)]
|
||||
[InlineData("disk", 2.0002, 0, false)]
|
||||
[InlineData("disk-box", 11.935615871667656, 10.503377293956166, true)] // arc cuts a corner
|
||||
public void TinyClampIsJudgedAgainstRealMaterial(string material, double a, double b, bool overlaps)
|
||||
{
|
||||
Program program;
|
||||
@@ -235,9 +236,16 @@ public class PlateOverlapAnalyzerClampZoneTests
|
||||
program.MoveTo(12, 10);
|
||||
program.ArcTo(8, 10, 10, 10, RotationType.CCW);
|
||||
program.ArcTo(12, 10, 10, 10, RotationType.CCW);
|
||||
var cx = 10 + a * System.Math.Cos(0.02);
|
||||
var cy = 10 + a * System.Math.Sin(0.02);
|
||||
zone = new Box(cx - 0.000005, cy - 0.000005, 1e-5, 1e-5);
|
||||
if (material == "disk-box")
|
||||
{
|
||||
zone = new Box(a, b, 1e-5, 1e-5);
|
||||
}
|
||||
else
|
||||
{
|
||||
var cx = 10 + a * System.Math.Cos(0.02);
|
||||
var cy = 10 + a * System.Math.Sin(0.02);
|
||||
zone = new Box(cx - 0.000005, cy - 0.000005, 1e-5, 1e-5);
|
||||
}
|
||||
}
|
||||
var parts = new List<Part> { new(new Drawing(material, program), new Vector(0, 0)) };
|
||||
|
||||
|
||||
Reference in new issue
Block a user