feat(machines): set spacing per thickness and save defaults from a nest

Plate > Set as Default and Nest > Edit's Defaults tab only changed the
open nest, so new nests never picked up the values. Set as Default now
also saves the new-nest defaults, and the Defaults tab gains an opt-in
"Use these defaults for new nests" check.

Machine thicknesses can now set part and edge spacing. A nest takes them
when its material, grade, thickness or machine changes and when a release
creates it; BOM import seeds its groups from them. Spacing edited in the
same dialog stays, and only an untouched trailing sheet follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-10-31 19:39:48 -04:00
1 parent 817c4482eb
commit 59d4c8d727
20 files changed
+457 -44

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
namespace OpenNest.Data;
/// <summary>A thickness's distance kept from each sheet edge, in the machine's units.</summary>
public class EdgeSpacingConfig
{
public double Left { get; set; }
public double Bottom { get; set; }
public double Right { get; set; }
public double Top { get; set; }
}
+48
View File
@@ -1,4 +1,5 @@
using OpenNest.CNC.CuttingStrategy;
using OpenNest.Geometry;
namespace OpenNest.Data;
@@ -64,6 +65,53 @@ public static class MachineCuttingDefaults
return true;
}
/// <summary>
/// Gives the nest's plate defaults the part and edge spacing the machine has for its
/// material and thickness. Spacing the machine leaves unset stays as it was. Returns false,
/// changing nothing, when the machine sets neither.
/// </summary>
public static bool ApplySpacingTo(Nest nest, MachineConfig? machine)
{
ArgumentNullException.ThrowIfNull(nest);
var settings = Find(machine, nest);
if (settings?.PartSpacing == null && settings?.EdgeSpacing == null)
return false;
var scale = Scale(machine!.Units, nest.Units);
if (settings.PartSpacing is { } partSpacing)
nest.PlateDefaults.PartSpacing = partSpacing * scale;
if (settings.EdgeSpacing is { } edge)
nest.PlateDefaults.EdgeSpacing = new Spacing(edge.Left * scale, edge.Bottom * scale, edge.Right * scale, edge.Top * scale);
return true;
}
/// <summary>
/// Runs <paramref name="edit"/>, then, when it changed the nest's material, grade or
/// thickness, applies the machine's spacing for them (<see cref="ApplySpacingTo"/>). A
/// spacing the edit changed itself is the user's choice and stays.
/// </summary>
public static void ApplySpacingAfter(Nest nest, MachineConfig? machine, Action edit)
{
ArgumentNullException.ThrowIfNull(nest);
ArgumentNullException.ThrowIfNull(edit);
var job = (nest.Material?.Name, nest.Material?.Grade, nest.Thickness);
var plate = nest.PlateDefaults;
var (partBefore, edgeBefore) = (plate.PartSpacing, plate.EdgeSpacing);
edit();
if (job == (nest.Material?.Name, nest.Material?.Grade, nest.Thickness))
return;
plate = nest.PlateDefaults;
var (partEdited, edgeEdited) = (plate.PartSpacing, plate.EdgeSpacing);
ApplySpacingTo(nest, machine);
if (partEdited != partBefore)
plate.PartSpacing = partEdited;
if (!edgeEdited.Equals(edgeBefore))
plate.EdgeSpacing = edgeEdited;
}
/// <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.
+6
View File
@@ -26,4 +26,10 @@ public class ThicknessConfig
public CutOffConfig CutOff { get; set; } = new();
public List<string> PlateSizes { get; set; } = new();
/// <summary>The part spacing of nests in this thickness; null keeps the nest defaults'.</summary>
public double? PartSpacing { get; set; }
/// <summary>The edge spacing of nests in this thickness; null keeps the nest defaults'.</summary>
public EdgeSpacingConfig? EdgeSpacing { get; set; }
}
@@ -1,5 +1,6 @@
using OpenNest.CNC.CuttingStrategy;
using OpenNest.Data;
using OpenNest.Geometry;
namespace OpenNest.Tests.Data;
@@ -41,6 +42,119 @@ public class MachineCuttingDefaultsTests
private static readonly LeadConfig LineArc = new() { Type = "Line + Arc", Length = 0.125, Angle = 90, Radius = 0.0625 };
private static MachineConfig SpacedMachine(UnitSystem units = UnitSystem.Inches)
{
var machine = Machine(LineArc, units: units);
var thickness = machine.Materials[0].Thicknesses[0];
thickness.PartSpacing = 0.5;
thickness.EdgeSpacing = new EdgeSpacingConfig { Left = 1, Bottom = 2, Right = 3, Top = 4 };
return machine;
}
private static Nest SpacedNest(double thickness = 0.25, Units units = Units.Inches)
{
var nest = NestOf(thickness, "A36", units);
nest.PlateDefaults.PartSpacing = 0.1;
nest.PlateDefaults.EdgeSpacing = new Spacing(0.2, 0.2, 0.2, 0.2);
return nest;
}
[Fact]
public void ApplySpacingTo_SetsTheMachinesSpacing()
{
var nest = SpacedNest();
Assert.True(MachineCuttingDefaults.ApplySpacingTo(nest, SpacedMachine()));
Assert.Equal(0.5, nest.PlateDefaults.PartSpacing);
Assert.Equal(new Spacing(1, 2, 3, 4), nest.PlateDefaults.EdgeSpacing);
}
[Fact]
public void ApplySpacingTo_ConvertsToTheNestsUnits()
{
var nest = SpacedNest(6.35, Units.Millimeters);
Assert.True(MachineCuttingDefaults.ApplySpacingTo(nest, SpacedMachine()));
Assert.Equal(12.7, nest.PlateDefaults.PartSpacing, 6);
Assert.Equal(25.4, nest.PlateDefaults.EdgeSpacing.Left, 6);
}
[Fact]
public void ApplySpacingTo_KeepsWhatTheThicknessLeavesUnset()
{
var machine = SpacedMachine();
machine.Materials[0].Thicknesses[0].EdgeSpacing = null;
var nest = SpacedNest();
Assert.True(MachineCuttingDefaults.ApplySpacingTo(nest, machine));
Assert.Equal(0.5, nest.PlateDefaults.PartSpacing);
Assert.Equal(new Spacing(0.2, 0.2, 0.2, 0.2), nest.PlateDefaults.EdgeSpacing);
}
[Fact]
public void ApplySpacingTo_WithoutSpacingOnFile_ChangesNothing()
{
var nest = SpacedNest();
Assert.False(MachineCuttingDefaults.ApplySpacingTo(nest, Machine(LineArc)));
Assert.False(MachineCuttingDefaults.ApplySpacingTo(nest, null));
Assert.False(MachineCuttingDefaults.ApplySpacingTo(SpacedNest(0.5), SpacedMachine()));
Assert.Equal(0.1, nest.PlateDefaults.PartSpacing);
}
[Fact]
public void ApplySpacingAfter_FollowsAChangedThicknessOnly()
{
var nest = SpacedNest(0.5);
MachineCuttingDefaults.ApplySpacingAfter(nest, SpacedMachine(), () => nest.Customer = "Acme");
Assert.Equal(0.1, nest.PlateDefaults.PartSpacing);
MachineCuttingDefaults.ApplySpacingAfter(nest, SpacedMachine(), () => nest.Thickness = 0.25);
Assert.Equal(0.5, nest.PlateDefaults.PartSpacing);
}
[Fact]
public void ApplySpacingAfter_KeepsSpacingTheEditChanged()
{
var nest = SpacedNest(0.5);
MachineCuttingDefaults.ApplySpacingAfter(nest, SpacedMachine(), () =>
{
nest.Thickness = 0.25;
nest.PlateDefaults.PartSpacing = 0.3;
});
Assert.Equal(0.3, nest.PlateDefaults.PartSpacing);
Assert.Equal(new Spacing(1, 2, 3, 4), nest.PlateDefaults.EdgeSpacing);
}
[Fact]
public void Spacing_SurvivesTheMachineFile()
{
var directory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
try
{
var provider = new LocalJsonProvider(directory);
var machine = SpacedMachine();
provider.SaveMachine(machine);
var thickness = provider.GetMachine(machine.Id)!.Materials[0].Thicknesses[0];
Assert.Equal(0.5, thickness.PartSpacing);
Assert.Equal(4, thickness.EdgeSpacing!.Top);
}
finally
{
if (Directory.Exists(directory))
Directory.Delete(directory, true);
}
}
[Fact]
public void IsRecordedFor_KeepsLeadsEditedAfterTheMachineSettingsWereTaken()
{
@@ -74,6 +74,25 @@ public class EditNestInfoFormTests
});
}
[Fact]
public void NewNestDefaultsChoice_NeedsTheOfferAndTheCheck()
{
RunSta(() =>
{
using var form = new EditNestInfoForm();
var box = (CheckBox)form.Controls.Find("useForNewNestsBox", true).Single();
box.Checked = true;
Assert.False(form.UseForNewNests);
form.OfferNewNestDefaults = true;
Assert.True(form.UseForNewNests);
box.Checked = false;
Assert.False(form.UseForNewNests);
});
}
private static Nest AcceptanceNest()
{
var nest = new Nest("Acceptance nest")
@@ -10,6 +10,57 @@ namespace OpenNest.WinForms.Tests.Forms;
/// </summary>
public class MachineCutsFormsTests
{
[Fact]
public void SpacingTab_SetsOnlyTheCheckedSpacing()
{
RunSta(() =>
{
var directory = Path.Combine(Path.GetTempPath(), "OpenNestTests", Guid.NewGuid().ToString());
Directory.CreateDirectory(directory);
try
{
var provider = new LocalJsonProvider(directory);
provider.SaveMachine(new MachineConfig
{
Name = "Laser",
Materials = { new MaterialConfig { Name = "Mild Steel", Thicknesses = { new ThicknessConfig { Value = 0.25 } } } },
});
using var form = new MachineConfigForm(provider);
var node = Descendants(Find<TreeView>(form, "_tree").Nodes).Single(n => n.Tag is ThicknessConfig);
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 page = All<TabControl>(form).Single().TabPages.Cast<TabPage>().Single(p => p.Text == "Spacing");
var checks = All<CheckBox>(page).ToList();
var boxes = All<NumericUpDown>(page).ToList();
Assert.Equal(2, checks.Count);
Assert.Equal(5, boxes.Count);
Assert.All(boxes, b => Assert.False(b.Enabled));
checks[0].Checked = true;
boxes[0].Value = 0.5m;
Assert.Equal(0.5, shown.PartSpacing);
Assert.Null(shown.EdgeSpacing);
checks[1].Checked = true;
boxes[4].Value = 2m;
Assert.Equal(2, shown.EdgeSpacing!.Top);
checks[0].Checked = false;
checks[1].Checked = false;
Assert.Null(shown.PartSpacing);
Assert.Null(shown.EdgeSpacing);
}
finally
{
Directory.Delete(directory, true);
}
});
}
[Fact]
public void InsideCutsTab_GivesHolesTheirOwnLeads()
{
+1 -1
View File
@@ -116,7 +116,7 @@ namespace OpenNest.Controls
nestAfterReleaseCheckBox.Checked = Properties.Settings.Default.ReleaseNestAfter;
nestAfterReleaseCheckBox.Click += NestAfterReleaseCheckBox_Click;
ShowMessage = (text, buttons, icon) => MessageBox.Show(FindForm(), text, "Release", buttons, icon);
CreateNest = (group, _) => NewNestFactory.CreateAsync(FindForm(), group.Units);
CreateNest = (group, _) => NewNestFactory.CreateAsync(FindForm(), group);
EditDrawing = edit =>
{
using var form = new EditLibraryDrawingForm(edit);
+22 -10
View File
@@ -280,6 +280,7 @@ namespace OpenNest.Forms
: _defaults.Size.Length;
var groups = BomImportGroups.Build(_parts);
var machine = MachineSettings.DefaultMachine(_defaults, MachineSettings.LoadMachines());
var table = new DataTable();
table.Columns.Add("Material", typeof(string));
@@ -297,6 +298,7 @@ namespace OpenNest.Forms
foreach (var group in groups)
{
var existing = _groupSettings.TryGetValue(group.Key, out var gs);
var defaults = GroupDefaults(group, machine);
table.Rows.Add(
group.Material,
@@ -305,11 +307,11 @@ namespace OpenNest.Forms
group.TotalQty,
existing ? gs.PlateWidth : defaultWidth,
existing ? gs.PlateLength : defaultLength,
existing ? gs.PartSpacing : _defaults.PartSpacing,
existing ? gs.EdgeLeft : _defaults.EdgeSpacing.Left,
existing ? gs.EdgeBottom : _defaults.EdgeSpacing.Bottom,
existing ? gs.EdgeRight : _defaults.EdgeSpacing.Right,
existing ? gs.EdgeTop : _defaults.EdgeSpacing.Top
existing ? gs.PartSpacing : defaults.PartSpacing,
existing ? gs.EdgeLeft : defaults.EdgeSpacing.Left,
existing ? gs.EdgeBottom : defaults.EdgeSpacing.Bottom,
existing ? gs.EdgeRight : defaults.EdgeSpacing.Right,
existing ? gs.EdgeTop : defaults.EdgeSpacing.Top
);
}
@@ -327,6 +329,15 @@ namespace OpenNest.Forms
btnCreateNests.Enabled = table.Rows.Count > 0;
}
// The saved defaults with the machine's spacing for the group's material and thickness.
private NestDefaults GroupDefaults(BomImportGroup group, MachineConfig machine)
{
var nest = new Nest { Thickness = group.Thickness, Material = new Material(group.Material) };
_defaults.ApplyTo(nest);
MachineCuttingDefaults.ApplySpacingTo(nest, machine);
return NestDefaults.FromNest(nest);
}
private void SaveGroupSettings()
{
if (dgvGroups.DataSource is not DataTable table)
@@ -416,15 +427,16 @@ namespace OpenNest.Forms
{
if (!_groupSettings.TryGetValue(group.Key, out var plate))
{
var defaults = GroupDefaults(group, machine);
plate = new BomGroupPlateSettings
{
PlateWidth = defaultWidth,
PlateLength = defaultLength,
PartSpacing = _defaults.PartSpacing,
EdgeLeft = _defaults.EdgeSpacing.Left,
EdgeBottom = _defaults.EdgeSpacing.Bottom,
EdgeRight = _defaults.EdgeSpacing.Right,
EdgeTop = _defaults.EdgeSpacing.Top,
PartSpacing = defaults.PartSpacing,
EdgeLeft = defaults.EdgeSpacing.Left,
EdgeBottom = defaults.EdgeSpacing.Bottom,
EdgeRight = defaults.EdgeSpacing.Right,
EdgeTop = defaults.EdgeSpacing.Top,
};
}
+8 -2
View File
@@ -826,7 +826,11 @@ namespace OpenNest.Forms
public async Task ShowNestInfoEditorAsync()
{
var form = new EditNestInfoForm { AllowRename = NestStorage.Settings.IsDatabaseMode };
var form = new EditNestInfoForm
{
AllowRename = NestStorage.Settings.IsDatabaseMode,
OfferNewNestDefaults = true,
};
form.SetMaterialCatalog(MachineSettings.LoadMaterials());
form.LoadNestInfo(Nest);
_ = ShowReleaseOriginAsync(form);
@@ -843,13 +847,15 @@ namespace OpenNest.Forms
using (var change = BeginChange("Nest Info"))
{
var before = UndoableNestInfo();
PlateManager.ChangePlateDefaults(() => form.SaveNestInfo(Nest));
MachineSettings.SaveNestInfo(PlateManager, form, Nest);
if (UndoableNestInfo() != before)
change.MarkChanged();
// The material, thickness or units may have changed.
MachineSettings.ApplyCutOffDefaults(PlateView, Nest);
change.Complete();
}
if (form.UseForNewNests)
MachineSettings.SaveNestDefaults(this, NestDefaults.FromNest(Nest), Nest.Machine);
Text = Nest.Name;
drawingListBox1.Units = Nest.Units;
drawingListBox1.Invalidate();
+13
View File
@@ -47,6 +47,7 @@
this.partSpacingBox = new OpenNest.Controls.NumericUpDown();
this.labelThk = new System.Windows.Forms.Label();
this.groupBox3 = new System.Windows.Forms.GroupBox();
this.useForNewNestsBox = new System.Windows.Forms.CheckBox();
this.quadrantSelect1 = new OpenNest.Controls.QuadrantSelect();
this.tabControl1 = new System.Windows.Forms.TabControl();
this.tabPage1 = new System.Windows.Forms.TabPage();
@@ -631,6 +632,7 @@
//
// tabPage2
//
this.tabPage2.Controls.Add(this.useForNewNestsBox);
this.tabPage2.Controls.Add(this.groupBox3);
this.tabPage2.Controls.Add(this.groupBox1);
this.tabPage2.Controls.Add(this.tableLayoutPanel1);
@@ -642,6 +644,16 @@
this.tabPage2.Text = "Defaults";
this.tabPage2.UseVisualStyleBackColor = true;
//
// useForNewNestsBox
//
this.useForNewNestsBox.AutoSize = true;
this.useForNewNestsBox.Location = new System.Drawing.Point(12, 288);
this.useForNewNestsBox.Name = "useForNewNestsBox";
this.useForNewNestsBox.TabIndex = 8;
this.useForNewNestsBox.Text = "Use these defaults for new nests";
this.useForNewNestsBox.UseVisualStyleBackColor = true;
this.useForNewNestsBox.Visible = false;
//
// tabPage3
//
this.tabPage3.Controls.Add(this.notesBox);
@@ -772,6 +784,7 @@
private Controls.NumericUpDown partSpacingBox;
private Controls.BottomPanel bottomPanel1;
private System.Windows.Forms.GroupBox groupBox3;
private System.Windows.Forms.CheckBox useForNewNestsBox;
private Controls.QuadrantSelect quadrantSelect1;
private System.Windows.Forms.Button applyButton;
private System.Windows.Forms.Label releaseLabel;
+19
View File
@@ -15,6 +15,7 @@ namespace OpenNest.Forms
private DateTime dateLastModified;
private List<MaterialConfig> materialCatalog = new();
private string gradeMaterialKey = "";
private bool offerNewNestDefaults;
public EditNestInfoForm()
{
@@ -58,6 +59,24 @@ namespace OpenNest.Forms
set { nameBox.ReadOnly = !value; }
}
/// <summary>
/// Shows the Defaults tab's choice to also start new nests from these values. Hidden
/// by default: the tab otherwise edits only this nest.
/// </summary>
public bool OfferNewNestDefaults
{
// Not read back from the box: Visible is false once the dialog has closed.
get { return offerNewNestDefaults; }
set
{
offerNewNestDefaults = value;
useForNewNestsBox.Visible = value;
}
}
/// <summary>The user chose to save the Defaults tab for new nests.</summary>
public bool UseForNewNests => OfferNewNestDefaults && useForNewNestsBox.Checked;
/// <summary>
/// The drawing set release this nest came from (Database mode), shown beside the
/// buttons; blank for none.
+71
View File
@@ -621,6 +621,7 @@ namespace OpenNest.Forms
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("Spacing", BuildSpacingTab(thickness)));
tabs.TabPages.Add(CreateTab("Plate Sizes", BuildPlateSizesTab(thickness)));
RememberTab(tabs, _thicknessTab, t => _thicknessTab = t);
@@ -701,6 +702,76 @@ namespace OpenNest.Forms
return panel;
}
// Unchecked leaves the spacing to the nest defaults, so a thickness need not set both.
private Control BuildSpacingTab(ThicknessConfig thickness)
{
var layout = CreateDetailLayout();
var row = 0;
var partBox = new CheckBox
{
Text = "Use for this thickness",
AutoSize = true,
Checked = thickness.PartSpacing.HasValue,
};
var part = CreateNumericBox(thickness.PartSpacing ?? 0, v =>
{
thickness.PartSpacing = partBox.Checked ? v : null;
MarkDirty();
}, 4, 1000, 0.0625m);
part.Enabled = partBox.Checked;
partBox.CheckedChanged += (s, e) =>
{
part.Enabled = partBox.Checked;
thickness.PartSpacing = partBox.Checked ? (double)part.Value : null;
MarkDirty();
};
AddField(layout, ref row, "Part Spacing:", partBox);
AddField(layout, ref row, $"Spacing ({LengthUnitSuffix(null)}):", part);
var edge = thickness.EdgeSpacing;
var edgeBox = new CheckBox
{
Text = "Use for this thickness",
AutoSize = true,
Checked = edge != null,
};
var sides = new NumericUpDown[4];
void StoreEdge()
{
thickness.EdgeSpacing = edgeBox.Checked
? new EdgeSpacingConfig
{
Left = (double)sides[0].Value,
Bottom = (double)sides[1].Value,
Right = (double)sides[2].Value,
Top = (double)sides[3].Value,
}
: null;
MarkDirty();
}
AddField(layout, ref row, "Edge Spacing:", edgeBox);
var values = new[] { edge?.Left ?? 0, edge?.Bottom ?? 0, edge?.Right ?? 0, edge?.Top ?? 0 };
var labels = new[] { "Left", "Bottom", "Right", "Top" }
.Select(side => $"{side} ({LengthUnitSuffix(null)}):").ToArray();
for (var i = 0; i < sides.Length; i++)
{
sides[i] = CreateNumericBox(values[i], _ => StoreEdge(), 4, 1000, 0.0625m);
sides[i].Enabled = edgeBox.Checked;
AddField(layout, ref row, labels[i], sides[i]);
}
edgeBox.CheckedChanged += (s, e) =>
{
foreach (var side in sides)
side.Enabled = edgeBox.Checked;
StoreEdge();
};
var panel = new Panel { Dock = DockStyle.Fill, Padding = new Padding(10) };
panel.Controls.Add(layout);
return panel;
}
private Control BuildOutsideCutsTab(ThicknessConfig thickness)
{
var editor = CreateLeadsEditor(CNC.CuttingStrategy.ContourType.External);
+32
View File
@@ -241,6 +241,14 @@ namespace OpenNest.Forms
return null;
}
/// <summary>
/// Saves a Nest Info dialog to <paramref name="nest"/>: a changed material or thickness
/// takes the machine's spacing, and an untouched trailing sheet follows the new defaults.
/// </summary>
public static void SaveNestInfo(PlateManager manager, EditNestInfoForm form, Nest nest) =>
manager.ChangePlateDefaults(() => MachineCuttingDefaults.ApplySpacingAfter(
nest, FindMachine(nest.Machine), () => form.SaveNestInfo(nest)));
/// <summary>
/// Gives <paramref name="view"/> the cut-off clearance, overtravel, minimum segment and
/// direction of the nest's machine for its material and thickness, when the machine has
@@ -317,6 +325,30 @@ namespace OpenNest.Forms
defaults.Save(NestDefaults.DefaultPath);
}
/// <summary>Saves the nest defaults, telling the user when they could not be saved.</summary>
public static void SaveNestDefaults(IWin32Window owner, NestDefaults defaults)
{
try
{
SaveNestDefaults(defaults);
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
MessageBox.Show(owner, "The nest defaults could not be saved.\n\n" + ex.Message, "Nest Defaults",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// Makes values taken from an open nest the defaults new nests start from. The nest's
/// machine becomes the default; a nest without one keeps the saved choice.
/// </summary>
public static void SaveNestDefaults(IWin32Window owner, NestDefaults defaults, NestMachine machine)
{
defaults.MachineId = machine?.Id ?? LoadSavedNestDefaults(out _).MachineId;
SaveNestDefaults(owner, defaults);
}
private static SharedSettingRecord SharedNestDefaults() =>
SharedSettings.Find(SharedSettingKinds.NestDefaults, SharedSettingKinds.DefaultKey);
}
+4 -2
View File
@@ -79,8 +79,9 @@ namespace OpenNest.Forms
items.Add(new ToolStripMenuItem("(no machines; see Tools > Machine Configuration)") { Enabled = false });
}
// Assigning a machine also puts every sheet on that machine's support grid and gives
// the views its cut-off settings; plates' own cutting parameters are kept.
// Assigning a machine also puts every sheet on that machine's support grid, gives the
// views its cut-off settings and the nest its spacing; plates' own cutting parameters are
// kept, and of the sheets only an untouched trailing one takes the spacing.
private void AssignMachine(MachineConfig machine)
{
var editForm = activeForm;
@@ -101,6 +102,7 @@ namespace OpenNest.Forms
MachineSettings.AssignMachine(nest, machine);
foreach (var view in views)
MachineSettings.ApplyCutOffDefaults(view, nest);
editForm.PlateManager.ChangePlateDefaults(() => MachineCuttingDefaults.ApplySpacingTo(nest, machine));
});
foreach (var view in views)
view.Invalidate();
+6 -1
View File
@@ -9,7 +9,12 @@ namespace OpenNest.Forms
{
#region Plate Menu Events
private void SetAsNestDefault_Click(object sender, EventArgs e) => WithActive(f => f.SetCurrentPlateAsNestDefault());
// Both this nest's next plates and every new nest start from the current plate.
private void SetAsNestDefault_Click(object sender, EventArgs e)
{
WithActive(f => f.SetCurrentPlateAsNestDefault());
SaveCurrentAsDefaults_Click(sender, e);
}
private void AddPlate_Click(object sender, EventArgs e)
{
+3 -19
View File
@@ -1,5 +1,4 @@
using System;
using System.IO;
using System.Windows.Forms;
using OpenNest.Actions;
using OpenNest.Data;
@@ -185,20 +184,7 @@ namespace OpenNest.Forms
using (var form = new NestDefaultsForm(defaults, MachineSettings.LoadMachines(), SharedSettings.NestDefaultsSource()))
{
if (form.ShowDialog(this) == DialogResult.OK)
SaveNestDefaults(form.GetDefaults());
}
}
private void SaveNestDefaults(NestDefaults defaults)
{
try
{
MachineSettings.SaveNestDefaults(defaults);
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
MessageBox.Show(this, "The nest defaults could not be saved.\n\n" + ex.Message, "Nest Defaults",
MessageBoxButtons.OK, MessageBoxIcon.Error);
MachineSettings.SaveNestDefaults(this, form.GetDefaults());
}
}
@@ -207,10 +193,8 @@ namespace OpenNest.Forms
if (activeForm == null)
return;
var defaults = NestDefaults.FromPlate(activeForm.Nest.Units, activeForm.PlateView.Plate);
// The nest's machine becomes the default; a nest without one keeps the saved choice.
defaults.MachineId = activeForm.Nest.Machine?.Id ?? MachineSettings.LoadSavedNestDefaults(out _).MachineId;
SaveNestDefaults(defaults);
MachineSettings.SaveNestDefaults(this, NestDefaults.FromPlate(activeForm.Nest.Units, activeForm.PlateView.Plate),
activeForm.Nest.Machine);
}
private void MachineConfig_Click(object sender, EventArgs e)
+14 -7
View File
@@ -19,13 +19,14 @@ namespace OpenNest.Forms
{
private static readonly TimeSpan ServerTimeout = TimeSpan.FromSeconds(3);
/// <param name="units">
/// The new nest's units, with the saved plate defaults converted to them (a drawing set
/// release group drawn in other units); null uses the defaults as saved.
/// <param name="release">
/// The drawing set release group the nest is for: its units, with the saved plate
/// defaults converted to them, its material and thickness, and the machine's spacing for
/// those. Null uses the defaults as saved.
/// </param>
public static async Task<Nest> CreateAsync(IWin32Window owner, Units? units = null)
public static async Task<Nest> CreateAsync(IWin32Window owner, SetReleaseGroup release = null)
{
var nest = CreateDefaultNest(owner, units);
var nest = CreateDefaultNest(release);
var now = DateTime.Now;
nest.DateCreated = now;
@@ -34,15 +35,21 @@ namespace OpenNest.Forms
return nest;
}
private static Nest CreateDefaultNest(IWin32Window owner, Units? units)
private static Nest CreateDefaultNest(SetReleaseGroup release)
{
var nest = new Nest();
var defaults = MachineSettings.LoadNestDefaults();
(units is { } converted ? defaults.ConvertedTo(converted) : defaults).ApplyTo(nest);
(release != null ? defaults.ConvertedTo(release.Units) : defaults).ApplyTo(nest);
// After the defaults so the rail and grid are converted to the new nest's units.
var machine = MachineSettings.DefaultMachine(defaults, MachineSettings.LoadMachines());
MachineSettings.LoadClampSettings(machine).ApplyToNewNest(nest);
MachineSettings.AssignMachine(nest, machine);
if (release != null)
{
nest.Material = new Material(release.MaterialName, release.MaterialGrade);
nest.Thickness = release.Thickness;
MachineCuttingDefaults.ApplySpacingTo(nest, machine);
}
return nest;
}
+1 -1
View File
@@ -872,7 +872,7 @@ public sealed class SavedNestsForm : Form
}
// Same default propagation as Nest > Nest Info in the open editor.
using var manager = new PlateManager(nest);
manager.ChangePlateDefaults(() => dialog.SaveNestInfo(nest));
MachineSettings.SaveNestInfo(manager, dialog, nest);
nest.DateLastModified = DateTime.Now;
summaryLabel.Text = "Saving nest information...";
var bytes = await Task.Run(() =>
+11 -1
View File
@@ -1,7 +1,7 @@
# Machine cutting defaults
A nest's assigned machine (`Nest.Machine`, see [nest file format](nest-file-format.md))
supplies the starting lead-ins, lead-outs, kerf and cut-off settings for the nest's
supplies the starting lead-ins, lead-outs, kerf, cut-off settings and spacing for the nest's
material and thickness, from that machine's Tools > Machine Configuration entry (in
Database mode, the server's [shared machines](shared-settings.md) once it holds them).
`MachineCuttingDefaults` (OpenNest.Data) holds the mapping; the desktop applies it.
@@ -47,6 +47,16 @@ which comes from the thickness). Types map as follows:
Lead-outs map None, Line (length, angle) and Arc (radius); any other type is none.
## Spacing
Each thickness can set a part spacing and an edge spacing (the Spacing tab), each optional:
one left unset stays at the nest defaults' value. The machine's spacing replaces the nest's
plate defaults when Nest > Edit or Nest Information changes the material, grade or thickness
(a spacing edited in the same dialog stays), when Nest > Machine changes the machine, and when a
drawing set release creates a nest. BOM import starts each group's row from it. File > New has
no material yet, so a new nest starts from the nest defaults. Plates are kept as they are, except
an untouched trailing empty sheet, which follows the plate defaults.
## Cut-offs
The plate view's part clearance, overtravel, minimum segment length and direction come from
+4
View File
@@ -13,6 +13,10 @@ building nests and posts the same way:
File mode, the console, MCP and API front ends use only this PC's copies.
Nest defaults are saved by Tools > Nest Defaults, Tools > Save Current Plate as Defaults,
Plate > Set as Default, and Nest > Edit's Defaults tab with "Use these defaults for new nests"
checked. Without that check the tab changes only the open nest.
## Which copy is used
Each kind is read from the server once the server holds it, and from this PC's copy until