mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-14 18:21:55 -04:00
fix(engine): treat a part as clamp-blocked only when no lane can place it
Last-resort removal treated the selected engine's failure to place a lone part as proof it could not fit with the clamps in place. Rectangles packs bounding boxes, so a concave part whose material clears a clamp lost that clamp for nothing (C10). A part now counts as blocked only when the caller's engine, Irregular (true material, NFP) and Rectangles all fail on every offered stock; Default already runs both lanes. PM c5984c6e-3882-4d15-8a42-8de76b4c2080
This commit is contained in:
1 parent
af50da7609
commit
f19a825c4e
2 files changed
+57
-2
No files matched your search
@@ -1,4 +1,5 @@
|
||||
using OpenNest.Engine.Jobs;
|
||||
using OpenNest.Engine.Jobs.Adapters;
|
||||
using OpenNest.Engine.NestingEngines.Rectangles;
|
||||
using OpenNest.Engine.Tests.NestingEngines;
|
||||
using OpenNest.Geometry;
|
||||
@@ -144,4 +145,39 @@ public class ClampRemovalRepairTests
|
||||
Assert.DoesNotContain(sheet.Stock.ClampZones, z => z.Id == "clamp-1");
|
||||
Assert.Contains(sheet.Stock.ClampZones, z => z.Id == "clamp-2");
|
||||
}
|
||||
// PM c5984c6e (C10): one lane's failure to place is not proof the part cannot fit. The
|
||||
// fixed-rotation L below fits at the origin with its notch over the clamp (an explicit
|
||||
// witness passes the layout check), but Rectangles packs its 10 x 10 bounding box and
|
||||
// cannot place it; no clamp may be removed for it.
|
||||
[Fact]
|
||||
public void PartThatFitsClampedInSomeLaneNeverRemovesAClamp()
|
||||
{
|
||||
var program = new OpenNest.CNC.Program();
|
||||
program.MoveTo(0, 0);
|
||||
program.LineTo(2, 0);
|
||||
program.LineTo(2, 8);
|
||||
program.LineTo(10, 8);
|
||||
program.LineTo(10, 10);
|
||||
program.LineTo(0, 10);
|
||||
program.LineTo(0, 0);
|
||||
var rail = new ClampRail(1.5, 0.625, new[] { 5.75 }); // clamp-1 spans X [5, 6.5]
|
||||
var size = new Size(10, 10);
|
||||
var stock = new NestPlateStock("sheet", size, null, 0, default, 1, null, rail.ResolveFor(size, 1))
|
||||
{ ClampRail = rail };
|
||||
var drawing = new Drawing("notched", program);
|
||||
|
||||
var witnessJob = new NestJob(new[] { DrawingJobMapper.FromItem("p", new NestItem { Drawing = drawing, Quantity = 1 }) },
|
||||
new[] { stock });
|
||||
var witness = new NestJobResult(NestJobStatus.Complete, NestJobStopReason.Completed,
|
||||
new[] { new NestJobPlateResult(0, stock, new[] { new NestJobPlacement("p", 0, 0, 0, 0) }) },
|
||||
Array.Empty<PartFulfillment>(), Array.Empty<StockUsage>());
|
||||
Assert.Empty(NestLayoutCheck.Violations(witnessJob, witness)); // fits with the clamp in place
|
||||
|
||||
var result = NestPipeline.Run(new NestPipelineRequest("Rectangles",
|
||||
new[] { new NestItem { Drawing = drawing, Quantity = 1 } },
|
||||
new[] { stock }));
|
||||
|
||||
Assert.All(result.Job.Plates, s => Assert.DoesNotContain("removed", s.Id));
|
||||
Assert.All(result.Raw.Plates, sheet => Assert.Single(sheet.Stock.ClampZones));
|
||||
}
|
||||
}
|
||||
@@ -69,9 +69,11 @@ internal static class ClampRemovalRecovery
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
// C10: only a part that fits nowhere with every clamp in place is "blocked".
|
||||
// C10: only a part that fits nowhere with every clamp in place is "blocked". One
|
||||
// lane's failure is not proof: a part any built-in lane can place clamped is not.
|
||||
var lanes = FeasibilityLanes(engine);
|
||||
var blocked = remaining
|
||||
.Where(x => !FitsAnywhere(x.Part, availableStock, job.Options, engine, token))
|
||||
.Where(x => !lanes.Any(lane => FitsAnywhere(x.Part, availableStock, job.Options, lane, token)))
|
||||
.Select(x => x.Part)
|
||||
.ToList();
|
||||
if (blocked.Count == 0)
|
||||
@@ -131,6 +133,23 @@ internal static class ClampRemovalRecovery
|
||||
return merged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Engines whose placement of a lone part proves it fits (C10): the caller's own engine,
|
||||
/// plus the built-in Irregular (NFP, true material) and Rectangles lanes. Default already
|
||||
/// runs both, so it is enough on its own.
|
||||
/// </summary>
|
||||
private static IReadOnlyList<INestingEngine> FeasibilityLanes(INestingEngine engine)
|
||||
{
|
||||
if (engine is NestingEngines.Default.DefaultNestingEngine)
|
||||
return new[] { engine };
|
||||
var lanes = new List<INestingEngine> { engine };
|
||||
if (engine is not NestingEngines.Irregular.IrregularNestingEngine)
|
||||
lanes.Add(new NestingEngines.Irregular.IrregularNestingEngine());
|
||||
if (engine is not NestingEngines.Rectangles.RectanglesNestingEngine)
|
||||
lanes.Add(new NestingEngines.Rectangles.RectanglesNestingEngine());
|
||||
return lanes;
|
||||
}
|
||||
|
||||
/// <summary>Same stock, quantity reduced by what phase 1 already used (unlimited stays
|
||||
/// unlimited); id/zones unchanged. Used for stock phase 2 may still draw from.</summary>
|
||||
private static NestPlateStock ResidualCopy(NestPlateStock stock, IReadOnlyDictionary<string, int> usedByStock) =>
|
||||
|
||||
Reference in new issue
Block a user