fix(engine): don't drop the topmost part when no other drawing is waiting
RemnantFiller removes the topmost placed part to keep a clean rectangular obstacle for the next drawing, but the envelope then walls that slot off, so the part was lost for nothing (4 squares on a 9x9 plate became 3). Only remove it while another drawing still has demand. Fixes the mixed-stock NestRunner tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -114,7 +114,10 @@ namespace OpenNest.Engine.Fill
|
|||||||
// rectangular obstacle boundary. Without this, gaps between
|
// rectangular obstacle boundary. Without this, gaps between
|
||||||
// individual bounding boxes cause the next drawing to fill
|
// individual bounding boxes cause the next drawing to fill
|
||||||
// into inter-row spaces, producing an interleaved layout.
|
// into inter-row spaces, producing an interleaved layout.
|
||||||
if (placed.Count > 2)
|
// Only worthwhile while another drawing is still waiting for
|
||||||
|
// space; otherwise the removed part's slot is walled off by the
|
||||||
|
// envelope below and the part is lost for nothing.
|
||||||
|
if (placed.Count > 2 && HasOtherDemand(items, item, localQty))
|
||||||
RemoveTopmostPart(placed);
|
RemoveTopmostPart(placed);
|
||||||
|
|
||||||
allParts.AddRange(placed);
|
allParts.AddRange(placed);
|
||||||
@@ -132,6 +135,23 @@ namespace OpenNest.Engine.Fill
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool HasOtherDemand(
|
||||||
|
List<NestItem> items,
|
||||||
|
NestItem current,
|
||||||
|
Dictionary<Drawing, int> localQty
|
||||||
|
)
|
||||||
|
{
|
||||||
|
foreach (var other in items)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(other.Drawing, current.Drawing))
|
||||||
|
continue;
|
||||||
|
if (localQty[other.Drawing] > 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static void RemoveTopmostPart(List<Part> parts)
|
private static void RemoveTopmostPart(List<Part> parts)
|
||||||
{
|
{
|
||||||
var topIdx = 0;
|
var topIdx = 0;
|
||||||
|
|||||||
@@ -103,4 +103,30 @@ public class RemnantFillerTests2
|
|||||||
// Should not throw, returns whatever was placed
|
// Should not throw, returns whatever was placed
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FillItems_SingleDrawing_KeepsEveryPlacedPart()
|
||||||
|
{
|
||||||
|
// With no other drawing waiting for space there is nothing to keep clear, so a full
|
||||||
|
// grid fill must not lose its topmost part.
|
||||||
|
var workArea = new Box(0, 0, 100, 100);
|
||||||
|
var filler = new RemnantFiller(workArea, 0);
|
||||||
|
var items = new List<NestItem>
|
||||||
|
{
|
||||||
|
new NestItem { Drawing = MakeSquareDrawing(10), Quantity = 4 },
|
||||||
|
};
|
||||||
|
|
||||||
|
Func<NestItem, Box, List<Part>> fillFunc = (ni, b) =>
|
||||||
|
new List<Part>
|
||||||
|
{
|
||||||
|
TestHelpers.MakePartAt(0, 0, 10),
|
||||||
|
TestHelpers.MakePartAt(10, 0, 10),
|
||||||
|
TestHelpers.MakePartAt(0, 10, 10),
|
||||||
|
TestHelpers.MakePartAt(10, 10, 10),
|
||||||
|
};
|
||||||
|
|
||||||
|
var placed = filler.FillItems(items, fillFunc);
|
||||||
|
|
||||||
|
Assert.Equal(4, placed.Count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user