From 199095ee43feab62a125c2b6fc0fb035c2f799ff Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Mon, 20 Apr 2026 09:43:56 -0400 Subject: [PATCH] fix(engine): canonicalize PlaceBestFitPairs builds to match BestFitCache frame --- OpenNest.Engine/NestEngineBase.cs | 36 ++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/OpenNest.Engine/NestEngineBase.cs b/OpenNest.Engine/NestEngineBase.cs index 26b3966..54bf64c 100644 --- a/OpenNest.Engine/NestEngineBase.cs +++ b/OpenNest.Engine/NestEngineBase.cs @@ -334,6 +334,12 @@ namespace OpenNest var bestFits = BestFitCache.GetOrCompute( item.Drawing, Plate.Size.Length, Plate.Size.Width, Plate.PartSpacing); + // BestFitCache stores pair coordinates in canonical frame. Build candidates + // from a canonical drawing copy so geometry and coords share a frame; rebind + // + un-rotate winning pair to the original drawing's frame before returning. + var canonicalDrawing = CanonicalFrame.AsCanonicalCopy(item.Drawing); + var sourceAngle = item.Drawing?.Source?.Angle ?? 0.0; + List bestPlacement = null; Box bestTarget = null; @@ -342,7 +348,7 @@ namespace OpenNest if (!fit.Keep) continue; - var parts = fit.BuildParts(item.Drawing); + var parts = fit.BuildParts(canonicalDrawing); var pairBbox = ((IEnumerable)parts).GetBoundingBox(); var pairW = pairBbox.Width; var pairL = pairBbox.Length; @@ -374,6 +380,10 @@ namespace OpenNest if (bestPlacement == null) continue; + // Rebind to the original drawing and compose sourceAngle onto rotation so the + // final placed parts sit in the user's visible frame. + bestPlacement = RebindPairToOriginal(bestPlacement, item.Drawing, sourceAngle); + result.AddRange(bestPlacement); item.Quantity = 0; @@ -388,6 +398,30 @@ namespace OpenNest return result; } + /// + /// Rebinds each canonical-frame Part in the pair to the original Drawing at its current + /// world pose, then composes sourceAngle onto each via CanonicalFrame.FromCanonical so + /// the returned list is in the original drawing's visible frame. Mirrors + /// DefaultNestEngine.RebindAndUnCanonicalize. + /// + private static List RebindPairToOriginal(List parts, Drawing original, double sourceAngle) + { + if (parts == null || parts.Count == 0) + return parts; + + for (var i = 0; i < parts.Count; i++) + { + var p = parts[i]; + var rebound = Part.CreateAtOrigin(original, p.Rotation); + var delta = p.BoundingBox.Location - rebound.BoundingBox.Location; + rebound.Offset(delta); + rebound.UpdateBounds(); + parts[i] = rebound; + } + + return CanonicalFrame.FromCanonical(parts, sourceAngle); + } + /// /// Determines whether a drawing should use grid-fill (true) or bin-pack (false). /// Low-quantity items whose total area is a small fraction of the plate are