From ef12cf2966585b64babd8ecf712d794ec1e8e476 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Sun, 15 Mar 2026 12:54:25 -0400 Subject: [PATCH] fix(engine): Compactor treats pushed parts as obstacles for subsequent pushes Previously each moving part only checked against the original stationary set. Parts pushed earlier in the loop were invisible to later parts, causing overlaps (utilization > 100%). Now each pushed part is added to the obstacle set so subsequent parts collide correctly. Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenNest.Engine/Compactor.cs | 41 ++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/OpenNest.Engine/Compactor.cs b/OpenNest.Engine/Compactor.cs index b5ff5ab..3ef73eb 100644 --- a/OpenNest.Engine/Compactor.cs +++ b/OpenNest.Engine/Compactor.cs @@ -29,16 +29,20 @@ namespace OpenNest private static void Push(List movingParts, Plate plate, PushDirection direction) { - var stationaryParts = plate.Parts + // Start with parts already on the plate (excluding the moving group). + var obstacleParts = plate.Parts .Where(p => !movingParts.Contains(p)) .ToList(); - var stationaryBoxes = new Box[stationaryParts.Count]; + var obstacleBoxes = new List(obstacleParts.Count + movingParts.Count); + var obstacleLines = new List>(obstacleParts.Count + movingParts.Count); - for (var i = 0; i < stationaryParts.Count; i++) - stationaryBoxes[i] = stationaryParts[i].BoundingBox; + for (var i = 0; i < obstacleParts.Count; i++) + { + obstacleBoxes.Add(obstacleParts[i].BoundingBox); + obstacleLines.Add(null); // lazy + } - var stationaryLines = new List[stationaryParts.Count]; var opposite = Helper.OppositeDirection(direction); var halfSpacing = plate.PartSpacing / 2; var isHorizontal = Helper.IsHorizontalDirection(direction); @@ -56,15 +60,15 @@ namespace OpenNest List movingLines = null; - for (var i = 0; i < stationaryBoxes.Length; i++) + for (var i = 0; i < obstacleBoxes.Count; i++) { - var gap = Helper.DirectionalGap(movingBox, stationaryBoxes[i], direction); + var gap = Helper.DirectionalGap(movingBox, obstacleBoxes[i], direction); if (gap < 0 || gap >= distance) continue; var perpOverlap = isHorizontal - ? movingBox.IsHorizontalTo(stationaryBoxes[i], out _) - : movingBox.IsVerticalTo(stationaryBoxes[i], out _); + ? movingBox.IsHorizontalTo(obstacleBoxes[i], out _) + : movingBox.IsVerticalTo(obstacleBoxes[i], out _); if (!perpOverlap) continue; @@ -73,11 +77,17 @@ namespace OpenNest ? Helper.GetOffsetPartLines(moving, halfSpacing, direction, ChordTolerance) : Helper.GetPartLines(moving, direction, ChordTolerance); - stationaryLines[i] ??= halfSpacing > 0 - ? Helper.GetOffsetPartLines(stationaryParts[i], halfSpacing, opposite, ChordTolerance) - : Helper.GetPartLines(stationaryParts[i], opposite, ChordTolerance); + var obstaclePart = i < obstacleParts.Count ? obstacleParts[i] : null; - var d = Helper.DirectionalDistance(movingLines, stationaryLines[i], direction); + obstacleLines[i] ??= obstaclePart != null + ? (halfSpacing > 0 + ? Helper.GetOffsetPartLines(obstaclePart, halfSpacing, opposite, ChordTolerance) + : Helper.GetPartLines(obstaclePart, opposite, ChordTolerance)) + : (halfSpacing > 0 + ? Helper.GetOffsetPartLines(moving, halfSpacing, opposite, ChordTolerance) + : Helper.GetPartLines(moving, opposite, ChordTolerance)); + + var d = Helper.DirectionalDistance(movingLines, obstacleLines[i], direction); if (d < distance) distance = d; } @@ -87,6 +97,11 @@ namespace OpenNest var offset = Helper.DirectionToOffset(direction, distance); moving.Offset(offset); } + + // This part is now an obstacle for subsequent moving parts. + obstacleBoxes.Add(moving.BoundingBox); + obstacleParts.Add(moving); + obstacleLines.Add(null); // will be lazily computed if needed } } }