feat(rectangles): avoid clamp zones in sheet packing

Block each clamp's keep-out into the max-rects free list before any
packing: same split logic as placements, not a used item (no contact
score, no material). The blocked rectangle accounts for the inflated
item model exactly - material keeps the bottom-edge clearance from the
clamp without paying an extra part-spacing tax; degenerate spans
collapse to the zone centerline (conservative, never permissive).

Zero-clamp sheets skip blocking entirely: three fixture layout hashes
(rectangle grid, mixed shapes, multi-sheet overflow), captured from the
unchanged packer at a3f1124, stay bit-identical (C2). With clamps:
placements never intrude, demand lands between two clamps when it fits,
a full-width front rail makes an otherwise feasible job Incomplete, and
a far-away zone leaves the layout hash equal to no-clamps.

Rectangles lane only; Irregular parity is Slice 2.
This commit is contained in:
aj committed 2026-10-13 18:15:49 -04:00
1 parent dc87911b2f
commit de9a087027
4 files changed
+211 -3

No files matched your search

@@ -91,6 +91,20 @@ internal sealed class MaxRectsSheet
/// <summary>Commits an item and splits every free rectangle it intersects.</summary>
public void Place(Rect item)
{
Split(item);
used.Add(item);
}
/// <summary>
/// Carves a keep-out rectangle (a machine clamp's inflated exclusion, for example) out
/// of the free space before any packing: same split logic as placements, but not a used
/// item - it consumes no material and contributes nothing to contact scoring. The
/// rectangle may extend past the bin; only interior intersections count.
/// </summary>
public void Block(Rect keepOut) => Split(keepOut);
private void Split(Rect item)
{
var next = new List<Rect>(free.Count + 8);
foreach (var f in free)
@@ -111,7 +125,6 @@ internal sealed class MaxRectsSheet
}
free.Clear();
free.AddRange(Prune(next));
used.Add(item);
}
private static List<Rect> Prune(List<Rect> rects)