using OpenNest.CNC;
using OpenNest.Engine.Sequencing;
using OpenNest.Geometry;
namespace OpenNest.Tests.CutOffs;
///
/// Trim, Extend and Swap Through-Cut at a line's ends change only its start and end limits (gaps
/// are in ): a trim stops on
/// the crossing line's center, an extend runs to the next crossing line or the sheet edge, and a
/// swap makes the stopped line run through while the through line loses its far side.
///
public class CutOffTrimTests
{
private const double SeparatorY = 31.510593;
private const double LastVerticalX = 70.221406;
private static readonly CutOffSettings Settings = new();
// N26-264 plate 1: three vertical lines stop at a full-length horizontal separator.
private static (Plate Plate, CutOff Vertical, CutOff Horizontal) N26264()
{
var plate = new Plate(48, 96);
foreach (var x in new[] { 35, 68.6, LastVerticalX })
plate.CutOffs.Add(new CutOff(new Vector(x, 0), CutOffAxis.Vertical) { EndLimit = SeparatorY });
var horizontal = new CutOff(new Vector(0, SeparatorY), CutOffAxis.Horizontal);
plate.CutOffs.Add(horizontal);
plate.RegenerateCutOffs(Settings);
return (plate, plate.CutOffs[2], horizontal);
}
[Fact]
public void Swap_AtTheN26264Junction_RunsTheVerticalThroughAndStopsTheHorizontalAtIt()
{
var (plate, vertical, horizontal) = N26264();
var junction = CutOffTrim.JunctionAt(plate, new Vector(LastVerticalX + 0.1, SeparatorY), 0.5);
Assert.Equal((horizontal, vertical), junction);
var edit = CutOffTrim.Swap(plate, junction!.Value.Through, junction.Value.Stopper, Settings);
Assert.True(edit.Succeeded, edit.Refusal);
edit.Apply();
Assert.Null(vertical.StartLimit);
Assert.Null(vertical.EndLimit);
Assert.Null(horizontal.StartLimit);
Assert.Equal(LastVerticalX, horizontal.EndLimit);
Assert.All(plate.CutOffs.Take(2), other => Assert.Equal(SeparatorY, other.EndLimit));
Assert.Null(edit.Notice);
}
[Fact]
public void Swap_PlacesTheNewSeparatorLast()
{
var (plate, vertical, horizontal) = N26264();
Assert.Same(horizontal.Drawing, plate.Parts[^1].BaseDrawing);
var edit = CutOffTrim.Swap(plate, horizontal, vertical, Settings);
edit.Apply();
plate.RegenerateCutOffs(Settings);
PlateSequencing.PlaceCutOffs(plate, edit.Changes.Select(change => change.CutOff));
var last = plate.Parts[^1];
Assert.Same(vertical.Drawing, last.BaseDrawing);
Assert.True(CutOff.SeparatesSheet(last, vertical, plate.BoundingBox(includeParts: false)));
}
[Fact]
public void Swap_RefusesLinesThatBothRunThrough()
{
var plate = new Plate(48, 96);
var vertical = new CutOff(new Vector(40, 0), CutOffAxis.Vertical);
var horizontal = new CutOff(new Vector(0, 20), CutOffAxis.Horizontal);
plate.CutOffs.Add(vertical);
plate.CutOffs.Add(horizontal);
Assert.Null(CutOffTrim.JunctionAt(plate, new Vector(40, 20), 0.5));
Assert.Equal(CutOffTrim.NotAJunction, CutOffTrim.Swap(plate, vertical, horizontal, Settings).Refusal);
}
[Fact]
public void Swap_RefusesWhenTheThroughLineWouldBeTooShort()
{
var plate = new Plate(48, 96);
var vertical = new CutOff(new Vector(0.03, 0), CutOffAxis.Vertical) { EndLimit = 20 };
var horizontal = new CutOff(new Vector(0, 20), CutOffAxis.Horizontal);
plate.CutOffs.Add(vertical);
plate.CutOffs.Add(horizontal);
Assert.Equal(CutOffTrim.TooShort, CutOffTrim.Swap(plate, horizontal, vertical, Settings).Refusal);
}
[Fact]
public void Trim_RemovesTheFarPieceUpToTheCrossingLine()
{
var (plate, _, horizontal) = N26264();
var edit = CutOffTrim.Trim(plate, horizontal, new Vector(80, SeparatorY), Settings);
edit.Apply();
Assert.Null(horizontal.StartLimit);
Assert.Equal(LastVerticalX, horizontal.EndLimit);
}
[Fact]
public void Trim_RemovesTheOriginPieceUpToTheCrossingLine()
{
var (plate, _, horizontal) = N26264();
var edit = CutOffTrim.Trim(plate, horizontal, new Vector(10, SeparatorY), Settings);
edit.Apply();
Assert.Equal(35, horizontal.StartLimit);
Assert.Null(horizontal.EndLimit);
}
[Fact]
public void Trim_RefusalChangesNothing()
{
var plate = new Plate(48, 96);
var lone = new CutOff(new Vector(40, 0), CutOffAxis.Vertical);
plate.CutOffs.Add(lone);
var refused = CutOffTrim.Trim(plate, lone, new Vector(40, 10), Settings);
Assert.Empty(refused.Changes);
Assert.Throws(refused.Apply);
}
[Fact]
public void Trim_RefusesALineNothingCrosses()
{
var plate = new Plate(48, 96);
var lone = new CutOff(new Vector(40, 0), CutOffAxis.Vertical);
plate.CutOffs.Add(lone);
Assert.Equal(CutOffTrim.NothingToTrimTo, CutOffTrim.Trim(plate, lone, new Vector(40, 10), Settings).Refusal);
}
[Fact]
public void Trim_IgnoresALineThatStopsShortOfTheTarget()
{
var (plate, _, _) = N26264();
var high = new CutOff(new Vector(0, 40), CutOffAxis.Horizontal);
plate.CutOffs.Add(high);
// The verticals end at the separator, below this line.
Assert.Equal(CutOffTrim.NothingToTrimTo, CutOffTrim.Trim(plate, high, new Vector(80, 40), Settings).Refusal);
}
[Fact]
public void Trim_RefusesToLeaveLessThanTheMinimumSegment()
{
var plate = new Plate(48, 96);
var horizontal = new CutOff(new Vector(0, 10), CutOffAxis.Horizontal);
plate.CutOffs.Add(horizontal);
plate.CutOffs.Add(new CutOff(new Vector(0.03, 0), CutOffAxis.Vertical));
Assert.Equal(CutOffTrim.TooShort, CutOffTrim.Trim(plate, horizontal, new Vector(50, 10), Settings).Refusal);
}
[Fact]
public void Edits_RefuseACutOffFromAnotherPlate()
{
var (plate, _, _) = N26264();
var stranger = new CutOff(new Vector(10, 0), CutOffAxis.Vertical);
Assert.Equal(CutOffTrim.NotOnPlate, CutOffTrim.Trim(plate, stranger, new Vector(10, 5), Settings).Refusal);
Assert.Equal(CutOffTrim.NotOnPlate, CutOffTrim.Extend(plate, stranger, new Vector(10, 5)).Refusal);
Assert.Equal(CutOffTrim.NotOnPlate, CutOffTrim.Swap(plate, stranger, plate.CutOffs[3], Settings).Refusal);
}
[Fact]
public void Extend_WithNothingBeyond_RunsToTheSheetEdge()
{
var (plate, vertical, _) = N26264();
var edit = CutOffTrim.Extend(plate, vertical, new Vector(LastVerticalX, 30));
edit.Apply();
Assert.Null(vertical.StartLimit);
Assert.Null(vertical.EndLimit);
}
[Fact]
public void Extend_SkipsTheLineAtItsEndAndStopsAtTheNextOne()
{
var (plate, vertical, _) = N26264();
plate.CutOffs.Add(new CutOff(new Vector(0, 40), CutOffAxis.Horizontal));
var edit = CutOffTrim.Extend(plate, vertical, new Vector(LastVerticalX, 30));
edit.Apply();
Assert.Equal(40, vertical.EndLimit);
}
[Fact]
public void Extend_GrowsTheStartWhenPickedNearIt()
{
var plate = new Plate(48, 96);
var vertical = new CutOff(new Vector(40, 0), CutOffAxis.Vertical) { StartLimit = 20, EndLimit = 30 };
plate.CutOffs.Add(vertical);
plate.CutOffs.Add(new CutOff(new Vector(0, 5), CutOffAxis.Horizontal));
CutOffTrim.Extend(plate, vertical, new Vector(40, 21)).Apply();
Assert.Equal(5, vertical.StartLimit);
Assert.Equal(30, vertical.EndLimit);
}
[Fact]
public void Extend_RefusesAnEndAlreadyAtTheSheetEdge()
{
var (plate, _, horizontal) = N26264();
Assert.Equal(CutOffTrim.AtSheetEdge, CutOffTrim.Extend(plate, horizontal, new Vector(90, SeparatorY)).Refusal);
}
[Fact]
public void HeadLockedOvercut_FollowsTheFarEdgeLimit()
{
var (plate, vertical, horizontal) = N26264();
horizontal.HeadLockOvercut = 1.5;
vertical.HeadLockOvercut = 1.5;
var trim = CutOffTrim.Trim(plate, horizontal, new Vector(80, SeparatorY), Settings);
Assert.Equal(CutOffTrim.OvercutOff, trim.Notice);
var extend = CutOffTrim.Extend(plate, vertical, new Vector(LastVerticalX, 30));
Assert.Equal(CutOffTrim.OvercutOn, extend.Notice);
trim.Apply();
plate.RegenerateCutOffs(Settings);
Assert.DoesNotContain(horizontal.Drawing.Program.Codes, HeadHeightLock.IsLock);
}
[Fact]
public void OutlinedSheet_ExtendStillCutsOnlyMaterial()
{
// An L-shaped sheet: material is 24 tall right of X 48.
var plate = new Plate { Outline = SheetOutlineTests.EllSheet() };
var vertical = new CutOff(new Vector(60, 0), CutOffAxis.Vertical) { EndLimit = 12 };
plate.CutOffs.Add(vertical);
plate.CutOffs.Add(new CutOff(new Vector(0, 12), CutOffAxis.Horizontal));
CutOffTrim.Extend(plate, vertical, new Vector(60, 11)).Apply();
plate.RegenerateCutOffs(Settings);
Assert.Null(vertical.EndLimit);
var top = vertical.Drawing.Program.Codes.OfType().Max(move => move.EndPoint.Y);
Assert.Equal(24, top, 6);
}
}