feat(library): edit a library drawing's details as a new revision

Edit Settings becomes Edit Drawing: name, customer, material, grade,
thickness and cut type are editable beside the nesting settings and
notes. Units stay read-only. The save is a metadata revision with the
same package, so nests keep the revision they hold, and the release
refusal for drawings without material or thickness now has a fix.
This commit is contained in:
aj committed 2026-10-24 10:02:23 -04:00
1 parent bc21382c5d
commit 414479fc4e
8 files changed
+438 -86

No files matched your search

+61 -26
View File
@@ -1,10 +1,23 @@
namespace OpenNest.Data;
/// <summary>
/// Edits one listed library drawing's notes and nesting settings in place: the save is a
/// metadata-only revision guarded by the revision it was listed with, so the package (and
/// its hash) stays the same. A stale revision raises <see cref="DrawingLibraryConflictException"/>
/// and changes nothing.
/// A library drawing's editable details. Units are not among them: they give the stored
/// geometry its meaning, so changing them would rescale the part.
/// </summary>
public sealed record DrawingLibraryDetails(
string Name,
string Customer,
string MaterialName,
string MaterialGrade,
double Thickness,
CutType CutType);
/// <summary>
/// Edits one listed library drawing's details (name, customer, material, grade, thickness,
/// cut type), notes and nesting settings in place: the save is a metadata-only revision
/// guarded by the revision it was listed with, so the package (and its hash) stays the same
/// and nests keep the revision they hold. A stale revision raises
/// <see cref="DrawingLibraryConflictException"/> and changes nothing.
/// </summary>
public sealed class DrawingLibraryEditSession
{
@@ -26,42 +39,64 @@ public sealed class DrawingLibraryEditSession
public string SavedBy { get; }
/// <summary>The record to save: the listed details with new notes and settings.</summary>
public DrawingLibraryRecord Build(string notes, DrawingNestingSettings settings) => new()
/// <summary>The listed record's details, the starting point of an edit.</summary>
public DrawingLibraryDetails Details => new(Listed.Name, Listed.Customer, Listed.MaterialName,
Listed.MaterialGrade, Listed.Thickness, Listed.CutType);
/// <summary>
/// The record to save: the edited details (text trimmed, thickness rounded as stored),
/// the listed units, and the new notes and settings.
/// </summary>
public DrawingLibraryRecord Build(DrawingLibraryDetails details, string notes, DrawingNestingSettings settings)
{
Id = Listed.Id,
Name = Listed.Name,
Customer = Listed.Customer,
MaterialName = Listed.MaterialName,
MaterialGrade = Listed.MaterialGrade,
Thickness = Listed.Thickness,
Units = Listed.Units,
CutType = Listed.CutType,
Notes = notes ?? "",
Settings = settings ?? throw new ArgumentNullException(nameof(settings)),
SavedBy = SavedBy,
};
ArgumentNullException.ThrowIfNull(details);
return new DrawingLibraryRecord
{
Id = Listed.Id,
Name = (details.Name ?? "").Trim(),
Customer = (details.Customer ?? "").Trim(),
MaterialName = (details.MaterialName ?? "").Trim(),
MaterialGrade = (details.MaterialGrade ?? "").Trim(),
Thickness = double.IsFinite(details.Thickness)
? DrawingLibraryRules.NormalizeThickness(details.Thickness)
: details.Thickness,
Units = Listed.Units,
CutType = details.CutType,
Notes = notes ?? "",
Settings = settings ?? throw new ArgumentNullException(nameof(settings)),
SavedBy = SavedBy,
};
}
/// <summary>The first rule the edit breaks, or null when it may be saved.</summary>
public string? GetError(string notes, DrawingNestingSettings settings) =>
DrawingLibraryRules.GetMetadataError(Build(notes, settings));
public string? GetError(DrawingLibraryDetails details, string notes, DrawingNestingSettings settings) =>
DrawingLibraryRules.GetMetadataError(Build(details, notes, settings));
/// <summary>True when saving would change nothing.</summary>
public bool IsUnchanged(string notes, DrawingNestingSettings settings) =>
(notes ?? "") == Listed.Notes && Equals(settings, Listed.Settings);
public bool IsUnchanged(DrawingLibraryDetails details, string notes, DrawingNestingSettings settings) =>
SameDetails(Build(details, notes, settings), Listed)
&& (notes ?? "") == Listed.Notes && Equals(settings, Listed.Settings);
/// <summary>Saves the edit as the next revision; returns the stored record.</summary>
public async Task<DrawingLibraryRecord> SaveAsync(string notes, DrawingNestingSettings settings,
CancellationToken cancellationToken = default)
public async Task<DrawingLibraryRecord> SaveAsync(DrawingLibraryDetails details, string notes,
DrawingNestingSettings settings, CancellationToken cancellationToken = default)
{
var record = Build(notes, settings);
var record = Build(details, notes, settings);
var error = DrawingLibraryRules.GetMetadataError(record);
if (error != null)
throw new ArgumentException(error, nameof(settings));
throw new ArgumentException(error, nameof(details));
var saved = await _library.UpdateMetadataAsync(Listed.Id, record, Listed.Revision, cancellationToken);
if (!string.Equals(saved.PackageSha256, Listed.PackageSha256, StringComparison.OrdinalIgnoreCase)
|| !SameDetails(saved, record) || saved.Units != record.Units
|| !Equals(saved.Settings, record.Settings) || saved.Notes != record.Notes)
throw new IOException("The nest server stored a different library drawing than was edited. Reload the library.");
return saved;
}
// Compares a built (trimmed, rounded) record with another, as stored.
private static bool SameDetails(DrawingLibraryRecord built, DrawingLibraryRecord other) =>
built.Name == other.Name.Trim() && built.Customer == other.Customer.Trim()
&& built.MaterialName == other.MaterialName.Trim() && built.MaterialGrade == other.MaterialGrade.Trim()
&& built.Thickness == DrawingLibraryRules.NormalizeThickness(other.Thickness)
&& built.CutType == other.CutType;
}
@@ -29,9 +29,9 @@ public class DrawingLibraryEditSessionTests
var session = new DrawingLibraryEditSession(store, listed, "Synthetic operator");
var settings = new DrawingNestingSettings { CutFirst = true };
Assert.True(session.IsUnchanged("Calibration", new DrawingNestingSettings()));
Assert.False(session.IsUnchanged("Calibration", settings));
var saved = await session.SaveAsync("Calibration", settings);
Assert.True(session.IsUnchanged(session.Details, "Calibration", new DrawingNestingSettings()));
Assert.False(session.IsUnchanged(session.Details, "Calibration", settings));
var saved = await session.SaveAsync(session.Details, "Calibration", settings);
var (id, expected, record) = Assert.Single(store.Updates);
Assert.Equal((listed.Id, 4), (id, expected));
@@ -49,11 +49,68 @@ public class DrawingLibraryEditSessionTests
var session = new DrawingLibraryEditSession(store, Listed(), "");
var invalid = new DrawingNestingSettings { StepAngle = -1 };
Assert.NotNull(session.GetError("", invalid));
await Assert.ThrowsAsync<ArgumentException>(() => session.SaveAsync("", invalid));
Assert.NotNull(session.GetError(session.Details, "", invalid));
await Assert.ThrowsAsync<ArgumentException>(() => session.SaveAsync(session.Details, "", invalid));
Assert.Empty(store.Updates);
}
[Fact]
public async Task SaveAsync_SendsEditedDetailsTrimmedAsTheNextRevisionOfTheSamePackage()
{
var listed = Listed();
var store = new RecordingStore();
var session = new DrawingLibraryEditSession(store, listed, "Synthetic operator");
var details = new DrawingLibraryDetails(" Test square B ", "Other customer", " Synthetic Stainless ", "304 ",
0.1875, CutType.Waterjet);
Assert.False(session.IsUnchanged(details, listed.Notes, listed.Settings));
var saved = await session.SaveAsync(details, listed.Notes, listed.Settings);
var (id, expected, record) = Assert.Single(store.Updates);
Assert.Equal((listed.Id, 4), (id, expected));
Assert.Equal(("Test square B", "Other customer", "Synthetic Stainless", "304", 0.1875, CutType.Waterjet),
(record.Name, record.Customer, record.MaterialName, record.MaterialGrade, record.Thickness, record.CutType));
// Units are never edited here: they give the stored geometry its meaning.
Assert.Equal(listed.Units, record.Units);
Assert.Equal(("Test square B", 0.1875), (saved.Name, saved.Thickness));
}
[Fact]
public void Details_StartFromTheListedRecordAndOnlyRealChangesCount()
{
var listed = Listed();
var session = new DrawingLibraryEditSession(new RecordingStore(), listed, "");
Assert.Equal(new DrawingLibraryDetails(listed.Name, listed.Customer, listed.MaterialName, listed.MaterialGrade,
listed.Thickness, listed.CutType), session.Details);
// Surrounding spaces and sub-rounding thickness noise are not edits.
Assert.True(session.IsUnchanged(session.Details with { Name = " Test square ", Thickness = 0.250001 },
listed.Notes, listed.Settings));
Assert.False(session.IsUnchanged(session.Details with { MaterialGrade = "Grade B" }, listed.Notes, listed.Settings));
}
[Fact]
public async Task SaveAsync_RefusesABlankNameWithoutSending()
{
var store = new RecordingStore();
var session = new DrawingLibraryEditSession(store, Listed(), "");
var blank = session.Details with { Name = " " };
Assert.Equal("name is required.", session.GetError(blank, "", new DrawingNestingSettings()));
await Assert.ThrowsAsync<ArgumentException>(() => session.SaveAsync(blank, "", new DrawingNestingSettings()));
Assert.Empty(store.Updates);
}
[Fact]
public async Task SaveAsync_RefusesAReplyWithOtherDetailsThanSent()
{
var store = new RecordingStore { DropDetails = true };
var session = new DrawingLibraryEditSession(store, Listed(), "");
await Assert.ThrowsAsync<IOException>(() =>
session.SaveAsync(session.Details with { MaterialName = "Other" }, "", new DrawingNestingSettings()));
}
[Fact]
public void ArchivedDrawings_CannotBeEdited()
{
@@ -64,6 +121,9 @@ public class DrawingLibraryEditSessionTests
{
public List<(Guid Id, int Expected, DrawingLibraryRecord Record)> Updates { get; } = new();
/// <summary>Replies with the listed details instead of the sent ones.</summary>
public bool DropDetails { get; init; }
public Task<DrawingLibraryRecord> UpdateMetadataAsync(Guid id, DrawingLibraryRecord record, int expectedRevision,
CancellationToken cancellationToken = default)
{
@@ -72,7 +132,13 @@ public class DrawingLibraryEditSessionTests
{
Id = id,
Revision = expectedRevision + 1,
Name = record.Name,
Name = DropDetails ? "Test square" : record.Name,
Customer = DropDetails ? "Synthetic customer" : record.Customer,
MaterialName = DropDetails ? "Synthetic Steel" : record.MaterialName,
MaterialGrade = DropDetails ? "Grade A" : record.MaterialGrade,
Thickness = DropDetails ? 0.25 : record.Thickness,
Units = record.Units,
CutType = DropDetails ? CutType.Laser : record.CutType,
Notes = record.Notes,
Settings = record.Settings,
PackageSha256 = "ABC123",
@@ -267,6 +267,57 @@ public class DrawingLibraryFormTests
"The drawing library edit test did not complete.");
}
[Fact]
public void EditDrawing_SavesNewDetailsAsANewRevision_KeepingUnits()
{
StaTestThread.Run(
() =>
{
var library = new FakeLibrary();
var record = library.Add("SYN-405", "", "", 0, Units.Inches, "Test square");
var nest = MatchingNest();
using var form = new DrawingLibraryForm(library, Server, nest);
form.ShowMessage = (text, _, _) => throw new Xunit.Sdk.XunitException(text);
form.EditDrawing = edit =>
{
using var editor = new EditLibraryDrawingForm(edit);
editor.ShowMessage = (text, _, _) => throw new Xunit.Sdk.XunitException(text);
editor.Show();
var save = Field<Button>(editor, "saveButton");
Assert.Equal("SYN-405", Field<TextBox>(editor, "nameBox").Text);
Assert.Equal("in", Field<Label>(editor, "unitsLabel").Text);
Assert.False(save.Enabled);
Field<TextBox>(editor, "nameBox").Text = "SYN-405B";
Field<TextBox>(editor, "materialBox").Text = "Synthetic Stainless";
Field<TextBox>(editor, "gradeBox").Text = "304";
Field<NumericUpDown>(editor, "thicknessBox").Value = 0.1875m;
Field<ComboBox>(editor, "cutTypeBox").SelectedItem = "Waterjet";
Assert.True(save.Enabled);
Field<TextBox>(editor, "nameBox").Text = " ";
Assert.False(save.Enabled);
Field<TextBox>(editor, "nameBox").Text = "SYN-405B";
Run(editor.SaveAsync());
return editor.Saved;
};
form.Show();
var grid = Field<DataGridView>(form, "drawingGrid");
PumpUntil(() => grid.Rows.Count == 1, "the library page");
Run(form.EditSelectedAsync());
PumpUntil(() => Equals(grid.Rows[0].Cells["nameColumn"].Value, "SYN-405B"), "the reloaded page");
var update = Assert.Single(library.MetadataUpdates);
Assert.Equal((record.Id, record.Revision), (update.Id, update.ExpectedRevision));
Assert.Equal(("SYN-405B", "Synthetic Stainless", "304", 0.1875, CutType.Waterjet, Units.Inches),
(update.Record.Name, update.Record.MaterialName, update.Record.MaterialGrade, update.Record.Thickness,
update.Record.CutType, update.Record.Units));
Assert.Equal("Waterjet", grid.Rows[0].Cells["cutTypeColumn"].Value);
},
TestTimeout,
"The drawing library details edit test did not complete.");
}
private static Nest MatchingNest() => new("Synthetic job")
{
Units = Units.Inches,
@@ -358,13 +409,13 @@ public class DrawingLibraryFormTests
{
Id = id,
Revision = current.Revision + 1,
Name = current.Name,
Customer = current.Customer,
MaterialName = current.MaterialName,
MaterialGrade = current.MaterialGrade,
Thickness = current.Thickness,
Units = current.Units,
CutType = current.CutType,
Name = record.Name,
Customer = record.Customer,
MaterialName = record.MaterialName,
MaterialGrade = record.MaterialGrade,
Thickness = record.Thickness,
Units = record.Units,
CutType = record.CutType,
Notes = record.Notes,
Settings = record.Settings,
PackageSha256 = current.PackageSha256,
+1 -1
View File
@@ -734,7 +734,7 @@ namespace OpenNest.Forms
editButton.Margin = new System.Windows.Forms.Padding(9, 3, 0, 0);
editButton.Name = "editButton";
editButton.TabIndex = 3;
editButton.Text = "Edit Settings...";
editButton.Text = "Edit Drawing...";
editButton.UseVisualStyleBackColor = true;
editButton.Click += EditButton_Click;
//
+4 -4
View File
@@ -18,9 +18,9 @@ namespace OpenNest.Forms
/// one bounded page at a time through <see cref="DrawingLibraryBrowseSession"/>, filtered
/// to the nest's material and thickness unless Show all is ticked. Add downloads the
/// listed revision's exact package, plans it with <see cref="LibraryDrawingInsert"/>,
/// shows any warning or refusal first and only then changes the nest. Edit Settings
/// changes the selected drawing's notes and nesting settings (Cut first, priority,
/// rotation, color) on the server as a new revision with the same package. With a set
/// shows any warning or refusal first and only then changes the nest. Edit Drawing
/// changes the selected drawing's details (name, customer, material, grade, thickness,
/// cut type), notes and nesting settings (Cut first, priority, rotation, color) on the server as a new revision with the same package. With a set
/// store, the drawing grid's menu lists the sets a drawing is in, adds it to a set, lists
/// the releases that had it on its own and, once release is enabled, releases it; sets
/// themselves are browsed and released in <see cref="DrawingSetsForm"/>.
@@ -565,7 +565,7 @@ namespace OpenNest.Forms
private async void EditButton_Click(object sender, EventArgs e) => await EditSelectedAsync();
/// <summary>
/// Edits the selected drawing's notes and nesting settings as a new library revision,
/// Edits the selected drawing's details, notes and nesting settings as a new library revision,
/// then reloads the page so the list shows the saved revision.
/// </summary>
internal async Task EditSelectedAsync()
+205 -30
View File
@@ -18,6 +18,21 @@ namespace OpenNest.Forms
private void InitializeComponent()
{
layoutTable = new System.Windows.Forms.TableLayoutPanel();
nameLabel = new System.Windows.Forms.Label();
nameBox = new System.Windows.Forms.TextBox();
customerLabel = new System.Windows.Forms.Label();
customerBox = new System.Windows.Forms.TextBox();
materialLabel = new System.Windows.Forms.Label();
materialBox = new System.Windows.Forms.TextBox();
gradeLabel = new System.Windows.Forms.Label();
gradeBox = new System.Windows.Forms.TextBox();
thicknessLabel = new System.Windows.Forms.Label();
thicknessPanel = new System.Windows.Forms.FlowLayoutPanel();
thicknessBox = new System.Windows.Forms.NumericUpDown();
unitsLabel = new System.Windows.Forms.Label();
cutTypeLabel = new System.Windows.Forms.Label();
cutTypeBox = new System.Windows.Forms.ComboBox();
settingsLabel = new System.Windows.Forms.Label();
cutFirstCheckBox = new System.Windows.Forms.CheckBox();
priorityLabel = new System.Windows.Forms.Label();
priorityBox = new System.Windows.Forms.NumericUpDown();
@@ -37,6 +52,8 @@ namespace OpenNest.Forms
cancelButton = new System.Windows.Forms.Button();
saveButton = new System.Windows.Forms.Button();
layoutTable.SuspendLayout();
thicknessPanel.SuspendLayout();
((System.ComponentModel.ISupportInitialize)thicknessBox).BeginInit();
((System.ComponentModel.ISupportInitialize)priorityBox).BeginInit();
((System.ComponentModel.ISupportInitialize)stepBox).BeginInit();
((System.ComponentModel.ISupportInitialize)startBox).BeginInit();
@@ -49,29 +66,50 @@ namespace OpenNest.Forms
layoutTable.ColumnCount = 2;
layoutTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
layoutTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
layoutTable.Controls.Add(cutFirstCheckBox, 1, 0);
layoutTable.Controls.Add(priorityLabel, 0, 1);
layoutTable.Controls.Add(priorityBox, 1, 1);
layoutTable.Controls.Add(stepLabel, 0, 2);
layoutTable.Controls.Add(stepBox, 1, 2);
layoutTable.Controls.Add(startLabel, 0, 3);
layoutTable.Controls.Add(startBox, 1, 3);
layoutTable.Controls.Add(endLabel, 0, 4);
layoutTable.Controls.Add(endBox, 1, 4);
layoutTable.Controls.Add(allow180CheckBox, 1, 5);
layoutTable.Controls.Add(colorLabel, 0, 6);
layoutTable.Controls.Add(colorButton, 1, 6);
layoutTable.Controls.Add(notesLabel, 0, 7);
layoutTable.Controls.Add(notesBox, 1, 7);
layoutTable.Controls.Add(statusLabel, 0, 8);
layoutTable.Controls.Add(buttonPanel, 0, 9);
layoutTable.Controls.Add(nameLabel, 0, 0);
layoutTable.Controls.Add(nameBox, 1, 0);
layoutTable.Controls.Add(customerLabel, 0, 1);
layoutTable.Controls.Add(customerBox, 1, 1);
layoutTable.Controls.Add(materialLabel, 0, 2);
layoutTable.Controls.Add(materialBox, 1, 2);
layoutTable.Controls.Add(gradeLabel, 0, 3);
layoutTable.Controls.Add(gradeBox, 1, 3);
layoutTable.Controls.Add(thicknessLabel, 0, 4);
layoutTable.Controls.Add(thicknessPanel, 1, 4);
layoutTable.Controls.Add(cutTypeLabel, 0, 5);
layoutTable.Controls.Add(cutTypeBox, 1, 5);
layoutTable.Controls.Add(settingsLabel, 0, 6);
layoutTable.Controls.Add(cutFirstCheckBox, 1, 7);
layoutTable.Controls.Add(priorityLabel, 0, 8);
layoutTable.Controls.Add(priorityBox, 1, 8);
layoutTable.Controls.Add(stepLabel, 0, 9);
layoutTable.Controls.Add(stepBox, 1, 9);
layoutTable.Controls.Add(startLabel, 0, 10);
layoutTable.Controls.Add(startBox, 1, 10);
layoutTable.Controls.Add(endLabel, 0, 11);
layoutTable.Controls.Add(endBox, 1, 11);
layoutTable.Controls.Add(allow180CheckBox, 1, 12);
layoutTable.Controls.Add(colorLabel, 0, 13);
layoutTable.Controls.Add(colorButton, 1, 13);
layoutTable.Controls.Add(notesLabel, 0, 14);
layoutTable.Controls.Add(notesBox, 1, 14);
layoutTable.Controls.Add(statusLabel, 0, 15);
layoutTable.Controls.Add(buttonPanel, 0, 16);
layoutTable.SetColumnSpan(settingsLabel, 2);
layoutTable.SetColumnSpan(statusLabel, 2);
layoutTable.SetColumnSpan(buttonPanel, 2);
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 = 10;
layoutTable.RowCount = 17;
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
@@ -82,14 +120,133 @@ namespace OpenNest.Forms
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());
layoutTable.Size = new System.Drawing.Size(464, 441);
layoutTable.Size = new System.Drawing.Size(464, 641);
layoutTable.TabIndex = 0;
//
// nameLabel
//
nameLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
nameLabel.AutoSize = true;
nameLabel.Name = "nameLabel";
nameLabel.Text = "Name:";
//
// nameBox
//
nameBox.Dock = System.Windows.Forms.DockStyle.Fill;
nameBox.MaxLength = 200;
nameBox.Name = "nameBox";
nameBox.TabIndex = 0;
nameBox.TextChanged += Value_Changed;
//
// customerLabel
//
customerLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
customerLabel.AutoSize = true;
customerLabel.Name = "customerLabel";
customerLabel.Text = "Customer:";
//
// customerBox
//
customerBox.Dock = System.Windows.Forms.DockStyle.Fill;
customerBox.MaxLength = 200;
customerBox.Name = "customerBox";
customerBox.TabIndex = 1;
customerBox.TextChanged += Value_Changed;
//
// materialLabel
//
materialLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
materialLabel.AutoSize = true;
materialLabel.Name = "materialLabel";
materialLabel.Text = "Material:";
//
// materialBox
//
materialBox.Dock = System.Windows.Forms.DockStyle.Fill;
materialBox.MaxLength = 200;
materialBox.Name = "materialBox";
materialBox.TabIndex = 2;
materialBox.TextChanged += Value_Changed;
//
// gradeLabel
//
gradeLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
gradeLabel.AutoSize = true;
gradeLabel.Name = "gradeLabel";
gradeLabel.Text = "Grade:";
//
// gradeBox
//
gradeBox.Dock = System.Windows.Forms.DockStyle.Fill;
gradeBox.MaxLength = 200;
gradeBox.Name = "gradeBox";
gradeBox.TabIndex = 3;
gradeBox.TextChanged += Value_Changed;
//
// thicknessLabel
//
thicknessLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
thicknessLabel.AutoSize = true;
thicknessLabel.Name = "thicknessLabel";
thicknessLabel.Text = "Thickness:";
//
// thicknessPanel
//
thicknessPanel.AutoSize = true;
thicknessPanel.Controls.Add(thicknessBox);
thicknessPanel.Controls.Add(unitsLabel);
thicknessPanel.Margin = new System.Windows.Forms.Padding(0);
thicknessPanel.Name = "thicknessPanel";
thicknessPanel.TabIndex = 4;
thicknessPanel.WrapContents = false;
//
// thicknessBox
//
thicknessBox.DecimalPlaces = 4;
thicknessBox.Increment = new decimal(new int[] { 1, 0, 0, 131072 });
thicknessBox.Maximum = new decimal(new int[] { 100000, 0, 0, 0 });
thicknessBox.Name = "thicknessBox";
thicknessBox.Size = new System.Drawing.Size(100, 25);
thicknessBox.TabIndex = 4;
thicknessBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
thicknessBox.ValueChanged += Value_Changed;
//
// unitsLabel
//
unitsLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
unitsLabel.AutoSize = true;
unitsLabel.Name = "unitsLabel";
unitsLabel.Text = "in";
//
// cutTypeLabel
//
cutTypeLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
cutTypeLabel.AutoSize = true;
cutTypeLabel.Name = "cutTypeLabel";
cutTypeLabel.Text = "Cut type:";
//
// cutTypeBox
//
cutTypeBox.Anchor = System.Windows.Forms.AnchorStyles.Left;
cutTypeBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
cutTypeBox.Name = "cutTypeBox";
cutTypeBox.Size = new System.Drawing.Size(120, 25);
cutTypeBox.TabIndex = 5;
cutTypeBox.SelectedIndexChanged += Value_Changed;
//
// settingsLabel
//
settingsLabel.AutoSize = true;
settingsLabel.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold);
settingsLabel.Margin = new System.Windows.Forms.Padding(3, 12, 3, 3);
settingsLabel.Name = "settingsLabel";
settingsLabel.Text = "Nesting settings";
//
// cutFirstCheckBox
//
cutFirstCheckBox.AutoSize = true;
cutFirstCheckBox.Name = "cutFirstCheckBox";
cutFirstCheckBox.TabIndex = 0;
cutFirstCheckBox.TabIndex = 6;
cutFirstCheckBox.Text = "Cut first (sequence these parts before others)";
cutFirstCheckBox.UseVisualStyleBackColor = true;
cutFirstCheckBox.CheckedChanged += Value_Changed;
@@ -108,7 +265,7 @@ namespace OpenNest.Forms
priorityBox.Minimum = int.MinValue;
priorityBox.Name = "priorityBox";
priorityBox.Size = new System.Drawing.Size(100, 25);
priorityBox.TabIndex = 1;
priorityBox.TabIndex = 7;
priorityBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
priorityBox.ValueChanged += Value_Changed;
//
@@ -126,7 +283,7 @@ namespace OpenNest.Forms
stepBox.Maximum = new decimal(new int[] { 360, 0, 0, 0 });
stepBox.Name = "stepBox";
stepBox.Size = new System.Drawing.Size(100, 25);
stepBox.TabIndex = 2;
stepBox.TabIndex = 8;
stepBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
stepBox.ValueChanged += Value_Changed;
//
@@ -144,7 +301,7 @@ namespace OpenNest.Forms
startBox.Maximum = new decimal(new int[] { 360, 0, 0, 0 });
startBox.Name = "startBox";
startBox.Size = new System.Drawing.Size(100, 25);
startBox.TabIndex = 3;
startBox.TabIndex = 9;
startBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
startBox.ValueChanged += Value_Changed;
//
@@ -162,7 +319,7 @@ namespace OpenNest.Forms
endBox.Maximum = new decimal(new int[] { 360, 0, 0, 0 });
endBox.Name = "endBox";
endBox.Size = new System.Drawing.Size(100, 25);
endBox.TabIndex = 4;
endBox.TabIndex = 10;
endBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
endBox.ValueChanged += Value_Changed;
//
@@ -170,7 +327,7 @@ namespace OpenNest.Forms
//
allow180CheckBox.AutoSize = true;
allow180CheckBox.Name = "allow180CheckBox";
allow180CheckBox.TabIndex = 5;
allow180CheckBox.TabIndex = 11;
allow180CheckBox.Text = "Allow 180° equivalent";
allow180CheckBox.UseVisualStyleBackColor = true;
allow180CheckBox.CheckedChanged += Value_Changed;
@@ -188,7 +345,7 @@ namespace OpenNest.Forms
colorButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
colorButton.Name = "colorButton";
colorButton.Size = new System.Drawing.Size(60, 25);
colorButton.TabIndex = 6;
colorButton.TabIndex = 12;
colorButton.Click += ColorButton_Click;
//
// notesLabel
@@ -206,7 +363,7 @@ namespace OpenNest.Forms
notesBox.Multiline = true;
notesBox.Name = "notesBox";
notesBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
notesBox.TabIndex = 7;
notesBox.TabIndex = 13;
notesBox.TextChanged += Value_Changed;
//
// statusLabel
@@ -226,7 +383,7 @@ namespace OpenNest.Forms
buttonPanel.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft;
buttonPanel.Margin = new System.Windows.Forms.Padding(0, 6, 0, 0);
buttonPanel.Name = "buttonPanel";
buttonPanel.TabIndex = 8;
buttonPanel.TabIndex = 14;
//
// cancelButton
//
@@ -252,17 +409,20 @@ namespace OpenNest.Forms
AcceptButton = saveButton;
AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
CancelButton = cancelButton;
ClientSize = new System.Drawing.Size(464, 441);
ClientSize = new System.Drawing.Size(464, 641);
Controls.Add(layoutTable);
Font = new System.Drawing.Font("Segoe UI", 9.75F);
MaximizeBox = false;
MinimizeBox = false;
MinimumSize = new System.Drawing.Size(420, 400);
MinimumSize = new System.Drawing.Size(420, 600);
Name = "EditLibraryDrawingForm";
ShowIcon = false;
ShowInTaskbar = false;
StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
Text = "Edit Library Drawing";
Text = "Edit Drawing";
thicknessPanel.ResumeLayout(false);
thicknessPanel.PerformLayout();
((System.ComponentModel.ISupportInitialize)thicknessBox).EndInit();
layoutTable.ResumeLayout(false);
layoutTable.PerformLayout();
((System.ComponentModel.ISupportInitialize)priorityBox).EndInit();
@@ -277,6 +437,21 @@ namespace OpenNest.Forms
#endregion
private System.Windows.Forms.TableLayoutPanel layoutTable;
private System.Windows.Forms.Label nameLabel;
private System.Windows.Forms.TextBox nameBox;
private System.Windows.Forms.Label customerLabel;
private System.Windows.Forms.TextBox customerBox;
private System.Windows.Forms.Label materialLabel;
private System.Windows.Forms.TextBox materialBox;
private System.Windows.Forms.Label gradeLabel;
private System.Windows.Forms.TextBox gradeBox;
private System.Windows.Forms.Label thicknessLabel;
private System.Windows.Forms.FlowLayoutPanel thicknessPanel;
private System.Windows.Forms.NumericUpDown thicknessBox;
private System.Windows.Forms.Label unitsLabel;
private System.Windows.Forms.Label cutTypeLabel;
private System.Windows.Forms.ComboBox cutTypeBox;
private System.Windows.Forms.Label settingsLabel;
private System.Windows.Forms.CheckBox cutFirstCheckBox;
private System.Windows.Forms.Label priorityLabel;
private System.Windows.Forms.NumericUpDown priorityBox;
+26 -5
View File
@@ -11,10 +11,11 @@ using OpenNest.Math;
namespace OpenNest.Forms
{
/// <summary>
/// Drawing library &gt; Edit Settings. Edits one library drawing's notes and nesting
/// Drawing library &gt; Edit Drawing. Edits one library drawing's details (name, customer,
/// material, grade, thickness, cut type; units are shown, not edited), notes and nesting
/// settings (Cut first, priority, rotation constraints, color) through
/// <see cref="DrawingLibraryEditSession"/>. Saving adds a revision on the server that
/// keeps the same package; nests that already hold the drawing are not changed.
/// keeps the same package; nests that already hold the drawing keep their revision.
/// </summary>
public partial class EditLibraryDrawingForm : Form
{
@@ -38,6 +39,15 @@ namespace OpenNest.Forms
ShowMessage = (text, buttons, icon) => MessageBox.Show(this, text, Text, buttons, icon);
var listed = session.Listed;
nameBox.Text = listed.Name;
customerBox.Text = listed.Customer;
materialBox.Text = listed.MaterialName;
gradeBox.Text = listed.MaterialGrade;
thicknessBox.Value = System.Math.Clamp((decimal)DrawingLibraryRules.NormalizeThickness(listed.Thickness),
thicknessBox.Minimum, thicknessBox.Maximum);
unitsLabel.Text = UnitsHelper.GetShortString(listed.Units);
cutTypeBox.Items.AddRange(Enum.GetNames<CutType>());
cutTypeBox.SelectedItem = listed.CutType.ToString();
var settings = listed.Settings ?? new DrawingNestingSettings();
cutFirstCheckBox.Checked = settings.CutFirst;
priorityBox.Value = settings.Priority;
@@ -53,6 +63,15 @@ namespace OpenNest.Forms
UpdateCommands();
}
/// <summary>The details as currently entered.</summary>
internal DrawingLibraryDetails ReadDetails() => new(
nameBox.Text,
customerBox.Text,
materialBox.Text,
gradeBox.Text,
(double)thicknessBox.Value,
Enum.TryParse<CutType>(cutTypeBox.SelectedItem as string, out var cutType) ? cutType : session.Listed.CutType);
/// <summary>The settings as currently entered; unchanged angles keep their exact stored radians.</summary>
internal DrawingNestingSettings ReadSettings()
{
@@ -95,7 +114,7 @@ namespace OpenNest.Forms
statusLabel.Text = "Saving...";
try
{
Saved = await session.SaveAsync(notesBox.Text, ReadSettings());
Saved = await session.SaveAsync(ReadDetails(), notesBox.Text, ReadSettings());
if (IsDisposed)
return;
DialogResult = DialogResult.OK;
@@ -122,13 +141,15 @@ namespace OpenNest.Forms
private bool CanSave()
{
var details = ReadDetails();
var settings = ReadSettings();
return session.GetError(notesBox.Text, settings) == null && !session.IsUnchanged(notesBox.Text, settings);
return session.GetError(details, notesBox.Text, settings) == null
&& !session.IsUnchanged(details, notesBox.Text, settings);
}
private void UpdateCommands()
{
var error = session.GetError(notesBox.Text, ReadSettings());
var error = session.GetError(ReadDetails(), notesBox.Text, ReadSettings());
saveButton.Enabled = !saving && CanSave();
if (!saving)
statusLabel.Text = error == null ? "" : char.ToUpper(error[0], CultureInfo.CurrentCulture) + error[1..];
+11 -7
View File
@@ -321,13 +321,17 @@ is edited in place (0 to 99,999; one click starts editing, a bad number keeps
the editor open with an error) and applies to the nest at once, like an add;
the main window's drawing list refreshes when the dialog closes.
The browser's **Cut first** column shows the setting. **Edit Settings...**
The browser's **Cut first** column shows the setting. **Edit Drawing...**
(`OpenNest.Data.DrawingLibraryEditSession`) edits the selected active drawing's
Cut first flag, priority, rotation step/start/end (degrees), 180° equivalent,
color and notes, and saves them through the metadata endpoint guarded by the
listed revision: a new revision with the same package. A 409 is shown and the
operator reloads. Nests that already hold the drawing keep their copy and
revision.
details (name, customer, material, grade, thickness and cut type; text is
trimmed, the name is required, units are shown but not editable since they give
the geometry its meaning) and its Cut first flag, priority, rotation
step/start/end (degrees), 180° equivalent, color and notes, and saves them
through the metadata endpoint guarded by the listed revision: a new revision
with the same package. A material or thickness change is an edit of the same
drawing, not a new one. A 409 is shown and the operator reloads. Nests that
already hold the drawing keep their copy and revision; sets list the drawing,
so their next release uses the new details.
### Save to Drawing Library (desktop)
@@ -340,7 +344,7 @@ has none), and the cut type and notes of the linked revision when the drawing
came from this server's library, otherwise the cut type last saved on this PC.
Units follow the nest and cannot be changed. The drawing's own nesting
settings (Cut first, priority, rotation, color) are saved in the record and
summarized read-only in the dialog; change them later with Edit Settings.
summarized read-only in the dialog; change them later with Edit Drawing.
Whenever the details change, the dialog looks up active library drawings with
the same normalized customer, name, material and grade and the exact thickness,