From 23b53583525361ae29bf433c47c6535c39da616b Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Mon, 9 Mar 2026 22:53:02 -0400 Subject: [PATCH] perf: add CloneAtOffset to skip re-rotation and bbox walk on part clones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part.Clone() re-clones from the drawing's unrotated program, re-rotates, and walks all CNC codes twice for bounding box — 4 O(c) passes per clone. CloneAtOffset clones from the already-rotated program and computes the bounding box arithmetically, reducing to 1 O(c) pass per clone. Co-Authored-By: Claude Opus 4.6 --- OpenNest.Core/Part.cs | 24 ++++++++++++++++++++++++ OpenNest.Engine/FillLinear.cs | 6 ++---- OpenNest.Engine/Pattern.cs | 6 +----- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/OpenNest.Core/Part.cs b/OpenNest.Core/Part.cs index 1c8ef21..09c3fe6 100644 --- a/OpenNest.Core/Part.cs +++ b/OpenNest.Core/Part.cs @@ -208,5 +208,29 @@ namespace OpenNest return part; } + + /// + /// Creates an offset copy of the part. Clones from the already-rotated + /// program (skips re-rotation) and computes the bounding box arithmetically + /// (skips Program.BoundingBox walk). + /// + public Part CloneAtOffset(Vector offset) + { + var clonedProgram = Program.Clone() as Program; + var part = new Part(BaseDrawing, clonedProgram, + location + offset, + new Box(BoundingBox.X + offset.X, BoundingBox.Y + offset.Y, + BoundingBox.Width, BoundingBox.Height)); + + return part; + } + + private Part(Drawing baseDrawing, Program program, Vector location, Box boundingBox) + { + BaseDrawing = baseDrawing; + Program = program; + this.location = location; + BoundingBox = boundingBox; + } } } diff --git a/OpenNest.Engine/FillLinear.cs b/OpenNest.Engine/FillLinear.cs index b850f74..c251fb7 100644 --- a/OpenNest.Engine/FillLinear.cs +++ b/OpenNest.Engine/FillLinear.cs @@ -76,8 +76,7 @@ namespace OpenNest var pushDir = GetPushDirection(direction); var opposite = Helper.OppositeDirection(pushDir); - var partB = (Part)partA.Clone(); - partB.Offset(MakeOffset(direction, bboxDim)); + var partB = partA.CloneAtOffset(MakeOffset(direction, bboxDim)); var movingLines = boundary.GetLines(partB.Location, pushDir); var stationaryLines = boundary.GetLines(partA.Location, opposite); @@ -364,8 +363,7 @@ namespace OpenNest if (nextPos + dim > limit + Tolerance.Epsilon) break; - var clone = (Part)template.Clone(); - clone.Offset(MakeOffset(direction, copyDistance * count)); + var clone = template.CloneAtOffset(MakeOffset(direction, copyDistance * count)); seed.Parts.Add(clone); count++; } diff --git a/OpenNest.Engine/Pattern.cs b/OpenNest.Engine/Pattern.cs index c5fb8ed..67f40e4 100644 --- a/OpenNest.Engine/Pattern.cs +++ b/OpenNest.Engine/Pattern.cs @@ -24,11 +24,7 @@ namespace OpenNest var pattern = new Pattern(); foreach (var part in Parts) - { - var clone = (Part)part.Clone(); - clone.Offset(offset); - pattern.Parts.Add(clone); - } + pattern.Parts.Add(part.CloneAtOffset(offset)); pattern.UpdateBounds(); return pattern;