using OpenNest.CNC; using OpenNest.Data; using OpenNest.Engine; using OpenNest.Engine.Jobs; using OpenNest.Geometry; using OpenNest.IO; namespace OpenNest.Tests.Engine; /// /// The desktop clamp workflow end to end without the form: enabled machine settings give a /// new nest its rail, whole-job Auto Nest (template stock, validated pipeline, commit) keeps /// every part clear of the clamps on every sheet while still using the edge between them, /// and save/reload preserves clamps and placements without writing the empty tail sheet. /// public class ClampRailWorkflowTests { private const double EdgeSpacing = 0.25; private static readonly double[] Centers = { 3, 10.5, 18 }; private static Drawing Block() { var program = new Program(); program.MoveTo(0, 0); program.LineTo(4, 0); program.LineTo(4, 2); program.LineTo(0, 2); program.LineTo(0, 0); return new Drawing("neutral-block", program); } private static Nest NewNest(int quadrant) { var nest = new Nest { Units = Units.Inches }; nest.PlateDefaults.Size = new Size(12, 24); // 12 deep (Y), 24 along the clamp edge (X) nest.PlateDefaults.Quadrant = quadrant; nest.PlateDefaults.EdgeSpacing = new Spacing(EdgeSpacing, EdgeSpacing, EdgeSpacing, EdgeSpacing); nest.PlateDefaults.PartSpacing = 0.25; new ClampSettings { Enabled = true, Centers = Centers }.ApplyToNewNest(nest); return nest; } /// Independent oracle: part boxes against clamps grown by the bottom edge spacing. private static void AssertClearOfClamps(Plate plate) { var left = plate.Quadrant is 2 or 3 ? -plate.Size.Length : 0; var bottom = plate.Quadrant is 3 or 4 ? -plate.Size.Width : 0; Assert.NotEmpty(plate.ClampZones); foreach (var zone in plate.ClampZones) { var keepOut = zone.Bounds.Translate(left, bottom); foreach (var part in plate.Parts) { var box = part.BoundingBox; var overlapX = System.Math.Min(box.Right, keepOut.Right + EdgeSpacing) - System.Math.Max(box.Left, keepOut.Left - EdgeSpacing); var overlapY = System.Math.Min(box.Top, keepOut.Top + EdgeSpacing) - System.Math.Max(box.Bottom, keepOut.Bottom - EdgeSpacing); Assert.False(overlapX > 1e-6 && overlapY > 1e-6, $"{part.BaseDrawing.Name} at {box} enters {zone.Id} at {keepOut}"); } } } [Theory] [InlineData("Rectangles", 1)] [InlineData("Default", 1)] [InlineData("Rectangles", 3)] public void AutoNestAvoidsMachineClampsAndTheyRoundTrip(string engine, int quadrant) { const int quantity = 40; var drawing = Block(); var nest = NewNest(quadrant); nest.Drawings.Add(drawing); var rail = nest.PlateDefaults.ClampRail; Assert.NotNull(rail); using var manager = new PlateManager(nest); var template = manager.GetOrCreateEmpty(); Assert.Equal(3, template.ClampZones.Count); var item = new NestItem { Drawing = drawing, Quantity = quantity, StepAngle = OpenNest.Math.Angle.HalfPI, RotationStart = 0, RotationEnd = OpenNest.Math.Angle.HalfPI, }; var result = NestPipeline.Run(new NestPipelineRequest(engine, [item], NestStockBuilder.FromTemplate(template, null))); Assert.True(result.IsValid, string.Join("; ", result.Violations)); Assert.Equal(quantity, Assert.Single(result.Raw.Fulfillment).Placed); var applied = NestPipelineCommit.ApplyToEmptyPlates(result, manager); Assert.True(applied.Count >= 2, $"expected several sheets, got {applied.Count}"); foreach (var plate in applied) { Assert.Same(rail, plate.ClampRail); Assert.True(rail.Matches(plate)); AssertClearOfClamps(plate); } if (quadrant == 1) { // The edge between clamps stays usable: some part sits in the clamp band. Assert.Contains(applied.SelectMany(p => p.Parts), p => p.BoundingBox.Bottom < 0.625 + EdgeSpacing); } using var stream = new MemoryStream(); Assert.True(new NestWriter(nest).Write(stream)); var loaded = new NestReader(new MemoryStream(stream.ToArray())).Read(); Assert.Equal(rail, loaded.PlateDefaults.ClampRail); Assert.Equal(applied.Count, loaded.Plates.Count); // trailing empty sheet not written for (var i = 0; i < applied.Count; i++) { var reloaded = loaded.Plates[i]; Assert.Same(loaded.PlateDefaults.ClampRail, reloaded.ClampRail); Assert.Equal(applied[i].ClampZones.Select(z => (z.Id, z.Bounds.X, z.Bounds.Y, z.Bounds.Length, z.Bounds.Width)), reloaded.ClampZones.Select(z => (z.Id, z.Bounds.X, z.Bounds.Y, z.Bounds.Length, z.Bounds.Width))); Assert.Equal(applied[i].Parts.Select(p => (p.Location, p.Rotation)), reloaded.Parts.Select(p => (p.Location, p.Rotation))); AssertClearOfClamps(reloaded); } Assert.Equal(3, loaded.CreatePlate().ClampZones.Count); } }