style: apply CSharpier formatting to all C# sources
Repo-wide sweep with the pinned CSharpier 1.3.0 tool. Whitespace and line-wrapping only; OpenNest.Engine.Tests (109) and OpenNest.IO.Tests pass after reformat, full solution builds 0 errors. Added .csharpierignore so csproj/config XML keeps its existing layout (CSharpier's XML wrapping churns attributes with zero benefit). Formatting is now enforceable: dotnet csharpier check . passes.
This commit is contained in:
@@ -7,6 +7,7 @@ public class AccumulatingProgressTests
|
||||
private class CapturingProgress : IProgress<NestProgress>
|
||||
{
|
||||
public NestProgress Last { get; private set; }
|
||||
|
||||
public void Report(NestProgress value) => Last = value;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,10 @@ public class AngleCandidateBuilderTests
|
||||
return new Drawing("rect", pgm);
|
||||
}
|
||||
|
||||
private static ClassificationResult MakeClassification(double primaryAngle = 0, PartType type = PartType.Irregular)
|
||||
=> new ClassificationResult { PrimaryAngle = primaryAngle, Type = type };
|
||||
private static ClassificationResult MakeClassification(
|
||||
double primaryAngle = 0,
|
||||
PartType type = PartType.Irregular
|
||||
) => new ClassificationResult { PrimaryAngle = primaryAngle, Type = type };
|
||||
|
||||
[Fact]
|
||||
public void Build_ReturnsAtLeastTwoAngles()
|
||||
@@ -81,8 +83,10 @@ public class AngleCandidateBuilderTests
|
||||
builder.ForceFullSweep = false;
|
||||
var secondAngles = builder.Build(item, MakeClassification(), workArea);
|
||||
|
||||
Assert.True(secondAngles.Count < firstAngles.Count,
|
||||
$"Pruned ({secondAngles.Count}) should be fewer than full ({firstAngles.Count})");
|
||||
Assert.True(
|
||||
secondAngles.Count < firstAngles.Count,
|
||||
$"Pruned ({secondAngles.Count}) should be fewer than full ({firstAngles.Count})"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -128,8 +132,10 @@ public class AngleCandidateBuilderTests
|
||||
|
||||
var angles = builder.Build(item, classification, workArea);
|
||||
|
||||
Assert.True(angles.Count > 2,
|
||||
$"User constraints should override rect classification, got {angles.Count} angles");
|
||||
Assert.True(
|
||||
angles.Count > 2,
|
||||
$"User constraints should override rect classification, got {angles.Count} angles"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -149,7 +155,9 @@ public class AngleCandidateBuilderTests
|
||||
var angles = builder.Build(item, classification, workArea);
|
||||
|
||||
// Start=0, End=PI is NOT "no constraints" — it's a real 0-180 range
|
||||
Assert.True(angles.Count > 2,
|
||||
$"0-to-PI constraint should produce multiple angles, got {angles.Count}");
|
||||
Assert.True(
|
||||
angles.Count > 2,
|
||||
$"0-to-PI constraint should produce multiple angles, got {angles.Count}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest;
|
||||
using OpenNest.Engine.Fill;
|
||||
using OpenNest.Geometry;
|
||||
using Xunit;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Tests.Fill
|
||||
{
|
||||
@@ -31,8 +31,7 @@ namespace OpenNest.Tests.Fill
|
||||
|
||||
// Verify: after moving, the closest point on the arc should be within
|
||||
// tolerance of the line, not past it.
|
||||
var theta = System.Math.Atan2(
|
||||
line.pt2.X - line.pt1.X, -(line.pt2.Y - line.pt1.Y));
|
||||
var theta = System.Math.Atan2(line.pt2.X - line.pt1.X, -(line.pt2.Y - line.pt1.Y));
|
||||
theta = OpenNest.Math.Angle.NormalizeRad(theta + System.Math.PI);
|
||||
var qx = arc.Center.X + arc.Radius * System.Math.Cos(theta);
|
||||
var qy = arc.Center.Y + arc.Radius * System.Math.Sin(theta) + dist;
|
||||
@@ -41,9 +40,11 @@ namespace OpenNest.Tests.Fill
|
||||
// Line equation: (y - 4) / (x - 3) = (6 - 4) / (7 - 3) = 0.5
|
||||
// y = 0.5x + 2.5
|
||||
var lineYAtQx = 0.5 * qx + 2.5;
|
||||
Assert.True(qy <= lineYAtQx + 0.001,
|
||||
$"Arc point ({qx:F4}, {qy:F4}) should not be past line (line Y={lineYAtQx:F4} at X={qx:F4}). " +
|
||||
$"dist={dist:F6}, overshot by {qy - lineYAtQx:F6}");
|
||||
Assert.True(
|
||||
qy <= lineYAtQx + 0.001,
|
||||
$"Arc point ({qx:F4}, {qy:F4}) should not be past line (line Y={lineYAtQx:F4} at X={qx:F4}). "
|
||||
+ $"dist={dist:F6}, overshot by {qy - lineYAtQx:F6}"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -57,17 +58,26 @@ namespace OpenNest.Tests.Fill
|
||||
// Phase 1/2 vertex-only distance: sample arc endpoints + cardinal extreme.
|
||||
var vertices = new[]
|
||||
{
|
||||
new Vector(7, 0), // arc endpoint θ=0
|
||||
new Vector(3, 0), // arc endpoint θ=π
|
||||
new Vector(5, 2), // cardinal extreme θ=π/2
|
||||
new Vector(7, 0), // arc endpoint θ=0
|
||||
new Vector(3, 0), // arc endpoint θ=π
|
||||
new Vector(5, 2), // cardinal extreme θ=π/2
|
||||
};
|
||||
|
||||
var vertexMin = double.MaxValue;
|
||||
foreach (var v in vertices)
|
||||
{
|
||||
var d = SpatialQuery.RayEdgeDistance(v.X, v.Y,
|
||||
line.pt1.X, line.pt1.Y, line.pt2.X, line.pt2.Y, 0, 1);
|
||||
if (d < vertexMin) vertexMin = d;
|
||||
var d = SpatialQuery.RayEdgeDistance(
|
||||
v.X,
|
||||
v.Y,
|
||||
line.pt1.X,
|
||||
line.pt1.Y,
|
||||
line.pt2.X,
|
||||
line.pt2.Y,
|
||||
0,
|
||||
1
|
||||
);
|
||||
if (d < vertexMin)
|
||||
vertexMin = d;
|
||||
}
|
||||
|
||||
// Full directional distance (includes Phase 3 arc-to-line).
|
||||
@@ -75,9 +85,12 @@ namespace OpenNest.Tests.Fill
|
||||
var stationary = new List<Entity> { line };
|
||||
var fullDist = SpatialQuery.DirectionalDistance(moving, stationary, new Vector(0, 1));
|
||||
|
||||
Assert.True(fullDist < vertexMin,
|
||||
$"Full distance ({fullDist:F6}) should be less than vertex-only ({vertexMin:F6})");
|
||||
Assert.True(
|
||||
fullDist < vertexMin,
|
||||
$"Full distance ({fullDist:F6}) should be less than vertex-only ({vertexMin:F6})"
|
||||
);
|
||||
}
|
||||
|
||||
private static Drawing MakeRectDrawing(double w, double h)
|
||||
{
|
||||
var pgm = new OpenNest.CNC.Program();
|
||||
@@ -187,12 +200,24 @@ namespace OpenNest.Tests.Fill
|
||||
// Push without spacing.
|
||||
var obstacle1 = MakeRectPart(0, 0, 10, 10);
|
||||
var part1 = MakeRectPart(50, 0, 10, 10);
|
||||
var distNoSpacing = Compactor.Push(new List<Part> { part1 }, new List<Part> { obstacle1 }, workArea, 0, PushDirection.Left);
|
||||
var distNoSpacing = Compactor.Push(
|
||||
new List<Part> { part1 },
|
||||
new List<Part> { obstacle1 },
|
||||
workArea,
|
||||
0,
|
||||
PushDirection.Left
|
||||
);
|
||||
|
||||
// Push with spacing.
|
||||
var obstacle2 = MakeRectPart(0, 0, 10, 10);
|
||||
var part2 = MakeRectPart(50, 0, 10, 10);
|
||||
var distWithSpacing = Compactor.Push(new List<Part> { part2 }, new List<Part> { obstacle2 }, workArea, 2, PushDirection.Left);
|
||||
var distWithSpacing = Compactor.Push(
|
||||
new List<Part> { part2 },
|
||||
new List<Part> { obstacle2 },
|
||||
workArea,
|
||||
2,
|
||||
PushDirection.Left
|
||||
);
|
||||
|
||||
// Spacing should cause the part to stop at a different position than without spacing.
|
||||
Assert.NotEqual(distNoSpacing, distWithSpacing);
|
||||
@@ -235,11 +260,19 @@ namespace OpenNest.Tests.Fill
|
||||
public void Push_WithSpacing_StopsBeforeNearMissOutsideRawBounds(double degrees)
|
||||
{
|
||||
var obstacle = MakeRectPart(20, 20, 10, 10);
|
||||
var moving = Part.CreateAtOrigin(MakeRectDrawing(10, 10), OpenNest.Math.Angle.ToRadians(degrees));
|
||||
var moving = Part.CreateAtOrigin(
|
||||
MakeRectDrawing(10, 10),
|
||||
OpenNest.Math.Angle.ToRadians(degrees)
|
||||
);
|
||||
moving.Offset(60, 31);
|
||||
|
||||
Compactor.Push(new List<Part> { moving }, new List<Part> { obstacle },
|
||||
new Box(0, 0, 100, 100), 2, PushDirection.Left);
|
||||
Compactor.Push(
|
||||
new List<Part> { moving },
|
||||
new List<Part> { obstacle },
|
||||
new Box(0, 0, 100, 100),
|
||||
2,
|
||||
PushDirection.Left
|
||||
);
|
||||
|
||||
// Must stop at the first clearance boundary, not pass the obstacle
|
||||
// and finish in a clear position on the far side.
|
||||
@@ -253,8 +286,13 @@ namespace OpenNest.Tests.Fill
|
||||
var obstacle = MakeRectPart(20, 20, 10, 10);
|
||||
var moving = MakeRectPart(60, 20, 10, 10);
|
||||
|
||||
Compactor.Push(new List<Part> { moving }, new List<Part> { obstacle },
|
||||
new Box(31, 0, 100, 100), 2, PushDirection.Left);
|
||||
Compactor.Push(
|
||||
new List<Part> { moving },
|
||||
new List<Part> { obstacle },
|
||||
new Box(31, 0, 100, 100),
|
||||
2,
|
||||
PushDirection.Left
|
||||
);
|
||||
|
||||
AssertClearance(moving, obstacle, 2);
|
||||
Assert.Equal(32, moving.BoundingBox.Left, 7);
|
||||
@@ -267,31 +305,39 @@ namespace OpenNest.Tests.Fill
|
||||
foreach (var b in PartGeometry.GetPartLines(obstacle))
|
||||
{
|
||||
Assert.False(Intersect.Intersects(a, b, out _));
|
||||
clearance = System.Math.Min(clearance, a.StartPoint.DistanceTo(b.ClosestPointTo(a.StartPoint)));
|
||||
clearance = System.Math.Min(clearance, b.StartPoint.DistanceTo(a.ClosestPointTo(b.StartPoint)));
|
||||
clearance = System.Math.Min(
|
||||
clearance,
|
||||
a.StartPoint.DistanceTo(b.ClosestPointTo(a.StartPoint))
|
||||
);
|
||||
clearance = System.Math.Min(
|
||||
clearance,
|
||||
b.StartPoint.DistanceTo(a.ClosestPointTo(b.StartPoint))
|
||||
);
|
||||
}
|
||||
Assert.True(clearance >= spacing - 1e-7, $"Clearance {clearance:R} is less than spacing {spacing:R}");
|
||||
Assert.True(
|
||||
clearance >= spacing - 1e-7,
|
||||
$"Clearance {clearance:R} is less than spacing {spacing:R}"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Push_Up_AllowsSharedDiagonalEdgeToSeparate()
|
||||
{
|
||||
var workArea = new Box(0, 0, 20, 20);
|
||||
var obstacle = MakeTrianglePart(
|
||||
new Vector(0, 0),
|
||||
new Vector(10, 0),
|
||||
new Vector(0, 10));
|
||||
var obstacle = MakeTrianglePart(new Vector(0, 0), new Vector(10, 0), new Vector(0, 10));
|
||||
var movingPart = MakeTrianglePart(
|
||||
new Vector(0, 10),
|
||||
new Vector(10, 0),
|
||||
new Vector(10, 10));
|
||||
new Vector(10, 10)
|
||||
);
|
||||
|
||||
var distance = Compactor.Push(
|
||||
new List<Part> { movingPart },
|
||||
new List<Part> { obstacle },
|
||||
workArea,
|
||||
0,
|
||||
PushDirection.Up);
|
||||
PushDirection.Up
|
||||
);
|
||||
|
||||
Assert.True(distance > 0);
|
||||
Assert.True(movingPart.BoundingBox.Top > 19.9);
|
||||
@@ -303,15 +349,19 @@ namespace OpenNest.Tests.Fill
|
||||
{
|
||||
var workArea = new Box(0, 0, 24, 24);
|
||||
var leftTriangle = MakeTrianglePart(
|
||||
2, 2,
|
||||
2,
|
||||
2,
|
||||
new Vector(0, 0),
|
||||
new Vector(8, 0),
|
||||
new Vector(4, 10));
|
||||
new Vector(4, 10)
|
||||
);
|
||||
var rightTriangle = MakeTrianglePart(
|
||||
14, 4,
|
||||
14,
|
||||
4,
|
||||
new Vector(0, 10),
|
||||
new Vector(8, 10),
|
||||
new Vector(4, 0));
|
||||
new Vector(4, 0)
|
||||
);
|
||||
|
||||
var moving = new List<Part> { rightTriangle };
|
||||
var obstacles = new List<Part> { leftTriangle };
|
||||
@@ -333,21 +383,20 @@ namespace OpenNest.Tests.Fill
|
||||
public void Push_Left_BlocksWhenSharedDiagonalEdgeWouldOverlap()
|
||||
{
|
||||
var workArea = new Box(0, 0, 20, 20);
|
||||
var obstacle = MakeTrianglePart(
|
||||
new Vector(0, 0),
|
||||
new Vector(10, 0),
|
||||
new Vector(0, 10));
|
||||
var obstacle = MakeTrianglePart(new Vector(0, 0), new Vector(10, 0), new Vector(0, 10));
|
||||
var movingPart = MakeTrianglePart(
|
||||
new Vector(0, 10),
|
||||
new Vector(10, 0),
|
||||
new Vector(10, 10));
|
||||
new Vector(10, 10)
|
||||
);
|
||||
|
||||
var distance = Compactor.Push(
|
||||
new List<Part> { movingPart },
|
||||
new List<Part> { obstacle },
|
||||
workArea,
|
||||
0,
|
||||
PushDirection.Left);
|
||||
PushDirection.Left
|
||||
);
|
||||
|
||||
Assert.Equal(0, distance);
|
||||
Assert.Equal(0, movingPart.BoundingBox.Left);
|
||||
@@ -362,7 +411,10 @@ namespace OpenNest.Tests.Fill
|
||||
var obstacles = new List<Part>();
|
||||
|
||||
// direction = left
|
||||
var direction = new Vector(System.Math.Cos(System.Math.PI), System.Math.Sin(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);
|
||||
@@ -394,7 +446,13 @@ namespace OpenNest.Tests.Fill
|
||||
var moving = new List<Part> { part };
|
||||
var obstacles = new List<Part>();
|
||||
|
||||
var distance = Compactor.PushBoundingBox(moving, obstacles, workArea, 0, PushDirection.Left);
|
||||
var distance = Compactor.PushBoundingBox(
|
||||
moving,
|
||||
obstacles,
|
||||
workArea,
|
||||
0,
|
||||
PushDirection.Left
|
||||
);
|
||||
|
||||
Assert.True(distance > 0);
|
||||
Assert.True(part.BoundingBox.Left < 1);
|
||||
|
||||
@@ -37,12 +37,12 @@ public class DefaultFillComparerTests
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(20, 0, 10),
|
||||
TestHelpers.MakePartAt(40, 0, 10)
|
||||
TestHelpers.MakePartAt(40, 0, 10),
|
||||
};
|
||||
var current = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(20, 0, 10)
|
||||
TestHelpers.MakePartAt(20, 0, 10),
|
||||
};
|
||||
Assert.True(comparer.IsBetter(candidate, current, workArea));
|
||||
}
|
||||
@@ -53,12 +53,12 @@ public class DefaultFillComparerTests
|
||||
var candidate = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(12, 0, 10)
|
||||
TestHelpers.MakePartAt(12, 0, 10),
|
||||
};
|
||||
var current = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(50, 0, 10)
|
||||
TestHelpers.MakePartAt(50, 0, 10),
|
||||
};
|
||||
Assert.True(comparer.IsBetter(candidate, current, workArea));
|
||||
}
|
||||
@@ -76,12 +76,12 @@ public class VerticalRemnantComparerTests
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(40, 0, 10),
|
||||
TestHelpers.MakePartAt(80, 0, 10)
|
||||
TestHelpers.MakePartAt(80, 0, 10),
|
||||
};
|
||||
var current = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(12, 0, 10)
|
||||
TestHelpers.MakePartAt(12, 0, 10),
|
||||
};
|
||||
Assert.True(comparer.IsBetter(candidate, current, workArea));
|
||||
}
|
||||
@@ -92,12 +92,12 @@ public class VerticalRemnantComparerTests
|
||||
var candidate = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(12, 0, 10)
|
||||
TestHelpers.MakePartAt(12, 0, 10),
|
||||
};
|
||||
var current = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(50, 0, 10)
|
||||
TestHelpers.MakePartAt(50, 0, 10),
|
||||
};
|
||||
Assert.True(comparer.IsBetter(candidate, current, workArea));
|
||||
}
|
||||
@@ -108,12 +108,12 @@ public class VerticalRemnantComparerTests
|
||||
var candidate = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(40, 0, 10)
|
||||
TestHelpers.MakePartAt(40, 0, 10),
|
||||
};
|
||||
var current = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(40, 40, 10)
|
||||
TestHelpers.MakePartAt(40, 40, 10),
|
||||
};
|
||||
Assert.True(comparer.IsBetter(candidate, current, workArea));
|
||||
}
|
||||
@@ -144,12 +144,12 @@ public class HorizontalRemnantComparerTests
|
||||
var candidate = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(0, 12, 10)
|
||||
TestHelpers.MakePartAt(0, 12, 10),
|
||||
};
|
||||
var current = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(0, 50, 10)
|
||||
TestHelpers.MakePartAt(0, 50, 10),
|
||||
};
|
||||
Assert.True(comparer.IsBetter(candidate, current, workArea));
|
||||
}
|
||||
@@ -161,12 +161,12 @@ public class HorizontalRemnantComparerTests
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(0, 40, 10),
|
||||
TestHelpers.MakePartAt(0, 80, 10)
|
||||
TestHelpers.MakePartAt(0, 80, 10),
|
||||
};
|
||||
var current = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(0, 12, 10)
|
||||
TestHelpers.MakePartAt(0, 12, 10),
|
||||
};
|
||||
Assert.True(comparer.IsBetter(candidate, current, workArea));
|
||||
}
|
||||
|
||||
@@ -41,10 +41,14 @@ public class FillExtentsTests
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
Assert.True(part.BoundingBox.Right <= workArea.Right + 0.01,
|
||||
$"Part right edge {part.BoundingBox.Right} exceeds work area {workArea.Right}");
|
||||
Assert.True(part.BoundingBox.Top <= workArea.Top + 0.01,
|
||||
$"Part top edge {part.BoundingBox.Top} exceeds work area {workArea.Top}");
|
||||
Assert.True(
|
||||
part.BoundingBox.Right <= workArea.Right + 0.01,
|
||||
$"Part right edge {part.BoundingBox.Right} exceeds work area {workArea.Right}"
|
||||
);
|
||||
Assert.True(
|
||||
part.BoundingBox.Top <= workArea.Top + 0.01,
|
||||
$"Part top edge {part.BoundingBox.Top} exceeds work area {workArea.Top}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +86,10 @@ public class FillExtentsTests
|
||||
|
||||
// After adjustment, the gap should be small (within one part spacing).
|
||||
var gap = workArea.Top - topEdge;
|
||||
Assert.True(gap < 1.0,
|
||||
$"Gap of {gap:F2} is too large — adjustment should fill close to the top");
|
||||
Assert.True(
|
||||
gap < 1.0,
|
||||
$"Gap of {gap:F2} is too large — adjustment should fill close to the top"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -96,8 +102,10 @@ public class FillExtentsTests
|
||||
var parts = filler.Fill(drawing);
|
||||
|
||||
// With a 120-wide sheet and ~10-wide parts, we should get multiple columns.
|
||||
Assert.True(parts.Count >= 8,
|
||||
$"Expected multiple columns but got only {parts.Count} parts");
|
||||
Assert.True(
|
||||
parts.Count >= 8,
|
||||
$"Expected multiple columns but got only {parts.Count} parts"
|
||||
);
|
||||
|
||||
// Verify all parts are within bounds.
|
||||
foreach (var part in parts)
|
||||
@@ -136,10 +144,14 @@ public class FillExtentsTests
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
Assert.True(part.BoundingBox.Left >= workArea.Left - 0.01,
|
||||
$"Part left {part.BoundingBox.Left} below work area left {workArea.Left}");
|
||||
Assert.True(part.BoundingBox.Bottom >= workArea.Bottom - 0.01,
|
||||
$"Part bottom {part.BoundingBox.Bottom} below work area bottom {workArea.Bottom}");
|
||||
Assert.True(
|
||||
part.BoundingBox.Left >= workArea.Left - 0.01,
|
||||
$"Part left {part.BoundingBox.Left} below work area left {workArea.Left}"
|
||||
);
|
||||
Assert.True(
|
||||
part.BoundingBox.Bottom >= workArea.Bottom - 0.01,
|
||||
$"Part bottom {part.BoundingBox.Bottom} below work area bottom {workArea.Bottom}"
|
||||
);
|
||||
Assert.True(part.BoundingBox.Right <= workArea.Right + 0.01);
|
||||
Assert.True(part.BoundingBox.Top <= workArea.Top + 0.01);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Converters;
|
||||
@@ -6,8 +8,6 @@ using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest.Tests.Fill
|
||||
{
|
||||
@@ -32,19 +32,23 @@ namespace OpenNest.Tests.Fill
|
||||
// Outer circle (CCW)
|
||||
var outerStart = new Vector(outerRadius * 2, outerRadius);
|
||||
pgm.Codes.Add(new RapidMove(outerStart));
|
||||
pgm.Codes.Add(new ArcMove(outerStart, new Vector(outerRadius, outerRadius), RotationType.CCW));
|
||||
pgm.Codes.Add(
|
||||
new ArcMove(outerStart, new Vector(outerRadius, outerRadius), RotationType.CCW)
|
||||
);
|
||||
// Inner circle (CW = hole)
|
||||
var innerStart = new Vector(outerRadius + innerRadius, outerRadius);
|
||||
pgm.Codes.Add(new RapidMove(innerStart));
|
||||
pgm.Codes.Add(new ArcMove(innerStart, new Vector(outerRadius, outerRadius), RotationType.CW));
|
||||
pgm.Codes.Add(
|
||||
new ArcMove(innerStart, new Vector(outerRadius, outerRadius), RotationType.CW)
|
||||
);
|
||||
return new Drawing("ring", pgm);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(2.0, 0.125)] // 4" diameter circle, 1/8" spacing
|
||||
[InlineData(1.0, 0.125)] // 2" diameter circle
|
||||
[InlineData(3.0, 0.0625)] // 6" diameter circle, 1/16" spacing
|
||||
[InlineData(0.5, 0.25)] // 1" diameter circle, 1/4" spacing
|
||||
[InlineData(2.0, 0.125)] // 4" diameter circle, 1/8" spacing
|
||||
[InlineData(1.0, 0.125)] // 2" diameter circle
|
||||
[InlineData(3.0, 0.0625)] // 6" diameter circle, 1/16" spacing
|
||||
[InlineData(0.5, 0.25)] // 1" diameter circle, 1/4" spacing
|
||||
public void CircleFill_OffsetBoundaries_DoNotOverlap(double radius, double spacing)
|
||||
{
|
||||
var drawing = MakeCircleDrawing(radius);
|
||||
@@ -58,21 +62,31 @@ namespace OpenNest.Tests.Fill
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(2.0, 1.5, 0.125)] // Ring: outer R=2, inner R=1.5
|
||||
[InlineData(1.5, 1.0, 0.125)] // Ring: outer R=1.5, inner R=1.0
|
||||
public void RingFill_OffsetBoundaries_DoNotOverlap(double outerR, double innerR, double spacing)
|
||||
[InlineData(2.0, 1.5, 0.125)] // Ring: outer R=2, inner R=1.5
|
||||
[InlineData(1.5, 1.0, 0.125)] // Ring: outer R=1.5, inner R=1.0
|
||||
public void RingFill_OffsetBoundaries_DoNotOverlap(
|
||||
double outerR,
|
||||
double innerR,
|
||||
double spacing
|
||||
)
|
||||
{
|
||||
var drawing = MakeRingDrawing(outerR, innerR);
|
||||
var workArea = new Box(0, 0, 48, 48);
|
||||
var engine = new FillLinear(workArea, spacing);
|
||||
var parts = engine.Fill(drawing, 0, NestDirection.Horizontal);
|
||||
|
||||
_output.WriteLine($"Ring outerR={outerR}, innerR={innerR}, spacing={spacing}: {parts.Count} parts");
|
||||
_output.WriteLine(
|
||||
$"Ring outerR={outerR}, innerR={innerR}, spacing={spacing}: {parts.Count} parts"
|
||||
);
|
||||
|
||||
AssertNoOffsetOverlap(parts, spacing, outerR * 2);
|
||||
}
|
||||
|
||||
private void AssertNoOffsetOverlap(List<Part> parts, double spacing, double expectedDiameter)
|
||||
private void AssertNoOffsetOverlap(
|
||||
List<Part> parts,
|
||||
double spacing,
|
||||
double expectedDiameter
|
||||
)
|
||||
{
|
||||
if (parts.Count < 2)
|
||||
{
|
||||
@@ -109,21 +123,27 @@ namespace OpenNest.Tests.Fill
|
||||
violationCount++;
|
||||
if (violationCount <= 5)
|
||||
{
|
||||
_output.WriteLine($" SPACING VIOLATION parts[{i}] vs parts[{j}]: " +
|
||||
$"centerDist={centerDist:F6}, rawGap={rawGap:F6}, offsetGap={offsetGap:F6}, " +
|
||||
$"expected>={spacing:F4}");
|
||||
_output.WriteLine(
|
||||
$" SPACING VIOLATION parts[{i}] vs parts[{j}]: "
|
||||
+ $"centerDist={centerDist:F6}, rawGap={rawGap:F6}, offsetGap={offsetGap:F6}, "
|
||||
+ $"expected>={spacing:F4}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_output.WriteLine($" Min gap={minGap:F6}, expected>={spacing:F4}, violations={violationCount}");
|
||||
_output.WriteLine(
|
||||
$" Min gap={minGap:F6}, expected>={spacing:F4}, violations={violationCount}"
|
||||
);
|
||||
|
||||
if (violationCount > 0)
|
||||
{
|
||||
var maxDeficit = spacing - minGap;
|
||||
_output.WriteLine($" Max deficit={maxDeficit:F6}");
|
||||
Assert.Fail($"{violationCount} pairs violate spacing: min gap={minGap:F6}, expected>={spacing}, deficit={maxDeficit:F6}");
|
||||
Assert.Fail(
|
||||
$"{violationCount} pairs violate spacing: min gap={minGap:F6}, expected>={spacing}, deficit={maxDeficit:F6}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,19 @@ public class FillWithDirectionPreferenceTests
|
||||
[Fact]
|
||||
public void NullPreference_TriesBothDirections_ReturnsBetter()
|
||||
{
|
||||
var hParts = new List<Part> { TestHelpers.MakePartAt(0, 0, 10), TestHelpers.MakePartAt(12, 0, 10) };
|
||||
var hParts = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(12, 0, 10),
|
||||
};
|
||||
var vParts = new List<Part> { TestHelpers.MakePartAt(0, 0, 10) };
|
||||
|
||||
var result = FillHelpers.FillWithDirectionPreference(
|
||||
dir => dir == NestDirection.Horizontal ? hParts : vParts,
|
||||
null, comparer, workArea);
|
||||
null,
|
||||
comparer,
|
||||
workArea
|
||||
);
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
}
|
||||
@@ -26,12 +33,24 @@ public class FillWithDirectionPreferenceTests
|
||||
[Fact]
|
||||
public void PreferredDirection_UsedFirst_WhenProducesResults()
|
||||
{
|
||||
var hParts = new List<Part> { TestHelpers.MakePartAt(0, 0, 10), TestHelpers.MakePartAt(12, 0, 10) };
|
||||
var vParts = new List<Part> { TestHelpers.MakePartAt(0, 0, 10), TestHelpers.MakePartAt(0, 12, 10), TestHelpers.MakePartAt(0, 24, 10) };
|
||||
var hParts = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(12, 0, 10),
|
||||
};
|
||||
var vParts = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(0, 12, 10),
|
||||
TestHelpers.MakePartAt(0, 24, 10),
|
||||
};
|
||||
|
||||
var result = FillHelpers.FillWithDirectionPreference(
|
||||
dir => dir == NestDirection.Horizontal ? hParts : vParts,
|
||||
NestDirection.Horizontal, comparer, workArea);
|
||||
NestDirection.Horizontal,
|
||||
comparer,
|
||||
workArea
|
||||
);
|
||||
|
||||
Assert.Equal(2, result.Count); // H has results, so H is returned (preferred)
|
||||
}
|
||||
@@ -43,7 +62,10 @@ public class FillWithDirectionPreferenceTests
|
||||
|
||||
var result = FillHelpers.FillWithDirectionPreference(
|
||||
dir => dir == NestDirection.Horizontal ? new List<Part>() : vParts,
|
||||
NestDirection.Horizontal, comparer, workArea);
|
||||
NestDirection.Horizontal,
|
||||
comparer,
|
||||
workArea
|
||||
);
|
||||
|
||||
Assert.Equal(1, result.Count); // Falls back to V
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Engine.Fill;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Tests.Fill;
|
||||
|
||||
@@ -57,7 +57,10 @@ public class FillScoreTests
|
||||
[Fact]
|
||||
public void Compute_EmptyParts_ReturnsDefault()
|
||||
{
|
||||
var score = FillScore.Compute(new System.Collections.Generic.List<Part>(), new Box(0, 0, 100, 100));
|
||||
var score = FillScore.Compute(
|
||||
new System.Collections.Generic.List<Part>(),
|
||||
new Box(0, 0, 100, 100)
|
||||
);
|
||||
|
||||
Assert.Equal(0, score.Count);
|
||||
}
|
||||
@@ -69,7 +72,7 @@ public class FillScoreTests
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(20, 0, 10),
|
||||
TestHelpers.MakePartAt(40, 0, 10)
|
||||
TestHelpers.MakePartAt(40, 0, 10),
|
||||
};
|
||||
var score = FillScore.Compute(parts, new Box(0, 0, 100, 100));
|
||||
|
||||
|
||||
@@ -19,7 +19,12 @@ public class IterativeShrinkFillerTests
|
||||
public void Fill_EmptyItems_ReturnsEmpty()
|
||||
{
|
||||
Func<NestItem, Box, List<Part>> fillFunc = (ni, b) => new List<Part>();
|
||||
var result = IterativeShrinkFiller.Fill(new List<NestItem>(), new Box(0, 0, 100, 100), fillFunc, 1.0);
|
||||
var result = IterativeShrinkFiller.Fill(
|
||||
new List<NestItem>(),
|
||||
new Box(0, 0, 100, 100),
|
||||
fillFunc,
|
||||
1.0
|
||||
);
|
||||
|
||||
Assert.Empty(result.Parts);
|
||||
Assert.Empty(result.Leftovers);
|
||||
@@ -42,7 +47,7 @@ public class IterativeShrinkFillerTests
|
||||
var drawing = MakeRectDrawing(20, 10);
|
||||
var items = new List<NestItem>
|
||||
{
|
||||
new NestItem { Drawing = drawing, Quantity = 5 }
|
||||
new NestItem { Drawing = drawing, Quantity = 5 },
|
||||
};
|
||||
|
||||
Func<NestItem, Box, List<Part>> fillFunc = (ni, b) =>
|
||||
@@ -110,7 +115,7 @@ public class IterativeShrinkFillerTests
|
||||
{
|
||||
var items = new List<NestItem>
|
||||
{
|
||||
new NestItem { Drawing = MakeRectDrawing(20, 10), Quantity = 0 }
|
||||
new NestItem { Drawing = MakeRectDrawing(20, 10), Quantity = 0 },
|
||||
};
|
||||
|
||||
Func<NestItem, Box, List<Part>> fillFunc = (ni, b) =>
|
||||
@@ -134,13 +139,19 @@ public class IterativeShrinkFillerTests
|
||||
|
||||
var items = new List<NestItem>
|
||||
{
|
||||
new NestItem { Drawing = MakeRectDrawing(20, 10), Quantity = 10 }
|
||||
new NestItem { Drawing = MakeRectDrawing(20, 10), Quantity = 10 },
|
||||
};
|
||||
|
||||
Func<NestItem, Box, List<Part>> fillFunc = (ni, b) =>
|
||||
new List<Part> { TestHelpers.MakePartAt(0, 0, 10) };
|
||||
|
||||
var result = IterativeShrinkFiller.Fill(items, new Box(0, 0, 100, 100), fillFunc, 1.0, cts.Token);
|
||||
var result = IterativeShrinkFiller.Fill(
|
||||
items,
|
||||
new Box(0, 0, 100, 100),
|
||||
fillFunc,
|
||||
1.0,
|
||||
cts.Token
|
||||
);
|
||||
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
@@ -47,10 +47,10 @@ public class PairOverlapDiagnosticTests
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)] // 0 degrees
|
||||
[InlineData(90)] // 90 degrees
|
||||
[InlineData(180)] // 180 degrees
|
||||
[InlineData(270)] // 270 degrees
|
||||
[InlineData(0)] // 0 degrees
|
||||
[InlineData(90)] // 90 degrees
|
||||
[InlineData(180)] // 180 degrees
|
||||
[InlineData(270)] // 270 degrees
|
||||
public void PartBoundary_HasEdgesAtAllRotations_RoundedRect(double angleDeg)
|
||||
{
|
||||
var drawing = MakeRoundedRect();
|
||||
@@ -109,8 +109,8 @@ public class PairOverlapDiagnosticTests
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)] // simple rect
|
||||
[InlineData(true)] // rounded rect
|
||||
[InlineData(false)] // simple rect
|
||||
[InlineData(true)] // rounded rect
|
||||
public void FillExtents_NoPairOverlap_At90Degrees(bool rounded)
|
||||
{
|
||||
var drawing = rounded ? MakeRoundedRect() : MakeSimpleRect();
|
||||
@@ -126,8 +126,10 @@ public class PairOverlapDiagnosticTests
|
||||
for (var i = 0; i < parts.Count; i++)
|
||||
{
|
||||
var p = parts[i];
|
||||
_output.WriteLine($" [{i}] rot={Angle.ToDegrees(p.Rotation):F1}° " +
|
||||
$"bbox=({p.BoundingBox.Left:F2},{p.BoundingBox.Bottom:F2})-({p.BoundingBox.Right:F2},{p.BoundingBox.Top:F2})");
|
||||
_output.WriteLine(
|
||||
$" [{i}] rot={Angle.ToDegrees(p.Rotation):F1}° "
|
||||
+ $"bbox=({p.BoundingBox.Left:F2},{p.BoundingBox.Bottom:F2})-({p.BoundingBox.Right:F2},{p.BoundingBox.Top:F2})"
|
||||
);
|
||||
}
|
||||
|
||||
// Check for overlapping bounding boxes
|
||||
@@ -137,15 +139,21 @@ public class PairOverlapDiagnosticTests
|
||||
for (var j = i + 1; j < parts.Count; j++)
|
||||
{
|
||||
var b2 = parts[j].BoundingBox;
|
||||
var overlapX = System.Math.Min(b1.Right, b2.Right) - System.Math.Max(b1.Left, b2.Left);
|
||||
var overlapY = System.Math.Min(b1.Top, b2.Top) - System.Math.Max(b1.Bottom, b2.Bottom);
|
||||
var overlapX =
|
||||
System.Math.Min(b1.Right, b2.Right) - System.Math.Max(b1.Left, b2.Left);
|
||||
var overlapY =
|
||||
System.Math.Min(b1.Top, b2.Top) - System.Math.Max(b1.Bottom, b2.Bottom);
|
||||
|
||||
if (overlapX > 0.01 && overlapY > 0.01)
|
||||
_output.WriteLine($" OVERLAP: [{i}] and [{j}] overlap by ({overlapX:F3}, {overlapY:F3})");
|
||||
_output.WriteLine(
|
||||
$" OVERLAP: [{i}] and [{j}] overlap by ({overlapX:F3}, {overlapY:F3})"
|
||||
);
|
||||
|
||||
Assert.False(overlapX > 0.01 && overlapY > 0.01,
|
||||
$"Parts [{i}] and [{j}] have overlapping bounding boxes " +
|
||||
$"({overlapX:F3} x {overlapY:F3})");
|
||||
Assert.False(
|
||||
overlapX > 0.01 && overlapY > 0.01,
|
||||
$"Parts [{i}] and [{j}] have overlapping bounding boxes "
|
||||
+ $"({overlapX:F3} x {overlapY:F3})"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,8 +180,12 @@ public class PairOverlapDiagnosticTests
|
||||
var b1 = new PartBoundary(part1, partSpacing / 2);
|
||||
var b2 = new PartBoundary(part2, partSpacing / 2);
|
||||
|
||||
_output.WriteLine($"Part1 (90°) boundary edges: L={b1.GetEdges(PushDirection.Left).Length} R={b1.GetEdges(PushDirection.Right).Length}");
|
||||
_output.WriteLine($"Part2 (270°) boundary edges: L={b2.GetEdges(PushDirection.Left).Length} R={b2.GetEdges(PushDirection.Right).Length}");
|
||||
_output.WriteLine(
|
||||
$"Part1 (90°) boundary edges: L={b1.GetEdges(PushDirection.Left).Length} R={b1.GetEdges(PushDirection.Right).Length}"
|
||||
);
|
||||
_output.WriteLine(
|
||||
$"Part2 (270°) boundary edges: L={b2.GetEdges(PushDirection.Left).Length} R={b2.GetEdges(PushDirection.Right).Length}"
|
||||
);
|
||||
|
||||
var movingLines = b2.GetLines(part2.Location, PushDirection.Left);
|
||||
var stationaryLines = b1.GetLines(part1.Location, PushDirection.Right);
|
||||
@@ -189,7 +201,11 @@ public class PairOverlapDiagnosticTests
|
||||
foreach (var l in stationaryLines)
|
||||
_output.WriteLine($" ({l.pt1.X:F4},{l.pt1.Y:F4})->({l.pt2.X:F4},{l.pt2.Y:F4})");
|
||||
|
||||
var slideDist = SpatialQuery.DirectionalDistance(movingLines, stationaryLines, PushDirection.Left);
|
||||
var slideDist = SpatialQuery.DirectionalDistance(
|
||||
movingLines,
|
||||
stationaryLines,
|
||||
PushDirection.Left
|
||||
);
|
||||
_output.WriteLine($"Slide distance: {slideDist:F4}");
|
||||
|
||||
if (slideDist < double.MaxValue && slideDist > 0)
|
||||
@@ -198,8 +214,12 @@ public class PairOverlapDiagnosticTests
|
||||
part2.UpdateBounds();
|
||||
}
|
||||
|
||||
_output.WriteLine($"Part1 bbox: ({part1.BoundingBox.Left:F2},{part1.BoundingBox.Bottom:F2})-({part1.BoundingBox.Right:F2},{part1.BoundingBox.Top:F2})");
|
||||
_output.WriteLine($"Part2 bbox: ({part2.BoundingBox.Left:F2},{part2.BoundingBox.Bottom:F2})-({part2.BoundingBox.Right:F2},{part2.BoundingBox.Top:F2})");
|
||||
_output.WriteLine(
|
||||
$"Part1 bbox: ({part1.BoundingBox.Left:F2},{part1.BoundingBox.Bottom:F2})-({part1.BoundingBox.Right:F2},{part1.BoundingBox.Top:F2})"
|
||||
);
|
||||
_output.WriteLine(
|
||||
$"Part2 bbox: ({part2.BoundingBox.Left:F2},{part2.BoundingBox.Bottom:F2})-({part2.BoundingBox.Right:F2},{part2.BoundingBox.Top:F2})"
|
||||
);
|
||||
|
||||
// Now tile this pair pattern
|
||||
var pattern = new Pattern();
|
||||
@@ -216,8 +236,10 @@ public class PairOverlapDiagnosticTests
|
||||
for (var i = 0; i < parts.Count; i++)
|
||||
{
|
||||
var p = parts[i];
|
||||
_output.WriteLine($" [{i}] rot={Angle.ToDegrees(p.Rotation):F1}° " +
|
||||
$"bbox=({p.BoundingBox.Left:F2},{p.BoundingBox.Bottom:F2})-({p.BoundingBox.Right:F2},{p.BoundingBox.Top:F2})");
|
||||
_output.WriteLine(
|
||||
$" [{i}] rot={Angle.ToDegrees(p.Rotation):F1}° "
|
||||
+ $"bbox=({p.BoundingBox.Left:F2},{p.BoundingBox.Bottom:F2})-({p.BoundingBox.Right:F2},{p.BoundingBox.Top:F2})"
|
||||
);
|
||||
}
|
||||
|
||||
// Check for overlaps
|
||||
@@ -230,8 +252,10 @@ public class PairOverlapDiagnosticTests
|
||||
var ox = System.Math.Min(bi.Right, bj.Right) - System.Math.Max(bi.Left, bj.Left);
|
||||
var oy = System.Math.Min(bi.Top, bj.Top) - System.Math.Max(bi.Bottom, bj.Bottom);
|
||||
|
||||
Assert.False(ox > 0.01 && oy > 0.01,
|
||||
$"Parts [{i}] and [{j}] overlap ({ox:F3} x {oy:F3})");
|
||||
Assert.False(
|
||||
ox > 0.01 && oy > 0.01,
|
||||
$"Parts [{i}] and [{j}] overlap ({ox:F3} x {oy:F3})"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public class RemnantFillerTests2
|
||||
var drawing = MakeSquareDrawing(10);
|
||||
var items = new List<NestItem>
|
||||
{
|
||||
new NestItem { Drawing = drawing, Quantity = 5 }
|
||||
new NestItem { Drawing = drawing, Quantity = 5 },
|
||||
};
|
||||
|
||||
Func<NestItem, Box, List<Part>> fillFunc = (ni, b) =>
|
||||
@@ -52,7 +52,7 @@ public class RemnantFillerTests2
|
||||
var drawing = MakeSquareDrawing(10);
|
||||
var items = new List<NestItem>
|
||||
{
|
||||
new NestItem { Drawing = drawing, Quantity = 3 }
|
||||
new NestItem { Drawing = drawing, Quantity = 3 },
|
||||
};
|
||||
|
||||
Func<NestItem, Box, List<Part>> fillFunc = (ni, b) =>
|
||||
@@ -92,7 +92,7 @@ public class RemnantFillerTests2
|
||||
var drawing = MakeSquareDrawing(10);
|
||||
var items = new List<NestItem>
|
||||
{
|
||||
new NestItem { Drawing = drawing, Quantity = 5 }
|
||||
new NestItem { Drawing = drawing, Quantity = 5 },
|
||||
};
|
||||
|
||||
Func<NestItem, Box, List<Part>> fillFunc = (ni, b) =>
|
||||
|
||||
@@ -108,16 +108,15 @@ public class RemnantFinderTests
|
||||
var remnants = finder.FindRemnants();
|
||||
|
||||
var gap = remnants.FirstOrDefault(r =>
|
||||
r.Length >= 19.9 && r.Length <= 20.1 &&
|
||||
r.Width >= 99.9);
|
||||
r.Length >= 19.9 && r.Length <= 20.1 && r.Width >= 99.9
|
||||
);
|
||||
Assert.NotNull(gap);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromPlate_CreatesFinderWithPartsAsObstacles()
|
||||
{
|
||||
var plate = TestHelpers.MakePlate(60, 120,
|
||||
TestHelpers.MakePartAt(0, 0, 20));
|
||||
var plate = TestHelpers.MakePlate(60, 120, TestHelpers.MakePartAt(0, 0, 20));
|
||||
var finder = RemnantFinder.FromPlate(plate);
|
||||
var remnants = finder.FindRemnants();
|
||||
|
||||
@@ -146,8 +145,8 @@ public class RemnantFinderTests
|
||||
|
||||
// Should find the 80x100 strip on the left
|
||||
var left = remnants.FirstOrDefault(r =>
|
||||
r.Length >= 79.9 && r.Length <= 80.1 &&
|
||||
r.Width >= 99.9);
|
||||
r.Length >= 79.9 && r.Length <= 80.1 && r.Width >= 99.9
|
||||
);
|
||||
Assert.NotNull(left);
|
||||
}
|
||||
|
||||
@@ -163,9 +162,16 @@ public class RemnantFinderTests
|
||||
foreach (var r in remnants)
|
||||
{
|
||||
Assert.False(
|
||||
r.Left < 60 && r.Right > 0 && r.Bottom < 60 && r.Top > 0
|
||||
&& r.Left < 100 && r.Right > 40 && r.Bottom < 100 && r.Top > 40,
|
||||
"Remnant should not overlap both obstacles simultaneously in their shared region");
|
||||
r.Left < 60
|
||||
&& r.Right > 0
|
||||
&& r.Bottom < 60
|
||||
&& r.Top > 0
|
||||
&& r.Left < 100
|
||||
&& r.Right > 40
|
||||
&& r.Bottom < 100
|
||||
&& r.Top > 40,
|
||||
"Remnant should not overlap both obstacles simultaneously in their shared region"
|
||||
);
|
||||
}
|
||||
|
||||
// Total remnant area + obstacle coverage should not exceed work area
|
||||
@@ -177,16 +183,11 @@ public class RemnantFinderTests
|
||||
[Fact]
|
||||
public void ConstructorWithObstaclesList()
|
||||
{
|
||||
var obstacles = new List<Box>
|
||||
{
|
||||
new Box(0, 0, 40, 100),
|
||||
new Box(60, 0, 40, 100)
|
||||
};
|
||||
var obstacles = new List<Box> { new Box(0, 0, 40, 100), new Box(60, 0, 40, 100) };
|
||||
var finder = new RemnantFinder(new Box(0, 0, 100, 100), obstacles);
|
||||
var remnants = finder.FindRemnants();
|
||||
|
||||
var gap = remnants.FirstOrDefault(r =>
|
||||
r.Length >= 19.9 && r.Length <= 20.1);
|
||||
var gap = remnants.FirstOrDefault(r => r.Length >= 19.9 && r.Length <= 20.1);
|
||||
Assert.NotNull(gap);
|
||||
}
|
||||
|
||||
@@ -194,15 +195,10 @@ public class RemnantFinderTests
|
||||
public void AddObstacles_Plural_AddsMultiple()
|
||||
{
|
||||
var finder = new RemnantFinder(new Box(0, 0, 100, 100));
|
||||
finder.AddObstacles(new[]
|
||||
{
|
||||
new Box(0, 0, 40, 100),
|
||||
new Box(60, 0, 40, 100)
|
||||
});
|
||||
finder.AddObstacles(new[] { new Box(0, 0, 40, 100), new Box(60, 0, 40, 100) });
|
||||
var remnants = finder.FindRemnants();
|
||||
|
||||
var gap = remnants.FirstOrDefault(r =>
|
||||
r.Length >= 19.9 && r.Length <= 20.1);
|
||||
var gap = remnants.FirstOrDefault(r => r.Length >= 19.9 && r.Length <= 20.1);
|
||||
Assert.NotNull(gap);
|
||||
}
|
||||
|
||||
@@ -239,11 +235,17 @@ public class RemnantFinderTests
|
||||
{
|
||||
// Check no remnant overlaps obstacle 1
|
||||
var overlaps1 = r.Left < 50 && r.Right > 20 && r.Bottom < 50 && r.Top > 20;
|
||||
Assert.False(overlaps1, $"Remnant ({r.X},{r.Y} {r.Width}x{r.Length}) overlaps obstacle 1");
|
||||
Assert.False(
|
||||
overlaps1,
|
||||
$"Remnant ({r.X},{r.Y} {r.Width}x{r.Length}) overlaps obstacle 1"
|
||||
);
|
||||
|
||||
// Check no remnant overlaps obstacle 2
|
||||
var overlaps2 = r.Left < 85 && r.Right > 60 && r.Bottom < 90 && r.Top > 10;
|
||||
Assert.False(overlaps2, $"Remnant ({r.X},{r.Y} {r.Width}x{r.Length}) overlaps obstacle 2");
|
||||
Assert.False(
|
||||
overlaps2,
|
||||
$"Remnant ({r.X},{r.Y} {r.Width}x{r.Length}) overlaps obstacle 2"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,8 +256,8 @@ public class RemnantFinderTests
|
||||
|
||||
// Place a 5x5 grid of 10x10 obstacles with 10-unit gaps
|
||||
for (var row = 0; row < 5; row++)
|
||||
for (var col = 0; col < 5; col++)
|
||||
finder.AddObstacle(new Box(col * 20, row * 20, 10, 10));
|
||||
for (var col = 0; col < 5; col++)
|
||||
finder.AddObstacle(new Box(col * 20, row * 20, 10, 10));
|
||||
|
||||
var remnants = finder.FindRemnants();
|
||||
|
||||
@@ -304,16 +306,19 @@ public class RemnantFinderTests
|
||||
|
||||
// Use smallest drawing bbox dimension as minDim (same as UI).
|
||||
var minDim = nest.Drawings.Min(d =>
|
||||
System.Math.Min(d.Program.BoundingBox().Width, d.Program.BoundingBox().Length));
|
||||
System.Math.Min(d.Program.BoundingBox().Width, d.Program.BoundingBox().Length)
|
||||
);
|
||||
|
||||
var tiered = finder.FindTieredRemnants(minDim);
|
||||
|
||||
// Should find a remnant near (0.25, 53.13) — the gap above the main grid.
|
||||
var topGap = tiered.FirstOrDefault(t =>
|
||||
t.Box.Bottom > 50 && t.Box.Bottom < 55 &&
|
||||
t.Box.Left < 1 &&
|
||||
t.Box.Length > 100 &&
|
||||
t.Box.Width > 5);
|
||||
t.Box.Bottom > 50
|
||||
&& t.Box.Bottom < 55
|
||||
&& t.Box.Left < 1
|
||||
&& t.Box.Length > 100
|
||||
&& t.Box.Width > 5
|
||||
);
|
||||
|
||||
Assert.True(topGap.Box.Length > 0, "Expected remnant above main grid");
|
||||
}
|
||||
@@ -337,24 +342,44 @@ public class RemnantFinderTests
|
||||
double[] oddY = { 0.75, 9.48, 18.21, 26.94, 35.67, 44.40 };
|
||||
|
||||
foreach (var cx in colX)
|
||||
foreach (var ey in evenY)
|
||||
obstacles.Add(new Box(cx - spacing, ey - spacing, 20.65 + spacing * 2, 5.56 + spacing * 2));
|
||||
foreach (var ey in evenY)
|
||||
obstacles.Add(
|
||||
new Box(cx - spacing, ey - spacing, 20.65 + spacing * 2, 5.56 + spacing * 2)
|
||||
);
|
||||
foreach (var cx in colXOdd)
|
||||
foreach (var oy in oddY)
|
||||
obstacles.Add(new Box(cx - spacing, oy - spacing, 20.65 + spacing * 2, 5.56 + spacing * 2));
|
||||
foreach (var oy in oddY)
|
||||
obstacles.Add(
|
||||
new Box(cx - spacing, oy - spacing, 20.65 + spacing * 2, 5.56 + spacing * 2)
|
||||
);
|
||||
|
||||
// Right-side rotated parts (only 2 extend high: parts 62 and 66).
|
||||
obstacles.Add(new Box(106.70 - spacing, 37.59 - spacing, 5.56 + spacing * 2, 20.65 + spacing * 2));
|
||||
obstacles.Add(new Box(114.19 - spacing, 37.59 - spacing, 5.56 + spacing * 2, 20.65 + spacing * 2));
|
||||
obstacles.Add(
|
||||
new Box(106.70 - spacing, 37.59 - spacing, 5.56 + spacing * 2, 20.65 + spacing * 2)
|
||||
);
|
||||
obstacles.Add(
|
||||
new Box(114.19 - spacing, 37.59 - spacing, 5.56 + spacing * 2, 20.65 + spacing * 2)
|
||||
);
|
||||
// Parts 63, 67 (lower rotated)
|
||||
obstacles.Add(new Box(105.02 - spacing, 29.35 - spacing, 5.56 + spacing * 2, 20.65 + spacing * 2));
|
||||
obstacles.Add(new Box(112.51 - spacing, 29.35 - spacing, 5.56 + spacing * 2, 20.65 + spacing * 2));
|
||||
obstacles.Add(
|
||||
new Box(105.02 - spacing, 29.35 - spacing, 5.56 + spacing * 2, 20.65 + spacing * 2)
|
||||
);
|
||||
obstacles.Add(
|
||||
new Box(112.51 - spacing, 29.35 - spacing, 5.56 + spacing * 2, 20.65 + spacing * 2)
|
||||
);
|
||||
// Parts 60, 64 (upper-right rotated, lower)
|
||||
obstacles.Add(new Box(106.70 - spacing, 8.99 - spacing, 5.56 + spacing * 2, 20.65 + spacing * 2));
|
||||
obstacles.Add(new Box(114.19 - spacing, 8.99 - spacing, 5.56 + spacing * 2, 20.65 + spacing * 2));
|
||||
obstacles.Add(
|
||||
new Box(106.70 - spacing, 8.99 - spacing, 5.56 + spacing * 2, 20.65 + spacing * 2)
|
||||
);
|
||||
obstacles.Add(
|
||||
new Box(114.19 - spacing, 8.99 - spacing, 5.56 + spacing * 2, 20.65 + spacing * 2)
|
||||
);
|
||||
// Parts 61, 65
|
||||
obstacles.Add(new Box(105.02 - spacing, 0.75 - spacing, 5.56 + spacing * 2, 20.65 + spacing * 2));
|
||||
obstacles.Add(new Box(112.51 - spacing, 0.75 - spacing, 5.56 + spacing * 2, 20.65 + spacing * 2));
|
||||
obstacles.Add(
|
||||
new Box(105.02 - spacing, 0.75 - spacing, 5.56 + spacing * 2, 20.65 + spacing * 2)
|
||||
);
|
||||
obstacles.Add(
|
||||
new Box(112.51 - spacing, 0.75 - spacing, 5.56 + spacing * 2, 20.65 + spacing * 2)
|
||||
);
|
||||
|
||||
var finder = new RemnantFinder(workArea, obstacles);
|
||||
var remnants = finder.FindRemnants(5.375);
|
||||
|
||||
@@ -72,8 +72,14 @@ public class ShrinkFillerTests
|
||||
Func<NestItem, Box, List<Part>> fillFunc = (ni, b) =>
|
||||
new List<Part> { TestHelpers.MakePartAt(0, 0, 10) };
|
||||
|
||||
var result = ShrinkFiller.Shrink(fillFunc, item, box, 1.0,
|
||||
ShrinkAxis.Length, token: cts.Token);
|
||||
var result = ShrinkFiller.Shrink(
|
||||
fillFunc,
|
||||
item,
|
||||
box,
|
||||
1.0,
|
||||
ShrinkAxis.Length,
|
||||
token: cts.Token
|
||||
);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.Parts.Count > 0);
|
||||
@@ -84,10 +90,10 @@ public class ShrinkFillerTests
|
||||
{
|
||||
var parts = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 5), // Right = 5
|
||||
TestHelpers.MakePartAt(10, 0, 5), // Right = 15
|
||||
TestHelpers.MakePartAt(20, 0, 5), // Right = 25
|
||||
TestHelpers.MakePartAt(30, 0, 5), // Right = 35
|
||||
TestHelpers.MakePartAt(0, 0, 5), // Right = 5
|
||||
TestHelpers.MakePartAt(10, 0, 5), // Right = 15
|
||||
TestHelpers.MakePartAt(20, 0, 5), // Right = 25
|
||||
TestHelpers.MakePartAt(30, 0, 5), // Right = 35
|
||||
};
|
||||
|
||||
var trimmed = ShrinkFiller.TrimToCount(parts, 2, ShrinkAxis.Width);
|
||||
@@ -101,10 +107,10 @@ public class ShrinkFillerTests
|
||||
{
|
||||
var parts = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 5), // Top = 5
|
||||
TestHelpers.MakePartAt(0, 10, 5), // Top = 15
|
||||
TestHelpers.MakePartAt(0, 20, 5), // Top = 25
|
||||
TestHelpers.MakePartAt(0, 30, 5), // Top = 35
|
||||
TestHelpers.MakePartAt(0, 0, 5), // Top = 5
|
||||
TestHelpers.MakePartAt(0, 10, 5), // Top = 15
|
||||
TestHelpers.MakePartAt(0, 20, 5), // Top = 25
|
||||
TestHelpers.MakePartAt(0, 30, 5), // Top = 35
|
||||
};
|
||||
|
||||
var trimmed = ShrinkFiller.TrimToCount(parts, 2, ShrinkAxis.Length);
|
||||
|
||||
Reference in New Issue
Block a user