Files
OpenNest/OpenNest.Tests/CuttingPlanning/SafestRouteFallbackTests.cs
T
ajandClaude Opus 5.5 f0d84e041c test: share the circle, square, rectangle and contour fixtures
Cutting-planning, diagnostics and fill tests drew the same circles,
squares, clockwise rectangles, closed polygons, rounded rectangles and
right-midpoint rectangle contours in private helpers. They now use
TestHelpers; files whose helper had its own default size keep a
one-line wrapper for it.

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

125 lines
5.8 KiB
C#

using OpenNest.CNC;
using OpenNest.CNC.CuttingPlanning;
using OpenNest.Diagnostics;
using OpenNest.Engine.CuttingPlanning;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.CuttingPlanning;
public class SafestRouteFallbackTests
{
[Fact]
public void KeptOrderThroughAWall_IntoALockedPart_OffersTheSafestRouteAsAnUnverifiedPlan()
{
// The rapid into a locked program cannot be marked to raise the head, so it stays a finding.
var plate = WalledPlate(lockRight: true);
var proposal = CuttingPlanBatch.Capture([plate], ExplicitContourTests.Parameters(), preservePartOrder: true).Plan();
var planned = Assert.Single(proposal.Plates);
Assert.True(planned.Result.IsSafestRouteFallback);
Assert.Equal(CuttingPlanStatus.BestEffort, planned.Result.Status);
Assert.False(planned.IsReady);
Assert.True(planned.CanApplyWithWarnings);
Assert.False(planned.HasOnlyFixedLeadWarnings);
Assert.False(planned.SearchLimitReached);
Assert.Equal(plate.Parts, planned.Result.ProposedOrder.Select(p => p.SourcePart));
Assert.Equal(0, planned.Result.HeadRaises);
// Only the rapid from the left square to the right one has to cross the cut bar.
var crossing = Assert.Single(planned.Result.Findings, f => f.Kind == PostVerificationKind.RapidCrossing);
Assert.Equal(2, crossing.SourceOrdinal);
Assert.Equal(0, crossing.OtherSourceOrdinal);
Assert.DoesNotContain(planned.Result.Findings, f => f.Kind is null or PostVerificationKind.Incomplete);
Assert.True(proposal.RequiresWarningAcceptance);
var text = string.Join("\n", proposal.Describe("in"));
Assert.Contains("Safest-route plan available", text);
Assert.Contains("safest route found, unverified", text);
Assert.Contains("breaks 1 check", text);
}
[Fact]
public void SafestRoute_AppliesOnlyWithExplicitAcceptance()
{
var plate = WalledPlate(lockRight: true);
var proposal = CuttingPlanBatch.Capture([plate], ExplicitContourTests.Parameters(), preservePartOrder: true).Plan();
Assert.Equal(CuttingCommitStatus.InvalidInput, proposal.Apply().Status);
Assert.All(plate.Parts.Take(2), part => Assert.False(part.HasManualLeadIns));
Assert.Equal(CuttingCommitStatus.Applied, proposal.Apply(acceptWarnings: true).Status);
Assert.All(plate.Parts, part => Assert.True(part.HasManualLeadIns));
}
[Fact]
public void FreeOrder_FindsTheRouteThatPassesEveryCheck_WithoutTheFallback()
{
var proposal = CuttingPlanBatch.Capture([WalledPlate()], ExplicitContourTests.Parameters(), preservePartOrder: false).Plan();
var planned = Assert.Single(proposal.Plates);
Assert.True(planned.IsReady);
Assert.False(planned.Result.IsSafestRouteFallback);
Assert.Equal(0, planned.Result.HeadRaises);
}
[Fact]
public void SafestRoute_ThatPassesEveryCheck_IsReadyAndReplayed()
{
var snapshot = CuttingPlanService.Capture(CuttingPlanRequest.ForPlate(WalledPlate(),
confirmedParameters: ExplicitContourTests.Parameters()));
var result = CuttingPlanService.PlanSafestRoute(snapshot, CancellationToken.None);
Assert.NotNull(result);
Assert.Equal(CuttingPlanStatus.Ready, result.Status);
Assert.True(result.IndependentlyReplayed);
Assert.False(result.IsSafestRouteFallback);
Assert.Empty(result.Findings);
}
[Fact]
public void SafestRoute_NeverMutatesThePlate()
{
var plate = WalledPlate();
var programs = plate.Parts.Select(p => ExplicitContourTests.Fingerprint(p.Program)).ToArray();
var snapshot = CuttingPlanService.Capture(CuttingPlanRequest.ForPlate(plate,
confirmedParameters: ExplicitContourTests.Parameters(), preservePartOrder: true));
Assert.NotNull(CuttingPlanService.PlanSafestRoute(snapshot, CancellationToken.None));
Assert.Equal(programs, plate.Parts.Select(p => ExplicitContourTests.Fingerprint(p.Program)));
Assert.All(plate.Parts, part => Assert.False(part.HasManualLeadIns));
}
[Fact]
public void Severity_RanksMaterialBeforeRapidsBeforeEverythingElse()
{
var lead = RouteSeverity.Of([null]);
var rapids = RouteSeverity.Of([PostVerificationKind.RapidCrossing, PostVerificationKind.RapidCrossing]);
var minor = RouteSeverity.Of([PostVerificationKind.PierceZone, PostVerificationKind.PartSupport, PostVerificationKind.MissingLeadIn]);
Assert.Equal(new RouteSeverity(1, 0, 0), lead);
Assert.True(minor < rapids);
Assert.True(rapids < lead);
Assert.True(default(RouteSeverity) < minor);
Assert.True(default(RouteSeverity).IsClear);
Assert.Equal(new RouteSeverity(1, 0, 0), RouteSeverity.Of([PostVerificationKind.NestedCutOrder]));
Assert.Equal(new RouteSeverity(1, 0, 0), RouteSeverity.Of([PostVerificationKind.Incomplete]));
}
// A tall bar between two squares, in the order bar, left, right: once the bar is cut every
// straight rapid from the left square to the right one crosses it. Cut last, it crosses nothing.
private static Plate WalledPlate(bool lockRight = false)
{
var plate = new Nest().CreatePlate();
plate.Size = new Size(60, 60);
plate.Parts.Add(new Part(
new Drawing("bar", MakeClockwiseRectProgram(1, 50)), new Vector(25, 2)));
plate.Parts.Add(new Part(new Drawing("left", ExplicitContourTests.Square(false)), new Vector(5, 20)));
var right = new Part(new Drawing("right", ExplicitContourTests.Square(false)), new Vector(36, 20));
if (lockRight)
Assert.True(right.RestoreLeadInProgram((Program)right.Program.Clone(), true));
plate.Parts.Add(right);
return plate;
}
}