Files
OpenNest/OpenNest.Tests/CuttingPlanning/PinnedLeadWarningTests.cs
T
aj 1ed66bbf20 fix(ui): point operator messages at commands that exist
Pinned-lead and locked-lead warnings named a Drawing Pins Off command and a
Clear Lead-ins command that the desktop does not have; name Plate > Remove
Lead-ins and spacing instead. Closing Plan Cutting no longer claims nothing
changed after lead-ins or cut-offs were cleared, the no-stock reason no
longer says the part is larger than every sheet when only the usable area at
the checked rotations is too small, and the console says whole-job engines.
2026-10-19 12:15:40 -04:00

153 lines
7.6 KiB
C#

using OpenNest.CNC;
using OpenNest.CNC.CuttingPlanning;
using OpenNest.CNC.CuttingStrategy;
using OpenNest.Diagnostics;
using OpenNest.Engine.CuttingPlanning;
using OpenNest.Geometry;
namespace OpenNest.Tests.CuttingPlanning;
/// <summary>
/// D1a for drawing pins: a pinned lead that meets material is a named warning on an UNVERIFIED
/// proposal, never relocated or shortened. The rapids between contours stay strict.
/// </summary>
public class PinnedLeadWarningTests
{
[Fact]
public void PinnedLeadIntoNeighbour_IsUnverifiedWithTheRestPlannedAndTheEntryKept()
{
var (plate, pinned, neighbour) = NeighbourFixture(pins: true);
var proposal = CuttingPlanBatch.Capture([plate], PinnedContourTests.NestSettings(), false).Plan();
var planned = Assert.Single(proposal.Plates);
Assert.True(planned.HasOnlyFixedLeadWarnings, string.Join("\n", proposal.Describe("in")));
var warning = Assert.Single(planned.Result.Findings);
Assert.Equal(PostVerificationKind.FixedLead, warning.Kind);
Assert.Same(pinned, warning.SourcePart);
Assert.Same(neighbour, warning.OtherSourcePart);
Assert.Equal(new FixedLeadWarning(1, 1, 1), warning.FixedLead);
Assert.Equal("Drawing pin 1 lead on cutting contour 1 contacts or enters another part's material. "
+ "The pinned contour is cut exactly as its drawing says. Increase spacing or move this part away "
+ "from the part it meets, then replan.", warning.Message);
// The search never relocates the pinned entry, and the neighbour is planned normally.
var proposed = planned.Result.ProposedOrder;
var placement = Assert.Single(proposed, p => ReferenceEquals(p.SourcePart, pinned));
Assert.Equal(new Vector(10, 5), Assert.Single(placement.ContourChoices).Point);
var lead = ExecutionMotionReader.Read(placement.CopyProgram(), pinned.Location, null, default).Motions
.Single(m => m.Layer == LayerType.Leadin);
Assert.Equal(1.0, lead.Length, 9); // Not shortened to fit.
Assert.True(Assert.Single(proposed, p => ReferenceEquals(p.SourcePart, neighbour)).IsRegenerated);
Assert.Equal(CuttingCommitStatus.InvalidInput, proposal.Apply().Status);
Assert.Equal(CuttingCommitStatus.Applied, proposal.Apply(acceptWarnings: true).Status);
}
[Fact]
public void UnpinnedControl_IsReady()
{
var (plate, _, _) = NeighbourFixture(pins: false);
var proposal = CuttingPlanBatch.Capture([plate], PinnedContourTests.NestSettings(), false).Plan();
Assert.True(proposal.CanApply, string.Join("\n", proposal.Describe("in")));
}
[Fact]
public void PinsOffOnThePinnedPart_IsReady()
{
var (plate, pinned, _) = NeighbourFixture(pins: true);
pinned.DrawingPinsOff = true;
var proposal = CuttingPlanBatch.Capture([plate], PinnedContourTests.NestSettings(), false).Plan();
Assert.True(proposal.CanApply, string.Join("\n", proposal.Describe("in")));
}
[Fact]
public void PinnedHoleLeadIntoItsOwnPartsMaterial_Warns()
{
// Two square holes; hole 2's pinned 2.5 lead runs from beyond its right edge.
var program = new Program();
program.MoveTo(2, 2); program.LineTo(4, 2); program.LineTo(4, 4); program.LineTo(2, 4); program.LineTo(2, 2);
program.MoveTo(4.5, 2); program.LineTo(6.5, 2); program.LineTo(6.5, 4); program.LineTo(4.5, 4); program.LineTo(4.5, 2);
program.MoveTo(0, 0); program.LineTo(0, 10); program.LineTo(10, 10); program.LineTo(10, 0); program.LineTo(0, 0);
var drawing = new Drawing("holes", program);
drawing.CutPins.Add(new ContourCutPin(new Vector(4.5, 3), new LineLeadIn { Length = 2.5, ApproachAngle = 90 },
new NoLeadOut()));
var part = new Part(drawing, new Vector(1, 1));
var result = CuttingPlanService.Plan(new CuttingPlanRequest([part], new Vector(-2, -2),
confirmedParameters: PinnedContourTests.NestSettings()));
Assert.Equal(CuttingPlanStatus.BestEffort, result.Status);
Assert.True(result.IndependentlyReplayed);
var warning = Assert.Single(result.Findings);
Assert.StartsWith("Drawing pin 1 lead on cutting contour", warning.Message);
Assert.Contains("contacts or enters its own part's material", warning.Message);
Assert.Null(warning.OtherSourcePart);
Assert.Equal(1, warning.FixedLead!.PinNumber);
}
[Fact]
public void RapidIntoAPinnedContour_StaysStrict()
{
// Holes(): circles at (3, 3) and (7, 3) in a 10 x 10 square; the outside is pinned at
// (10, 3). Cutting the left hole last and leaving from its far side makes the planner's
// rapid to the pinned pierce cross both finished holes: a refusal, never a warning.
var drawing = new Drawing("holes", PreparedContourTests.Holes());
drawing.CutPins.Add(new ContourCutPin(new Vector(10, 3), new LineLeadIn { Length = 0.25, ApproachAngle = 90 },
new NoLeadOut()));
var part = new Part(drawing, Vector.Zero);
var snapshot = CuttingPlanService.Capture(new CuttingPlanRequest([part], new Vector(-2, -2),
confirmedParameters: PinnedContourTests.NestSettings()));
var source = snapshot.Placements[0];
var prepared = source.Prepared!;
var choices = new[] { prepared.Entry(1, 0, new Vector(6, 3)), prepared.Entry(0, 0, new Vector(2, 3)),
prepared.PinnedEntry(2)! };
var program = prepared.Emit(choices);
var proposal = source.Propose(program, ExecutionMotionReader.Read(program, Vector.Zero, new Vector(-2, -2), default),
choices);
var replay = CuttingPlanService.ReplayPrograms(snapshot, [proposal], 0, default);
Assert.Equal(CuttingPlanStatus.ConstraintConflict, replay.Status);
Assert.Contains(replay.Findings, f => f.Kind == PostVerificationKind.RapidCrossing);
Assert.DoesNotContain(replay.Findings, f => f.Kind == PostVerificationKind.FixedLead);
}
[Fact]
public void FeasibilityProbe_AcceptsAPinnedEntryAsGiven()
{
var (plate, pinned, _) = NeighbourFixture(pins: true);
var snapshot = CuttingPlanService.Capture(CuttingPlanRequest.ForPlate(plate,
confirmedParameters: PinnedContourTests.NestSettings()));
var source = snapshot.Placements.Single(p => ReferenceEquals(p.SourcePart, pinned));
var adapter = new ContourEntryFeasibility(source.Prepared!, source.Location, source.Material!,
snapshot.Placements.Select(p => p.Material!).ToArray());
var verdict = adapter.Check(source.Prepared!.PinnedEntry(0)!);
Assert.True(verdict.IsClear);
Assert.Equal(0, adapter.EvaluationCount);
}
/// <summary>
/// A 10 x 10 square pinned at its right edge midpoint with a 1.0 straight lead, and a
/// 2 x 2 neighbour 0.3 to its right that the lead runs through.
/// </summary>
private static (Plate Plate, Part Pinned, Part Neighbour) NeighbourFixture(bool pins)
{
var drawing = new Drawing("pinned", ExplicitContourTests.Square(false));
if (pins)
drawing.CutPins.Add(new ContourCutPin(new Vector(10, 5), new LineLeadIn { Length = 1.0, ApproachAngle = 90 },
new NoLeadOut()));
var pinned = new Part(drawing, Vector.Zero);
var neighbour = new Part(new Drawing("neighbour", LeadPathValidationTests.Rectangle(0, 0, 2, 2)),
new Vector(10.3, 4));
var plate = new Nest().CreatePlate();
plate.Parts.Add(pinned);
plate.Parts.Add(neighbour);
return (plate, pinned, neighbour);
}
}