mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-14 09:15:06 -05:00
The rules behind the WinForms MachineSettings and SharedSettings move into OpenNest.Desktop as MachineSettingsSource and SharedSettingsSource, so the Avalonia app reads machines, clamps and nest defaults the same way: in Database mode the nest server's copy once it holds one (machines as a whole, nest defaults and clamps as single documents), otherwise this PC's files; the copy is re-read after two minutes (one while offline), and while the server is unreachable the last copy read is used, read-only. Legacy clamps still move onto the defaults' or the only machine once, asking at most once per session, and cutting settings edited for a nest still save onto the machine's thickness, or this PC (stamped for that job when the machine cannot be saved). Nothing in the shared code shows UI: it returns the warning texts, takes the clamp-machine question as a delegate and reads the DefaultUnit and CuttingParametersJson settings through IMachineUserSettings (with a user-settings.json implementation under the WinForms names). Cut-off defaults take CutOffSettings instead of a PlateView. MachineSettings and SharedSettings stay in the WinForms project as thin wrappers with the same members, showing the same dialogs and reading Properties.Settings and NestStorage as before, so their callers are unchanged. Tests cover File vs Database mode precedence, the offline read-only copy and its once-per-session warning, staleness and retry timing, uploads, post settings, legacy clamp migration, the cutting-settings save and the user-settings file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
456 lines
21 KiB
C#
456 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using OpenNest.CNC.CuttingStrategy;
|
|
using OpenNest.Data;
|
|
using OpenNest.IO;
|
|
|
|
namespace OpenNest.Desktop;
|
|
|
|
/// <summary>
|
|
/// The two per-user settings the machine entry points read: the legacy default unit, used
|
|
/// when the nest defaults give none, and this PC's last cutting settings (saving one persists it).
|
|
/// </summary>
|
|
public interface IMachineUserSettings
|
|
{
|
|
/// <summary>The DefaultUnit setting.</summary>
|
|
Units DefaultUnit { get; }
|
|
|
|
/// <summary>The CuttingParametersJson setting; setting it saves it.</summary>
|
|
string CuttingParametersJson { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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="SharedSettingsSource"/>), otherwise from this PC's files. Nothing here shows UI:
|
|
/// the warning texts it returns are shown by the front end, at most once per session where
|
|
/// noted.
|
|
/// </summary>
|
|
public sealed class MachineSettingsSource
|
|
{
|
|
private readonly IMachineUserSettings user;
|
|
private readonly string machinesDirectory;
|
|
private readonly string nestDefaultsPath;
|
|
private readonly string legacyClampsPath;
|
|
private readonly string legacySupportGridPath;
|
|
private bool defaultsWarned;
|
|
private bool clampMigrationAsked;
|
|
|
|
/// <param name="shared">Where shared copies come from.</param>
|
|
/// <param name="user">The per-user settings this reads and saves.</param>
|
|
/// <param name="machinesDirectory">This PC's machines; defaults to %APPDATA%\OpenNest\Machines.</param>
|
|
/// <param name="nestDefaultsPath">This PC's nest defaults; defaults to <see cref="NestDefaults.DefaultPath"/>.</param>
|
|
/// <param name="legacyClampsPath">The pre-machine clamps.json; defaults to <see cref="ClampSettings.DefaultPath"/>.</param>
|
|
/// <param name="legacySupportGridPath">The pre-machine support-grid.json; defaults to <see cref="SupportGridSettings.DefaultPath"/>.</param>
|
|
public MachineSettingsSource(
|
|
SharedSettingsSource shared,
|
|
IMachineUserSettings user,
|
|
string machinesDirectory = null,
|
|
string nestDefaultsPath = null,
|
|
string legacyClampsPath = null,
|
|
string legacySupportGridPath = null)
|
|
{
|
|
Shared = shared ?? throw new ArgumentNullException(nameof(shared));
|
|
this.user = user ?? throw new ArgumentNullException(nameof(user));
|
|
this.machinesDirectory = machinesDirectory ?? DefaultMachinesDirectory;
|
|
this.nestDefaultsPath = nestDefaultsPath ?? NestDefaults.DefaultPath;
|
|
this.legacyClampsPath = legacyClampsPath ?? ClampSettings.DefaultPath;
|
|
this.legacySupportGridPath = legacySupportGridPath ?? SupportGridSettings.DefaultPath;
|
|
}
|
|
|
|
/// <summary>%APPDATA%\OpenNest\Machines.</summary>
|
|
public static string DefaultMachinesDirectory => Path.Combine(
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
|
"OpenNest",
|
|
"Machines");
|
|
|
|
/// <summary>Where shared copies come from.</summary>
|
|
public SharedSettingsSource Shared { get; }
|
|
|
|
/// <summary>This PC's nest defaults file.</summary>
|
|
public string NestDefaultsPath => nestDefaultsPath;
|
|
|
|
/// <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.
|
|
/// </summary>
|
|
public IDataProvider CreateMachineProvider(bool refresh = false) =>
|
|
Shared.Holding(SharedSettingKinds.Machine, refresh) is { } shared
|
|
? new SharedMachineProvider(shared)
|
|
: CreateLocalMachineProvider();
|
|
|
|
/// <summary>This PC's machine configurations, with the built-in ones written on first use.</summary>
|
|
public LocalJsonProvider CreateLocalMachineProvider()
|
|
{
|
|
var provider = new LocalJsonProvider(machinesDirectory);
|
|
provider.EnsureDefaults();
|
|
return provider;
|
|
}
|
|
|
|
/// <summary>Every readable machine configuration, for choosing acceleration limits.</summary>
|
|
public List<MachineConfig> LoadMachines() => ReadMachines(() => CreateMachineProvider());
|
|
|
|
/// <summary>Every readable machine configuration on this PC, shared or not, for uploading.</summary>
|
|
public List<MachineConfig> LoadLocalMachines() => ReadMachines(CreateLocalMachineProvider);
|
|
|
|
private static List<MachineConfig> ReadMachines(Func<IDataProvider> createProvider)
|
|
{
|
|
var machines = new List<MachineConfig>();
|
|
try
|
|
{
|
|
var provider = createProvider();
|
|
foreach (var summary in provider.GetMachines())
|
|
{
|
|
if (provider.GetMachine(summary.Id) is { } machine)
|
|
machines.Add(machine);
|
|
}
|
|
}
|
|
catch (IOException) { }
|
|
catch (UnauthorizedAccessException) { }
|
|
return machines;
|
|
}
|
|
|
|
/// <summary>Every material and grade on file across the readable machine configurations.</summary>
|
|
public IEnumerable<MaterialConfig> LoadMaterials() =>
|
|
LoadMachines().SelectMany(machine => machine.Materials);
|
|
|
|
/// <summary>
|
|
/// 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(MachineConfig machine) =>
|
|
machine?.GetClampSettings() ?? ClampSettings.Fallback;
|
|
|
|
/// <summary>Saves <paramref name="settings"/> as the clamps of the machine with <paramref name="machineId"/>.</summary>
|
|
public 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 <paramref name="askMachine"/> picks one (null for not
|
|
/// now), asked once per session. Returns what to tell the user when the copy failed, or null.
|
|
/// </summary>
|
|
public string MigrateLegacyClamps(Func<IReadOnlyList<MachineConfig>, MachineConfig> askMachine)
|
|
{
|
|
try
|
|
{
|
|
var legacy = LoadLegacyClampSettings();
|
|
var provider = CreateMachineProvider();
|
|
var machines = ReadMachines(() => provider)
|
|
.OrderBy(m => m.Name, StringComparer.CurrentCultureIgnoreCase)
|
|
.ToList();
|
|
if (!LegacyClampMigration.IsNeeded(legacy, machines))
|
|
return null;
|
|
|
|
var target = LegacyClampMigration.Target(machines, LoadSavedNestDefaults(out _).MachineId);
|
|
if (target == null)
|
|
{
|
|
if (clampMigrationAsked)
|
|
return null;
|
|
clampMigrationAsked = true;
|
|
target = askMachine?.Invoke(machines);
|
|
if (target == null)
|
|
return null;
|
|
}
|
|
|
|
LegacyClampMigration.Apply(legacy, target);
|
|
provider.SaveMachine(target);
|
|
return null;
|
|
}
|
|
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
|
|
{
|
|
return $"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).";
|
|
}
|
|
}
|
|
|
|
// 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 ClampSettings LoadLegacyClampSettings()
|
|
{
|
|
if (Shared.Find(SharedSettingKinds.Clamps, SharedSettingKinds.DefaultKey) is { } shared)
|
|
return ClampSettings.Parse(shared.Json, out _);
|
|
return File.Exists(legacyClampsPath) ? ClampSettings.Load(legacyClampsPath) : null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The machine new nests are assigned: the nest defaults' machine when it is on file,
|
|
/// otherwise the first by name, or null when there are none.
|
|
/// </summary>
|
|
public static MachineConfig DefaultMachine(NestDefaults defaults, IEnumerable<MachineConfig> machines)
|
|
{
|
|
var sorted = machines.OrderBy(m => m.Name, StringComparer.CurrentCultureIgnoreCase).ToList();
|
|
return sorted.FirstOrDefault(m => m.Id == defaults?.MachineId) ?? sorted.FirstOrDefault();
|
|
}
|
|
|
|
/// <summary>The configuration of <paramref name="machine"/>, or null when none is assigned or it is not on file.</summary>
|
|
public MachineConfig FindMachine(NestMachine machine)
|
|
{
|
|
if (machine == null)
|
|
return null;
|
|
try
|
|
{
|
|
return CreateMachineProvider().GetMachine(machine.Id);
|
|
}
|
|
catch (IOException) { }
|
|
catch (UnauthorizedAccessException) { }
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Assigns <paramref name="machine"/> to <paramref name="nest"/> (null for none) and gives
|
|
/// every sheet that machine's support grid, or none when the machine gives nests no grid.
|
|
/// </summary>
|
|
public void AssignMachine(Nest nest, MachineConfig machine)
|
|
{
|
|
nest.Machine = machine == null ? null : new NestMachine(machine.Id, machine.Name);
|
|
var grid = LoadSupportGridSettings(machine);
|
|
nest.SetSupportBed(grid.Enabled ? grid.BedFor(nest.Units) : null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The cutting parameters a plate of <paramref name="nest"/> without its own starts from:
|
|
/// the last ones applied (or the built-in defaults), with the lead-ins, lead-outs and kerf
|
|
/// of the nest's machine for its material and thickness when the machine has them. Last
|
|
/// parameters already taken from that machine, material and thickness keep the user's
|
|
/// edits to them.
|
|
/// </summary>
|
|
public CuttingParameters DefaultCuttingParameters(Nest nest)
|
|
{
|
|
var parameters = LastCuttingParameters();
|
|
var machine = FindMachine(nest.Machine);
|
|
if (!MachineCuttingDefaults.IsRecordedFor(parameters, machine, nest))
|
|
MachineCuttingDefaults.ApplyTo(parameters, machine, nest);
|
|
return parameters;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The cutting parameters to plan or place lead-ins on <paramref name="plate"/> with: the
|
|
/// user's (see <see cref="DefaultCuttingParameters"/>), so an edit reaches nests already
|
|
/// planned. The plate's own are used only when no user settings are saved or readable.
|
|
/// </summary>
|
|
public CuttingParameters CuttingParametersFor(Plate plate, Nest nest) =>
|
|
plate?.CuttingParameters != null && ReadLastCuttingParameters() == null
|
|
? plate.CuttingParameters
|
|
: DefaultCuttingParameters(nest);
|
|
|
|
/// <summary>Whether the cutting settings of <paramref name="nest"/> are kept on its machine's thickness.</summary>
|
|
public bool HasMachineThickness(Nest nest) =>
|
|
nest != null && MachineCuttingDefaults.Find(FindMachine(nest.Machine), nest) != null;
|
|
|
|
/// <summary>
|
|
/// Where the cutting settings of <paramref name="nest"/> are kept, for the settings
|
|
/// dialogs: the machine's thickness, or why this PC's settings are used instead.
|
|
/// </summary>
|
|
public string DescribeCuttingSource(Nest nest)
|
|
{
|
|
var machine = FindMachine(nest?.Machine);
|
|
if (nest == null || machine == null)
|
|
return "This PC's settings: the nest has no machine.";
|
|
var material = nest.Material?.Name;
|
|
if (string.IsNullOrWhiteSpace(material) || !double.IsFinite(nest.Thickness) || nest.Thickness <= 0)
|
|
return "This PC's settings: the nest has no material and thickness.";
|
|
var job = $"{material}{(string.IsNullOrWhiteSpace(nest.Material.Grade) ? "" : " " + nest.Material.Grade)} "
|
|
+ $"{nest.Thickness:0.####} {(nest.Units == Units.Millimeters ? "mm" : "in")}";
|
|
return MachineCuttingDefaults.Find(machine, nest) == null
|
|
? $"This PC's settings: {machine.Name} has no {job}."
|
|
: $"Saved for {machine.Name} · {job}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves cutting settings edited for <paramref name="nest"/>: on the machine's thickness
|
|
/// when it has one, otherwise as this PC's settings. When the machine cannot be saved
|
|
/// (the nest server is offline, or someone else changed the machine), the settings are
|
|
/// kept on this PC for this job and <paramref name="warn"/> is told why. Returns whether
|
|
/// they reached the machine.
|
|
/// </summary>
|
|
public bool SaveCuttingParameters(Nest nest, CuttingParameters parameters, Action<string> warn) =>
|
|
SaveCuttingParameters(nest, parameters, CreateMachineProvider(), warn);
|
|
|
|
/// <summary>
|
|
/// <see cref="SaveCuttingParameters(Nest, CuttingParameters, Action{string})"/> with the
|
|
/// machines of <paramref name="provider"/>.
|
|
/// </summary>
|
|
public bool SaveCuttingParameters(Nest nest, CuttingParameters parameters, IDataProvider provider, Action<string> warn)
|
|
{
|
|
MachineConfig machine = null;
|
|
string error;
|
|
try
|
|
{
|
|
machine = nest?.Machine == null ? null : provider.GetMachine(nest.Machine.Id);
|
|
if (machine == null || !MachineCuttingDefaults.WriteTo(machine, nest, parameters))
|
|
{
|
|
SaveLastCuttingParameters(parameters);
|
|
return false;
|
|
}
|
|
provider.SaveMachine(machine);
|
|
// A copy kept on this PC for this job would hide the machine's from now on.
|
|
var last = ReadLastCuttingParameters();
|
|
if (last != null && MachineCuttingDefaults.IsRecordedFor(last, machine, nest))
|
|
{
|
|
last.MachineName = null;
|
|
SaveLastCuttingParameters(last);
|
|
}
|
|
return true;
|
|
}
|
|
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or ArgumentException)
|
|
{
|
|
error = ex.Message;
|
|
}
|
|
|
|
// Stamped for this job, so the edit sticks on this PC until the machine takes it.
|
|
var kept = CuttingParametersSerializer.Deserialize(CuttingParametersSerializer.Serialize(parameters));
|
|
kept.MachineName = machine?.Name ?? nest.Machine.Name;
|
|
kept.MaterialName = nest.Material?.Name;
|
|
kept.Grade = nest.Material?.Grade;
|
|
kept.Thickness = nest.Thickness;
|
|
SaveLastCuttingParameters(kept);
|
|
warn?.Invoke($"The cutting settings could not be saved to {kept.MachineName}:\n\n{error}\n\n"
|
|
+ "They are kept on this PC for this material and thickness.");
|
|
return false;
|
|
}
|
|
|
|
/// <summary>Saves <paramref name="parameters"/> as this PC's last cutting settings.</summary>
|
|
public void SaveLastCuttingParameters(CuttingParameters parameters) =>
|
|
user.CuttingParametersJson = CuttingParametersSerializer.Serialize(parameters);
|
|
|
|
/// <summary>The cutting parameters last applied, or the built-in defaults when none are saved or readable.</summary>
|
|
public CuttingParameters LastCuttingParameters() =>
|
|
ReadLastCuttingParameters() ?? new CuttingParameters();
|
|
|
|
private CuttingParameters ReadLastCuttingParameters()
|
|
{
|
|
var json = user.CuttingParametersJson;
|
|
if (!string.IsNullOrEmpty(json))
|
|
{
|
|
try
|
|
{
|
|
// Settings saved with the retired "Tab every part" read as tabs by size over
|
|
// every width. A plate's recorded parameters are never migrated.
|
|
var parameters = CuttingParametersSerializer.Deserialize(json);
|
|
Support.SupportTabSettings.MigrateTabEveryPart(parameters);
|
|
return parameters;
|
|
}
|
|
catch
|
|
{ /* fall through */
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gives <paramref name="settings"/> the cut-off clearance, overtravel, minimum segment and
|
|
/// direction of the nest's machine for its material and thickness, when the machine has
|
|
/// them; otherwise they are left as they are.
|
|
/// </summary>
|
|
public void ApplyCutOffDefaults(CutOffSettings settings, Nest nest) =>
|
|
MachineCuttingDefaults.ApplyTo(settings, FindMachine(nest.Machine), nest);
|
|
|
|
/// <summary>
|
|
/// The support grid of <paramref name="machine"/>; a machine without one saved yet, or no
|
|
/// machine, uses the legacy shared support-grid.json. Never throws, and loads as off when unreadable.
|
|
/// </summary>
|
|
public SupportGridSettings LoadSupportGridSettings(MachineConfig machine) =>
|
|
machine?.SupportGrid ?? SupportGridSettings.Load(legacySupportGridPath);
|
|
|
|
/// <summary>Saves <paramref name="settings"/> as the support grid of the machine with <paramref name="machineId"/>.</summary>
|
|
public void SaveSupportGridSettings(Guid machineId, SupportGridSettings settings)
|
|
{
|
|
var provider = CreateMachineProvider();
|
|
var machine = provider.GetMachine(machineId)
|
|
?? throw new IOException("The machine configuration is no longer on file.");
|
|
machine.SupportGrid = settings;
|
|
provider.SaveMachine(machine);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the persisted nest defaults (see <see cref="LoadSavedNestDefaults"/>).
|
|
/// <paramref name="warning"/> says, the first time in a session, that they could not be
|
|
/// read and the built-in defaults are used; it is null otherwise.
|
|
/// </summary>
|
|
public NestDefaults LoadNestDefaults(out string warning)
|
|
{
|
|
var defaults = LoadSavedNestDefaults(out var status);
|
|
warning = null;
|
|
if (status == NestDefaultsStatus.Invalid && !defaultsWarned)
|
|
{
|
|
defaultsWarned = true;
|
|
var source = SharedNestDefaults() != null ? "on the nest server" : nestDefaultsPath;
|
|
warning = $"The nest defaults could not be read:\n{source}\n\nBuilt-in defaults will be used. Re-save your defaults from Tools > Nest Defaults.";
|
|
}
|
|
return defaults;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The single desktop entry point for persisted nest defaults. Units
|
|
/// fall back to the legacy DefaultUnit setting whenever the file is
|
|
/// missing or unusable or has no valid unit, so New, BOM import and
|
|
/// the defaults dialog always agree.
|
|
/// </summary>
|
|
public NestDefaults LoadSavedNestDefaults(out NestDefaultsStatus status) =>
|
|
SharedNestDefaults() is { } shared
|
|
? NestDefaults.Parse(shared.Json, user.DefaultUnit, out status)
|
|
: NestDefaults.Load(nestDefaultsPath, user.DefaultUnit, out status);
|
|
|
|
/// <summary>This PC's own nest defaults, shared or not, for uploading.</summary>
|
|
public NestDefaults LoadLocalNestDefaults() =>
|
|
NestDefaults.Load(nestDefaultsPath, user.DefaultUnit, out _);
|
|
|
|
/// <summary>
|
|
/// Saves the nest defaults 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 void SaveNestDefaults(NestDefaults defaults)
|
|
{
|
|
if (Shared.Current() is { } current
|
|
&& current.Get(SharedSettingKinds.NestDefaults, SharedSettingKinds.DefaultKey) != null)
|
|
current.Save(SharedSettingKinds.NestDefaults, SharedSettingKinds.DefaultKey, defaults.ToJson());
|
|
else
|
|
defaults.Save(nestDefaultsPath);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This PC's machines, <paramref name="postProcessors"/>' settings and nest defaults that
|
|
/// <paramref name="current"/> does not hold yet (see <see cref="SharedSettingsSource.PendingUploads"/>).
|
|
/// </summary>
|
|
public IReadOnlyList<SharedSettingUpload> PendingUploads(SharedSettingsStore current, IEnumerable<IPostProcessor> postProcessors) =>
|
|
SharedSettingsSource.PendingUploads(current, LoadLocalMachines, postProcessors,
|
|
() => File.Exists(nestDefaultsPath) ? LoadLocalNestDefaults().ToJson() : null);
|
|
|
|
private SharedSettingRecord SharedNestDefaults() =>
|
|
Shared.Find(SharedSettingKinds.NestDefaults, SharedSettingKinds.DefaultKey);
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="IMachineUserSettings"/> kept in the desktop user-settings file
|
|
/// (<see cref="UserSettingsFile"/>) under the WinForms names and text: DefaultUnit as the unit's
|
|
/// name ("Inches" when missing or unreadable) and CuttingParametersJson as JSON text.
|
|
/// </summary>
|
|
public sealed class MachineUserSettingsFile(string path) : IMachineUserSettings
|
|
{
|
|
public const string DefaultUnitName = "DefaultUnit";
|
|
public const string CuttingParametersName = "CuttingParametersJson";
|
|
|
|
public Units DefaultUnit =>
|
|
UserSettingsFile.Read(path).TryGetValue(DefaultUnitName, out var text)
|
|
&& Enum.TryParse<Units>(text?.Trim(), ignoreCase: false, out var units)
|
|
&& Enum.IsDefined(units)
|
|
? units
|
|
: Units.Inches;
|
|
|
|
public string CuttingParametersJson
|
|
{
|
|
get => UserSettingText.ReadString(UserSettingsFile.Read(path), CuttingParametersName, "");
|
|
set => UserSettingsFile.Update(path, values => values[CuttingParametersName] = value ?? "");
|
|
}
|
|
}
|