feat(desktop): preview the release and fold its options away

The release dialog now reads as a sentence ("Release set 'X' ×N") and
previews what Make Nests will do ("Creates 2 nests: ..."), with refused
materials counted and Fix Drawings reachable without selecting them.
An Options toggle holds the Release ticks, the Nest column and Include
archived drawings; collapsed, an option stays shown while it differs
from its default, and the Nest column shows whenever a Draft nest could
be merged into. Defaults, release, undo and retry are unchanged. The
Status column wraps so refusals are not cut off, and the retry status
reads "... Try Again records it; the nests are not made again."

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-10-24 15:33:13 -04:00
1 parent ee208b2766
commit 49bb7389f7
4 files changed
+297 -44

No files matched your search

@@ -114,6 +114,72 @@ public class SetReleaseFormsTests
"The release choice test did not complete.");
}
[Fact]
public void Release_PreviewsTheNestsWithOptionsCollapsed_AndKeepsChangedOptionsShown()
{
StaTestThread.Run(
() =>
{
var library = new PackageLibrary();
var a = library.Add("SYN-A", "Synthetic Steel");
var c = library.Add("SYN-C", "Synthetic Aluminum");
var old = library.Add("SYN-OLD", "Synthetic Steel", archived: true);
var sets = new InMemoryDrawingSets();
var set = CreateSet(sets, (a, 2), (c, 1), (old, 1));
var nests = new MemoryNests();
using var form = new ReleaseSetForm(sets, library, nests, Server, set, (_, _) => { });
form.ShowMessage = (_, _, _) => DialogResult.OK;
form.CreateNest = (group, _) => Task.FromResult(new Nest("N") { Units = group.Units });
form.Show();
var grid = Field<DataGridView>(form, "groupGrid");
var archived = Field<CheckBox>(form, "includeArchivedCheckBox");
var options = Field<Button>(form, "optionsButton");
var preview = Field<Label>(form, "previewLabel");
Run(form.LoadDraftsAsync());
// Collapsed: the defaults, read-only, with no draft to merge into.
Assert.False(form.OptionsExpanded);
Assert.Equal("Release set 'Synthetic kit' ×1", Field<Label>(form, "setLabel").Text);
Assert.Equal("Creates 2 nests: Synthetic Aluminum Grade A 0.25 in, Synthetic Steel Grade A 0.25 in.", preview.Text);
Assert.Equal((false, false, false), (grid.Columns["includeColumn"]!.Visible, grid.Columns["targetColumn"]!.Visible, archived.Visible));
Assert.Contains("under Options", Field<Label>(form, "skippedLabel").Text);
Assert.True(grid.Rows.Cast<DataGridViewRow>().All(r => (bool)r.Cells["includeColumn"].Value));
Assert.Equal(new object[] { "New Draft nest", "New Draft nest" }, grid.Rows.Cast<DataGridViewRow>().Select(r => r.Cells["targetColumn"].Value));
Assert.True(Field<CheckBox>(form, "nestAfterReleaseCheckBox").Checked);
Field<DataGridView>(form, "setGrid").Rows[0].Cells["kitsColumn"].Value = "3";
Assert.Equal("Release set 'Synthetic kit' ×3", Field<Label>(form, "setLabel").Text);
options.PerformClick();
Assert.True(form.OptionsExpanded);
Assert.Equal("Options ▾", options.Text);
Assert.Equal((true, true, true), (grid.Columns["includeColumn"]!.Visible, grid.Columns["targetColumn"]!.Visible, archived.Visible));
// Options left changed stay shown when collapsed; the defaults hide again.
grid.Rows[0].Cells["includeColumn"].Value = false;
archived.Checked = true;
options.PerformClick();
Assert.Equal("Options ▸", options.Text);
Assert.Equal((true, false, true), (grid.Columns["includeColumn"]!.Visible, grid.Columns["targetColumn"]!.Visible, archived.Visible));
Assert.Equal("Creates 1 nest: Synthetic Steel Grade A 0.25 in. 1 material is left out (unticked under Release).", preview.Text);
grid.Rows[0].Cells["includeColumn"].Value = true;
archived.Checked = false;
// Nothing vanishes while collapsed until Options is toggled.
Assert.True(grid.Columns["includeColumn"]!.Visible);
options.PerformClick();
options.PerformClick();
Assert.Equal((false, false, false), (grid.Columns["includeColumn"]!.Visible, grid.Columns["targetColumn"]!.Visible, archived.Visible));
Run(form.ReleaseAsync());
Assert.Equal(2, Assert.Single(sets.Releases).Nests.Count);
},
TestTimeout,
"The collapsed options test did not complete.");
}
[Fact]
public void FixDrawings_EditsEachDrawingTheGroupIsRefusedFor_ThenTheGroupCanBeReleased()
{
@@ -143,14 +209,16 @@ public class SetReleaseFormsTests
var fix = Field<Button>(form, "fixDrawingsButton");
var refused = grid.Rows.Cast<DataGridViewRow>().Single(r => ((SetReleaseGroup)r.Tag!).IsUnassigned);
Assert.Contains("Edit Drawing", (string)refused.Cells["statusColumn"].Value);
Assert.Contains("1 material can't be released yet: choose Fix Drawings.", Field<Label>(form, "previewLabel").Text);
// With a releasable group selected, Fix Drawings takes the refused one.
grid.CurrentCell = grid.Rows[0].Cells["materialColumn"];
Assert.False(fix.Enabled);
grid.CurrentCell = refused.Cells["materialColumn"];
Assert.NotSame(refused, grid.CurrentRow);
Assert.True(fix.Enabled);
Run(form.FixSelectedGroupAsync());
Assert.Equal(new[] { "SYN-B", "SYN-C" }, edits);
Assert.False(fix.Enabled);
var group = Assert.IsType<SetReleaseGroup>(Assert.Single(grid.Rows.Cast<DataGridViewRow>()).Tag);
Assert.True(group.CanRelease);
Assert.Equal(new[] { ("SYN-A", 1), ("SYN-B", 2), ("SYN-C", 2) }, group.Items.Select(i => (i.Drawing.Name, i.Drawing.Revision)));
@@ -188,7 +256,8 @@ public class SetReleaseFormsTests
Assert.Equal(1, opened);
Assert.Empty(sets.Releases);
Assert.Equal(("Try Again", true), (release.Text, release.Enabled));
Assert.Contains("Synthetic release post failure.", Field<Label>(form, "statusLabel").Text);
Assert.EndsWith("Synthetic release post failure. Try Again records it; the nests are not made again.",
Field<Label>(form, "statusLabel").Text);
Run(form.RetryRecordAsync());
@@ -273,6 +342,10 @@ public class SetReleaseFormsTests
var grid = Field<DataGridView>(form, "groupGrid");
DataGridViewRow Steel() => grid.Rows.Cast<DataGridViewRow>().Single(r => ((SetReleaseGroup)r.Tag!).MaterialName == "Synthetic Steel");
PumpUntil(() => ((DataGridViewComboBoxCell)Steel().Cells["targetColumn"]).Items.Count == 2, "the drafts to merge into");
// A draft to merge into shows the Nest column while Options is collapsed.
Assert.False(form.OptionsExpanded);
Assert.True(grid.Columns["targetColumn"]!.Visible);
Assert.Contains("A Draft nest of the same material exists", Field<Label>(form, "previewLabel").Text);
Assert.Equal(new object[] { 1, 4 }, new[] { a, b }.Select(d => (object)((SetReleaseGroup)Steel().Tag!).Items.Single(i => i.Drawing.Id == d.Id).Quantity));
var mergeChoice = (string)((DataGridViewComboBoxCell)Steel().Cells["targetColumn"]).Items[1]!;
Assert.StartsWith("Merge into N26-0007 (1 sheet", mergeChoice);
@@ -288,6 +361,8 @@ public class SetReleaseFormsTests
Assert.Same(draft, form.ChosenMerges().Values.Single());
Assert.Contains("Replaces the layout of 'N26-0007'", (string)Steel().Cells["statusColumn"].Value);
Assert.Contains("1 new Draft nest, 1 merge, 6 parts", Field<Label>(form, "summaryLabel").Text);
Assert.Contains("Creates 1 nest and merges into 1 Draft nest: ", Field<Label>(form, "previewLabel").Text);
Assert.Contains("Synthetic Steel Grade A 0.25 in into 'N26-0007'", Field<Label>(form, "previewLabel").Text);
Run(form.ReleaseAsync());
+67 -20
View File
@@ -41,6 +41,9 @@ namespace OpenNest.Forms
nestAfterReleaseCheckBox = new System.Windows.Forms.CheckBox();
notesLabel = new System.Windows.Forms.Label();
notesBox = new System.Windows.Forms.TextBox();
previewTable = new System.Windows.Forms.TableLayoutPanel();
previewLabel = new System.Windows.Forms.Label();
optionsButton = new System.Windows.Forms.Button();
groupGrid = new System.Windows.Forms.DataGridView();
includeColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn();
materialColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
@@ -65,6 +68,7 @@ namespace OpenNest.Forms
addSetTable.SuspendLayout();
((System.ComponentModel.ISupportInitialize)setGrid).BeginInit();
optionsTable.SuspendLayout();
previewTable.SuspendLayout();
((System.ComponentModel.ISupportInitialize)groupGrid).BeginInit();
bottomTable.SuspendLayout();
SuspendLayout();
@@ -77,19 +81,21 @@ namespace OpenNest.Forms
layoutTable.Controls.Add(addSetTable, 0, 1);
layoutTable.Controls.Add(setGrid, 0, 2);
layoutTable.Controls.Add(optionsTable, 0, 3);
layoutTable.Controls.Add(groupGrid, 0, 4);
layoutTable.Controls.Add(skippedLabel, 0, 5);
layoutTable.Controls.Add(bottomTable, 0, 6);
layoutTable.Controls.Add(statusLabel, 0, 7);
layoutTable.Controls.Add(previewTable, 0, 4);
layoutTable.Controls.Add(groupGrid, 0, 5);
layoutTable.Controls.Add(skippedLabel, 0, 6);
layoutTable.Controls.Add(bottomTable, 0, 7);
layoutTable.Controls.Add(statusLabel, 0, 8);
layoutTable.Dock = System.Windows.Forms.DockStyle.Fill;
layoutTable.Location = new System.Drawing.Point(0, 0);
layoutTable.Name = "layoutTable";
layoutTable.Padding = new System.Windows.Forms.Padding(8);
layoutTable.RowCount = 8;
layoutTable.RowCount = 9;
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 130F));
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
@@ -238,15 +244,13 @@ namespace OpenNest.Forms
// optionsTable
//
optionsTable.AutoSize = true;
optionsTable.ColumnCount = 4;
optionsTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
optionsTable.ColumnCount = 3;
optionsTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
optionsTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
optionsTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
optionsTable.Controls.Add(includeArchivedCheckBox, 0, 0);
optionsTable.Controls.Add(nestAfterReleaseCheckBox, 1, 0);
optionsTable.Controls.Add(notesLabel, 2, 0);
optionsTable.Controls.Add(notesBox, 3, 0);
optionsTable.Controls.Add(nestAfterReleaseCheckBox, 0, 0);
optionsTable.Controls.Add(notesLabel, 1, 0);
optionsTable.Controls.Add(notesBox, 2, 0);
optionsTable.Dock = System.Windows.Forms.DockStyle.Fill;
optionsTable.Margin = new System.Windows.Forms.Padding(0, 0, 0, 6);
optionsTable.Name = "optionsTable";
@@ -258,8 +262,10 @@ namespace OpenNest.Forms
//
includeArchivedCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Left;
includeArchivedCheckBox.AutoSize = true;
includeArchivedCheckBox.Margin = new System.Windows.Forms.Padding(12, 3, 3, 3);
includeArchivedCheckBox.Name = "includeArchivedCheckBox";
includeArchivedCheckBox.TabIndex = 0;
includeArchivedCheckBox.TabIndex = 1;
includeArchivedCheckBox.Visible = false;
includeArchivedCheckBox.Text = "Include archived drawings";
includeArchivedCheckBox.UseVisualStyleBackColor = true;
includeArchivedCheckBox.CheckedChanged += Options_Changed;
@@ -270,9 +276,8 @@ namespace OpenNest.Forms
nestAfterReleaseCheckBox.AutoSize = true;
nestAfterReleaseCheckBox.Checked = true;
nestAfterReleaseCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
nestAfterReleaseCheckBox.Margin = new System.Windows.Forms.Padding(12, 3, 3, 3);
nestAfterReleaseCheckBox.Name = "nestAfterReleaseCheckBox";
nestAfterReleaseCheckBox.TabIndex = 1;
nestAfterReleaseCheckBox.TabIndex = 0;
nestAfterReleaseCheckBox.Text = "Nest after release";
nestAfterReleaseCheckBox.UseVisualStyleBackColor = true;
nestAfterReleaseCheckBox.CheckedChanged += NestAfterReleaseCheckBox_CheckedChanged;
@@ -283,7 +288,7 @@ namespace OpenNest.Forms
notesLabel.AutoSize = true;
notesLabel.Margin = new System.Windows.Forms.Padding(12, 0, 3, 0);
notesLabel.Name = "notesLabel";
notesLabel.TabIndex = 2;
notesLabel.TabIndex = 1;
notesLabel.Text = "Release notes:";
//
// notesBox
@@ -291,7 +296,42 @@ namespace OpenNest.Forms
notesBox.Dock = System.Windows.Forms.DockStyle.Fill;
notesBox.MaxLength = 4000;
notesBox.Name = "notesBox";
notesBox.TabIndex = 3;
notesBox.TabIndex = 2;
//
// previewTable
//
previewTable.AutoSize = true;
previewTable.ColumnCount = 3;
previewTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
previewTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
previewTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
previewTable.Controls.Add(previewLabel, 0, 0);
previewTable.Controls.Add(includeArchivedCheckBox, 1, 0);
previewTable.Controls.Add(optionsButton, 2, 0);
previewTable.Dock = System.Windows.Forms.DockStyle.Fill;
previewTable.Margin = new System.Windows.Forms.Padding(0, 0, 0, 3);
previewTable.Name = "previewTable";
previewTable.RowCount = 1;
previewTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
previewTable.TabIndex = 4;
//
// previewLabel
//
previewLabel.AutoSize = true;
previewLabel.Dock = System.Windows.Forms.DockStyle.Fill;
previewLabel.Margin = new System.Windows.Forms.Padding(3, 6, 3, 6);
previewLabel.Name = "previewLabel";
previewLabel.TabIndex = 0;
//
// optionsButton
//
optionsButton.Anchor = System.Windows.Forms.AnchorStyles.Right;
optionsButton.AutoSize = true;
optionsButton.Name = "optionsButton";
optionsButton.TabIndex = 2;
optionsButton.Text = "Options ▸";
optionsButton.UseVisualStyleBackColor = true;
optionsButton.Click += OptionsButton_Click;
//
// groupGrid
//
@@ -306,7 +346,7 @@ namespace OpenNest.Forms
groupGrid.RowHeadersVisible = false;
groupGrid.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
groupGrid.StandardTab = true;
groupGrid.TabIndex = 4;
groupGrid.TabIndex = 5;
groupGrid.CellValueChanged += GroupGrid_CellValueChanged;
groupGrid.CurrentCellDirtyStateChanged += GroupGrid_CurrentCellDirtyStateChanged;
groupGrid.DataError += GroupGrid_DataError;
@@ -318,6 +358,7 @@ namespace OpenNest.Forms
//
includeColumn.HeaderText = "Release";
includeColumn.Name = "includeColumn";
includeColumn.Visible = false;
includeColumn.Width = 65;
//
// materialColumn
@@ -386,6 +427,7 @@ namespace OpenNest.Forms
targetColumn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
targetColumn.HeaderText = "Nest";
targetColumn.Name = "targetColumn";
targetColumn.Visible = false;
targetColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
targetColumn.ToolTipText = "Make a new Draft nest, or merge into a Draft nest of the same material and renest it.";
targetColumn.Width = 260;
@@ -405,7 +447,7 @@ namespace OpenNest.Forms
skippedLabel.Margin = new System.Windows.Forms.Padding(3, 6, 3, 0);
skippedLabel.MaximumSize = new System.Drawing.Size(1080, 0);
skippedLabel.Name = "skippedLabel";
skippedLabel.TabIndex = 5;
skippedLabel.TabIndex = 6;
skippedLabel.Visible = false;
//
// bottomTable
@@ -425,7 +467,7 @@ namespace OpenNest.Forms
bottomTable.Name = "bottomTable";
bottomTable.RowCount = 1;
bottomTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
bottomTable.TabIndex = 6;
bottomTable.TabIndex = 7;
//
// summaryLabel
//
@@ -471,7 +513,7 @@ namespace OpenNest.Forms
statusLabel.Margin = new System.Windows.Forms.Padding(3, 6, 3, 0);
statusLabel.Name = "statusLabel";
statusLabel.Size = new System.Drawing.Size(100, 20);
statusLabel.TabIndex = 7;
statusLabel.TabIndex = 8;
//
// searchTimer
//
@@ -504,6 +546,8 @@ namespace OpenNest.Forms
((System.ComponentModel.ISupportInitialize)setGrid).EndInit();
optionsTable.ResumeLayout(false);
optionsTable.PerformLayout();
previewTable.ResumeLayout(false);
previewTable.PerformLayout();
((System.ComponentModel.ISupportInitialize)groupGrid).EndInit();
bottomTable.ResumeLayout(false);
bottomTable.PerformLayout();
@@ -531,6 +575,9 @@ namespace OpenNest.Forms
private System.Windows.Forms.CheckBox nestAfterReleaseCheckBox;
private System.Windows.Forms.Label notesLabel;
private System.Windows.Forms.TextBox notesBox;
private System.Windows.Forms.TableLayoutPanel previewTable;
private System.Windows.Forms.Label previewLabel;
private System.Windows.Forms.Button optionsButton;
private System.Windows.Forms.DataGridView groupGrid;
private System.Windows.Forms.DataGridViewCheckBoxColumn includeColumn;
private System.Windows.Forms.DataGridViewTextBoxColumn materialColumn;
+124 -9
View File
@@ -18,10 +18,12 @@ namespace OpenNest.Forms
/// <summary>
/// Drawing Sets &gt; Release: the operator lists the sets to release, typed in or added from
/// the browser, each with a number of kits, and library drawings on their own (Add
/// Drawings), each with a quantity, and the dialog previews how they split into one
/// nest per material group (<see cref="SetReleasePlan"/>), with an archived-drawing toggle
/// and a tick per group. Each group makes a new Draft nest or merges into a Draft nest of
/// the same material, whose layout is discarded. <see cref="SetReleaseSession"/> then builds
/// Drawings), each with a quantity, and the dialog previews, in a sentence and a grid, how
/// they split into one nest per material group (<see cref="SetReleasePlan"/>). Options
/// shows an archived-drawing toggle and a tick per group; an option left changed stays
/// shown while Options is collapsed. Each group makes a new Draft nest or merges into a
/// Draft nest of the same material, whose layout is discarded; the Nest column shows
/// whenever such a draft exists. <see cref="SetReleaseSession"/> then builds
/// those nests, nests each one with Auto Nest when Nest after release is ticked, saves them,
/// opens each in its own tab, and records the release once every chosen nest exists. The
/// dialog shows every group's outcome; a release runs once per dialog. A group refused for
@@ -53,6 +55,8 @@ namespace OpenNest.Forms
private bool running;
private bool loadingDrafts;
private bool editing;
private bool optionsExpanded;
private bool archivedShown;
/// <summary>Shows a message and returns the operator's answer; replaced by tests.</summary>
internal Func<string, MessageBoxButtons, MessageBoxIcon, DialogResult> ShowMessage { get; set; }
@@ -81,6 +85,23 @@ namespace OpenNest.Forms
/// <summary>Shows Edit Drawing and returns the saved record, or null; replaced by tests.</summary>
internal Func<DrawingLibraryEditSession, DrawingLibraryRecord> EditDrawing { get; set; }
/// <summary>
/// Shows the Release ticks, the Nest column and Include archived drawings; collapsed,
/// they show only when they differ from the defaults or a draft could be merged into.
/// </summary>
internal bool OptionsExpanded
{
get => optionsExpanded;
set
{
// Hiding a column mid-edit would drop the edit, so it is committed first.
if (optionsExpanded == value || !groupGrid.EndEdit())
return;
optionsExpanded = value;
UpdateOptions(plan == null ? Array.Empty<SetReleaseGroup>() : ChosenGroups(), collapsing: !value);
}
}
/// <summary>The finished release, once one has run; otherwise null.</summary>
public SetReleaseResult Result { get; private set; }
@@ -130,6 +151,10 @@ namespace OpenNest.Forms
GridStyle.Apply(setGrid);
GridStyle.Apply(groupGrid);
// A refusal or outcome wraps rather than being cut off.
groupGrid.RowTemplate.MinimumHeight = groupGrid.RowTemplate.Height;
groupGrid.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells;
statusColumn.DefaultCellStyle.WrapMode = DataGridViewTriState.True;
populating = true;
foreach (var set in setsToRelease)
AddRow(set, set.Name, set.Customer, set.Revision, 1);
@@ -251,6 +276,8 @@ namespace OpenNest.Forms
int.TryParse(Convert.ToString(row.Cells["kitsColumn"].Value, CultureInfo.CurrentCulture), NumberStyles.Integer,
CultureInfo.CurrentCulture, out var kits) ? kits : 1;
private void OptionsButton_Click(object sender, EventArgs e) => OptionsExpanded = !OptionsExpanded;
private void Options_Changed(object sender, EventArgs e)
{
if (!running && Result == null)
@@ -522,16 +549,24 @@ namespace OpenNest.Forms
}
}
// The selected refused group, else the first one.
private DataGridViewRow RefusedRow() =>
groupGrid.CurrentRow?.Tag is SetReleaseGroup { CanRelease: false } ? groupGrid.CurrentRow
: groupGrid.Rows.Cast<DataGridViewRow>().FirstOrDefault(r => r.Tag is SetReleaseGroup { CanRelease: false });
private bool CanEditDrawings() => libraryStore != null && !running && !editing && Result == null;
/// <summary>
/// Opens Edit Drawing, one after another, on each drawing the selected group is refused
/// for; cancelling one stops. The plan is rebuilt after each save.
/// for, or the first refused group's when a releasable one is selected; cancelling one
/// stops. The plan is rebuilt after each save.
/// </summary>
internal async Task FixSelectedGroupAsync()
{
if (!CanEditDrawings() || groupGrid.CurrentRow?.Tag is not SetReleaseGroup { CanRelease: false } group)
if (!CanEditDrawings() || RefusedRow() is not { Tag: SetReleaseGroup group } row)
return;
if (groupGrid.CurrentRow != row)
groupGrid.CurrentCell = row.Cells[materialColumn.Index];
foreach (var item in group.RefusedItems.ToList())
{
if (!await EditItemDrawingAsync(item) || IsDisposed)
@@ -768,6 +803,7 @@ namespace OpenNest.Forms
1 => $"Release '{(plan.Lines.Count == 1 ? plan.Lines[0].Set.Name : plan.Drawings[0].Drawing.Name)}'",
_ => $"Release {DescribeLines()}",
};
setLabel.Text = Heading();
populating = true;
groupGrid.Rows.Clear();
foreach (var group in plan.Groups)
@@ -797,11 +833,29 @@ namespace OpenNest.Forms
$"{skipped.Count} archived drawing{(skipped.Count == 1 ? " is" : "s are")} left out: "
+ string.Join(", ", skipped.Take(5).Select(m => $"'{m.Drawing!.Name}'"))
+ (skipped.Count > 5 ? $" and {skipped.Count - 5} more" : "")
+ ". Tick Include archived drawings to add them.";
+ ". Tick Include archived drawings (under Options) to add them.";
UpdateCommands();
_ = LoadDraftsAsync();
}
// "Release set 'Kit A' ×2", "Release 'Kit A' ×2, 'Kit B' ×1 and 'SYN-A' ×4".
private string Heading()
{
if (plan.Lines.Count + plan.Drawings.Count == 1)
return plan.Lines.Count == 1
? $"Release set '{plan.Lines[0].Set.Name}' ×{plan.Lines[0].Quantity}"
: $"Release drawing '{plan.Drawings[0].Drawing.Name}' ×{plan.Drawings[0].Quantity}";
const int named = 3;
var lines = plan.Lines.Select(l => $"'{l.Set.Name}' ×{l.Quantity}")
.Concat(plan.Drawings.Select(d => $"'{d.Drawing.Name}' ×{d.Quantity}"))
.ToList();
if (lines.Count == 0)
return "Add a set or drawings to release";
if (lines.Count <= named + 1)
return $"Release {string.Join(", ", lines.Take(lines.Count - 1))} and {lines[^1]}";
return $"Release {string.Join(", ", lines.Take(named))} and {lines.Count - named} more";
}
// "set 'Kit A' ×2", "3 sets and 2 drawings", "drawing 'SYN-A' ×4".
private string DescribeLines()
{
@@ -873,8 +927,13 @@ namespace OpenNest.Forms
FillTargets(row, group);
}
populating = false;
UpdateCommands();
}
// A releasable group has a draft it could merge into.
private bool CanMerge() =>
plan.Groups.Any(g => g.CanRelease && drafts.TryGetValue(g.Key, out var candidates) && candidates.Count > 0);
private string GroupNote(SetReleaseGroup group)
{
if (!group.CanRelease)
@@ -929,7 +988,8 @@ namespace OpenNest.Forms
if (Result.Release != null)
statusLabel.Text = $"Done: {nestsText} saved and opened.";
else if (Result.ReleaseError.Length > 0)
statusLabel.Text = Result.ReleaseError + " Choose Try Again.";
statusLabel.Text = Result.ReleaseError.TrimEnd('.', ' ')
+ ". Try Again records it; the nests are not made again.";
else if (Result.Canceled)
statusLabel.Text = $"Canceled: {nestsText} saved, release not recorded.";
else
@@ -963,7 +1023,8 @@ namespace OpenNest.Forms
}
}
fixDrawingsButton.Enabled = CanEditDrawings() && groupGrid.CurrentRow?.Tag is SetReleaseGroup { CanRelease: false };
fixDrawingsButton.Enabled = CanEditDrawings() && RefusedRow() != null;
UpdateOptions(chosen);
var retry = Result is { Release: null, ReleaseError.Length: > 0 };
releaseButton.Text = retry ? "Try Again" : "Make Nests...";
@@ -978,6 +1039,60 @@ namespace OpenNest.Forms
+ $", {parts:N0} part{(parts == 1 ? "" : "s")}";
}
// Collapsed, an option shows while it differs from its default or, for the Nest column,
// while a draft could be merged into. Only collapsing hides one: hiding a column from its
// own cell's events would move the current cell mid-event, and a control should not
// vanish under the pointer.
private void UpdateOptions(IReadOnlyList<SetReleaseGroup> chosen, bool collapsing = false)
{
var anyExcluded = plan != null && plan.Groups.Any(g => g.CanRelease && excluded.Contains(g.Key));
var canMerge = plan != null && CanMerge();
optionsButton.Text = optionsExpanded ? "Options ▾" : "Options ▸";
// A control reads as hidden until the form is shown, so the checkbox's state is kept here.
archivedShown = Reveal(includeArchivedCheckBox.Checked, archivedShown);
includeArchivedCheckBox.Visible = archivedShown;
includeColumn.Visible = Reveal(anyExcluded, includeColumn.Visible);
targetColumn.Visible = Reveal(canMerge || targets.Count > 0, targetColumn.Visible);
previewLabel.Text = Preview(chosen, canMerge);
bool Reveal(bool changed, bool shown) => optionsExpanded || changed || (shown && !collapsing);
}
// "Creates 2 nests: A36 0.25 in, 304 0.1875 in." and what needs the operator's attention.
private string Preview(IReadOnlyList<SetReleaseGroup> chosen, bool canMerge)
{
if (plan == null || plan.Groups.Count == 0)
return "Add a set or drawings to see the nests they make.";
const int named = 4;
var merges = chosen.Count(g => targets.ContainsKey(g.Key));
var created = chosen.Count - merges;
var text = (created, merges) switch
{
(0, 0) => "No nests chosen.",
(_, 0) => $"Creates {created} nest{(created == 1 ? "" : "s")}: ",
(0, _) => $"Merges into {merges} Draft nest{(merges == 1 ? "" : "s")}: ",
_ => $"Creates {created} nest{(created == 1 ? "" : "s")} and merges into "
+ $"{merges} Draft nest{(merges == 1 ? "" : "s")}: ",
};
if (chosen.Count > 0)
text += string.Join(", ", chosen.Take(named).Select(g =>
targets.TryGetValue(g.Key, out var draft) ? $"{Describe(g)} into '{draft.Name}'" : Describe(g)))
+ (chosen.Count > named ? $" and {chosen.Count - named} more." : ".");
var refused = plan.Groups.Count(g => !g.CanRelease);
if (refused == 1)
text += " 1 material can't be released yet: " + (libraryStore == null ? "see its Status." : "choose Fix Drawings.");
else if (refused > 1)
text += $" {refused} materials can't be released yet: "
+ (libraryStore == null ? "see their Status." : "select one and choose Fix Drawings.");
var left = plan.Groups.Count(g => g.CanRelease && excluded.Contains(g.Key));
if (left > 0)
text += $" {left} material{(left == 1 ? " is" : "s are")} left out (unticked under Release).";
if (merges == 0 && canMerge)
text += " A Draft nest of the same material exists: choose it under Nest to merge into it.";
return text;
}
private static string Describe(SetReleaseGroup group) =>
group == null ? "" : string.Join(" ", new[]
{
+28 -12
View File
@@ -362,7 +362,8 @@ Edit Drawing opens from:
- the release dialog: a group refused for its drawings (no material or
thickness, or two drawings with one name) has **Fix Drawings...**, which opens
Edit Drawing on each drawing it is refused for in turn (Cancel stops), as does
a double-click on the group. Its right-click menu lists Edit Drawing for each of
a double-click on the group; with a releasable group selected, it takes the
first refused one. Its right-click menu lists Edit Drawing for each of
the group's drawings. Each edit reads the current record first, and the plan
is rebuilt with what was saved, so a fixed drawing joins its material group.
@@ -468,16 +469,30 @@ and skipped, and an archived one is refused. **Remove** takes a set or drawing
out while another remains. The list shows each line's type; **Quantity** is how
many of a set (1 to 1000, multiplying its quantities) or of a drawing (1 to
100,000), and a set or drawing is listed once. A drawing that a set also lists
becomes one item with both quantities. The grid below has one row per material
(name, grade, thickness, units and cut type) across all the sets and drawings
(`OpenNest.Data.SetReleasePlan`): material, grade, thickness, units, cut type,
drawings, parts, **Nest** and status. **Include archived drawings** puts archived
members back in, otherwise they are named below the grid as left out. Each group
has a **Release** tick; refused groups (two drawings with one name, or drawings
without material or thickness) show why and cannot be ticked, and a tick
survives changing the sets, quantities or the archived toggle. **Fix Drawings...**
opens [Edit Drawing](#edit-drawing-desktop) on a refused group's drawings and
rebuilds the plan with what was saved.
becomes one item with both quantities. The heading reads as a sentence of what
is listed, such as "Release set 'Frame kit' ×2", or the first few lines by name
and quantity when there are several.
Below, a sentence previews what **Make Nests...** will do, such as "Creates 3
nests: A36 Grade A 0.25 in, 304 0.1875 in, ...", and the grid has one row per
material (name, grade, thickness, units and cut type) across all the sets and
drawings (`OpenNest.Data.SetReleasePlan`): material, grade, thickness, units,
cut type, drawings, parts and status. Refused groups (two drawings with one name,
or drawings without material or thickness) show why in the status column and
the preview counts them. **Fix Drawings...** opens
[Edit Drawing](#edit-drawing-desktop) on the selected refused group's drawings,
or the first refused group's when another row is selected, and rebuilds the plan
with what was saved. Archived members are left out and named below the grid.
The common case needs nothing more: every releasable material makes a new Draft
nest and **Nest after release** is ticked. **Options ▸** expands the choices that
change that: a **Release** tick per group (refused groups cannot be ticked), the
**Nest** column, and **Include archived drawings**, which puts archived members
back in. A tick survives changing the sets, quantities or the archived toggle.
Collapsing Options hides only what is at its default: an unticked group keeps the
Release column shown, and a ticked Include archived drawings stays shown. The
**Nest** column also shows, with a hint in the preview, whenever a Draft nest
exists that a material could merge into.
**Nest** is **New Draft nest** or **Merge into** one of the Draft nests of the
group's material, grade and thickness, listed with their sheet count and when
@@ -512,7 +527,8 @@ grid then shows every group's outcome: Created or Merged into with
the nest name, Refused or Failed with the reason, or Not started. Cancel during
preparing or nesting saves nothing; once saving starts it stops between saves,
never during one. When every nest was saved but recording failed,
the button becomes **Try Again** and posts the same release again. A dialog
the button becomes **Try Again** and posts the same release again without making
the nests again. A dialog
runs one release.
**Releases...** lists the releases that include the selected set, newest first: