mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 00:21:06 -04:00
Roslyn duplicate scans found the same code copied across layers: - Data: the three browse sessions share BrowseSession<TPage, TSort>; the remote clients share NestServerHttp (JSON options, If-Match, error text). - WinForms: five paged lists share BrowsePager for loading text, navigation, sort glyphs and failure messages. The Add Drawings to Release, Drawing Library and set browser dialogs now dispose their browse sessions. - Server: NestQueryRequest reuses DrawingLibraryRequest's helpers; revision history queries share count and paging binds. - Engine: RemnantPlateNester delegates to DefaultPlateNester; CuttingPlanService reuses JointCuttingPlanSearch.Map. - Core: LineArc and CleanHole lead-ins share LineArcApproach with unchanged arithmetic order, so output is bit-identical. - IO: Dxf.GetGeometry overloads share one reader; .nest JSON test helpers move to NestArchiveJson. Also fixes the production compiler warnings (CS8632, CS8603, CS0108) and moves a test off an obsolete forwarding API. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
174 lines
7.2 KiB
C#
174 lines
7.2 KiB
C#
using System.Text.Json;
|
|
using OpenNest.Support;
|
|
|
|
namespace OpenNest.Data;
|
|
|
|
/// <summary>
|
|
/// The machine's laser support grid, persisted to a single JSON file (by default
|
|
/// %APPDATA%\OpenNest\support-grid.json). Off by default: until <see cref="Enabled"/> is set,
|
|
/// new nests get no support bed. Loading never throws; an invalid value falls back to its
|
|
/// default and a file that cannot be read loads as off.
|
|
/// </summary>
|
|
public sealed class SupportGridSettings
|
|
{
|
|
public const int CurrentVersion = 1;
|
|
|
|
private const double MillimetersPerInch = 25.4;
|
|
|
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
|
{
|
|
WriteIndented = true,
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
};
|
|
|
|
/// <summary>Whether new nests receive this grid. Default off.</summary>
|
|
public bool Enabled { get; set; }
|
|
|
|
/// <summary>Units of every length below.</summary>
|
|
public Units Units { get; set; } = Units.Inches;
|
|
|
|
public double PitchX { get; set; } = 2;
|
|
public double PitchY { get; set; } = 2;
|
|
public double OffsetX { get; set; }
|
|
public double OffsetY { get; set; }
|
|
public double RegistrationTolerance { get; set; } = 0.125;
|
|
public double StabilityMargin { get; set; } = 0.25;
|
|
|
|
/// <summary>%APPDATA%\OpenNest\support-grid.json.</summary>
|
|
public static string DefaultPath =>
|
|
Path.Combine(
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
|
"OpenNest",
|
|
"support-grid.json"
|
|
);
|
|
|
|
/// <summary>Settings holding <paramref name="grid"/> (or the defaults when null) in <paramref name="units"/>.</summary>
|
|
public static SupportGridSettings FromGrid(SupportGrid grid, Units units, bool enabled)
|
|
{
|
|
var settings = new SupportGridSettings().ConvertedTo(units);
|
|
settings.Enabled = enabled;
|
|
if (grid != null)
|
|
{
|
|
settings.PitchX = grid.PitchX;
|
|
settings.PitchY = grid.PitchY;
|
|
settings.OffsetX = grid.OffsetX;
|
|
settings.OffsetY = grid.OffsetY;
|
|
settings.RegistrationTolerance = grid.RegistrationTolerance;
|
|
settings.StabilityMargin = grid.StabilityMargin;
|
|
}
|
|
return settings;
|
|
}
|
|
|
|
/// <summary>The grid these settings describe in <paramref name="units"/>, or null when it is invalid.</summary>
|
|
public SupportGrid? GridFor(Units units)
|
|
{
|
|
var converted = ConvertedTo(units);
|
|
try
|
|
{
|
|
return new SupportGrid(converted.PitchX, converted.PitchY, converted.OffsetX, converted.OffsetY,
|
|
converted.RegistrationTolerance, converted.StabilityMargin);
|
|
}
|
|
catch (ArgumentOutOfRangeException)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>A copy with every length expressed in <paramref name="units"/> (inches and millimeters convert at 25.4).</summary>
|
|
public SupportGridSettings ConvertedTo(Units units)
|
|
{
|
|
var scale = Units == units ? 1 : Units == Units.Inches ? MillimetersPerInch : 1 / MillimetersPerInch;
|
|
return new SupportGridSettings
|
|
{
|
|
Enabled = Enabled,
|
|
Units = units,
|
|
PitchX = PitchX * scale,
|
|
PitchY = PitchY * scale,
|
|
OffsetX = OffsetX * scale,
|
|
OffsetY = OffsetY * scale,
|
|
RegistrationTolerance = RegistrationTolerance * scale,
|
|
StabilityMargin = StabilityMargin * scale,
|
|
};
|
|
}
|
|
|
|
/// <summary>Gives a newly created nest this grid when enabled (after its units are set).</summary>
|
|
public void ApplyToNewNest(Nest nest)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(nest);
|
|
if (Enabled && GridFor(nest.Units) is { } grid)
|
|
nest.SetSupportBed(grid);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the settings, never throwing. A missing or unreadable file yields the defaults
|
|
/// (off); each invalid value in a readable file keeps its default.
|
|
/// </summary>
|
|
public static SupportGridSettings Load(string path)
|
|
{
|
|
var settings = new SupportGridSettings();
|
|
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
|
|
return settings;
|
|
|
|
try
|
|
{
|
|
using var document = JsonDocument.Parse(File.ReadAllText(path));
|
|
var root = document.RootElement;
|
|
if (root.ValueKind != JsonValueKind.Object)
|
|
return settings;
|
|
|
|
if (root.TryGetProperty("enabled", out var enabled) && enabled.ValueKind is JsonValueKind.True or JsonValueKind.False)
|
|
settings.Enabled = enabled.GetBoolean();
|
|
// Lengths read below are in the file's units; the defaults are converted to them first.
|
|
if (root.TryGetProperty("units", out var units) && units.ValueKind == JsonValueKind.String)
|
|
foreach (var unit in Enum.GetValues<Units>())
|
|
if (string.Equals(unit.ToString(), units.GetString(), StringComparison.OrdinalIgnoreCase))
|
|
settings = settings.ConvertedTo(unit);
|
|
|
|
settings.PitchX = Length(root, "pitchX", settings.PitchX, positive: true);
|
|
settings.PitchY = Length(root, "pitchY", settings.PitchY, positive: true);
|
|
settings.OffsetX = Length(root, "offsetX", settings.OffsetX, positive: false, signed: true);
|
|
settings.OffsetY = Length(root, "offsetY", settings.OffsetY, positive: false, signed: true);
|
|
settings.RegistrationTolerance = Length(root, "registrationTolerance", settings.RegistrationTolerance, positive: false);
|
|
settings.StabilityMargin = Length(root, "stabilityMargin", settings.StabilityMargin, positive: false);
|
|
return settings;
|
|
}
|
|
catch (Exception ex) when (ex is JsonException or IOException or UnauthorizedAccessException
|
|
or NotSupportedException or System.Security.SecurityException)
|
|
{
|
|
return new SupportGridSettings();
|
|
}
|
|
}
|
|
|
|
/// <summary>Writes the file, creating its directory.</summary>
|
|
public void Save(string path)
|
|
{
|
|
var dto = new Dictionary<string, object>
|
|
{
|
|
["version"] = CurrentVersion,
|
|
["enabled"] = Enabled,
|
|
["units"] = Units.ToString().ToLowerInvariant(),
|
|
["pitchX"] = PitchX,
|
|
["pitchY"] = PitchY,
|
|
["offsetX"] = OffsetX,
|
|
["offsetY"] = OffsetY,
|
|
["registrationTolerance"] = RegistrationTolerance,
|
|
["stabilityMargin"] = StabilityMargin,
|
|
};
|
|
var directory = Path.GetDirectoryName(Path.GetFullPath(path));
|
|
if (!string.IsNullOrEmpty(directory))
|
|
Directory.CreateDirectory(directory);
|
|
File.WriteAllText(path, JsonSerializer.Serialize(dto, JsonOptions));
|
|
}
|
|
|
|
// A finite number that stays finite in either unit; otherwise the fallback.
|
|
private static double Length(JsonElement root, string name, double fallback, bool positive, bool signed = false)
|
|
{
|
|
if (!root.TryGetProperty(name, out var element) || element.ValueKind != JsonValueKind.Number
|
|
|| !element.TryGetDouble(out var value) || !double.IsFinite(value) || !double.IsFinite(value * MillimetersPerInch))
|
|
return fallback;
|
|
if (positive ? value <= 0 : !signed && value < 0)
|
|
return fallback;
|
|
return value;
|
|
}
|
|
}
|