Reapply "refactor(compactor): deduplicate Push — PushDirection delegates to Vector overload"
This reverts commit e695e29355.
This commit is contained in:
@@ -31,16 +31,16 @@ namespace OpenNest.Engine.Fill
|
|||||||
.Where(p => !movingParts.Contains(p))
|
.Where(p => !movingParts.Contains(p))
|
||||||
.ToList();
|
.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pushes movingParts along an arbitrary angle (radians, 0 = right, π/2 = up).
|
/// Pushes movingParts along an arbitrary angle (radians, 0 = right, π/2 = up).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static double Push(List<Part> movingParts, List<Part> obstacleParts,
|
public static double Push(List<Part> movingParts, List<Part> 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 opposite = -direction;
|
||||||
|
|
||||||
var obstacleBoxes = new Box[obstacleParts.Count];
|
var obstacleBoxes = new Box[obstacleParts.Count];
|
||||||
@@ -104,72 +104,8 @@ namespace OpenNest.Engine.Fill
|
|||||||
public static double Push(List<Part> movingParts, List<Part> obstacleParts,
|
public static double Push(List<Part> movingParts, List<Part> obstacleParts,
|
||||||
Box workArea, double partSpacing, PushDirection direction)
|
Box workArea, double partSpacing, PushDirection direction)
|
||||||
{
|
{
|
||||||
var obstacleBoxes = new Box[obstacleParts.Count];
|
var vector = SpatialQuery.DirectionToOffset(direction, 1.0);
|
||||||
var obstacleLines = new List<Line>[obstacleParts.Count];
|
return Push(movingParts, obstacleParts, workArea, partSpacing, vector);
|
||||||
|
|
||||||
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<Line> 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
using OpenNest.Engine.Strategies;
|
||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
using OpenNest.Math;
|
using OpenNest.Math;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace OpenNest.Engine.Fill
|
namespace OpenNest.Engine.Fill
|
||||||
@@ -105,7 +107,7 @@ namespace OpenNest.Engine.Fill
|
|||||||
|
|
||||||
private List<Part> BuildColumn(Part part1, Part part2, Box pairBbox)
|
private List<Part> BuildColumn(Part part1, Part part2, Box pairBbox)
|
||||||
{
|
{
|
||||||
var column = new List<Part> { (Part)part1.Clone(), (Part)part2.Clone() };
|
var pairParts = new List<Part> { (Part)part1.Clone(), (Part)part2.Clone() };
|
||||||
|
|
||||||
// Find geometry-aware copy distance for the pair vertically.
|
// Find geometry-aware copy distance for the pair vertically.
|
||||||
var boundary1 = new PartBoundary(part1, halfSpacing);
|
var boundary1 = new PartBoundary(part1, halfSpacing);
|
||||||
@@ -126,22 +128,11 @@ namespace OpenNest.Engine.Fill
|
|||||||
boundary1, boundary2, pairHeight);
|
boundary1, boundary2, pairHeight);
|
||||||
|
|
||||||
if (copyDistance <= 0)
|
if (copyDistance <= 0)
|
||||||
return column;
|
return pairParts;
|
||||||
|
|
||||||
var count = 1;
|
var result = new List<Part>(pairParts);
|
||||||
while (true)
|
result.AddRange(FillHelpers.Tile(pairParts, workArea, copyDistance, NestDirection.Vertical, allowPartial: false));
|
||||||
{
|
return result;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private double FindVerticalCopyDistance(
|
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})");
|
Debug.WriteLine($"[FillExtents] Column copy distance: {copyDistance:F2} (bbox width: {columnWidth:F2}, spacing: {partSpacing:F2})");
|
||||||
|
|
||||||
// Build all columns.
|
|
||||||
var result = new List<Part>(column);
|
var result = new List<Part>(column);
|
||||||
|
result.AddRange(FillHelpers.Tile(column, workArea, copyDistance, NestDirection.Horizontal, allowPartial: true));
|
||||||
// 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++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using OpenNest.Engine.Strategies;
|
||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
using OpenNest.Math;
|
using OpenNest.Math;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -249,57 +250,7 @@ namespace OpenNest.Engine.Fill
|
|||||||
private List<Part> TilePattern(Pattern basePattern, NestDirection direction, PartBoundary[] boundaries)
|
private List<Part> TilePattern(Pattern basePattern, NestDirection direction, PartBoundary[] boundaries)
|
||||||
{
|
{
|
||||||
var copyDistance = FindPatternCopyDistance(basePattern, direction, boundaries);
|
var copyDistance = FindPatternCopyDistance(basePattern, direction, boundaries);
|
||||||
|
return FillHelpers.Tile(basePattern.Parts, WorkArea, copyDistance, direction, allowPartial: true);
|
||||||
if (copyDistance <= 0)
|
|
||||||
return new List<Part>();
|
|
||||||
|
|
||||||
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<Part>(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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -109,8 +109,9 @@ namespace OpenNest.Tests
|
|||||||
var moving = new List<Part> { part };
|
var moving = new List<Part> { part };
|
||||||
var obstacles = new List<Part>();
|
var obstacles = new List<Part>();
|
||||||
|
|
||||||
// angle = π = push left
|
// direction = left
|
||||||
var distance = Compactor.Push(moving, obstacles, workArea, 0, System.Math.PI);
|
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(distance > 0);
|
||||||
Assert.True(part.BoundingBox.Left < 1);
|
Assert.True(part.BoundingBox.Left < 1);
|
||||||
@@ -124,8 +125,10 @@ namespace OpenNest.Tests
|
|||||||
var moving = new List<Part> { part };
|
var moving = new List<Part> { part };
|
||||||
var obstacles = new List<Part>();
|
var obstacles = new List<Part>();
|
||||||
|
|
||||||
// angle = 3π/2 = push down
|
// direction = down
|
||||||
var distance = Compactor.Push(moving, obstacles, workArea, 0, 3 * System.Math.PI / 2);
|
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(distance > 0);
|
||||||
Assert.True(part.BoundingBox.Bottom < 1);
|
Assert.True(part.BoundingBox.Bottom < 1);
|
||||||
|
|||||||
@@ -213,12 +213,14 @@ namespace OpenNest.Forms
|
|||||||
if (System.Math.Sqrt(dx * dx + dy * dy) < 0.01)
|
if (System.Math.Sqrt(dx * dx + dy * dy) < 0.01)
|
||||||
continue;
|
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> { part };
|
var single = new List<Part> { part };
|
||||||
var obstacles = parts.Where(p => p != part).ToList();
|
var obstacles = parts.Where(p => p != part).ToList();
|
||||||
|
|
||||||
totalMoved += Compactor.Push(single, obstacles,
|
totalMoved += Compactor.Push(single, obstacles,
|
||||||
syntheticWorkArea, spacing, angle);
|
syntheticWorkArea, spacing, direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalMoved < 0.01)
|
if (totalMoved < 0.01)
|
||||||
|
|||||||
Reference in New Issue
Block a user