mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-22 15:21:37 -04:00
fix(engine): keep solved clamp zones on committed plates
ApplyToEmptyPlates copied size, spacing, quadrant and quantity from the solved stock but not its clamp zones, so a sheet the commit created had no keep-outs although its parts were validated against them, and a reused plate could keep zones the layout was not solved with. Map the stock's placement-frame zones back to plate coordinates on every committed plate; zones already equal in the placement frame are left untouched so an unchanged plate raises no change events. Project Memory: bcc2dd2a-2ab6-4a2a-9c13-197e0aeb24bc
This commit is contained in:
1 parent
fbca54637b
commit
040776807a
2 files changed
+150
No files matched your search
@@ -0,0 +1,110 @@
|
||||
using OpenNest.Engine.Jobs;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Engine.Tests.Jobs;
|
||||
|
||||
/// <summary>
|
||||
/// A committed live plate must carry exactly the clamp keep-outs its layout was solved and
|
||||
/// validated against, in plate coordinates, including sheets the commit had to create.
|
||||
/// </summary>
|
||||
public class ClampZoneCommitTests
|
||||
{
|
||||
// A 1.5 x 0.625 clamp on the front edge of a 48 x 96 sheet (Length 96 runs along X).
|
||||
private const double ZoneX = 5.25;
|
||||
|
||||
private static ClampZone Zone() => new("clamp-1", new Box(ZoneX, 0, 1.5, 0.625));
|
||||
|
||||
/// <summary>Two different one-part sheets so repeat grouping cannot merge them.</summary>
|
||||
private sealed class TwoSheetStub : INestingEngine
|
||||
{
|
||||
public NestJobResult Solve(NestJob job, IProgress<NestJobProgress>? progress = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var stock = job.Plates[0];
|
||||
var work = stock.WorkArea;
|
||||
var builder = new NestJobResultBuilder(job);
|
||||
builder.AddSheet(stock, new[] { (job.Parts[0].Id, work.Left + 20, work.Bottom + 10, 0.0) });
|
||||
builder.AddSheet(stock, new[] { (job.Parts[0].Id, work.Left + 40, work.Bottom + 20, 0.0) });
|
||||
return builder.Build(NestJobStopReason.Completed);
|
||||
}
|
||||
}
|
||||
|
||||
private static NestPipelineResult Result(Drawing drawing, int quadrant, params ClampZone[] zones)
|
||||
{
|
||||
var result = NestPipeline.Run(new TwoSheetStub(), "stub", new NestPipelineRequest("stub",
|
||||
new[] { new NestItem { Drawing = drawing, Quantity = 2 } },
|
||||
new[]
|
||||
{
|
||||
new NestPlateStock("sheet", new Size(48, 96), null, 0.25, new Spacing(1, 1, 1, 1),
|
||||
quadrant, cost: null, clampZones: zones),
|
||||
}));
|
||||
Assert.True(result.IsValid, string.Join("; ", result.Violations));
|
||||
Assert.Equal(2, result.Plates.Count);
|
||||
return result;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
[InlineData(3)]
|
||||
[InlineData(4)]
|
||||
public void EveryCommittedPlateCarriesTheSolvedZonesInPlateCoordinates(int quadrant)
|
||||
{
|
||||
var drawing = new Drawing("part", TestDrawingFactory.Rectangle());
|
||||
var nest = new Nest();
|
||||
nest.Drawings.Add(drawing);
|
||||
nest.CreatePlate(); // empty template without zones; the second sheet is created by commit
|
||||
using var manager = new PlateManager(nest);
|
||||
|
||||
var applied = NestPipelineCommit.ApplyToEmptyPlates(Result(drawing, quadrant, Zone()), manager);
|
||||
|
||||
Assert.Equal(2, applied.Count);
|
||||
Assert.All(applied, plate =>
|
||||
{
|
||||
var zone = Assert.Single(plate.ClampZones);
|
||||
Assert.Equal("clamp-1", zone.Id);
|
||||
// Exact bits: the inverse of the stock's placement-frame translation.
|
||||
Assert.Equal(ZoneX, zone.Bounds.X);
|
||||
Assert.Equal(0, zone.Bounds.Y);
|
||||
Assert.Equal(1.5, zone.Bounds.Length);
|
||||
Assert.Equal(0.625, zone.Bounds.Width);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchingPlateZonesAreKeptWithoutChangeEvents()
|
||||
{
|
||||
var drawing = new Drawing("part", TestDrawingFactory.Rectangle());
|
||||
var nest = new Nest();
|
||||
nest.Drawings.Add(drawing);
|
||||
var template = nest.CreatePlate();
|
||||
template.Quadrant = 3;
|
||||
template.Size = new Size(48, 96);
|
||||
var original = Zone();
|
||||
template.ClampZones.Add(original);
|
||||
var changes = 0;
|
||||
template.ClampZonesChanged += (_, _) => changes++;
|
||||
using var manager = new PlateManager(nest);
|
||||
|
||||
var applied = NestPipelineCommit.ApplyToEmptyPlates(Result(drawing, 3, Zone()), manager);
|
||||
|
||||
Assert.Same(template, applied[0]);
|
||||
Assert.Same(original, Assert.Single(template.ClampZones));
|
||||
Assert.Equal(0, changes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZonesTheLayoutWasNotSolvedWithAreRemoved()
|
||||
{
|
||||
var drawing = new Drawing("part", TestDrawingFactory.Rectangle());
|
||||
var nest = new Nest();
|
||||
nest.Drawings.Add(drawing);
|
||||
var template = nest.CreatePlate();
|
||||
template.ClampZones.Add(Zone());
|
||||
using var manager = new PlateManager(nest);
|
||||
|
||||
var applied = NestPipelineCommit.ApplyToEmptyPlates(Result(drawing, 1), manager);
|
||||
|
||||
Assert.All(applied, plate => Assert.Empty(plate.ClampZones));
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ public static class NestPipelineCommit
|
||||
plate.EdgeSpacing = proposed.Stock.EdgeSpacing;
|
||||
plate.Quadrant = proposed.Stock.Quadrant;
|
||||
plate.Quantity = proposed.Quantity;
|
||||
SyncClampZones(plate, proposed.Stock);
|
||||
plate.Parts.AddRange(proposed.Parts);
|
||||
applied.Add(plate);
|
||||
}
|
||||
@@ -52,4 +53,43 @@ public static class NestPipelineCommit
|
||||
}
|
||||
return applied.AsReadOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The live plate keeps exactly the clamp keep-outs its layout was solved and validated
|
||||
/// against, including sheets created for this commit. Stock zones live in the placement
|
||||
/// frame; plate zones in plate coordinates, so the stock's translation is inverted. Zones
|
||||
/// already equal in the placement frame are left untouched (no change events).
|
||||
/// </summary>
|
||||
private static void SyncClampZones(Plate plate, NestPlateStock stock)
|
||||
{
|
||||
var left = stock.Quadrant is 1 or 4 ? 0 : -stock.Size.Length;
|
||||
var bottom = stock.Quadrant is 1 or 2 ? 0 : -stock.Size.Width;
|
||||
if (SameInPlacementFrame(plate.ClampZones, stock.ClampZones, left, bottom))
|
||||
return;
|
||||
|
||||
plate.ClampZones.Clear();
|
||||
foreach (var zone in stock.ClampZones)
|
||||
plate.ClampZones.Add(new ClampZone(zone.Id, zone.Bounds.Translate(-left, -bottom)));
|
||||
}
|
||||
|
||||
private static bool SameInPlacementFrame(
|
||||
IList<ClampZone> plateZones,
|
||||
IReadOnlyList<ClampZone> stockZones,
|
||||
double left,
|
||||
double bottom)
|
||||
{
|
||||
if (plateZones.Count != stockZones.Count)
|
||||
return false;
|
||||
for (var i = 0; i < plateZones.Count; i++)
|
||||
{
|
||||
// Same arithmetic NestPlateStock uses to carry plate zones into the placement frame.
|
||||
var placed = plateZones[i].Bounds.Translate(left, bottom);
|
||||
var solved = stockZones[i].Bounds;
|
||||
if (!string.Equals(plateZones[i].Id, stockZones[i].Id, StringComparison.Ordinal)
|
||||
|| placed.X != solved.X || placed.Y != solved.Y
|
||||
|| placed.Length != solved.Length || placed.Width != solved.Width)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user