fix(engine): remove only rail clamps and always name them on the sheet

Last-resort removal ran on any clamped stock, but only a rail-following
plate can record which clamp to pull and its removal time: an explicit-zone
target lost the instruction entirely. NestPlateStock now carries the rail its
zones were resolved from (NestStockBuilder, DrawingJobMapper.FromPlate);
recovery removes clamps only from rail-backed stock, and commit falls back to
the stock's rail when the target plate has none. Runs are now ordered by
distance from the origin along the edge, not by zone list order.

PM 346d46ed-9336-4706-9392-52ed14d6bd33; PM 6ded8ac4 item (d)
This commit is contained in:
aj committed 2026-10-13 18:15:49 -04:00
1 parent 3bf63b74f9
commit af50da7609
7 files changed
+101 -13

No files matched your search

@@ -58,7 +58,8 @@ public static class DrawingJobMapper
plate.Quadrant,
cost: null,
clampZones: new List<ClampZone>(plate.ClampZones)
);
)
{ ClampRail = plate.ClampRail };
}
public static Program ToProgram(PartGeometrySnapshot geometry)
+17 -6
View File
@@ -56,7 +56,7 @@ internal static class ClampRemovalRecovery
return null;
var availableStock = job.Plates.Where(s => s.Quantity != 0).ToList();
if (!availableStock.Any(s => s.ClampZones.Count > 0))
if (!availableStock.Any(CanRemoveClamps))
return null;
var partsById = job.Parts.ToDictionary(p => p.Id, StringComparer.Ordinal);
@@ -153,7 +153,7 @@ internal static class ClampRemovalRecovery
Removal best = null;
foreach (var stock in stocks)
{
if (stock.ClampZones.Count == 0)
if (!CanRemoveClamps(stock))
continue;
token.ThrowIfCancellationRequested();
var found = MinimalRemovalForStock(part, stock, options, engine, token);
@@ -165,10 +165,16 @@ internal static class ClampRemovalRecovery
return best;
}
/// <summary>Only clamps resolved from a machine rail can be removed (see
/// <see cref="NestPlateStock.ClampRail"/>): explicit zones have no clamp to name.</summary>
private static bool CanRemoveClamps(NestPlateStock stock) =>
stock.ClampRail != null && stock.ClampZones.Count > 0;
/// <summary>
/// Ascending run size, then ascending start index: the first contiguous run of the
/// stock's own clamp zones (already ordered by rail position) whose removal lets the
/// part fit alone is both the fewest clamps and, among ties, the run nearest the origin.
/// stock's own clamp zones, ordered by distance from the origin along the edge, whose
/// removal lets the part fit alone is both the fewest clamps and, among ties, the run
/// nearest the origin.
/// </summary>
private static Removal MinimalRemovalForStock(
NestJobPart part,
@@ -178,7 +184,11 @@ internal static class ClampRemovalRecovery
CancellationToken token
)
{
var zones = stock.ClampZones;
// Placement-frame X is signed from the origin, so |centre X| is the distance along
// the edge in every quadrant.
var zones = stock.ClampZones
.OrderBy(z => System.Math.Abs(z.Bounds.X + z.Bounds.Length / 2))
.ToList();
for (var run = 1; run <= zones.Count; run++)
{
for (var start = 0; start + run <= zones.Count; start++)
@@ -263,7 +273,8 @@ internal static class ClampRemovalRecovery
stock.Quadrant,
stock.Cost,
plateFrameZones
);
)
{ ClampRail = stock.ClampRail };
}
private static string Suffixed(string stockId, IReadOnlyList<string> removedIds) =>
+3 -1
View File
@@ -71,7 +71,9 @@ public static class NestPipelineCommit
if (SameInPlacementFrame(plate.ClampZones, stock.ClampZones, left, bottom))
return;
var rail = plate.ClampRail;
// A plate without a rail still takes the rail its stock was resolved from, so a
// last-resort removal on that stock is never committed without naming the clamp.
var rail = plate.ClampRail ?? stock.ClampRail;
plate.SetClampRail(null); // explicit zones from here on
foreach (var zone in stock.ClampZones)
plate.ClampZones.Add(new ClampZone(zone.Id, zone.Bounds.Translate(-left, -bottom)));
+8
View File
@@ -81,6 +81,14 @@ public sealed class NestPlateStock
/// </summary>
internal IReadOnlyList<ClampZone> SheetClampZones { get; }
/// <summary>
/// The machine clamp rail <see cref="ClampZones"/> were resolved from, when the stock was
/// built from a rail-following plate; null for explicit zones. Only rail-backed clamps may
/// be taken off as a last resort (D8): the rail is what lets a committed sheet name the
/// clamps to pull and carry their removal time, so explicit zones are never removed.
/// </summary>
internal ClampRail ClampRail { get; init; }
/// <summary>Generic per-physical-sheet cost; null selects area scoring.</summary>
public double? Cost { get; }
public string Id { get; }
+2 -1
View File
@@ -33,7 +33,8 @@ public static class NestStockBuilder
template.Quadrant,
option.Cost == 0 ? null : option.Cost,
ClampZonesFor(template, size)
));
)
{ ClampRail = template.ClampRail });
}
NestJobValidator.ValidateStockCosts(stock);
return stock.AsReadOnly();