fix(cutting): keep Plan Cutting's lead and tab edits

Plan Cutting saved its settings only on a successful Apply, so a plate
that would not plan lost them. Each open then laid the machine's leads
back over the last parameters, and the cutting panel rebuilt parameters
from scratch, dropping kerf and the machine/material/thickness stamp.

Save the settings when the dialog is accepted, keep the panel's
unexposed fields, and only apply the machine's leads when the last
parameters were not already taken from that machine, material, grade
and thickness.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-10-29 00:58:14 -04:00
1 parent a9396ba065
commit 41460481d6
8 files changed
+108 -21

No files matched your search

+19
View File
@@ -73,6 +73,25 @@ public static class MachineCuttingDefaults
return true;
}
/// <summary>
/// Whether <paramref name="parameters"/> are stamped with the machine, material, grade and
/// thickness <see cref="ApplyTo(CuttingParameters, MachineConfig?, Nest)"/> would record for
/// the nest: they started from the machine's settings for it, and any lead edits since are
/// the user's choice for that same job, which the machine's settings must not replace.
/// </summary>
public static bool IsRecordedFor(CuttingParameters parameters, MachineConfig? machine, Nest nest)
{
ArgumentNullException.ThrowIfNull(parameters);
ArgumentNullException.ThrowIfNull(nest);
if (Find(machine, nest) == null)
return false;
var tolerance = ThicknessToleranceInches * Scale(Units.Inches, nest.Units);
return string.Equals(parameters.MachineName, machine!.Name, StringComparison.Ordinal)
&& string.Equals(parameters.MaterialName, nest.Material.Name, StringComparison.Ordinal)
&& string.Equals(parameters.Grade ?? "", nest.Material.Grade ?? "", StringComparison.Ordinal)
&& System.Math.Abs(parameters.Thickness - nest.Thickness) <= tolerance;
}
/// <summary>
/// Replaces the part clearance, overtravel, minimum segment and direction of
/// <paramref name="settings"/> with the machine's for the nest; the head-lock choice is kept.
@@ -41,6 +41,19 @@ public class MachineCuttingDefaultsTests
private static readonly LeadConfig LineArc = new() { Type = "Line + Arc", Length = 0.125, Angle = 90, Radius = 0.0625 };
[Fact]
public void IsRecordedFor_KeepsLeadsEditedAfterTheMachineSettingsWereTaken()
{
var parameters = new CuttingParameters();
Assert.True(MachineCuttingDefaults.ApplyTo(parameters, Machine(LineArc), NestOf(0.25, "A36")));
parameters.ExternalLeadOut = new NoLeadOut();
Assert.True(MachineCuttingDefaults.IsRecordedFor(parameters, Machine(LineArc), NestOf(0.2505, "A36")));
Assert.False(MachineCuttingDefaults.IsRecordedFor(parameters, Machine(LineArc), NestOf(0.25, "A572")));
Assert.False(MachineCuttingDefaults.IsRecordedFor(parameters, Machine(LineArc), NestOf(0.5, "A36")));
Assert.False(MachineCuttingDefaults.IsRecordedFor(new CuttingParameters(), Machine(LineArc), NestOf(0.25, "A36")));
}
[Fact]
public void ApplyTo_GivesEveryContourTheMachineLeadsAndStampsTheSource()
{
@@ -0,0 +1,39 @@
using OpenNest.CNC.CuttingStrategy;
using OpenNest.Controls;
namespace OpenNest.WinForms.Tests.Controls;
public class CuttingPanelTests
{
[Fact]
public void BuildParameters_KeepsTheSettingsThePanelHasNoFieldFor()
{
StaTestThread.Run(() =>
{
using var panel = new CuttingPanel();
panel.LoadFromParameters(new CuttingParameters
{
MachineName = "CL-980",
MaterialName = "Stainless Steel",
Grade = "304",
Thickness = 0.075,
Kerf = 0.008,
ExternalLeadIn = new ArcLeadIn { Radius = 0.0625 },
ExternalLeadOut = new NoLeadOut(),
TabsEnabled = true,
TabConfig = new NormalTab { Size = 0.25 },
});
var built = panel.BuildParameters();
// Without the stamp, the machine's leads would replace these edits on the next open.
Assert.Equal("CL-980", built.MachineName);
Assert.Equal("Stainless Steel", built.MaterialName);
Assert.Equal("304", built.Grade);
Assert.Equal(0.075, built.Thickness);
Assert.Equal(0.008, built.Kerf);
Assert.IsType<NoLeadOut>(built.ExternalLeadOut);
Assert.True(built.TabsEnabled);
}, TimeSpan.FromSeconds(30), "The cutting panel did not build its parameters.");
}
}
+23 -17
View File
@@ -2,6 +2,7 @@ using System;
using System.Drawing;
using System.Windows.Forms;
using OpenNest.CNC.CuttingStrategy;
using OpenNest.IO;
namespace OpenNest.Controls
{
@@ -57,6 +58,8 @@ namespace OpenNest.Controls
private readonly NumericUpDown nudPierceClearance;
private readonly NumericUpDown nudPierceDiameter;
private CuttingParameters loaded;
private readonly CheckBox chkRoundLeadInAngles;
private readonly NumericUpDown nudLeadInAngleIncrement;
@@ -294,30 +297,33 @@ namespace OpenNest.Controls
base.Dispose(disposing);
}
// Settings the panel has no field for (kerf, the machine/material/thickness stamp,
// sequencing) come back unchanged from the parameters last loaded.
public CuttingParameters BuildParameters()
{
return new CuttingParameters
{
ExternalLeadIn = BuildLeadIn(cboExternalLeadIn, pnlExternalLeadIn),
ExternalLeadOut = BuildLeadOut(cboExternalLeadOut, pnlExternalLeadOut),
InternalLeadIn = BuildLeadIn(cboInternalLeadIn, pnlInternalLeadIn),
InternalLeadOut = BuildLeadOut(cboInternalLeadOut, pnlInternalLeadOut),
TabsEnabled = chkTabsEnabled.Checked && rbTabAll.Checked,
TabConfig = new NormalTab { Size = (double)nudTabWidth.Value },
PierceClearance = (double)nudPierceClearance.Value,
PierceDiameter = (double)nudPierceDiameter.Value,
RoundLeadInAngles = chkRoundLeadInAngles.Checked,
LeadInAngleIncrement = (double)nudLeadInAngleIncrement.Value,
SupportTabs = chkTabsEnabled.Checked && rbSupportTab.Checked,
NoBedTabMinWidth = (double)nudNoBedTabMin.Value,
NoBedTabMaxWidth = (double)nudNoBedTabMax.Value,
TabHeldMaxLength = (double)nudTabHeldMax.Value,
};
var parameters = loaded == null ? new CuttingParameters()
: CuttingParametersSerializer.Deserialize(CuttingParametersSerializer.Serialize(loaded));
parameters.ExternalLeadIn = BuildLeadIn(cboExternalLeadIn, pnlExternalLeadIn);
parameters.ExternalLeadOut = BuildLeadOut(cboExternalLeadOut, pnlExternalLeadOut);
parameters.InternalLeadIn = BuildLeadIn(cboInternalLeadIn, pnlInternalLeadIn);
parameters.InternalLeadOut = BuildLeadOut(cboInternalLeadOut, pnlInternalLeadOut);
parameters.TabsEnabled = chkTabsEnabled.Checked && rbTabAll.Checked;
parameters.TabConfig = new NormalTab { Size = (double)nudTabWidth.Value };
parameters.PierceClearance = (double)nudPierceClearance.Value;
parameters.PierceDiameter = (double)nudPierceDiameter.Value;
parameters.RoundLeadInAngles = chkRoundLeadInAngles.Checked;
parameters.LeadInAngleIncrement = (double)nudLeadInAngleIncrement.Value;
parameters.SupportTabs = chkTabsEnabled.Checked && rbSupportTab.Checked;
parameters.NoBedTabMinWidth = (double)nudNoBedTabMin.Value;
parameters.NoBedTabMaxWidth = (double)nudNoBedTabMax.Value;
parameters.TabHeldMaxLength = (double)nudTabHeldMax.Value;
return parameters;
}
public void LoadFromParameters(CuttingParameters p)
{
suppressEvents = true;
loaded = CuttingParametersSerializer.Deserialize(CuttingParametersSerializer.Serialize(p));
LoadLeadIn(cboExternalLeadIn, pnlExternalLeadIn, p.ExternalLeadIn);
LoadLeadOut(cboExternalLeadOut, pnlExternalLeadOut, p.ExternalLeadOut);
+2
View File
@@ -1258,6 +1258,8 @@ internal sealed class CuttingInspectionPanel : UserControl
dialog.LoadParameters(parameters);
if (dialog.ShowDialog(this) != DialogResult.OK || closed || closeRequested) return;
parameters = dialog.GetParameters();
// Saved now, not only on Apply: a plate that will not plan must not lose the edit.
EditNestForm.SaveCuttingParameters(parameters);
ShowSettings();
RestartPlanning();
}
+1 -1
View File
@@ -1356,7 +1356,7 @@ namespace OpenNest.Forms
PlateView.SetAction(typeof(Actions.ActionLeadIn));
}
private static void SaveCuttingParameters(CuttingParameters parameters)
internal static void SaveCuttingParameters(CuttingParameters parameters)
{
var json = CuttingParametersSerializer.Serialize(parameters);
Properties.Settings.Default.CuttingParametersJson = json;
+6 -2
View File
@@ -198,12 +198,16 @@ namespace OpenNest.Forms
/// <summary>
/// The cutting parameters a plate of <paramref name="nest"/> without its own starts from:
/// the last ones applied (or the built-in defaults), with the lead-ins, lead-outs and kerf
/// of the nest's machine for its material and thickness when the machine has them.
/// of the nest's machine for its material and thickness when the machine has them. Last
/// parameters already taken from that machine, material and thickness keep the user's
/// edits to them.
/// </summary>
public static CuttingParameters DefaultCuttingParameters(Nest nest)
{
var parameters = LastCuttingParameters();
MachineCuttingDefaults.ApplyTo(parameters, FindMachine(nest.Machine), nest);
var machine = FindMachine(nest.Machine);
if (!MachineCuttingDefaults.IsRecordedFor(parameters, machine, nest))
MachineCuttingDefaults.ApplyTo(parameters, machine, nest);
return parameters;
}
+5 -1
View File
@@ -19,7 +19,11 @@ machine's units to the nest's.
A plate with no cutting parameters of its own (Plan Cutting, Place Lead-in) starts from the
last applied parameters, as before, with the machine's leads and kerf laid over them and the
machine, material, grade and thickness recorded on the parameters. A plate that already has
machine, material, grade and thickness recorded on the parameters. When the last parameters
already record the same machine, material, grade and thickness, the machine's settings are not
laid over them again, so lead and tab edits made for that job stick; the cutting panel keeps the
record (and the kerf) through an edit. Plan Cutting saves its settings as the last parameters as
soon as its settings dialog is accepted, not only on Apply. A plate that already has
parameters keeps them: changing the machine, material or thickness does not rewrite leads
the plate was planned with.