fix(ui): keep an add-new row in Auto Nest stock options
This commit is contained in:
@@ -0,0 +1,109 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.ExceptionServices;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using OpenNest.Forms;
|
||||||
|
|
||||||
|
namespace OpenNest.WinForms.Tests.Forms;
|
||||||
|
|
||||||
|
public class AutoNestFormStockOptionsTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void StockOptionsKeepAnAddRowAfterAddingAPlate(bool loadSavedOptions)
|
||||||
|
{
|
||||||
|
RunSta(() =>
|
||||||
|
{
|
||||||
|
using var form = new AutoNestForm(new Nest());
|
||||||
|
if (loadSavedOptions)
|
||||||
|
form.LoadPlateOptions(new List<PlateOption>
|
||||||
|
{
|
||||||
|
new() { Width = 48, Length = 96, Cost = 25 },
|
||||||
|
}, 0.5);
|
||||||
|
form.OptimizePlateSize = true;
|
||||||
|
var grid = GetGrid(form);
|
||||||
|
var initialCount = form.GetPlateOptions().Count;
|
||||||
|
|
||||||
|
AssertNewRow(grid);
|
||||||
|
var rows = Assert.IsAssignableFrom<IBindingList>(grid.DataSource);
|
||||||
|
Assert.True(rows.AllowNew);
|
||||||
|
var added = rows.AddNew()!;
|
||||||
|
SetValue(added, "Size", "84 x 168");
|
||||||
|
SetValue(added, "Cost", 123.5);
|
||||||
|
((ICancelAddNew)rows).EndNew(rows.Count - 1);
|
||||||
|
|
||||||
|
var options = form.GetPlateOptions();
|
||||||
|
Assert.Equal(initialCount + 1, options.Count);
|
||||||
|
var option = options.Last();
|
||||||
|
Assert.Equal(84, option.Width);
|
||||||
|
Assert.Equal(168, option.Length);
|
||||||
|
Assert.Equal(123.5, option.Cost);
|
||||||
|
AssertNewRow(grid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BlankAndCancelledRowsDoNotBecomeStockOptions()
|
||||||
|
{
|
||||||
|
RunSta(() =>
|
||||||
|
{
|
||||||
|
using var form = new AutoNestForm(new Nest());
|
||||||
|
var saved = new List<PlateOption>
|
||||||
|
{
|
||||||
|
new() { Width = 48, Length = 96, Cost = 25 },
|
||||||
|
};
|
||||||
|
form.LoadPlateOptions(saved, 0.5);
|
||||||
|
var grid = GetGrid(form);
|
||||||
|
var rows = Assert.IsAssignableFrom<IBindingList>(grid.DataSource);
|
||||||
|
var addNew = Assert.IsAssignableFrom<ICancelAddNew>(rows);
|
||||||
|
|
||||||
|
rows.AddNew();
|
||||||
|
Assert.Single(form.GetPlateOptions());
|
||||||
|
addNew.CancelNew(rows.Count - 1);
|
||||||
|
Assert.Single((IEnumerable)rows);
|
||||||
|
|
||||||
|
rows.Clear();
|
||||||
|
Assert.Empty(form.GetPlateOptions());
|
||||||
|
AssertNewRow(grid);
|
||||||
|
Assert.Single(saved);
|
||||||
|
Assert.Equal(48, saved[0].Width);
|
||||||
|
Assert.Equal(96, saved[0].Length);
|
||||||
|
Assert.Equal(25, saved[0].Cost);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DataGridView GetGrid(AutoNestForm form)
|
||||||
|
{
|
||||||
|
// Binding needs a context even when the test does not show the form.
|
||||||
|
form.BindingContext = new BindingContext();
|
||||||
|
var grid = Assert.IsType<DataGridView>(form.Controls.Find("plateGrid", true).Single());
|
||||||
|
grid.CreateControl();
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertNewRow(DataGridView grid)
|
||||||
|
{
|
||||||
|
Assert.True(grid.AllowUserToAddRows);
|
||||||
|
Assert.True(grid.NewRowIndex >= 0);
|
||||||
|
Assert.True(grid.Rows[grid.NewRowIndex].IsNewRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetValue(object row, string property, object value) =>
|
||||||
|
TypeDescriptor.GetProperties(row)[property]!.SetValue(row, value);
|
||||||
|
|
||||||
|
private static void RunSta(Action action)
|
||||||
|
{
|
||||||
|
Exception? error = null;
|
||||||
|
var thread = new Thread(() =>
|
||||||
|
{
|
||||||
|
try { action(); }
|
||||||
|
catch (Exception ex) { error = ex; }
|
||||||
|
});
|
||||||
|
thread.SetApartmentState(ApartmentState.STA);
|
||||||
|
thread.Start();
|
||||||
|
Assert.True(thread.Join(TimeSpan.FromSeconds(15)));
|
||||||
|
if (error != null)
|
||||||
|
ExceptionDispatchInfo.Capture(error).Throw();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -195,8 +195,9 @@ namespace OpenNest.Forms
|
|||||||
|
|
||||||
public List<PlateOption> GetPlateOptions()
|
public List<PlateOption> GetPlateOptions()
|
||||||
{
|
{
|
||||||
|
plateGrid.EndEdit();
|
||||||
var result = new List<PlateOption>();
|
var result = new List<PlateOption>();
|
||||||
var gridItems = plateGrid.DataSource as List<PlateOptionItem>;
|
var gridItems = plateGrid.DataSource as BindingList<PlateOptionItem>;
|
||||||
if (gridItems == null)
|
if (gridItems == null)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
@@ -231,7 +232,7 @@ namespace OpenNest.Forms
|
|||||||
Cost = o.Cost,
|
Cost = o.Cost,
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
plateGrid.DataSource = items;
|
plateGrid.DataSource = new BindingList<PlateOptionItem>(items);
|
||||||
optimizePlateSizeBox.Checked = true;
|
optimizePlateSizeBox.Checked = true;
|
||||||
}
|
}
|
||||||
SalvageRate = salvageRate;
|
SalvageRate = salvageRate;
|
||||||
@@ -239,7 +240,8 @@ namespace OpenNest.Forms
|
|||||||
|
|
||||||
private void LoadDefaultPlateOptions()
|
private void LoadDefaultPlateOptions()
|
||||||
{
|
{
|
||||||
var items = new List<PlateOptionItem>
|
// A bound DataGridView needs IBindingList.AddNew support for its blank last row.
|
||||||
|
var items = new BindingList<PlateOptionItem>
|
||||||
{
|
{
|
||||||
new() { Size = "48 x 96", Cost = 0 },
|
new() { Size = "48 x 96", Cost = 0 },
|
||||||
new() { Size = "48 x 120", Cost = 0 },
|
new() { Size = "48 x 120", Cost = 0 },
|
||||||
@@ -385,6 +387,8 @@ namespace OpenNest.Forms
|
|||||||
|
|
||||||
private class PlateOptionItem
|
private class PlateOptionItem
|
||||||
{
|
{
|
||||||
|
public PlateOptionItem() { }
|
||||||
|
|
||||||
public string Size { get; set; }
|
public string Size { get; set; }
|
||||||
public double Cost { get; set; }
|
public double Cost { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
Every engine selected in **Nest > Auto Nest** uses the pipeline. Part-First and its sorting controls are removed. Use interactive Fill Area/remnant tools for leftover space on occupied sheets; these are not whole-job nesting.
|
Every engine selected in **Nest > Auto Nest** uses the pipeline. Part-First and its sorting controls are removed. Use interactive Fill Area/remnant tools for leftover space on occupied sheets; these are not whole-job nesting.
|
||||||
|
|
||||||
Auto Nest starts on empty/new sheets and does not change existing populated plates. Stock Options offers sheet sizes and salvage settings; minimum salvage size is part of that section, not a Part-First option. The maximum remains 100 physical sheets per run.
|
Auto Nest starts on empty/new sheets and does not change existing populated plates. Stock Options offers sheet sizes and salvage settings; minimum salvage size is part of that section, not a Part-First option. When enabled, its grid keeps a blank last row for adding another size (`W x L`) and cost, including after loading saved options. Unused blank rows are not offered as stock. The maximum remains 100 physical sheets per run.
|
||||||
|
|
||||||
Progress is owned and modal so the input drawings cannot be edited while a worker uses them. Stop or closing progress cancels and discards the whole proposal. The dialog waits for the worker to finish; no engine has an Accept-early button on this path. A plug-in that ignores the cancellation token cannot commit its late result.
|
Progress is owned and modal so the input drawings cannot be edited while a worker uses them. Stop or closing progress cancels and discards the whole proposal. The dialog waits for the worker to finish; no engine has an Accept-early button on this path. A plug-in that ignores the cancellation token cannot commit its late result.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user