From d57678ecee41a36edbf04edc234536578d64ca76 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Sun, 1 Nov 2026 16:33:27 -0500 Subject: [PATCH] Disable toolbar buttons that cannot be used Plate commands need an open nest and no running job; Trim/Extend Cut-Off needs a cut-off on the plate and Sequence by Path needs two parts. The Plate menu's Trim/Extend item follows the same rule. Co-Authored-By: Claude Opus 5.5 --- .../Forms/MainFormCharacterizationTests.cs | 60 +++++++++++++++++++ OpenNest/Forms/MainForm.Status.cs | 28 +++++++++ OpenNest/Forms/MainForm.Undo.cs | 7 ++- OpenNest/Forms/MainForm.cs | 26 ++++++-- 4 files changed, 114 insertions(+), 7 deletions(-) diff --git a/OpenNest.WinForms.Tests/Forms/MainFormCharacterizationTests.cs b/OpenNest.WinForms.Tests/Forms/MainFormCharacterizationTests.cs index d3aaedf5..57250638 100644 --- a/OpenNest.WinForms.Tests/Forms/MainFormCharacterizationTests.cs +++ b/OpenNest.WinForms.Tests/Forms/MainFormCharacterizationTests.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Windows.Forms; using OpenNest.Controls; using OpenNest.Forms; using OpenNest.Geometry; @@ -77,6 +78,65 @@ public class MainFormCharacterizationTests Assert.Equal(new Guards(false, true, false, false), Read(host, editor, views)); }, TimeSpan.FromMinutes(1), "The STA test did not complete."); + [Fact] + public void ToolbarFollowsTheActiveDocumentAndItsPlate() => UndoTestEnv.Run(env => + { + var host = env.Host; + bool Enabled(string button) => Field(typeof(MainForm), host, button).Enabled; + void AddCutOff() + { + using var change = env.View.BeginChange("Add Cut-Off"); + change.MarkChanged(); + env.View.Plate.CutOffs.Add(new CutOff(new Vector(50, 0), CutOffAxis.Vertical)); + env.View.Plate.RegenerateCutOffs(env.View.CutOffSettings); + change.Complete(); + } + + // The first plate holds two parts and no cut-offs. + env.Editor.PlateManager.LoadAt(0); + Assert.True(Enabled("btnCutOff")); + Assert.True(Enabled("btnSequenceByPath")); + Assert.False(Enabled("btnTrimExtendCutOff")); + Assert.False(UndoTestEnv.MenuItem(host, "mnuPlateTrimExtendCutOff").Enabled); + + AddCutOff(); + Assert.True(Enabled("btnTrimExtendCutOff")); + Assert.True(UndoTestEnv.MenuItem(host, "mnuPlateTrimExtendCutOff").Enabled); + + Assert.True(host.TryUndo(env.Editor)); + Assert.False(Enabled("btnTrimExtendCutOff")); + Assert.True(host.TryRedo(env.Editor)); + Assert.True(Enabled("btnTrimExtendCutOff")); + + // Another plate has its own cut-offs; the third one is empty. + env.Editor.PlateManager.LoadAt(2); + Assert.False(Enabled("btnTrimExtendCutOff")); + Assert.False(Enabled("btnSequenceByPath")); + env.View.Plate.Parts.Add(new Part(env.Nest.Drawings.First(), new Vector(5, 5))); + env.View.Plate.Parts.Add(new Part(env.Nest.Drawings.First(), new Vector(25, 5))); + Assert.True(Enabled("btnSequenceByPath")); + + // A running job locks the plate commands, as it does the Nest and Plate menus. + Invoke(host, "SetNestingLockout", true); + Assert.False(Enabled("btnAutoNest")); + Assert.False(Enabled("btnCutOff")); + Assert.False(Enabled("postButton")); + Invoke(host, "SetNestingLockout", false); + Assert.True(Enabled("btnAutoNest")); + + env.Editor.Close(); + foreach (var button in ToolbarPlateCommands) + Assert.False(Enabled(button), button); + Assert.True(Enabled("btnNew")); + Assert.True(Enabled("btnOpen")); + }); + + private static readonly string[] ToolbarPlateCommands = + { + "btnSave", "btnSaveAs", "btnAutoNest", "btnShowRemnants", "btnCutOff", "btnTrimExtendCutOff", + "btnCutPlanner", "btnSimulateCutting", "btnSequenceByPath", "postButton", + }; + private sealed record Guards(bool ExpandSpacing, bool CuttingPlan, bool Clamps, bool NestJob); private static Guards Read(MainForm host, EditNestForm editor, PlateView[] views) => new( diff --git a/OpenNest/Forms/MainForm.Status.cs b/OpenNest/Forms/MainForm.Status.cs index e6bae0b5..aa67039e 100644 --- a/OpenNest/Forms/MainForm.Status.cs +++ b/OpenNest/Forms/MainForm.Status.cs @@ -1,5 +1,6 @@ using System; using System.Windows.Forms; +using OpenNest.Collections; namespace OpenNest.Forms { @@ -8,6 +9,7 @@ namespace OpenNest.Forms private void EnableCheck() { NavigationEnableCheck(); + ToolbarEnableCheck(); var hasValue = activeForm != null; @@ -45,6 +47,31 @@ namespace OpenNest.Forms mnuNestLastPlate.Enabled = hasPlates && !plates.IsLast; } + // The plate commands need a plate to act on and, like the Nest and Plate menus, no + // running job. Trim/Extend works on existing cut-offs; Sequence by Path needs two parts. + private void ToolbarEnableCheck() + { + var plate = nestingInProgress ? null : activeForm?.PlateView?.Plate; + var hasPlate = plate != null; + var hasCutOffs = plate?.CutOffs.Count > 0; + + btnAutoNest.Enabled = hasPlate; + btnShowRemnants.Enabled = hasPlate; + btnCutOff.Enabled = hasPlate; + btnTrimExtendCutOff.Enabled = hasCutOffs; + mnuPlateTrimExtendCutOff.Enabled = hasCutOffs; + btnCutPlanner.Enabled = hasPlate; + btnSimulateCutting.Enabled = hasPlate; + btnSequenceByPath.Enabled = plate?.Parts.Count >= 2; + postButton.Enabled = hasPlate; + } + + private void EditForm_PlateChanged(object sender, EventArgs e) => ToolbarEnableCheck(); + + private void PlateView_PartAdded(object sender, ItemAddedEventArgs e) => ToolbarEnableCheck(); + + private void PlateView_PartRemoved(object sender, ItemRemovedEventArgs e) => ToolbarEnableCheck(); + private void SetNestingLockout(bool locked) { nestingInProgress = locked; @@ -53,6 +80,7 @@ namespace OpenNest.Forms mnuNest.Enabled = !locked; mnuPlate.Enabled = !locked; NavigationEnableCheck(locked); + ToolbarEnableCheck(); } private void EngineComboBox_SelectedIndexChanged(object sender, EventArgs e) diff --git a/OpenNest/Forms/MainForm.Undo.cs b/OpenNest/Forms/MainForm.Undo.cs index dd5fa7b5..c37412f9 100644 --- a/OpenNest/Forms/MainForm.Undo.cs +++ b/OpenNest/Forms/MainForm.Undo.cs @@ -105,7 +105,12 @@ namespace OpenNest.Forms UpdateUndoMenu(); } - private void History_Changed(object sender, EventArgs e) => UpdateUndoMenu(); + // Every recorded edit lands here, including those that add or remove a cut-off. + private void History_Changed(object sender, EventArgs e) + { + UpdateUndoMenu(); + ToolbarEnableCheck(); + } internal void UpdateUndoMenu() { diff --git a/OpenNest/Forms/MainForm.cs b/OpenNest/Forms/MainForm.cs index 50c3542d..0548d528 100644 --- a/OpenNest/Forms/MainForm.cs +++ b/OpenNest/Forms/MainForm.cs @@ -25,6 +25,9 @@ namespace OpenNest.Forms private readonly NestStorageController storage; private readonly ToolStripMenuItem storageModeMenu = new("Storage Mode..."); private readonly ToolStripMenuItem exportNestMenu = new("Export .nest..."); + private readonly ToolStripButton btnCutPlanner; + private readonly ToolStripButton btnSimulateCutting; + private readonly ToolStripButton btnSequenceByPath; private readonly ToolStripDropDownButton postButton = new() { Name = "btnPost", @@ -124,12 +127,15 @@ namespace OpenNest.Forms statusStripToolTips = new StatusStripToolTips(statusStrip1); // The hold list lives in the ledger label's tooltip. statusBar = new MainStatusBar(statusLabel1, locationStatusLabel, selectionStatusLabel, machineStatusLabel, materialStatusLabel, plateUtilStatusLabel, ledgerStatusLabel); - toolStrip1.Items.Add(CreateActionButton("btnCutPlanner", "Plan Cutting", - Resources.cut_planner, ToolbarPlanCutting_Click)); - toolStrip1.Items.Add(CreateActionButton("btnSimulateCutting", "Simulate Cutting", - Resources.cut_simulate, SimulateCutting_Click)); - toolStrip1.Items.Add(CreateActionButton("btnSequenceByPath", "Sequence by Path", - Resources.manual_sequence, SequenceByPath_Click)); + btnCutPlanner = CreateActionButton("btnCutPlanner", "Plan Cutting", + Resources.cut_planner, ToolbarPlanCutting_Click); + btnSimulateCutting = CreateActionButton("btnSimulateCutting", "Simulate Cutting", + Resources.cut_simulate, SimulateCutting_Click); + btnSequenceByPath = CreateActionButton("btnSequenceByPath", "Sequence by Path", + Resources.manual_sequence, SequenceByPath_Click); + toolStrip1.Items.Add(btnCutPlanner); + toolStrip1.Items.Add(btnSimulateCutting); + toolStrip1.Items.Add(btnSequenceByPath); toolStrip1.Items.Add(postButton); storageModeMenu.Click += StorageMode_Click; exportNestMenu.Click += ExportNest_Click; @@ -262,7 +268,12 @@ namespace OpenNest.Forms statusBar.Unbind(); if (activeForm != null) + { + activeForm.PlateChanged -= EditForm_PlateChanged; activeForm.PlateView.OverlapStateChanged -= OverlapStateChanged; + activeForm.PlateView.PartAdded -= PlateView_PartAdded; + activeForm.PlateView.PartRemoved -= PlateView_PartRemoved; + } // If nesting is in progress and the active form changed, cancel nesting if (nestingInProgress && nestingCts != null) @@ -280,7 +291,10 @@ namespace OpenNest.Forms if (activeForm == null) return; + activeForm.PlateChanged += EditForm_PlateChanged; activeForm.PlateView.OverlapStateChanged += OverlapStateChanged; + activeForm.PlateView.PartAdded += PlateView_PartAdded; + activeForm.PlateView.PartRemoved += PlateView_PartRemoved; var activeNest = activeForm.Nest; activeForm.PlateView.CanEditClamps = () => !ClampsInUse(activeNest); mnuViewDrawRapids.Checked = activeForm.PlateView.DrawRapid;