mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-30 13:01:07 -04:00
feat(support): add slat support beds alongside the grid
Slats are strips of a set thickness running the full length or width of the table, one every pitch. A part's contacts are the corners of where its material crosses a slat, so the support check and support tabs work as on the grid. Nest > Support Bed... picks grid or slats; nest files store type "slats" and support-grid.json keeps both layouts' values. Draw Support shows the slats cut off to the sheet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cd1961e26b
commit
48863d51c8
17 files changed
+715
-106
No files matched your search
@@ -0,0 +1,12 @@
|
||||
namespace OpenNest.Support
|
||||
{
|
||||
/// <summary>The axis a <see cref="SupportSlats"/> bed's slats run along, the full length or width of the table.</summary>
|
||||
public enum SlatAxis
|
||||
{
|
||||
/// <summary>Slats run along X, the table's full length; they repeat along Y.</summary>
|
||||
X,
|
||||
|
||||
/// <summary>Slats run along Y, the table's full width; they repeat along X.</summary>
|
||||
Y,
|
||||
}
|
||||
}
|
||||
@@ -182,6 +182,25 @@ namespace OpenNest.Support
|
||||
return spans;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The spans of the vertical line at <paramref name="x"/> that lie in material, in
|
||||
/// ascending order, on the same half-open rule as <see cref="SpansAtY"/>.
|
||||
/// </summary>
|
||||
public IReadOnlyList<(double Start, double End)> SpansAtX(double x)
|
||||
{
|
||||
var crossings = new List<double>();
|
||||
if (!IsComplete || x < bounds.Left || x > bounds.Right)
|
||||
return Array.Empty<(double, double)>();
|
||||
AddCrossingsAtX(outline, x, crossings);
|
||||
foreach (var hole in holes)
|
||||
AddCrossingsAtX(hole, x, crossings);
|
||||
crossings.Sort();
|
||||
var spans = new List<(double, double)>(crossings.Count / 2);
|
||||
for (var i = 0; i + 1 < crossings.Count; i += 2)
|
||||
spans.Add((crossings[i], crossings[i + 1]));
|
||||
return spans;
|
||||
}
|
||||
|
||||
/// <summary>Whether <paramref name="point"/> lies in material (even-odd over outline and holes).</summary>
|
||||
public bool Contains(Vector point)
|
||||
{
|
||||
@@ -205,6 +224,17 @@ namespace OpenNest.Support
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddCrossingsAtX(Vector[] ring, double x, List<double> crossings)
|
||||
{
|
||||
for (int i = 0, j = ring.Length - 1; i < ring.Length; j = i++)
|
||||
{
|
||||
var a = ring[j];
|
||||
var b = ring[i];
|
||||
if ((a.X > x) != (b.X > x))
|
||||
crossings.Add(a.Y + (x - a.X) * (b.Y - a.Y) / (b.X - a.X));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool Inside(Vector[] ring, Vector point)
|
||||
{
|
||||
var inside = false;
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Support
|
||||
{
|
||||
/// <summary>
|
||||
/// A bed of straight slats, as on a plasma table: each slat a rectangle <see cref="Thickness"/>
|
||||
/// wide running the full length of the table along <see cref="Axis"/>, one every
|
||||
/// <see cref="Pitch"/> across it, the first centered <see cref="Offset"/> from the plate origin
|
||||
/// into the sheet. A sheet in another quadrant sees the same slats mirrored. A part rests on
|
||||
/// the region where its material crosses a slat; the corners of that region (where the
|
||||
/// outline crosses a slat's edges, and outline vertices on the slat) are its contacts.
|
||||
/// Lengths are in the nest's units.
|
||||
/// </summary>
|
||||
public sealed class SupportSlats : SupportBed
|
||||
{
|
||||
public SupportSlats(SlatAxis axis, double pitch, double thickness, double offset = 0,
|
||||
double registrationTolerance = 0, double stabilityMargin = 0)
|
||||
: base(registrationTolerance, stabilityMargin)
|
||||
{
|
||||
if (axis is not (SlatAxis.X or SlatAxis.Y))
|
||||
throw new ArgumentOutOfRangeException(nameof(axis), "Slats run along X or Y.");
|
||||
if (!double.IsFinite(pitch) || pitch <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(pitch), "Slat pitch must be finite and positive.");
|
||||
if (!double.IsFinite(thickness) || thickness < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(thickness), "Slat thickness must be finite and zero or more.");
|
||||
if (!double.IsFinite(offset))
|
||||
throw new ArgumentOutOfRangeException(nameof(offset), "Slat offset must be finite.");
|
||||
Axis = axis;
|
||||
Pitch = pitch;
|
||||
Thickness = thickness;
|
||||
Offset = offset;
|
||||
}
|
||||
|
||||
public SlatAxis Axis { get; }
|
||||
|
||||
/// <summary>The distance between neighboring slats' center lines.</summary>
|
||||
public double Pitch { get; }
|
||||
|
||||
/// <summary>How wide each slat's top edge is; zero treats a slat as a line.</summary>
|
||||
public double Thickness { get; }
|
||||
|
||||
/// <summary>The first slat's center line, measured from the plate origin into the sheet.</summary>
|
||||
public double Offset { get; }
|
||||
|
||||
public override IReadOnlyList<Vector> Contacts(SupportMaterial material, int quadrant, Vector shift,
|
||||
bool extremesOnly = false)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(material);
|
||||
var contacts = new List<Vector>();
|
||||
if (!material.IsComplete)
|
||||
return contacts;
|
||||
|
||||
// Work across the slats: Y for slats along X, X for slats along Y.
|
||||
var alongX = Axis == SlatAxis.X;
|
||||
var baseline = alongX ? SignY(quadrant) * Offset + shift.Y : SignX(quadrant) * Offset + shift.X;
|
||||
var bounds = material.Bounds;
|
||||
var low = alongX ? bounds.Bottom : bounds.Left;
|
||||
var high = alongX ? bounds.Top : bounds.Right;
|
||||
var half = Thickness / 2;
|
||||
var first = (long)System.Math.Ceiling((low - half - baseline) / Pitch);
|
||||
var last = (long)System.Math.Floor((high + half - baseline) / Pitch);
|
||||
if (first > last)
|
||||
return contacts;
|
||||
|
||||
// Where the outline crosses each slat's edges.
|
||||
for (var slat = first; slat <= last; slat++)
|
||||
{
|
||||
var center = baseline + slat * Pitch;
|
||||
AddEdge(material, alongX, center - half, contacts);
|
||||
if (half > 0)
|
||||
AddEdge(material, alongX, center + half, contacts);
|
||||
}
|
||||
|
||||
// Outline and hole vertices that sit on a slat: every vertex on one slat's top.
|
||||
AddVertices(material.Outline, alongX, baseline, half, contacts);
|
||||
foreach (var hole in material.Holes)
|
||||
AddVertices(hole, alongX, baseline, half, contacts);
|
||||
return contacts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The slats within <paramref name="region"/> (world coordinates) on a sheet in
|
||||
/// <paramref name="quadrant"/>, each cut off to the region, for display; empty when there
|
||||
/// would be more than <paramref name="limit"/>.
|
||||
/// </summary>
|
||||
public IReadOnlyList<Box> SlatsIn(Box region, int quadrant, int limit = 5000)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(region);
|
||||
var alongX = Axis == SlatAxis.X;
|
||||
var baseline = alongX ? SignY(quadrant) * Offset : SignX(quadrant) * Offset;
|
||||
var low = alongX ? region.Bottom : region.Left;
|
||||
var high = alongX ? region.Top : region.Right;
|
||||
var half = Thickness / 2;
|
||||
var first = System.Math.Ceiling((low - half - baseline) / Pitch);
|
||||
var last = System.Math.Floor((high + half - baseline) / Pitch);
|
||||
var count = last - first + 1;
|
||||
if (!double.IsFinite(count) || count <= 0 || count > limit)
|
||||
return Array.Empty<Box>();
|
||||
|
||||
var slats = new List<Box>((int)count);
|
||||
for (var slat = first; slat <= last; slat++)
|
||||
{
|
||||
var center = baseline + slat * Pitch;
|
||||
var start = System.Math.Max(low, center - half);
|
||||
var end = System.Math.Min(high, center + half);
|
||||
slats.Add(alongX
|
||||
? new Box(region.Left, start, region.Length, end - start)
|
||||
: new Box(start, region.Bottom, end - start, region.Width));
|
||||
}
|
||||
return slats;
|
||||
}
|
||||
|
||||
public override SupportBed Scaled(double factor) =>
|
||||
new SupportSlats(Axis, Pitch * factor, Thickness * factor, Offset * factor,
|
||||
RegistrationTolerance * factor, StabilityMargin * factor);
|
||||
|
||||
public override bool Equals(SupportBed other) =>
|
||||
other is SupportSlats slats
|
||||
&& slats.Axis == Axis && slats.Pitch == Pitch
|
||||
&& slats.Thickness == Thickness && slats.Offset == Offset
|
||||
&& slats.RegistrationTolerance == RegistrationTolerance
|
||||
&& slats.StabilityMargin == StabilityMargin;
|
||||
|
||||
public override int GetHashCode() =>
|
||||
HashCode.Combine(Axis, Pitch, Thickness, Offset, RegistrationTolerance, StabilityMargin);
|
||||
|
||||
private static void AddEdge(SupportMaterial material, bool alongX, double across, List<Vector> contacts)
|
||||
{
|
||||
var spans = alongX ? material.SpansAtY(across) : material.SpansAtX(across);
|
||||
foreach (var (start, end) in spans)
|
||||
{
|
||||
contacts.Add(alongX ? new Vector(start, across) : new Vector(across, start));
|
||||
contacts.Add(alongX ? new Vector(end, across) : new Vector(across, end));
|
||||
}
|
||||
}
|
||||
|
||||
private void AddVertices(IReadOnlyList<Vector> ring, bool alongX, double baseline, double half,
|
||||
List<Vector> contacts)
|
||||
{
|
||||
foreach (var vertex in ring)
|
||||
{
|
||||
var across = alongX ? vertex.Y : vertex.X;
|
||||
// The nearest slat center; the vertex is on that slat when within half its thickness.
|
||||
var center = baseline + System.Math.Round((across - baseline) / Pitch) * Pitch;
|
||||
if (System.Math.Abs(across - center) <= half)
|
||||
contacts.Add(vertex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,9 @@ 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,
|
||||
/// The machine's support bed, a laser grid of points or a plasma table's slats, persisted to a
|
||||
/// single JSON file (by default %APPDATA%\OpenNest\support-grid.json). Both layouts' values are
|
||||
/// kept; <see cref="UseSlats"/> picks one. 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>
|
||||
@@ -31,6 +32,15 @@ public sealed class SupportGridSettings
|
||||
public double PitchY { get; set; } = 2;
|
||||
public double OffsetX { get; set; }
|
||||
public double OffsetY { get; set; }
|
||||
|
||||
/// <summary>Whether the bed is slats rather than the grid of points.</summary>
|
||||
public bool UseSlats { get; set; }
|
||||
|
||||
public SlatAxis SlatAxis { get; set; } = SlatAxis.Y;
|
||||
public double SlatPitch { get; set; } = 4;
|
||||
public double SlatThickness { get; set; } = 0.125;
|
||||
public double SlatOffset { get; set; }
|
||||
|
||||
public double RegistrationTolerance { get; set; } = 0.125;
|
||||
public double StabilityMargin { get; set; } = 0.25;
|
||||
|
||||
@@ -42,31 +52,50 @@ public sealed class SupportGridSettings
|
||||
"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)
|
||||
/// <summary>
|
||||
/// Settings holding <paramref name="bed"/> in <paramref name="units"/>; the other layout's
|
||||
/// values come from <paramref name="others"/> (or the defaults when null).
|
||||
/// </summary>
|
||||
public static SupportGridSettings FromBed(SupportBed bed, Units units, bool enabled, SupportGridSettings others = null)
|
||||
{
|
||||
var settings = new SupportGridSettings().ConvertedTo(units);
|
||||
var settings = (others ?? new SupportGridSettings()).ConvertedTo(units);
|
||||
settings.Enabled = enabled;
|
||||
if (grid != null)
|
||||
if (bed != 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;
|
||||
settings.RegistrationTolerance = bed.RegistrationTolerance;
|
||||
settings.StabilityMargin = bed.StabilityMargin;
|
||||
}
|
||||
switch (bed)
|
||||
{
|
||||
case SupportGrid grid:
|
||||
settings.UseSlats = false;
|
||||
settings.PitchX = grid.PitchX;
|
||||
settings.PitchY = grid.PitchY;
|
||||
settings.OffsetX = grid.OffsetX;
|
||||
settings.OffsetY = grid.OffsetY;
|
||||
break;
|
||||
case SupportSlats slats:
|
||||
settings.UseSlats = true;
|
||||
settings.SlatAxis = slats.Axis;
|
||||
settings.SlatPitch = slats.Pitch;
|
||||
settings.SlatThickness = slats.Thickness;
|
||||
settings.SlatOffset = slats.Offset;
|
||||
break;
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
/// <summary>The grid these settings describe in <paramref name="units"/>, or null when it is invalid.</summary>
|
||||
public SupportGrid? GridFor(Units units)
|
||||
/// <summary>The bed these settings describe in <paramref name="units"/>, or null when it is invalid.</summary>
|
||||
public SupportBed? BedFor(Units units)
|
||||
{
|
||||
var converted = ConvertedTo(units);
|
||||
try
|
||||
{
|
||||
return new SupportGrid(converted.PitchX, converted.PitchY, converted.OffsetX, converted.OffsetY,
|
||||
converted.RegistrationTolerance, converted.StabilityMargin);
|
||||
return UseSlats
|
||||
? new SupportSlats(converted.SlatAxis, converted.SlatPitch, converted.SlatThickness,
|
||||
converted.SlatOffset, converted.RegistrationTolerance, converted.StabilityMargin)
|
||||
: new SupportGrid(converted.PitchX, converted.PitchY, converted.OffsetX, converted.OffsetY,
|
||||
converted.RegistrationTolerance, converted.StabilityMargin);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
@@ -86,17 +115,22 @@ public sealed class SupportGridSettings
|
||||
PitchY = PitchY * scale,
|
||||
OffsetX = OffsetX * scale,
|
||||
OffsetY = OffsetY * scale,
|
||||
UseSlats = UseSlats,
|
||||
SlatAxis = SlatAxis,
|
||||
SlatPitch = SlatPitch * scale,
|
||||
SlatThickness = SlatThickness * scale,
|
||||
SlatOffset = SlatOffset * scale,
|
||||
RegistrationTolerance = RegistrationTolerance * scale,
|
||||
StabilityMargin = StabilityMargin * scale,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Gives a newly created nest this grid when enabled (after its units are set).</summary>
|
||||
/// <summary>Gives a newly created nest this bed 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);
|
||||
if (Enabled && BedFor(nest.Units) is { } bed)
|
||||
nest.SetSupportBed(bed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -128,6 +162,15 @@ public sealed class SupportGridSettings
|
||||
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);
|
||||
if (root.TryGetProperty("layout", out var layout) && layout.ValueKind == JsonValueKind.String)
|
||||
settings.UseSlats = string.Equals(layout.GetString(), "slats", StringComparison.OrdinalIgnoreCase);
|
||||
if (root.TryGetProperty("slatAxis", out var axis) && axis.ValueKind == JsonValueKind.String
|
||||
&& Enum.TryParse<SlatAxis>(axis.GetString(), ignoreCase: true, out var slatAxis)
|
||||
&& Enum.IsDefined(slatAxis))
|
||||
settings.SlatAxis = slatAxis;
|
||||
settings.SlatPitch = Length(root, "slatPitch", settings.SlatPitch, positive: true);
|
||||
settings.SlatThickness = Length(root, "slatThickness", settings.SlatThickness, positive: false);
|
||||
settings.SlatOffset = Length(root, "slatOffset", settings.SlatOffset, positive: false, signed: true);
|
||||
settings.RegistrationTolerance = Length(root, "registrationTolerance", settings.RegistrationTolerance, positive: false);
|
||||
settings.StabilityMargin = Length(root, "stabilityMargin", settings.StabilityMargin, positive: false);
|
||||
return settings;
|
||||
@@ -151,6 +194,11 @@ public sealed class SupportGridSettings
|
||||
["pitchY"] = PitchY,
|
||||
["offsetX"] = OffsetX,
|
||||
["offsetY"] = OffsetY,
|
||||
["layout"] = UseSlats ? "slats" : "grid",
|
||||
["slatAxis"] = SlatAxis.ToString().ToLowerInvariant(),
|
||||
["slatPitch"] = SlatPitch,
|
||||
["slatThickness"] = SlatThickness,
|
||||
["slatOffset"] = SlatOffset,
|
||||
["registrationTolerance"] = RegistrationTolerance,
|
||||
["stabilityMargin"] = StabilityMargin,
|
||||
};
|
||||
|
||||
@@ -80,6 +80,35 @@ public class SupportGridPersistenceTests
|
||||
Assert.Equal(-0.25, bed["offsetY"]!.GetValue<double>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SlatsRoundTripWithTheirTypeAndAxis()
|
||||
{
|
||||
var slats = new SupportSlats(SlatAxis.Y, 4, 0.125, 1.5, 0.125, 0.25);
|
||||
var file = Write(NestWith(slats));
|
||||
|
||||
var bed = NestJson(file)["plateDefaults"]!["supportBed"]!;
|
||||
var reopened = new NestReader(file).Read();
|
||||
|
||||
Assert.Equal("slats", bed["type"]!.GetValue<string>());
|
||||
Assert.Equal("y", bed["axis"]!.GetValue<string>());
|
||||
Assert.Equal(0.125, bed["thickness"]!.GetValue<double>());
|
||||
Assert.False(bed.AsObject().ContainsKey("pitchX"));
|
||||
Assert.Equal(slats, reopened.SupportBed);
|
||||
Assert.Same(reopened.SupportBed, Assert.Single(reopened.Plates).SupportBed);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("""{"type":"slats","axis":"z","pitch":4,"thickness":0.125}""")]
|
||||
[InlineData("""{"type":"slats","axis":"x","pitch":0,"thickness":0.125}""")]
|
||||
[InlineData("""{"type":"slats","axis":"x","pitch":4}""")]
|
||||
[InlineData("""{"type":"slats","axis":"x","pitch":4,"thickness":0.125,"offset":"one"}""")]
|
||||
public void MalformedSlats_LoadAsNone(string bed)
|
||||
{
|
||||
var file = Rewrite(Write(NestWith(Grid)), json => json["plateDefaults"]!["supportBed"] = JsonNode.Parse(bed));
|
||||
|
||||
Assert.Null(new NestReader(file).Read().SupportBed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NestWithoutGrid_WritesNoKey()
|
||||
{
|
||||
|
||||
+68
-21
@@ -110,8 +110,10 @@ namespace OpenNest.IO
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A support bed layout. <see cref="Type"/> names the layout: only <c>grid</c>, a regular
|
||||
/// grid of contact points, so far. Lengths are in the nest's units.
|
||||
/// A support bed layout. <see cref="Type"/> names the layout: <c>grid</c>, a regular grid
|
||||
/// of contact points (pitch and offset along X and Y), or <c>slats</c>, straight slats
|
||||
/// running along <see cref="Axis"/> (<see cref="Pitch"/>, <see cref="Thickness"/>,
|
||||
/// <see cref="Offset"/>). Lengths are in the nest's units.
|
||||
/// </summary>
|
||||
public record SupportBedDto
|
||||
{
|
||||
@@ -120,6 +122,10 @@ namespace OpenNest.IO
|
||||
public double PitchY { get; init; }
|
||||
public double OffsetX { get; init; }
|
||||
public double OffsetY { get; init; }
|
||||
public string Axis { get; init; }
|
||||
public double Pitch { get; init; }
|
||||
public double Thickness { get; init; }
|
||||
public double Offset { get; init; }
|
||||
public double RegistrationTolerance { get; init; }
|
||||
public double StabilityMargin { get; init; }
|
||||
}
|
||||
@@ -131,7 +137,9 @@ namespace OpenNest.IO
|
||||
/// </summary>
|
||||
public sealed class LenientSupportBedConverter : JsonConverter<SupportBedDto>
|
||||
{
|
||||
private static readonly string[] Optional = { "offsetX", "offsetY", "registrationTolerance", "stabilityMargin" };
|
||||
private static readonly string[] Shared = { "registrationTolerance", "stabilityMargin" };
|
||||
private static readonly string[] GridOffsets = { "offsetX", "offsetY" };
|
||||
private static readonly string[] SlatOffset = { "offset" };
|
||||
|
||||
public override SupportBedDto Read(ref Utf8JsonReader reader, Type typeToConvert,
|
||||
JsonSerializerOptions options)
|
||||
@@ -140,37 +148,76 @@ namespace OpenNest.IO
|
||||
var bed = document.RootElement;
|
||||
if (bed.ValueKind != JsonValueKind.Object
|
||||
|| !bed.TryGetProperty("type", out var type) || type.ValueKind != JsonValueKind.String
|
||||
|| type.GetString() != "grid"
|
||||
|| !TryNumber(bed, "pitchX", out var pitchX) || !TryNumber(bed, "pitchY", out var pitchY))
|
||||
|| !TryOptional(bed, Shared, out var shared))
|
||||
return null;
|
||||
var values = new double[Optional.Length];
|
||||
for (var i = 0; i < Optional.Length; i++)
|
||||
if (bed.TryGetProperty(Optional[i], out _) && !TryNumber(bed, Optional[i], out values[i]))
|
||||
return null;
|
||||
return new SupportBedDto
|
||||
switch (type.GetString())
|
||||
{
|
||||
PitchX = pitchX,
|
||||
PitchY = pitchY,
|
||||
OffsetX = values[0],
|
||||
OffsetY = values[1],
|
||||
RegistrationTolerance = values[2],
|
||||
StabilityMargin = values[3],
|
||||
};
|
||||
case "grid":
|
||||
if (!TryNumber(bed, "pitchX", out var pitchX) || !TryNumber(bed, "pitchY", out var pitchY)
|
||||
|| !TryOptional(bed, GridOffsets, out var offsets))
|
||||
return null;
|
||||
return new SupportBedDto
|
||||
{
|
||||
PitchX = pitchX,
|
||||
PitchY = pitchY,
|
||||
OffsetX = offsets[0],
|
||||
OffsetY = offsets[1],
|
||||
RegistrationTolerance = shared[0],
|
||||
StabilityMargin = shared[1],
|
||||
};
|
||||
case "slats":
|
||||
if (!bed.TryGetProperty("axis", out var axis) || axis.ValueKind != JsonValueKind.String
|
||||
|| !TryNumber(bed, "pitch", out var pitch) || !TryNumber(bed, "thickness", out var thickness)
|
||||
|| !TryOptional(bed, SlatOffset, out var offset))
|
||||
return null;
|
||||
return new SupportBedDto
|
||||
{
|
||||
Type = "slats",
|
||||
Axis = axis.GetString(),
|
||||
Pitch = pitch,
|
||||
Thickness = thickness,
|
||||
Offset = offset[0],
|
||||
RegistrationTolerance = shared[0],
|
||||
StabilityMargin = shared[1],
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, SupportBedDto value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("type", value.Type);
|
||||
writer.WriteNumber("pitchX", value.PitchX);
|
||||
writer.WriteNumber("pitchY", value.PitchY);
|
||||
writer.WriteNumber("offsetX", value.OffsetX);
|
||||
writer.WriteNumber("offsetY", value.OffsetY);
|
||||
if (value.Type == "slats")
|
||||
{
|
||||
writer.WriteString("axis", value.Axis);
|
||||
writer.WriteNumber("pitch", value.Pitch);
|
||||
writer.WriteNumber("thickness", value.Thickness);
|
||||
writer.WriteNumber("offset", value.Offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteNumber("pitchX", value.PitchX);
|
||||
writer.WriteNumber("pitchY", value.PitchY);
|
||||
writer.WriteNumber("offsetX", value.OffsetX);
|
||||
writer.WriteNumber("offsetY", value.OffsetY);
|
||||
}
|
||||
writer.WriteNumber("registrationTolerance", value.RegistrationTolerance);
|
||||
writer.WriteNumber("stabilityMargin", value.StabilityMargin);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
// Optional lengths default to zero; one that is present must be a number.
|
||||
private static bool TryOptional(JsonElement bed, string[] names, out double[] values)
|
||||
{
|
||||
values = new double[names.Length];
|
||||
for (var i = 0; i < names.Length; i++)
|
||||
if (bed.TryGetProperty(names[i], out _) && !TryNumber(bed, names[i], out values[i]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryNumber(JsonElement bed, string name, out double value)
|
||||
{
|
||||
value = 0;
|
||||
|
||||
@@ -481,8 +481,19 @@ namespace OpenNest.IO
|
||||
return null;
|
||||
try
|
||||
{
|
||||
return new SupportGrid(dto.PitchX, dto.PitchY, dto.OffsetX, dto.OffsetY,
|
||||
dto.RegistrationTolerance, dto.StabilityMargin);
|
||||
if (dto.Type != "slats")
|
||||
return new SupportGrid(dto.PitchX, dto.PitchY, dto.OffsetX, dto.OffsetY,
|
||||
dto.RegistrationTolerance, dto.StabilityMargin);
|
||||
SlatAxis? axis = dto.Axis switch
|
||||
{
|
||||
"x" => SlatAxis.X,
|
||||
"y" => SlatAxis.Y,
|
||||
_ => null,
|
||||
};
|
||||
return axis is { } along
|
||||
? new SupportSlats(along, dto.Pitch, dto.Thickness, dto.Offset,
|
||||
dto.RegistrationTolerance, dto.StabilityMargin)
|
||||
: null;
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
|
||||
@@ -159,8 +159,9 @@ namespace OpenNest.IO
|
||||
}
|
||||
|
||||
private static SupportBedDto ToDto(SupportBed bed) =>
|
||||
bed is SupportGrid grid
|
||||
? new SupportBedDto
|
||||
bed switch
|
||||
{
|
||||
SupportGrid grid => new SupportBedDto
|
||||
{
|
||||
PitchX = grid.PitchX,
|
||||
PitchY = grid.PitchY,
|
||||
@@ -168,8 +169,19 @@ namespace OpenNest.IO
|
||||
OffsetY = grid.OffsetY,
|
||||
RegistrationTolerance = grid.RegistrationTolerance,
|
||||
StabilityMargin = grid.StabilityMargin,
|
||||
}
|
||||
: null;
|
||||
},
|
||||
SupportSlats slats => new SupportBedDto
|
||||
{
|
||||
Type = "slats",
|
||||
Axis = slats.Axis == SlatAxis.X ? "x" : "y",
|
||||
Pitch = slats.Pitch,
|
||||
Thickness = slats.Thickness,
|
||||
Offset = slats.Offset,
|
||||
RegistrationTolerance = slats.RegistrationTolerance,
|
||||
StabilityMargin = slats.StabilityMargin,
|
||||
},
|
||||
_ => null,
|
||||
};
|
||||
|
||||
private List<DrawingDto> BuildDrawingDtos()
|
||||
{
|
||||
|
||||
@@ -60,6 +60,48 @@ public class SupportGridSettingsTests : IDisposable
|
||||
Assert.Equal(6.35, grid.StabilityMargin, 9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SavedSlatsRoundTrip_AndKeepTheGridValuesForSwitchingBack()
|
||||
{
|
||||
new SupportGridSettings
|
||||
{
|
||||
Enabled = true,
|
||||
PitchX = 3,
|
||||
UseSlats = true,
|
||||
SlatAxis = SlatAxis.X,
|
||||
SlatPitch = 4,
|
||||
SlatThickness = 0.125,
|
||||
SlatOffset = 2,
|
||||
}.Save(_path);
|
||||
var settings = SupportGridSettings.Load(_path);
|
||||
var nest = new Nest { Units = Units.Millimeters };
|
||||
|
||||
settings.ApplyToNewNest(nest);
|
||||
|
||||
var slats = Assert.IsType<SupportSlats>(nest.SupportBed);
|
||||
Assert.Equal(SlatAxis.X, slats.Axis);
|
||||
Assert.Equal(101.6, slats.Pitch, 9);
|
||||
Assert.Equal(3.175, slats.Thickness, 9);
|
||||
Assert.Equal(50.8, slats.Offset, 9);
|
||||
Assert.Equal(3, settings.PitchX);
|
||||
|
||||
var fromBed = SupportGridSettings.FromBed(slats, Units.Millimeters, enabled: true, settings);
|
||||
Assert.True(fromBed.UseSlats);
|
||||
Assert.Equal(76.2, fromBed.PitchX, 9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OlderFile_WithoutALayout_IsAGrid()
|
||||
{
|
||||
File.WriteAllText(_path, """{"enabled":true,"pitchX":2,"pitchY":2}""");
|
||||
|
||||
var settings = SupportGridSettings.Load(_path);
|
||||
|
||||
Assert.False(settings.UseSlats);
|
||||
Assert.Equal(0.125, settings.SlatThickness);
|
||||
Assert.IsType<SupportGrid>(settings.BedFor(Units.Inches));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidValues_KeepTheirDefaults_AndBadJsonLoadsAsOff()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Support;
|
||||
|
||||
namespace OpenNest.Tests.Support;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a cut part stays level on a slat bed: each slat a strip of its thickness running the
|
||||
/// table's full length or width, and a part's contacts the corners of where its material crosses one.
|
||||
/// </summary>
|
||||
public class SupportSlatsTests
|
||||
{
|
||||
private static SupportMaterial Rectangle(double x1, double y1, double x2, double y2) =>
|
||||
SupportMaterial.FromPolygons(
|
||||
new[] { new Vector(x1, y1), new Vector(x1, y2), new Vector(x2, y2), new Vector(x2, y1) });
|
||||
|
||||
[Fact]
|
||||
public void Square_AcrossTwoSlats_RestsOnTheSlatsOuterEdges()
|
||||
{
|
||||
var slats = new SupportSlats(SlatAxis.Y, pitch: 2, thickness: 0.125, offset: 1);
|
||||
|
||||
var support = SupportAnalyzer.Analyze(slats, 1, Rectangle(0, 0, 4, 4));
|
||||
|
||||
Assert.True(support.IsComplete);
|
||||
Assert.Equal(8, support.Contacts.Count);
|
||||
Assert.Contains(new Vector(0.9375, 0), support.Contacts);
|
||||
Assert.Contains(new Vector(3.0625, 4), support.Contacts);
|
||||
Assert.Equal(1.0625, support.Margin, 12);
|
||||
Assert.True(support.IsSupported);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Strip_AcrossOneSlat_BalancesOnItsThicknessButTipsUnderTheStabilityMargin()
|
||||
{
|
||||
var slats = new SupportSlats(SlatAxis.Y, pitch: 20, thickness: 0.125, offset: 5, stabilityMargin: 0.25);
|
||||
|
||||
var support = SupportAnalyzer.Analyze(slats, 1, Rectangle(0, 0, 10, 1));
|
||||
|
||||
Assert.Equal(4, support.Contacts.Count);
|
||||
Assert.Equal(0.0625, support.Margin, 12);
|
||||
Assert.False(support.IsSupported);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Strip_BetweenSlatsAlongItsLength_FallsThrough()
|
||||
{
|
||||
var slats = new SupportSlats(SlatAxis.X, pitch: 2, thickness: 0.125, offset: 1);
|
||||
|
||||
var support = SupportAnalyzer.Analyze(slats, 1, Rectangle(0, 1.25, 10, 1.75));
|
||||
|
||||
Assert.Empty(support.Contacts);
|
||||
Assert.Equal(double.NegativeInfinity, support.Margin);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Part_NarrowerThanASlat_RestsOnItsOwnCorners()
|
||||
{
|
||||
var slats = new SupportSlats(SlatAxis.X, pitch: 4, thickness: 0.5, offset: 1);
|
||||
|
||||
var support = SupportAnalyzer.Analyze(slats, 1, Rectangle(0, 0.8, 3, 1.2));
|
||||
|
||||
Assert.Equal(4, support.Contacts.Distinct().Count());
|
||||
Assert.Contains(new Vector(0, 0.8), support.Contacts);
|
||||
Assert.Contains(new Vector(3, 1.2), support.Contacts);
|
||||
Assert.Equal(0.2, support.Margin, 12);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Vertex_RestingOnASlat_IsAContact()
|
||||
{
|
||||
var slats = new SupportSlats(SlatAxis.X, pitch: 10, thickness: 0.125, offset: 0.05);
|
||||
var triangle = SupportMaterial.FromPolygons(new[] { new Vector(0, 0), new Vector(4, 5), new Vector(8, 0) });
|
||||
|
||||
var contacts = slats.Contacts(triangle, 1, Vector.Zero);
|
||||
|
||||
Assert.Contains(new Vector(0, 0), contacts);
|
||||
Assert.Contains(new Vector(8, 0), contacts);
|
||||
// The bottom edge lies on the slat, so only the slat's top edge crosses the outline.
|
||||
Assert.Contains(contacts, c => System.Math.Abs(c.Y - 0.1125) < 1e-12);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, 1)]
|
||||
[InlineData(3, -1)]
|
||||
public void Quadrant_MeasuresTheSlatsFromTheOriginIntoTheSheet(int quadrant, double sign)
|
||||
{
|
||||
var slats = new SupportSlats(SlatAxis.X, pitch: 2, thickness: 0.125, offset: 0.5);
|
||||
var sheet = new Box(sign < 0 ? -10 : 0, sign < 0 ? -4 : 0, 10, 4);
|
||||
|
||||
var strips = slats.SlatsIn(sheet, quadrant);
|
||||
|
||||
Assert.Equal(2, strips.Count);
|
||||
Assert.Contains(strips, s => System.Math.Abs(s.Center.Y - sign * 0.5) < 1e-12);
|
||||
Assert.All(strips, s => Assert.Equal(10, s.Length, 12));
|
||||
Assert.All(strips, s => Assert.Equal(0.125, s.Width, 12));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SlatsIn_CutsTheEdgeSlatsOffToTheSheet()
|
||||
{
|
||||
var slats = new SupportSlats(SlatAxis.Y, pitch: 4, thickness: 0.5, offset: 0);
|
||||
|
||||
var strips = slats.SlatsIn(new Box(0, 0, 8, 3), 1);
|
||||
|
||||
Assert.Equal(3, strips.Count);
|
||||
Assert.Equal(0.25, strips[0].Length, 12);
|
||||
Assert.Equal(0.5, strips[1].Length, 12);
|
||||
Assert.Equal(0.25, strips[2].Length, 12);
|
||||
Assert.Equal(3, strips[0].Width, 12);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScaledAndEqual_CompareEveryLength()
|
||||
{
|
||||
var slats = new SupportSlats(SlatAxis.Y, 4, 0.125, 1, 0.125, 0.25);
|
||||
|
||||
var scaled = Assert.IsType<SupportSlats>(slats.Scaled(2));
|
||||
Assert.Equal(new SupportSlats(SlatAxis.Y, 8, 0.25, 2, 0.25, 0.5), scaled);
|
||||
Assert.Equal(slats, new SupportSlats(SlatAxis.Y, 4, 0.125, 1, 0.125, 0.25));
|
||||
Assert.NotEqual<SupportBed>(slats, new SupportSlats(SlatAxis.X, 4, 0.125, 1, 0.125, 0.25));
|
||||
Assert.NotEqual<SupportBed>(slats, new SupportGrid(4, 4, 1, 1, 0.125, 0.25));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new SupportSlats(SlatAxis.Y, 0, 0.125));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new SupportSlats(SlatAxis.Y, 4, -0.125));
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ using OpenNest.Support;
|
||||
namespace OpenNest.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// View > Draw Support: the plate's support grid and, for each part, the contacts it rests
|
||||
/// on and its center of gravity, green where the grid holds it level and red where it can
|
||||
/// View > Draw Support: the plate's support grid or slats and, for each part, the contacts it
|
||||
/// rests on and its center of gravity, green where the bed holds it level and red where it can
|
||||
/// tip; a red part's best tab point is marked with an orange diamond. Display only: it
|
||||
/// takes part in no hit test, selection or sequencing. Analyses are cached per part until
|
||||
/// its program, pose, the bed or the quadrant changes.
|
||||
@@ -23,6 +23,7 @@ namespace OpenNest.Controls
|
||||
private const float GridPointSize = 3f;
|
||||
|
||||
private static readonly Color GridColor = Color.FromArgb(110, 120, 120, 120);
|
||||
private static readonly Color SlatColor = Color.FromArgb(90, 120, 120, 120);
|
||||
private static readonly Color ContactColor = Color.FromArgb(220, 30, 150, 60);
|
||||
private static readonly Color SupportedColor = Color.FromArgb(230, 20, 140, 50);
|
||||
private static readonly Color TippingColor = Color.FromArgb(240, 210, 30, 30);
|
||||
@@ -41,6 +42,8 @@ namespace OpenNest.Controls
|
||||
|
||||
if (bed is SupportGrid grid)
|
||||
DrawGrid(g, view, plate, grid);
|
||||
else if (bed is SupportSlats slats)
|
||||
DrawSlats(g, view, plate, slats);
|
||||
|
||||
using var contactBrush = new SolidBrush(ContactColor);
|
||||
using var supportedPen = new Pen(SupportedColor, 2f);
|
||||
@@ -76,13 +79,47 @@ namespace OpenNest.Controls
|
||||
return support;
|
||||
}
|
||||
|
||||
private static Box Sheet(Plate plate)
|
||||
{
|
||||
var left = plate.Quadrant is 2 or 3 ? -plate.Size.Length : 0;
|
||||
var bottom = plate.Quadrant is 3 or 4 ? -plate.Size.Width : 0;
|
||||
return new Box(left, bottom, plate.Size.Length, plate.Size.Width);
|
||||
}
|
||||
|
||||
// Each slat a strip cut off to the sheet, at least a pixel wide however far out the view is.
|
||||
private static void DrawSlats(Graphics g, PlateView view, Plate plate, SupportSlats slats)
|
||||
{
|
||||
if (view.LengthWorldToGui(slats.Pitch) < MinimumGridSpacing)
|
||||
return;
|
||||
using var brush = new SolidBrush(SlatColor);
|
||||
const float minimum = 1f;
|
||||
foreach (var slat in slats.SlatsIn(Sheet(plate), plate.Quadrant))
|
||||
{
|
||||
var a = view.PointWorldToGraph(new Vector(slat.Left, slat.Top));
|
||||
var b = view.PointWorldToGraph(new Vector(slat.Right, slat.Bottom));
|
||||
var x = System.Math.Min(a.X, b.X);
|
||||
var y = System.Math.Min(a.Y, b.Y);
|
||||
var width = System.Math.Abs(b.X - a.X);
|
||||
var height = System.Math.Abs(b.Y - a.Y);
|
||||
if (width < minimum)
|
||||
{
|
||||
x -= (minimum - width) / 2;
|
||||
width = minimum;
|
||||
}
|
||||
if (height < minimum)
|
||||
{
|
||||
y -= (minimum - height) / 2;
|
||||
height = minimum;
|
||||
}
|
||||
g.FillRectangle(brush, x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawGrid(Graphics g, PlateView view, Plate plate, SupportGrid grid)
|
||||
{
|
||||
if (view.LengthWorldToGui(System.Math.Min(grid.PitchX, grid.PitchY)) < MinimumGridSpacing)
|
||||
return;
|
||||
var left = plate.Quadrant is 2 or 3 ? -plate.Size.Length : 0;
|
||||
var bottom = plate.Quadrant is 3 or 4 ? -plate.Size.Width : 0;
|
||||
var sheet = new Box(left, bottom, plate.Size.Length, plate.Size.Width);
|
||||
var sheet = Sheet(plate);
|
||||
using var brush = new SolidBrush(GridColor);
|
||||
// Anti-aliased dots smear differently at each subpixel offset; whole-pixel squares
|
||||
// drawn without smoothing are all the same size.
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace OpenNest.Forms
|
||||
{
|
||||
nest.Machine = machine == null ? null : new NestMachine(machine.Id, machine.Name);
|
||||
var grid = LoadSupportGridSettings(machine);
|
||||
nest.SetSupportBed(grid.Enabled ? grid.GridFor(nest.Units) : null);
|
||||
nest.SetSupportBed(grid.Enabled ? grid.BedFor(nest.Units) : null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Generated
+1
-1
@@ -632,7 +632,7 @@ namespace OpenNest.Forms
|
||||
//
|
||||
mnuNestSupportGrid.Name = "mnuNestSupportGrid";
|
||||
mnuNestSupportGrid.Size = new System.Drawing.Size(205, 22);
|
||||
mnuNestSupportGrid.Text = "Support Grid...";
|
||||
mnuNestSupportGrid.Text = "Support Bed...";
|
||||
mnuNestSupportGrid.Click += SupportGrid_Click;
|
||||
//
|
||||
// mnuNestImportDrawing
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace OpenNest.Forms
|
||||
if (IsPlateWorkBusy(views))
|
||||
{
|
||||
MessageBox.Show(this, "Finish or cancel the current nesting or plate action first.",
|
||||
"Support Grid", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
"Support Bed", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ namespace OpenNest.Forms
|
||||
if (form.ShowDialog(this) != DialogResult.OK)
|
||||
return;
|
||||
|
||||
nest.SetSupportBed(form.Grid);
|
||||
nest.SetSupportBed(form.Bed);
|
||||
foreach (var view in views)
|
||||
view.Invalidate();
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ using OpenNest.Support;
|
||||
namespace OpenNest.Forms;
|
||||
|
||||
/// <summary>
|
||||
/// Nest > Support Grid...: edits the laser grid of contact points every sheet of the current
|
||||
/// nest rests on, used to judge whether cut parts stay level and where support tabs go (see
|
||||
/// Nest > Support Bed...: edits what every sheet of the current nest rests on, a laser grid of
|
||||
/// contact points or a plasma table's slats, used to judge whether cut parts stay level and where
|
||||
/// support tabs go (see
|
||||
/// docs/support-tabs.md). OK applies it to the nest; Cancel discards the edits. Save as Machine
|
||||
/// Defaults writes the grid to the nest's machine configuration immediately and is not undone by Cancel.
|
||||
/// </summary>
|
||||
@@ -23,6 +24,12 @@ public sealed class SupportGridForm : Form
|
||||
private readonly NumericUpDown pitchYBox;
|
||||
private readonly NumericUpDown offsetXBox;
|
||||
private readonly NumericUpDown offsetYBox;
|
||||
private readonly RadioButton gridButton;
|
||||
private readonly RadioButton slatsButton;
|
||||
private readonly ComboBox slatAxisBox;
|
||||
private readonly NumericUpDown slatPitchBox;
|
||||
private readonly NumericUpDown slatThicknessBox;
|
||||
private readonly NumericUpDown slatOffsetBox;
|
||||
private readonly NumericUpDown toleranceBox;
|
||||
private readonly NumericUpDown marginBox;
|
||||
private readonly CheckBox applyToNewNestsBox;
|
||||
@@ -38,14 +45,14 @@ public sealed class SupportGridForm : Form
|
||||
this.units = units;
|
||||
this.machineDefaults = machineDefaults ?? new SupportGridSettings();
|
||||
this.saveMachineDefaults = saveMachineDefaults;
|
||||
var grid = bed as SupportGrid;
|
||||
// A nest without a grid starts from the machine defaults, so enabling it is one click.
|
||||
var initial = grid != null
|
||||
? SupportGridSettings.FromGrid(grid, units, this.machineDefaults.Enabled)
|
||||
// A nest without a bed starts from the machine defaults, so enabling it is one click; the
|
||||
// layout it does not use keeps the defaults' values.
|
||||
var initial = bed != null
|
||||
? SupportGridSettings.FromBed(bed, units, this.machineDefaults.Enabled, this.machineDefaults)
|
||||
: this.machineDefaults.ConvertedTo(units);
|
||||
var unit = UnitsHelper.GetShortString(units);
|
||||
|
||||
Text = "Support Grid";
|
||||
Text = "Support Bed";
|
||||
StartPosition = FormStartPosition.CenterParent;
|
||||
AutoSize = true;
|
||||
AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
@@ -66,30 +73,55 @@ public sealed class SupportGridForm : Form
|
||||
AutoSize = true,
|
||||
MaximumSize = new Size(430, 0),
|
||||
Padding = new Padding(0, 0, 0, 8),
|
||||
Text = "The grid of contact points cut parts rest on, one every pitch along X and Y, the first "
|
||||
+ "at the offset from the plate origin into the sheet. A part whose center of gravity "
|
||||
+ "is not inside its contacts by the stability margin can tip; with \"Tab parts the "
|
||||
+ "grid cannot support\" in Cutting Settings, Plan Cutting tabs it where the tab "
|
||||
+ "steadies it. The registration tolerance allows for a sheet loaded off its place.",
|
||||
Text = "What cut parts rest on: a grid of contact points, one every pitch along X and Y, the "
|
||||
+ "first at the offset from the plate origin into the sheet; or slats, each a strip of "
|
||||
+ "the slat thickness running the full length or width of the table, one every pitch, "
|
||||
+ "the first centered at the offset. A part whose center of gravity is not inside its "
|
||||
+ "contacts by the stability margin can tip; with \"Tab parts the support grid cannot "
|
||||
+ "hold level\" in Cutting Settings, Plan Cutting tabs it where the tab steadies it. "
|
||||
+ "The registration tolerance allows for a sheet loaded off its place.",
|
||||
});
|
||||
|
||||
useGridBox = new CheckBox { Text = "This nest's sheets rest on a support grid", AutoSize = true, Checked = grid != null };
|
||||
useGridBox = new CheckBox { Text = "This nest's sheets rest on a support bed", AutoSize = true, Checked = bed != null };
|
||||
layout.Controls.Add(useGridBox);
|
||||
|
||||
var values = new TableLayoutPanel { AutoSize = true, ColumnCount = 3 };
|
||||
var kinds = new FlowLayoutPanel { AutoSize = true, WrapContents = false, Padding = new Padding(16, 0, 0, 0) };
|
||||
gridButton = new RadioButton { Text = "Grid of points", AutoSize = true, Checked = !initial.UseSlats };
|
||||
slatsButton = new RadioButton { Text = "Slats", AutoSize = true, Checked = initial.UseSlats };
|
||||
kinds.Controls.Add(gridButton);
|
||||
kinds.Controls.Add(slatsButton);
|
||||
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);
|
||||
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);
|
||||
layout.Controls.Add(slatValues);
|
||||
|
||||
var sharedValues = new TableLayoutPanel { AutoSize = true, ColumnCount = 3 };
|
||||
toleranceBox = NewLengthBox(initial.RegistrationTolerance, 0m);
|
||||
marginBox = NewLengthBox(initial.StabilityMargin, 0m);
|
||||
AddRow(values, "Pitch along X:", pitchXBox, unit);
|
||||
AddRow(values, "Pitch along Y:", pitchYBox, unit);
|
||||
AddRow(values, "First point, X from the origin:", offsetXBox, unit);
|
||||
AddRow(values, "First point, Y from the origin:", offsetYBox, unit);
|
||||
AddRow(values, "Registration tolerance:", toleranceBox, unit);
|
||||
AddRow(values, "Stability margin:", marginBox, unit);
|
||||
layout.Controls.Add(values);
|
||||
AddRow(sharedValues, "Registration tolerance:", toleranceBox, unit);
|
||||
AddRow(sharedValues, "Stability margin:", marginBox, unit);
|
||||
layout.Controls.Add(sharedValues);
|
||||
|
||||
var defaultsGroup = new GroupBox
|
||||
{
|
||||
@@ -106,7 +138,7 @@ public sealed class SupportGridForm : Form
|
||||
};
|
||||
applyToNewNestsBox = new CheckBox
|
||||
{
|
||||
Text = "Give nests on this machine this grid",
|
||||
Text = "Give nests on this machine this bed",
|
||||
AutoSize = true,
|
||||
Checked = this.machineDefaults.Enabled,
|
||||
};
|
||||
@@ -143,14 +175,15 @@ public sealed class SupportGridForm : Form
|
||||
CancelButton = cancel;
|
||||
|
||||
useGridBox.CheckedChanged += (_, _) => UpdateEnabled();
|
||||
slatsButton.CheckedChanged += (_, _) => UpdateEnabled();
|
||||
loadDefaultsButton.Click += (_, _) => ShowValues(this.machineDefaults.ConvertedTo(units));
|
||||
saveDefaultsButton.Click += (_, _) => SaveMachineDefaults();
|
||||
ok.Click += (_, _) => Accept();
|
||||
UpdateEnabled();
|
||||
}
|
||||
|
||||
/// <summary>The grid the nest's sheets rest on after OK, or null for none.</summary>
|
||||
public SupportGrid Grid { get; private set; }
|
||||
/// <summary>The bed the nest's sheets rest on after OK, or null for none.</summary>
|
||||
public SupportBed Bed { get; private set; }
|
||||
|
||||
private SupportGridSettings Read() => new()
|
||||
{
|
||||
@@ -160,6 +193,11 @@ public sealed class SupportGridForm : Form
|
||||
PitchY = (double)pitchYBox.Value,
|
||||
OffsetX = (double)offsetXBox.Value,
|
||||
OffsetY = (double)offsetYBox.Value,
|
||||
UseSlats = slatsButton.Checked,
|
||||
SlatAxis = slatAxisBox.SelectedIndex == 0 ? SlatAxis.X : SlatAxis.Y,
|
||||
SlatPitch = (double)slatPitchBox.Value,
|
||||
SlatThickness = (double)slatThicknessBox.Value,
|
||||
SlatOffset = (double)slatOffsetBox.Value,
|
||||
RegistrationTolerance = (double)toleranceBox.Value,
|
||||
StabilityMargin = (double)marginBox.Value,
|
||||
};
|
||||
@@ -170,6 +208,12 @@ public sealed class SupportGridForm : Form
|
||||
pitchYBox.Value = ToBoxValue(pitchYBox, settings.PitchY);
|
||||
offsetXBox.Value = ToBoxValue(offsetXBox, settings.OffsetX);
|
||||
offsetYBox.Value = 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);
|
||||
applyToNewNestsBox.Checked = settings.Enabled;
|
||||
@@ -177,8 +221,13 @@ public sealed class SupportGridForm : Form
|
||||
|
||||
private void UpdateEnabled()
|
||||
{
|
||||
foreach (var box in new[] { pitchXBox, pitchYBox, offsetXBox, offsetYBox, toleranceBox, marginBox })
|
||||
box.Enabled = useGridBox.Checked;
|
||||
var on = useGridBox.Checked;
|
||||
gridButton.Enabled = slatsButton.Enabled = on;
|
||||
foreach (var box in new Control[] { pitchXBox, pitchYBox, offsetXBox, offsetYBox })
|
||||
box.Enabled = on && !slatsButton.Checked;
|
||||
foreach (var box in new Control[] { slatAxisBox, slatPitchBox, slatThicknessBox, slatOffsetBox })
|
||||
box.Enabled = on && slatsButton.Checked;
|
||||
toleranceBox.Enabled = marginBox.Enabled = on;
|
||||
}
|
||||
|
||||
private void SaveMachineDefaults()
|
||||
@@ -188,8 +237,8 @@ public sealed class SupportGridForm : Form
|
||||
{
|
||||
saveMachineDefaults(settings);
|
||||
savedLabel.Text = settings.Enabled
|
||||
? $"Saved {DateTime.Now:t}: nests on this machine get this grid. Cancel does not undo this."
|
||||
: $"Saved {DateTime.Now:t}: nests on this machine get no grid. Cancel does not undo this.";
|
||||
? $"Saved {DateTime.Now:t}: nests on this machine get this bed. Cancel does not undo this."
|
||||
: $"Saved {DateTime.Now:t}: nests on this machine get no bed. Cancel does not undo this.";
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or ArgumentException)
|
||||
{
|
||||
@@ -200,10 +249,11 @@ public sealed class SupportGridForm : Form
|
||||
|
||||
private void Accept()
|
||||
{
|
||||
Grid = useGridBox.Checked ? Read().GridFor(units) : null;
|
||||
if (useGridBox.Checked && Grid == null)
|
||||
Bed = useGridBox.Checked ? Read().BedFor(units) : null;
|
||||
if (useGridBox.Checked && Bed == null)
|
||||
{
|
||||
MessageBox.Show(this, "Both pitches must be greater than zero.", Text,
|
||||
MessageBox.Show(this, slatsButton.Checked ? "The slat pitch must be greater than zero."
|
||||
: "Both pitches must be greater than zero.", Text,
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -152,13 +152,16 @@ spacing differs from the saved defaults, is still written.
|
||||
## Support grid
|
||||
|
||||
`plateDefaults.supportBed` optionally stores the machine support bed every sheet of
|
||||
the nest rests on (see [support tabs](support-tabs.md)): `type` (`grid`, the only
|
||||
layout so far), `pitchX`, `pitchY`, `offsetX`, `offsetY` (the first grid point,
|
||||
measured from the plate origin into the sheet), `registrationTolerance` and
|
||||
`stabilityMargin`, all in the nest's units. The key is omitted when the nest has
|
||||
none, so such files are unchanged. A missing pitch, a non-numeric value, an unknown
|
||||
`type` or an invalid value (a non-positive pitch, a negative tolerance or margin)
|
||||
loads as no bed; the rest of the nest loads. Every plate follows the nest's bed.
|
||||
the nest rests on (see [support tabs](support-tabs.md)): `type`, then for `grid`
|
||||
`pitchX`, `pitchY`, `offsetX`, `offsetY` (the first grid point, measured from the
|
||||
plate origin into the sheet), or for `slats` `axis` (`x` or `y`, the axis the slats
|
||||
run along), `pitch`, `thickness` and `offset` (the first slat's center line); both
|
||||
end with `registrationTolerance` and `stabilityMargin`. All lengths are in the
|
||||
nest's units. The key is omitted when the nest has none, so such files are
|
||||
unchanged. A missing pitch or thickness, a non-numeric value, an unknown `type` or
|
||||
`axis`, or an invalid value (a non-positive pitch, a negative thickness, tolerance
|
||||
or margin) loads as no bed; the rest of the nest loads. Versions without slat
|
||||
support read a `slats` bed as none. Every plate follows the nest's bed.
|
||||
|
||||
## Drawing edits
|
||||
|
||||
|
||||
+25
-13
@@ -9,10 +9,20 @@ perimeter point where one tab steadies the part most.
|
||||
|
||||
`OpenNest.Support.SupportBed` is the machine's bed, fixed to the machine and measured from the
|
||||
plate origin into the sheet; a sheet in another quadrant sees it mirrored. The built-in layout
|
||||
is the laser grid, `SupportGrid`: one contact point every `PitchX` along X and `PitchY` along
|
||||
Y, the first at (`OffsetX`, `OffsetY`). Lengths are in the nest's units. Straight slats, and
|
||||
curved slats imported from a CAD layout (as on the plasma table), are planned as further
|
||||
`SupportBed` subclasses: a layout only reports its contacts under a part's material.
|
||||
is one of two layouts; a layout only reports its contacts under a part's material. Lengths are
|
||||
in the nest's units.
|
||||
|
||||
- The laser grid, `SupportGrid`: one contact point every `PitchX` along X and `PitchY` along
|
||||
Y, the first at (`OffsetX`, `OffsetY`).
|
||||
- Straight slats, `SupportSlats` (as on the plasma table): each slat a strip `Thickness` wide
|
||||
running the full length (`Axis` X) or width (`Axis` Y) of the table, one every `Pitch`
|
||||
across it, the first centered `Offset` from the origin. A part rests on the region where its
|
||||
material crosses a slat, so its contacts are that region's corners: where the outline and
|
||||
holes cross each slat's two edges, and the outline and hole vertices on a slat. A part
|
||||
narrower than a slat rests on its own corners; one balanced across a single slat has a margin
|
||||
of at most half the thickness, which the stability margin rejects.
|
||||
|
||||
Curved slats imported from a CAD layout are not modeled yet.
|
||||
|
||||
Every bed also carries:
|
||||
|
||||
@@ -23,14 +33,15 @@ Every bed also carries:
|
||||
|
||||
The bed belongs to the nest (`Nest.SetSupportBed`); every plate follows it, including plates
|
||||
added later. It is display, analysis and tab placement only: never material, spacing or a
|
||||
keep-out. Nest > Support Grid... edits it; Save as Machine Defaults writes it to the nest's
|
||||
machine configuration (`MachineConfig.SupportGrid`), which nests assigned that machine receive
|
||||
while it is enabled: File > New and BOM import assign the Tools > Nest Defaults machine (the
|
||||
first by name when none is chosen), and Nest > Machine (or the status bar's machine label) reassigns a nest and replaces its grid
|
||||
with the new machine's (none when that machine gives nests no grid). A machine with no grid
|
||||
saved yet uses the legacy shared `%APPDATA%\OpenNest\support-grid.json`. The nest file stores
|
||||
the bed in `plateDefaults.supportBed` and the machine in `machine` (see
|
||||
[nest file format](nest-file-format.md)).
|
||||
keep-out. Nest > Support Bed... edits it and picks the grid or slats; Save as Machine Defaults
|
||||
writes it to the nest's machine configuration (`MachineConfig.SupportGrid`: both layouts' values
|
||||
and which one is in use; settings saved before slats existed are a grid), which nests assigned that
|
||||
machine receive while it is enabled: File > New and BOM import assign the Tools > Nest Defaults
|
||||
machine (the first by name when none is chosen), and Nest > Machine (or the status bar's machine
|
||||
label) reassigns a nest and replaces its bed with the new machine's (none when that machine gives
|
||||
nests no bed). A machine with no bed saved yet uses the legacy shared
|
||||
`%APPDATA%\OpenNest\support-grid.json`. The nest file stores the bed in
|
||||
`plateDefaults.supportBed` and the machine in `machine` (see [nest file format](nest-file-format.md)).
|
||||
|
||||
## The support check
|
||||
|
||||
@@ -61,7 +72,8 @@ off the corner only), and `AcceptsTabAt` rejects closer points. The tie-break ab
|
||||
the clear points only; points equally near the center line go to the one farthest from an
|
||||
inside corner. Only when every outline point is too close does the clearance give way.
|
||||
|
||||
View > Draw Support shows the grid (while its points are at least 6 pixels apart), each part's
|
||||
View > Draw Support shows the grid or the slats, cut off to the sheet (while its points or slats
|
||||
are at least 6 pixels apart), each part's
|
||||
contacts, and its center of gravity: green when supported, red when it can tip, with an orange
|
||||
diamond on a red part's best tab point (keeping the plate's tab-size clearance).
|
||||
|
||||
|
||||
Reference in new issue
Block a user