diff --git a/OpenNest.Data/NestMaterialSelection.cs b/OpenNest.Data/NestMaterialSelection.cs
new file mode 100644
index 0000000..c9aefef
--- /dev/null
+++ b/OpenNest.Data/NestMaterialSelection.cs
@@ -0,0 +1,25 @@
+namespace OpenNest.Data;
+
+///
+/// Chooses the nest material when the Nest Info dialog is accepted. The dialog
+/// edits only the material name, so grade and density must come from the
+/// material the nest already has.
+///
+public static class NestMaterialSelection
+{
+ ///
+ /// Returns a new material named . When that
+ /// name matches the current material's name by ,
+ /// the current grade and density are kept; any other name gets a name-only
+ /// material. The current material is never modified or returned.
+ ///
+ public static Material Apply(Material? current, string? selectedName)
+ {
+ var name = selectedName ?? "";
+
+ if (current != null && SharedListNames.Key(current.Name) == SharedListNames.Key(name))
+ return new Material(name, current.Grade, current.Density);
+
+ return new Material(name);
+ }
+}
diff --git a/OpenNest.Data/SharedListNames.cs b/OpenNest.Data/SharedListNames.cs
new file mode 100644
index 0000000..8533f30
--- /dev/null
+++ b/OpenNest.Data/SharedListNames.cs
@@ -0,0 +1,18 @@
+namespace OpenNest.Data;
+
+///
+/// Normalized matching key for names in company lists (customers, materials).
+///
+public static class SharedListNames
+{
+ ///
+ /// Returns the name trimmed, with internal whitespace runs collapsed to one
+ /// space, in invariant upper case. Names that differ only in spacing or case
+ /// share a key. A null or blank name has the empty key.
+ ///
+ public static string Key(string? name) =>
+ name == null
+ ? ""
+ : string.Join(' ', name.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries))
+ .ToUpperInvariant();
+}
diff --git a/OpenNest.Tests/Data/NestMaterialSelectionTests.cs b/OpenNest.Tests/Data/NestMaterialSelectionTests.cs
new file mode 100644
index 0000000..243855a
--- /dev/null
+++ b/OpenNest.Tests/Data/NestMaterialSelectionTests.cs
@@ -0,0 +1,90 @@
+using OpenNest.Data;
+
+namespace OpenNest.Tests.Data;
+
+public class NestMaterialSelectionTests
+{
+ [Fact]
+ public void Apply_UnchangedName_KeepsGradeAndDensity()
+ {
+ var current = new Material("Stainless", "304", 0.289);
+
+ var result = NestMaterialSelection.Apply(current, "Stainless");
+
+ Assert.Equal("Stainless", result.Name);
+ Assert.Equal("304", result.Grade);
+ Assert.Equal(0.289, result.Density);
+ }
+
+ [Fact]
+ public void Apply_NameDifferingOnlyInSpacingAndCase_KeepsGradeAndDensityAndTakesTypedName()
+ {
+ var current = new Material("Cold Rolled Steel", "1008", 0.284);
+
+ var result = NestMaterialSelection.Apply(current, " cold rolled\tsteel ");
+
+ Assert.Equal(" cold rolled\tsteel ", result.Name);
+ Assert.Equal("1008", result.Grade);
+ Assert.Equal(0.284, result.Density);
+ }
+
+ [Fact]
+ public void Apply_ChangedName_GetsNameOnly()
+ {
+ var current = new Material("Stainless", "304", 0.289);
+
+ var result = NestMaterialSelection.Apply(current, "Aluminum");
+
+ Assert.Equal("Aluminum", result.Name);
+ Assert.True(string.IsNullOrEmpty(result.Grade));
+ Assert.Equal(0, result.Density);
+ }
+
+ [Fact]
+ public void Apply_ClearedName_GetsEmptyNameOnly()
+ {
+ var current = new Material("Stainless", "304", 0.289);
+
+ var result = NestMaterialSelection.Apply(current, "");
+
+ Assert.Equal("", result.Name);
+ Assert.True(string.IsNullOrEmpty(result.Grade));
+ Assert.Equal(0, result.Density);
+ }
+
+ [Fact]
+ public void Apply_NoCurrentMaterial_GetsNameOnly()
+ {
+ var result = NestMaterialSelection.Apply(null, "Stainless");
+
+ Assert.Equal("Stainless", result.Name);
+ Assert.True(string.IsNullOrEmpty(result.Grade));
+ Assert.Equal(0, result.Density);
+ }
+
+ [Fact]
+ public void Apply_ReturnsCopy_LeavingCurrentMaterialUnchanged()
+ {
+ var current = new Material("Stainless", "304", 0.289);
+
+ var result = NestMaterialSelection.Apply(current, "stainless");
+ result.Grade = "316";
+ result.Density = 1;
+
+ Assert.NotSame(current, result);
+ Assert.Equal("Stainless", current.Name);
+ Assert.Equal("304", current.Grade);
+ Assert.Equal(0.289, current.Density);
+ }
+
+ [Theory]
+ [InlineData("Carbon Steel", "CARBON STEEL")]
+ [InlineData(" carbon \t steel\n", "CARBON STEEL")]
+ [InlineData("", "")]
+ [InlineData(" ", "")]
+ [InlineData(null, "")]
+ public void SharedListNamesKey_TrimsCollapsesWhitespaceAndUppercases(string? name, string expected)
+ {
+ Assert.Equal(expected, SharedListNames.Key(name));
+ }
+}
diff --git a/OpenNest.WinForms.Tests/Forms/EditNestInfoFormTests.cs b/OpenNest.WinForms.Tests/Forms/EditNestInfoFormTests.cs
new file mode 100644
index 0000000..44bb9a8
--- /dev/null
+++ b/OpenNest.WinForms.Tests/Forms/EditNestInfoFormTests.cs
@@ -0,0 +1,44 @@
+using OpenNest.Forms;
+
+namespace OpenNest.WinForms.Tests.Forms;
+
+public class EditNestInfoFormTests
+{
+ [Fact]
+ public void SaveWithUnchangedMaterial_KeepsGradeAndDensity()
+ {
+ RunSta(() =>
+ {
+ var nest = new Nest { Material = new Material("Stainless", "304", 0.289) };
+ using var form = new EditNestInfoForm();
+ form.LoadNestInfo(nest);
+
+ form.SaveNestInfo(nest);
+
+ Assert.Equal("Stainless", nest.Material.Name);
+ Assert.Equal("304", nest.Material.Grade);
+ Assert.Equal(0.289, nest.Material.Density);
+ });
+ }
+
+ [Fact]
+ public void SaveWithChangedMaterial_GetsNameOnly()
+ {
+ RunSta(() =>
+ {
+ var nest = new Nest { Material = new Material("Stainless", "304", 0.289) };
+ using var form = new EditNestInfoForm();
+ form.LoadNestInfo(nest);
+ form.MaterialName = "Aluminum";
+
+ form.SaveNestInfo(nest);
+
+ Assert.Equal("Aluminum", nest.Material.Name);
+ Assert.True(string.IsNullOrEmpty(nest.Material.Grade));
+ Assert.Equal(0, nest.Material.Density);
+ });
+ }
+
+ private static void RunSta(Action action) =>
+ StaTestThread.Run(action, TimeSpan.FromSeconds(15), "The STA test did not complete.");
+}
diff --git a/OpenNest/Forms/EditNestInfoForm.cs b/OpenNest/Forms/EditNestInfoForm.cs
index f1d38af..3cd4f62 100644
--- a/OpenNest/Forms/EditNestInfoForm.cs
+++ b/OpenNest/Forms/EditNestInfoForm.cs
@@ -1,5 +1,6 @@
using System;
using System.Windows.Forms;
+using OpenNest.Data;
using OpenNest.Geometry;
using Timer = System.Timers.Timer;
@@ -238,7 +239,7 @@ namespace OpenNest.Forms
nest.DateCreated = DateCreated;
nest.DateLastModified = DateLastModified;
nest.Thickness = Thickness;
- nest.Material = new Material(MaterialName);
+ nest.Material = NestMaterialSelection.Apply(nest.Material, MaterialName);
nest.Status = Status;
nest.MadeBy = MadeBy;
nest.PlateDefaults.Size = OpenNest.Geometry.Size.Parse(SizeString);