From 53759c58774838f595eed254960f7a6bc3fc00c8 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Mon, 9 Mar 2026 23:02:59 -0400 Subject: [PATCH] perf: eliminate Program.Clone calls from copy-distance computations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- OpenNest.Engine/FillLinear.cs | 43 +++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/OpenNest.Engine/FillLinear.cs b/OpenNest.Engine/FillLinear.cs index c251fb7..316f5f2 100644 --- a/OpenNest.Engine/FillLinear.cs +++ b/OpenNest.Engine/FillLinear.cs @@ -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[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; } + /// + /// Gets boundary lines for all parts in a pattern, with an additional + /// location offset applied. Avoids cloning the pattern. + /// + private static List GetOffsetPatternLines(Pattern pattern, Vector offset, PartBoundary boundary, PushDirection direction) + { + var lines = new List(); + + foreach (var part in pattern.Parts) + lines.AddRange(boundary.GetLines(part.Location + offset, direction)); + + return lines; + } + /// /// 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 &&