using System.Windows.Forms; using OpenNest.Actions; using OpenNest.Controls; using OpenNest.Engine; using OpenNest.Engine.Strategies; using OpenNest.Forms; using OpenNest.Geometry; namespace OpenNest.WinForms.Tests.Forms; // Each desktop edit records one labeled undo entry and Undo puts the nest back. Reads // Application.OpenForms through the busy guards, so it must not overlap fill-lifetime tests. [Collection("Fill operation lifetime")] public class UndoRecordingTests { // Placement [Fact] public void AFillRecordsOneEntryAtItsCommit() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(2); var square = env.Nest.Drawings.Single(); using var fill = UseFill(context => [new Part(context.Item.Drawing, context.WorkArea.Location)]); env.AssertOneUndoableEdit("Fill", () => { env.View.FillWithProgress([new Part(square)], env.View.Plate.WorkArea()); WaitForFill(env); }); }); [Fact] public void AFillThatPlacesNothingRecordsNothing() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(2); using var fill = UseFill(_ => []); env.View.FillWithProgress([new Part(env.Nest.Drawings.Single())], env.View.Plate.WorkArea()); WaitForFill(env); Assert.Equal(0, env.Editor.History.UndoCount); }); [Fact] public void FillAreaRecordsOneEntry() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(2); using var fill = UseFill(context => [new Part(context.Item.Drawing, context.WorkArea.Location)]); env.View.SetAction(typeof(ActionFillArea), env.Nest.Drawings.Single()); var action = (ActionFillArea)env.View.Actions.CurrentAction; UndoTestEnv.SetBackingField(action, "SelectedArea", new Box(0, 0, 50, 50)); env.AssertOneUndoableEdit("Fill Area", () => UndoTestEnv.Invoke(action, "FillArea")); }); [Theory] [InlineData(PatternTileTarget.CurrentPlate)] [InlineData(PatternTileTarget.NewPlate)] public void PatternTileRecordsOneEntry(PatternTileTarget target) => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); var square = env.Nest.Drawings.Single(); var result = new PatternTileResult { Target = target, PlateSize = new Geometry.Size(60, 60), Parts = [new Part(square, new Vector(1, 1)), new Part(square, new Vector(12, 1)), new Part(square, new Vector(23, 1))], }; env.AssertOneUndoableEdit("Pattern Tile", () => MainForm.ApplyPatternTile(env.Editor, result)); }); [Fact] public void ClearNestRecordsOneEntry() => UndoTestEnv.Run(env => env.AssertOneUndoableEdit("Clear Nest", env.Editor.ClearNest)); [Fact] public void RemoveEmptyPlatesRecordsOneEntry() => UndoTestEnv.Run(env => env.AssertOneUndoableEdit("Remove Empty Plates", env.Editor.RemoveEmptyPlates)); [Fact] public void MovePlateRecordsOneEntry() => UndoTestEnv.Run(env => env.AssertOneUndoableEdit("Move Plate", () => Assert.True(env.Editor.MovePlate(3, 0)))); [Fact] public void DuplicatePlateRecordsOneEntry() => UndoTestEnv.Run(env => env.AssertOneUndoableEdit("Duplicate Plate", () => Assert.True(env.Editor.DuplicatePlate(0, 1)))); [Fact] public void MovePlateIntoItsOwnPlaceRecordsNothing() => UndoTestEnv.Run(env => { var count = env.Editor.History.UndoCount; Assert.False(env.Editor.MovePlate(1, 1)); // The trailing empty new-plate sentinel stays last. Assert.False(env.Editor.MovePlate(0, env.Nest.Plates.Count - 1)); Assert.Equal(count, env.Editor.History.UndoCount); }); [Fact] public void CenterPartsRecordsOneEntry() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); env.AssertOneUndoableEdit("Center Parts", () => env.Click("CenterParts_Click")); }); // Plate [Theory] [InlineData(nameof(EditNestForm.RotateCw))] [InlineData(nameof(EditNestForm.RotateCcw))] [InlineData(nameof(EditNestForm.Rotate180))] public void RotatingAPlateRecordsOneEntry(string command) => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); env.AssertOneUndoableEdit("Rotate Plate", () => UndoTestEnv.Invoke(env.Editor, command)); }, () => { var nest = UndoTestEnv.BuildNest(); nest.Plates[0].Size = new Geometry.Size(60, 100); return nest; }); [Fact] public void ResizingAPlateToItsPartsRecordsOneEntry() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); env.AssertOneUndoableEdit("Resize Plate", env.Editor.ResizePlateToFitParts); }); [Fact] public void EditPlateRecordsOneEntryOnOk() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); UndoTestEnv.AcceptWhenShown(form => form.Quantity = 3); env.AssertOneUndoableEdit("Edit Plate", env.Editor.EditPlate); Assert.Equal(3, env.Nest.Plates[0].Quantity); }); [Fact] public void CancelingEditPlateRecordsNothing() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(2); UndoTestEnv.WhenShown(form => form.DialogResult = DialogResult.Cancel); var before = UndoTestEnv.Describe(env.Nest); env.Editor.EditPlate(); Assert.Equal(0, env.Editor.History.UndoCount); Assert.Equal(before, UndoTestEnv.Describe(env.Nest)); }); [Fact] public void SettingTheNestDefaultPlateRecordsOneEntry() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); env.AssertOneUndoableEdit("Set Nest Default Plate", env.Editor.SetCurrentPlateAsNestDefault); }); [Fact] public void TheClampsDialogRecordsOneEntry() => UndoTestEnv.Run(env => { UndoTestEnv.AcceptWhenShown(form => Assert.True(form.Editor.TryAddClamp(new RailClamp(ClampEdge.OriginSide, 20), out _))); env.AssertOneUndoableEdit("Clamps", () => env.Click("Clamps_Click")); }); [Fact] public void TheSupportGridDialogRecordsOneEntry() => UndoTestEnv.Run(env => { UndoTestEnv.AcceptWhenShown(form => AllControls(form).OfType().First(box => box.Text.StartsWith("This nest's sheets")).Checked = true); env.AssertOneUndoableEdit("Support Bed", () => env.Click("SupportGrid_Click")); }); [Fact] public void RemovingAndPuttingBackAClampRecordOneEntryEach() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); var plate = env.View.Plate; var id = plate.ClampZones[0].Id; env.AssertOneUndoableEdit("Remove Clamp", () => UndoTestEnv.Invoke(env.View.ClampMenu, "Apply", plate, id, true)); env.AssertOneUndoableEdit("Put Clamp Back", () => UndoTestEnv.Invoke(env.View.ClampMenu, "Apply", env.View.Plate, id, false)); }, NestWithClamps); [Fact] public void EditingClampsOnThePlateRecordsOneEntryPerEdit() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); env.View.SetAction(typeof(ActionClampEdit)); var action = env.View.Actions.CurrentAction; var first = new RailClamp(ClampEdge.OriginSide, 20); var added = new RailClamp(ClampEdge.OriginSide, 50); env.AssertOneUndoableEdit("Add Clamp", () => UndoTestEnv.Invoke(action, "Add", added)); env.View.SetAction(typeof(ActionClampEdit)); action = env.View.Actions.CurrentAction; env.AssertOneUndoableEdit("Move Clamp", () => UndoTestEnv.Invoke(action, "Move", first, new RailClamp(ClampEdge.OriginSide, 30))); env.View.SetAction(typeof(ActionClampEdit)); action = env.View.Actions.CurrentAction; env.AssertOneUndoableEdit("Delete Clamp", () => UndoTestEnv.Invoke(action, "Delete", added)); }, NestWithClamps); // Cutting [Fact] public void PlacingACutOffRecordsOneEntry() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); env.View.ZoomToFit(); env.View.SetAction(typeof(ActionCutOff)); var at = env.View.PointWorldToControl(new Vector(70, 50)); UndoTestEnv.Mouse(env.View, "OnMouseMove", MouseButtons.None, at); env.AssertOneUndoableEdit("Add Cut-Off", () => UndoTestEnv.Mouse(env.View, "OnMouseDown", MouseButtons.Left, at)); }); [Fact] public void DraggingACutOffRecordsOneEntry() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); env.View.ZoomToFit(); var grab = env.View.PointWorldToControl(new Vector(70, 60)); env.AssertOneUndoableEdit("Move Cut-Off", () => { UndoTestEnv.Mouse(env.View, "OnMouseMove", MouseButtons.None, grab); UndoTestEnv.Mouse(env.View, "OnMouseDown", MouseButtons.Left, grab); for (var step = 1; step <= 4; step++) UndoTestEnv.Mouse(env.View, "OnMouseMove", MouseButtons.Left, new System.Drawing.Point(grab.X + step * 5, grab.Y)); UndoTestEnv.Mouse(env.View, "OnMouseUp", MouseButtons.Left, new System.Drawing.Point(grab.X + 20, grab.Y)); }); Assert.Equal(1, env.Editor.History.UndoCount); }, NestWithCutOff); [Fact] public void DeletingASelectedCutOffRecordsOneEntry() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); env.View.Selection.SelectedCutOffs.Add(env.View.Plate.CutOffs[0]); env.AssertOneUndoableEdit("Delete", env.View.Selection.DeleteSelected); }, NestWithCutOff); [Fact] public void AutomaticCutOffsRecordOneEntry() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); using var form = new AutomaticCutOffForm(env.View, env.Nest.Units); env.AssertOneUndoableEdit("Automatic Scrap Cutoffs", () => UndoTestEnv.Invoke(form, "ApplyButton_Click", null, EventArgs.Empty)); }); [Fact] public void AutoSequencingRecordsOneEntry() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); env.AssertOneUndoableEdit("Auto Sequence", env.Editor.AutoSequenceCurrentPlate); env.AssertOneUndoableEdit("Auto Sequence All Plates", env.Editor.AutoSequenceAllPlates); }, NestOutOfOrder); [Fact] public void SetSequenceRecordsOneEntryPerClick() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); env.View.ZoomToFit(); env.View.SetAction(typeof(ActionSetSequence)); var action = env.View.Actions.CurrentAction; var second = env.View.PointWorldToControl(env.View.Plate.Parts[1].BoundingBox.Center); env.AssertOneUndoableEdit("Set Sequence", () => UndoTestEnv.Invoke(action, "plateView_MouseClick", env.View, new MouseEventArgs(MouseButtons.Left, 1, second.X, second.Y, 0))); env.View.EndAction(); }); [Fact] public void SequenceByPathRecordsTheWholeSessionAsOneEntry() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); env.View.ZoomToFit(); var parts = env.View.Plate.Parts; var first = env.View.PointWorldToControl(parts[1].BoundingBox.Center); var second = env.View.PointWorldToControl(parts[0].BoundingBox.Center); env.AssertOneUndoableEdit("Sequence by Path", () => { env.View.SetAction(typeof(ActionSequenceByPath)); var action = env.View.Actions.CurrentAction!; Click(env.View, first); Click(env.View, second); // A line taken back inside the tool is the tool's own step, not the nest's. Assert.True(action.TryUndoStep()); Click(env.View, second); Assert.Equal(0, env.Editor.History.UndoCount); env.View.EndAction(); }); }); [Fact] public void PlacingALeadInRecordsOneEntry() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(3); env.View.ZoomToFit(); var part = env.View.Plate.Parts[0]; env.Editor.PlaceLeadIn_Click(null!, EventArgs.Empty); Assert.Equal(0, env.Editor.History.UndoCount); // Opening the tool changes nothing. env.AssertOneUndoableEdit("Place Lead-In", () => { PlaceLeadIn(env); Assert.True(part.HasManualLeadIns); }); env.View.EndAction(); }); [Fact] public void TheLeadInToolRecordsItsMenuRemovalAndAParameterChange() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(3); env.View.ZoomToFit(); env.Editor.PlaceLeadIn_Click(null!, EventArgs.Empty); var action = env.View.Actions.CurrentAction!; var center = PlaceLeadIn(env); Assert.Equal("Place Lead-In", env.Editor.History.UndoLabel); Assert.True(action.IsBusy(), "The square is no longer picked."); env.AssertOneUndoableEdit("Remove Lead-Ins", () => { UndoTestEnv.Invoke(action, "ShowContextMenu", center); var menu = (ContextMenuStrip)action.GetType().GetField("contextMenu", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!.GetValue(action)!; menu.Items[0].PerformClick(); menu.Close(); }); // Undo ended the tool; a changed panel is recorded when the tool closes. env.Editor.PlaceLeadIn_Click(null!, EventArgs.Empty); action = env.View.Actions.CurrentAction!; var panel = (CuttingPanel)action.GetType().GetField("cuttingPanel", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!.GetValue(action)!; panel.LoadFromParameters(new CNC.CuttingStrategy.CuttingParameters { ExternalLeadIn = new CNC.CuttingStrategy.LineLeadIn { Length = 0.75, ApproachAngle = 30 }, }); env.AssertOneUndoableEdit("Cutting Parameters", env.View.EndAction); }); [Fact] public void ClosingTheLeadInToolUnchangedRecordsNothing() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); env.Editor.PlaceLeadIn_Click(null!, EventArgs.Empty); env.View.EndAction(); Assert.Equal(0, env.Editor.History.UndoCount); }, NestWithLeadIns); [Fact] public void RemovingLeadInsRecordsOneEntry() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); env.AssertOneUndoableEdit("Remove Lead-Ins", () => env.Editor.RemoveLeadIns_Click(null!, EventArgs.Empty)); env.AssertOneUndoableEdit("Remove Lead-Ins", env.Editor.RemoveLeadInsAllPlates); }, NestWithLeadIns); [Fact] public void ApplyingACuttingPlanRecordsOneEntry() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); env.AssertOneUndoableEdit("Plan Cutting", () => { env.Editor.OpenCuttingInspection(); var panel = env.Editor.CuttingInspection!; PumpUntil(() => panel.Proposal != null && !panel.IsPlanning); UndoTestEnv.Invoke(panel, "Apply"); PumpUntil(() => env.Editor.CuttingInspection == null); }); }); [Fact] public void ClosingACuttingPlanWithoutApplyingRecordsNothing() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(0); var before = UndoTestEnv.Describe(env.Nest); env.Editor.OpenCuttingInspection(); var panel = env.Editor.CuttingInspection!; PumpUntil(() => panel.Proposal != null && !panel.IsPlanning); panel.RequestClose(); PumpUntil(() => env.Editor.CuttingInspection == null); Assert.Equal(0, env.Editor.History.UndoCount); Assert.Equal(before, UndoTestEnv.Describe(env.Nest)); }); // Picks the current plate's first part, locks its left edge and places a lead-in there; // returns the pick point. private static System.Drawing.Point PlaceLeadIn(UndoTestEnv env) { var box = env.View.Plate.Parts[0].BoundingBox; var center = env.View.PointWorldToControl(box.Center); var edge = env.View.PointWorldToControl(new Vector(box.Left, box.Center.Y)); UndoTestEnv.Mouse(env.View, "OnMouseMove", MouseButtons.None, center); UndoTestEnv.Mouse(env.View, "OnMouseDown", MouseButtons.Left, center); UndoTestEnv.Mouse(env.View, "OnMouseMove", MouseButtons.None, edge); UndoTestEnv.Mouse(env.View, "OnMouseDown", MouseButtons.Left, edge); UndoTestEnv.Mouse(env.View, "OnMouseMove", MouseButtons.None, edge); UndoTestEnv.Mouse(env.View, "OnMouseDown", MouseButtons.Left, edge); return center; } // Drawings [Fact] public void ImportingDrawingsRecordsOneEntry() => UndoTestEnv.Run(env => { var directory = Directory.CreateTempSubdirectory("opennest-undo-"); try { var path = Path.Combine(directory.FullName, "plate.dxf"); var document = new ACadSharp.CadDocument(); var corners = new[] { (0.0, 0.0), (8.0, 0.0), (8.0, 4.0), (0.0, 4.0) }; for (var i = 0; i < corners.Length; i++) { var (x1, y1) = corners[i]; var (x2, y2) = corners[(i + 1) % corners.Length]; document.Entities.Add(new ACadSharp.Entities.Line(new CSMath.XYZ(x1, y1, 0), new CSMath.XYZ(x2, y2, 0))); } ACadSharp.IO.DxfWriter.Write(path, document, false); UndoTestEnv.AcceptWhenShown(ready: form => form.GetDrawings().Count > 0); env.AssertOneUndoableEdit("Import Drawings", () => env.Editor.ImportFiles([path])); Assert.Equal(2, env.Nest.Drawings.Count); } finally { directory.Delete(true); } }); [Fact] public void AddingFromTheShapeLibraryRecordsOneEntry() => UndoTestEnv.Run(env => { UndoTestEnv.WhenShown(form => UndoTestEnv.Invoke(form, "AddButton_Click", null, EventArgs.Empty)); env.AssertOneUndoableEdit("Shape Library", () => env.Click("ShapeLibrary_Click")); }); [Fact] public void EditingDrawingsInTheConverterRecordsOneEntry() => UndoTestEnv.Run(env => { UndoTestEnv.AcceptWhenShown(form => ((System.Windows.Forms.NumericUpDown)AllControls(form).Single(c => c.Name == "numQuantity")).Value = 7); env.AssertOneUndoableEdit("Edit Drawings", () => UndoTestEnv.Invoke(env.Editor, "EditDrawingsInConverter", env.Nest.Drawings.ToList())); Assert.Equal(7, env.Nest.Drawings.Single().Quantity.Required); }, NestWithRequiredQuantity); [Fact] public void CancelingTheConverterRecordsNothing() => UndoTestEnv.Run(env => { var before = UndoTestEnv.Describe(env.Nest); UndoTestEnv.WhenShown(form => form.DialogResult = DialogResult.Cancel); UndoTestEnv.Invoke(env.Editor, "EditDrawingsInConverter", env.Nest.Drawings.ToList()); Assert.Equal(0, env.Editor.History.UndoCount); Assert.Equal(before, UndoTestEnv.Describe(env.Nest)); }, NestWithRequiredQuantity); // The converter's quantity box starts at 1, so it cannot load a drawing that requires none. private static Nest NestWithRequiredQuantity() { var nest = UndoTestEnv.BuildNest(); nest.Drawings.Single().Quantity.Required = 5; return nest; } [Fact] public void DeletingADrawingRecordsOneEntry() => UndoTestEnv.Run(env => env.AssertOneUndoableEdit("Delete Drawing", () => env.Editor.DeleteDrawing(env.Nest.Drawings.Single()))); [Fact] public void CleaningUnusedDrawingsRecordsOneEntry() => UndoTestEnv.Run(env => env.AssertOneUndoableEdit("Clean Unused Drawings", env.Editor.CleanUnusedDrawings), () => { var nest = UndoTestEnv.BuildNest(); nest.Drawings.Add(new Drawing("unused", UndoTestEnv.Square(4))); return nest; }); [Fact] public void EditingDrawingPropertiesRecordsOneEntry() => UndoTestEnv.Run(env => { var list = (DrawingListBox)AllControls(env.Editor).Single(c => c is DrawingListBox); list.SelectedItem = env.Nest.Drawings.Single(); UndoTestEnv.AcceptWhenShown(form => typeof(EditDrawingForm).GetProperty("Quantity", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!.SetValue(form, 9)); env.AssertOneUndoableEdit("Drawing Properties", () => UndoTestEnv.Invoke(env.Editor, "drawingListBox1_DoubleClick", list, EventArgs.Empty)); Assert.Equal(9, env.Nest.Drawings.Single().Quantity.Required); }); [Fact] public void DroppingADrawingOnThePlateRecordsOneEntry() => UndoTestEnv.Run(env => { env.Editor.PlateManager.LoadAt(2); env.View.ZoomToFit(); var at = env.View.PointToScreen(env.View.PointWorldToControl(new Vector(50, 50))); var drop = new DragEventArgs(new DataObject(env.Nest.Drawings.Single()), 0, at.X, at.Y, DragDropEffects.Copy, DragDropEffects.Copy); env.AssertOneUndoableEdit("Add Part", () => UndoTestEnv.Invoke(env.View, "OnDragDrop", drop)); }); // Nest [Fact] public void AssigningAMachineRecordsOneEntry() => UndoTestEnv.Run(env => { var machine = new Data.MachineConfig { Name = "Undo test laser" }; env.AssertOneUndoableEdit("Machine", () => UndoTestEnv.Invoke(env.Host, "AssignMachine", machine)); Assert.Equal(machine.Id, env.Nest.Machine!.Id); }); [Fact] public void EditingNestInfoRecordsOneEntry() => UndoTestEnv.Run(env => { UndoTestEnv.AcceptWhenShown(form => { form.Notes = "edited"; form.Customer = "someone"; }); env.AssertOneUndoableEdit("Nest Info", () => Assert.True(env.Editor.ShowNestInfoEditorAsync().IsCompleted)); Assert.Equal("edited", env.Nest.Notes); }); [Fact] public void ANestInfoEditOfOnlyItsIdentityRecordsNothing() => UndoTestEnv.Run(env => { UndoTestEnv.AcceptWhenShown(form => { form.Status = NestStatus.HasBeenCut; form.MadeBy = "someone else"; }); Assert.True(env.Editor.ShowNestInfoEditorAsync().IsCompleted); Assert.Equal(NestStatus.HasBeenCut, env.Nest.Status); Assert.Equal(0, env.Editor.History.UndoCount); }); private static void Click(PlateView view, System.Drawing.Point at) { UndoTestEnv.Mouse(view, "OnMouseMove", MouseButtons.None, at); UndoTestEnv.Mouse(view, "OnMouseDown", MouseButtons.Left, at); UndoTestEnv.Mouse(view, "OnMouseUp", MouseButtons.Left, at); } private static void PumpUntil(Func condition) { var deadline = DateTime.UtcNow.AddSeconds(30); while (!condition()) { Assert.True(DateTime.UtcNow < deadline, "Timed out."); Application.DoEvents(); Thread.Sleep(5); } } private static Nest NestWithCutOff() { var nest = UndoTestEnv.BuildNest(); nest.Plates[0].CutOffs.Add(new CutOff(new Vector(70, 0), CutOffAxis.Vertical)); nest.Plates[0].RegenerateCutOffs(new CutOffSettings()); return nest; } // Three squares in a row, cut middle, far, near: any sequencer reorders them. private static Nest NestOutOfOrder() { var nest = UndoTestEnv.BuildNest(); var square = nest.Drawings.Single(); foreach (var plate in nest.Plates.Take(2)) { plate.Parts.Clear(); foreach (var x in new[] { 35.0, 65.0, 5.0 }) plate.Parts.Add(new Part(square, new Vector(x, 5))); } return nest; } private static Nest NestWithLeadIns() { var nest = UndoTestEnv.BuildNest(); var parameters = new CNC.CuttingStrategy.CuttingParameters(); foreach (var plate in nest.Plates.Where(p => p.Parts.Count > 0)) { plate.CuttingParameters = parameters; foreach (var part in plate.Parts) part.ApplyLeadIns(parameters, new Vector(0, 0)); } return nest; } private static Nest NestWithClamps() { var nest = UndoTestEnv.BuildNest(); nest.SetClampRail(new ClampRail(1.5, 0.625, new[] { 20.0, 60.0 })); return nest; } private static IEnumerable AllControls(Control parent) => parent.Controls.Cast().SelectMany(child => AllControls(child).Prepend(child)); private static IDisposable UseFill(Func> fill) { var prior = EngineSelection.EngineName; EngineSelection.EngineName = EngineSelection.DefaultEngineName; var strategies = FillStrategyRegistry.UseForTesting([new TestFill(fill)]); return new Restore(() => { strategies.Dispose(); EngineSelection.EngineName = prior; }); } private static void WaitForFill(UndoTestEnv env) { var deadline = DateTime.UtcNow.AddSeconds(30); while (env.View.IsFillInProgress) { Assert.True(DateTime.UtcNow < deadline, "The fill did not finish."); Application.DoEvents(); Thread.Sleep(5); } } private sealed class TestFill(Func> fill) : IFillStrategy { public string Name => "Undo recording test fill"; public NestPhase Phase => NestPhase.Linear; public int Order => 0; public List Fill(FillContext context) => fill(context); } private sealed class Restore(System.Action restore) : IDisposable { public void Dispose() => restore(); } }