using System.Collections.Generic; using System.Linq; using OpenNest.Geometry; namespace OpenNest { /// /// Pushes a group of parts left and down to close gaps after placement. /// Uses the same directional-distance logic as PlateView.PushSelected /// but operates on Part objects directly. /// public static class Compactor { private const double ChordTolerance = 0.001; /// /// Compacts movingParts toward the bottom-left of the plate work area. /// Everything already on the plate (excluding movingParts) is treated /// as stationary obstacles. /// public static void Compact(List movingParts, Plate plate) { if (movingParts == null || movingParts.Count == 0) return; Push(movingParts, plate, PushDirection.Left); Push(movingParts, plate, PushDirection.Down); } private static void Push(List movingParts, Plate plate, PushDirection direction) { // Start with parts already on the plate (excluding the moving group). var obstacleParts = plate.Parts .Where(p => !movingParts.Contains(p)) .ToList(); var obstacleBoxes = new List(obstacleParts.Count + movingParts.Count); var obstacleLines = new List>(obstacleParts.Count + movingParts.Count); for (var i = 0; i < obstacleParts.Count; i++) { obstacleBoxes.Add(obstacleParts[i].BoundingBox); obstacleLines.Add(null); // lazy } var opposite = Helper.OppositeDirection(direction); var halfSpacing = plate.PartSpacing / 2; var isHorizontal = Helper.IsHorizontalDirection(direction); var workArea = plate.WorkArea(); foreach (var moving in movingParts) { var distance = double.MaxValue; var movingBox = moving.BoundingBox; // Plate edge distance. var edgeDist = Helper.EdgeDistance(movingBox, workArea, direction); if (edgeDist > 0 && edgeDist < distance) distance = edgeDist; List movingLines = null; for (var i = 0; i < obstacleBoxes.Count; i++) { var gap = Helper.DirectionalGap(movingBox, obstacleBoxes[i], direction); if (gap < 0 || gap >= distance) continue; var perpOverlap = isHorizontal ? movingBox.IsHorizontalTo(obstacleBoxes[i], out _) : movingBox.IsVerticalTo(obstacleBoxes[i], out _); if (!perpOverlap) continue; movingLines ??= halfSpacing > 0 ? Helper.GetOffsetPartLines(moving, halfSpacing, direction, ChordTolerance) : Helper.GetPartLines(moving, direction, ChordTolerance); var obstaclePart = i < obstacleParts.Count ? obstacleParts[i] : null; 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; } if (distance < double.MaxValue && distance > 0) { 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 } } } }