mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-19 14:21:37 -04:00
A clamp is an avoidance area, so a clamp that only partly reaches a sheet (for example centered at 60.5" on a 60" sheet, or near the origin) now keeps its whole 1.5" x 0.625" footprint instead of being clipped to the sheet; clamps wholly beyond the sheet are still left off. Job validation accepts a zone that hangs past the sheet edges and still rejects one that does not reach the sheet at all. Both packing lanes already block keep-outs extending past the bin; a hanging-clamp solve through Rectangles, Irregular and Default keeps every part clear of the whole inflated footprint. User decision 2026-10-12, settling the plan's pending D2 question.
104 lines
4.3 KiB
C#
104 lines
4.3 KiB
C#
using OpenNest.Engine;
|
|
using OpenNest.Engine.Jobs;
|
|
using OpenNest.Engine.Jobs.Adapters;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Engine.Tests.Jobs;
|
|
|
|
/// <summary>
|
|
/// A template plate following the machine clamp rail offers every stock size with the rail
|
|
/// resolved for that size (D2): no clamp beyond a shorter sheet, every clamp on a longer
|
|
/// one, measured from the origin in every quadrant.
|
|
/// </summary>
|
|
public class ClampRailStockTests
|
|
{
|
|
private static readonly double[] MachineCenters = { 6, 21, 36, 51, 66, 81, 96, 111 };
|
|
|
|
private static Plate RailTemplate(int quadrant = 1)
|
|
{
|
|
var template = new Plate(48, 120) { Quadrant = quadrant, EdgeSpacing = new Spacing(0.25, 0.25, 0.25, 0.25) };
|
|
template.SetClampRail(new ClampRail(1.5, 0.625, MachineCenters));
|
|
return template;
|
|
}
|
|
|
|
private static PlateOption[] Options() =>
|
|
[
|
|
new PlateOption { Width = 48, Length = 96 },
|
|
new PlateOption { Width = 60, Length = 144 },
|
|
new PlateOption { Width = 48, Length = 60 },
|
|
];
|
|
|
|
[Fact]
|
|
public void EachOptionGetsTheRailResolvedForItsOwnSize()
|
|
{
|
|
var stock = NestStockBuilder.FromTemplate(RailTemplate(), Options());
|
|
|
|
Assert.Equal(new[] { 7, 8, 4 }, stock.Select(s => s.ClampZones.Count));
|
|
Assert.Equal(new[] { 5.25, 20.25, 35.25, 50.25 }, stock[2].ClampZones.Select(z => z.Bounds.X));
|
|
// Every offered sheet passes job validation: no clamp lies outside its sheet.
|
|
NestJobValidator.Validate(new NestJob([], stock));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(2)]
|
|
[InlineData(3)]
|
|
public void OptionsResolveFromTheOriginWhenSheetsExtendToNegativeX(int quadrant)
|
|
{
|
|
var stock = NestStockBuilder.FromTemplate(RailTemplate(quadrant), Options());
|
|
|
|
foreach (var sheet in stock)
|
|
{
|
|
// Placement frame: the first clamp is 6" from the origin, toward -X.
|
|
var first = sheet.ClampZones.Single(z => z.Id == "clamp-1").Bounds;
|
|
Assert.Equal(-6.0, first.Center.X, 9);
|
|
}
|
|
NestJobValidator.Validate(new NestJob([], stock));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Rectangles")]
|
|
[InlineData("Irregular")]
|
|
[InlineData("Default")]
|
|
public void LanesAvoidTheWholeFootprintOfAClampHangingPastTheSheetEnd(string engine)
|
|
{
|
|
// 12" sheet, clamp centered 12.25" from the origin: 1" of its 1.5" lies past the end.
|
|
var template = new Plate(10, 12) { EdgeSpacing = new Spacing(0.25, 0.25, 0.25, 0.25), PartSpacing = 0.25 };
|
|
template.SetClampRail(new ClampRail(1.5, 0.625, new[] { 12.25 }));
|
|
var zone = Assert.Single(template.ClampZones);
|
|
Assert.Equal(11.5, zone.Bounds.X);
|
|
Assert.Equal(1.5, zone.Bounds.Length);
|
|
var drawing = new Drawing("square", TestDrawingFactory.Rectangle(1, 1));
|
|
|
|
var result = NestPipeline.Run(new NestPipelineRequest(engine,
|
|
[new NestItem { Drawing = drawing, Quantity = 80 }], NestStockBuilder.SinglePlate(template)));
|
|
|
|
Assert.True(result.IsValid, string.Join("; ", result.Violations));
|
|
var parts = result.Plates.SelectMany(p => p.Parts).ToList();
|
|
Assert.NotEmpty(parts);
|
|
// Independent oracle: the whole clamp grown by the bottom edge spacing.
|
|
var keepOut = zone.Bounds.Offset(0.25);
|
|
Assert.All(parts, part =>
|
|
{
|
|
var box = part.BoundingBox;
|
|
var overlapX = System.Math.Min(box.Right, keepOut.Right) - System.Math.Max(box.Left, keepOut.Left);
|
|
var overlapY = System.Math.Min(box.Top, keepOut.Top) - System.Math.Max(box.Bottom, keepOut.Bottom);
|
|
Assert.False(overlapX > 1e-6 && overlapY > 1e-6, $"{box} enters {keepOut}");
|
|
});
|
|
// Rectangles still uses the front edge beside the clamp. (Irregular may lift its whole
|
|
// block above the clamp band at the same count; that is a layout choice, not a leak.)
|
|
if (engine == "Rectangles")
|
|
Assert.Contains(parts, p => p.BoundingBox.Bottom < keepOut.Top && p.BoundingBox.Right <= keepOut.Left + 1e-6);
|
|
}
|
|
|
|
[Fact]
|
|
public void SingleSheetStockCarriesTheTemplatesRailZones()
|
|
{
|
|
var template = RailTemplate();
|
|
|
|
var single = Assert.Single(NestStockBuilder.SinglePlate(template));
|
|
|
|
Assert.Equal(8, single.ClampZones.Count);
|
|
Assert.Equal(template.ClampZones.Select(z => z.Bounds.X), single.ClampZones.Select(z => z.Bounds.X));
|
|
}
|
|
}
|