fix(engine): use required spacing, not a sampled gap, when resequencing shrink-fill strips
SortStrips measured the gap between only the first two strips in original placement order and replayed that single value between every strip after reordering by height/width. Real (non-uniform) geometry produces varying inter-strip gaps, so resequencing could expand the total footprint beyond the plate's already-fitted work area, crashing StripPlateNester with "Candidate placement falls outside the usable stock area." Using the actual required spacing guarantees the resequenced span never exceeds the original, since real gaps are always >= spacing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
using OpenNest.Engine.Fill;
|
||||||
|
using OpenNest.Geometry;
|
||||||
|
|
||||||
|
namespace OpenNest.Engine.Tests.Fill;
|
||||||
|
|
||||||
|
public class SortStripsTests
|
||||||
|
{
|
||||||
|
private static Part MakeRectPart(double x, double y, double w, double h)
|
||||||
|
{
|
||||||
|
var pgm = new OpenNest.CNC.Program();
|
||||||
|
pgm.Codes.Add(new OpenNest.CNC.RapidMove(new Vector(0, 0)));
|
||||||
|
pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(w, 0)));
|
||||||
|
pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(w, h)));
|
||||||
|
pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(0, h)));
|
||||||
|
pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(0, 0)));
|
||||||
|
var drawing = new Drawing("rect", pgm);
|
||||||
|
return new Part(drawing, new Vector(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SortColumnsByHeight_NonUniformGaps_DoesNotExceedOriginalSpan()
|
||||||
|
{
|
||||||
|
// Three columns with non-uniform gaps between them (5, then 1) and heights
|
||||||
|
// ordered so the sort-by-height pass must reorder them (tallest first, then
|
||||||
|
// shortest, then medium). The tallest column's original position leaves a
|
||||||
|
// 5-unit gap to its neighbor; that single sampled gap must not get replayed
|
||||||
|
// as the spacing for the whole staircase once it's no longer the leading pair.
|
||||||
|
var tall = MakeRectPart(0, 0, 10, 30); // Left 0-10, gap of 5 to next
|
||||||
|
var shortCol = MakeRectPart(15, 0, 5, 5); // Left 15-20, gap of 1 to next
|
||||||
|
var medium = MakeRectPart(21, 0, 20, 15); // Left 21-41
|
||||||
|
|
||||||
|
var originalRight = new[] { tall, shortCol, medium }.Max(p => p.BoundingBox.Right);
|
||||||
|
var originalLeft = new[] { tall, shortCol, medium }.Min(p => p.BoundingBox.Left);
|
||||||
|
var originalSpan = originalRight - originalLeft;
|
||||||
|
|
||||||
|
var parts = new List<Part> { tall, shortCol, medium };
|
||||||
|
IterativeShrinkFiller.SortColumnsByHeight(parts, spacing: 1.0);
|
||||||
|
|
||||||
|
var newRight = parts.Max(p => p.BoundingBox.Right);
|
||||||
|
var newLeft = parts.Min(p => p.BoundingBox.Left);
|
||||||
|
var newSpan = newRight - newLeft;
|
||||||
|
|
||||||
|
Assert.True(newSpan <= originalSpan + 1e-9,
|
||||||
|
$"Resequenced columns must not exceed the original footprint: original span {originalSpan}, new span {newSpan}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -219,8 +219,10 @@ namespace OpenNest.Engine.Fill
|
|||||||
if (strips.Count <= 1)
|
if (strips.Count <= 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var gap = stripMin(strips[1]) - stripMax(strips[0]);
|
// Use the required clearance as the inter-strip gap, not a gap sampled from one
|
||||||
|
// original pair: actual placement gaps vary for irregular/mixed-size geometry, and
|
||||||
|
// replaying a larger sampled gap across every reordered pair can push the trailing
|
||||||
|
// strip past the original (already plate-fitted) footprint.
|
||||||
strips.Sort((a, b) => sortMetric(a).CompareTo(sortMetric(b)));
|
strips.Sort((a, b) => sortMetric(a).CompareTo(sortMetric(b)));
|
||||||
|
|
||||||
var pos = primaryEdge(parts[0].BoundingBox);
|
var pos = primaryEdge(parts[0].BoundingBox);
|
||||||
@@ -236,7 +238,7 @@ namespace OpenNest.Engine.Fill
|
|||||||
part.Offset(offset);
|
part.Offset(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
pos = stripMax(s) + gap;
|
pos = stripMax(s) + spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
parts.Clear();
|
parts.Clear();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<InternalsVisibleTo Include="OpenNest.Tests" />
|
<InternalsVisibleTo Include="OpenNest.Tests" />
|
||||||
|
<InternalsVisibleTo Include="OpenNest.Engine.Tests" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\OpenNest.Core\OpenNest.Core.csproj" />
|
<ProjectReference Include="..\OpenNest.Core\OpenNest.Core.csproj" />
|
||||||
|
|||||||
Reference in New Issue
Block a user