diff --git a/OpenNest.Core/PlateManager.cs b/OpenNest.Core/PlateManager.cs index a70ca2d6..17d0ebae 100644 --- a/OpenNest.Core/PlateManager.cs +++ b/OpenNest.Core/PlateManager.cs @@ -195,6 +195,54 @@ namespace OpenNest nest.Plates.RemoveAt(CurrentIndex); } + /// + /// Whether would change anything: more than one plate, or the + /// remaining plate still holds parts or cut-off definitions. + /// + public bool CanClearAll + { + get + { + if (Count == 0) + return false; + + var remaining = nest.Plates[0]; + return Count > 1 + || remaining.Parts.Count > 0 + || remaining.CutOffs.Count > 0 + || remaining.Quantity > 1; + } + } + + /// + /// Clears the nest: removes every plate after the first and empties the remaining + /// plate (parts and cut-off definitions), resetting its repeat quantity. Nest metadata + /// and plate defaults are preserved; the first plate's configuration stays as the + /// workspace sentinel — the same result as removing every other plate one at a time. + /// The plate count never reaches zero, so observers never see a null current plate. + /// + public void ClearAll() + { + if (Count == 0) + return; + + BeginBatch(); + try + { + while (nest.Plates.Count > 1) + nest.Plates.RemoveAt(nest.Plates.Count - 1); + + var remaining = nest.Plates[0]; + remaining.CutOffs.Clear(); + remaining.Parts.Clear(); + remaining.Quantity = 1; + } + finally + { + EndBatch(); + } + } + private void SubscribeToTailPlates() { UnsubscribeFromTailPlates(); diff --git a/OpenNest.Tests/PlateManagerTests.cs b/OpenNest.Tests/PlateManagerTests.cs index a5bff2a7..5a047483 100644 --- a/OpenNest.Tests/PlateManagerTests.cs +++ b/OpenNest.Tests/PlateManagerTests.cs @@ -399,6 +399,166 @@ public class PlateManagerTests Assert.True(mgr.CanRemoveCurrent); } + [Fact] + public void ClearAll_RemovesAllButFirstAndEmptiesRemaining() + { + var nest = CreateNest(); + var plate1 = nest.CreatePlate(); + plate1.Parts.Add(MakePart()); + var plate2 = nest.CreatePlate(); + plate2.Parts.Add(MakePart()); + var plate3 = nest.CreatePlate(); + plate3.Parts.Add(MakePart()); + plate3.CutOffs.Add(new CutOff(new Vector(1, 1), CutOffAxis.Vertical)); + plate3.Quantity = 5; + using var mgr = new PlateManager(nest); + mgr.EnsureSentinel(); + Assert.Equal(4, nest.Plates.Count); + mgr.LoadAt(2); + + mgr.ClearAll(); + + // The first plate survives, empty, with its repeat quantity reset + Assert.Single(nest.Plates); + Assert.Same(plate1, nest.Plates[0]); + Assert.Empty(nest.Plates[0].Parts); + Assert.Empty(nest.Plates[0].CutOffs); + Assert.Equal(1, nest.Plates[0].Quantity); + Assert.Equal(0, mgr.CurrentIndex); + Assert.Same(plate1, mgr.CurrentPlate); + } + + [Fact] + public void ClearAll_NeverExposesNullCurrentPlate() + { + var nest = CreateNest(); + for (var i = 0; i < 3; i++) + { + var plate = nest.CreatePlate(); + plate.Parts.Add(MakePart()); + } + using var mgr = new PlateManager(nest); + mgr.EnsureSentinel(); + + var currentPlates = new List(); + mgr.CurrentPlateChanged += (s, e) => currentPlates.Add(e.Plate); + + mgr.ClearAll(); + + // Every event during the clear reported a live plate (the one that stays) + Assert.NotEmpty(currentPlates); + Assert.All(currentPlates, p => Assert.NotNull(p)); + Assert.All(currentPlates, p => Assert.Same(nest.Plates[0], p)); + } + + [Fact] + public void ClearAll_FiresPlateListAndCurrentPlateChanged() + { + var nest = CreateNest(); + var plate1 = nest.CreatePlate(); + plate1.Parts.Add(MakePart()); + nest.CreatePlate(); + using var mgr = new PlateManager(nest); + mgr.EnsureSentinel(); + + var listChanged = 0; + var currentChanged = 0; + mgr.PlateListChanged += (s, e) => listChanged++; + mgr.CurrentPlateChanged += (s, e) => currentChanged++; + + mgr.ClearAll(); + + Assert.True(listChanged > 0); + Assert.True(currentChanged > 0); + } + + [Fact] + public void ClearAll_SinglePlateWithParts_EmptiesIt() + { + var nest = CreateNest(); + var plate1 = nest.CreatePlate(); + plate1.Parts.Add(MakePart()); + plate1.CutOffs.Add(new CutOff(new Vector(2, 2), CutOffAxis.Horizontal)); + plate1.Quantity = 3; + using var mgr = new PlateManager(nest); + + mgr.ClearAll(); + + Assert.Single(nest.Plates); + Assert.Same(plate1, nest.Plates[0]); + Assert.Empty(plate1.Parts); + Assert.Empty(plate1.CutOffs); + Assert.Equal(1, plate1.Quantity); + } + + [Fact] + public void ClearAll_PreservesNestMetadataAndFirstPlateConfiguration() + { + var nest = CreateNest(); + nest.Name = "my nest"; + nest.Thickness = 0.25; + var defaults = nest.PlateDefaults.Size; + var plate1 = nest.CreatePlate(); + plate1.Parts.Add(MakePart()); + // Edit the survivor away from the defaults so replacement would be visible + plate1.Size = new Size(99, 88); + plate1.PartSpacing = 0.4; + nest.CreatePlate(); + using var mgr = new PlateManager(nest); + + mgr.ClearAll(); + + Assert.Equal("my nest", nest.Name); + Assert.Equal(0.25, nest.Thickness); + // The survivor keeps its own configuration; it was not replaced by a default plate + Assert.Same(plate1, nest.Plates[0]); + Assert.Equal(new Size(99, 88), nest.Plates[0].Size); + Assert.Equal(0.4, nest.Plates[0].PartSpacing); + Assert.Equal(defaults, nest.PlateDefaults.Size); + } + + [Fact] + public void ClearAll_EmptyNest_DoesNothing() + { + var nest = CreateNest(); + using var mgr = new PlateManager(nest); + + mgr.ClearAll(); + + Assert.Empty(nest.Plates); + } + + [Fact] + public void CanClearAll_ReflectsClearableState() + { + var nest = CreateNest(); + using var mgr = new PlateManager(nest); + + Assert.False(mgr.CanClearAll); + + var plate1 = nest.CreatePlate(); + Assert.False(mgr.CanClearAll); // a single untouched empty plate has nothing to clear + + plate1.Quantity = 2; + Assert.True(mgr.CanClearAll); + plate1.Quantity = 1; + Assert.False(mgr.CanClearAll); + + plate1.CutOffs.Add(new CutOff(new Vector(0, 0), CutOffAxis.Vertical)); + Assert.True(mgr.CanClearAll); + plate1.CutOffs.Clear(); + Assert.False(mgr.CanClearAll); + + plate1.Parts.Add(MakePart()); + Assert.Equal(2, nest.Plates.Count); // trailing sentinel created reactively + Assert.True(mgr.CanClearAll); // multiple plates are clearable + + plate1.Parts.Clear(); + // The reactive sentinel collapses the now-trailing empty pair back to one plate + Assert.Single(nest.Plates); + Assert.False(mgr.CanClearAll); + } + [Fact] public void RemoveEmptyPlates_SentinelIsRestored() { diff --git a/OpenNest/Forms/EditNestForm.cs b/OpenNest/Forms/EditNestForm.cs index 13de566b..7b74b388 100644 --- a/OpenNest/Forms/EditNestForm.cs +++ b/OpenNest/Forms/EditNestForm.cs @@ -894,6 +894,11 @@ namespace OpenNest.Forms PlateManager.RemoveCurrent(); } + public void ClearNest() + { + PlateManager.ClearAll(); + } + public void AutoSequenceCurrentPlate() { SequencePlate(PlateView.Plate); diff --git a/OpenNest/Forms/MainForm.Designer.cs b/OpenNest/Forms/MainForm.Designer.cs index b770d868..f4c69920 100644 --- a/OpenNest/Forms/MainForm.Designer.cs +++ b/OpenNest/Forms/MainForm.Designer.cs @@ -102,6 +102,7 @@ namespace OpenNest.Forms runAutoNestToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); autoSequenceAllPlatesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); mnuNestRemoveEmptyPlates = new System.Windows.Forms.ToolStripMenuItem(); + mnuNestClear = new System.Windows.Forms.ToolStripMenuItem(); mnuNestValidate = new System.Windows.Forms.ToolStripMenuItem(); mnuNestPost = new System.Windows.Forms.ToolStripMenuItem(); toolStripMenuItem19 = new System.Windows.Forms.ToolStripSeparator(); @@ -573,7 +574,7 @@ namespace OpenNest.Forms // // mnuNest // - mnuNest.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { mnuNestEdit, mnuNestClamps, mnuNestImportDrawing, mnuNestShapeLibrary, mnuNestDrawingLibrary, toolStripMenuItem7, mnuNestFirstPlate, mnuNestLastPlate, toolStripMenuItem6, mnuNestNextPlate, mnuNestPreviousPlate, toolStripMenuItem12, runAutoNestToolStripMenuItem, autoSequenceAllPlatesToolStripMenuItem, mnuNestRemoveEmptyPlates, mnuNestValidate, mnuNestPost, toolStripMenuItem19, calculateCutTimeToolStripMenuItem, toolStripMenuItem22, mnuNestAutomaticCutOff, mnuNestPlanCutting, mnuNestAssignLeadIns, mnuNestRemoveLeadIns }); + mnuNest.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { mnuNestEdit, mnuNestClamps, mnuNestImportDrawing, mnuNestShapeLibrary, mnuNestDrawingLibrary, toolStripMenuItem7, mnuNestFirstPlate, mnuNestLastPlate, toolStripMenuItem6, mnuNestNextPlate, mnuNestPreviousPlate, toolStripMenuItem12, runAutoNestToolStripMenuItem, autoSequenceAllPlatesToolStripMenuItem, mnuNestRemoveEmptyPlates, mnuNestClear, mnuNestValidate, mnuNestPost, toolStripMenuItem19, calculateCutTimeToolStripMenuItem, toolStripMenuItem22, mnuNestAutomaticCutOff, mnuNestPlanCutting, mnuNestAssignLeadIns, mnuNestRemoveLeadIns }); mnuNest.Name = "mnuNest"; mnuNest.Size = new System.Drawing.Size(43, 20); mnuNest.Text = "&Nest"; @@ -685,6 +686,13 @@ namespace OpenNest.Forms mnuNestRemoveEmptyPlates.Text = "Remove Empty Plates"; mnuNestRemoveEmptyPlates.Click += RemoveEmptyPlates_Click; // + // mnuNestClear + // + mnuNestClear.Name = "mnuNestClear"; + mnuNestClear.Size = new System.Drawing.Size(205, 22); + mnuNestClear.Text = "Clear Nest..."; + mnuNestClear.Click += ClearNest_Click; + // // mnuNestValidate // mnuNestValidate.Name = "mnuNestValidate"; @@ -1401,6 +1409,7 @@ namespace OpenNest.Forms private System.Windows.Forms.ToolStripButton btnNew; private System.Windows.Forms.ToolStripMenuItem mnuPlateAdd; private System.Windows.Forms.ToolStripMenuItem mnuNestRemoveEmptyPlates; + private System.Windows.Forms.ToolStripMenuItem mnuNestClear; private System.Windows.Forms.ToolStripMenuItem mnuViewZoomIn; private System.Windows.Forms.ToolStripMenuItem mnuViewZoomOut; private System.Windows.Forms.ToolStripSeparator toolStripMenuItem14; diff --git a/OpenNest/Forms/MainForm.File.cs b/OpenNest/Forms/MainForm.File.cs index f984e2b2..d72b4866 100644 --- a/OpenNest/Forms/MainForm.File.cs +++ b/OpenNest/Forms/MainForm.File.cs @@ -26,6 +26,7 @@ namespace OpenNest.Forms NavigationEnableCheck(); statusBar.UpdatePlate(); mnuPlateRemove.Enabled = activeForm?.PlateManager.CanRemoveCurrent ?? false; + mnuNestClear.Enabled = activeForm?.PlateManager.CanClearAll ?? false; }; editForm.WindowState = windowState; editForm.Show(); diff --git a/OpenNest/Forms/MainForm.Nest.cs b/OpenNest/Forms/MainForm.Nest.cs index c8ba479e..efb9bb82 100644 --- a/OpenNest/Forms/MainForm.Nest.cs +++ b/OpenNest/Forms/MainForm.Nest.cs @@ -43,6 +43,8 @@ namespace OpenNest.Forms mnuNest.DropDown.ShowItemToolTips = true; mnuNestDrawingLibrary.Enabled = activeForm != null && databaseMode; mnuNestDrawingLibrary.ToolTipText = databaseMode ? "" : NestStorage.DatabaseModeOnly; + // Parts can be added without a plate change, so refresh the clear state on open. + mnuNestClear.Enabled = activeForm?.PlateManager.CanClearAll ?? false; } private async void EditNest_Click(object sender, EventArgs e) @@ -85,6 +87,37 @@ namespace OpenNest.Forms private void RemoveEmptyPlates_Click(object sender, EventArgs e) => WithActive(f => f.Nest.Plates.RemoveEmptyPlates()); + private void ClearNest_Click(object sender, EventArgs e) + { + var editForm = activeForm; + if (editForm == null) + return; + + const string busy = "Finish or cancel the current nesting, save or plate action first."; + if (storage.IsSaving) + { + MessageBox.Show(this, busy, "Clear Nest", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + if (!TryBeginPlateCommand(editForm.Nest, busy, "Clear Nest")) + return; + + var result = MessageBox.Show( + this, + "Remove all plates and parts from this nest?\nOne empty plate will remain. This cannot be undone.", + "Clear Nest", + MessageBoxButtons.YesNo, + MessageBoxIcon.Warning, + MessageBoxDefaultButton.Button2 + ); + + if (result != DialogResult.Yes) + return; + + editForm.ClearNest(); + } + private void LoadFirstPlate_Click(object sender, EventArgs e) => WithActive(f => f.PlateManager.LoadFirst()); private void LoadLastPlate_Click(object sender, EventArgs e) => WithActive(f => f.PlateManager.LoadLast()); diff --git a/OpenNest/Forms/MainForm.Status.cs b/OpenNest/Forms/MainForm.Status.cs index c781a220..03145eb8 100644 --- a/OpenNest/Forms/MainForm.Status.cs +++ b/OpenNest/Forms/MainForm.Status.cs @@ -15,6 +15,7 @@ namespace OpenNest.Forms btnSave.Enabled = hasValue && !storage.IsSaving; btnSaveAs.Enabled = hasValue && !storage.IsSaving; mnuFileSaveAs.Enabled = hasValue && !storage.IsSaving; + mnuNestClear.Enabled = activeForm?.PlateManager.CanClearAll ?? false; exportNestMenu.Enabled = hasValue; mnuFileExport.Enabled = hasValue; mnuFileExportAll.Enabled = hasValue;