mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 15:01:07 -04:00
feat(machines): keep the clamp rail on each machine
Clamps are physical fixtures that differ between machines, but one global clamps.json served every machine, so a plasma table and a fiber laser could not have different rails. - MachineConfig.Clamps holds each machine's rail in the machine's units, edited on a new Clamps tab in Tools > Machine Configuration. - New and BOM import give a nest the rail of the machine they assign; Nest > Clamps loads and saves the nest's machine's clamps. Each nest keeps its own copy, so machine edits never move existing nests' clamps. - At startup, while no machine has clamps, the legacy clamps.json (or the server's shared copy) is copied once into the default or only machine; with several machines and no default the user picks one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3e767ca3b6
commit
3e1df45c7f
19 files changed
+770
-99
No files matched your search
@@ -23,9 +23,11 @@ public enum ClampSettingsStatus
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The machine's fixed edge-clamp rail, persisted to a single JSON file (by default
|
||||
/// %APPDATA%\OpenNest\clamps.json). Off by default: until <see cref="Enabled"/> is set
|
||||
/// with at least one center, new nests get no clamps and behave exactly as before.
|
||||
/// An edge-clamp rail in given units, as a machine gives new nests (see
|
||||
/// <see cref="MachineConfig.GetClampSettings"/>) and as the legacy single
|
||||
/// %APPDATA%\OpenNest\clamps.json held it before each machine kept its own; that file is now
|
||||
/// read only to copy it onto a machine once (<see cref="LegacyClampMigration"/>). Off by default:
|
||||
/// until <see cref="Enabled"/> is set with at least one center, new nests get no clamps.
|
||||
/// Loading never throws; values are validated field by field.
|
||||
/// </summary>
|
||||
public sealed class ClampSettings
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace OpenNest.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Moves the single legacy clamps.json (or its shared copy) onto a machine once. The legacy
|
||||
/// rail is copied only while no machine has clamps of its own; after that it is never read.
|
||||
/// </summary>
|
||||
public static class LegacyClampMigration
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether <paramref name="legacy"/> (null when there is no legacy file) still has to be
|
||||
/// copied: it lists at least one clamp and none of <paramref name="machines"/> has clamps yet.
|
||||
/// </summary>
|
||||
public static bool IsNeeded(ClampSettings? legacy, IReadOnlyCollection<MachineConfig> machines) =>
|
||||
legacy is { ClampCount: > 0 } && machines.Count > 0 && machines.All(m => m.Clamps == null);
|
||||
|
||||
/// <summary>
|
||||
/// The machine that takes the legacy rail without asking: the nest defaults' machine when it
|
||||
/// is on file, otherwise the only machine. Null when there are several and none is the
|
||||
/// default, so the user has to choose.
|
||||
/// </summary>
|
||||
public static MachineConfig? Target(IReadOnlyCollection<MachineConfig> machines, Guid? defaultMachineId) =>
|
||||
machines.FirstOrDefault(m => m.Id == defaultMachineId)
|
||||
?? (machines.Count == 1 ? machines.First() : null);
|
||||
|
||||
/// <summary>Gives <paramref name="machine"/> the legacy rail, converted to the machine's units.</summary>
|
||||
public static void Apply(ClampSettings legacy, MachineConfig machine)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(legacy);
|
||||
ArgumentNullException.ThrowIfNull(machine);
|
||||
machine.Clamps = MachineClamps.FromSettings(legacy, machine.Units);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
namespace OpenNest.Data;
|
||||
|
||||
/// <summary>
|
||||
/// A machine's fixed edge-clamp rail, stored on its <see cref="MachineConfig"/> in the machine's
|
||||
/// units. New nests assigned the machine copy it as their own rail; editing it never changes an
|
||||
/// existing nest. <see cref="ToSettings"/> validates the stored values field by field.
|
||||
/// </summary>
|
||||
public sealed class MachineClamps
|
||||
{
|
||||
/// <summary>Whether new nests assigned this machine receive the rail. Default off.</summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>Clamp extent along its edge.</summary>
|
||||
public double Width { get; set; } = ClampSettings.DefaultWidth;
|
||||
|
||||
/// <summary>Clamp intrusion into the sheet from its edge.</summary>
|
||||
public double Depth { get; set; } = ClampSettings.DefaultDepth;
|
||||
|
||||
/// <summary>Minutes to remove one clamp and put it back after cutting; never scaled.</summary>
|
||||
public double RemovalMinutes { get; set; } = ClampRail.DefaultRemovalMinutes;
|
||||
|
||||
/// <summary>Origin-side clamp centers along X, measured from the plate origin.</summary>
|
||||
public List<double> Centers { get; set; } = new();
|
||||
|
||||
/// <summary>Far-side clamp centers along X, measured from the plate origin.</summary>
|
||||
public List<double> FarSideCenters { get; set; } = new();
|
||||
|
||||
/// <summary>Origin-end clamp centers along Y, measured from the plate origin.</summary>
|
||||
public List<double> OriginEndCenters { get; set; } = new();
|
||||
|
||||
/// <summary>Far-end clamp centers along Y, measured from the plate origin.</summary>
|
||||
public List<double> FarEndCenters { get; set; } = new();
|
||||
|
||||
/// <summary>The <see cref="Units"/> matching a machine's <see cref="UnitSystem"/>.</summary>
|
||||
public static Units UnitsOf(UnitSystem units) =>
|
||||
units == UnitSystem.Millimeters ? Units.Millimeters : Units.Inches;
|
||||
|
||||
/// <summary>
|
||||
/// <paramref name="settings"/> converted to a machine in <paramref name="units"/>, e.g. for
|
||||
/// "Save as Machine Defaults" from a nest in other units or the clamps.json migration.
|
||||
/// </summary>
|
||||
public static MachineClamps FromSettings(ClampSettings settings, UnitSystem units)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(settings);
|
||||
var converted = settings.ConvertedTo(UnitsOf(units));
|
||||
return new MachineClamps
|
||||
{
|
||||
Enabled = converted.Enabled,
|
||||
Width = converted.Width,
|
||||
Depth = converted.Depth,
|
||||
RemovalMinutes = converted.RemovalMinutes,
|
||||
Centers = converted.Centers.ToList(),
|
||||
FarSideCenters = converted.FarSideCenters.ToList(),
|
||||
OriginEndCenters = converted.OriginEndCenters.ToList(),
|
||||
FarEndCenters = converted.FarEndCenters.ToList(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The rail as clamp settings in the machine's <paramref name="units"/>. An invalid size or
|
||||
/// removal time falls back to the default, and an invalid center is dropped, as when
|
||||
/// <see cref="ClampSettings.Parse"/> reads a file.
|
||||
/// </summary>
|
||||
public ClampSettings ToSettings(UnitSystem units)
|
||||
{
|
||||
var defaults = ClampSettings.FromRail(null, UnitsOf(units), Enabled);
|
||||
if (IsLength(Width) && Width > 0)
|
||||
defaults.Width = Width;
|
||||
if (IsLength(Depth) && Depth > 0)
|
||||
defaults.Depth = Depth;
|
||||
if (IsLength(RemovalMinutes) && RemovalMinutes >= 0)
|
||||
defaults.RemovalMinutes = RemovalMinutes;
|
||||
defaults.Centers = Valid(Centers);
|
||||
defaults.FarSideCenters = Valid(FarSideCenters);
|
||||
defaults.OriginEndCenters = Valid(OriginEndCenters);
|
||||
defaults.FarEndCenters = Valid(FarEndCenters);
|
||||
return defaults;
|
||||
}
|
||||
|
||||
private static double[] Valid(IEnumerable<double>? centers) =>
|
||||
(centers ?? Enumerable.Empty<double>()).Where(c => IsLength(c) && c >= 0).Distinct().Order().ToArray();
|
||||
|
||||
// Finite in either unit, so converting to an inch or millimeter nest cannot overflow.
|
||||
private static bool IsLength(double value) => double.IsFinite(value) && double.IsFinite(value * 25.4);
|
||||
}
|
||||
@@ -20,8 +20,18 @@ public class MachineConfig
|
||||
/// </summary>
|
||||
public SupportGridSettings? SupportGrid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The edge-clamp rail new nests assigned this machine receive, in <see cref="Units"/>; null
|
||||
/// until set (the machine then gives nests no clamps).
|
||||
/// </summary>
|
||||
public MachineClamps? Clamps { get; set; }
|
||||
|
||||
public MotionConfig GetMotionOrPlaceholder() => Motion ?? MotionConfig.Placeholder(Units);
|
||||
|
||||
/// <summary>The machine's clamps in its units; off, at the default clamp size, when it has none.</summary>
|
||||
public ClampSettings GetClampSettings() =>
|
||||
Clamps?.ToSettings(Units) ?? ClampSettings.FromRail(null, MachineClamps.UnitsOf(Units), enabled: false);
|
||||
|
||||
public ThicknessConfig? GetParameters(string material, double thickness) =>
|
||||
GetParameters(material, null, thickness);
|
||||
|
||||
|
||||
@@ -40,7 +40,10 @@ public static class SharedSettingKinds
|
||||
/// <summary>A post processor's settings, keyed by the post's type name.</summary>
|
||||
public const string Post = "post";
|
||||
|
||||
/// <summary>The <see cref="ClampSettings"/> new nests start from, under <see cref="DefaultKey"/>.</summary>
|
||||
/// <summary>
|
||||
/// The legacy <see cref="ClampSettings"/> new nests started from before each machine held its
|
||||
/// clamps, under <see cref="DefaultKey"/>; read only to copy them onto a machine once.
|
||||
/// </summary>
|
||||
public const string Clamps = "clamps";
|
||||
|
||||
/// <summary>The <see cref="NestDefaults"/> new nests start from, under <see cref="DefaultKey"/>.</summary>
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
using OpenNest.Data;
|
||||
|
||||
namespace OpenNest.Tests.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Each machine's own clamp rail (<see cref="MachineConfig.Clamps"/>, in the machine's units),
|
||||
/// the rail new nests copy from it, and the one-time copy of the legacy clamps.json onto a machine.
|
||||
/// </summary>
|
||||
public class MachineClampsTests : IDisposable
|
||||
{
|
||||
private readonly string _directory = Path.Combine(Path.GetTempPath(), "OpenNestTests", Guid.NewGuid().ToString());
|
||||
|
||||
public MachineClampsTests() => Directory.CreateDirectory(_directory);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(_directory))
|
||||
Directory.Delete(_directory, recursive: true);
|
||||
}
|
||||
|
||||
private static MachineClamps Rail(bool enabled = true) => new()
|
||||
{
|
||||
Enabled = enabled,
|
||||
Width = 1.5,
|
||||
Depth = 0.625,
|
||||
RemovalMinutes = 3,
|
||||
Centers = new List<double> { 6, 21, 36 },
|
||||
FarEndCenters = new List<double> { 10 },
|
||||
};
|
||||
|
||||
private static ClampSettings Legacy(Units units = Units.Inches) => new()
|
||||
{
|
||||
Enabled = true,
|
||||
Units = units,
|
||||
Width = 2,
|
||||
Depth = 0.5,
|
||||
RemovalMinutes = 4,
|
||||
Centers = new double[] { 12, 24 },
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void SaveMachine_RoundTripsTheClamps()
|
||||
{
|
||||
var provider = new LocalJsonProvider(_directory);
|
||||
var machine = new MachineConfig { Name = "Laser", Clamps = Rail() };
|
||||
|
||||
provider.SaveMachine(machine);
|
||||
var clamps = provider.GetMachine(machine.Id)!.Clamps!;
|
||||
|
||||
Assert.True(clamps.Enabled);
|
||||
Assert.Equal(1.5, clamps.Width);
|
||||
Assert.Equal(0.625, clamps.Depth);
|
||||
Assert.Equal(3, clamps.RemovalMinutes);
|
||||
Assert.Equal(new double[] { 6, 21, 36 }, clamps.Centers);
|
||||
Assert.Empty(clamps.FarSideCenters);
|
||||
Assert.Equal(new double[] { 10 }, clamps.FarEndCenters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MachineSavedBeforeClamps_LoadsWithoutClamps()
|
||||
{
|
||||
var provider = new LocalJsonProvider(_directory);
|
||||
var machine = new MachineConfig { Name = "Laser" };
|
||||
provider.SaveMachine(machine);
|
||||
|
||||
var loaded = provider.GetMachine(machine.Id)!;
|
||||
|
||||
Assert.Null(loaded.Clamps);
|
||||
Assert.Null(loaded.GetClampSettings().RailFor(Units.Inches));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MachineWithoutClamps_GivesNoRailAtTheDefaultSizeInItsUnits()
|
||||
{
|
||||
var machine = new MachineConfig { Units = UnitSystem.Millimeters };
|
||||
|
||||
var settings = machine.GetClampSettings();
|
||||
|
||||
Assert.False(settings.Enabled);
|
||||
Assert.Equal(Units.Millimeters, settings.Units);
|
||||
Assert.Equal(ClampSettings.DefaultWidth * 25.4, settings.Width, 9);
|
||||
Assert.Equal(0, settings.ClampCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisabledClamps_GiveNewNestsNoRail()
|
||||
{
|
||||
var machine = new MachineConfig { Clamps = Rail(enabled: false) };
|
||||
var nest = new Nest { Units = Units.Inches };
|
||||
|
||||
machine.GetClampSettings().ApplyToNewNest(nest);
|
||||
|
||||
Assert.Null(nest.PlateDefaults.ClampRail);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MillimeterMachine_GivesAnInchNestItsRailConverted()
|
||||
{
|
||||
var machine = new MachineConfig
|
||||
{
|
||||
Units = UnitSystem.Millimeters,
|
||||
Clamps = new MachineClamps
|
||||
{
|
||||
Enabled = true,
|
||||
Width = 38.1,
|
||||
Depth = 12.7,
|
||||
RemovalMinutes = 2,
|
||||
Centers = new List<double> { 254 },
|
||||
},
|
||||
};
|
||||
var nest = new Nest { Units = Units.Inches };
|
||||
|
||||
machine.GetClampSettings().ApplyToNewNest(nest);
|
||||
|
||||
var rail = nest.PlateDefaults.ClampRail!;
|
||||
Assert.Equal(1.5, rail.Width, 9);
|
||||
Assert.Equal(0.5, rail.Depth, 9);
|
||||
Assert.Equal(2, rail.RemovalMinutes);
|
||||
Assert.Equal(10, Assert.Single(rail.Clamps).Center, 9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EditingTheMachine_NeverMovesAnExistingNestsClamps()
|
||||
{
|
||||
var machine = new MachineConfig { Clamps = Rail() };
|
||||
var nest = new Nest { Units = Units.Inches };
|
||||
machine.GetClampSettings().ApplyToNewNest(nest);
|
||||
|
||||
machine.Clamps!.Centers = new List<double> { 50 };
|
||||
machine.Clamps.Width = 3;
|
||||
|
||||
var rail = nest.PlateDefaults.ClampRail!;
|
||||
Assert.Equal(1.5, rail.Width);
|
||||
Assert.Equal(new double[] { 6, 21, 36 },
|
||||
rail.Clamps.Where(c => c.Edge == ClampEdge.OriginSide).Select(c => c.Center));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidStoredValues_FallBackOrAreDropped()
|
||||
{
|
||||
var clamps = new MachineClamps
|
||||
{
|
||||
Enabled = true,
|
||||
Width = -1,
|
||||
Depth = double.NaN,
|
||||
RemovalMinutes = -2,
|
||||
Centers = new List<double> { 30, -5, double.PositiveInfinity, 10, 30 },
|
||||
FarSideCenters = null!,
|
||||
};
|
||||
|
||||
var settings = clamps.ToSettings(UnitSystem.Inches);
|
||||
|
||||
Assert.Equal(ClampSettings.DefaultWidth, settings.Width);
|
||||
Assert.Equal(ClampSettings.DefaultDepth, settings.Depth);
|
||||
Assert.Equal(ClampRail.DefaultRemovalMinutes, settings.RemovalMinutes);
|
||||
Assert.Equal(new double[] { 10, 30 }, settings.Centers);
|
||||
Assert.Empty(settings.FarSideCenters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromSettings_ConvertsToTheMachineUnits()
|
||||
{
|
||||
var clamps = MachineClamps.FromSettings(Legacy(), UnitSystem.Millimeters);
|
||||
|
||||
Assert.True(clamps.Enabled);
|
||||
Assert.Equal(50.8, clamps.Width, 9);
|
||||
Assert.Equal(12.7, clamps.Depth, 9);
|
||||
Assert.Equal(4, clamps.RemovalMinutes);
|
||||
Assert.Equal(new[] { 304.8, 609.6 }, clamps.Centers.Select(c => System.Math.Round(c, 9)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Migration_IsNeededOnlyWhileNoMachineHasClamps()
|
||||
{
|
||||
var a = new MachineConfig { Name = "A" };
|
||||
var b = new MachineConfig { Name = "B" };
|
||||
|
||||
Assert.True(LegacyClampMigration.IsNeeded(Legacy(), new[] { a, b }));
|
||||
Assert.False(LegacyClampMigration.IsNeeded(null, new[] { a, b }));
|
||||
Assert.False(LegacyClampMigration.IsNeeded(new ClampSettings { Enabled = true }, new[] { a, b }));
|
||||
Assert.False(LegacyClampMigration.IsNeeded(Legacy(), Array.Empty<MachineConfig>()));
|
||||
|
||||
LegacyClampMigration.Apply(Legacy(), a);
|
||||
|
||||
Assert.False(LegacyClampMigration.IsNeeded(Legacy(), new[] { a, b }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Migration_TargetsTheDefaultMachine()
|
||||
{
|
||||
var a = new MachineConfig { Name = "A" };
|
||||
var b = new MachineConfig { Name = "B" };
|
||||
|
||||
Assert.Same(b, LegacyClampMigration.Target(new[] { a, b }, b.Id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Migration_TargetsTheOnlyMachine()
|
||||
{
|
||||
var only = new MachineConfig { Name = "Only" };
|
||||
|
||||
Assert.Same(only, LegacyClampMigration.Target(new[] { only }, null));
|
||||
Assert.Same(only, LegacyClampMigration.Target(new[] { only }, Guid.NewGuid()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Migration_WithSeveralMachinesAndNoDefault_LeavesTheChoiceToTheUser()
|
||||
{
|
||||
var machines = new[] { new MachineConfig { Name = "A" }, new MachineConfig { Name = "B" } };
|
||||
|
||||
Assert.Null(LegacyClampMigration.Target(machines, null));
|
||||
Assert.Null(LegacyClampMigration.Target(machines, Guid.NewGuid()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Migration_CopiesTheLegacyFileIntoTheMachineUnits()
|
||||
{
|
||||
var path = Path.Combine(_directory, "clamps.json");
|
||||
Legacy().Save(path);
|
||||
var machine = new MachineConfig { Units = UnitSystem.Millimeters };
|
||||
|
||||
LegacyClampMigration.Apply(ClampSettings.Load(path), machine);
|
||||
|
||||
var rail = machine.GetClampSettings().RailFor(Units.Inches)!;
|
||||
Assert.Equal(2, rail.Width, 9);
|
||||
Assert.Equal(4, rail.RemovalMinutes);
|
||||
Assert.Equal(new[] { 12.0, 24.0 }, rail.Clamps.Select(c => System.Math.Round(c.Center, 9)));
|
||||
Assert.Equal(new[] { 304.8, 609.6 }, machine.Clamps!.Centers.Select(c => System.Math.Round(c, 9)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Data;
|
||||
using OpenNest.Forms;
|
||||
|
||||
namespace OpenNest.WinForms.Tests.Forms;
|
||||
|
||||
/// <summary>
|
||||
/// Machine clamps in the desktop: the Clamps tab of Machine Configuration edits a machine's rail,
|
||||
/// and Nest > Clamps saves its machine defaults to the nest's machine.
|
||||
/// </summary>
|
||||
public class MachineClampsFormsTests
|
||||
{
|
||||
[Fact]
|
||||
public void ClampsTab_EditsTheMachineRail()
|
||||
{
|
||||
RunSta(() =>
|
||||
{
|
||||
var directory = Path.Combine(Path.GetTempPath(), "OpenNestTests", Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(directory);
|
||||
try
|
||||
{
|
||||
var provider = new LocalJsonProvider(directory);
|
||||
var machine = new MachineConfig { Name = "Laser" };
|
||||
provider.SaveMachine(machine);
|
||||
|
||||
using var form = new MachineConfigForm(provider);
|
||||
var node = Find<TreeView>(form, "_tree").Nodes.Cast<TreeNode>().Single(n => n.Tag is MachineConfig);
|
||||
// A hidden tree raises no AfterSelect, so show the machine 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 = (MachineConfig)node.Tag;
|
||||
Assert.Null(shown.Clamps);
|
||||
|
||||
var tab = All<TabControl>(form).Single().TabPages.Cast<TabPage>().Single(p => p.Text == "Clamps");
|
||||
All<CheckBox>(tab).Single(c => c.Text == "Give new nests these clamps").Checked = true;
|
||||
var add = All<Button>(tab).Single(b => b.Text == "Add");
|
||||
All<System.Windows.Forms.NumericUpDown>(add.Parent).Single().Value = 24;
|
||||
Click(add);
|
||||
|
||||
Assert.NotNull(shown.Clamps);
|
||||
Assert.True(shown.Clamps!.Enabled);
|
||||
Assert.Equal(new double[] { 24 }, shown.Clamps.Centers);
|
||||
Assert.Equal(ClampSettings.DefaultWidth, shown.Clamps.Width);
|
||||
Assert.NotNull(shown.GetClampSettings().RailFor(Units.Inches));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(directory, recursive: true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClampsDialog_WithoutAMachine_CannotSaveMachineDefaults()
|
||||
{
|
||||
RunSta(() =>
|
||||
{
|
||||
using var form = new ClampsForm(null, Units.Inches, ClampSettings.Fallback, null, null);
|
||||
|
||||
Assert.False(All<Button>(form).Single(b => b.Text == "Save as Machine Defaults").Enabled);
|
||||
Assert.Contains(All<GroupBox>(form), g => g.Text == "Machine defaults (no machine assigned)");
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClampsDialog_SavesToTheNestsMachine_AndLoadsWhatItSaved()
|
||||
{
|
||||
RunSta(() =>
|
||||
{
|
||||
var rail = new ClampRail(1.5, 0.625, new[] { new RailClamp(ClampEdge.OriginSide, 6) });
|
||||
ClampSettings saved = null;
|
||||
using var form = new ClampsForm(rail, Units.Inches, ClampSettings.Fallback, "Fiber", s => saved = s);
|
||||
Assert.Contains(All<GroupBox>(form), g => g.Text == "Machine defaults (Fiber)");
|
||||
|
||||
All<CheckBox>(form).Single(c => c.Text == "Give new nests these clamps").Checked = true;
|
||||
Click(All<Button>(form).Single(b => b.Text == "Save as Machine Defaults"));
|
||||
|
||||
Assert.NotNull(saved);
|
||||
Assert.True(saved.Enabled);
|
||||
Assert.Equal(new double[] { 6 }, saved.Centers);
|
||||
|
||||
form.Editor.RemoveCenterAt(0);
|
||||
Click(All<Button>(form).Single(b => b.Text == "Load Machine Defaults"));
|
||||
Assert.Equal(6, Assert.Single(form.Editor.Clamps).Center);
|
||||
});
|
||||
}
|
||||
|
||||
// PerformClick does nothing on a form that is not shown.
|
||||
private static void Click(Button button) =>
|
||||
typeof(Control).GetMethod("OnClick", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!
|
||||
.Invoke(button, new object[] { EventArgs.Empty });
|
||||
|
||||
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.");
|
||||
}
|
||||
@@ -84,9 +84,9 @@ namespace OpenNest.Actions
|
||||
|
||||
private Units Units => Nest?.Units ?? Units.Inches;
|
||||
|
||||
// A nest without clamps takes its first clamp's size from the machine defaults.
|
||||
// A nest without clamps takes its first clamp's size from its machine's clamps.
|
||||
private ClampSettings Defaults =>
|
||||
defaults ??= MachineSettings.LoadSavedClampSettings(out _).ConvertedTo(Units);
|
||||
defaults ??= MachineSettings.LoadClampSettings(MachineSettings.FindMachine(Nest?.Machine)).ConvertedTo(Units);
|
||||
|
||||
private double ClampWidth => Rail?.Width ?? Defaults.Width;
|
||||
|
||||
|
||||
@@ -408,9 +408,9 @@ namespace OpenNest.Forms
|
||||
var jobName = txtJobName.Text.Trim();
|
||||
var nestsCreated = 0;
|
||||
var importErrors = new List<string>();
|
||||
// Read when the nests are created, warning (once per session) like New does.
|
||||
var clamps = MachineSettings.LoadClampSettings(this);
|
||||
// Read when the nests are created, like New does.
|
||||
var machine = MachineSettings.DefaultMachine(_defaults, MachineSettings.LoadMachines());
|
||||
var clamps = MachineSettings.LoadClampSettings(machine);
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
|
||||
@@ -10,13 +10,13 @@ namespace OpenNest.Forms;
|
||||
/// <summary>
|
||||
/// Nest > Clamps...: edits the current nest's machine clamp rail (identical clamps, each on
|
||||
/// one sheet edge with its center measured from the plate origin along it). OK applies the rail
|
||||
/// to every plate of this nest; Cancel discards those edits. Save as Machine Defaults writes
|
||||
/// clamps.json immediately and is not undone by Cancel. All decisions live in
|
||||
/// to every plate of this nest; Cancel discards those edits. Save as Machine Defaults writes the
|
||||
/// nest's machine configuration immediately and is not undone by Cancel. All decisions live in
|
||||
/// <see cref="ClampRailEditor"/>; this form only renders and forwards input.
|
||||
/// </summary>
|
||||
public sealed class ClampsForm : Form
|
||||
{
|
||||
private readonly ClampSettings machineDefaults;
|
||||
private ClampSettings machineDefaults;
|
||||
private readonly Action<ClampSettings> saveDefaults;
|
||||
private readonly NumericUpDown widthBox;
|
||||
private readonly NumericUpDown depthBox;
|
||||
@@ -30,11 +30,17 @@ public sealed class ClampsForm : Form
|
||||
private readonly CheckBox applyToNewNestsBox;
|
||||
private readonly Label savedLabel;
|
||||
|
||||
/// <param name="saveDefaults">Saves the machine defaults; this PC's clamp settings file when omitted.</param>
|
||||
public ClampsForm(ClampRail rail, Units units, ClampSettings machineDefaults, Action<ClampSettings> saveDefaults = null)
|
||||
/// <param name="machineDefaults">The clamps of the nest's machine, in any units.</param>
|
||||
/// <param name="machineName">The nest's machine, or null when none is assigned.</param>
|
||||
/// <param name="saveDefaults">
|
||||
/// Saves the clamps as the nest's machine's; null when the nest has no machine on file, which
|
||||
/// disables saving.
|
||||
/// </param>
|
||||
public ClampsForm(ClampRail rail, Units units, ClampSettings machineDefaults, string machineName,
|
||||
Action<ClampSettings> saveDefaults)
|
||||
{
|
||||
this.machineDefaults = machineDefaults ?? ClampSettings.Fallback;
|
||||
this.saveDefaults = saveDefaults ?? (settings => settings.Save(ClampSettings.DefaultPath));
|
||||
this.saveDefaults = saveDefaults;
|
||||
Editor = new ClampRailEditor(rail, units, this.machineDefaults.Enabled);
|
||||
var unit = UnitsHelper.GetShortString(units);
|
||||
|
||||
@@ -113,7 +119,7 @@ public sealed class ClampsForm : Form
|
||||
|
||||
var defaultsGroup = new GroupBox
|
||||
{
|
||||
Text = "Machine defaults (clamps.json)",
|
||||
Text = string.IsNullOrEmpty(machineName) ? "Machine defaults (no machine assigned)" : $"Machine defaults ({machineName})",
|
||||
Dock = DockStyle.Top,
|
||||
AutoSize = true,
|
||||
Padding = new Padding(8),
|
||||
@@ -140,10 +146,13 @@ public sealed class ClampsForm : Form
|
||||
{
|
||||
AutoSize = true,
|
||||
MaximumSize = new Size(430, 0),
|
||||
Text =
|
||||
"Save as Machine Defaults writes the file immediately; Cancel below does not undo "
|
||||
+ "it. OK and Cancel only affect this nest.",
|
||||
Text = saveDefaults == null
|
||||
? "Assign this nest a machine on file in Nest > Machine to save its clamps."
|
||||
: "Save as Machine Defaults writes the machine configuration immediately; Cancel below "
|
||||
+ "does not undo it. OK and Cancel only affect this nest.",
|
||||
};
|
||||
applyToNewNestsBox.Enabled = saveDefaults != null;
|
||||
saveDefaultsButton.Enabled = saveDefaults != null;
|
||||
defaultsLayout.Controls.Add(applyToNewNestsBox);
|
||||
defaultsLayout.Controls.Add(defaultsButtons);
|
||||
defaultsLayout.Controls.Add(savedLabel);
|
||||
@@ -270,6 +279,7 @@ public sealed class ClampsForm : Form
|
||||
{
|
||||
var settings = Editor.ToMachineDefaults();
|
||||
saveDefaults(settings);
|
||||
machineDefaults = settings;
|
||||
savedLabel.Text = settings.Enabled
|
||||
? $"Saved {DateTime.Now:t}: new nests get these {settings.ClampCount} clamps. Cancel does not undo this."
|
||||
: $"Saved {DateTime.Now:t}: new nests get no clamps. Cancel does not undo this.";
|
||||
|
||||
@@ -250,8 +250,8 @@ namespace OpenNest.Forms
|
||||
return;
|
||||
|
||||
var confirm = MessageBox.Show(
|
||||
"Changing units does not convert existing thickness, kerf, lead and "
|
||||
+ "motion values — they will keep their current numbers. Continue?",
|
||||
"Changing units does not convert existing thickness, kerf, lead, "
|
||||
+ "motion and clamp values — they will keep their current numbers. Continue?",
|
||||
"Change Units",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Warning
|
||||
@@ -267,9 +267,139 @@ namespace OpenNest.Forms
|
||||
AddField(layout, ref row, "Units:", unitsCombo);
|
||||
|
||||
group.Controls.Add(layout);
|
||||
var general = new Panel { Dock = DockStyle.Fill, AutoScroll = true };
|
||||
// Docked to the top, so the last added sits first.
|
||||
_detailPanel.Controls.Add(BuildMotionGroup(machine));
|
||||
_detailPanel.Controls.Add(group);
|
||||
general.Controls.Add(BuildMotionGroup(machine));
|
||||
general.Controls.Add(group);
|
||||
|
||||
var tabs = new TabControl { Dock = DockStyle.Fill };
|
||||
tabs.TabPages.Add(CreateTab("General", general));
|
||||
tabs.TabPages.Add(CreateTab("Clamps", BuildClampsTab(machine)));
|
||||
|
||||
var host = new Panel { Dock = DockStyle.Fill, Height = 420 };
|
||||
host.Controls.Add(tabs);
|
||||
_detailPanel.Controls.Add(host);
|
||||
}
|
||||
|
||||
// The machine's edge-clamp rail, which new nests assigned the machine copy. The first edit
|
||||
// stores the shown values (the default clamp size, off, when it has none) with the change.
|
||||
private Control BuildClampsTab(MachineConfig machine)
|
||||
{
|
||||
var shown = machine.GetClampSettings();
|
||||
var editor = new ClampRailEditor(null, shown.Units, shown.Enabled);
|
||||
editor.LoadMachineDefaults(shown);
|
||||
|
||||
MachineClamps Clamps() => machine.Clamps ??= new MachineClamps
|
||||
{
|
||||
Enabled = shown.Enabled,
|
||||
Width = shown.Width,
|
||||
Depth = shown.Depth,
|
||||
RemovalMinutes = shown.RemovalMinutes,
|
||||
};
|
||||
|
||||
void Set(Action<MachineClamps> apply)
|
||||
{
|
||||
var clamps = Clamps();
|
||||
apply(clamps);
|
||||
var edges = new ClampSettings();
|
||||
edges.SetClamps(editor.Clamps);
|
||||
clamps.Centers = edges.Centers.ToList();
|
||||
clamps.FarSideCenters = edges.FarSideCenters.ToList();
|
||||
clamps.OriginEndCenters = edges.OriginEndCenters.ToList();
|
||||
clamps.FarEndCenters = edges.FarEndCenters.ToList();
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
var layout = CreateDetailLayout();
|
||||
var row = 0;
|
||||
var enabledBox = new CheckBox
|
||||
{
|
||||
Text = "Give new nests these clamps",
|
||||
AutoSize = true,
|
||||
Checked = shown.Enabled,
|
||||
};
|
||||
enabledBox.CheckedChanged += (s, e) => Set(c => c.Enabled = enabledBox.Checked);
|
||||
AddField(layout, ref row, "New nests:", enabledBox);
|
||||
AddField(layout, ref row, "Clamp width:",
|
||||
CreateLabeledNumeric(shown.Width, v => Set(c => c.Width = v), 4, LengthUnitSuffix, 1000, 0.125m));
|
||||
AddField(layout, ref row, "Clamp depth:",
|
||||
CreateLabeledNumeric(shown.Depth, v => Set(c => c.Depth = v), 4, LengthUnitSuffix, 1000, 0.125m));
|
||||
AddField(layout, ref row, "Removal time:",
|
||||
CreateLabeledNumeric(shown.RemovalMinutes, v => Set(c => c.RemovalMinutes = v), 2, _ => "min per clamp", 1000, 0.5m));
|
||||
|
||||
var centersGroup = new GroupBox
|
||||
{
|
||||
Text = $"Clamp centers, measured from the plate origin ({LengthUnitSuffix(null)})",
|
||||
Dock = DockStyle.Fill,
|
||||
Padding = new Padding(8),
|
||||
};
|
||||
var centersList = new ListBox { Dock = DockStyle.Fill, IntegralHeight = false };
|
||||
var entry = new FlowLayoutPanel { Dock = DockStyle.Bottom, AutoSize = true, WrapContents = false };
|
||||
var edgeBox = new ComboBox { DropDownStyle = ComboBoxStyle.DropDownList, Width = 120 };
|
||||
foreach (var edge in ClampEdgeText.All)
|
||||
edgeBox.Items.Add(ClampEdgeText.Name(edge));
|
||||
edgeBox.SelectedIndex = 0;
|
||||
var centerBox = CreateNumericBox(0, _ => { }, 4, 100000, 0.125m);
|
||||
var addButton = new Button { Text = "Add", AutoSize = true };
|
||||
var removeButton = new Button { Text = "Remove", AutoSize = true, Enabled = false };
|
||||
entry.Controls.Add(edgeBox);
|
||||
entry.Controls.Add(new Label { Text = "Center:", AutoSize = true, Padding = new Padding(0, 6, 0, 0) });
|
||||
entry.Controls.Add(centerBox);
|
||||
entry.Controls.Add(addButton);
|
||||
entry.Controls.Add(removeButton);
|
||||
centersGroup.Controls.Add(centersList);
|
||||
centersGroup.Controls.Add(entry);
|
||||
|
||||
void RefreshCenters()
|
||||
{
|
||||
centersList.BeginUpdate();
|
||||
centersList.Items.Clear();
|
||||
foreach (var clamp in editor.Clamps)
|
||||
centersList.Items.Add(
|
||||
$"{ClampEdgeText.Name(clamp.Edge)}: {clamp.Center.ToString("0.####", System.Globalization.CultureInfo.CurrentCulture)}");
|
||||
centersList.EndUpdate();
|
||||
removeButton.Enabled = centersList.SelectedIndex >= 0;
|
||||
}
|
||||
|
||||
addButton.Click += (s, e) =>
|
||||
{
|
||||
var clamp = new RailClamp(ClampEdgeText.All[edgeBox.SelectedIndex], (double)centerBox.Value);
|
||||
if (!editor.TryAddClamp(clamp, out var error))
|
||||
{
|
||||
MessageBox.Show(this, error, Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
Set(_ => { });
|
||||
RefreshCenters();
|
||||
};
|
||||
removeButton.Click += (s, e) =>
|
||||
{
|
||||
var index = centersList.SelectedIndex;
|
||||
editor.RemoveCenterAt(index);
|
||||
Set(_ => { });
|
||||
RefreshCenters();
|
||||
if (centersList.Items.Count > 0)
|
||||
centersList.SelectedIndex = System.Math.Min(index, centersList.Items.Count - 1);
|
||||
};
|
||||
centersList.SelectedIndexChanged += (s, e) => removeButton.Enabled = centersList.SelectedIndex >= 0;
|
||||
RefreshCenters();
|
||||
|
||||
var note = new Label
|
||||
{
|
||||
Dock = DockStyle.Top,
|
||||
AutoSize = true,
|
||||
MaximumSize = new Size(560, 0),
|
||||
ForeColor = SystemColors.GrayText,
|
||||
Padding = new Padding(4, 0, 0, 8),
|
||||
Text = "New nests assigned this machine copy these clamps. Changing them here never "
|
||||
+ "moves the clamps of existing nests; use Load Machine Defaults in Nest > Clamps for that.",
|
||||
};
|
||||
|
||||
var panel = new Panel { Dock = DockStyle.Fill, Padding = new Padding(10) };
|
||||
panel.Controls.Add(centersGroup);
|
||||
panel.Controls.Add(note);
|
||||
panel.Controls.Add(layout);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private GroupBox BuildMotionGroup(MachineConfig machine)
|
||||
|
||||
@@ -11,15 +11,13 @@ using OpenNest.IO;
|
||||
namespace OpenNest.Forms
|
||||
{
|
||||
/// <summary>
|
||||
/// The desktop entry points for the persisted machines, machine clamp settings and nest
|
||||
/// The desktop entry points for the persisted machines (with their clamps) and nest
|
||||
/// defaults. In Database mode each comes from the nest server once it holds a shared copy
|
||||
/// (<see cref="SharedSettings"/>), otherwise from this PC's files. The warning variants
|
||||
/// report unusable settings once per session.
|
||||
/// </summary>
|
||||
internal static class MachineSettings
|
||||
{
|
||||
private static bool clampSettingsWarned;
|
||||
|
||||
/// <summary>
|
||||
/// The machine configurations edited in Tools > Machine Configuration: the server's when
|
||||
/// it holds any, otherwise this PC's. <paramref name="refresh"/> re-reads the server first.
|
||||
@@ -66,37 +64,100 @@ namespace OpenNest.Forms
|
||||
/// <summary>Every material and grade on file across the readable machine configurations.</summary>
|
||||
public static IEnumerable<MaterialConfig> LoadMaterials() =>
|
||||
LoadMachines().SelectMany(machine => machine.Materials);
|
||||
|
||||
private static bool defaultsWarned;
|
||||
private static bool clampMigrationAsked;
|
||||
|
||||
/// <summary>
|
||||
/// Loads the machine clamp settings for creating new nests (New, BOM import); an
|
||||
/// unreadable or partly invalid file warns once per session, because the new nest's
|
||||
/// clamps may not match the machine.
|
||||
/// The clamps new nests assigned <paramref name="machine"/> receive (New, BOM import, Load
|
||||
/// Machine Defaults); off when there is no machine or it has no clamps.
|
||||
/// </summary>
|
||||
public static ClampSettings LoadClampSettings(IWin32Window owner)
|
||||
{
|
||||
var settings = LoadSavedClampSettings(out var status);
|
||||
public static ClampSettings LoadClampSettings(MachineConfig machine) =>
|
||||
machine?.GetClampSettings() ?? ClampSettings.Fallback;
|
||||
|
||||
if (
|
||||
status is ClampSettingsStatus.Invalid or ClampSettingsStatus.Partial
|
||||
&& !clampSettingsWarned
|
||||
)
|
||||
/// <summary>Saves <paramref name="settings"/> as the clamps of the machine with <paramref name="machineId"/>.</summary>
|
||||
public static void SaveClampSettings(Guid machineId, ClampSettings settings)
|
||||
{
|
||||
var provider = CreateMachineProvider();
|
||||
var machine = provider.GetMachine(machineId)
|
||||
?? throw new IOException("The machine configuration is no longer on file.");
|
||||
machine.Clamps = MachineClamps.FromSettings(settings, machine.Units);
|
||||
provider.SaveMachine(machine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the legacy clamps.json (or the server's shared copy) onto a machine once, while
|
||||
/// no machine has clamps of its own: the nest defaults' machine, or the only machine. With
|
||||
/// several machines and no default the user picks one, asked once per session.
|
||||
/// </summary>
|
||||
public static void MigrateLegacyClamps(IWin32Window owner)
|
||||
{
|
||||
try
|
||||
{
|
||||
clampSettingsWarned = true;
|
||||
var problem =
|
||||
status == ClampSettingsStatus.Invalid
|
||||
? "could not be read, so new nests have no clamps"
|
||||
: "have invalid values that were ignored, so new nests may be missing clamps";
|
||||
var source = SharedClamps() != null ? "on the nest server" : ClampSettings.DefaultPath;
|
||||
MessageBox.Show(
|
||||
owner,
|
||||
$"The clamp settings {problem}:\n{source}\n\nCheck the clamps in Nest > Clamps and use Save as Machine Defaults to rewrite them.",
|
||||
"Clamps",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning
|
||||
);
|
||||
var legacy = LoadLegacyClampSettings();
|
||||
var provider = CreateMachineProvider();
|
||||
var machines = ReadMachines(() => provider)
|
||||
.OrderBy(m => m.Name, StringComparer.CurrentCultureIgnoreCase)
|
||||
.ToList();
|
||||
if (!LegacyClampMigration.IsNeeded(legacy, machines))
|
||||
return;
|
||||
|
||||
var target = LegacyClampMigration.Target(machines, LoadSavedNestDefaults(out _).MachineId);
|
||||
if (target == null)
|
||||
{
|
||||
if (clampMigrationAsked)
|
||||
return;
|
||||
clampMigrationAsked = true;
|
||||
target = AskClampMachine(owner, machines);
|
||||
if (target == null)
|
||||
return;
|
||||
}
|
||||
|
||||
LegacyClampMigration.Apply(legacy, target);
|
||||
provider.SaveMachine(target);
|
||||
}
|
||||
return settings;
|
||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
|
||||
{
|
||||
MessageBox.Show(owner,
|
||||
$"The clamp settings could not be copied to a machine:\n\n{ex.Message}\n\n"
|
||||
+ "New nests get no clamps until a machine has them (Tools > Machine Configuration).",
|
||||
"Clamps", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
// The pre-machine clamp settings: the server's shared copy once it holds one, otherwise
|
||||
// this PC's clamps.json; null when neither exists.
|
||||
private static ClampSettings LoadLegacyClampSettings()
|
||||
{
|
||||
if (SharedSettings.Find(SharedSettingKinds.Clamps, SharedSettingKinds.DefaultKey) is { } shared)
|
||||
return ClampSettings.Parse(shared.Json, out _);
|
||||
return File.Exists(ClampSettings.DefaultPath) ? ClampSettings.Load(ClampSettings.DefaultPath) : null;
|
||||
}
|
||||
|
||||
private static MachineConfig AskClampMachine(IWin32Window owner, IReadOnlyList<MachineConfig> machines)
|
||||
{
|
||||
var page = new TaskDialogPage
|
||||
{
|
||||
Caption = "Clamps",
|
||||
Heading = "Which machine are these clamps on?",
|
||||
Text = "Clamps are now kept with each machine. Choose the machine that gets the clamps "
|
||||
+ "saved before, or choose later in Tools > Machine Configuration. Until then new "
|
||||
+ "nests get no clamps.",
|
||||
Icon = TaskDialogIcon.Information,
|
||||
AllowCancel = true,
|
||||
};
|
||||
foreach (var machine in machines)
|
||||
page.RadioButtons.Add(new TaskDialogRadioButton(machine.Name) { Tag = machine });
|
||||
page.RadioButtons[0].Checked = true;
|
||||
var copy = new TaskDialogButton("Copy Clamps");
|
||||
page.Buttons.Add(copy);
|
||||
page.Buttons.Add(new TaskDialogButton("Not Now"));
|
||||
|
||||
var handle = owner?.Handle ?? IntPtr.Zero;
|
||||
var result = handle == IntPtr.Zero ? TaskDialog.ShowDialog(page) : TaskDialog.ShowDialog(handle, page);
|
||||
return result == copy
|
||||
? page.RadioButtons.FirstOrDefault(b => b.Checked)?.Tag as MachineConfig
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -189,29 +250,6 @@ namespace OpenNest.Forms
|
||||
provider.SaveMachine(machine);
|
||||
}
|
||||
|
||||
/// <summary>The single desktop entry point for the persisted machine clamp settings.</summary>
|
||||
public static ClampSettings LoadSavedClampSettings(out ClampSettingsStatus status) =>
|
||||
SharedClamps() is { } shared
|
||||
? ClampSettings.Parse(shared.Json, out status)
|
||||
: ClampSettings.Load(ClampSettings.DefaultPath, out status);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the clamp settings new nests start from where they are read: the server once it
|
||||
/// holds them, otherwise this PC's file. Throws <see cref="IOException"/> when the server
|
||||
/// cannot take them.
|
||||
/// </summary>
|
||||
public static void SaveClampSettings(ClampSettings settings)
|
||||
{
|
||||
if (SharedSettings.Current() is { } current
|
||||
&& current.Get(SharedSettingKinds.Clamps, SharedSettingKinds.DefaultKey) != null)
|
||||
current.Save(SharedSettingKinds.Clamps, SharedSettingKinds.DefaultKey, settings.ToJson());
|
||||
else
|
||||
settings.Save(ClampSettings.DefaultPath);
|
||||
}
|
||||
|
||||
private static SharedSettingRecord SharedClamps() =>
|
||||
SharedSettings.Find(SharedSettingKinds.Clamps, SharedSettingKinds.DefaultKey);
|
||||
|
||||
/// <summary>
|
||||
/// Loads the persisted nest defaults (see <see cref="LoadSavedNestDefaults"/>);
|
||||
/// a corrupt or unreadable file warns once per session.
|
||||
|
||||
@@ -135,8 +135,12 @@ namespace OpenNest.Forms
|
||||
return;
|
||||
}
|
||||
|
||||
var defaults = MachineSettings.LoadSavedClampSettings(out _);
|
||||
using var form = new ClampsForm(nest.PlateDefaults.ClampRail, nest.Units, defaults, MachineSettings.SaveClampSettings);
|
||||
var machine = MachineSettings.FindMachine(nest.Machine);
|
||||
Action<ClampSettings> save = machine == null
|
||||
? null
|
||||
: settings => MachineSettings.SaveClampSettings(machine.Id, settings);
|
||||
using var form = new ClampsForm(nest.PlateDefaults.ClampRail, nest.Units, MachineSettings.LoadClampSettings(machine),
|
||||
machine?.Name ?? nest.Machine?.Name, save);
|
||||
if (form.ShowDialog(this) != DialogResult.OK)
|
||||
return;
|
||||
|
||||
|
||||
@@ -213,8 +213,13 @@ namespace OpenNest.Forms
|
||||
mnuNestPost.Visible = mnuNestPost.DropDownItems.Count > 0;
|
||||
postButton.Visible = postButton.DropDownItems.Count > 0;
|
||||
|
||||
// Database mode: offer this PC's settings to a server that has none yet.
|
||||
Shown += (_, _) => SharedSettings.OfferUpload(this, posts.PostProcessors);
|
||||
// Database mode: offer this PC's settings to a server that has none yet; then move
|
||||
// the clamps saved before machines held them onto a machine, once.
|
||||
Shown += (_, _) =>
|
||||
{
|
||||
SharedSettings.OfferUpload(this, posts.PostProcessors);
|
||||
MachineSettings.MigrateLegacyClamps(this);
|
||||
};
|
||||
|
||||
if (posts.Failures.Count > 0)
|
||||
Shown += (_, _) => MessageBox.Show(this,
|
||||
|
||||
@@ -40,8 +40,9 @@ namespace OpenNest.Forms
|
||||
var defaults = MachineSettings.LoadNestDefaults();
|
||||
(units is { } converted ? defaults.ConvertedTo(converted) : defaults).ApplyTo(nest);
|
||||
// After the defaults so the rail and grid are converted to the new nest's units.
|
||||
MachineSettings.LoadClampSettings(owner).ApplyToNewNest(nest);
|
||||
MachineSettings.AssignMachine(nest, MachineSettings.DefaultMachine(defaults, MachineSettings.LoadMachines()));
|
||||
var machine = MachineSettings.DefaultMachine(defaults, MachineSettings.LoadMachines());
|
||||
MachineSettings.LoadClampSettings(machine).ApplyToNewNest(nest);
|
||||
MachineSettings.AssignMachine(nest, machine);
|
||||
return nest;
|
||||
}
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ namespace OpenNest.Forms
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the server holds none of this PC's machines, post settings, clamp settings or nest
|
||||
/// When the server holds none of this PC's machines (with their clamps), post settings or nest
|
||||
/// defaults, offers to upload them so every workstation uses them. Documents the server
|
||||
/// already holds are never replaced.
|
||||
/// </summary>
|
||||
@@ -200,10 +200,6 @@ namespace OpenNest.Forms
|
||||
if (current.Get(SharedSettingKinds.Post, PostKey(post)) == null)
|
||||
uploads.Add((SharedSettingKinds.Post, PostKey(post), post.SerializeConfig(), $"Post settings: {post.Name}"));
|
||||
}
|
||||
if (current.Get(SharedSettingKinds.Clamps, SharedSettingKinds.DefaultKey) == null
|
||||
&& File.Exists(ClampSettings.DefaultPath))
|
||||
uploads.Add((SharedSettingKinds.Clamps, SharedSettingKinds.DefaultKey,
|
||||
ClampSettings.Load(ClampSettings.DefaultPath).ToJson(), "Clamp settings"));
|
||||
if (current.Get(SharedSettingKinds.NestDefaults, SharedSettingKinds.DefaultKey) == null
|
||||
&& File.Exists(NestDefaults.DefaultPath))
|
||||
uploads.Add((SharedSettingKinds.NestDefaults, SharedSettingKinds.DefaultKey,
|
||||
|
||||
+23
-10
@@ -66,18 +66,31 @@ the origin: the origin side and far side run along X, the origin end and far
|
||||
end along Y. **Nest > Clamps...** edits the current nest's rail as a list, and
|
||||
**Plate > Edit Clamps** edits it on the plate view (below). **OK** applies it to every plate of the nest (**Cancel** discards
|
||||
the edits); removing every center removes the clamps. **Load Machine Defaults**
|
||||
only fills the dialog. **Save as Machine Defaults** writes
|
||||
`%APPDATA%\OpenNest\clamps.json` (or, in Database mode once the server holds them, the
|
||||
[shared clamp settings](shared-settings.md)) immediately, with the clamps, their units and
|
||||
whether new nests receive them; Cancel does not undo that write. The dialog
|
||||
only fills the dialog with the clamps of the nest's machine (**Nest > Machine**).
|
||||
**Save as Machine Defaults** writes the clamps, and whether new nests receive them,
|
||||
into that machine's configuration immediately (in Database mode, the
|
||||
[shared machine](shared-settings.md) once the server holds it); Cancel does not undo
|
||||
that write. A nest without a machine on file cannot save its clamps. The dialog
|
||||
refuses to open while nesting or a plate action is running.
|
||||
|
||||
Clamps are off by default. Until `clamps.json` is saved with clamps enabled and
|
||||
at least one center, New and BOM import create nests without clamps exactly as
|
||||
before. When enabled, a new nest gets the rail converted to its units. A missing
|
||||
file keeps clamps off; an unreadable file, or one with ignored invalid values
|
||||
(a bad clamp center is dropped), warns once per session that new nests may not
|
||||
match the machine.
|
||||
Clamps are physical fixtures of a machine, so each machine keeps its own rail on the
|
||||
**Clamps** tab of **Tools > Machine Configuration**: whether new nests get it, the
|
||||
clamp width and depth, the minutes to remove one clamp, and the clamp centers, all in
|
||||
the machine's units. New and BOM import give a new nest the rail of the machine they
|
||||
assign it (the **Tools > Nest Defaults** machine, or the first by name), converted to
|
||||
the nest's units. A nest keeps its own copy: editing a machine's clamps, or assigning a
|
||||
nest another machine, never moves the clamps of an existing nest; use **Load Machine
|
||||
Defaults** for that. Clamps are off by default: a machine without clamps, or with them
|
||||
switched off or without a center, gives new nests none. A stored size or removal time
|
||||
that is not valid falls back to the default and an invalid center is dropped.
|
||||
|
||||
Before machines held clamps, one `%APPDATA%\OpenNest\clamps.json` (or the server's
|
||||
shared clamp settings) served every machine. At startup, while no machine has clamps of
|
||||
its own, OpenNest copies that rail once into the Nest Defaults machine, or the only
|
||||
machine, converted to the machine's units. With several machines and no default it asks
|
||||
which machine has those clamps (once per session; **Not Now** leaves new nests without
|
||||
clamps until a machine has some). Once any machine has clamps the old file is no longer
|
||||
read; it is left in place.
|
||||
|
||||
Every plate follows the nest's rail at its own size and quadrant: clamps wholly
|
||||
beyond a shorter sheet are left off it, one still reaching onto the sheet keeps
|
||||
|
||||
@@ -118,8 +118,9 @@ utilization, cut sequence or CNC output. Files without the key load with no
|
||||
zones, and an individually malformed entry (blank id, non-positive size) is
|
||||
dropped while the rest of the nest loads.
|
||||
|
||||
`plateDefaults.clampRail` optionally stores the nest's machine clamp rail:
|
||||
`width` (along the edge), `depth` (into the sheet) and `centers` (X positions
|
||||
`plateDefaults.clampRail` optionally stores the nest's machine clamp rail, the nest's
|
||||
own copy (a new nest copies it from its `machine`'s clamps; later machine edits do not
|
||||
change it): `width` (along the edge), `depth` (into the sheet) and `centers` (X positions
|
||||
measured from the plate origin along the origin-side edge). Clamps on the other
|
||||
edges are listed in optional `farSideCenters` (X, along the edge across the
|
||||
sheet's width from the origin), `originEndCenters` (Y, along the edge through
|
||||
|
||||
@@ -6,9 +6,9 @@ building nests and posts the same way:
|
||||
|
||||
| Kind | Key | What it holds | This PC's own copy |
|
||||
|---|---|---|---|
|
||||
| `machine` | machine id | A Tools > Machine Configuration entry: materials, grades, thicknesses, leads, kerf, cut-offs, motion limits, plate sizes, support grid | `%APPDATA%\OpenNest\Machines\<id>.json` |
|
||||
| `machine` | machine id | A Tools > Machine Configuration entry: materials, grades, thicknesses, leads, kerf, cut-offs, motion limits, plate sizes, support grid, clamps | `%APPDATA%\OpenNest\Machines\<id>.json` |
|
||||
| `post` | post type name, e.g. `OpenNest.Posts.CincinnatiCIFiber.CIFiberPostProcessor` | A post's settings, as its JSON file holds them | `<post>.json` beside the post DLL in `Posts\` |
|
||||
| `clamps` | `default` | Machine clamp defaults (Save as Machine Defaults) | `%APPDATA%\OpenNest\clamps.json` |
|
||||
| `clamps` | `default` | Legacy clamp settings, read only to copy them onto a machine once (machines now hold their clamps) | `%APPDATA%\OpenNest\clamps.json` |
|
||||
| `nest-defaults` | `default` | Tools > Nest Defaults: units, sheet size, quadrant, spacing, default machine | `%APPDATA%\OpenNest\defaults.json` |
|
||||
|
||||
File mode, the console, MCP and API front ends use only this PC's copies.
|
||||
@@ -21,7 +21,7 @@ one), posts per post, clamps and nest defaults as single documents. Saves go whe
|
||||
settings were read, so editing a kind the server does not hold yet changes only this PC.
|
||||
|
||||
When the desktop opens in Database mode and the server lacks any of this PC's machines (all
|
||||
of them, when it holds none), post settings, clamp settings or nest defaults, it lists them
|
||||
of them, when it holds none), post settings or nest defaults, it lists them
|
||||
and offers to upload them. The first workstation to answer Yes seeds the server; a document
|
||||
the server already holds is never replaced, so later workstations only add what is missing
|
||||
(a post installed on just one PC, for example). Declining keeps that PC on its own copies and
|
||||
@@ -45,7 +45,8 @@ saved first the server refuses it and the desktop takes their copy:
|
||||
even though the Cincinnati post records the material library the nest selected.
|
||||
- Machine Configuration: the unsaved machines stay pending; reopen the window to see the
|
||||
other changes.
|
||||
- Clamps, nest defaults, support grid: the save reports the conflict.
|
||||
- Nest > Clamps and Nest > Support Grid machine defaults, nest defaults: the save reports
|
||||
the conflict.
|
||||
|
||||
The window title of Tools > Machine Configuration says whether it edits the shared machines,
|
||||
this PC's (not shared yet), or a read-only copy. Tools > Nest Defaults says the same beside
|
||||
|
||||
Reference in new issue
Block a user