From 62f00055b725db8b0622141eef8136a55d194891 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Wed, 18 Mar 2026 20:26:14 -0400 Subject: [PATCH] =?UTF-8?q?Reapply=20"refactor(compactor):=20deduplicate?= =?UTF-8?q?=20Push=20=E2=80=94=20PushDirection=20delegates=20to=20Vector?= =?UTF-8?q?=20overload"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit e695e29355ee0dcbcf937f6bb0fe64f8e8f003d8. --- OpenNest.Engine/Fill/Compactor.cs | 74 ++--------------------------- OpenNest.Engine/Fill/FillExtents.cs | 63 ++++-------------------- OpenNest.Engine/Fill/FillLinear.cs | 53 +-------------------- OpenNest.Tests/CompactorTests.cs | 11 +++-- OpenNest/Forms/PatternTileForm.cs | 6 ++- 5 files changed, 26 insertions(+), 181 deletions(-) diff --git a/OpenNest.Engine/Fill/Compactor.cs b/OpenNest.Engine/Fill/Compactor.cs index 676f625..66af511 100644 --- a/OpenNest.Engine/Fill/Compactor.cs +++ b/OpenNest.Engine/Fill/Compactor.cs @@ -31,16 +31,16 @@ namespace OpenNest.Engine.Fill .Where(p => !movingParts.Contains(p)) .ToList(); - return Push(movingParts, obstacleParts, plate.WorkArea(), plate.PartSpacing, angle); + var direction = new Vector(System.Math.Cos(angle), System.Math.Sin(angle)); + return Push(movingParts, obstacleParts, plate.WorkArea(), plate.PartSpacing, direction); } /// /// Pushes movingParts along an arbitrary angle (radians, 0 = right, π/2 = up). /// public static double Push(List movingParts, List obstacleParts, - Box workArea, double partSpacing, double angle) + Box workArea, double partSpacing, Vector direction) { - var direction = new Vector(System.Math.Cos(angle), System.Math.Sin(angle)); var opposite = -direction; var obstacleBoxes = new Box[obstacleParts.Count]; @@ -104,72 +104,8 @@ namespace OpenNest.Engine.Fill public static double Push(List movingParts, List obstacleParts, Box workArea, double partSpacing, PushDirection direction) { - var obstacleBoxes = new Box[obstacleParts.Count]; - var obstacleLines = new List[obstacleParts.Count]; - - for (var i = 0; i < obstacleParts.Count; i++) - obstacleBoxes[i] = obstacleParts[i].BoundingBox; - - var opposite = SpatialQuery.OppositeDirection(direction); - var halfSpacing = partSpacing / 2; - var isHorizontal = SpatialQuery.IsHorizontalDirection(direction); - var distance = double.MaxValue; - - foreach (var moving in movingParts) - { - var edgeDist = SpatialQuery.EdgeDistance(moving.BoundingBox, workArea, direction); - if (edgeDist <= 0) - distance = 0; - else if (edgeDist < distance) - distance = edgeDist; - - var movingBox = moving.BoundingBox; - List movingLines = null; - - for (var i = 0; i < obstacleBoxes.Length; i++) - { - // Use the reverse-direction gap to check if the obstacle is entirely - // behind the moving part. The forward gap (gap < 0) is unreliable for - // irregular shapes whose bounding boxes overlap even when the actual - // geometry still has a valid contact in the push direction. - var reverseGap = SpatialQuery.DirectionalGap(movingBox, obstacleBoxes[i], opposite); - if (reverseGap > 0) - continue; - - var gap = SpatialQuery.DirectionalGap(movingBox, obstacleBoxes[i], direction); - if (gap >= distance) - continue; - - var perpOverlap = isHorizontal - ? movingBox.IsHorizontalTo(obstacleBoxes[i], out _) - : movingBox.IsVerticalTo(obstacleBoxes[i], out _); - - if (!perpOverlap) - continue; - - movingLines ??= halfSpacing > 0 - ? PartGeometry.GetOffsetPartLines(moving, halfSpacing, direction, ChordTolerance) - : PartGeometry.GetPartLines(moving, direction, ChordTolerance); - - obstacleLines[i] ??= halfSpacing > 0 - ? PartGeometry.GetOffsetPartLines(obstacleParts[i], halfSpacing, opposite, ChordTolerance) - : PartGeometry.GetPartLines(obstacleParts[i], opposite, ChordTolerance); - - var d = SpatialQuery.DirectionalDistance(movingLines, obstacleLines[i], direction); - if (d < distance) - distance = d; - } - } - - if (distance < double.MaxValue && distance > 0) - { - var offset = SpatialQuery.DirectionToOffset(direction, distance); - foreach (var moving in movingParts) - moving.Offset(offset); - return distance; - } - - return 0; + var vector = SpatialQuery.DirectionToOffset(direction, 1.0); + return Push(movingParts, obstacleParts, workArea, partSpacing, vector); } /// diff --git a/OpenNest.Engine/Fill/FillExtents.cs b/OpenNest.Engine/Fill/FillExtents.cs index 40f7dee..e5ccd6f 100644 --- a/OpenNest.Engine/Fill/FillExtents.cs +++ b/OpenNest.Engine/Fill/FillExtents.cs @@ -1,8 +1,10 @@ +using OpenNest.Engine.Strategies; using OpenNest.Geometry; using OpenNest.Math; using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading; namespace OpenNest.Engine.Fill @@ -105,7 +107,7 @@ namespace OpenNest.Engine.Fill private List BuildColumn(Part part1, Part part2, Box pairBbox) { - var column = new List { (Part)part1.Clone(), (Part)part2.Clone() }; + var pairParts = new List { (Part)part1.Clone(), (Part)part2.Clone() }; // Find geometry-aware copy distance for the pair vertically. var boundary1 = new PartBoundary(part1, halfSpacing); @@ -126,22 +128,11 @@ namespace OpenNest.Engine.Fill boundary1, boundary2, pairHeight); if (copyDistance <= 0) - return column; + return pairParts; - var count = 1; - while (true) - { - var nextBottom = pairBbox.Bottom + copyDistance * count; - if (nextBottom + pairHeight > workArea.Top + Tolerance.Epsilon) - break; - - var offset = new Vector(0, copyDistance * count); - column.Add(part1.CloneAtOffset(offset)); - column.Add(part2.CloneAtOffset(offset)); - count++; - } - - return column; + var result = new List(pairParts); + result.AddRange(FillHelpers.Tile(pairParts, workArea, copyDistance, NestDirection.Vertical, allowPartial: false)); + return result; } private double FindVerticalCopyDistance( @@ -324,48 +315,10 @@ namespace OpenNest.Engine.Fill Debug.WriteLine($"[FillExtents] Column copy distance: {copyDistance:F2} (bbox width: {columnWidth:F2}, spacing: {partSpacing:F2})"); - // Build all columns. var result = new List(column); - - // Add the test column we already computed as column 2. - foreach (var part in testColumn) - { - if (IsWithinWorkArea(part)) - result.Add(part); - } - - // Tile additional columns at the copy distance. - var colIndex = 2; - while (!token.IsCancellationRequested) - { - var offset = new Vector(copyDistance * colIndex, 0); - var anyFit = false; - - foreach (var part in column) - { - var clone = part.CloneAtOffset(offset); - if (IsWithinWorkArea(clone)) - { - result.Add(clone); - anyFit = true; - } - } - - if (!anyFit) - break; - - colIndex++; - } + result.AddRange(FillHelpers.Tile(column, workArea, copyDistance, NestDirection.Horizontal, allowPartial: true)); return result; } - - private bool IsWithinWorkArea(Part part) - { - return part.BoundingBox.Right <= workArea.Right + Tolerance.Epsilon && - part.BoundingBox.Top <= workArea.Top + Tolerance.Epsilon && - part.BoundingBox.Left >= workArea.Left - Tolerance.Epsilon && - part.BoundingBox.Bottom >= workArea.Bottom - Tolerance.Epsilon; - } } } diff --git a/OpenNest.Engine/Fill/FillLinear.cs b/OpenNest.Engine/Fill/FillLinear.cs index 674bff3..98e88ed 100644 --- a/OpenNest.Engine/Fill/FillLinear.cs +++ b/OpenNest.Engine/Fill/FillLinear.cs @@ -1,3 +1,4 @@ +using OpenNest.Engine.Strategies; using OpenNest.Geometry; using OpenNest.Math; using System.Collections.Generic; @@ -249,57 +250,7 @@ namespace OpenNest.Engine.Fill private List TilePattern(Pattern basePattern, NestDirection direction, PartBoundary[] boundaries) { var copyDistance = FindPatternCopyDistance(basePattern, direction, boundaries); - - if (copyDistance <= 0) - return new List(); - - var dim = GetDimension(basePattern.BoundingBox, direction); - var start = GetStart(basePattern.BoundingBox, direction); - var limit = GetLimit(direction); - - var estimatedCopies = (int)((limit - start - dim) / copyDistance); - var result = new List(estimatedCopies * basePattern.Parts.Count); - - var count = 1; - - while (true) - { - var nextPos = start + copyDistance * count; - - if (nextPos + dim > limit + Tolerance.Epsilon) - break; - - var offset = MakeOffset(direction, copyDistance * count); - - foreach (var part in basePattern.Parts) - result.Add(part.CloneAtOffset(offset)); - - count++; - } - - // For multi-part patterns, try to place individual parts from the - // next copy that didn't fit as a whole. This handles cases where - // e.g. a 2-part pair only partially fits — one part may still be - // within the work area even though the full pattern exceeds it. - if (basePattern.Parts.Count > 1) - { - var offset = MakeOffset(direction, copyDistance * count); - - foreach (var basePart in basePattern.Parts) - { - var part = basePart.CloneAtOffset(offset); - - if (part.BoundingBox.Right <= WorkArea.Right + Tolerance.Epsilon && - part.BoundingBox.Top <= WorkArea.Top + Tolerance.Epsilon && - part.BoundingBox.Left >= WorkArea.Left - Tolerance.Epsilon && - part.BoundingBox.Bottom >= WorkArea.Bottom - Tolerance.Epsilon) - { - result.Add(part); - } - } - } - - return result; + return FillHelpers.Tile(basePattern.Parts, WorkArea, copyDistance, direction, allowPartial: true); } /// diff --git a/OpenNest.Tests/CompactorTests.cs b/OpenNest.Tests/CompactorTests.cs index a205d24..8d3e2c5 100644 --- a/OpenNest.Tests/CompactorTests.cs +++ b/OpenNest.Tests/CompactorTests.cs @@ -109,8 +109,9 @@ namespace OpenNest.Tests var moving = new List { part }; var obstacles = new List(); - // angle = π = push left - var distance = Compactor.Push(moving, obstacles, workArea, 0, System.Math.PI); + // direction = left + var direction = new Vector(System.Math.Cos(System.Math.PI), System.Math.Sin(System.Math.PI)); + var distance = Compactor.Push(moving, obstacles, workArea, 0, direction); Assert.True(distance > 0); Assert.True(part.BoundingBox.Left < 1); @@ -124,8 +125,10 @@ namespace OpenNest.Tests var moving = new List { part }; var obstacles = new List(); - // angle = 3π/2 = push down - var distance = Compactor.Push(moving, obstacles, workArea, 0, 3 * System.Math.PI / 2); + // direction = down + var angle = 3 * System.Math.PI / 2; + var direction = new Vector(System.Math.Cos(angle), System.Math.Sin(angle)); + var distance = Compactor.Push(moving, obstacles, workArea, 0, direction); Assert.True(distance > 0); Assert.True(part.BoundingBox.Bottom < 1); diff --git a/OpenNest/Forms/PatternTileForm.cs b/OpenNest/Forms/PatternTileForm.cs index acbde4d..5d2a52d 100644 --- a/OpenNest/Forms/PatternTileForm.cs +++ b/OpenNest/Forms/PatternTileForm.cs @@ -213,12 +213,14 @@ namespace OpenNest.Forms if (System.Math.Sqrt(dx * dx + dy * dy) < 0.01) continue; - var angle = System.Math.Atan2(dy, dx); + var direction = new Vector(dx, dy); + var len = System.Math.Sqrt(dx * dx + dy * dy); + if (len > 0) direction = new Vector(dx / len, dy / len); var single = new List { part }; var obstacles = parts.Where(p => p != part).ToList(); totalMoved += Compactor.Push(single, obstacles, - syntheticWorkArea, spacing, angle); + syntheticWorkArea, spacing, direction); } if (totalMoved < 0.01)