feat: create reusable CuttingPanel control

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-02 13:36:22 -04:00
parent 9f9111975d
commit ebab795f86

View File

@@ -0,0 +1,603 @@
using OpenNest.CNC.CuttingStrategy;
using System;
using System.Drawing;
using System.Windows.Forms;
namespace OpenNest.Controls
{
public class CuttingPanel : Panel
{
private static readonly string[] LeadInTypes =
{ "None", "Line", "Arc", "Line + Arc", "Clean Hole", "Line + Line" };
private static readonly string[] LeadOutTypes =
{ "None", "Line", "Arc", "Microtab" };
private readonly TabControl tabControl;
private readonly ComboBox cboExternalLeadIn, cboExternalLeadOut;
private readonly ComboBox cboInternalLeadIn, cboInternalLeadOut;
private readonly ComboBox cboArcCircleLeadIn, cboArcCircleLeadOut;
private readonly Panel pnlExternalLeadIn, pnlExternalLeadOut;
private readonly Panel pnlInternalLeadIn, pnlInternalLeadOut;
private readonly Panel pnlArcCircleLeadIn, pnlArcCircleLeadOut;
private readonly CheckBox chkTabsEnabled;
private readonly NumericUpDown nudTabWidth;
private readonly NumericUpDown nudAutoTabMin;
private readonly NumericUpDown nudAutoTabMax;
private readonly NumericUpDown nudPierceClearance;
private readonly Button btnAutoAssign;
private bool suppressEvents;
public event EventHandler ParametersChanged;
public event EventHandler AutoAssignClicked;
public bool ShowAutoAssign
{
get => btnAutoAssign.Visible;
set => btnAutoAssign.Visible = value;
}
public ContourType? ActiveContourType
{
get
{
return tabControl.SelectedIndex switch
{
0 => ContourType.External,
1 => ContourType.Internal,
2 => ContourType.ArcCircle,
_ => null
};
}
set
{
if (value == null)
return;
var index = value.Value switch
{
ContourType.External => 0,
ContourType.Internal => 1,
ContourType.ArcCircle => 2,
_ => -1
};
if (index >= 0 && tabControl.SelectedIndex != index)
tabControl.SelectedIndex = index;
}
}
public CuttingPanel()
{
AutoScroll = true;
BackColor = Color.White;
var y = 0;
// Tab control for contour types
tabControl = new TabControl
{
Location = new Point(4, y),
Size = new Size(372, 340),
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right
};
var tabExternal = new TabPage("External") { Padding = new Padding(4) };
var tabInternal = new TabPage("Internal") { Padding = new Padding(4) };
var tabArcCircle = new TabPage("Arc / Circle") { Padding = new Padding(4) };
SetupTab(tabExternal, out cboExternalLeadIn, out pnlExternalLeadIn,
out cboExternalLeadOut, out pnlExternalLeadOut);
SetupTab(tabInternal, out cboInternalLeadIn, out pnlInternalLeadIn,
out cboInternalLeadOut, out pnlInternalLeadOut);
SetupTab(tabArcCircle, out cboArcCircleLeadIn, out pnlArcCircleLeadIn,
out cboArcCircleLeadOut, out pnlArcCircleLeadOut);
tabControl.Controls.Add(tabExternal);
tabControl.Controls.Add(tabInternal);
tabControl.Controls.Add(tabArcCircle);
Controls.Add(tabControl);
y += tabControl.Height + 4;
// Tabs section
var tabsPanel = new CollapsiblePanel
{
HeaderText = "Tabs",
Location = new Point(0, y),
Size = new Size(380, 120),
ExpandedHeight = 120,
IsExpanded = true,
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right
};
chkTabsEnabled = new CheckBox
{
Text = "Enable Tabs",
Location = new Point(12, 4),
AutoSize = true
};
chkTabsEnabled.CheckedChanged += (s, e) =>
{
nudTabWidth.Enabled = chkTabsEnabled.Checked;
OnParametersChanged();
};
tabsPanel.ContentPanel.Controls.Add(chkTabsEnabled);
tabsPanel.ContentPanel.Controls.Add(new Label
{
Text = "Width:",
Location = new Point(160, 6),
AutoSize = true
});
nudTabWidth = CreateNumeric(215, 3, 0.25, 0.0625);
nudTabWidth.Enabled = false;
tabsPanel.ContentPanel.Controls.Add(nudTabWidth);
tabsPanel.ContentPanel.Controls.Add(new Label
{
Text = "Auto-Tab Min Size:",
Location = new Point(12, 32),
AutoSize = true
});
nudAutoTabMin = CreateNumeric(140, 29, 0, 0.0625);
tabsPanel.ContentPanel.Controls.Add(nudAutoTabMin);
tabsPanel.ContentPanel.Controls.Add(new Label
{
Text = "Auto-Tab Max Size:",
Location = new Point(12, 58),
AutoSize = true
});
nudAutoTabMax = CreateNumeric(140, 55, 0, 0.0625);
tabsPanel.ContentPanel.Controls.Add(nudAutoTabMax);
Controls.Add(tabsPanel);
y += tabsPanel.Height + 4;
// Pierce section
var piercePanel = new CollapsiblePanel
{
HeaderText = "Pierce",
Location = new Point(0, y),
Size = new Size(380, 60),
ExpandedHeight = 60,
IsExpanded = true,
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right
};
piercePanel.ContentPanel.Controls.Add(new Label
{
Text = "Pierce Clearance:",
Location = new Point(12, 6),
AutoSize = true
});
nudPierceClearance = CreateNumeric(130, 3, 0.0625, 0.0625);
piercePanel.ContentPanel.Controls.Add(nudPierceClearance);
Controls.Add(piercePanel);
y += piercePanel.Height + 4;
// Auto-Assign button
btnAutoAssign = new Button
{
Text = "Auto-Assign Lead-ins",
Location = new Point(4, y),
Size = new Size(372, 32),
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
Visible = false
};
btnAutoAssign.Click += (s, e) => AutoAssignClicked?.Invoke(this, EventArgs.Empty);
Controls.Add(btnAutoAssign);
// Wire up change events
PopulateDropdowns();
WireChangeEvents();
}
public CuttingParameters BuildParameters()
{
return new CuttingParameters
{
ExternalLeadIn = BuildLeadIn(cboExternalLeadIn, pnlExternalLeadIn),
ExternalLeadOut = BuildLeadOut(cboExternalLeadOut, pnlExternalLeadOut),
InternalLeadIn = BuildLeadIn(cboInternalLeadIn, pnlInternalLeadIn),
InternalLeadOut = BuildLeadOut(cboInternalLeadOut, pnlInternalLeadOut),
ArcCircleLeadIn = BuildLeadIn(cboArcCircleLeadIn, pnlArcCircleLeadIn),
ArcCircleLeadOut = BuildLeadOut(cboArcCircleLeadOut, pnlArcCircleLeadOut),
TabsEnabled = chkTabsEnabled.Checked,
TabConfig = new NormalTab { Size = (double)nudTabWidth.Value },
PierceClearance = (double)nudPierceClearance.Value,
AutoTabMinSize = (double)nudAutoTabMin.Value,
AutoTabMaxSize = (double)nudAutoTabMax.Value
};
}
public void LoadFromParameters(CuttingParameters p)
{
suppressEvents = true;
LoadLeadIn(cboExternalLeadIn, pnlExternalLeadIn, p.ExternalLeadIn);
LoadLeadOut(cboExternalLeadOut, pnlExternalLeadOut, p.ExternalLeadOut);
LoadLeadIn(cboInternalLeadIn, pnlInternalLeadIn, p.InternalLeadIn);
LoadLeadOut(cboInternalLeadOut, pnlInternalLeadOut, p.InternalLeadOut);
LoadLeadIn(cboArcCircleLeadIn, pnlArcCircleLeadIn, p.ArcCircleLeadIn);
LoadLeadOut(cboArcCircleLeadOut, pnlArcCircleLeadOut, p.ArcCircleLeadOut);
chkTabsEnabled.Checked = p.TabsEnabled;
if (p.TabConfig != null)
nudTabWidth.Value = (decimal)p.TabConfig.Size;
nudPierceClearance.Value = (decimal)p.PierceClearance;
nudAutoTabMin.Value = (decimal)p.AutoTabMinSize;
nudAutoTabMax.Value = (decimal)p.AutoTabMaxSize;
suppressEvents = false;
}
private void OnParametersChanged()
{
if (!suppressEvents)
ParametersChanged?.Invoke(this, EventArgs.Empty);
}
private static void SetupTab(TabPage tab,
out ComboBox leadInCombo, out Panel leadInPanel,
out ComboBox leadOutCombo, out Panel leadOutPanel)
{
var grpLeadIn = new GroupBox
{
Text = "Lead-In",
Location = new Point(4, 4),
Size = new Size(340, 148)
};
tab.Controls.Add(grpLeadIn);
grpLeadIn.Controls.Add(new Label
{
Text = "Type:",
Location = new Point(8, 22),
AutoSize = true
});
leadInCombo = new ComboBox
{
DropDownStyle = ComboBoxStyle.DropDownList,
Location = new Point(90, 19),
Size = new Size(230, 24)
};
grpLeadIn.Controls.Add(leadInCombo);
leadInPanel = new Panel
{
Location = new Point(8, 48),
Size = new Size(320, 92),
AutoScroll = true
};
grpLeadIn.Controls.Add(leadInPanel);
var grpLeadOut = new GroupBox
{
Text = "Lead-Out",
Location = new Point(4, 156),
Size = new Size(340, 132)
};
tab.Controls.Add(grpLeadOut);
grpLeadOut.Controls.Add(new Label
{
Text = "Type:",
Location = new Point(8, 22),
AutoSize = true
});
leadOutCombo = new ComboBox
{
DropDownStyle = ComboBoxStyle.DropDownList,
Location = new Point(90, 19),
Size = new Size(230, 24)
};
grpLeadOut.Controls.Add(leadOutCombo);
leadOutPanel = new Panel
{
Location = new Point(8, 48),
Size = new Size(320, 76),
AutoScroll = true
};
grpLeadOut.Controls.Add(leadOutPanel);
}
private void PopulateDropdowns()
{
foreach (var combo in new[] { cboExternalLeadIn, cboInternalLeadIn, cboArcCircleLeadIn })
{
combo.Items.AddRange(LeadInTypes);
combo.SelectedIndex = 0;
}
foreach (var combo in new[] { cboExternalLeadOut, cboInternalLeadOut, cboArcCircleLeadOut })
{
combo.Items.AddRange(LeadOutTypes);
combo.SelectedIndex = 0;
}
}
private void WireChangeEvents()
{
cboExternalLeadIn.SelectedIndexChanged += OnLeadInTypeChanged;
cboInternalLeadIn.SelectedIndexChanged += OnLeadInTypeChanged;
cboArcCircleLeadIn.SelectedIndexChanged += OnLeadInTypeChanged;
cboExternalLeadOut.SelectedIndexChanged += OnLeadOutTypeChanged;
cboInternalLeadOut.SelectedIndexChanged += OnLeadOutTypeChanged;
cboArcCircleLeadOut.SelectedIndexChanged += OnLeadOutTypeChanged;
}
private void OnLeadInTypeChanged(object sender, EventArgs e)
{
var combo = (ComboBox)sender;
var panel = GetLeadInPanel(combo);
if (panel != null)
BuildLeadInParamControls(panel, combo.SelectedIndex);
OnParametersChanged();
}
private void OnLeadOutTypeChanged(object sender, EventArgs e)
{
var combo = (ComboBox)sender;
var panel = GetLeadOutPanel(combo);
if (panel != null)
BuildLeadOutParamControls(panel, combo.SelectedIndex);
OnParametersChanged();
}
private Panel GetLeadInPanel(ComboBox combo)
{
if (combo == cboExternalLeadIn) return pnlExternalLeadIn;
if (combo == cboInternalLeadIn) return pnlInternalLeadIn;
if (combo == cboArcCircleLeadIn) return pnlArcCircleLeadIn;
return null;
}
private Panel GetLeadOutPanel(ComboBox combo)
{
if (combo == cboExternalLeadOut) return pnlExternalLeadOut;
if (combo == cboInternalLeadOut) return pnlInternalLeadOut;
if (combo == cboArcCircleLeadOut) return pnlArcCircleLeadOut;
return null;
}
private void BuildLeadInParamControls(Panel panel, int typeIndex)
{
panel.Controls.Clear();
var y = 0;
switch (typeIndex)
{
case 1:
AddNumericField(panel, "Length:", 0.25, ref y, "Length");
AddNumericField(panel, "Approach Angle:", 90, ref y, "ApproachAngle");
break;
case 2:
AddNumericField(panel, "Radius:", 0.25, ref y, "Radius");
break;
case 3:
AddNumericField(panel, "Line Length:", 0.25, ref y, "LineLength");
AddNumericField(panel, "Arc Radius:", 0.125, ref y, "ArcRadius");
AddNumericField(panel, "Approach Angle:", 135, ref y, "ApproachAngle");
break;
case 4:
AddNumericField(panel, "Line Length:", 0.25, ref y, "LineLength");
AddNumericField(panel, "Arc Radius:", 0.125, ref y, "ArcRadius");
AddNumericField(panel, "Kerf:", 0.06, ref y, "Kerf");
break;
case 5:
AddNumericField(panel, "Length 1:", 0.25, ref y, "Length1");
AddNumericField(panel, "Angle 1:", 90, ref y, "Angle1");
AddNumericField(panel, "Length 2:", 0.25, ref y, "Length2");
AddNumericField(panel, "Angle 2:", 90, ref y, "Angle2");
break;
}
}
private void BuildLeadOutParamControls(Panel panel, int typeIndex)
{
panel.Controls.Clear();
var y = 0;
switch (typeIndex)
{
case 1:
AddNumericField(panel, "Length:", 0.25, ref y, "Length");
AddNumericField(panel, "Approach Angle:", 90, ref y, "ApproachAngle");
break;
case 2:
AddNumericField(panel, "Radius:", 0.25, ref y, "Radius");
break;
case 3:
AddNumericField(panel, "Gap Size:", 0.06, ref y, "GapSize");
break;
}
}
private void AddNumericField(Panel panel, string label, double defaultValue,
ref int y, string tag)
{
panel.Controls.Add(new Label
{
Text = label,
Location = new Point(0, y + 3),
AutoSize = true
});
var nud = CreateNumeric(130, y, defaultValue, 0.0625);
nud.Tag = tag;
nud.ValueChanged += (s, e) => OnParametersChanged();
panel.Controls.Add(nud);
y += 30;
}
private static NumericUpDown CreateNumeric(int x, int y, double defaultValue, double increment)
{
return new NumericUpDown
{
Location = new Point(x, y),
Size = new Size(120, 22),
DecimalPlaces = 4,
Increment = (decimal)increment,
Minimum = 0,
Maximum = 9999,
Value = (decimal)defaultValue
};
}
private static void LoadLeadIn(ComboBox combo, Panel panel, LeadIn leadIn)
{
switch (leadIn)
{
case LineLeadIn line:
combo.SelectedIndex = 1;
SetParam(panel, "Length", line.Length);
SetParam(panel, "ApproachAngle", line.ApproachAngle);
break;
case ArcLeadIn arc:
combo.SelectedIndex = 2;
SetParam(panel, "Radius", arc.Radius);
break;
case LineArcLeadIn lineArc:
combo.SelectedIndex = 3;
SetParam(panel, "LineLength", lineArc.LineLength);
SetParam(panel, "ArcRadius", lineArc.ArcRadius);
SetParam(panel, "ApproachAngle", lineArc.ApproachAngle);
break;
case CleanHoleLeadIn cleanHole:
combo.SelectedIndex = 4;
SetParam(panel, "LineLength", cleanHole.LineLength);
SetParam(panel, "ArcRadius", cleanHole.ArcRadius);
SetParam(panel, "Kerf", cleanHole.Kerf);
break;
case LineLineLeadIn lineLine:
combo.SelectedIndex = 5;
SetParam(panel, "Length1", lineLine.Length1);
SetParam(panel, "Angle1", lineLine.ApproachAngle1);
SetParam(panel, "Length2", lineLine.Length2);
SetParam(panel, "Angle2", lineLine.ApproachAngle2);
break;
default:
combo.SelectedIndex = 0;
break;
}
}
private static void LoadLeadOut(ComboBox combo, Panel panel, LeadOut leadOut)
{
switch (leadOut)
{
case LineLeadOut line:
combo.SelectedIndex = 1;
SetParam(panel, "Length", line.Length);
SetParam(panel, "ApproachAngle", line.ApproachAngle);
break;
case ArcLeadOut arc:
combo.SelectedIndex = 2;
SetParam(panel, "Radius", arc.Radius);
break;
case MicrotabLeadOut microtab:
combo.SelectedIndex = 3;
SetParam(panel, "GapSize", microtab.GapSize);
break;
default:
combo.SelectedIndex = 0;
break;
}
}
private static LeadIn BuildLeadIn(ComboBox combo, Panel panel)
{
return combo.SelectedIndex switch
{
1 => new LineLeadIn
{
Length = GetParam(panel, "Length", 0.25),
ApproachAngle = GetParam(panel, "ApproachAngle", 90)
},
2 => new ArcLeadIn
{
Radius = GetParam(panel, "Radius", 0.25)
},
3 => new LineArcLeadIn
{
LineLength = GetParam(panel, "LineLength", 0.25),
ArcRadius = GetParam(panel, "ArcRadius", 0.125),
ApproachAngle = GetParam(panel, "ApproachAngle", 135)
},
4 => new CleanHoleLeadIn
{
LineLength = GetParam(panel, "LineLength", 0.25),
ArcRadius = GetParam(panel, "ArcRadius", 0.125),
Kerf = GetParam(panel, "Kerf", 0.06)
},
5 => new LineLineLeadIn
{
Length1 = GetParam(panel, "Length1", 0.25),
ApproachAngle1 = GetParam(panel, "Angle1", 90),
Length2 = GetParam(panel, "Length2", 0.25),
ApproachAngle2 = GetParam(panel, "Angle2", 90)
},
_ => new NoLeadIn()
};
}
private static LeadOut BuildLeadOut(ComboBox combo, Panel panel)
{
return combo.SelectedIndex switch
{
1 => new LineLeadOut
{
Length = GetParam(panel, "Length", 0.25),
ApproachAngle = GetParam(panel, "ApproachAngle", 90)
},
2 => new ArcLeadOut
{
Radius = GetParam(panel, "Radius", 0.25)
},
3 => new MicrotabLeadOut
{
GapSize = GetParam(panel, "GapSize", 0.06)
},
_ => new NoLeadOut()
};
}
private static void SetParam(Panel panel, string tag, double value)
{
foreach (Control c in panel.Controls)
{
if (c is NumericUpDown nud && (string)nud.Tag == tag)
{
nud.Value = (decimal)value;
return;
}
}
}
private static double GetParam(Panel panel, string tag, double defaultValue)
{
foreach (Control c in panel.Controls)
{
if (c is NumericUpDown nud && (string)nud.Tag == tag)
return (double)nud.Value;
}
return defaultValue;
}
}
}