feat: persist cutting parameters and add pierce clearance UI

Save/restore cutting parameters as JSON in user settings so values
survive between sessions. Add pierce clearance numeric input to the
CuttingParametersForm.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-30 19:35:51 -04:00
parent e860ca3f4a
commit 428dbdb03c
6 changed files with 228 additions and 4 deletions
+2 -2
View File
@@ -114,7 +114,7 @@ namespace OpenNest.Forms
this.bottomPanel.Controls.Add(this.acceptButton); this.bottomPanel.Controls.Add(this.acceptButton);
this.bottomPanel.Controls.Add(this.cancelButton); this.bottomPanel.Controls.Add(this.cancelButton);
this.bottomPanel.Dock = System.Windows.Forms.DockStyle.Bottom; this.bottomPanel.Dock = System.Windows.Forms.DockStyle.Bottom;
this.bottomPanel.Location = new System.Drawing.Point(0, 406); this.bottomPanel.Location = new System.Drawing.Point(0, 466);
this.bottomPanel.Name = "bottomPanel"; this.bottomPanel.Name = "bottomPanel";
this.bottomPanel.Size = new System.Drawing.Size(380, 50); this.bottomPanel.Size = new System.Drawing.Size(380, 50);
this.bottomPanel.TabIndex = 1; this.bottomPanel.TabIndex = 1;
@@ -125,7 +125,7 @@ namespace OpenNest.Forms
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.cancelButton; this.CancelButton = this.cancelButton;
this.ClientSize = new System.Drawing.Size(380, 456); this.ClientSize = new System.Drawing.Size(380, 516);
this.Controls.Add(this.tabControl); this.Controls.Add(this.tabControl);
this.Controls.Add(this.bottomPanel); this.Controls.Add(this.bottomPanel);
this.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+70 -2
View File
@@ -22,8 +22,20 @@ namespace OpenNest.Forms
private CheckBox chkTabsEnabled; private CheckBox chkTabsEnabled;
private NumericUpDown nudTabWidth; private NumericUpDown nudTabWidth;
private NumericUpDown nudPierceClearance;
public CuttingParameters Parameters { get; set; } = new CuttingParameters(); private bool hasCustomParameters;
private CuttingParameters parameters = new CuttingParameters();
public CuttingParameters Parameters
{
get => parameters;
set
{
parameters = value;
hasCustomParameters = true;
}
}
public CuttingParametersForm() public CuttingParametersForm()
{ {
@@ -54,9 +66,33 @@ namespace OpenNest.Forms
protected override void OnLoad(EventArgs e) protected override void OnLoad(EventArgs e)
{ {
base.OnLoad(e); base.OnLoad(e);
// If caller didn't provide custom parameters, try loading saved ones
if (!hasCustomParameters)
{
var json = Properties.Settings.Default.CuttingParametersJson;
if (!string.IsNullOrEmpty(json))
{
try { Parameters = CuttingParametersSerializer.Deserialize(json); }
catch { /* use defaults on corrupt data */ }
}
}
LoadFromParameters(Parameters); LoadFromParameters(Parameters);
} }
protected override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
if (DialogResult == System.Windows.Forms.DialogResult.OK)
{
var json = CuttingParametersSerializer.Serialize(BuildParameters());
Properties.Settings.Default.CuttingParametersJson = json;
Properties.Settings.Default.Save();
}
}
private static void SetupTab(TabPage tab, private static void SetupTab(TabPage tab,
out ComboBox leadInCombo, out Panel leadInPanel, out ComboBox leadInCombo, out Panel leadInPanel,
out ComboBox leadOutCombo, out Panel leadOutPanel) out ComboBox leadOutCombo, out Panel leadOutPanel)
@@ -164,6 +200,35 @@ namespace OpenNest.Forms
grpTabs.Controls.Add(nudTabWidth); grpTabs.Controls.Add(nudTabWidth);
Controls.Add(grpTabs); Controls.Add(grpTabs);
var grpPierce = new GroupBox
{
Text = "Pierce",
Location = new System.Drawing.Point(4, 410),
Size = new System.Drawing.Size(372, 55),
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right
};
grpPierce.Controls.Add(new Label
{
Text = "Pierce Clearance:",
Location = new System.Drawing.Point(12, 23),
AutoSize = true
});
nudPierceClearance = new NumericUpDown
{
Location = new System.Drawing.Point(130, 20),
Size = new System.Drawing.Size(100, 22),
DecimalPlaces = 4,
Increment = 0.0625m,
Minimum = 0,
Maximum = 9999,
Value = 0.0625m
};
grpPierce.Controls.Add(nudPierceClearance);
Controls.Add(grpPierce);
} }
private void PopulateDropdowns() private void PopulateDropdowns()
@@ -305,6 +370,8 @@ namespace OpenNest.Forms
chkTabsEnabled.Checked = p.TabsEnabled; chkTabsEnabled.Checked = p.TabsEnabled;
if (p.TabConfig != null) if (p.TabConfig != null)
nudTabWidth.Value = (decimal)p.TabConfig.Size; nudTabWidth.Value = (decimal)p.TabConfig.Size;
nudPierceClearance.Value = (decimal)p.PierceClearance;
} }
private static void LoadLeadIn(ComboBox combo, Panel panel, LeadIn leadIn) private static void LoadLeadIn(ComboBox combo, Panel panel, LeadIn leadIn)
@@ -379,7 +446,8 @@ namespace OpenNest.Forms
ArcCircleLeadIn = BuildLeadIn(cboArcCircleLeadIn, pnlArcCircleLeadIn), ArcCircleLeadIn = BuildLeadIn(cboArcCircleLeadIn, pnlArcCircleLeadIn),
ArcCircleLeadOut = BuildLeadOut(cboArcCircleLeadOut, pnlArcCircleLeadOut), ArcCircleLeadOut = BuildLeadOut(cboArcCircleLeadOut, pnlArcCircleLeadOut),
TabsEnabled = chkTabsEnabled.Checked, TabsEnabled = chkTabsEnabled.Checked,
TabConfig = new NormalTab { Size = (double)nudTabWidth.Value } TabConfig = new NormalTab { Size = (double)nudTabWidth.Value },
PierceClearance = (double)nudPierceClearance.Value
}; };
return p; return p;
} }
@@ -0,0 +1,138 @@
using OpenNest.CNC.CuttingStrategy;
using System.Text.Json;
namespace OpenNest.Forms
{
internal static class CuttingParametersSerializer
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = false,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
public static string Serialize(CuttingParameters p)
{
var dto = new CuttingParametersDto
{
ExternalLeadIn = ToDto(p.ExternalLeadIn),
ExternalLeadOut = ToLeadOutDto(p.ExternalLeadOut),
InternalLeadIn = ToDto(p.InternalLeadIn),
InternalLeadOut = ToLeadOutDto(p.InternalLeadOut),
ArcCircleLeadIn = ToDto(p.ArcCircleLeadIn),
ArcCircleLeadOut = ToLeadOutDto(p.ArcCircleLeadOut),
TabsEnabled = p.TabsEnabled,
TabWidth = p.TabConfig?.Size ?? 0.25,
PierceClearance = p.PierceClearance
};
return JsonSerializer.Serialize(dto, JsonOptions);
}
public static CuttingParameters Deserialize(string json)
{
var dto = JsonSerializer.Deserialize<CuttingParametersDto>(json, JsonOptions);
if (dto == null)
return new CuttingParameters();
return new CuttingParameters
{
ExternalLeadIn = FromDto(dto.ExternalLeadIn),
ExternalLeadOut = FromLeadOutDto(dto.ExternalLeadOut),
InternalLeadIn = FromDto(dto.InternalLeadIn),
InternalLeadOut = FromLeadOutDto(dto.InternalLeadOut),
ArcCircleLeadIn = FromDto(dto.ArcCircleLeadIn),
ArcCircleLeadOut = FromLeadOutDto(dto.ArcCircleLeadOut),
TabsEnabled = dto.TabsEnabled,
TabConfig = new NormalTab { Size = dto.TabWidth },
PierceClearance = dto.PierceClearance
};
}
private static LeadInDto ToDto(LeadIn leadIn)
{
return leadIn switch
{
LineLeadIn line => new LeadInDto { Type = "Line", Length = line.Length, ApproachAngle = line.ApproachAngle },
ArcLeadIn arc => new LeadInDto { Type = "Arc", Radius = arc.Radius },
LineArcLeadIn la => new LeadInDto { Type = "LineArc", LineLength = la.LineLength, ArcRadius = la.ArcRadius, ApproachAngle = la.ApproachAngle },
CleanHoleLeadIn ch => new LeadInDto { Type = "CleanHole", LineLength = ch.LineLength, ArcRadius = ch.ArcRadius, Kerf = ch.Kerf },
LineLineLeadIn ll => new LeadInDto { Type = "LineLine", Length1 = ll.Length1, Angle1 = ll.ApproachAngle1, Length2 = ll.Length2, Angle2 = ll.ApproachAngle2 },
_ => new LeadInDto { Type = "None" }
};
}
private static LeadIn FromDto(LeadInDto dto)
{
if (dto == null) return new NoLeadIn();
return dto.Type switch
{
"Line" => new LineLeadIn { Length = dto.Length, ApproachAngle = dto.ApproachAngle },
"Arc" => new ArcLeadIn { Radius = dto.Radius },
"LineArc" => new LineArcLeadIn { LineLength = dto.LineLength, ArcRadius = dto.ArcRadius, ApproachAngle = dto.ApproachAngle },
"CleanHole" => new CleanHoleLeadIn { LineLength = dto.LineLength, ArcRadius = dto.ArcRadius, Kerf = dto.Kerf },
"LineLine" => new LineLineLeadIn { Length1 = dto.Length1, ApproachAngle1 = dto.Angle1, Length2 = dto.Length2, ApproachAngle2 = dto.Angle2 },
_ => new NoLeadIn()
};
}
private static LeadOutDto ToLeadOutDto(LeadOut leadOut)
{
return leadOut switch
{
LineLeadOut line => new LeadOutDto { Type = "Line", Length = line.Length, ApproachAngle = line.ApproachAngle },
ArcLeadOut arc => new LeadOutDto { Type = "Arc", Radius = arc.Radius },
MicrotabLeadOut mt => new LeadOutDto { Type = "Microtab", GapSize = mt.GapSize },
_ => new LeadOutDto { Type = "None" }
};
}
private static LeadOut FromLeadOutDto(LeadOutDto dto)
{
if (dto == null) return new NoLeadOut();
return dto.Type switch
{
"Line" => new LineLeadOut { Length = dto.Length, ApproachAngle = dto.ApproachAngle },
"Arc" => new ArcLeadOut { Radius = dto.Radius },
"Microtab" => new MicrotabLeadOut { GapSize = dto.GapSize },
_ => new NoLeadOut()
};
}
private class CuttingParametersDto
{
public LeadInDto ExternalLeadIn { get; set; }
public LeadOutDto ExternalLeadOut { get; set; }
public LeadInDto InternalLeadIn { get; set; }
public LeadOutDto InternalLeadOut { get; set; }
public LeadInDto ArcCircleLeadIn { get; set; }
public LeadOutDto ArcCircleLeadOut { get; set; }
public bool TabsEnabled { get; set; }
public double TabWidth { get; set; }
public double PierceClearance { get; set; }
}
private class LeadInDto
{
public string Type { get; set; } = "None";
public double Length { get; set; }
public double ApproachAngle { get; set; }
public double Radius { get; set; }
public double LineLength { get; set; }
public double ArcRadius { get; set; }
public double Kerf { get; set; }
public double Length1 { get; set; }
public double Angle1 { get; set; }
public double Length2 { get; set; }
public double Angle2 { get; set; }
}
private class LeadOutDto
{
public string Type { get; set; } = "None";
public double Length { get; set; }
public double ApproachAngle { get; set; }
public double Radius { get; set; }
public double GapSize { get; set; }
}
}
}
+12
View File
@@ -226,5 +226,17 @@ namespace OpenNest.Properties {
this["DisabledStrategies"] = value; this["DisabledStrategies"] = value;
} }
} }
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string CuttingParametersJson {
get {
return ((string)(this["CuttingParametersJson"]));
}
set {
this["CuttingParametersJson"] = value;
}
}
} }
} }
+3
View File
@@ -53,5 +53,8 @@
<Setting Name="DisabledStrategies" Type="System.String" Scope="User"> <Setting Name="DisabledStrategies" Type="System.String" Scope="User">
<Value Profile="(Default)" /> <Value Profile="(Default)" />
</Setting> </Setting>
<Setting Name="CuttingParametersJson" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
</Settings> </Settings>
</SettingsFile> </SettingsFile>
+3
View File
@@ -56,6 +56,9 @@
<setting name="LastPierceTime" serializeAs="String"> <setting name="LastPierceTime" serializeAs="String">
<value>0</value> <value>0</value>
</setting> </setting>
<setting name="CuttingParametersJson" serializeAs="String">
<value/>
</setting>
</OpenNest.Properties.Settings> </OpenNest.Properties.Settings>
<OpenNest.Resources.Settings> <OpenNest.Resources.Settings>
<setting name="MainFormLocation" serializeAs="String"> <setting name="MainFormLocation" serializeAs="String">