refactor(geometry): move polygon offset callers onto ClipperBridge
Per-entity offsetting left spikes and inverted loops wherever a feature is narrower than the spacing (1.nest), and RemoveSelfIntersections only caught proper crossings. The callers that already flatten to polygons now take a single Clipper region offset instead: - PolygonHelper (BestFit) and PartBoundary use the conservative mode, which keeps their never-under-estimate guarantee. PartBoundary also keeps holes that appear when a perimeter curls back on itself. - NestValidator offsets perimeter and cutouts in one region; Clipper drops collapsed cutouts, so the collapsed-or-flipped heuristic goes away. - CutOff.IntersectPerimeter offsets through the bridge. The old OffsetEntity(Left) grew CW perimeters but shrank CCW ones, so with the plate's perimeter cache a cut-off ran through the part; slots narrower than twice the clearance now close up instead of leaving a gap. - GetOffsetPartLines (3 overloads) and the AddOffset* helpers had no callers and are removed, as is EntityView's never-defined DRAW_OFFSET block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+39
-12
@@ -13,6 +13,8 @@ namespace OpenNest
|
||||
|
||||
public class CutOff
|
||||
{
|
||||
private const double OffsetTolerance = 0.001;
|
||||
|
||||
public Vector Position { get; set; }
|
||||
public CutOffAxis Axis { get; set; }
|
||||
public double? StartLimit { get; set; }
|
||||
@@ -163,14 +165,23 @@ namespace OpenNest
|
||||
double clearance
|
||||
)
|
||||
{
|
||||
var target = OffsetOutward(perimeter, clearance) ?? perimeter;
|
||||
var usedOffset = target != perimeter;
|
||||
var offset = OffsetOutward(perimeter, clearance);
|
||||
var usedOffset = offset != null;
|
||||
var targets = offset ?? new List<Entity> { perimeter };
|
||||
var cutLine = new Line(
|
||||
MakePoint(cutPosition, lineStart),
|
||||
MakePoint(cutPosition, lineEnd)
|
||||
);
|
||||
|
||||
if (!target.Intersects(cutLine, out var pts) || pts.Count < 2)
|
||||
var pts = new List<Vector>();
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (target.Intersects(cutLine, out var targetPts))
|
||||
pts.AddRange(targetPts);
|
||||
}
|
||||
|
||||
if (pts.Count < 2)
|
||||
return null;
|
||||
|
||||
var coords = pts.Select(pt => Axis == CutOffAxis.Vertical ? pt.Y : pt.X)
|
||||
@@ -188,21 +199,37 @@ namespace OpenNest
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Entity OffsetOutward(Entity perimeter, double clearance)
|
||||
/// <summary>
|
||||
/// Grows the perimeter by the clearance as one Clipper region offset, so slots
|
||||
/// narrower than twice the clearance close up instead of leaving a gap the cut
|
||||
/// could run into. Holes appear only where the perimeter curls back on itself.
|
||||
/// </summary>
|
||||
private static List<Entity> OffsetOutward(Entity perimeter, double clearance)
|
||||
{
|
||||
if (clearance <= 0)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var offset = perimeter.OffsetEntity(clearance, OffsetSide.Left);
|
||||
offset?.UpdateBounds();
|
||||
return offset;
|
||||
}
|
||||
catch
|
||||
var offset = perimeter switch
|
||||
{
|
||||
Shape shape => ClipperBridge.OffsetPerimeter(
|
||||
shape,
|
||||
clearance,
|
||||
OffsetTolerance,
|
||||
circumscribe: true
|
||||
),
|
||||
Polygon polygon => ClipperBridge.OffsetPerimeter(
|
||||
polygon,
|
||||
clearance,
|
||||
OffsetTolerance,
|
||||
circumscribe: true
|
||||
),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
if (offset == null || offset.Outers.Count == 0)
|
||||
return null;
|
||||
}
|
||||
|
||||
return offset.Outers.Concat(offset.Holes).Cast<Entity>().ToList();
|
||||
}
|
||||
|
||||
private Vector MakePoint(double cutCoord, double lineCoord) =>
|
||||
|
||||
Reference in New Issue
Block a user