From cd1961e26b54ee103a31a27340fa164f440b8b70 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Sat, 24 Oct 2026 14:32:05 -0400 Subject: [PATCH] fix(desktop): keep parts muted when Sequence by Path is restarted Starting the tool while it was open built the new action before the old one was disconnected, and the old one's teardown cleared the new muting. The outgoing action now leaves the view alone once a successor owns it, and opening the tool repaints the plate right away. Co-Authored-By: Claude Opus 5.5 --- .../Controls/ActionSequenceByPathTests.cs | 60 +++++++++++++++++++ OpenNest/Actions/ActionSequenceByPath.cs | 22 +++++-- 2 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 OpenNest.WinForms.Tests/Controls/ActionSequenceByPathTests.cs diff --git a/OpenNest.WinForms.Tests/Controls/ActionSequenceByPathTests.cs b/OpenNest.WinForms.Tests/Controls/ActionSequenceByPathTests.cs new file mode 100644 index 00000000..a05b7929 --- /dev/null +++ b/OpenNest.WinForms.Tests/Controls/ActionSequenceByPathTests.cs @@ -0,0 +1,60 @@ +using OpenNest.Actions; +using OpenNest.CNC; +using OpenNest.Controls; +using OpenNest.Geometry; + +namespace OpenNest.WinForms.Tests.Controls; + +/// +/// Sequence by Path mutes every part until a line reaches it, however the tool is started. +/// +public class ActionSequenceByPathTests +{ + private static PlateView View() + { + var drawing = new Drawing("square", Square()); + var plate = new Plate(48, 48); + plate.Parts.Add(new Part(drawing, new Vector(2, 2))); + plate.Parts.Add(new Part(drawing, new Vector(20, 2))); + var view = new PlateView { Plate = plate, Size = new System.Drawing.Size(600, 600) }; + view.ZoomToFit(); + return view; + } + + private static Program Square() + { + var program = new Program(); + program.Codes.Add(new RapidMove(0, 0)); + program.Codes.Add(new LinearMove(4, 0)); + program.Codes.Add(new LinearMove(4, 4)); + program.Codes.Add(new LinearMove(0, 4)); + program.Codes.Add(new LinearMove(0, 0)); + return program; + } + + [Fact] + public void StartingTheToolMutesEveryPart() => StaTestThread.Run(() => + { + using var view = View(); + + view.SetAction(typeof(ActionSequenceByPath)); + + Assert.All(view.Plate.Parts, part => Assert.Contains(part, view.MutedParts!)); + view.SetAction(typeof(ActionSelect)); + Assert.Null(view.MutedParts); + }, TimeSpan.FromMinutes(1), "The STA test did not complete."); + + [Fact] + public void RestartingTheToolKeepsEveryPartMuted() => StaTestThread.Run(() => + { + using var view = View(); + + view.SetAction(typeof(ActionSequenceByPath)); + view.SetAction(typeof(ActionSequenceByPath)); + + Assert.NotNull(view.MutedParts); + Assert.All(view.Plate.Parts, part => Assert.Contains(part, view.MutedParts!)); + view.SetAction(typeof(ActionSelect)); + Assert.Null(view.MutedParts); + }, TimeSpan.FromMinutes(1), "The STA test did not complete."); +} diff --git a/OpenNest/Actions/ActionSequenceByPath.cs b/OpenNest/Actions/ActionSequenceByPath.cs index be0a481f..366afc10 100644 --- a/OpenNest/Actions/ActionSequenceByPath.cs +++ b/OpenNest/Actions/ActionSequenceByPath.cs @@ -38,6 +38,11 @@ namespace OpenNest.Actions }; private List drawing; private bool disconnecting; + private IReadOnlySet muted; + + // Starting the tool again builds the new one before this one is cancelled and + // disconnected, so this one must leave the view's muting to its successor. + private bool OwnsView => ReferenceEquals(plateView.MutedParts, muted); public override bool ResumeOnEscape => false; @@ -69,6 +74,7 @@ namespace OpenNest.Actions plateView.KeyDown += plateView_KeyDown; plateView.Paint += plateView_Paint; UpdateMuted(lines); + plateView.Invalidate(); } private void plateView_MouseDown(object sender, MouseEventArgs e) @@ -182,13 +188,14 @@ namespace OpenNest.Actions /// Mutes the parts the lines do not reach, so what is left to sequence stands out. private void UpdateMuted(IReadOnlyList> paths) { - var muted = new HashSet(ReferenceEqualityComparer.Instance); + var unreached = new HashSet(ReferenceEqualityComparer.Instance); foreach (var part in plateView.Plate.Parts) { if (!DrawnPathSequencing.Reaches(paths, part)) - muted.Add(part); + unreached.Add(part); } - plateView.MutedParts = muted; + muted = unreached; + plateView.MutedParts = unreached; } private void plateView_Paint(object sender, PaintEventArgs e) @@ -230,8 +237,11 @@ namespace OpenNest.Actions plateView.MouseUp -= plateView_MouseUp; plateView.KeyDown -= plateView_KeyDown; plateView.Paint -= plateView_Paint; - plateView.PreviewSequence = null; - plateView.MutedParts = null; + if (OwnsView) + { + plateView.PreviewSequence = null; + plateView.MutedParts = null; + } form.Close(); form.Dispose(); linePen.Dispose(); @@ -246,6 +256,8 @@ namespace OpenNest.Actions public override void CancelAction() { drawing = null; + if (!OwnsView) + return; plateView.PreviewSequence = null; UpdateMuted(lines); plateView.Invalidate();