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:
aj
2026-09-23 09:21:48 -04:00
co-authored by Claude Opus 5.5
parent 12f97474b7
commit 9b9386b029
8 changed files with 180 additions and 254 deletions
+8 -9
View File
@@ -26,16 +26,15 @@ namespace OpenNest.Engine.BestFit
if (perimeter == null)
return new PolygonExtractionResult(null, Vector.Zero);
// Ensure CW winding for correct outward offset direction.
definedShape.NormalizeWinding();
// Circumscribe so the polygon never under-estimates the part (or its offset).
var polygon =
halfSpacing > 0
? ClipperBridge
.OffsetPerimeter(perimeter, halfSpacing, 0.01, circumscribe: true)
.LargestOuter()
: perimeter.ToPolygonWithTolerance(0.01, circumscribe: true);
var inflated =
halfSpacing > 0 ? (perimeter.OffsetOutward(halfSpacing) ?? perimeter) : perimeter;
// Convert to polygon with circumscribed arcs for tight nesting.
var polygon = inflated.ToPolygonWithTolerance(0.01, circumscribe: true);
if (polygon.Vertices.Count < 3)
if (polygon == null || polygon.Vertices.Count < 3)
return new PolygonExtractionResult(null, Vector.Zero);
// Normalize: move polygon to origin.
+10 -13
View File
@@ -34,19 +34,16 @@ namespace OpenNest.Engine.Fill
if (perimeter != null)
{
var offsetEntity = perimeter.OffsetOutward(spacing);
if (offsetEntity != null)
{
// Circumscribe arcs so polygon vertices are always outside
// the true arc — guarantees the boundary never under-estimates.
var polygon = offsetEntity.ToPolygonWithTolerance(
PolygonTolerance,
circumscribe: true
);
polygon.RemoveSelfIntersections();
_polygons.Add(polygon);
}
// Conservative offset: the boundary never under-estimates the spacing.
// Holes appear only where the perimeter curls back on itself.
var offset = ClipperBridge.OffsetPerimeter(
perimeter,
spacing,
PolygonTolerance,
circumscribe: true
);
_polygons.AddRange(offset.Outers);
_polygons.AddRange(offset.Holes);
}
PrecomputeDirectionalEdges(