mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-31 19:11:53 -04:00
refactor: merge duplicated geometry, settings-file and form helpers
Roslyn Bridge flagged these as copy-paste clusters; each pair now shares one implementation so a fix lands everywhere at once: - Shape.OffsetInward/OffsetOutward differed only in the target winding. - BestFitFinder kept its own copy of SpatialQuery's arc-extreme points. - SplitDrawingForm rebuilt DrawingSplitter's clip-region grid for preview. - GeometryOptimizer's collinear/coradial finders (one misspelled GetCoradialArs) become one generic FindRelated. - Five settings files repeated the write-with-retry loop, and two also repeated the unit parser and unreadable-file filter (SettingsFile). - ClampsForm and SupportGridForm repeated their row/box builders. - Vertical/HorizontalRemnantComparer share a RemnantComparer base. Behavior is unchanged except that Engine/NestStorage settings loads now also tolerate NotSupported/Security exceptions, matching the other settings files' never-throw contract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4028dafb26
commit
b550640ef0
18 files changed
+291
-517
No files matched your search
@@ -10,14 +10,14 @@ namespace OpenNest.Geometry
|
||||
public static void Optimize(IList<Arc> arcs) =>
|
||||
MergePass(
|
||||
arcs,
|
||||
(list, item, i) => list.GetCoradialArs(item, i),
|
||||
(a, b) => a.IsCoradialTo(b),
|
||||
(Arc a, Arc b, out Arc joined) => TryJoinArcs(a, b, out joined)
|
||||
);
|
||||
|
||||
public static void Optimize(IList<Line> lines) =>
|
||||
MergePass(
|
||||
lines,
|
||||
(list, item, i) => list.GetCollinearLines(item, i),
|
||||
(a, b) => a.IsCollinearTo(b),
|
||||
(Line a, Line b, out Line joined) => TryJoinLines(a, b, out joined)
|
||||
);
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace OpenNest.Geometry
|
||||
|
||||
private static void MergePass<T>(
|
||||
IList<T> items,
|
||||
Func<IList<T>, T, int, List<T>> findCandidates,
|
||||
Func<T, T, bool> related,
|
||||
TryJoin<T> tryJoin
|
||||
)
|
||||
where T : class
|
||||
@@ -69,7 +69,7 @@ namespace OpenNest.Geometry
|
||||
for (var i = 0; i < items.Count; ++i)
|
||||
{
|
||||
var item = items[i];
|
||||
var candidates = findCandidates(items, item, i);
|
||||
var candidates = FindRelated(items, item, i, related);
|
||||
var index = 0;
|
||||
|
||||
while (index < candidates.Count)
|
||||
@@ -214,62 +214,41 @@ namespace OpenNest.Geometry
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<Line> GetCollinearLines(
|
||||
this IList<Line> lines,
|
||||
Line line,
|
||||
int startIndex
|
||||
/// <summary>
|
||||
/// The items from <paramref name="startIndex"/> on, other than <paramref name="item"/>
|
||||
/// itself, that are <paramref name="related"/> to it.
|
||||
/// </summary>
|
||||
private static List<T> FindRelated<T>(
|
||||
IList<T> items,
|
||||
T item,
|
||||
int startIndex,
|
||||
Func<T, T, bool> related
|
||||
)
|
||||
where T : class
|
||||
{
|
||||
var collinearLines = new List<Line>();
|
||||
var matches = new List<T>();
|
||||
|
||||
Parallel.For(
|
||||
startIndex,
|
||||
lines.Count,
|
||||
items.Count,
|
||||
index =>
|
||||
{
|
||||
var compareLine = lines[index];
|
||||
var candidate = items[index];
|
||||
|
||||
if (Object.ReferenceEquals(line, compareLine))
|
||||
if (Object.ReferenceEquals(item, candidate))
|
||||
return;
|
||||
|
||||
if (!line.IsCollinearTo(compareLine))
|
||||
if (!related(item, candidate))
|
||||
return;
|
||||
|
||||
lock (collinearLines)
|
||||
lock (matches)
|
||||
{
|
||||
collinearLines.Add(compareLine);
|
||||
matches.Add(candidate);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return collinearLines;
|
||||
}
|
||||
|
||||
private static List<Arc> GetCoradialArs(this IList<Arc> arcs, Arc arc, int startIndex)
|
||||
{
|
||||
var coradialArcs = new List<Arc>();
|
||||
|
||||
Parallel.For(
|
||||
startIndex,
|
||||
arcs.Count,
|
||||
index =>
|
||||
{
|
||||
var compareArc = arcs[index];
|
||||
|
||||
if (Object.ReferenceEquals(arc, compareArc))
|
||||
return;
|
||||
|
||||
if (!arc.IsCoradialTo(compareArc))
|
||||
return;
|
||||
|
||||
lock (coradialArcs)
|
||||
{
|
||||
coradialArcs.Add(compareArc);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return coradialArcs;
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
@@ -750,64 +750,25 @@ namespace OpenNest.Geometry
|
||||
/// Normalizes to CW winding before offsetting Left (which is outward for CW),
|
||||
/// making the method independent of the original contour winding direction.
|
||||
/// </summary>
|
||||
internal Shape OffsetOutward(double distance)
|
||||
{
|
||||
var poly = ToPolygon();
|
||||
|
||||
if (
|
||||
poly == null
|
||||
|| poly.Vertices.Count < 3
|
||||
|| poly.RotationDirection() == RotationType.CW
|
||||
)
|
||||
return OffsetEntity(distance, OffsetSide.Left) as Shape;
|
||||
|
||||
// Shape is CCW — reverse to CW so Left offset goes outward.
|
||||
var copy = new Shape();
|
||||
|
||||
for (var i = Entities.Count - 1; i >= 0; i--)
|
||||
{
|
||||
switch (Entities[i])
|
||||
{
|
||||
case Line l:
|
||||
copy.Entities.Add(new Line(l.EndPoint, l.StartPoint) { Layer = l.Layer });
|
||||
break;
|
||||
case Arc a:
|
||||
copy.Entities.Add(
|
||||
new Arc(a.Center, a.Radius, a.EndAngle, a.StartAngle, !a.IsReversed)
|
||||
{
|
||||
Layer = a.Layer,
|
||||
}
|
||||
);
|
||||
break;
|
||||
case Circle c:
|
||||
copy.Entities.Add(
|
||||
new Circle(c.Center, c.Radius)
|
||||
{
|
||||
Layer = c.Layer,
|
||||
Rotation = RotationType.CW,
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return copy.OffsetEntity(distance, OffsetSide.Left) as Shape;
|
||||
}
|
||||
internal Shape OffsetOutward(double distance) =>
|
||||
OffsetLeftWithWinding(distance, RotationType.CW);
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the shape inward by the given distance.
|
||||
/// Normalizes to CCW winding before offsetting Left (which is inward for CCW),
|
||||
/// making the method independent of the original contour winding direction.
|
||||
/// </summary>
|
||||
internal Shape OffsetInward(double distance)
|
||||
internal Shape OffsetInward(double distance) =>
|
||||
OffsetLeftWithWinding(distance, RotationType.CCW);
|
||||
|
||||
/// <summary>
|
||||
/// Offsets Left after normalizing the contour to <paramref name="winding"/>.
|
||||
/// </summary>
|
||||
private Shape OffsetLeftWithWinding(double distance, RotationType winding)
|
||||
{
|
||||
var poly = ToPolygon();
|
||||
|
||||
if (
|
||||
poly == null
|
||||
|| poly.Vertices.Count < 3
|
||||
|| poly.RotationDirection() == RotationType.CCW
|
||||
)
|
||||
if (poly == null || poly.Vertices.Count < 3 || poly.RotationDirection() == winding)
|
||||
return OffsetEntity(distance, OffsetSide.Left) as Shape;
|
||||
|
||||
// Create a reversed copy to avoid mutating shared entity objects.
|
||||
@@ -833,7 +794,7 @@ namespace OpenNest.Geometry
|
||||
new Circle(c.Center, c.Radius)
|
||||
{
|
||||
Layer = c.Layer,
|
||||
Rotation = RotationType.CCW,
|
||||
Rotation = winding,
|
||||
}
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -791,7 +791,11 @@ namespace OpenNest.Geometry
|
||||
return vertices.ToArray();
|
||||
}
|
||||
|
||||
private static void AddArcExtremeVertices(HashSet<Vector> points, Arc arc)
|
||||
/// <summary>
|
||||
/// Adds the cardinal extreme points of an arc (0°, 90°, 180°, 270°)
|
||||
/// if they fall within the arc's angular span.
|
||||
/// </summary>
|
||||
internal static void AddArcExtremeVertices(ICollection<Vector> points, Arc arc)
|
||||
{
|
||||
var a1 = arc.StartAngle;
|
||||
var a2 = arc.EndAngle;
|
||||
|
||||
@@ -252,13 +252,17 @@ public static class DrawingSplitter
|
||||
&& line.Position < bounds.Top - OpenNest.Math.Tolerance.Epsilon;
|
||||
}
|
||||
|
||||
private static List<Box> BuildClipRegions(List<SplitLine> sortedLines, Box bounds)
|
||||
/// <summary>
|
||||
/// The grid of pieces the split lines cut <paramref name="bounds"/> into, row by row
|
||||
/// from the bottom-left.
|
||||
/// </summary>
|
||||
public static List<Box> BuildClipRegions(IEnumerable<SplitLine> lines, Box bounds)
|
||||
{
|
||||
var verticals = sortedLines
|
||||
var verticals = lines
|
||||
.Where(l => l.Axis == CutOffAxis.Vertical)
|
||||
.OrderBy(l => l.Position)
|
||||
.ToList();
|
||||
var horizontals = sortedLines
|
||||
var horizontals = lines
|
||||
.Where(l => l.Axis == CutOffAxis.Horizontal)
|
||||
.OrderBy(l => l.Position)
|
||||
.ToList();
|
||||
|
||||
@@ -216,7 +216,7 @@ public sealed class ClampSettings
|
||||
{
|
||||
json = File.ReadAllText(path);
|
||||
}
|
||||
catch (Exception ex) when (IsUnreadableFile(ex))
|
||||
catch (Exception ex) when (SettingsFile.IsUnreadableFile(ex))
|
||||
{
|
||||
status = ClampSettingsStatus.Invalid;
|
||||
return Fallback;
|
||||
@@ -247,7 +247,7 @@ public sealed class ClampSettings
|
||||
foreach (var property in document.RootElement.EnumerateObject())
|
||||
values[property.Name] = property.Value.Clone();
|
||||
}
|
||||
catch (Exception ex) when (IsUnreadableFile(ex))
|
||||
catch (Exception ex) when (SettingsFile.IsUnreadableFile(ex))
|
||||
{
|
||||
status = ClampSettingsStatus.Invalid;
|
||||
return settings;
|
||||
@@ -265,7 +265,7 @@ public sealed class ClampSettings
|
||||
|
||||
if (values.TryGetValue("units", out var units))
|
||||
{
|
||||
if (TryParseUnits(units, out var parsed))
|
||||
if (SettingsFile.TryParseUnits(units, out var parsed))
|
||||
settings.Units = parsed;
|
||||
else
|
||||
ignored = true;
|
||||
@@ -347,24 +347,7 @@ public sealed class ClampSettings
|
||||
/// </summary>
|
||||
public void Save(string path, int maxRetries = 3)
|
||||
{
|
||||
var json = ToJson();
|
||||
|
||||
var directory = Path.GetDirectoryName(Path.GetFullPath(path));
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
for (var attempt = 0; attempt < maxRetries; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.WriteAllText(path, json);
|
||||
return;
|
||||
}
|
||||
catch (IOException) when (attempt < maxRetries - 1)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
SettingsFile.Write(path, ToJson(), maxRetries);
|
||||
}
|
||||
|
||||
/// <summary>The settings as <see cref="Save"/> writes them, for the shared copy.</summary>
|
||||
@@ -412,35 +395,4 @@ public sealed class ClampSettings
|
||||
&& double.IsFinite(value)
|
||||
&& double.IsFinite(value * MillimetersPerInch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accepts only a defined unit name (case-insensitive); Enum.TryParse would also accept
|
||||
/// numeric strings and comma-joined names.
|
||||
/// </summary>
|
||||
private static bool TryParseUnits(JsonElement element, out Units units)
|
||||
{
|
||||
units = default;
|
||||
if (element.ValueKind != JsonValueKind.String)
|
||||
return false;
|
||||
|
||||
var text = element.GetString();
|
||||
foreach (var candidate in Enum.GetValues<Units>())
|
||||
{
|
||||
if (string.Equals(candidate.ToString(), text, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
units = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsUnreadableFile(Exception ex) =>
|
||||
ex
|
||||
is JsonException
|
||||
or IOException
|
||||
or UnauthorizedAccessException
|
||||
or NotSupportedException
|
||||
or System.Security.SecurityException;
|
||||
}
|
||||
@@ -27,20 +27,9 @@ public sealed class EngineSelectionSettings
|
||||
/// <summary>Missing, unreadable or corrupt settings safely use Default without writing.</summary>
|
||||
public static EngineSelectionSettings Load(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
|
||||
return new();
|
||||
|
||||
try
|
||||
{
|
||||
var settings = JsonSerializer.Deserialize<EngineSelectionSettings>(
|
||||
File.ReadAllText(path), JsonOptions) ?? new();
|
||||
settings.EngineName = NormalizeName(settings.EngineName);
|
||||
return settings;
|
||||
}
|
||||
catch (Exception ex) when (ex is JsonException or IOException or UnauthorizedAccessException)
|
||||
{
|
||||
return new();
|
||||
}
|
||||
var settings = SettingsFile.Read<EngineSelectionSettings>(path, JsonOptions) ?? new();
|
||||
settings.EngineName = NormalizeName(settings.EngineName);
|
||||
return settings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -81,22 +70,7 @@ public sealed class EngineSelectionSettings
|
||||
{
|
||||
var json = JsonSerializer.Serialize(
|
||||
new EngineSelectionSettings { EngineName = NormalizeName(EngineName) }, JsonOptions);
|
||||
var directory = Path.GetDirectoryName(Path.GetFullPath(path));
|
||||
|
||||
for (var attempt = 0; attempt < 3; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
Directory.CreateDirectory(directory);
|
||||
File.WriteAllText(path, json);
|
||||
return;
|
||||
}
|
||||
catch (IOException) when (attempt < 2)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
SettingsFile.Write(path, json);
|
||||
}
|
||||
|
||||
private static string NormalizeName(string? name) =>
|
||||
|
||||
@@ -42,7 +42,7 @@ public class LocalJsonProvider : IDataProvider
|
||||
{
|
||||
var json = JsonSerializer.Serialize(machine, JsonOptions);
|
||||
var path = GetPath(machine.Id);
|
||||
WriteWithRetry(path, json);
|
||||
SettingsFile.Write(path, json);
|
||||
}
|
||||
|
||||
public void DeleteMachine(Guid id)
|
||||
@@ -97,20 +97,4 @@ public class LocalJsonProvider : IDataProvider
|
||||
|
||||
SaveMachine(config);
|
||||
}
|
||||
|
||||
private static void WriteWithRetry(string path, string json, int maxRetries = 3)
|
||||
{
|
||||
for (var attempt = 0; attempt < maxRetries; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.WriteAllText(path, json);
|
||||
return;
|
||||
}
|
||||
catch (IOException) when (attempt < maxRetries - 1)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,7 @@ public sealed class NestDefaults
|
||||
{
|
||||
json = File.ReadAllText(path);
|
||||
}
|
||||
catch (Exception ex) when (IsUnreadableFile(ex))
|
||||
catch (Exception ex) when (SettingsFile.IsUnreadableFile(ex))
|
||||
{
|
||||
// A file that exists but cannot be read (locked, access-denied)
|
||||
// must never block creating a nest.
|
||||
@@ -129,7 +129,7 @@ public sealed class NestDefaults
|
||||
{
|
||||
dto = JsonSerializer.Deserialize<NestDefaultsDto>(json ?? "", JsonOptions);
|
||||
}
|
||||
catch (Exception ex) when (IsUnreadableFile(ex))
|
||||
catch (Exception ex) when (SettingsFile.IsUnreadableFile(ex))
|
||||
{
|
||||
// Defaults that cannot be parsed must never block creating a nest.
|
||||
status = NestDefaultsStatus.Invalid;
|
||||
@@ -144,7 +144,7 @@ public sealed class NestDefaults
|
||||
|
||||
status = NestDefaultsStatus.Ok;
|
||||
|
||||
if (TryParseUnits(dto.Units, out var units))
|
||||
if (SettingsFile.TryParseUnits(dto.Units, out var units))
|
||||
defaults.Units = units;
|
||||
|
||||
if (
|
||||
@@ -261,24 +261,7 @@ public sealed class NestDefaults
|
||||
/// </summary>
|
||||
public void Save(string path, int maxRetries = 3)
|
||||
{
|
||||
var json = ToJson();
|
||||
|
||||
var directory = Path.GetDirectoryName(Path.GetFullPath(path));
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
for (var attempt = 0; attempt < maxRetries; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.WriteAllText(path, json);
|
||||
return;
|
||||
}
|
||||
catch (IOException) when (attempt < maxRetries - 1)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
SettingsFile.Write(path, ToJson(), maxRetries);
|
||||
}
|
||||
|
||||
/// <summary>The defaults as <see cref="Save"/> writes them, for the shared copy.</summary>
|
||||
@@ -304,38 +287,6 @@ public sealed class NestDefaults
|
||||
return JsonSerializer.Serialize(dto, JsonOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accepts only a defined unit name (case-insensitive). Enum.TryParse
|
||||
/// would also accept numeric strings ("7") and comma-joined names,
|
||||
/// producing undefined or unintended values.
|
||||
/// </summary>
|
||||
private static bool TryParseUnits(JsonElement? element, out Units units)
|
||||
{
|
||||
units = default;
|
||||
if (element is not { ValueKind: JsonValueKind.String } value)
|
||||
return false;
|
||||
|
||||
var text = value.GetString();
|
||||
foreach (var candidate in Enum.GetValues<Units>())
|
||||
{
|
||||
if (string.Equals(candidate.ToString(), text, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
units = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsUnreadableFile(Exception ex) =>
|
||||
ex
|
||||
is JsonException
|
||||
or IOException
|
||||
or UnauthorizedAccessException
|
||||
or NotSupportedException
|
||||
or System.Security.SecurityException;
|
||||
|
||||
private static bool IsValidSize(double width, double length) =>
|
||||
!double.IsNaN(width)
|
||||
&& !double.IsNaN(length)
|
||||
|
||||
@@ -51,20 +51,9 @@ public sealed class NestStorageSettings
|
||||
|
||||
public static NestStorageSettings Load(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
|
||||
return new();
|
||||
|
||||
try
|
||||
{
|
||||
var settings = JsonSerializer.Deserialize<NestStorageSettings>(
|
||||
File.ReadAllText(path), JsonOptions) ?? new();
|
||||
settings.ServerUrl = NormalizeUrl(settings.ServerUrl);
|
||||
return settings;
|
||||
}
|
||||
catch (Exception ex) when (ex is JsonException or IOException or UnauthorizedAccessException)
|
||||
{
|
||||
return new();
|
||||
}
|
||||
var settings = SettingsFile.Read<NestStorageSettings>(path, JsonOptions) ?? new();
|
||||
settings.ServerUrl = NormalizeUrl(settings.ServerUrl);
|
||||
return settings;
|
||||
}
|
||||
|
||||
/// <summary>Writes camelCase JSON, creating the parent directory and retrying IO collisions.</summary>
|
||||
@@ -73,22 +62,7 @@ public sealed class NestStorageSettings
|
||||
var json = JsonSerializer.Serialize(
|
||||
new NestStorageSettings { Mode = Mode, ServerUrl = NormalizeUrl(ServerUrl) },
|
||||
JsonOptions);
|
||||
var directory = Path.GetDirectoryName(Path.GetFullPath(path));
|
||||
|
||||
for (var attempt = 0; attempt < 3; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
Directory.CreateDirectory(directory);
|
||||
File.WriteAllText(path, json);
|
||||
return;
|
||||
}
|
||||
catch (IOException) when (attempt < 2)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
SettingsFile.Write(path, json);
|
||||
}
|
||||
|
||||
private static string NormalizeUrl(string? url) => (url ?? "").Trim().TrimEnd('/');
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace OpenNest.Data;
|
||||
|
||||
/// <summary>File handling shared by the JSON settings files under %APPDATA%\OpenNest.</summary>
|
||||
internal static class SettingsFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes the file, creating the parent directory, and retries briefly on IO collisions.
|
||||
/// The last failure propagates; the host handles a persistent write failure.
|
||||
/// </summary>
|
||||
public static void Write(string path, string json, int maxRetries = 3)
|
||||
{
|
||||
var directory = Path.GetDirectoryName(Path.GetFullPath(path));
|
||||
|
||||
for (var attempt = 0; attempt < maxRetries; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
Directory.CreateDirectory(directory);
|
||||
File.WriteAllText(path, json);
|
||||
return;
|
||||
}
|
||||
catch (IOException) when (attempt < maxRetries - 1)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the file, or returns null when it is missing, unreadable or corrupt.
|
||||
/// </summary>
|
||||
public static T? Read<T>(string path, JsonSerializerOptions options)
|
||||
where T : class
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<T>(File.ReadAllText(path), options);
|
||||
}
|
||||
catch (Exception ex) when (IsUnreadableFile(ex))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsUnreadableFile(Exception ex) =>
|
||||
ex
|
||||
is JsonException
|
||||
or IOException
|
||||
or UnauthorizedAccessException
|
||||
or NotSupportedException
|
||||
or System.Security.SecurityException;
|
||||
|
||||
/// <summary>
|
||||
/// Accepts only a defined unit name (case-insensitive). Enum.TryParse would also accept
|
||||
/// numeric strings ("7") and comma-joined names, producing undefined or unintended values.
|
||||
/// </summary>
|
||||
public static bool TryParseUnits(JsonElement? element, out Units units)
|
||||
{
|
||||
units = default;
|
||||
if (element is not { ValueKind: JsonValueKind.String } value)
|
||||
return false;
|
||||
|
||||
var text = value.GetString();
|
||||
foreach (var candidate in Enum.GetValues<Units>())
|
||||
{
|
||||
if (string.Equals(candidate.ToString(), text, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
units = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -191,7 +191,7 @@ namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
points.Add(arc.StartPoint());
|
||||
points.Add(arc.EndPoint());
|
||||
AddArcExtremes(points, arc);
|
||||
SpatialQuery.AddArcExtremeVertices(points, arc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,35 +220,6 @@ namespace OpenNest.Engine.BestFit
|
||||
return hullAngles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the cardinal extreme points of an arc (0°, 90°, 180°, 270°)
|
||||
/// if they fall within the arc's angular span.
|
||||
/// </summary>
|
||||
private static void AddArcExtremes(List<Vector> points, Arc arc)
|
||||
{
|
||||
var a1 = arc.StartAngle;
|
||||
var a2 = arc.EndAngle;
|
||||
|
||||
if (arc.IsReversed)
|
||||
Generic.Swap(ref a1, ref a2);
|
||||
|
||||
// Right (0°)
|
||||
if (Angle.IsBetweenRad(Angle.TwoPI, a1, a2))
|
||||
points.Add(new Vector(arc.Center.X + arc.Radius, arc.Center.Y));
|
||||
|
||||
// Top (90°)
|
||||
if (Angle.IsBetweenRad(Angle.HalfPI, a1, a2))
|
||||
points.Add(new Vector(arc.Center.X, arc.Center.Y + arc.Radius));
|
||||
|
||||
// Left (180°)
|
||||
if (Angle.IsBetweenRad(System.Math.PI, a1, a2))
|
||||
points.Add(new Vector(arc.Center.X - arc.Radius, arc.Center.Y));
|
||||
|
||||
// Bottom (270°)
|
||||
if (Angle.IsBetweenRad(System.Math.PI * 1.5, a1, a2))
|
||||
points.Add(new Vector(arc.Center.X, arc.Center.Y - arc.Radius));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Minimum angular separation (radians) between hull-derived rotation candidates.
|
||||
/// Tessellated arcs produce many hull edges with nearly identical angles;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
@@ -8,44 +6,10 @@ namespace OpenNest.Engine.Fill
|
||||
/// Ranks fill results to minimize Y-extent (preserve top-side horizontal remnant).
|
||||
/// Tiebreak chain: count > smallest Y-extent > highest density.
|
||||
/// </summary>
|
||||
public class HorizontalRemnantComparer : IFillComparer
|
||||
public class HorizontalRemnantComparer : RemnantComparer
|
||||
{
|
||||
public bool IsBetter(List<Part> candidate, List<Part> current, Box workArea)
|
||||
{
|
||||
if (candidate == null || candidate.Count == 0)
|
||||
return false;
|
||||
protected override double Low(Box box) => box.Bottom;
|
||||
|
||||
if (current == null || current.Count == 0)
|
||||
return true;
|
||||
|
||||
if (candidate.Count != current.Count)
|
||||
return candidate.Count > current.Count;
|
||||
|
||||
var candExtent = YExtent(candidate);
|
||||
var currExtent = YExtent(current);
|
||||
|
||||
if (!candExtent.IsEqualTo(currExtent))
|
||||
return candExtent < currExtent;
|
||||
|
||||
return FillScore.Compute(candidate, workArea).Density
|
||||
> FillScore.Compute(current, workArea).Density;
|
||||
}
|
||||
|
||||
private static double YExtent(List<Part> parts)
|
||||
{
|
||||
var minY = double.MaxValue;
|
||||
var maxY = double.MinValue;
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var bb = part.BoundingBox;
|
||||
if (bb.Bottom < minY)
|
||||
minY = bb.Bottom;
|
||||
if (bb.Top > maxY)
|
||||
maxY = bb.Top;
|
||||
}
|
||||
|
||||
return maxY - minY;
|
||||
}
|
||||
protected override double High(Box box) => box.Top;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
/// <summary>
|
||||
/// Ranks fill results to keep one side of the work area free for a remnant.
|
||||
/// Tiebreak chain: count > smallest extent along the ranked axis > highest density.
|
||||
/// </summary>
|
||||
public abstract class RemnantComparer : IFillComparer
|
||||
{
|
||||
/// <summary>The lower edge of a bounding box along the ranked axis.</summary>
|
||||
protected abstract double Low(Box box);
|
||||
|
||||
/// <summary>The upper edge of a bounding box along the ranked axis.</summary>
|
||||
protected abstract double High(Box box);
|
||||
|
||||
public bool IsBetter(List<Part> candidate, List<Part> current, Box workArea)
|
||||
{
|
||||
if (candidate == null || candidate.Count == 0)
|
||||
return false;
|
||||
|
||||
if (current == null || current.Count == 0)
|
||||
return true;
|
||||
|
||||
if (candidate.Count != current.Count)
|
||||
return candidate.Count > current.Count;
|
||||
|
||||
var candExtent = Extent(candidate);
|
||||
var currExtent = Extent(current);
|
||||
|
||||
if (!candExtent.IsEqualTo(currExtent))
|
||||
return candExtent < currExtent;
|
||||
|
||||
return FillScore.Compute(candidate, workArea).Density
|
||||
> FillScore.Compute(current, workArea).Density;
|
||||
}
|
||||
|
||||
private double Extent(List<Part> parts)
|
||||
{
|
||||
var min = double.MaxValue;
|
||||
var max = double.MinValue;
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var bb = part.BoundingBox;
|
||||
var low = Low(bb);
|
||||
var high = High(bb);
|
||||
if (low < min)
|
||||
min = low;
|
||||
if (high > max)
|
||||
max = high;
|
||||
}
|
||||
|
||||
return max - min;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
@@ -8,44 +6,10 @@ namespace OpenNest.Engine.Fill
|
||||
/// Ranks fill results to minimize X-extent (preserve right-side vertical remnant).
|
||||
/// Tiebreak chain: count > smallest X-extent > highest density.
|
||||
/// </summary>
|
||||
public class VerticalRemnantComparer : IFillComparer
|
||||
public class VerticalRemnantComparer : RemnantComparer
|
||||
{
|
||||
public bool IsBetter(List<Part> candidate, List<Part> current, Box workArea)
|
||||
{
|
||||
if (candidate == null || candidate.Count == 0)
|
||||
return false;
|
||||
protected override double Low(Box box) => box.Left;
|
||||
|
||||
if (current == null || current.Count == 0)
|
||||
return true;
|
||||
|
||||
if (candidate.Count != current.Count)
|
||||
return candidate.Count > current.Count;
|
||||
|
||||
var candExtent = XExtent(candidate);
|
||||
var currExtent = XExtent(current);
|
||||
|
||||
if (!candExtent.IsEqualTo(currExtent))
|
||||
return candExtent < currExtent;
|
||||
|
||||
return FillScore.Compute(candidate, workArea).Density
|
||||
> FillScore.Compute(current, workArea).Density;
|
||||
}
|
||||
|
||||
private static double XExtent(List<Part> parts)
|
||||
{
|
||||
var minX = double.MaxValue;
|
||||
var maxX = double.MinValue;
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var bb = part.BoundingBox;
|
||||
if (bb.Left < minX)
|
||||
minX = bb.Left;
|
||||
if (bb.Right > maxX)
|
||||
maxX = bb.Right;
|
||||
}
|
||||
|
||||
return maxX - minX;
|
||||
}
|
||||
protected override double High(Box box) => box.Right;
|
||||
}
|
||||
}
|
||||
@@ -83,12 +83,12 @@ public sealed class ClampsForm : Form
|
||||
);
|
||||
|
||||
var size = new TableLayoutPanel { AutoSize = true, ColumnCount = 3, Dock = DockStyle.Top };
|
||||
widthBox = NewLengthBox(Editor.Width, minimum: 0.0001m);
|
||||
depthBox = NewLengthBox(Editor.Depth, minimum: 0.0001m);
|
||||
removalMinutesBox = NewLengthBox(Editor.RemovalMinutes, minimum: 0m);
|
||||
AddRow(size, "Clamp width (along the edge):", widthBox, unit);
|
||||
AddRow(size, "Clamp depth (into the plate):", depthBox, unit);
|
||||
AddRow(size, "Minutes to remove and replace one clamp:", removalMinutesBox, "min");
|
||||
widthBox = SettingsRows.NewLengthBox(Editor.Width, minimum: 0.0001m);
|
||||
depthBox = SettingsRows.NewLengthBox(Editor.Depth, minimum: 0.0001m);
|
||||
removalMinutesBox = SettingsRows.NewLengthBox(Editor.RemovalMinutes, minimum: 0m);
|
||||
SettingsRows.AddRow(size, "Clamp width (along the edge):", widthBox, unit);
|
||||
SettingsRows.AddRow(size, "Clamp depth (into the plate):", depthBox, unit);
|
||||
SettingsRows.AddRow(size, "Minutes to remove and replace one clamp:", removalMinutesBox, "min");
|
||||
ownClearanceBox = new CheckBox
|
||||
{
|
||||
Text = "Clearance from parts:",
|
||||
@@ -96,7 +96,7 @@ public sealed class ClampsForm : Form
|
||||
Checked = Editor.Clearance.HasValue,
|
||||
Padding = new Padding(0, 3, 6, 0),
|
||||
};
|
||||
clearanceBox = NewLengthBox(Editor.Clearance ?? 0, minimum: 0m);
|
||||
clearanceBox = SettingsRows.NewLengthBox(Editor.Clearance ?? 0, minimum: 0m);
|
||||
clearanceBox.Enabled = ownClearanceBox.Checked;
|
||||
ownClearanceBox.CheckedChanged += (_, _) => clearanceBox.Enabled = ownClearanceBox.Checked;
|
||||
size.Controls.Add(ownClearanceBox);
|
||||
@@ -117,7 +117,7 @@ public sealed class ClampsForm : Form
|
||||
AutoSize = true,
|
||||
WrapContents = false,
|
||||
};
|
||||
centerBox = NewLengthBox(0, minimum: 0m);
|
||||
centerBox = SettingsRows.NewLengthBox(0, minimum: 0m);
|
||||
edgeBox = new ComboBox { DropDownStyle = ComboBoxStyle.DropDownList, Width = 120 };
|
||||
foreach (var edge in ClampEdgeText.All)
|
||||
edgeBox.Items.Add(ClampEdgeText.Name(edge));
|
||||
@@ -209,34 +209,6 @@ public sealed class ClampsForm : Form
|
||||
/// <summary>The edited rail; the caller applies it to the nest after OK.</summary>
|
||||
internal ClampRailEditor Editor { get; }
|
||||
|
||||
private static NumericUpDown NewLengthBox(double value, decimal minimum)
|
||||
{
|
||||
var box = new NumericUpDown
|
||||
{
|
||||
DecimalPlaces = 4,
|
||||
Minimum = minimum,
|
||||
Maximum = 100000,
|
||||
Increment = 0.125m,
|
||||
Width = 110,
|
||||
};
|
||||
box.Value = ToBoxValue(box, value);
|
||||
return box;
|
||||
}
|
||||
|
||||
private static decimal ToBoxValue(NumericUpDown box, double value)
|
||||
{
|
||||
if (!double.IsFinite(value) || value < (double)box.Minimum)
|
||||
return box.Minimum;
|
||||
return value > (double)box.Maximum ? box.Maximum : (decimal)value;
|
||||
}
|
||||
|
||||
private static void AddRow(TableLayoutPanel table, string label, Control box, string unit)
|
||||
{
|
||||
table.Controls.Add(new Label { Text = label, AutoSize = true, Padding = new Padding(0, 6, 6, 0) });
|
||||
table.Controls.Add(box);
|
||||
table.Controls.Add(new Label { Text = unit, AutoSize = true, Padding = new Padding(0, 6, 0, 0) });
|
||||
}
|
||||
|
||||
private void ReadSizes()
|
||||
{
|
||||
Editor.Width = (double)widthBox.Value;
|
||||
@@ -282,11 +254,11 @@ public sealed class ClampsForm : Form
|
||||
private void LoadMachineDefaults()
|
||||
{
|
||||
Editor.LoadMachineDefaults(machineDefaults);
|
||||
widthBox.Value = ToBoxValue(widthBox, Editor.Width);
|
||||
depthBox.Value = ToBoxValue(depthBox, Editor.Depth);
|
||||
removalMinutesBox.Value = ToBoxValue(removalMinutesBox, Editor.RemovalMinutes);
|
||||
widthBox.Value = SettingsRows.ToBoxValue(widthBox, Editor.Width);
|
||||
depthBox.Value = SettingsRows.ToBoxValue(depthBox, Editor.Depth);
|
||||
removalMinutesBox.Value = SettingsRows.ToBoxValue(removalMinutesBox, Editor.RemovalMinutes);
|
||||
ownClearanceBox.Checked = Editor.Clearance.HasValue;
|
||||
clearanceBox.Value = ToBoxValue(clearanceBox, Editor.Clearance ?? 0);
|
||||
clearanceBox.Value = SettingsRows.ToBoxValue(clearanceBox, Editor.Clearance ?? 0);
|
||||
applyToNewNestsBox.Checked = Editor.ApplyToNewNests;
|
||||
RefreshCenters();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace OpenNest.Forms;
|
||||
|
||||
/// <summary>
|
||||
/// Label / value box / unit rows shared by the machine-settings dialogs built in code
|
||||
/// (Clamps, Support Grid).
|
||||
/// </summary>
|
||||
internal static class SettingsRows
|
||||
{
|
||||
public static NumericUpDown NewLengthBox(double value, decimal minimum)
|
||||
{
|
||||
var box = new NumericUpDown
|
||||
{
|
||||
DecimalPlaces = 4,
|
||||
Minimum = minimum,
|
||||
Maximum = 100000,
|
||||
Increment = 0.125m,
|
||||
Width = 110,
|
||||
};
|
||||
box.Value = ToBoxValue(box, value);
|
||||
return box;
|
||||
}
|
||||
|
||||
/// <summary>The value clamped to the box's range; non-finite values become the minimum.</summary>
|
||||
public static decimal ToBoxValue(NumericUpDown box, double value)
|
||||
{
|
||||
if (!double.IsFinite(value) || value < (double)box.Minimum)
|
||||
return box.Minimum;
|
||||
return value > (double)box.Maximum ? box.Maximum : (decimal)value;
|
||||
}
|
||||
|
||||
public static void AddRow(TableLayoutPanel table, string label, Control box, string unit)
|
||||
{
|
||||
table.Controls.Add(new Label { Text = label, AutoSize = true, Padding = new Padding(0, 6, 6, 0) });
|
||||
table.Controls.Add(box);
|
||||
table.Controls.Add(new Label { Text = unit, AutoSize = true, Padding = new Padding(0, 6, 0, 0) });
|
||||
}
|
||||
}
|
||||
@@ -581,39 +581,8 @@ public partial class SplitDrawingForm : Form
|
||||
Color.FromArgb(40, 128, 222, 234),
|
||||
};
|
||||
|
||||
private List<Box> BuildPreviewRegions()
|
||||
{
|
||||
var verticals = _splitLines
|
||||
.Where(l => l.Axis == CutOffAxis.Vertical)
|
||||
.OrderBy(l => l.Position)
|
||||
.ToList();
|
||||
var horizontals = _splitLines
|
||||
.Where(l => l.Axis == CutOffAxis.Horizontal)
|
||||
.OrderBy(l => l.Position)
|
||||
.ToList();
|
||||
|
||||
var xEdges = new List<double> { _drawingBounds.Left };
|
||||
xEdges.AddRange(verticals.Select(v => v.Position));
|
||||
xEdges.Add(_drawingBounds.Right);
|
||||
|
||||
var yEdges = new List<double> { _drawingBounds.Bottom };
|
||||
yEdges.AddRange(horizontals.Select(h => h.Position));
|
||||
yEdges.Add(_drawingBounds.Top);
|
||||
|
||||
var regions = new List<Box>();
|
||||
for (var yi = 0; yi < yEdges.Count - 1; yi++)
|
||||
for (var xi = 0; xi < xEdges.Count - 1; xi++)
|
||||
regions.Add(
|
||||
new Box(
|
||||
xEdges[xi],
|
||||
yEdges[yi],
|
||||
xEdges[xi + 1] - xEdges[xi],
|
||||
yEdges[yi + 1] - yEdges[yi]
|
||||
)
|
||||
);
|
||||
|
||||
return regions;
|
||||
}
|
||||
private List<Box> BuildPreviewRegions() =>
|
||||
DrawingSplitter.BuildClipRegions(_splitLines, _drawingBounds);
|
||||
|
||||
// --- OK/Cancel ---
|
||||
|
||||
|
||||
@@ -80,34 +80,34 @@ public sealed class SupportGridForm : Form
|
||||
layout.Controls.Add(kinds);
|
||||
|
||||
var gridValues = new TableLayoutPanel { AutoSize = true, ColumnCount = 3 };
|
||||
pitchXBox = NewLengthBox(initial.PitchX, 0.0001m);
|
||||
pitchYBox = NewLengthBox(initial.PitchY, 0.0001m);
|
||||
offsetXBox = NewLengthBox(initial.OffsetX, -100000m);
|
||||
offsetYBox = NewLengthBox(initial.OffsetY, -100000m);
|
||||
AddRow(gridValues, "Pitch along X:", pitchXBox, unit);
|
||||
AddRow(gridValues, "Pitch along Y:", pitchYBox, unit);
|
||||
AddRow(gridValues, "First point, X from the origin:", offsetXBox, unit);
|
||||
AddRow(gridValues, "First point, Y from the origin:", offsetYBox, unit);
|
||||
pitchXBox = SettingsRows.NewLengthBox(initial.PitchX, 0.0001m);
|
||||
pitchYBox = SettingsRows.NewLengthBox(initial.PitchY, 0.0001m);
|
||||
offsetXBox = SettingsRows.NewLengthBox(initial.OffsetX, -100000m);
|
||||
offsetYBox = SettingsRows.NewLengthBox(initial.OffsetY, -100000m);
|
||||
SettingsRows.AddRow(gridValues, "Pitch along X:", pitchXBox, unit);
|
||||
SettingsRows.AddRow(gridValues, "Pitch along Y:", pitchYBox, unit);
|
||||
SettingsRows.AddRow(gridValues, "First point, X from the origin:", offsetXBox, unit);
|
||||
SettingsRows.AddRow(gridValues, "First point, Y from the origin:", offsetYBox, unit);
|
||||
layout.Controls.Add(gridValues);
|
||||
|
||||
var slatValues = new TableLayoutPanel { AutoSize = true, ColumnCount = 3 };
|
||||
slatAxisBox = new ComboBox { DropDownStyle = ComboBoxStyle.DropDownList, Width = 200 };
|
||||
slatAxisBox.Items.AddRange(new object[] { "Along X (the full length)", "Along Y (the full width)" });
|
||||
slatAxisBox.SelectedIndex = initial.SlatAxis == SlatAxis.X ? 0 : 1;
|
||||
slatPitchBox = NewLengthBox(initial.SlatPitch, 0.0001m);
|
||||
slatThicknessBox = NewLengthBox(initial.SlatThickness, 0m);
|
||||
slatOffsetBox = NewLengthBox(initial.SlatOffset, -100000m);
|
||||
AddRow(slatValues, "Slats run:", slatAxisBox, "");
|
||||
AddRow(slatValues, "Pitch between slats:", slatPitchBox, unit);
|
||||
AddRow(slatValues, "Slat thickness:", slatThicknessBox, unit);
|
||||
AddRow(slatValues, "First slat's center from the origin:", slatOffsetBox, unit);
|
||||
slatPitchBox = SettingsRows.NewLengthBox(initial.SlatPitch, 0.0001m);
|
||||
slatThicknessBox = SettingsRows.NewLengthBox(initial.SlatThickness, 0m);
|
||||
slatOffsetBox = SettingsRows.NewLengthBox(initial.SlatOffset, -100000m);
|
||||
SettingsRows.AddRow(slatValues, "Slats run:", slatAxisBox, "");
|
||||
SettingsRows.AddRow(slatValues, "Pitch between slats:", slatPitchBox, unit);
|
||||
SettingsRows.AddRow(slatValues, "Slat thickness:", slatThicknessBox, unit);
|
||||
SettingsRows.AddRow(slatValues, "First slat's center from the origin:", slatOffsetBox, unit);
|
||||
layout.Controls.Add(slatValues);
|
||||
|
||||
var sharedValues = new TableLayoutPanel { AutoSize = true, ColumnCount = 3 };
|
||||
toleranceBox = NewLengthBox(initial.RegistrationTolerance, 0m);
|
||||
marginBox = NewLengthBox(initial.StabilityMargin, 0m);
|
||||
AddRow(sharedValues, "Registration tolerance:", toleranceBox, unit);
|
||||
AddRow(sharedValues, "Stability margin:", marginBox, unit);
|
||||
toleranceBox = SettingsRows.NewLengthBox(initial.RegistrationTolerance, 0m);
|
||||
marginBox = SettingsRows.NewLengthBox(initial.StabilityMargin, 0m);
|
||||
SettingsRows.AddRow(sharedValues, "Registration tolerance:", toleranceBox, unit);
|
||||
SettingsRows.AddRow(sharedValues, "Stability margin:", marginBox, unit);
|
||||
layout.Controls.Add(sharedValues);
|
||||
|
||||
var defaultsGroup = new GroupBox
|
||||
@@ -211,18 +211,18 @@ public sealed class SupportGridForm : Form
|
||||
|
||||
private void ShowValues(SupportGridSettings settings)
|
||||
{
|
||||
pitchXBox.Value = ToBoxValue(pitchXBox, settings.PitchX);
|
||||
pitchYBox.Value = ToBoxValue(pitchYBox, settings.PitchY);
|
||||
offsetXBox.Value = ToBoxValue(offsetXBox, settings.OffsetX);
|
||||
offsetYBox.Value = ToBoxValue(offsetYBox, settings.OffsetY);
|
||||
pitchXBox.Value = SettingsRows.ToBoxValue(pitchXBox, settings.PitchX);
|
||||
pitchYBox.Value = SettingsRows.ToBoxValue(pitchYBox, settings.PitchY);
|
||||
offsetXBox.Value = SettingsRows.ToBoxValue(offsetXBox, settings.OffsetX);
|
||||
offsetYBox.Value = SettingsRows.ToBoxValue(offsetYBox, settings.OffsetY);
|
||||
gridButton.Checked = !settings.UseSlats;
|
||||
slatsButton.Checked = settings.UseSlats;
|
||||
slatAxisBox.SelectedIndex = settings.SlatAxis == SlatAxis.X ? 0 : 1;
|
||||
slatPitchBox.Value = ToBoxValue(slatPitchBox, settings.SlatPitch);
|
||||
slatThicknessBox.Value = ToBoxValue(slatThicknessBox, settings.SlatThickness);
|
||||
slatOffsetBox.Value = ToBoxValue(slatOffsetBox, settings.SlatOffset);
|
||||
toleranceBox.Value = ToBoxValue(toleranceBox, settings.RegistrationTolerance);
|
||||
marginBox.Value = ToBoxValue(marginBox, settings.StabilityMargin);
|
||||
slatPitchBox.Value = SettingsRows.ToBoxValue(slatPitchBox, settings.SlatPitch);
|
||||
slatThicknessBox.Value = SettingsRows.ToBoxValue(slatThicknessBox, settings.SlatThickness);
|
||||
slatOffsetBox.Value = SettingsRows.ToBoxValue(slatOffsetBox, settings.SlatOffset);
|
||||
toleranceBox.Value = SettingsRows.ToBoxValue(toleranceBox, settings.RegistrationTolerance);
|
||||
marginBox.Value = SettingsRows.ToBoxValue(marginBox, settings.StabilityMargin);
|
||||
applyToNewNestsBox.Checked = settings.Enabled;
|
||||
}
|
||||
|
||||
@@ -266,32 +266,4 @@ public sealed class SupportGridForm : Form
|
||||
}
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
private static NumericUpDown NewLengthBox(double value, decimal minimum)
|
||||
{
|
||||
var box = new NumericUpDown
|
||||
{
|
||||
DecimalPlaces = 4,
|
||||
Minimum = minimum,
|
||||
Maximum = 100000,
|
||||
Increment = 0.125m,
|
||||
Width = 110,
|
||||
};
|
||||
box.Value = ToBoxValue(box, value);
|
||||
return box;
|
||||
}
|
||||
|
||||
private static decimal ToBoxValue(NumericUpDown box, double value)
|
||||
{
|
||||
if (!double.IsFinite(value) || value < (double)box.Minimum)
|
||||
return box.Minimum;
|
||||
return value > (double)box.Maximum ? box.Maximum : (decimal)value;
|
||||
}
|
||||
|
||||
private static void AddRow(TableLayoutPanel table, string label, Control box, string unit)
|
||||
{
|
||||
table.Controls.Add(new Label { Text = label, AutoSize = true, Padding = new Padding(0, 6, 6, 0) });
|
||||
table.Controls.Add(box);
|
||||
table.Controls.Add(new Label { Text = unit, AutoSize = true, Padding = new Padding(0, 6, 0, 0) });
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user