mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-17 13:51:40 -04:00
Slice 3b task 3 (D9 + display). OpenNest.Core: TimingInfo gains ClampRemovalMinutes (included in +, -, * so every aggregation stays consistent); Timing.GetTimingInfo(Plate) adds plate.ClampRemovalMinutes once per physical sheet, before the quantity multiply so it scales with everything else cut on that sheet; CalculateTime adds it to the estimate as a flat number of minutes regardless of the cut parameters' unit. A plate with nothing removed is unaffected (zero). OpenNest.Data: ClampSettings and ClampRailEditor carry RemovalMinutes through the same three boundaries as clamp size - the rail/constructor, the settings file (missing key falls back to ClampRail.DefaultRemovalMinutes; present but negative/non-numeric/null is ignored and reported Partial, same as width/depth), and ToMachineDefaults - except it is a time, not a length, so ConvertedTo never scales it between inches and millimeters. Desktop: ClampsForm gets a 'Minutes to remove and replace one clamp' field wired to the editor; PlateRenderer.DrawRemovedClamps draws each clamp Plate.RemovedClamps lists (resolved from the full rail, since a removed clamp is absent from Plate.ClampZones) as a dashed grey outline with a small 'remove' marker, distinct from an active clamp's solid hatch and still display-only - no hit test, selection or sequencing; PlateView calls it right after the active-zone pass. MainForm's Auto Nest completion flow lists every committed sheet with a removal as 'Sheet N: remove clamp-X (+5 min)', using Plate.RemovedClamps/ClampRemovalMinutes from Slice 3b tasks 1-2. Tests: 10 new Data (ClampSettingsTests, ClampRailEditorTests) covering round-trip/default/invalid-value handling and the never-scaled-by-units rule; 4 new Core (TimingTests) covering the per-sheet add, the quantity scaling, CalculateTime, and nest-level aggregation across plates. Desktop changes (dialog field, renderer, Auto Nest summary) are compile-verified only (-p:EnableWindowsTargeting=true), matching the project's existing WinForms testing bar - no automated UI test exists for the sibling DrawClampZones either. Gates: Release OpenNest.Tests 3898 passed/31 skipped, OpenNest.IO.Tests 105 passed, OpenNest.Engine.Tests 878 passed/2 skipped; desktop and OpenNest.WinForms.Tests cross-builds clean; scoped dotnet format --verify-no-changes clean. This completes Slice 3b (D8/D9) on top of Slice 3's rail-following plates. Not published; master and other feature/clamp-zones-* branches untouched. Windows runtime/visual acceptance not claimed - the plan's click-through checklist (after slices 3-5) still applies.
215 lines
7.7 KiB
C#
215 lines
7.7 KiB
C#
using OpenNest.Data;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Tests.Data;
|
|
|
|
/// <summary>
|
|
/// Decision logic of the desktop Clamps dialog: edits one nest's rail in its units, keeps
|
|
/// "apply to this nest" (OK) separate from "save as machine defaults" (immediate write).
|
|
/// </summary>
|
|
public class ClampRailEditorTests
|
|
{
|
|
private static readonly double[] MachineCenters = { 6, 21, 36, 51, 66, 81, 96, 111 };
|
|
|
|
[Fact]
|
|
public void NestWithoutARailStartsEmptyAtTheDefaultClampSize()
|
|
{
|
|
var editor = new ClampRailEditor(null, Units.Inches, applyToNewNests: false);
|
|
|
|
Assert.Equal(1.5, editor.Width);
|
|
Assert.Equal(0.625, editor.Depth);
|
|
Assert.Empty(editor.Centers);
|
|
Assert.Null(editor.BuildRail());
|
|
|
|
var metric = new ClampRailEditor(null, Units.Millimeters, applyToNewNests: false);
|
|
Assert.Equal(1.5 * 25.4, metric.Width, 9);
|
|
}
|
|
|
|
[Fact]
|
|
public void NestRailIsEditedInPlace()
|
|
{
|
|
var rail = new ClampRail(1.5, 0.625, MachineCenters);
|
|
|
|
var editor = new ClampRailEditor(rail, Units.Inches, applyToNewNests: true);
|
|
|
|
Assert.Equal(MachineCenters, editor.Centers);
|
|
Assert.Equal(rail, editor.BuildRail());
|
|
Assert.True(editor.ApplyToNewNests);
|
|
}
|
|
|
|
[Fact]
|
|
public void CentersStaySortedAndRefuseInvalidOrDuplicateValues()
|
|
{
|
|
var editor = new ClampRailEditor(null, Units.Inches, applyToNewNests: false);
|
|
|
|
Assert.True(editor.TryAddCenter(21, out _));
|
|
Assert.True(editor.TryAddCenter(6, out var none));
|
|
Assert.Null(none);
|
|
Assert.False(editor.TryAddCenter(21, out var duplicate));
|
|
Assert.Contains("already", duplicate);
|
|
Assert.False(editor.TryAddCenter(-1, out var negative));
|
|
Assert.Contains("origin", negative);
|
|
Assert.False(editor.TryAddCenter(double.NaN, out _));
|
|
Assert.Equal(new[] { 6.0, 21 }, editor.Centers);
|
|
|
|
editor.RemoveCenterAt(0);
|
|
editor.RemoveCenterAt(5); // out of range: ignored
|
|
Assert.Equal(new[] { 21.0 }, editor.Centers);
|
|
}
|
|
|
|
[Fact]
|
|
public void ApplyingToTheNestReResolvesEveryPlate()
|
|
{
|
|
var nest = new Nest();
|
|
nest.PlateDefaults.Size = new Size(48, 120);
|
|
var full = nest.CreatePlate();
|
|
var shorter = nest.CreatePlate();
|
|
shorter.Size = new Size(48, 60);
|
|
var editor = new ClampRailEditor(null, Units.Inches, applyToNewNests: false);
|
|
foreach (var center in MachineCenters)
|
|
Assert.True(editor.TryAddCenter(center, out _));
|
|
|
|
editor.ApplyTo(nest);
|
|
|
|
Assert.Equal(8, full.ClampZones.Count);
|
|
Assert.Equal(4, shorter.ClampZones.Count);
|
|
Assert.Equal(8, nest.CreatePlate().ClampZones.Count);
|
|
|
|
// Removing every clamp and applying removes them from the nest.
|
|
while (editor.Centers.Count > 0)
|
|
editor.RemoveCenterAt(0);
|
|
editor.ApplyTo(nest);
|
|
Assert.Null(nest.PlateDefaults.ClampRail);
|
|
Assert.All(nest.Plates, p => Assert.Empty(p.ClampZones));
|
|
}
|
|
|
|
[Fact]
|
|
public void InvalidClampSizeIsRefusedBeforeApplyingOrSaving()
|
|
{
|
|
var nest = new Nest();
|
|
var plate = nest.CreatePlate();
|
|
var editor = new ClampRailEditor(null, Units.Inches, applyToNewNests: true);
|
|
editor.TryAddCenter(6, out _);
|
|
editor.Width = 0;
|
|
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => editor.ApplyTo(nest));
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => editor.ToMachineDefaults());
|
|
Assert.Empty(plate.ClampZones);
|
|
Assert.Null(nest.PlateDefaults.ClampRail);
|
|
}
|
|
|
|
[Fact]
|
|
public void MachineDefaultsCaptureTheEditedValuesInTheNestsUnits()
|
|
{
|
|
var editor = new ClampRailEditor(null, Units.Millimeters, applyToNewNests: true);
|
|
editor.Width = 40;
|
|
editor.Depth = 16;
|
|
editor.TryAddCenter(150, out _);
|
|
|
|
var settings = editor.ToMachineDefaults();
|
|
|
|
Assert.True(settings.Enabled);
|
|
Assert.Equal(Units.Millimeters, settings.Units);
|
|
Assert.Equal(40, settings.Width);
|
|
Assert.Equal(16, settings.Depth);
|
|
Assert.Equal(new[] { 150.0 }, settings.Centers);
|
|
|
|
// Without any clamp listed, saved defaults never enable clamps for new nests.
|
|
editor.RemoveCenterAt(0);
|
|
Assert.False(editor.ToMachineDefaults().Enabled);
|
|
editor.ApplyToNewNests = false;
|
|
editor.TryAddCenter(150, out _);
|
|
Assert.False(editor.ToMachineDefaults().Enabled);
|
|
}
|
|
|
|
[Fact]
|
|
public void LoadingMachineDefaultsOnlyFillsTheEditorInTheNestsUnits()
|
|
{
|
|
var nest = new Nest { Units = Units.Millimeters };
|
|
var plate = nest.CreatePlate();
|
|
var defaults = new ClampSettings
|
|
{
|
|
Enabled = true,
|
|
Units = Units.Inches,
|
|
Width = 1.5,
|
|
Depth = 0.625,
|
|
Centers = new[] { 21.0, 6 },
|
|
};
|
|
var editor = new ClampRailEditor(null, Units.Millimeters, applyToNewNests: false);
|
|
|
|
editor.LoadMachineDefaults(defaults);
|
|
|
|
Assert.Equal(1.5 * 25.4, editor.Width, 9);
|
|
Assert.Equal(0.625 * 25.4, editor.Depth, 9);
|
|
Assert.Equal(new[] { 6 * 25.4, 21 * 25.4 }, editor.Centers);
|
|
Assert.True(editor.ApplyToNewNests);
|
|
Assert.Empty(plate.ClampZones); // nothing applied until OK
|
|
Assert.Null(nest.PlateDefaults.ClampRail);
|
|
|
|
// Disabled defaults still load their values but do not ask to enable new nests.
|
|
defaults.Enabled = false;
|
|
editor.LoadMachineDefaults(defaults);
|
|
Assert.False(editor.ApplyToNewNests);
|
|
Assert.Equal(2, editor.Centers.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void ConvertedSettingsKeepEnabledAndScaleEveryLength()
|
|
{
|
|
var inches = new ClampSettings { Enabled = true, Width = 1.5, Depth = 0.625, Centers = new[] { 6.0 } };
|
|
|
|
var metric = inches.ConvertedTo(Units.Millimeters);
|
|
|
|
Assert.True(metric.Enabled);
|
|
Assert.Equal(Units.Millimeters, metric.Units);
|
|
Assert.Equal(38.1, metric.Width, 9);
|
|
Assert.Equal(15.875, metric.Depth, 9);
|
|
Assert.Equal(152.4, metric.Centers[0], 9);
|
|
Assert.Equal(1.5, inches.Width); // the source is not modified
|
|
Assert.Equal(1.5, inches.ConvertedTo(Units.Inches).Width);
|
|
}
|
|
|
|
// D9: the editor carries removal minutes through the same three boundaries as size.
|
|
|
|
[Fact]
|
|
public void RemovalMinutesStartsFromTheRailOrMachineDefault()
|
|
{
|
|
var rail = new ClampRail(1.5, 0.625, MachineCenters, removalMinutes: 7);
|
|
Assert.Equal(7, new ClampRailEditor(rail, Units.Inches, false).RemovalMinutes);
|
|
Assert.Equal(ClampRail.DefaultRemovalMinutes,
|
|
new ClampRailEditor(null, Units.Inches, false).RemovalMinutes);
|
|
}
|
|
|
|
[Fact]
|
|
public void RemovalMinutesIsNeverConvertedBetweenUnitsOnLoadOrBuild()
|
|
{
|
|
var editor = new ClampRailEditor(null, Units.Millimeters, applyToNewNests: true);
|
|
editor.RemovalMinutes = 8;
|
|
editor.TryAddCenter(150, out _);
|
|
|
|
Assert.Equal(8, editor.ToMachineDefaults().RemovalMinutes);
|
|
Assert.Equal(8, editor.BuildRail()!.RemovalMinutes);
|
|
|
|
var defaults = new ClampSettings
|
|
{
|
|
Enabled = true,
|
|
Units = Units.Inches,
|
|
RemovalMinutes = 9,
|
|
Centers = new[] { 6.0 },
|
|
};
|
|
editor.LoadMachineDefaults(defaults);
|
|
Assert.Equal(9, editor.RemovalMinutes); // a time, so unaffected by the unit change
|
|
}
|
|
|
|
[Fact]
|
|
public void InvalidRemovalMinutesIsRefusedBeforeSaving()
|
|
{
|
|
var editor = new ClampRailEditor(null, Units.Inches, applyToNewNests: true);
|
|
editor.TryAddCenter(6, out _);
|
|
editor.RemovalMinutes = -1;
|
|
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => editor.ToMachineDefaults());
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => editor.BuildRail());
|
|
}
|
|
}
|