diff --git a/OpenNest.WinForms.Tests/CutOffWindowSelectionTests.cs b/OpenNest.WinForms.Tests/CutOffWindowSelectionTests.cs index 0e14d68..674965f 100644 --- a/OpenNest.WinForms.Tests/CutOffWindowSelectionTests.cs +++ b/OpenNest.WinForms.Tests/CutOffWindowSelectionTests.cs @@ -1,5 +1,6 @@ using System.Drawing; using System.Reflection; +using System.Windows.Forms; using OpenNest.Actions; using OpenNest.CNC; using OpenNest.Controls; @@ -109,6 +110,89 @@ public class CutOffWindowSelectionTests } }); + [Fact] + public void MixedDeleteRegeneratesSurvivingCutOffAfterRemovingPart() => RunSta(() => + { + var program = new CNC.Program(); + program.MoveTo(9, 5); + program.LineTo(11, 5); + program.LineTo(11, 10); + program.LineTo(9, 10); + program.LineTo(9, 5); + var part = new Part(new Drawing("square", program)); + var plate = new Plate(20, 20); + plate.Parts.Add(part); + var removed = new CutOff(new Vector(2, 0), CutOffAxis.Vertical) + { + StartLimit = 4, + EndLimit = 11, + }; + var kept = new CutOff(new Vector(10, 0), CutOffAxis.Vertical); + plate.CutOffs.Add(removed); + plate.CutOffs.Add(kept); + plate.RegenerateCutOffs(new CutOffSettings()); + using var view = new PlateView { Plate = plate }; + var action = (ActionSelect)view.Actions.CurrentAction; + action.Point1 = new Vector(0, 3); + action.Point2 = new Vector(12, 12); + SelectWindow(action); + Assert.Contains(view.SelectedParts, selected => selected.BasePart == part); + Assert.Same(removed, Assert.Single(view.Selection.SelectedCutOffs)); + Assert.True(kept.Drawing.Program.Codes.Count > 2); + + view.Selection.DeleteSelected(); + Assert.Same(kept, Assert.Single(plate.CutOffs)); + Assert.Empty(view.SelectedParts); + Assert.Equal(2, kept.Drawing.Program.Codes.Count); + Assert.Equal(2, Assert.Single(plate.Parts).Program.Codes.Count); + }); + + [Fact] + public void EscapeStopsCutOffDragBeforeAnotherWindowSelection() => RunSta(() => + { + var plate = new Plate(20, 20); + var cutoff = new CutOff(new Vector(10, 0), CutOffAxis.Vertical); + plate.CutOffs.Add(cutoff); + plate.RegenerateCutOffs(new CutOffSettings()); + using var view = new EventView { Plate = plate, Size = new System.Drawing.Size(500, 500) }; + view.ZoomToArea(-2, -2, 24, 24); + view.Down(new Vector(10, 2)); + Assert.True(view.CutOffs.IsDragging); + view.ProcessEscapeKey(); + Assert.Empty(view.Selection.SelectedCutOffs); + Assert.False(view.CutOffs.IsDragging); + view.Up(new Vector(10, 2)); + view.Down(new Vector(12, 12)); + view.MoveTo(new Vector(8, -1), MouseButtons.Left); + view.Up(new Vector(8, -1)); + Assert.Same(cutoff, Assert.Single(view.Selection.SelectedCutOffs)); + var before = cutoff.Position; + view.MoveTo(new Vector(13, 8), MouseButtons.None); + Assert.Equal(before.X, cutoff.Position.X); + }); + + private sealed class EventView : PlateView + { + public void Down(Vector world) + { + MoveTo(world, MouseButtons.None); + var point = PointWorldToControl(world); + OnMouseDown(new MouseEventArgs(MouseButtons.Left, 1, point.X, point.Y, 0)); + } + + public void Up(Vector world) + { + var point = PointWorldToControl(world); + OnMouseUp(new MouseEventArgs(MouseButtons.Left, 1, point.X, point.Y, 0)); + } + + public void MoveTo(Vector world, MouseButtons buttons) + { + var point = PointWorldToControl(world); + OnMouseMove(new MouseEventArgs(buttons, 0, point.X, point.Y, 0)); + } + } + private static void SelectWindow(ActionSelect action) => typeof(ActionSelect).GetMethod("SelectPartsInWindow", BindingFlags.Instance | BindingFlags.NonPublic)! .Invoke(action, null); diff --git a/OpenNest/Actions/ActionSelect.cs b/OpenNest/Actions/ActionSelect.cs index d2e5410..066c736 100644 --- a/OpenNest/Actions/ActionSelect.cs +++ b/OpenNest/Actions/ActionSelect.cs @@ -53,6 +53,7 @@ namespace OpenNest.Actions { status = Status.SetFirstPoint; + plateView.CutOffs.EndDrag(); plateView.DeselectAll(); plateView.Invalidate(); } diff --git a/OpenNest/Controls/PlateView.cs b/OpenNest/Controls/PlateView.cs index 41f49aa..6dfc282 100644 --- a/OpenNest/Controls/PlateView.cs +++ b/OpenNest/Controls/PlateView.cs @@ -470,7 +470,7 @@ namespace OpenNest.Controls } } - if (cutOffHandler.IsDragging && selection.SelectedCutOffs.Count > 0) + if (e.Button == MouseButtons.Left && cutOffHandler.IsDragging) { cutOffHandler.EndDrag(); return; @@ -545,7 +545,8 @@ namespace OpenNest.Controls if (InspectionOnly) return; - if (cutOffHandler.IsDragging && selection.SelectedCutOffs.Count > 0) + if (cutOffHandler.IsDragging && e.Button == MouseButtons.Left + && selection.SelectedCutOffs.Count > 0) { cutOffHandler.UpdateDrag(CurrentPoint, selection.SelectedCutOffs[0]); return; diff --git a/OpenNest/Controls/SelectionManager.cs b/OpenNest/Controls/SelectionManager.cs index 6c440e1..6384f2d 100644 --- a/OpenNest/Controls/SelectionManager.cs +++ b/OpenNest/Controls/SelectionManager.cs @@ -60,16 +60,16 @@ namespace OpenNest.Controls public void DeleteSelected() { - if (selectedCutOffs.Count > 0) - { - foreach (var cutOff in selectedCutOffs) - view.Plate.CutOffs.Remove(cutOff); + var regenerateCutOffs = view.Plate.CutOffs.Count > 0 + && (selectedCutOffs.Count > 0 || selectedParts.Count > 0); + foreach (var cutOff in selectedCutOffs) + view.Plate.CutOffs.Remove(cutOff); - view.Plate.RegenerateCutOffs(view.CutOffSettings); - } - - // A window can select both kinds of object; Delete must remove both. + // Finish removing both kinds before rebuilding surviving cut strokes; + // otherwise they retain gaps around parts deleted in the same window. RemoveSelectedParts(); + if (regenerateCutOffs) + view.Plate.RegenerateCutOffs(view.CutOffSettings); } public void RemoveSelectedParts()