From 014c0717165e9e1d4f84eadef888f306a910bbfd Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Tue, 22 Sep 2026 10:46:01 -0400 Subject: [PATCH] fix(core): stop Part.Clone from double-counting baked drawing rotation Part.Clone() rebuilt the clone from BaseDrawing and then re-applied the part's absolute Rotation on top of it. Since BaseDrawing.Program.Rotation is itself absolute (baked in), this double-counted it whenever the base drawing already carried a nonzero rotation, silently corrupting the clone's orientation while its Location stayed unchanged. This only manifests for drawings needing canonical-frame axis correction (nonzero Source.Angle), since DefaultPlateFiller wraps every drawing in a rotated canonical copy before running any fill strategy. FillHelpers. BuildRotatedPattern clones parts before tiling, so any strategy that tiles interlocking pairs (Pairs, Strip/Remnant, Column/Row) could produce overlapping placements for such drawings. Fix: clone the already-composed Program directly instead of re-deriving rotation from BaseDrawing. Co-Authored-By: Claude Sonnet 5 --- OpenNest.Core/Part.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/OpenNest.Core/Part.cs b/OpenNest.Core/Part.cs index d4df8d8..8ca73ec 100644 --- a/OpenNest.Core/Part.cs +++ b/OpenNest.Core/Part.cs @@ -294,9 +294,18 @@ namespace OpenNest /// public object Clone() { - var part = new Part(BaseDrawing); - part.Rotate(Rotation); - part.Location = Location; + // Clone the current Program directly rather than rebuilding from BaseDrawing and + // re-rotating by the absolute Rotation: when BaseDrawing.Program.Rotation is nonzero + // (e.g. a canonical-frame copy used internally during fill), `new Part(BaseDrawing)` + // already carries that baked rotation, so re-applying the full absolute Rotation on + // top of it double-counts the baseline and corrupts the clone's orientation. + var part = new Part( + BaseDrawing, + (Program)Program.Clone(), + Location, + new Box(BoundingBox.X, BoundingBox.Y, BoundingBox.Length, BoundingBox.Width) + ); + part.ownsProgram = true; return part; }