From 8e0c082876ed23e8b81ab0140952ac665da65168 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Sun, 15 Mar 2026 01:30:50 -0400 Subject: [PATCH] refactor(ui): optimize PushSelected with directional filtering and lazy line computation Extract direction helpers to Helper class (EdgeDistance, DirectionalGap, DirectionToOffset, IsHorizontalDirection) and use them to skip parts not ahead in the push direction or further than the current best distance. Defer line computation until parts survive bounding box checks. Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenNest.Core/Helper.cs | 41 ++++++++++++++++ OpenNest/Controls/PlateView.cs | 88 +++++++++++----------------------- 2 files changed, 69 insertions(+), 60 deletions(-) diff --git a/OpenNest.Core/Helper.cs b/OpenNest.Core/Helper.cs index 5b746c1..44aa015 100644 --- a/OpenNest.Core/Helper.cs +++ b/OpenNest.Core/Helper.cs @@ -1170,6 +1170,47 @@ namespace OpenNest } } + public static bool IsHorizontalDirection(PushDirection direction) + { + return direction is PushDirection.Left or PushDirection.Right; + } + + public static double EdgeDistance(Box box, Box boundary, PushDirection direction) + { + switch (direction) + { + case PushDirection.Left: return box.Left - boundary.Left; + case PushDirection.Right: return boundary.Right - box.Right; + case PushDirection.Up: return boundary.Top - box.Top; + case PushDirection.Down: return box.Bottom - boundary.Bottom; + default: return double.MaxValue; + } + } + + public static Vector DirectionToOffset(PushDirection direction, double distance) + { + switch (direction) + { + case PushDirection.Left: return new Vector(-distance, 0); + case PushDirection.Right: return new Vector(distance, 0); + case PushDirection.Up: return new Vector(0, distance); + case PushDirection.Down: return new Vector(0, -distance); + default: return new Vector(); + } + } + + public static double DirectionalGap(Box from, Box to, PushDirection direction) + { + switch (direction) + { + case PushDirection.Left: return from.Left - to.Right; + case PushDirection.Right: return to.Left - from.Right; + case PushDirection.Up: return to.Bottom - from.Top; + case PushDirection.Down: return from.Bottom - to.Top; + default: return double.MaxValue; + } + } + public static double ClosestDistanceLeft(Box box, List boxes) { var closestDistance = double.MaxValue; diff --git a/OpenNest/Controls/PlateView.cs b/OpenNest/Controls/PlateView.cs index 5ef9bba..051d82c 100644 --- a/OpenNest/Controls/PlateView.cs +++ b/OpenNest/Controls/PlateView.cs @@ -937,94 +937,62 @@ namespace OpenNest.Controls public void PushSelected(PushDirection direction) { - // Build line segments for all stationary parts. var stationaryParts = parts.Where(p => !p.IsSelected && !SelectedParts.Contains(p)).ToList(); - var stationaryLines = new List>(stationaryParts.Count); - var stationaryBoxes = new List(stationaryParts.Count); + var stationaryBoxes = new Box[stationaryParts.Count]; + var stationaryLines = new List[stationaryParts.Count]; var opposite = Helper.OppositeDirection(direction); var halfSpacing = Plate.PartSpacing / 2; + var isHorizontal = Helper.IsHorizontalDirection(direction); - foreach (var part in stationaryParts) - { - stationaryLines.Add(halfSpacing > 0 - ? Helper.GetOffsetPartLines(part.BasePart, halfSpacing, opposite, OffsetTolerance) - : Helper.GetPartLines(part.BasePart, opposite, OffsetTolerance)); - stationaryBoxes.Add(part.BoundingBox); - } + for (var i = 0; i < stationaryParts.Count; i++) + stationaryBoxes[i] = stationaryParts[i].BoundingBox; var workArea = Plate.WorkArea(); var distance = double.MaxValue; foreach (var selected in SelectedParts) { - // Get offset lines for the moving part (half-spacing, symmetric with stationary). - var movingLines = halfSpacing > 0 - ? Helper.GetOffsetPartLines(selected.BasePart, halfSpacing, direction, OffsetTolerance) - : Helper.GetPartLines(selected.BasePart, direction, OffsetTolerance); + // Check plate edge first to tighten the upper bound. + var edgeDist = Helper.EdgeDistance(selected.BoundingBox, workArea, direction); + if (edgeDist > 0 && edgeDist < distance) + distance = edgeDist; var movingBox = selected.BoundingBox; + List movingLines = null; - // Check geometry distance against each stationary part. - for (int i = 0; i < stationaryLines.Count; i++) + for (var i = 0; i < stationaryBoxes.Length; i++) { - // Early-out: skip if bounding boxes don't overlap on the perpendicular axis. - var stBox = stationaryBoxes[i]; - bool perpOverlap; + // Skip parts not ahead in the push direction or further than current best. + var gap = Helper.DirectionalGap(movingBox, stationaryBoxes[i], direction); + if (gap < 0 || gap >= distance) + continue; - switch (direction) - { - case PushDirection.Left: - case PushDirection.Right: - perpOverlap = !(movingBox.Bottom >= stBox.Top || movingBox.Top <= stBox.Bottom); - break; - default: // Up, Down - perpOverlap = !(movingBox.Left >= stBox.Right || movingBox.Right <= stBox.Left); - break; - } + var perpOverlap = isHorizontal + ? movingBox.IsHorizontalTo(stationaryBoxes[i], out _) + : movingBox.IsVerticalTo(stationaryBoxes[i], out _); if (!perpOverlap) continue; + // Compute lines lazily — only for parts that survive bounding box checks. + movingLines ??= halfSpacing > 0 + ? Helper.GetOffsetPartLines(selected.BasePart, halfSpacing, direction, OffsetTolerance) + : Helper.GetPartLines(selected.BasePart, direction, OffsetTolerance); + + stationaryLines[i] ??= halfSpacing > 0 + ? Helper.GetOffsetPartLines(stationaryParts[i].BasePart, halfSpacing, opposite, OffsetTolerance) + : Helper.GetPartLines(stationaryParts[i].BasePart, opposite, OffsetTolerance); + var d = Helper.DirectionalDistance(movingLines, stationaryLines[i], direction); if (d < distance) distance = d; } - - // Check distance to plate edge (actual geometry bbox, not offset). - double edgeDist; - switch (direction) - { - case PushDirection.Left: - edgeDist = selected.Left - workArea.Left; - break; - case PushDirection.Right: - edgeDist = workArea.Right - selected.Right; - break; - case PushDirection.Up: - edgeDist = workArea.Top - selected.Top; - break; - default: // Down - edgeDist = selected.Bottom - workArea.Bottom; - break; - } - - if (edgeDist > 0 && edgeDist < distance) - distance = edgeDist; } if (distance < double.MaxValue && distance > 0) { - var offset = new Vector(); - - switch (direction) - { - case PushDirection.Left: offset.X = -distance; break; - case PushDirection.Right: offset.X = distance; break; - case PushDirection.Up: offset.Y = distance; break; - case PushDirection.Down: offset.Y = -distance; break; - } - + var offset = Helper.DirectionToOffset(direction, distance); SelectedParts.ForEach(p => p.Offset(offset)); Invalidate(); }