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;