perf(geometry): compact Polygon.Cleanup in one pass

Cleanup removed each duplicate vertex with List.RemoveAt from the middle
of the list, so a polygon cost O(duplicates x vertices). Shape.ToPolygon
emits a duplicate at every entity junction and flattened arcs to 1000
points, so building the Drawing for a large arc-heavy part spent seconds
shifting memory: one 1 MB PEP nest took 12.7 s to convert and 11.2 s to
read back, a quarter of a 300-nest archive import's total time.

The single pass compares each vertex with its original predecessor, as
the back-to-front loop did, so tolerance-equal runs collapse identically
and the output is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-10-21 07:13:37 -04:00
1 parent 8c7dc41ce8
commit 9ab6fbbeaf
1 file changed
+21 -4
+21 -4
View File
@@ -519,16 +519,33 @@ namespace OpenNest.Geometry
get { return EntityType.Polygon; }
}
/// <summary>
/// Drops each vertex equal to the one before it in the original sequence. Compacts in
/// place in one pass: removing duplicates one at a time from the middle of the list was
/// O(duplicates x vertices), which dominated loading large drawings whose arcs are
/// flattened to many points.
/// </summary>
internal void Cleanup()
{
for (int i = Vertices.Count - 1; i > 0; i--)
if (Vertices.Count < 2)
return;
// Compare against the original predecessor, not the last kept vertex, so a run of
// tolerance-equal points collapses exactly as the old back-to-front RemoveAt loop did.
var previous = Vertices[0];
var kept = 1;
for (int i = 1; i < Vertices.Count; i++)
{
var vertex = Vertices[i];
var nextVertex = Vertices[i - 1];
if (vertex == nextVertex)
Vertices.RemoveAt(i);
if (vertex != previous)
Vertices[kept++] = vertex;
previous = vertex;
}
Vertices.RemoveRange(kept, Vertices.Count - kept);
}
public BoundingRectangleResult FindBestRotation()