mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 06:01:06 -04:00
feat(desktop): add Nest > Clear Nest
Clear Nest removes every plate after the first and empties the remaining plate (parts, cut-offs, repeat quantity), keeping nest metadata and the first plate's configuration. It asks first, defaulting to No, and refuses while nesting, a fill, a busy plate action or a database save is running. The menu item is enabled only when there is something to clear.
This commit is contained in:
1 parent
1ed66bbf20
commit
274651b810
7 files changed
+258
-1
No files matched your search
@@ -195,6 +195,54 @@ namespace OpenNest
|
||||
nest.Plates.RemoveAt(CurrentIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether <see cref="ClearAll"/> would change anything: more than one plate, or the
|
||||
/// remaining plate still holds parts or cut-off definitions.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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();
|
||||
|
||||
@@ -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<Plate>();
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -894,6 +894,11 @@ namespace OpenNest.Forms
|
||||
PlateManager.RemoveCurrent();
|
||||
}
|
||||
|
||||
public void ClearNest()
|
||||
{
|
||||
PlateManager.ClearAll();
|
||||
}
|
||||
|
||||
public void AutoSequenceCurrentPlate()
|
||||
{
|
||||
SequencePlate(PlateView.Plate);
|
||||
|
||||
Generated
+10
-1
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user