mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 13:51:06 -04:00
Model the machine's support bed as a grid of contact points measured from the plate origin, and check each part's center of gravity against the convex hull of the grid points under its material. The bed is extensible so straight and CAD-imported curved slats can follow. - Core: SupportBed/SupportGrid, SupportMaterial and SupportAnalyzer (margin with registration tolerance, best single tab point) - Nest-wide support bed that every plate follows, saved in plateDefaults.supportBed; machine defaults in support-grid.json - CuttingParameters.SupportTabs: Plan Cutting tabs only unsupported parts and limits their perimeter entries to points where the tab steadies the part, falling back to any fitting entry - Desktop: Nest > Support Grid..., View > Draw Support overlay and a "Tab parts the support grid cannot hold level" tab option - docs/support-tabs.md, nest file format and planner notes 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;
|
|
}
|
|
}
|