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 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 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.
var stationaryCache = new List<Line>[patternA.Parts.Count];
@@ -131,10 +131,10 @@ namespace OpenNest
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 movingLines = boundaries[j].GetLines(partB.Location, pushDir);
var locationB = patternA.Parts[j].Location + offset;
var movingLines = boundaries[j].GetLines(locationB, pushDir);
for (var i = 0; i < patternA.Parts.Count; i++)
{
@@ -167,9 +167,9 @@ namespace OpenNest
var pushDir = GetPushDirection(direction);
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 slideDistance = Helper.DirectionalDistance(movingLines, stationaryLines, pushDir);
@@ -189,6 +189,20 @@ namespace OpenNest
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>
/// Creates boundaries for all parts in a pattern. Parts that share the same
/// program geometry (same drawing and rotation) reuse the same boundary instance.
@@ -251,8 +265,11 @@ namespace OpenNest
if (nextPos + dim > limit + Tolerance.Epsilon)
break;
var clone = basePattern.Clone(MakeOffset(direction, copyDistance * count));
result.AddRange(clone.Parts);
var offset = MakeOffset(direction, copyDistance * count);
foreach (var part in basePattern.Parts)
result.Add(part.CloneAtOffset(offset));
count++;
}
@@ -262,10 +279,12 @@ namespace OpenNest
// within the work area even though the full pattern exceeds it.
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 &&
part.BoundingBox.Top <= WorkArea.Top + Tolerance.Epsilon &&
part.BoundingBox.Left >= WorkArea.Left - Tolerance.Epsilon &&