Files
OpenNest/OpenNest.WinForms.Tests/Forms/FixSpacingMenuTests.cs
T
ajandClaude Opus 5.5 821c97e8dc feat(arrange): add Fix Spacing to push apart only flagged parts
Expand Spacing opens up a whole selection; nests that are a hair short
of fitting need the opposite: move only the parts the overlap check
flags, by the shortfall, and leave everything else where it is.

Fix Spacing pushes one part of each flagged pair straight away from the
other and ripples the push into neighbors it crowds, so rows already
locked in at the spacing can make room. When a plate is packed edge to
edge and no straight push fits, a constraint relaxation shares each
shortfall between both parts along the closest-point normal. Every
result is confirmed by the overlap check before any part moves, and is
applied only when it has fewer findings and nothing new.

NativeContours gains a coarse-flattening overload, Translated, Nested
and a closest-points GapTo for the search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:29:10 -05:00

101 lines
3.8 KiB
C#

using System.Windows.Forms;
using OpenNest.Forms;
using OpenNest.Geometry;
using static OpenNest.WinForms.Tests.TestReflection;
namespace OpenNest.WinForms.Tests.Forms;
// Reads Application.OpenForms for the busy guard, so it must not overlap fill-lifetime tests
// that transiently open NestProgressForm.
[Collection("Fill operation lifetime")]
public class FixSpacingMenuTests
{
[Fact]
public void FixSpacingOpensTheTooClosePair() => StaTestThread.Run(() =>
{
using var host = new MenuHost();
host.Show();
var (nest, plate) = TooClosePair();
using var editor = Open(host, nest);
Menu(host, "mnuPlateFixSpacing").PerformClick();
Assert.Equal(new Vector(1, 1), plate.Parts[0].Location);
Assert.InRange(plate.Parts[1].Location.X, 11.25, 11.252);
Assert.StartsWith("Fixed 1 of 1 flagged pair", editor.PlateView.Status);
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
[Fact]
public void NothingFlaggedReportsItWithoutAnEdit() => StaTestThread.Run(() =>
{
using var host = new MenuHost();
host.Show();
var (nest, plate) = TooClosePair();
plate.PartSpacing = 0.1;
using var editor = Open(host, nest);
var count = editor.History.UndoCount;
Menu(host, "mnuPlateFixSpacing").PerformClick();
Assert.Equal("No overlapping or too-close parts to fix", editor.PlateView.Status);
Assert.Equal(count, editor.History.UndoCount);
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
[Fact]
public void RunningFillOnTheNestRefusesFixWithoutMovingParts() => StaTestThread.Run(() =>
{
using var host = new MenuHost();
host.Show();
var (nest, plate) = TooClosePair();
using var editor = Open(host, nest);
var before = plate.Parts.Select(p => p.Location).ToArray();
var notices = new List<string>();
host.ExpandSpacingNotice = notices.Add;
SetBackingField(editor.PlateView, "IsFillInProgress", true);
try
{
Menu(host, "mnuPlateFixSpacing").PerformClick();
Assert.Contains("before Fix Spacing", Assert.Single(notices));
Assert.Equal(before, plate.Parts.Select(p => p.Location).ToArray());
}
finally
{
SetBackingField(editor.PlateView, "IsFillInProgress", false);
}
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
/// <summary>Two squares 0.1 apart on a plate whose part spacing is 0.25.</summary>
private static (Nest Nest, Plate Plate) TooClosePair()
{
var nest = new Nest("fix job");
var plate = nest.CreatePlate();
plate.Size = new Size(100, 100);
plate.PartSpacing = 0.25;
plate.Parts.Add(new Part(new Drawing("left", TestPrograms.ClockwiseSquare(10)), new Vector(1, 1)));
plate.Parts.Add(new Part(new Drawing("right", TestPrograms.ClockwiseSquare(10)), new Vector(11.1, 1)));
return (nest, plate);
}
private static EditNestForm Open(MainForm host, Nest nest)
{
var editor = new EditNestForm(nest) { MdiParent = host };
editor.PlateView.SetOverlapAutoCheck(null); // Background overlap workers are irrelevant here.
editor.Show();
editor.Activate();
editor.PlateManager.LoadAt(0);
return editor;
}
private static ToolStripMenuItem Menu(MainForm host, string name) =>
(ToolStripMenuItem)host.MainMenuStrip!.Items.Find(name, true).Single();
private sealed class MenuHost : MainForm
{
// No startup migrations/automatic new document, and no settings save at close.
protected override void OnLoad(EventArgs e) { }
protected override void OnClosing(System.ComponentModel.CancelEventArgs e) { }
}
}