diff --git a/OpenNest.Core/Plate.cs b/OpenNest.Core/Plate.cs
index 5d55624e..378470b0 100644
--- a/OpenNest.Core/Plate.cs
+++ b/OpenNest.Core/Plate.cs
@@ -5,7 +5,6 @@ using System.Linq;
using OpenNest.Collections;
using OpenNest.Geometry;
using OpenNest.Math;
-using OpenNest.Shapes;
using OpenNest.Support;
namespace OpenNest
@@ -985,65 +984,6 @@ namespace OpenNest
);
}
- ///
- /// Sizes the plate using the catalog: small
- /// layouts snap to an increment, larger ones round up to the next
- /// standard mill sheet. The plate's long-axis orientation (X vs Y)
- /// is preserved. Does nothing if the plate has no parts.
- ///
- public PlateSizeResult SnapToStandardSize(PlateSizeOptions options = null)
- {
- if (Parts.Count == 0 || Outline != null)
- return default;
-
- var bounds = Parts.GetBoundingBox();
-
- // Quadrant-aware extents relative to the plate origin, matching AutoSize.
- double xExtent;
- double yExtent;
-
- switch (Quadrant)
- {
- case 1:
- xExtent = System.Math.Abs(bounds.Right) + EdgeSpacing.Right;
- yExtent = System.Math.Abs(bounds.Top) + EdgeSpacing.Top;
- break;
-
- case 2:
- xExtent = System.Math.Abs(bounds.Left) + EdgeSpacing.Left;
- yExtent = System.Math.Abs(bounds.Top) + EdgeSpacing.Top;
- break;
-
- case 3:
- xExtent = System.Math.Abs(bounds.Left) + EdgeSpacing.Left;
- yExtent = System.Math.Abs(bounds.Bottom) + EdgeSpacing.Bottom;
- break;
-
- case 4:
- xExtent = System.Math.Abs(bounds.Right) + EdgeSpacing.Right;
- yExtent = System.Math.Abs(bounds.Bottom) + EdgeSpacing.Bottom;
- break;
-
- default:
- return default;
- }
-
- // PlateSizes.Recommend takes (short, long); canonicalize then map
- // the result back so the plate's long axis stays aligned with the
- // parts' long axis.
- var shortDim = System.Math.Min(xExtent, yExtent);
- var longDim = System.Math.Max(xExtent, yExtent);
- var result = PlateSizes.Recommend(shortDim, longDim, options);
-
- // Plate convention: Length = X axis, Width = Y axis.
- if (xExtent >= yExtent)
- Size = new Size(result.Width, result.Length); // X is the long axis
- else
- Size = new Size(result.Length, result.Width); // Y is the long axis
-
- return result;
- }
-
///
/// Gets the area of the top surface of the plate: the material inside its
/// when it has one, otherwise the whole rectangle.
diff --git a/OpenNest.Core/Shapes/PlateSizes.cs b/OpenNest.Core/Shapes/PlateSizes.cs
index d8e1cbb9..fc8a7165 100644
--- a/OpenNest.Core/Shapes/PlateSizes.cs
+++ b/OpenNest.Core/Shapes/PlateSizes.cs
@@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using OpenNest.Geometry;
namespace OpenNest.Shapes
{
///
- /// Catalog of standard mill sheet sizes (inches) with helpers for matching
- /// a bounding box to a recommended plate size. Uses the project-wide
+ /// Catalog of standard mill sheet sizes (inches). Uses the project-wide
/// (Width, Length) convention where Width is the short dimension and
/// Length is the long dimension.
///
@@ -62,227 +59,5 @@ namespace OpenNest.Shapes
entry = default;
return false;
}
-
- ///
- /// Recommends a plate size for the given bounding box. The box's
- /// spatial axes are normalized to (short, long) so neither the bbox
- /// orientation nor Box's internal Length/Width naming matters.
- ///
- public static PlateSizeResult Recommend(Box bbox, PlateSizeOptions options = null)
- {
- var a = bbox.Width;
- var b = bbox.Length;
- return Recommend(System.Math.Min(a, b), System.Math.Max(a, b), options);
- }
-
- ///
- /// Recommends a plate size for the envelope of the given boxes.
- ///
- public static PlateSizeResult Recommend(
- IEnumerable boxes,
- PlateSizeOptions options = null
- )
- {
- if (boxes == null)
- throw new ArgumentNullException(nameof(boxes));
-
- var hasAny = false;
- var minX = double.PositiveInfinity;
- var minY = double.PositiveInfinity;
- var maxX = double.NegativeInfinity;
- var maxY = double.NegativeInfinity;
-
- foreach (var box in boxes)
- {
- hasAny = true;
- if (box.Left < minX)
- minX = box.Left;
- if (box.Bottom < minY)
- minY = box.Bottom;
- if (box.Right > maxX)
- maxX = box.Right;
- if (box.Top > maxY)
- maxY = box.Top;
- }
-
- if (!hasAny)
- throw new ArgumentException("At least one box is required.", nameof(boxes));
-
- var b = maxX - minX;
- var a = maxY - minY;
- return Recommend(System.Math.Min(a, b), System.Math.Max(a, b), options);
- }
-
- ///
- /// Recommends a plate size for a (width, length) pair.
- /// Inputs are treated as orientation-independent.
- ///
- public static PlateSizeResult Recommend(
- double width,
- double length,
- PlateSizeOptions options = null
- )
- {
- options ??= new PlateSizeOptions();
-
- var w = width + 2 * options.Margin;
- var l = length + 2 * options.Margin;
-
- // Canonicalize (short, long) — Fits handles rotation anyway, but
- // normalizing lets the below-min comparison use the narrower
- // MinSheet dimensions consistently.
- if (w > l)
- (w, l) = (l, w);
-
- // Below full-sheet threshold: snap each dimension up to the nearest increment.
- if (w <= options.MinSheetWidth && l <= options.MinSheetLength)
- return SnapResult(w, l, options.SnapIncrement);
-
- var catalog = BuildCatalog(options.AllowedSizes);
-
- var best = PickBest(catalog, w, l, options.Selection);
- if (best.HasValue)
- return new PlateSizeResult(best.Value.Width, best.Value.Length, best.Value.Label);
-
- // Nothing in the catalog fits - fall back to snap-up (ad-hoc oversize sheet).
- return SnapResult(w, l, options.SnapIncrement);
- }
-
- private static PlateSizeResult SnapResult(double width, double length, double increment)
- {
- if (increment <= 0)
- return new PlateSizeResult(width, length, null);
-
- return new PlateSizeResult(SnapUp(width, increment), SnapUp(length, increment), null);
- }
-
- private static double SnapUp(double value, double increment)
- {
- var steps = System.Math.Ceiling(value / increment);
- return steps * increment;
- }
-
- private static IReadOnlyList BuildCatalog(IReadOnlyList allowedSizes)
- {
- if (allowedSizes == null || allowedSizes.Count == 0)
- return All;
-
- var result = new List(allowedSizes.Count);
- foreach (var label in allowedSizes)
- {
- if (TryParseEntry(label, out var entry))
- result.Add(entry);
- }
-
- return result;
- }
-
- private static bool TryParseEntry(string label, out Entry entry)
- {
- if (TryGet(label, out entry))
- return true;
-
- // Accept ad-hoc "WxL" strings (e.g. "50x100", "50 x 100").
- if (!string.IsNullOrWhiteSpace(label))
- {
- var parts = label.Split(new[] { 'x', 'X' }, 2);
- if (
- parts.Length == 2
- && double.TryParse(
- parts[0].Trim(),
- System.Globalization.NumberStyles.Float,
- System.Globalization.CultureInfo.InvariantCulture,
- out var a
- )
- && double.TryParse(
- parts[1].Trim(),
- System.Globalization.NumberStyles.Float,
- System.Globalization.CultureInfo.InvariantCulture,
- out var b
- )
- && a > 0
- && b > 0
- )
- {
- var width = System.Math.Min(a, b);
- var length = System.Math.Max(a, b);
- entry = new Entry(label.Trim(), width, length);
- return true;
- }
- }
-
- entry = default;
- return false;
- }
-
- private static Entry? PickBest(
- IReadOnlyList catalog,
- double width,
- double length,
- PlateSizeSelection selection
- )
- {
- var fitting = catalog.Where(e => e.Fits(width, length));
-
- fitting = selection switch
- {
- PlateSizeSelection.NarrowestFirst => fitting
- .OrderBy(e => e.Width)
- .ThenBy(e => e.Area),
- _ => fitting.OrderBy(e => e.Area).ThenBy(e => e.Width),
- };
-
- foreach (var candidate in fitting)
- return candidate;
-
- return null;
- }
- }
-
- public readonly record struct PlateSizeResult(double Width, double Length, string MatchedLabel)
- {
- public bool IsStandard => MatchedLabel != null;
- }
-
- public sealed class PlateSizeOptions
- {
- ///
- /// If the margin-adjusted bounding box fits within MinSheetWidth x MinSheetLength
- /// the result is snapped to instead of routed to a
- /// standard sheet. Default 48" x 48".
- ///
- public double MinSheetWidth { get; set; } = 48;
- public double MinSheetLength { get; set; } = 48;
-
- ///
- /// Increment used for below-threshold rounding and oversize fallback. Default 1".
- ///
- public double SnapIncrement { get; set; } = 1.0;
-
- ///
- /// Extra clearance added to each side of the bounding box before matching.
- ///
- public double Margin { get; set; } = 0;
-
- ///
- /// Optional whitelist. When non-empty, only these sizes are considered.
- /// Entries may be standard catalog labels (e.g. "48x96") or arbitrary
- /// "WxL" strings (e.g. "50x100").
- ///
- public IReadOnlyList AllowedSizes { get; set; }
-
- ///
- /// Tiebreaker when multiple sheets can contain the bounding box.
- ///
- public PlateSizeSelection Selection { get; set; } = PlateSizeSelection.SmallestArea;
- }
-
- public enum PlateSizeSelection
- {
- /// Pick the cheapest sheet that contains the bbox (smallest area).
- SmallestArea,
-
- /// Prefer narrower-width sheets (e.g. 48-wide before 60-wide).
- NarrowestFirst,
}
}
diff --git a/OpenNest.Tests/PlateAutoSizeTests.cs b/OpenNest.Tests/PlateAutoSizeTests.cs
new file mode 100644
index 00000000..85c14686
--- /dev/null
+++ b/OpenNest.Tests/PlateAutoSizeTests.cs
@@ -0,0 +1,106 @@
+using OpenNest.CNC;
+using OpenNest.Geometry;
+
+namespace OpenNest.Tests;
+
+public class PlateAutoSizeTests
+{
+ private static Part MakeRectPart(double x, double y, double length, double width)
+ {
+ var pgm = new Program();
+ pgm.Codes.Add(new RapidMove(new Vector(0, 0)));
+ pgm.Codes.Add(new LinearMove(new Vector(length, 0)));
+ pgm.Codes.Add(new LinearMove(new Vector(length, width)));
+ pgm.Codes.Add(new LinearMove(new Vector(0, width)));
+ pgm.Codes.Add(new LinearMove(new Vector(0, 0)));
+ var drawing = new Drawing("test", pgm);
+ var part = new Part(drawing);
+ part.Offset(x, y);
+ return part;
+ }
+
+ [Fact]
+ public void AutoSize_SmallParts_RoundsUpToFactor()
+ {
+ var plate = new Plate(200, 200);
+ plate.Parts.Add(MakeRectPart(0, 0, 10.3, 20.7));
+
+ plate.AutoSize(0.25);
+
+ Assert.Equal(10.5, plate.Size.Length, 4); // X axis
+ Assert.Equal(20.75, plate.Size.Width, 4); // Y axis
+ }
+
+ [Fact]
+ public void AutoSize_ZeroFactor_FitsExactly()
+ {
+ var plate = new Plate(200, 200);
+ plate.Parts.Add(MakeRectPart(0, 0, 10.3, 20.7));
+
+ plate.AutoSize(0);
+
+ Assert.Equal(10.3, plate.Size.Length, 4);
+ Assert.Equal(20.7, plate.Size.Width, 4);
+ }
+
+ [Fact]
+ public void AutoSize_LargerThanFullSheet_RoundsUpInsteadOfPickingAStandardSheet()
+ {
+ var plate = new Plate(200, 200);
+ plate.Parts.Add(MakeRectPart(0, 0, 100, 50));
+
+ plate.AutoSize();
+
+ Assert.Equal(100, plate.Size.Length);
+ Assert.Equal(50, plate.Size.Width);
+ }
+
+ [Fact]
+ public void AutoSize_FullSheet_ShrinksToParts()
+ {
+ var plate = new Plate(60, 120);
+ plate.Parts.Add(MakeRectPart(0, 0, 100.2, 55.6));
+
+ plate.AutoSize();
+
+ Assert.Equal(101, plate.Size.Length);
+ Assert.Equal(56, plate.Size.Width);
+ }
+
+ [Fact]
+ public void AutoSize_IncludesEdgeSpacing()
+ {
+ var plate = new Plate(200, 200) { EdgeSpacing = new Spacing(0, 0, 0.5, 0.5) };
+ plate.Parts.Add(MakeRectPart(0, 0, 10, 20));
+
+ plate.AutoSize(0.25);
+
+ Assert.Equal(10.5, plate.Size.Length, 4);
+ Assert.Equal(20.5, plate.Size.Width, 4);
+ }
+
+ [Fact]
+ public void AutoSize_EmptyPlate_DoesNotModifySize()
+ {
+ var plate = new Plate(60, 120);
+
+ plate.AutoSize();
+
+ Assert.Equal(60, plate.Size.Width);
+ Assert.Equal(120, plate.Size.Length);
+ }
+
+ [Fact]
+ public void AutoSize_MultipleParts_UsesCombinedEnvelope()
+ {
+ var plate = new Plate(200, 200);
+ plate.Parts.Add(MakeRectPart(0, 0, 30, 40));
+ plate.Parts.Add(MakeRectPart(30, 0, 30, 40));
+ plate.Parts.Add(MakeRectPart(0, 40, 60, 60)); // combined extent = 60 x 100
+
+ plate.AutoSize();
+
+ Assert.Equal(60, plate.Size.Length); // X
+ Assert.Equal(100, plate.Size.Width); // Y
+ }
+}
diff --git a/OpenNest.Tests/PlateSnapToStandardSizeTests.cs b/OpenNest.Tests/PlateSnapToStandardSizeTests.cs
deleted file mode 100644
index 7112c0f5..00000000
--- a/OpenNest.Tests/PlateSnapToStandardSizeTests.cs
+++ /dev/null
@@ -1,118 +0,0 @@
-using OpenNest.CNC;
-using OpenNest.Geometry;
-using OpenNest.Shapes;
-
-namespace OpenNest.Tests;
-
-public class PlateSnapToStandardSizeTests
-{
- private static Part MakeRectPart(double x, double y, double length, double width)
- {
- var pgm = new Program();
- pgm.Codes.Add(new RapidMove(new Vector(0, 0)));
- pgm.Codes.Add(new LinearMove(new Vector(length, 0)));
- pgm.Codes.Add(new LinearMove(new Vector(length, width)));
- pgm.Codes.Add(new LinearMove(new Vector(0, width)));
- pgm.Codes.Add(new LinearMove(new Vector(0, 0)));
- var drawing = new Drawing("test", pgm);
- var part = new Part(drawing);
- part.Offset(x, y);
- return part;
- }
-
- [Fact]
- public void SnapToStandardSize_SmallParts_SnapsToIncrement()
- {
- var plate = new Plate(200, 200); // oversized starting size
- plate.Parts.Add(MakeRectPart(0, 0, 10, 20));
-
- var result = plate.SnapToStandardSize();
-
- // 10x20 is well below 48x48 MinSheet -> snap to integer increment.
- Assert.Null(result.MatchedLabel);
- Assert.Equal(10, plate.Size.Length); // X axis
- Assert.Equal(20, plate.Size.Width); // Y axis
- }
-
- [Fact]
- public void SnapToStandardSize_SmallPartsWithFractionalIncrement_UsesIncrement()
- {
- var plate = new Plate(200, 200);
- plate.Parts.Add(MakeRectPart(0, 0, 10.3, 20.7));
-
- var result = plate.SnapToStandardSize(new PlateSizeOptions { SnapIncrement = 0.25 });
-
- Assert.Null(result.MatchedLabel);
- Assert.Equal(10.5, plate.Size.Length, 4);
- Assert.Equal(20.75, plate.Size.Width, 4);
- }
-
- [Fact]
- public void SnapToStandardSize_40x90Part_SnapsToStandard48x96_XLong()
- {
- // Part is 90 long (X) x 40 wide (Y) -> X is the long axis.
- var plate = new Plate(200, 200);
- plate.Parts.Add(MakeRectPart(0, 0, 90, 40));
-
- var result = plate.SnapToStandardSize();
-
- Assert.Equal("48x96", result.MatchedLabel);
- Assert.Equal(96, plate.Size.Length); // X axis = long
- Assert.Equal(48, plate.Size.Width); // Y axis = short
- }
-
- [Fact]
- public void SnapToStandardSize_90TallPart_SnapsToStandard48x96_YLong()
- {
- // Part is 40 long (X) x 90 wide (Y) -> Y is the long axis.
- var plate = new Plate(200, 200);
- plate.Parts.Add(MakeRectPart(0, 0, 40, 90));
-
- var result = plate.SnapToStandardSize();
-
- Assert.Equal("48x96", result.MatchedLabel);
- Assert.Equal(48, plate.Size.Length); // X axis = short
- Assert.Equal(96, plate.Size.Width); // Y axis = long
- }
-
- [Fact]
- public void SnapToStandardSize_JustOver48_PicksNextStandardSize()
- {
- var plate = new Plate(200, 200);
- plate.Parts.Add(MakeRectPart(0, 0, 100, 50));
-
- var result = plate.SnapToStandardSize();
-
- Assert.Equal("60x120", result.MatchedLabel);
- Assert.Equal(120, plate.Size.Length); // X long
- Assert.Equal(60, plate.Size.Width);
- }
-
- [Fact]
- public void SnapToStandardSize_EmptyPlate_DoesNotModifySize()
- {
- var plate = new Plate(60, 120);
-
- var result = plate.SnapToStandardSize();
-
- Assert.Null(result.MatchedLabel);
- Assert.Equal(60, plate.Size.Width);
- Assert.Equal(120, plate.Size.Length);
- }
-
- [Fact]
- public void SnapToStandardSize_MultipleParts_UsesCombinedEnvelope()
- {
- var plate = new Plate(200, 200);
- plate.Parts.Add(MakeRectPart(0, 0, 30, 40));
- plate.Parts.Add(MakeRectPart(30, 0, 30, 40)); // combined X-extent = 60
- plate.Parts.Add(MakeRectPart(0, 40, 60, 60)); // combined extent = 60 x 100
-
- var result = plate.SnapToStandardSize();
-
- // 60 x 100 fits 60x120 standard sheet, Y is the long axis.
- Assert.Equal("60x120", result.MatchedLabel);
- Assert.Equal(60, plate.Size.Length); // X
- Assert.Equal(120, plate.Size.Width); // Y long
- }
-}
diff --git a/OpenNest.Tests/Shapes/PlateSizesTests.cs b/OpenNest.Tests/Shapes/PlateSizesTests.cs
index e05675c2..4b8691c2 100644
--- a/OpenNest.Tests/Shapes/PlateSizesTests.cs
+++ b/OpenNest.Tests/Shapes/PlateSizesTests.cs
@@ -1,6 +1,4 @@
-using System.Collections.Generic;
using System.Linq;
-using OpenNest.Geometry;
using OpenNest.Shapes;
namespace OpenNest.Tests.Shapes;
@@ -13,13 +11,6 @@ public class PlateSizesTests
Assert.NotEmpty(PlateSizes.All);
}
- [Fact]
- public void All_DoesNotContain48x48()
- {
- // 48x48 is not a standard sheet - it's the default MinSheet threshold only.
- Assert.DoesNotContain(PlateSizes.All, e => e.Width == 48 && e.Length == 48);
- }
-
[Fact]
public void All_Smallest_Is48x96()
{
@@ -76,233 +67,4 @@ public class PlateSizesTests
{
Assert.False(PlateSizes.TryGet("bogus", out _));
}
-
- [Fact]
- public void Recommend_BelowMin_SnapsToDefaultIncrementOfOne()
- {
- var bbox = new Box(0, 0, 10.3, 20.7);
-
- var result = PlateSizes.Recommend(bbox);
-
- Assert.Equal(11, result.Width);
- Assert.Equal(21, result.Length);
- Assert.Null(result.MatchedLabel);
- }
-
- [Fact]
- public void Recommend_BelowMin_UsesCustomIncrement()
- {
- var bbox = new Box(0, 0, 10.3, 20.7);
- var options = new PlateSizeOptions { SnapIncrement = 0.25 };
-
- var result = PlateSizes.Recommend(bbox, options);
-
- Assert.Equal(10.5, result.Width, 4);
- Assert.Equal(20.75, result.Length, 4);
- Assert.Null(result.MatchedLabel);
- }
-
- [Fact]
- public void Recommend_ExactlyAtMin_Snaps()
- {
- var bbox = new Box(0, 0, 48, 48);
-
- var result = PlateSizes.Recommend(bbox);
-
- Assert.Equal(48, result.Width);
- Assert.Equal(48, result.Length);
- Assert.Null(result.MatchedLabel);
- }
-
- [Fact]
- public void Recommend_AboveMin_PicksSmallestContainingStandardSheet()
- {
- var bbox = new Box(0, 0, 40, 90);
-
- var result = PlateSizes.Recommend(bbox);
-
- Assert.Equal(48, result.Width);
- Assert.Equal(96, result.Length);
- Assert.Equal("48x96", result.MatchedLabel);
- }
-
- [Fact]
- public void Recommend_AboveMin_WithRotation_PicksSmallestSheet()
- {
- var bbox = new Box(0, 0, 90, 40);
-
- var result = PlateSizes.Recommend(bbox);
-
- Assert.Equal("48x96", result.MatchedLabel);
- }
-
- [Fact]
- public void Recommend_JustOver48_PicksNextStandardSize()
- {
- var bbox = new Box(0, 0, 50, 100);
-
- var result = PlateSizes.Recommend(bbox);
-
- Assert.Equal(60, result.Width);
- Assert.Equal(120, result.Length);
- Assert.Equal("60x120", result.MatchedLabel);
- }
-
- [Fact]
- public void Recommend_MarginIsAppliedPerSide()
- {
- // 46 + 2*1 = 48 (fits exactly), 94 + 2*1 = 96 (fits exactly)
- var bbox = new Box(0, 0, 46, 94);
- var options = new PlateSizeOptions { Margin = 1 };
-
- var result = PlateSizes.Recommend(bbox, options);
-
- Assert.Equal("48x96", result.MatchedLabel);
- }
-
- [Fact]
- public void Recommend_MarginPushesToNextSheet()
- {
- // 47 + 2 = 49 > 48, so 48x96 no longer fits -> next standard
- var bbox = new Box(0, 0, 47, 95);
- var options = new PlateSizeOptions { Margin = 1 };
-
- var result = PlateSizes.Recommend(bbox, options);
-
- Assert.NotEqual("48x96", result.MatchedLabel);
- Assert.True(result.Width >= 49);
- Assert.True(result.Length >= 97);
- }
-
- [Fact]
- public void Recommend_AllowedSizes_StandardLabelWhitelist()
- {
- // 60x120 is the only option; 50x50 is above min so it routes to standard
- var bbox = new Box(0, 0, 50, 50);
- var options = new PlateSizeOptions { AllowedSizes = new[] { "60x120" } };
-
- var result = PlateSizes.Recommend(bbox, options);
-
- Assert.Equal("60x120", result.MatchedLabel);
- }
-
- [Fact]
- public void Recommend_AllowedSizes_ArbitraryWxHString()
- {
- // 50x100 isn't in the standard catalog but is valid as an ad-hoc entry.
- // bbox 49x99 doesn't fit 48x96 or 48x120, does fit 50x100 and 60x120,
- // but only 50x100 is allowed.
- var bbox = new Box(0, 0, 49, 99);
- var options = new PlateSizeOptions { AllowedSizes = new[] { "50x100" } };
-
- var result = PlateSizes.Recommend(bbox, options);
-
- Assert.Equal(50, result.Width);
- Assert.Equal(100, result.Length);
- Assert.Equal("50x100", result.MatchedLabel);
- }
-
- [Fact]
- public void Recommend_NothingFits_FallsBackToSnapUp()
- {
- // Larger than any catalog sheet
- var bbox = new Box(0, 0, 100, 300);
-
- var result = PlateSizes.Recommend(bbox);
-
- Assert.Equal(100, result.Width);
- Assert.Equal(300, result.Length);
- Assert.Null(result.MatchedLabel);
- }
-
- [Fact]
- public void Recommend_NothingFitsInAllowedList_FallsBackToSnapUp()
- {
- // Only 48x96 allowed, but bbox is too big for it
- var bbox = new Box(0, 0, 50, 100);
- var options = new PlateSizeOptions { AllowedSizes = new[] { "48x96" } };
-
- var result = PlateSizes.Recommend(bbox, options);
-
- Assert.Equal(50, result.Width);
- Assert.Equal(100, result.Length);
- Assert.Null(result.MatchedLabel);
- }
-
- [Fact]
- public void Recommend_BoxEnumerable_CombinesIntoEnvelope()
- {
- // Two boxes that together span 0..40 x 0..90 -> fits 48x96
- var boxes = new[] { new Box(0, 0, 40, 50), new Box(0, 40, 30, 50) };
-
- var result = PlateSizes.Recommend(boxes);
-
- Assert.Equal("48x96", result.MatchedLabel);
- }
-
- [Fact]
- public void Recommend_BoxEnumerable_Empty_Throws()
- {
- Assert.Throws(() =>
- PlateSizes.Recommend(System.Array.Empty())
- );
- }
-
- [Fact]
- public void PlateSizeOptions_Defaults()
- {
- var options = new PlateSizeOptions();
-
- Assert.Equal(48, options.MinSheetWidth);
- Assert.Equal(48, options.MinSheetLength);
- Assert.Equal(1.0, options.SnapIncrement);
- Assert.Equal(0, options.Margin);
- Assert.Null(options.AllowedSizes);
- Assert.Equal(PlateSizeSelection.SmallestArea, options.Selection);
- }
-
- [Fact]
- public void Recommend_NarrowestFirst_PicksNarrowerSheetOverSmallerArea()
- {
- // Hypothetical: bbox (47, 47) fits both 48x96 (area 4608) and some narrower option.
- // With SmallestArea: picks 48x96 (it's already the smallest 48-wide).
- // With NarrowestFirst: also picks 48x96 since that's the narrowest.
- // Better test: AllowedSizes = ["60x120", "48x120"] with bbox that fits both.
- // 48x120 (area 5760) is narrower; 60x120 (area 7200) has more area.
- // SmallestArea picks 48x120; NarrowestFirst also picks 48x120. Both pick the same.
- //
- // Real divergence: AllowedSizes = ["60x120", "72x120"] with bbox 55x100.
- // 60x120 has narrower width (60) AND smaller area (7200 vs 8640), so both agree.
- //
- // To force divergence: AllowedSizes = ["60x96", "48x144"] with bbox 47x95.
- // 60x96 area = 5760, 48x144 area = 6912. SmallestArea -> 60x96.
- // NarrowestFirst width 48 < 60 -> 48x144.
- var bbox = new Box(0, 0, 47, 95);
- var options = new PlateSizeOptions
- {
- AllowedSizes = new[] { "60x96", "48x144" },
- Selection = PlateSizeSelection.NarrowestFirst,
- };
-
- var result = PlateSizes.Recommend(bbox, options);
-
- Assert.Equal(48, result.Width);
- Assert.Equal(144, result.Length);
- }
-
- [Fact]
- public void Recommend_SmallestArea_PicksSmallerAreaOverNarrowerWidth()
- {
- var bbox = new Box(0, 0, 47, 95);
- var options = new PlateSizeOptions
- {
- AllowedSizes = new[] { "60x96", "48x144" },
- Selection = PlateSizeSelection.SmallestArea,
- };
-
- var result = PlateSizes.Recommend(bbox, options);
-
- Assert.Equal(60, result.Width);
- Assert.Equal(96, result.Length);
- }
}
diff --git a/OpenNest.Tests/SheetOutlineTests.cs b/OpenNest.Tests/SheetOutlineTests.cs
index c3986dc8..25aed606 100644
--- a/OpenNest.Tests/SheetOutlineTests.cs
+++ b/OpenNest.Tests/SheetOutlineTests.cs
@@ -179,8 +179,6 @@ public class SheetOutlineTests
plate.AutoSize();
Assert.Equal(96, plate.Size.Width);
Assert.NotNull(plate.Outline);
- Assert.Equal(default, plate.SnapToStandardSize());
- Assert.NotNull(plate.Outline);
}
[Fact]
diff --git a/OpenNest/Forms/EditNestForm.cs b/OpenNest/Forms/EditNestForm.cs
index 7903ba98..57215c39 100644
--- a/OpenNest/Forms/EditNestForm.cs
+++ b/OpenNest/Forms/EditNestForm.cs
@@ -21,7 +21,6 @@ using OpenNest.IO;
using OpenNest.Math;
using OpenNest.Properties;
using OpenNest.Reporting;
-using OpenNest.Shapes;
using Timer = System.Timers.Timer;
namespace OpenNest.Forms
@@ -962,11 +961,7 @@ namespace OpenNest.Forms
public void ResizePlateToFitParts()
{
- var options = new PlateSizeOptions
- {
- SnapIncrement = Settings.Default.AutoSizePlateFactor,
- };
- RecordChange("Resize Plate", () => PlateView.Plate.SnapToStandardSize(options));
+ RecordChange("Resize Plate", () => PlateView.Plate.AutoSize(Settings.Default.AutoSizePlateFactor));
PlateView.ZoomToPlate();
PlateView.Refresh();
UpdatePlateList();
diff --git a/docs/irregular-stock.md b/docs/irregular-stock.md
index eff7e57d..ad1f8ce1 100644
--- a/docs/irregular-stock.md
+++ b/docs/irregular-stock.md
@@ -38,7 +38,7 @@ Drawing an outline in the plate view and taking the leftover of a cut plate are
| Salvage and remnants | No salvage credit and no `expectedRemnant`: no rectangular edge strip is guaranteed inside an outline |
| Manual cut-offs | A line is cut only where it crosses material: each crossing is a segment of its own, started at the material's edge and overtraveled past the far one, within the user's limits and the usual part clearance. The head-locked overcut applies only to a span that reaches the bounding box's far edge |
| Automatic cut-offs | Refused: `AutomaticCutOffPlanner` rejects an outlined plate ("Automatic cut-offs need a rectangular sheet") |
-| AutoSize, Snap to Standard Size | Leave an outlined plate unchanged: the outline is a particular piece of material |
+| AutoSize (Resize Plate to Fit Parts) | Leaves an outlined plate unchanged: the outline is a particular piece of material |
| Rotate 90/180 | Turn the outline with the sheet |
| Repeated layouts, clamp-removal recovery, stock matching | An outlined stock is never the same stock as its rectangle |