From 428dbdb03c0e4c79c6e549197d8911375e53e2a3 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Mon, 30 Mar 2026 19:35:51 -0400 Subject: [PATCH] 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) --- .../Forms/CuttingParametersForm.Designer.cs | 4 +- OpenNest/Forms/CuttingParametersForm.cs | 72 ++++++++- OpenNest/Forms/CuttingParametersSerializer.cs | 138 ++++++++++++++++++ OpenNest/Properties/Settings.Designer.cs | 12 ++ OpenNest/Properties/Settings.settings | 3 + OpenNest/app.config | 3 + 6 files changed, 228 insertions(+), 4 deletions(-) create mode 100644 OpenNest/Forms/CuttingParametersSerializer.cs diff --git a/OpenNest/Forms/CuttingParametersForm.Designer.cs b/OpenNest/Forms/CuttingParametersForm.Designer.cs index 528b9d4..2522052 100644 --- a/OpenNest/Forms/CuttingParametersForm.Designer.cs +++ b/OpenNest/Forms/CuttingParametersForm.Designer.cs @@ -114,7 +114,7 @@ namespace OpenNest.Forms this.bottomPanel.Controls.Add(this.acceptButton); this.bottomPanel.Controls.Add(this.cancelButton); 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.Size = new System.Drawing.Size(380, 50); this.bottomPanel.TabIndex = 1; @@ -125,7 +125,7 @@ namespace OpenNest.Forms this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 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.bottomPanel); this.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); diff --git a/OpenNest/Forms/CuttingParametersForm.cs b/OpenNest/Forms/CuttingParametersForm.cs index b0403a9..72fcafd 100644 --- a/OpenNest/Forms/CuttingParametersForm.cs +++ b/OpenNest/Forms/CuttingParametersForm.cs @@ -22,8 +22,20 @@ namespace OpenNest.Forms private CheckBox chkTabsEnabled; 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() { @@ -54,9 +66,33 @@ namespace OpenNest.Forms protected override void OnLoad(EventArgs 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); } + 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, out ComboBox leadInCombo, out Panel leadInPanel, out ComboBox leadOutCombo, out Panel leadOutPanel) @@ -164,6 +200,35 @@ namespace OpenNest.Forms grpTabs.Controls.Add(nudTabWidth); 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() @@ -305,6 +370,8 @@ namespace OpenNest.Forms chkTabsEnabled.Checked = p.TabsEnabled; if (p.TabConfig != null) nudTabWidth.Value = (decimal)p.TabConfig.Size; + + nudPierceClearance.Value = (decimal)p.PierceClearance; } private static void LoadLeadIn(ComboBox combo, Panel panel, LeadIn leadIn) @@ -379,7 +446,8 @@ namespace OpenNest.Forms ArcCircleLeadIn = BuildLeadIn(cboArcCircleLeadIn, pnlArcCircleLeadIn), ArcCircleLeadOut = BuildLeadOut(cboArcCircleLeadOut, pnlArcCircleLeadOut), TabsEnabled = chkTabsEnabled.Checked, - TabConfig = new NormalTab { Size = (double)nudTabWidth.Value } + TabConfig = new NormalTab { Size = (double)nudTabWidth.Value }, + PierceClearance = (double)nudPierceClearance.Value }; return p; } diff --git a/OpenNest/Forms/CuttingParametersSerializer.cs b/OpenNest/Forms/CuttingParametersSerializer.cs new file mode 100644 index 0000000..43f8ae4 --- /dev/null +++ b/OpenNest/Forms/CuttingParametersSerializer.cs @@ -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(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; } + } + } +} diff --git a/OpenNest/Properties/Settings.Designer.cs b/OpenNest/Properties/Settings.Designer.cs index 4d0f038..7e44f3c 100644 --- a/OpenNest/Properties/Settings.Designer.cs +++ b/OpenNest/Properties/Settings.Designer.cs @@ -226,5 +226,17 @@ namespace OpenNest.Properties { 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; + } + } } } diff --git a/OpenNest/Properties/Settings.settings b/OpenNest/Properties/Settings.settings index ad2fe61..cc3fd9b 100644 --- a/OpenNest/Properties/Settings.settings +++ b/OpenNest/Properties/Settings.settings @@ -53,5 +53,8 @@ + + + \ No newline at end of file diff --git a/OpenNest/app.config b/OpenNest/app.config index b72f121..1290afd 100644 --- a/OpenNest/app.config +++ b/OpenNest/app.config @@ -56,6 +56,9 @@ 0 + + +