mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-01 01:11:36 -05:00
feat(machines): split machine leads into outside and inside cuts
Machine Configuration held one lead-in and lead-out per thickness and applied it to every contour. Each thickness now has Outside Cuts and Inside Cuts tabs; inside cuts follow the outside leads until "Same as outside cuts" is cleared, and configurations without inside leads keep applying one pair to both. The lead type lists, per-type fields and preview from Cutting Settings are now a reusable LeadsEditor control that both dialogs use. The machine tabs hide what the configuration cannot hold: the arc sweep, the clean-hole kerf (taken from the thickness) and Line + Line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7ec11993de
commit
bb21ffdf35
8 files changed
+957
-535
No files matched your search
@@ -3,12 +3,12 @@ using OpenNest.CNC.CuttingStrategy;
|
||||
namespace OpenNest.Data;
|
||||
|
||||
/// <summary>
|
||||
/// The lead-in, lead-out, kerf and cut-off a machine configuration has on file for a nest's
|
||||
/// material and thickness, converted to the nest's units. A machine has one lead-in and one
|
||||
/// lead-out per thickness, so they apply to external, internal and arc/circle contours alike;
|
||||
/// a Clean Hole lead-in is for holes only and leaves the external lead-in as it was, and a
|
||||
/// Line + Line lead-in, whose second segment the configuration does not hold, leaves every
|
||||
/// lead-in as it was.
|
||||
/// The lead-ins, lead-outs, kerf and cut-off a machine configuration has on file for a nest's
|
||||
/// material and thickness, converted to the nest's units. Outside cuts take the thickness's
|
||||
/// lead-in and lead-out; inside cuts (circles included) take its inside ones, or the outside
|
||||
/// ones when it has none. A Clean Hole lead-in is for holes only, so on outside cuts it leaves
|
||||
/// the external lead-in as it was, and a Line + Line lead-in, whose second segment the
|
||||
/// configuration does not hold, leaves the lead-in it would set as it was.
|
||||
/// </summary>
|
||||
public static class MachineCuttingDefaults
|
||||
{
|
||||
@@ -55,17 +55,8 @@ public static class MachineCuttingDefaults
|
||||
return false;
|
||||
|
||||
var scale = Scale(machine!.Units, nest.Units);
|
||||
var kerf = settings.Kerf * scale;
|
||||
// Each lead set gets its own instance, as the cutting panel builds them.
|
||||
if (ToLeadIn(settings.LeadIn, scale, kerf) is { } leadIn)
|
||||
{
|
||||
if (leadIn is not CleanHoleLeadIn)
|
||||
parameters.ExternalLeadIn = leadIn;
|
||||
parameters.InternalLeadIn = ToLeadIn(settings.LeadIn, scale, kerf);
|
||||
}
|
||||
parameters.ExternalLeadOut = ToLeadOut(settings.LeadOut, scale);
|
||||
parameters.InternalLeadOut = ToLeadOut(settings.LeadOut, scale);
|
||||
parameters.Kerf = kerf;
|
||||
ApplyLeads(parameters, settings, scale);
|
||||
parameters.Kerf = settings.Kerf * scale;
|
||||
parameters.MachineName = machine.Name;
|
||||
parameters.MaterialName = nest.Material.Name;
|
||||
parameters.Grade = nest.Material.Grade;
|
||||
@@ -73,6 +64,31 @@ public static class MachineCuttingDefaults
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The leads <paramref name="settings"/> give, in the machine's units, for previewing them:
|
||||
/// a lead the machine leaves as it was (Line + Line, or Clean Hole on outside cuts) is none.
|
||||
/// </summary>
|
||||
public static CuttingParameters LeadsOf(ThicknessConfig settings)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(settings);
|
||||
var parameters = new CuttingParameters { ExternalLeadIn = new NoLeadIn(), InternalLeadIn = new NoLeadIn() };
|
||||
ApplyLeads(parameters, settings, 1);
|
||||
parameters.Kerf = settings.Kerf;
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private static void ApplyLeads(CuttingParameters parameters, ThicknessConfig settings, double scale)
|
||||
{
|
||||
var kerf = settings.Kerf * scale;
|
||||
// Each lead set gets its own instance, as the cutting panel builds them.
|
||||
if (ToLeadIn(settings.LeadIn, scale, kerf) is { } leadIn and not CleanHoleLeadIn)
|
||||
parameters.ExternalLeadIn = leadIn;
|
||||
if (ToLeadIn(settings.InsideLeadIn ?? settings.LeadIn, scale, kerf) is { } insideLeadIn)
|
||||
parameters.InternalLeadIn = insideLeadIn;
|
||||
parameters.ExternalLeadOut = ToLeadOut(settings.LeadOut, scale);
|
||||
parameters.InternalLeadOut = ToLeadOut(settings.InsideLeadOut ?? settings.LeadOut, scale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether <paramref name="parameters"/> are stamped with the machine, material, grade and
|
||||
/// thickness <see cref="ApplyTo(CuttingParameters, MachineConfig?, Nest)"/> would record for
|
||||
@@ -112,6 +128,71 @@ public static class MachineCuttingDefaults
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The lead-in <paramref name="lead"/> describes, in the machine's units, with
|
||||
/// <paramref name="kerf"/> for a clean hole; null for a type it cannot build (Line + Line).
|
||||
/// </summary>
|
||||
public static LeadIn? LeadInOf(LeadConfig lead, double kerf) => ToLeadIn(lead, 1, kerf);
|
||||
|
||||
/// <summary>The lead-out <paramref name="lead"/> describes, in the machine's units.</summary>
|
||||
public static LeadOut LeadOutOf(LeadConfig lead) => ToLeadOut(lead, 1);
|
||||
|
||||
/// <summary>
|
||||
/// The machine configuration's form of <paramref name="leadIn"/>; what it cannot hold (an
|
||||
/// arc's sweep, a clean hole's kerf, a second line) is left out. Unset fields keep
|
||||
/// <paramref name="previous"/>'s values.
|
||||
/// </summary>
|
||||
public static LeadConfig ToConfig(LeadIn leadIn, LeadConfig? previous = null)
|
||||
{
|
||||
var config = Copy(previous);
|
||||
switch (leadIn)
|
||||
{
|
||||
case LineLeadIn line:
|
||||
(config.Type, config.Length, config.Angle) = ("Line", line.Length, line.ApproachAngle);
|
||||
break;
|
||||
case ArcLeadIn arc:
|
||||
(config.Type, config.Radius) = ("Arc", arc.Radius);
|
||||
break;
|
||||
case LineArcLeadIn lineArc:
|
||||
(config.Type, config.Length, config.Radius, config.Angle) =
|
||||
("Line + Arc", lineArc.LineLength, lineArc.ArcRadius, lineArc.ApproachAngle);
|
||||
break;
|
||||
case CleanHoleLeadIn cleanHole:
|
||||
(config.Type, config.Length, config.Radius) = ("Clean Hole", cleanHole.LineLength, cleanHole.ArcRadius);
|
||||
break;
|
||||
case LineLineLeadIn lineLine:
|
||||
(config.Type, config.Length, config.Angle) = ("Line + Line", lineLine.Length1, lineLine.ApproachAngle1);
|
||||
break;
|
||||
default:
|
||||
config.Type = "None";
|
||||
break;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/// <summary>The machine configuration's form of <paramref name="leadOut"/>.</summary>
|
||||
public static LeadConfig ToConfig(LeadOut leadOut, LeadConfig? previous = null)
|
||||
{
|
||||
var config = Copy(previous);
|
||||
switch (leadOut)
|
||||
{
|
||||
case LineLeadOut line:
|
||||
(config.Type, config.Length, config.Angle) = ("Line", line.Length, line.ApproachAngle);
|
||||
break;
|
||||
case ArcLeadOut arc:
|
||||
(config.Type, config.Radius) = ("Arc", arc.Radius);
|
||||
break;
|
||||
default:
|
||||
config.Type = "None";
|
||||
break;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
private static LeadConfig Copy(LeadConfig? lead) => lead == null
|
||||
? new LeadConfig()
|
||||
: new LeadConfig { Type = lead.Type, Length = lead.Length, Angle = lead.Angle, Radius = lead.Radius };
|
||||
|
||||
// Lead types are the names Tools > Machine Configuration offers; null when one cannot be built.
|
||||
private static LeadIn? ToLeadIn(LeadConfig? lead, double scale, double kerf) => lead?.Type switch
|
||||
{
|
||||
|
||||
@@ -11,8 +11,19 @@ public class ThicknessConfig
|
||||
|
||||
public double Kerf { get; set; }
|
||||
public string AssistGas { get; set; } = "";
|
||||
|
||||
/// <summary>The lead-in of outside cuts (part perimeters), and of inside cuts while <see cref="InsideLeadIn"/> is null.</summary>
|
||||
public LeadConfig LeadIn { get; set; } = new();
|
||||
|
||||
/// <summary>The lead-out of outside cuts, and of inside cuts while <see cref="InsideLeadOut"/> is null.</summary>
|
||||
public LeadConfig LeadOut { get; set; } = new();
|
||||
|
||||
/// <summary>The lead-in of inside cuts (cutouts and holes); null uses <see cref="LeadIn"/>.</summary>
|
||||
public LeadConfig? InsideLeadIn { get; set; }
|
||||
|
||||
/// <summary>The lead-out of inside cuts; null uses <see cref="LeadOut"/>.</summary>
|
||||
public LeadConfig? InsideLeadOut { get; set; }
|
||||
|
||||
public CutOffConfig CutOff { get; set; } = new();
|
||||
public List<string> PlateSizes { get; set; } = new();
|
||||
}
|
||||
@@ -146,6 +146,23 @@ public class MachineCuttingDefaultsTests
|
||||
Assert.Equal(0.012, Assert.IsType<CleanHoleLeadIn>(parameters.InternalLeadIn).Kerf);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsideLeads_GoToInternalContoursOnly()
|
||||
{
|
||||
var parameters = new CuttingParameters();
|
||||
var machine = Machine(LineArc);
|
||||
var thickness = machine.Materials[0].Thicknesses[0];
|
||||
thickness.InsideLeadIn = new LeadConfig { Type = "Clean Hole", Length = 0.1, Radius = 0.05 };
|
||||
thickness.InsideLeadOut = new LeadConfig { Type = "Arc", Radius = 0.04 };
|
||||
|
||||
Assert.True(MachineCuttingDefaults.ApplyTo(parameters, machine, NestOf(0.25)));
|
||||
|
||||
Assert.IsType<LineArcLeadIn>(parameters.ExternalLeadIn);
|
||||
Assert.IsType<LineLeadOut>(parameters.ExternalLeadOut);
|
||||
Assert.Equal(0.05, Assert.IsType<CleanHoleLeadIn>(parameters.InternalLeadIn).ArcRadius);
|
||||
Assert.Equal(0.04, Assert.IsType<ArcLeadOut>(parameters.InternalLeadOut).Radius);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LineLineLeadIn_KeepsTheLeadInsButStillSetsLeadOuts()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Data;
|
||||
using OpenNest.Forms;
|
||||
|
||||
namespace OpenNest.WinForms.Tests.Forms;
|
||||
|
||||
/// <summary>
|
||||
/// The Outside Cuts and Inside Cuts tabs of a thickness in Machine Configuration: inside cuts
|
||||
/// follow the outside leads until "Same as outside cuts" is cleared, which gives them a copy.
|
||||
/// </summary>
|
||||
public class MachineCutsFormsTests
|
||||
{
|
||||
[Fact]
|
||||
public void InsideCutsTab_GivesHolesTheirOwnLeads()
|
||||
{
|
||||
RunSta(() =>
|
||||
{
|
||||
var directory = Path.Combine(Path.GetTempPath(), "OpenNestTests", Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(directory);
|
||||
try
|
||||
{
|
||||
var provider = new LocalJsonProvider(directory);
|
||||
var thickness = new ThicknessConfig
|
||||
{
|
||||
Value = 0.25,
|
||||
LeadIn = new LeadConfig { Type = "Arc", Radius = 0.125 },
|
||||
LeadOut = new LeadConfig { Type = "None" },
|
||||
};
|
||||
provider.SaveMachine(new MachineConfig
|
||||
{
|
||||
Name = "Laser",
|
||||
Materials = { new MaterialConfig { Name = "Mild Steel", Thicknesses = { thickness } } },
|
||||
});
|
||||
|
||||
using var form = new MachineConfigForm(provider);
|
||||
var node = Descendants(Find<TreeView>(form, "_tree").Nodes).Single(n => n.Tag is ThicknessConfig);
|
||||
// A hidden tree raises no AfterSelect, so show the thickness as selecting it would.
|
||||
typeof(MachineConfigForm)
|
||||
.GetMethod("Tree_AfterSelect", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!
|
||||
.Invoke(form, new object[] { null, new TreeViewEventArgs(node) });
|
||||
var shown = (ThicknessConfig)node.Tag;
|
||||
|
||||
var pages = All<TabControl>(form).Single().TabPages.Cast<TabPage>().ToList();
|
||||
Assert.DoesNotContain(pages, p => p.Text is "Lead In" or "Lead Out");
|
||||
var outside = pages.Single(p => p.Text == "Outside Cuts");
|
||||
Assert.DoesNotContain(All<ComboBox>(outside), c => c.Items.Contains("Clean Hole"));
|
||||
|
||||
var inside = pages.Single(p => p.Text == "Inside Cuts");
|
||||
var same = All<CheckBox>(inside).Single(c => c.Text == "Same as outside cuts");
|
||||
Assert.True(same.Checked);
|
||||
Assert.All(All<ComboBox>(inside), c => Assert.False(c.Enabled));
|
||||
Assert.NotNull(All<OpenNest.Controls.LeadPreview>(inside).Single().Parameters);
|
||||
|
||||
same.Checked = false;
|
||||
Assert.Equal("Arc", shown.InsideLeadIn!.Type);
|
||||
Assert.Equal(0.125, shown.InsideLeadIn.Radius);
|
||||
Assert.NotSame(shown.LeadIn, shown.InsideLeadIn);
|
||||
Assert.All(All<ComboBox>(inside), c => Assert.True(c.Enabled));
|
||||
|
||||
All<ComboBox>(inside).Single(c => c.Items.Contains("Clean Hole")).SelectedItem = "Clean Hole";
|
||||
Assert.Equal("Clean Hole", shown.InsideLeadIn.Type);
|
||||
Assert.Equal("Arc", shown.LeadIn.Type);
|
||||
// Only the chosen type's fields show: line length and arc radius.
|
||||
var leadInGrid = All<NumericUpDown>(inside).ToList();
|
||||
Assert.Equal(2, leadInGrid.Count);
|
||||
leadInGrid[1].Value = 0.07m;
|
||||
Assert.Equal(0.07, shown.InsideLeadIn.Radius, 6);
|
||||
Assert.Equal(0.125, shown.LeadIn.Radius);
|
||||
var preview = All<OpenNest.Controls.LeadPreview>(inside).Single().Parameters;
|
||||
Assert.Equal(0.07, Assert.IsType<OpenNest.CNC.CuttingStrategy.CleanHoleLeadIn>(preview.InternalLeadIn).ArcRadius, 6);
|
||||
|
||||
same.Checked = true;
|
||||
Assert.Null(shown.InsideLeadIn);
|
||||
Assert.Null(shown.InsideLeadOut);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(directory, recursive: true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static IEnumerable<TreeNode> Descendants(TreeNodeCollection nodes) =>
|
||||
nodes.Cast<TreeNode>().SelectMany(n => Descendants(n.Nodes).Prepend(n));
|
||||
|
||||
private static T Find<T>(Control root, string name) where T : Control =>
|
||||
Assert.IsType<T>(root.Controls.Find(name, true).Single());
|
||||
|
||||
private static IEnumerable<T> All<T>(Control root) where T : Control
|
||||
{
|
||||
foreach (Control child in root.Controls)
|
||||
{
|
||||
if (child is T match)
|
||||
yield return match;
|
||||
foreach (var nested in All<T>(child))
|
||||
yield return nested;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunSta(System.Action action) =>
|
||||
StaTestThread.Run(action, Timeout.InfiniteTimeSpan, "The STA test did not complete.");
|
||||
}
|
||||
@@ -8,24 +8,10 @@ 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" };
|
||||
|
||||
// Every field label shares this column so inputs line up across sections.
|
||||
private const int LabelWidth = 150;
|
||||
private const int FieldIndent = 16;
|
||||
private const int InputWidth = 100;
|
||||
// The lead preview's least height, below the type with the most fields.
|
||||
private const int PreviewHeight = 110;
|
||||
|
||||
private static readonly Color HintColor = SystemColors.GrayText;
|
||||
|
||||
@@ -33,18 +19,8 @@ namespace OpenNest.Controls
|
||||
private readonly ToolTip toolTip = new ToolTip { AutoPopDelay = 15000 };
|
||||
|
||||
private readonly TabControl tabControl;
|
||||
private readonly ComboBox cboExternalLeadIn,
|
||||
cboExternalLeadOut;
|
||||
private readonly ComboBox cboInternalLeadIn,
|
||||
cboInternalLeadOut;
|
||||
|
||||
private readonly TableLayoutPanel pnlExternalLeadIn,
|
||||
pnlExternalLeadOut;
|
||||
private readonly TableLayoutPanel pnlInternalLeadIn,
|
||||
pnlInternalLeadOut;
|
||||
|
||||
private readonly LeadPreview externalPreview = new LeadPreview { Emphasis = ContourType.External };
|
||||
private readonly LeadPreview internalPreview = new LeadPreview { Emphasis = ContourType.Internal };
|
||||
private readonly LeadsEditor externalLeads = new LeadsEditor { Emphasis = ContourType.External };
|
||||
private readonly LeadsEditor internalLeads = new LeadsEditor { Emphasis = ContourType.Internal };
|
||||
|
||||
private readonly CheckBox chkTabsEnabled;
|
||||
private readonly TableLayoutPanel tabOptions;
|
||||
@@ -124,22 +100,13 @@ namespace OpenNest.Controls
|
||||
var tabInternal = new TabPage("Holes") { Padding = new Padding(6), UseVisualStyleBackColor = true };
|
||||
toolTip.SetToolTip(tabControl, "Outside: each part's perimeter. Holes: inner contours and circles.");
|
||||
|
||||
var externalContent = SetupTab(
|
||||
tabExternal,
|
||||
externalPreview,
|
||||
out cboExternalLeadIn,
|
||||
out pnlExternalLeadIn,
|
||||
out cboExternalLeadOut,
|
||||
out pnlExternalLeadOut
|
||||
);
|
||||
SetupTab(
|
||||
tabInternal,
|
||||
internalPreview,
|
||||
out cboInternalLeadIn,
|
||||
out pnlInternalLeadIn,
|
||||
out cboInternalLeadOut,
|
||||
out pnlInternalLeadOut
|
||||
);
|
||||
foreach (var (tab, leads) in new[] { (tabExternal, externalLeads), (tabInternal, internalLeads) })
|
||||
{
|
||||
leads.Dock = DockStyle.Fill;
|
||||
leads.LeadInChanged += (s, e) => OnParametersChanged();
|
||||
leads.LeadOutChanged += (s, e) => OnParametersChanged();
|
||||
tab.Controls.Add(leads);
|
||||
}
|
||||
|
||||
tabControl.Controls.Add(tabExternal);
|
||||
tabControl.Controls.Add(tabInternal);
|
||||
@@ -282,9 +249,9 @@ namespace OpenNest.Controls
|
||||
|
||||
Controls.Add(layout);
|
||||
|
||||
PopulateDropdowns();
|
||||
FitLeadTabs(externalContent);
|
||||
WireChangeEvents();
|
||||
// Sized for the type with the most fields, so picking one never moves the sections below;
|
||||
// the tab strip, page padding and borders take the rest.
|
||||
tabControl.Height = externalLeads.GetTallestHeight() + Font.Height + 28;
|
||||
UpdatePreviews();
|
||||
}
|
||||
|
||||
@@ -303,10 +270,10 @@ namespace OpenNest.Controls
|
||||
{
|
||||
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.ExternalLeadIn = externalLeads.LeadIn;
|
||||
parameters.ExternalLeadOut = externalLeads.LeadOut;
|
||||
parameters.InternalLeadIn = internalLeads.LeadIn;
|
||||
parameters.InternalLeadOut = internalLeads.LeadOut;
|
||||
parameters.TabsEnabled = chkTabsEnabled.Checked && rbTabAll.Checked;
|
||||
parameters.TabConfig = new NormalTab { Size = (double)nudTabWidth.Value };
|
||||
parameters.PierceClearance = (double)nudPierceClearance.Value;
|
||||
@@ -325,10 +292,10 @@ namespace OpenNest.Controls
|
||||
suppressEvents = true;
|
||||
loaded = CuttingParametersSerializer.Deserialize(CuttingParametersSerializer.Serialize(p));
|
||||
|
||||
LoadLeadIn(cboExternalLeadIn, pnlExternalLeadIn, p.ExternalLeadIn);
|
||||
LoadLeadOut(cboExternalLeadOut, pnlExternalLeadOut, p.ExternalLeadOut);
|
||||
LoadLeadIn(cboInternalLeadIn, pnlInternalLeadIn, p.InternalLeadIn);
|
||||
LoadLeadOut(cboInternalLeadOut, pnlInternalLeadOut, p.InternalLeadOut);
|
||||
externalLeads.LeadIn = p.ExternalLeadIn;
|
||||
externalLeads.LeadOut = p.ExternalLeadOut;
|
||||
internalLeads.LeadIn = p.InternalLeadIn;
|
||||
internalLeads.LeadOut = p.InternalLeadOut;
|
||||
|
||||
chkTabsEnabled.Checked = p.TabsEnabled || p.SupportTabs;
|
||||
rbSupportTab.Checked = p.SupportTabs;
|
||||
@@ -353,8 +320,8 @@ namespace OpenNest.Controls
|
||||
private void UpdatePreviews()
|
||||
{
|
||||
var parameters = BuildParameters();
|
||||
externalPreview.Parameters = parameters;
|
||||
internalPreview.Parameters = parameters;
|
||||
externalLeads.PreviewParameters = parameters;
|
||||
internalLeads.PreviewParameters = parameters;
|
||||
}
|
||||
|
||||
private void UpdateSupportTabOptions()
|
||||
@@ -375,225 +342,6 @@ namespace OpenNest.Controls
|
||||
ParametersChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sizes the lead tabs for the lead-in and lead-out with the most fields, so picking a
|
||||
/// type never clips its fields or moves the sections below.
|
||||
/// </summary>
|
||||
private void FitLeadTabs(Control content)
|
||||
{
|
||||
var tallestLeadIn = 0;
|
||||
for (var i = 0; i < LeadInTypes.Length; i++)
|
||||
{
|
||||
BuildLeadInParamControls(pnlExternalLeadIn, i);
|
||||
tallestLeadIn = System.Math.Max(tallestLeadIn, pnlExternalLeadIn.GetPreferredSize(Size.Empty).Height);
|
||||
}
|
||||
|
||||
var tallestLeadOut = 0;
|
||||
for (var i = 0; i < LeadOutTypes.Length; i++)
|
||||
{
|
||||
BuildLeadOutParamControls(pnlExternalLeadOut, i);
|
||||
tallestLeadOut = System.Math.Max(tallestLeadOut, pnlExternalLeadOut.GetPreferredSize(Size.Empty).Height);
|
||||
}
|
||||
|
||||
BuildLeadInParamControls(pnlExternalLeadIn, 0);
|
||||
BuildLeadOutParamControls(pnlExternalLeadOut, 0);
|
||||
|
||||
var contentHeight = content.GetPreferredSize(Size.Empty).Height + tallestLeadIn + tallestLeadOut
|
||||
+ PreviewHeight;
|
||||
// Tab strip, page padding and borders.
|
||||
tabControl.Height = contentHeight + Font.Height + 28;
|
||||
}
|
||||
|
||||
private TableLayoutPanel SetupTab(
|
||||
TabPage tab,
|
||||
LeadPreview preview,
|
||||
out ComboBox leadInCombo,
|
||||
out TableLayoutPanel leadInPanel,
|
||||
out ComboBox leadOutCombo,
|
||||
out TableLayoutPanel leadOutPanel
|
||||
)
|
||||
{
|
||||
var content = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Top,
|
||||
AutoSize = true,
|
||||
AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
ColumnCount = 1,
|
||||
};
|
||||
|
||||
leadInCombo = CreateCombo();
|
||||
var leadInType = CreateGrid(0);
|
||||
AddRow(leadInType, "Lead-in:", leadInCombo, 0, bold: true);
|
||||
content.Controls.Add(leadInType);
|
||||
|
||||
leadInPanel = CreateGrid(FieldIndent);
|
||||
content.Controls.Add(leadInPanel);
|
||||
|
||||
leadOutCombo = CreateCombo();
|
||||
var leadOutType = CreateGrid(0);
|
||||
leadOutType.Margin = new Padding(0, 8, 0, 0);
|
||||
AddRow(leadOutType, "Lead-out:", leadOutCombo, 0, bold: true);
|
||||
content.Controls.Add(leadOutType);
|
||||
|
||||
leadOutPanel = CreateGrid(FieldIndent);
|
||||
content.Controls.Add(leadOutPanel);
|
||||
|
||||
// The preview fills whatever the fields leave, so it grows for types with fewer.
|
||||
preview.Dock = DockStyle.Fill;
|
||||
preview.Margin = new Padding(0, 6, 0, 0);
|
||||
tab.Controls.Add(preview);
|
||||
tab.Controls.Add(content);
|
||||
preview.BringToFront();
|
||||
return content;
|
||||
}
|
||||
|
||||
private void PopulateDropdowns()
|
||||
{
|
||||
foreach (
|
||||
var combo in new[] { cboExternalLeadIn, cboInternalLeadIn }
|
||||
)
|
||||
{
|
||||
combo.Items.AddRange(LeadInTypes);
|
||||
combo.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
foreach (
|
||||
var combo in new[] { cboExternalLeadOut, cboInternalLeadOut }
|
||||
)
|
||||
{
|
||||
combo.Items.AddRange(LeadOutTypes);
|
||||
combo.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void WireChangeEvents()
|
||||
{
|
||||
cboExternalLeadIn.SelectedIndexChanged += OnLeadInTypeChanged;
|
||||
cboInternalLeadIn.SelectedIndexChanged += OnLeadInTypeChanged;
|
||||
|
||||
cboExternalLeadOut.SelectedIndexChanged += OnLeadOutTypeChanged;
|
||||
cboInternalLeadOut.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 TableLayoutPanel GetLeadInPanel(ComboBox combo)
|
||||
{
|
||||
if (combo == cboExternalLeadIn)
|
||||
return pnlExternalLeadIn;
|
||||
if (combo == cboInternalLeadIn)
|
||||
return pnlInternalLeadIn;
|
||||
return null;
|
||||
}
|
||||
|
||||
private TableLayoutPanel GetLeadOutPanel(ComboBox combo)
|
||||
{
|
||||
if (combo == cboExternalLeadOut)
|
||||
return pnlExternalLeadOut;
|
||||
if (combo == cboInternalLeadOut)
|
||||
return pnlInternalLeadOut;
|
||||
return null;
|
||||
}
|
||||
|
||||
private void BuildLeadInParamControls(TableLayoutPanel panel, int typeIndex)
|
||||
{
|
||||
ClearRows(panel);
|
||||
|
||||
switch (typeIndex)
|
||||
{
|
||||
case 1:
|
||||
AddNumericField(panel, "Length:", 0.25, "Length");
|
||||
AddAngleField(panel, "Approach angle:", 90, "ApproachAngle");
|
||||
break;
|
||||
case 2:
|
||||
AddNumericField(panel, "Radius:", 0.25, "Radius");
|
||||
// Degrees of arc: 90 is a quarter circle, 180 a half circle.
|
||||
AddAngleField(panel, "Sweep angle:", ArcLeadIn.DefaultSweepAngle, "SweepAngle",
|
||||
increment: 15, minimum: 1, maximum: 180);
|
||||
break;
|
||||
case 3:
|
||||
AddNumericField(panel, "Line length:", 0.25, "LineLength");
|
||||
AddNumericField(panel, "Arc radius:", 0.125, "ArcRadius");
|
||||
AddAngleField(panel, "Approach angle:", 135, "ApproachAngle");
|
||||
break;
|
||||
case 4:
|
||||
AddNumericField(panel, "Line length:", 0.25, "LineLength");
|
||||
AddNumericField(panel, "Arc radius:", 0.125, "ArcRadius");
|
||||
AddNumericField(panel, "Kerf:", 0.06, "Kerf");
|
||||
break;
|
||||
case 5:
|
||||
AddNumericField(panel, "First length:", 0.25, "Length1");
|
||||
AddAngleField(panel, "First angle:", 90, "Angle1");
|
||||
AddNumericField(panel, "Second length:", 0.25, "Length2");
|
||||
AddAngleField(panel, "Second angle:", 90, "Angle2");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildLeadOutParamControls(TableLayoutPanel panel, int typeIndex)
|
||||
{
|
||||
ClearRows(panel);
|
||||
|
||||
switch (typeIndex)
|
||||
{
|
||||
case 1:
|
||||
AddNumericField(panel, "Length:", 0.25, "Length");
|
||||
AddAngleField(panel, "Approach angle:", 90, "ApproachAngle");
|
||||
break;
|
||||
case 2:
|
||||
AddNumericField(panel, "Radius:", 0.25, "Radius");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddAngleField(
|
||||
TableLayoutPanel panel,
|
||||
string label,
|
||||
double defaultValue,
|
||||
string tag,
|
||||
double increment = 5,
|
||||
double minimum = 0,
|
||||
double maximum = 360
|
||||
) => AddNumericField(panel, label, defaultValue, tag, increment, minimum, maximum, "°", 2);
|
||||
|
||||
private void AddNumericField(
|
||||
TableLayoutPanel panel,
|
||||
string label,
|
||||
double defaultValue,
|
||||
string tag,
|
||||
double increment = 0.0625,
|
||||
double minimum = 0,
|
||||
double maximum = 9999,
|
||||
string suffix = null,
|
||||
int decimalPlaces = 4
|
||||
)
|
||||
{
|
||||
var nud = CreateNumeric(defaultValue, increment);
|
||||
nud.Minimum = (decimal)minimum;
|
||||
nud.Maximum = (decimal)maximum;
|
||||
nud.DecimalPlaces = decimalPlaces;
|
||||
nud.Value = System.Math.Clamp((decimal)defaultValue, nud.Minimum, nud.Maximum);
|
||||
nud.Tag = tag;
|
||||
nud.ValueChanged += (s, e) => OnParametersChanged();
|
||||
AddRow(panel, label, nud, FieldIndent, suffix);
|
||||
}
|
||||
|
||||
private void AddSection(string title)
|
||||
{
|
||||
var header = new Label
|
||||
@@ -633,8 +381,7 @@ namespace OpenNest.Controls
|
||||
string label,
|
||||
Control input,
|
||||
int indent,
|
||||
string hint = null,
|
||||
bool bold = false
|
||||
string hint = null
|
||||
)
|
||||
{
|
||||
var row = NextRow(grid);
|
||||
@@ -645,8 +392,6 @@ namespace OpenNest.Controls
|
||||
Anchor = AnchorStyles.Left,
|
||||
Margin = new Padding(0, 0, 4, 0),
|
||||
};
|
||||
if (bold)
|
||||
text.Font = new Font(text.Font, FontStyle.Bold);
|
||||
grid.Controls.Add(text, 0, row);
|
||||
|
||||
input.Anchor = AnchorStyles.Left;
|
||||
@@ -672,16 +417,6 @@ namespace OpenNest.Controls
|
||||
return row;
|
||||
}
|
||||
|
||||
private static void ClearRows(TableLayoutPanel grid)
|
||||
{
|
||||
grid.SuspendLayout();
|
||||
while (grid.Controls.Count > 0)
|
||||
grid.Controls[0].Dispose();
|
||||
grid.RowStyles.Clear();
|
||||
grid.RowCount = 0;
|
||||
grid.ResumeLayout();
|
||||
}
|
||||
|
||||
private static FlowLayoutPanel CreateInline(params Control[] controls)
|
||||
{
|
||||
var flow = new FlowLayoutPanel
|
||||
@@ -709,13 +444,6 @@ namespace OpenNest.Controls
|
||||
Margin = margin ?? new Padding(2, 0, 0, 0),
|
||||
};
|
||||
|
||||
private static ComboBox CreateCombo() =>
|
||||
new ComboBox
|
||||
{
|
||||
DropDownStyle = ComboBoxStyle.DropDownList,
|
||||
Width = 150,
|
||||
};
|
||||
|
||||
private static NumericUpDown CreateNumeric(double defaultValue, double increment)
|
||||
{
|
||||
return new NumericUpDown
|
||||
@@ -729,138 +457,5 @@ namespace OpenNest.Controls
|
||||
TextAlign = HorizontalAlignment.Right,
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
SetParam(panel, "SweepAngle", arc.SweepAngle);
|
||||
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;
|
||||
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),
|
||||
SweepAngle = GetParam(panel, "SweepAngle", ArcLeadIn.DefaultSweepAngle),
|
||||
},
|
||||
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) },
|
||||
_ => 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 = double.IsFinite(value)
|
||||
? System.Math.Clamp((decimal)value, nud.Minimum, nud.Maximum)
|
||||
: nud.Minimum;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,579 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.CNC.CuttingStrategy;
|
||||
|
||||
namespace OpenNest.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Edits one contour type's lead-in and lead-out: a type list for each, the chosen type's
|
||||
/// fields below it, and a <see cref="LeadPreview"/> filling the rest. Cutting Settings and
|
||||
/// Machine Configuration both use it; the options hide what a host cannot hold.
|
||||
/// </summary>
|
||||
public class LeadsEditor : UserControl
|
||||
{
|
||||
private const string None = "None";
|
||||
private const string Line = "Line";
|
||||
private const string Arc = "Arc";
|
||||
private const string LineArc = "Line + Arc";
|
||||
private const string CleanHole = "Clean Hole";
|
||||
private const string LineLine = "Line + Line";
|
||||
|
||||
private static readonly string[] AllLeadInTypes = { None, Line, Arc, LineArc, CleanHole, LineLine };
|
||||
private static readonly string[] LeadOutTypes = { None, Line, Arc };
|
||||
|
||||
// Every field label shares this column so inputs line up across hosts.
|
||||
private const int LabelWidth = 150;
|
||||
private const int FieldIndent = 16;
|
||||
private const int InputWidth = 100;
|
||||
// The preview's least height, below the type with the most fields.
|
||||
private const int PreviewHeight = 110;
|
||||
|
||||
private static readonly Color HintColor = SystemColors.GrayText;
|
||||
|
||||
private readonly TableLayoutPanel content;
|
||||
private readonly ComboBox leadInCombo;
|
||||
private readonly ComboBox leadOutCombo;
|
||||
private readonly TableLayoutPanel leadInFields;
|
||||
private readonly TableLayoutPanel leadOutFields;
|
||||
private readonly LeadPreview preview = new();
|
||||
|
||||
private bool allowCleanHole = true;
|
||||
private bool allowLineLine = true;
|
||||
private bool showArcSweep = true;
|
||||
private bool showCleanHoleKerf = true;
|
||||
private string lengthUnit;
|
||||
private bool loading;
|
||||
|
||||
/// <summary>Raised when the user changes the lead-in's type or fields.</summary>
|
||||
public event EventHandler LeadInChanged;
|
||||
|
||||
/// <summary>Raised when the user changes the lead-out's type or fields.</summary>
|
||||
public event EventHandler LeadOutChanged;
|
||||
|
||||
public LeadsEditor()
|
||||
{
|
||||
BackColor = Color.White;
|
||||
|
||||
content = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Top,
|
||||
AutoSize = true,
|
||||
AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
ColumnCount = 1,
|
||||
};
|
||||
|
||||
leadInCombo = CreateCombo();
|
||||
var leadInType = CreateGrid(0);
|
||||
AddRow(leadInType, "Lead-in:", leadInCombo, 0, bold: true);
|
||||
content.Controls.Add(leadInType);
|
||||
leadInFields = CreateGrid(FieldIndent);
|
||||
content.Controls.Add(leadInFields);
|
||||
|
||||
leadOutCombo = CreateCombo();
|
||||
var leadOutType = CreateGrid(0);
|
||||
leadOutType.Margin = new Padding(0, 8, 0, 0);
|
||||
AddRow(leadOutType, "Lead-out:", leadOutCombo, 0, bold: true);
|
||||
content.Controls.Add(leadOutType);
|
||||
leadOutFields = CreateGrid(FieldIndent);
|
||||
content.Controls.Add(leadOutFields);
|
||||
|
||||
// The preview fills whatever the fields leave, so it grows for types with fewer.
|
||||
preview.Dock = DockStyle.Fill;
|
||||
preview.Margin = new Padding(0, 6, 0, 0);
|
||||
Controls.Add(preview);
|
||||
Controls.Add(content);
|
||||
preview.BringToFront();
|
||||
|
||||
FillCombo(leadInCombo, LeadInTypes(), None);
|
||||
FillCombo(leadOutCombo, LeadOutTypes, None);
|
||||
leadInCombo.SelectedIndexChanged += (s, e) =>
|
||||
{
|
||||
BuildLeadInFields();
|
||||
if (!loading)
|
||||
LeadInChanged?.Invoke(this, EventArgs.Empty);
|
||||
};
|
||||
leadOutCombo.SelectedIndexChanged += (s, e) =>
|
||||
{
|
||||
BuildLeadOutFields();
|
||||
if (!loading)
|
||||
LeadOutChanged?.Invoke(this, EventArgs.Empty);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>The contour the preview draws strong; the other one's leads are faint.</summary>
|
||||
[DefaultValue(ContourType.External)]
|
||||
public ContourType Emphasis
|
||||
{
|
||||
get => preview.Emphasis;
|
||||
set
|
||||
{
|
||||
preview.Emphasis = value;
|
||||
preview.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Whether Clean Hole is offered; a lead already of that type still shows.</summary>
|
||||
[DefaultValue(true)]
|
||||
public bool AllowCleanHole
|
||||
{
|
||||
get => allowCleanHole;
|
||||
set
|
||||
{
|
||||
allowCleanHole = value;
|
||||
ReloadLeadInTypes();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Whether Line + Line is offered; a lead already of that type still shows.</summary>
|
||||
[DefaultValue(true)]
|
||||
public bool AllowLineLine
|
||||
{
|
||||
get => allowLineLine;
|
||||
set
|
||||
{
|
||||
allowLineLine = value;
|
||||
ReloadLeadInTypes();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Whether an arc lead-in shows its sweep angle; hidden, it is the default half circle.</summary>
|
||||
[DefaultValue(true)]
|
||||
public bool ShowArcSweep
|
||||
{
|
||||
get => showArcSweep;
|
||||
set
|
||||
{
|
||||
showArcSweep = value;
|
||||
RebuildFields();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Whether a clean-hole lead-in shows its kerf; hidden, the host supplies it.</summary>
|
||||
[DefaultValue(true)]
|
||||
public bool ShowCleanHoleKerf
|
||||
{
|
||||
get => showCleanHoleKerf;
|
||||
set
|
||||
{
|
||||
showCleanHoleKerf = value;
|
||||
RebuildFields();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>A unit shown after each length field, or null for none.</summary>
|
||||
[DefaultValue(null)]
|
||||
public string LengthUnit
|
||||
{
|
||||
get => lengthUnit;
|
||||
set
|
||||
{
|
||||
lengthUnit = value;
|
||||
RebuildFields();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The whole plate's leads, which the preview draws.</summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public CuttingParameters PreviewParameters
|
||||
{
|
||||
get => preview.Parameters;
|
||||
set => preview.Parameters = value;
|
||||
}
|
||||
|
||||
/// <summary>Whether the type lists and fields take edits; the preview stays.</summary>
|
||||
[DefaultValue(true)]
|
||||
public bool FieldsEnabled
|
||||
{
|
||||
get => content.Enabled;
|
||||
set => content.Enabled = value;
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public LeadIn LeadIn
|
||||
{
|
||||
get => (string)leadInCombo.SelectedItem switch
|
||||
{
|
||||
Line => new LineLeadIn
|
||||
{
|
||||
Length = GetParam(leadInFields, "Length", 0.25),
|
||||
ApproachAngle = GetParam(leadInFields, "ApproachAngle", 90),
|
||||
},
|
||||
Arc => new ArcLeadIn
|
||||
{
|
||||
Radius = GetParam(leadInFields, "Radius", 0.25),
|
||||
SweepAngle = GetParam(leadInFields, "SweepAngle", ArcLeadIn.DefaultSweepAngle),
|
||||
},
|
||||
LineArc => new LineArcLeadIn
|
||||
{
|
||||
LineLength = GetParam(leadInFields, "LineLength", 0.25),
|
||||
ArcRadius = GetParam(leadInFields, "ArcRadius", 0.125),
|
||||
ApproachAngle = GetParam(leadInFields, "ApproachAngle", 135),
|
||||
},
|
||||
CleanHole => new CleanHoleLeadIn
|
||||
{
|
||||
LineLength = GetParam(leadInFields, "LineLength", 0.25),
|
||||
ArcRadius = GetParam(leadInFields, "ArcRadius", 0.125),
|
||||
Kerf = GetParam(leadInFields, "Kerf", 0.06),
|
||||
},
|
||||
LineLine => new LineLineLeadIn
|
||||
{
|
||||
Length1 = GetParam(leadInFields, "Length1", 0.25),
|
||||
ApproachAngle1 = GetParam(leadInFields, "Angle1", 90),
|
||||
Length2 = GetParam(leadInFields, "Length2", 0.25),
|
||||
ApproachAngle2 = GetParam(leadInFields, "Angle2", 90),
|
||||
},
|
||||
_ => new NoLeadIn(),
|
||||
};
|
||||
set
|
||||
{
|
||||
loading = true;
|
||||
FillCombo(leadInCombo, LeadInTypes(), TypeOf(value));
|
||||
switch (value)
|
||||
{
|
||||
case LineLeadIn line:
|
||||
SetParam(leadInFields, "Length", line.Length);
|
||||
SetParam(leadInFields, "ApproachAngle", line.ApproachAngle);
|
||||
break;
|
||||
case ArcLeadIn arc:
|
||||
SetParam(leadInFields, "Radius", arc.Radius);
|
||||
SetParam(leadInFields, "SweepAngle", arc.SweepAngle);
|
||||
break;
|
||||
case LineArcLeadIn lineArc:
|
||||
SetParam(leadInFields, "LineLength", lineArc.LineLength);
|
||||
SetParam(leadInFields, "ArcRadius", lineArc.ArcRadius);
|
||||
SetParam(leadInFields, "ApproachAngle", lineArc.ApproachAngle);
|
||||
break;
|
||||
case CleanHoleLeadIn cleanHole:
|
||||
SetParam(leadInFields, "LineLength", cleanHole.LineLength);
|
||||
SetParam(leadInFields, "ArcRadius", cleanHole.ArcRadius);
|
||||
SetParam(leadInFields, "Kerf", cleanHole.Kerf);
|
||||
break;
|
||||
case LineLineLeadIn lineLine:
|
||||
SetParam(leadInFields, "Length1", lineLine.Length1);
|
||||
SetParam(leadInFields, "Angle1", lineLine.ApproachAngle1);
|
||||
SetParam(leadInFields, "Length2", lineLine.Length2);
|
||||
SetParam(leadInFields, "Angle2", lineLine.ApproachAngle2);
|
||||
break;
|
||||
}
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public LeadOut LeadOut
|
||||
{
|
||||
get => (string)leadOutCombo.SelectedItem switch
|
||||
{
|
||||
Line => new LineLeadOut
|
||||
{
|
||||
Length = GetParam(leadOutFields, "Length", 0.25),
|
||||
ApproachAngle = GetParam(leadOutFields, "ApproachAngle", 90),
|
||||
},
|
||||
Arc => new ArcLeadOut { Radius = GetParam(leadOutFields, "Radius", 0.25) },
|
||||
_ => new NoLeadOut(),
|
||||
};
|
||||
set
|
||||
{
|
||||
loading = true;
|
||||
FillCombo(leadOutCombo, LeadOutTypes, value switch
|
||||
{
|
||||
LineLeadOut => Line,
|
||||
ArcLeadOut => Arc,
|
||||
_ => None,
|
||||
});
|
||||
switch (value)
|
||||
{
|
||||
case LineLeadOut line:
|
||||
SetParam(leadOutFields, "Length", line.Length);
|
||||
SetParam(leadOutFields, "ApproachAngle", line.ApproachAngle);
|
||||
break;
|
||||
case ArcLeadOut arc:
|
||||
SetParam(leadOutFields, "Radius", arc.Radius);
|
||||
break;
|
||||
}
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The height that fits the lead-in and lead-out with the most fields above the preview's
|
||||
/// least height, so a host can size for it and picking a type never clips its fields.
|
||||
/// </summary>
|
||||
public int GetTallestHeight()
|
||||
{
|
||||
var (savedIn, savedOut) = (LeadIn, LeadOut);
|
||||
loading = true;
|
||||
|
||||
var tallestLeadIn = 0;
|
||||
foreach (var type in leadInCombo.Items.Cast<string>().ToList())
|
||||
{
|
||||
leadInCombo.SelectedItem = type;
|
||||
tallestLeadIn = System.Math.Max(tallestLeadIn, leadInFields.GetPreferredSize(Size.Empty).Height);
|
||||
}
|
||||
var tallestLeadOut = 0;
|
||||
foreach (var type in leadOutCombo.Items.Cast<string>().ToList())
|
||||
{
|
||||
leadOutCombo.SelectedItem = type;
|
||||
tallestLeadOut = System.Math.Max(tallestLeadOut, leadOutFields.GetPreferredSize(Size.Empty).Height);
|
||||
}
|
||||
|
||||
leadInCombo.SelectedItem = None;
|
||||
leadOutCombo.SelectedItem = None;
|
||||
var rows = content.GetPreferredSize(Size.Empty).Height;
|
||||
loading = false;
|
||||
LeadIn = savedIn;
|
||||
LeadOut = savedOut;
|
||||
return rows + tallestLeadIn + tallestLeadOut + PreviewHeight + Padding.Vertical;
|
||||
}
|
||||
|
||||
private IEnumerable<string> LeadInTypes() =>
|
||||
AllLeadInTypes.Where(t => (allowCleanHole || t != CleanHole) && (allowLineLine || t != LineLine));
|
||||
|
||||
// Keeps the values while the fields are laid out again.
|
||||
private void RebuildFields()
|
||||
{
|
||||
var (leadIn, leadOut) = (LeadIn, LeadOut);
|
||||
loading = true;
|
||||
BuildLeadInFields();
|
||||
BuildLeadOutFields();
|
||||
loading = false;
|
||||
LeadIn = leadIn;
|
||||
LeadOut = leadOut;
|
||||
}
|
||||
|
||||
private void ReloadLeadInTypes()
|
||||
{
|
||||
var lead = LeadIn;
|
||||
LeadIn = lead;
|
||||
}
|
||||
|
||||
private static string TypeOf(LeadIn leadIn) => leadIn switch
|
||||
{
|
||||
LineLeadIn => Line,
|
||||
ArcLeadIn => Arc,
|
||||
LineArcLeadIn => LineArc,
|
||||
CleanHoleLeadIn => CleanHole,
|
||||
LineLineLeadIn => LineLine,
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// A type the list does not offer still shows when it is the lead's.
|
||||
private static void FillCombo(ComboBox combo, IEnumerable<string> types, string selected)
|
||||
{
|
||||
var items = types.ToList();
|
||||
if (!items.Contains(selected))
|
||||
items.Add(selected);
|
||||
if (combo.Items.Cast<string>().SequenceEqual(items))
|
||||
{
|
||||
combo.SelectedItem = selected;
|
||||
return;
|
||||
}
|
||||
combo.Items.Clear();
|
||||
combo.Items.AddRange(items.ToArray());
|
||||
combo.SelectedItem = selected;
|
||||
}
|
||||
|
||||
private void BuildLeadInFields()
|
||||
{
|
||||
ClearRows(leadInFields);
|
||||
switch ((string)leadInCombo.SelectedItem)
|
||||
{
|
||||
case Line:
|
||||
AddLength(leadInFields, "Length:", 0.25, "Length");
|
||||
AddAngle(leadInFields, "Approach angle:", 90, "ApproachAngle");
|
||||
break;
|
||||
case Arc:
|
||||
AddLength(leadInFields, "Radius:", 0.25, "Radius");
|
||||
// Degrees of arc: 90 is a quarter circle, 180 a half circle.
|
||||
if (showArcSweep)
|
||||
AddAngle(leadInFields, "Sweep angle:", ArcLeadIn.DefaultSweepAngle, "SweepAngle",
|
||||
increment: 15, minimum: 1, maximum: 180);
|
||||
break;
|
||||
case LineArc:
|
||||
AddLength(leadInFields, "Line length:", 0.25, "LineLength");
|
||||
AddLength(leadInFields, "Arc radius:", 0.125, "ArcRadius");
|
||||
AddAngle(leadInFields, "Approach angle:", 135, "ApproachAngle");
|
||||
break;
|
||||
case CleanHole:
|
||||
AddLength(leadInFields, "Line length:", 0.25, "LineLength");
|
||||
AddLength(leadInFields, "Arc radius:", 0.125, "ArcRadius");
|
||||
if (showCleanHoleKerf)
|
||||
AddLength(leadInFields, "Kerf:", 0.06, "Kerf");
|
||||
break;
|
||||
case LineLine:
|
||||
AddLength(leadInFields, "First length:", 0.25, "Length1");
|
||||
AddAngle(leadInFields, "First angle:", 90, "Angle1");
|
||||
AddLength(leadInFields, "Second length:", 0.25, "Length2");
|
||||
AddAngle(leadInFields, "Second angle:", 90, "Angle2");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildLeadOutFields()
|
||||
{
|
||||
ClearRows(leadOutFields);
|
||||
switch ((string)leadOutCombo.SelectedItem)
|
||||
{
|
||||
case Line:
|
||||
AddLength(leadOutFields, "Length:", 0.25, "Length");
|
||||
AddAngle(leadOutFields, "Approach angle:", 90, "ApproachAngle");
|
||||
break;
|
||||
case Arc:
|
||||
AddLength(leadOutFields, "Radius:", 0.25, "Radius");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddLength(TableLayoutPanel grid, string label, double defaultValue, string tag) =>
|
||||
AddNumeric(grid, label, defaultValue, tag, 0.0625, 0, 9999, lengthUnit, 4);
|
||||
|
||||
private void AddAngle(
|
||||
TableLayoutPanel grid,
|
||||
string label,
|
||||
double defaultValue,
|
||||
string tag,
|
||||
double increment = 5,
|
||||
double minimum = 0,
|
||||
double maximum = 360
|
||||
) => AddNumeric(grid, label, defaultValue, tag, increment, minimum, maximum, "°", 2);
|
||||
|
||||
private void AddNumeric(
|
||||
TableLayoutPanel grid,
|
||||
string label,
|
||||
double defaultValue,
|
||||
string tag,
|
||||
double increment,
|
||||
double minimum,
|
||||
double maximum,
|
||||
string suffix,
|
||||
int decimalPlaces
|
||||
)
|
||||
{
|
||||
var nud = new NumericUpDown
|
||||
{
|
||||
Width = InputWidth,
|
||||
DecimalPlaces = decimalPlaces,
|
||||
Increment = (decimal)increment,
|
||||
Minimum = (decimal)minimum,
|
||||
Maximum = (decimal)maximum,
|
||||
TextAlign = HorizontalAlignment.Right,
|
||||
Tag = tag,
|
||||
};
|
||||
nud.Value = System.Math.Clamp((decimal)defaultValue, nud.Minimum, nud.Maximum);
|
||||
var leadInField = grid == leadInFields;
|
||||
nud.ValueChanged += (s, e) =>
|
||||
{
|
||||
if (loading)
|
||||
return;
|
||||
(leadInField ? LeadInChanged : LeadOutChanged)?.Invoke(this, EventArgs.Empty);
|
||||
};
|
||||
AddRow(grid, label, nud, FieldIndent, suffix);
|
||||
}
|
||||
|
||||
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 = double.IsFinite(value)
|
||||
? System.Math.Clamp((decimal)value, nud.Minimum, nud.Maximum)
|
||||
: nud.Minimum;
|
||||
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;
|
||||
}
|
||||
|
||||
private static TableLayoutPanel CreateGrid(int indent)
|
||||
{
|
||||
var grid = new TableLayoutPanel
|
||||
{
|
||||
AutoSize = true,
|
||||
AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
ColumnCount = 3,
|
||||
Margin = new Padding(indent, 0, 0, 0),
|
||||
};
|
||||
grid.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, LabelWidth - indent));
|
||||
grid.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
grid.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
return grid;
|
||||
}
|
||||
|
||||
private static void AddRow(
|
||||
TableLayoutPanel grid,
|
||||
string label,
|
||||
Control input,
|
||||
int indent,
|
||||
string hint = null,
|
||||
bool bold = false
|
||||
)
|
||||
{
|
||||
var row = grid.RowCount;
|
||||
grid.RowCount = row + 1;
|
||||
grid.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||
|
||||
var text = new Label
|
||||
{
|
||||
Text = label,
|
||||
AutoSize = true,
|
||||
Anchor = AnchorStyles.Left,
|
||||
Margin = new Padding(0, 0, 4, 0),
|
||||
};
|
||||
if (bold)
|
||||
text.Font = new Font(text.Font, FontStyle.Bold);
|
||||
grid.Controls.Add(text, 0, row);
|
||||
|
||||
input.Anchor = AnchorStyles.Left;
|
||||
input.Margin = new Padding(0, 2, 0, 2);
|
||||
grid.Controls.Add(input, 1, row);
|
||||
|
||||
if (hint != null)
|
||||
grid.Controls.Add(
|
||||
new Label
|
||||
{
|
||||
Text = hint,
|
||||
AutoSize = true,
|
||||
ForeColor = HintColor,
|
||||
Anchor = AnchorStyles.Left,
|
||||
Margin = new Padding(4, 0, 0, 0),
|
||||
},
|
||||
2,
|
||||
row
|
||||
);
|
||||
}
|
||||
|
||||
private static void ClearRows(TableLayoutPanel grid)
|
||||
{
|
||||
grid.SuspendLayout();
|
||||
while (grid.Controls.Count > 0)
|
||||
grid.Controls[0].Dispose();
|
||||
grid.RowStyles.Clear();
|
||||
grid.RowCount = 0;
|
||||
grid.ResumeLayout();
|
||||
}
|
||||
|
||||
private static ComboBox CreateCombo() =>
|
||||
new ComboBox
|
||||
{
|
||||
DropDownStyle = ComboBoxStyle.DropDownList,
|
||||
Width = 150,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -14,18 +14,6 @@ namespace OpenNest.Forms
|
||||
{
|
||||
public partial class MachineConfigForm : Form
|
||||
{
|
||||
private static readonly string[] LeadInTypes =
|
||||
{
|
||||
"None",
|
||||
"Line",
|
||||
"Arc",
|
||||
"Line + Arc",
|
||||
"Clean Hole",
|
||||
"Line + Line",
|
||||
};
|
||||
|
||||
private static readonly string[] LeadOutTypes = { "None", "Line", "Arc" };
|
||||
|
||||
private static readonly string[] AssistGasOptions = { "O2", "N2", "Air" };
|
||||
|
||||
private static readonly string[] CutOffDirectionOptions = Enum.GetNames<CutDirection>();
|
||||
@@ -630,12 +618,8 @@ namespace OpenNest.Forms
|
||||
{
|
||||
var tabs = new TabControl { Dock = DockStyle.Fill };
|
||||
tabs.TabPages.Add(CreateTab("General", BuildThicknessGeneralTab(thickness, node)));
|
||||
tabs.TabPages.Add(
|
||||
CreateTab("Lead In", BuildLeadTab(thickness.LeadIn, LeadInTypes))
|
||||
);
|
||||
tabs.TabPages.Add(
|
||||
CreateTab("Lead Out", BuildLeadTab(thickness.LeadOut, LeadOutTypes))
|
||||
);
|
||||
tabs.TabPages.Add(CreateTab("Outside Cuts", BuildOutsideCutsTab(thickness)));
|
||||
tabs.TabPages.Add(CreateTab("Inside Cuts", BuildInsideCutsTab(thickness)));
|
||||
tabs.TabPages.Add(CreateTab("Cut Off", BuildCutOffTab(thickness.CutOff)));
|
||||
tabs.TabPages.Add(CreateTab("Plate Sizes", BuildPlateSizesTab(thickness)));
|
||||
RememberTab(tabs, _thicknessTab, t => _thicknessTab = t);
|
||||
@@ -717,82 +701,130 @@ namespace OpenNest.Forms
|
||||
return panel;
|
||||
}
|
||||
|
||||
private Control BuildLeadTab(LeadConfig lead, string[] typeOptions)
|
||||
private Control BuildOutsideCutsTab(ThicknessConfig thickness)
|
||||
{
|
||||
var layout = CreateDetailLayout();
|
||||
var row = 0;
|
||||
|
||||
var typeCombo = new ComboBox
|
||||
var editor = CreateLeadsEditor(CNC.CuttingStrategy.ContourType.External);
|
||||
// Clean Hole is for holes only, so outside cuts do not offer it.
|
||||
editor.AllowCleanHole = false;
|
||||
ShowLeads(editor, thickness, thickness.LeadIn, thickness.LeadOut);
|
||||
editor.LeadInChanged += (s, e) =>
|
||||
{
|
||||
DropDownStyle = ComboBoxStyle.DropDownList,
|
||||
Width = InputWidth,
|
||||
thickness.LeadIn = MachineCuttingDefaults.ToConfig(editor.LeadIn, thickness.LeadIn);
|
||||
LeadsEdited(editor, thickness);
|
||||
};
|
||||
typeCombo.Items.AddRange(typeOptions);
|
||||
typeCombo.SelectedItem = typeOptions.Contains(lead.Type) ? lead.Type : typeOptions[0];
|
||||
typeCombo.SelectedIndexChanged += (s, e) =>
|
||||
editor.LeadOutChanged += (s, e) =>
|
||||
{
|
||||
lead.Type = typeCombo.SelectedItem?.ToString() ?? typeOptions[0];
|
||||
MarkDirty();
|
||||
thickness.LeadOut = MachineCuttingDefaults.ToConfig(editor.LeadOut, thickness.LeadOut);
|
||||
LeadsEdited(editor, thickness);
|
||||
};
|
||||
editor.VisibleChanged += (s, e) =>
|
||||
{
|
||||
if (editor.Visible)
|
||||
ShowLeads(editor, thickness, thickness.LeadIn, thickness.LeadOut);
|
||||
};
|
||||
AddField(layout, ref row, "Type:", typeCombo);
|
||||
|
||||
AddField(
|
||||
layout,
|
||||
ref row,
|
||||
"Length:",
|
||||
CreateLabeledNumeric(
|
||||
lead.Length,
|
||||
v =>
|
||||
{
|
||||
lead.Length = v;
|
||||
MarkDirty();
|
||||
},
|
||||
4,
|
||||
LengthUnitSuffix,
|
||||
50,
|
||||
0.001m
|
||||
)
|
||||
);
|
||||
AddField(
|
||||
layout,
|
||||
ref row,
|
||||
"Angle:",
|
||||
CreateLabeledNumeric(
|
||||
lead.Angle,
|
||||
v =>
|
||||
{
|
||||
lead.Angle = v;
|
||||
MarkDirty();
|
||||
},
|
||||
1,
|
||||
_ => "°",
|
||||
360,
|
||||
0.1m
|
||||
)
|
||||
);
|
||||
AddField(
|
||||
layout,
|
||||
ref row,
|
||||
"Radius:",
|
||||
CreateLabeledNumeric(
|
||||
lead.Radius,
|
||||
v =>
|
||||
{
|
||||
lead.Radius = v;
|
||||
MarkDirty();
|
||||
},
|
||||
4,
|
||||
LengthUnitSuffix,
|
||||
50,
|
||||
0.001m
|
||||
)
|
||||
);
|
||||
|
||||
var panel = new Panel { Dock = DockStyle.Fill, Padding = new Padding(10) };
|
||||
panel.Controls.Add(layout);
|
||||
return panel;
|
||||
var page = new Panel { Dock = DockStyle.Fill, BackColor = Color.White };
|
||||
page.Controls.Add(editor);
|
||||
page.Controls.Add(CreateTabNote("Part perimeters. Inside cuts use these too unless given their own."));
|
||||
return page;
|
||||
}
|
||||
|
||||
// Inside cuts (cutouts and holes) follow the outside cuts until they get leads of their
|
||||
// own, which start as a copy of the outside ones.
|
||||
private Control BuildInsideCutsTab(ThicknessConfig thickness)
|
||||
{
|
||||
var editor = CreateLeadsEditor(CNC.CuttingStrategy.ContourType.Internal);
|
||||
var sameBox = new CheckBox
|
||||
{
|
||||
Text = "Same as outside cuts",
|
||||
AutoSize = true,
|
||||
Dock = DockStyle.Top,
|
||||
Padding = new Padding(10, 0, 0, 0),
|
||||
Checked = thickness.InsideLeadIn == null && thickness.InsideLeadOut == null,
|
||||
};
|
||||
|
||||
void Show()
|
||||
{
|
||||
ShowLeads(editor, thickness, thickness.InsideLeadIn ?? thickness.LeadIn,
|
||||
thickness.InsideLeadOut ?? thickness.LeadOut);
|
||||
editor.FieldsEnabled = !sameBox.Checked;
|
||||
}
|
||||
|
||||
sameBox.CheckedChanged += (s, e) =>
|
||||
{
|
||||
thickness.InsideLeadIn = sameBox.Checked ? null : Copy(thickness.LeadIn);
|
||||
thickness.InsideLeadOut = sameBox.Checked ? null : Copy(thickness.LeadOut);
|
||||
MarkDirty();
|
||||
Show();
|
||||
};
|
||||
editor.LeadInChanged += (s, e) =>
|
||||
{
|
||||
thickness.InsideLeadIn = MachineCuttingDefaults.ToConfig(editor.LeadIn, thickness.InsideLeadIn);
|
||||
LeadsEdited(editor, thickness);
|
||||
};
|
||||
editor.LeadOutChanged += (s, e) =>
|
||||
{
|
||||
thickness.InsideLeadOut = MachineCuttingDefaults.ToConfig(editor.LeadOut, thickness.InsideLeadOut);
|
||||
LeadsEdited(editor, thickness);
|
||||
};
|
||||
// Shown again on each visit, so following the outside cuts shows their current values.
|
||||
editor.VisibleChanged += (s, e) =>
|
||||
{
|
||||
if (editor.Visible)
|
||||
Show();
|
||||
};
|
||||
Show();
|
||||
|
||||
var page = new Panel { Dock = DockStyle.Fill, BackColor = Color.White };
|
||||
page.Controls.Add(editor);
|
||||
page.Controls.Add(sameBox);
|
||||
page.Controls.Add(CreateTabNote("Cutouts and holes, full circles included."));
|
||||
return page;
|
||||
}
|
||||
|
||||
// The machine holds no arc sweep (leads are half circles) and takes a clean hole's kerf
|
||||
// from the thickness; Line + Line is never applied from it, so it is not offered.
|
||||
private Controls.LeadsEditor CreateLeadsEditor(CNC.CuttingStrategy.ContourType emphasis)
|
||||
{
|
||||
return new Controls.LeadsEditor
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
Padding = new Padding(10, 0, 10, 10),
|
||||
Emphasis = emphasis,
|
||||
AllowLineLine = false,
|
||||
ShowArcSweep = false,
|
||||
ShowCleanHoleKerf = false,
|
||||
LengthUnit = LengthUnitSuffix(null),
|
||||
};
|
||||
}
|
||||
|
||||
private static void ShowLeads(Controls.LeadsEditor editor, ThicknessConfig thickness, LeadConfig leadIn, LeadConfig leadOut)
|
||||
{
|
||||
editor.LeadIn = MachineCuttingDefaults.LeadInOf(leadIn, thickness.Kerf) ?? new CNC.CuttingStrategy.NoLeadIn();
|
||||
editor.LeadOut = MachineCuttingDefaults.LeadOutOf(leadOut);
|
||||
editor.PreviewParameters = MachineCuttingDefaults.LeadsOf(thickness);
|
||||
}
|
||||
|
||||
private void LeadsEdited(Controls.LeadsEditor editor, ThicknessConfig thickness)
|
||||
{
|
||||
editor.PreviewParameters = MachineCuttingDefaults.LeadsOf(thickness);
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
private static LeadConfig Copy(LeadConfig lead) =>
|
||||
new() { Type = lead.Type, Length = lead.Length, Angle = lead.Angle, Radius = lead.Radius };
|
||||
|
||||
private static Label CreateTabNote(string text) =>
|
||||
new()
|
||||
{
|
||||
Dock = DockStyle.Top,
|
||||
AutoSize = true,
|
||||
MaximumSize = new Size(560, 0),
|
||||
ForeColor = SystemColors.GrayText,
|
||||
Padding = new Padding(10, 8, 0, 6),
|
||||
Text = text,
|
||||
};
|
||||
|
||||
private Control BuildCutOffTab(CutOffConfig cutOff)
|
||||
{
|
||||
var layout = CreateDetailLayout();
|
||||
|
||||
@@ -27,8 +27,13 @@ soon as its settings dialog is accepted, not only on Apply. A plate that already
|
||||
parameters keeps them: changing the machine, material or thickness does not rewrite leads
|
||||
the plate was planned with.
|
||||
|
||||
The machine holds one lead-in and one lead-out per thickness; both go to external and internal
|
||||
contours (internal covers full circles). Types map as follows:
|
||||
Each thickness holds a lead-in and lead-out for outside cuts (the Outside Cuts tab, external
|
||||
contours) and, optionally, its own pair for inside cuts (the Inside Cuts tab, internal contours,
|
||||
full circles included). Inside cuts use the outside pair until "Same as outside cuts" is cleared,
|
||||
which starts them from a copy of it; configurations saved before the split have no inside pair
|
||||
and keep applying one pair to both. Both tabs use the shared `LeadsEditor` control that Cutting
|
||||
Settings uses, without the fields the configuration cannot hold (an arc's sweep; a clean hole's kerf,
|
||||
which comes from the thickness). Types map as follows:
|
||||
|
||||
| Machine type | Lead-in |
|
||||
|---|---|
|
||||
@@ -36,8 +41,8 @@ contours (internal covers full circles). Types map as follows:
|
||||
| Line | line (length, angle) |
|
||||
| Arc | arc (radius; the default 180-degree half-circle sweep) |
|
||||
| Line + Arc | line-arc (length, radius, angle) |
|
||||
| Clean Hole | clean hole (length, radius, the thickness's kerf), holes only: the external lead-in is kept |
|
||||
| Line + Line | not mapped (the configuration has no second segment): every lead-in is kept |
|
||||
| Clean Hole | clean hole (length, radius, the thickness's kerf), inside cuts only; an outside Clean Hole (older configurations) keeps the external lead-in |
|
||||
| Line + Line | not offered; an older configuration's is not mapped (it has no second segment): the lead-in it would set is kept |
|
||||
|
||||
Lead-outs map None, Line (length, angle) and Arc (radius); any other type is none.
|
||||
|
||||
|
||||
Reference in new issue
Block a user