perf: eliminate Program.Clone calls from copy-distance computations

FindCopyDistance and FindPatternCopyDistance were cloning entire Parts
(including deep Program copies) just to get offset locations for
GetLines. Compute offset locations directly instead. Also skip the
Pattern wrapper in TilePattern — clone parts directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 23:02:59 -04:00
parent 8ad01bb7f8
commit 53759c5877

View File

@@ -76,9 +76,9 @@ namespace OpenNest
var pushDir = GetPushDirection(direction); var pushDir = GetPushDirection(direction);
var opposite = Helper.OppositeDirection(pushDir); var opposite = Helper.OppositeDirection(pushDir);
var partB = partA.CloneAtOffset(MakeOffset(direction, bboxDim)); var locationB = partA.Location + MakeOffset(direction, bboxDim);
var movingLines = boundary.GetLines(partB.Location, pushDir); var movingLines = boundary.GetLines(locationB, pushDir);
var stationaryLines = boundary.GetLines(partA.Location, opposite); var stationaryLines = boundary.GetLines(partA.Location, opposite);
var slideDistance = Helper.DirectionalDistance(movingLines, stationaryLines, pushDir); var slideDistance = Helper.DirectionalDistance(movingLines, stationaryLines, pushDir);
@@ -121,7 +121,7 @@ namespace OpenNest
} }
} }
var patternB = patternA.Clone(MakeOffset(direction, startOffset)); var offset = MakeOffset(direction, startOffset);
// Pre-compute stationary lines for patternA parts. // Pre-compute stationary lines for patternA parts.
var stationaryCache = new List<Line>[patternA.Parts.Count]; var stationaryCache = new List<Line>[patternA.Parts.Count];
@@ -131,10 +131,10 @@ namespace OpenNest
var maxCopyDistance = 0.0; var maxCopyDistance = 0.0;
for (var j = 0; j < patternB.Parts.Count; j++) for (var j = 0; j < patternA.Parts.Count; j++)
{ {
var partB = patternB.Parts[j]; var locationB = patternA.Parts[j].Location + offset;
var movingLines = boundaries[j].GetLines(partB.Location, pushDir); var movingLines = boundaries[j].GetLines(locationB, pushDir);
for (var i = 0; i < patternA.Parts.Count; i++) for (var i = 0; i < patternA.Parts.Count; i++)
{ {
@@ -167,9 +167,9 @@ namespace OpenNest
var pushDir = GetPushDirection(direction); var pushDir = GetPushDirection(direction);
var opposite = Helper.OppositeDirection(pushDir); var opposite = Helper.OppositeDirection(pushDir);
var patternB = patternA.Clone(MakeOffset(direction, bboxDim)); var offset = MakeOffset(direction, bboxDim);
var movingLines = GetPatternLines(patternB, boundary, pushDir); var movingLines = GetOffsetPatternLines(patternA, offset, boundary, pushDir);
var stationaryLines = GetPatternLines(patternA, boundary, opposite); var stationaryLines = GetPatternLines(patternA, boundary, opposite);
var slideDistance = Helper.DirectionalDistance(movingLines, stationaryLines, pushDir); var slideDistance = Helper.DirectionalDistance(movingLines, stationaryLines, pushDir);
@@ -189,6 +189,20 @@ namespace OpenNest
return lines; return lines;
} }
/// <summary>
/// Gets boundary lines for all parts in a pattern, with an additional
/// location offset applied. Avoids cloning the pattern.
/// </summary>
private static List<Line> GetOffsetPatternLines(Pattern pattern, Vector offset, PartBoundary boundary, PushDirection direction)
{
var lines = new List<Line>();
foreach (var part in pattern.Parts)
lines.AddRange(boundary.GetLines(part.Location + offset, direction));
return lines;
}
/// <summary> /// <summary>
/// Creates boundaries for all parts in a pattern. Parts that share the same /// Creates boundaries for all parts in a pattern. Parts that share the same
/// program geometry (same drawing and rotation) reuse the same boundary instance. /// program geometry (same drawing and rotation) reuse the same boundary instance.
@@ -251,8 +265,11 @@ namespace OpenNest
if (nextPos + dim > limit + Tolerance.Epsilon) if (nextPos + dim > limit + Tolerance.Epsilon)
break; break;
var clone = basePattern.Clone(MakeOffset(direction, copyDistance * count)); var offset = MakeOffset(direction, copyDistance * count);
result.AddRange(clone.Parts);
foreach (var part in basePattern.Parts)
result.Add(part.CloneAtOffset(offset));
count++; count++;
} }
@@ -262,10 +279,12 @@ namespace OpenNest
// within the work area even though the full pattern exceeds it. // within the work area even though the full pattern exceeds it.
if (basePattern.Parts.Count > 1) if (basePattern.Parts.Count > 1)
{ {
var partialClone = basePattern.Clone(MakeOffset(direction, copyDistance * count)); var offset = MakeOffset(direction, copyDistance * count);
foreach (var part in partialClone.Parts) foreach (var basePart in basePattern.Parts)
{ {
var part = basePart.CloneAtOffset(offset);
if (part.BoundingBox.Right <= WorkArea.Right + Tolerance.Epsilon && if (part.BoundingBox.Right <= WorkArea.Right + Tolerance.Epsilon &&
part.BoundingBox.Top <= WorkArea.Top + Tolerance.Epsilon && part.BoundingBox.Top <= WorkArea.Top + Tolerance.Epsilon &&
part.BoundingBox.Left >= WorkArea.Left - Tolerance.Epsilon && part.BoundingBox.Left >= WorkArea.Left - Tolerance.Epsilon &&