mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-08 23:55:44 -05:00
A support-tabbed part that no single tab can hold level was limited to its one steadiest entry. Two strips stacked in a frame's cutout then blocked the plate: from the upper strip's only entry, every route to the lower one crossed holes already cut. Its other entries clear of inside corners now follow the steadiest, in the search and in contour recovery; the part is still tabbed and still warned about. Parts a tab can hold keep their steadying entries. The unverified headline also named fixed lead-ins when the only warning was a part that may tip; it now says what needs review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
204 lines
10 KiB
C#
204 lines
10 KiB
C#
using OpenNest.CNC.CuttingPlanning;
|
|
using OpenNest.CNC.CuttingStrategy;
|
|
using OpenNest.Diagnostics;
|
|
using OpenNest.Engine.CuttingPlanning;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Tests.CuttingPlanning;
|
|
|
|
public class CuttingPlanMessageTests
|
|
{
|
|
[Fact]
|
|
public void Describe_MixedBestEffortAndOpenContour_CountsOnlyBlockedPlateAndExplainsGap()
|
|
{
|
|
var open = new OpenNest.CNC.Program();
|
|
open.MoveTo(0, 0);
|
|
open.LineTo(10, 0);
|
|
open.LineTo(10, 10);
|
|
open.LineTo(0, 10);
|
|
open.LineTo(0, 0.000002);
|
|
var part = new Part(new Drawing("open perimeter", open));
|
|
var original = OwnedProgramCopy.Copy(part.BaseDrawing.Program);
|
|
var plates = new[]
|
|
{
|
|
BestEffortCuttingPlanTests.Plate(BestEffortCuttingPlanTests.TouchingContours()),
|
|
BestEffortCuttingPlanTests.Plate(part),
|
|
};
|
|
|
|
var proposal = CuttingPlanBatch.Capture(plates, ExplicitContourTests.Parameters(), false).Plan();
|
|
var text = string.Join("\n", proposal.Describe("in"));
|
|
|
|
Assert.StartsWith("Apply is unavailable: 1 of 2 plates could not be planned.", text);
|
|
Assert.Contains("Plate 1: best-effort, unverified.", text);
|
|
Assert.Contains("Plate 2: blocked:", text);
|
|
Assert.Contains("contour 1 is open: endpoint gap 2E-06 model units", text);
|
|
Assert.Contains("closure tolerance 1E-06", text);
|
|
Assert.Contains("Review the source contour in the drawing editor", text);
|
|
Assert.False(proposal.CanApplyWithWarnings);
|
|
Assert.Equal(CuttingCommitStatus.InvalidInput, proposal.Apply(true).Status);
|
|
Assert.True(ProgramContent.Equal(original, part.BaseDrawing.Program));
|
|
Assert.All(plates.SelectMany(p => p.Parts), p => Assert.False(p.HasManualLeadIns));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false, false)]
|
|
[InlineData(false, true)]
|
|
[InlineData(true, false)]
|
|
[InlineData(true, true)]
|
|
public void Describe_NoneIsSupportedButDegenerateLeadMotionStillRefuses(bool keepOrder, bool zeroLength)
|
|
{
|
|
var part = new Part(new Drawing("sample", ExplicitContourTests.Square(false)), new Vector(1, 1));
|
|
var plate = BestEffortCuttingPlanTests.Plate(part);
|
|
var original = OwnedProgramCopy.Copy(part.Program);
|
|
var settings = new CuttingParameters();
|
|
if (zeroLength)
|
|
settings.ExternalLeadIn = new LineLeadIn { Length = 0, ApproachAngle = 90 };
|
|
|
|
var proposal = CuttingPlanBatch.Capture([plate], settings, keepOrder).Plan();
|
|
|
|
if (zeroLength)
|
|
{
|
|
Assert.False(proposal.CanApply);
|
|
Assert.Contains("Lead motion is missing, degenerate or inconsistent", string.Join("\n", proposal.Describe("in")));
|
|
Assert.True(ProgramContent.Equal(original, part.Program));
|
|
return;
|
|
}
|
|
|
|
Assert.True(proposal.CanApply, string.Join("\n", proposal.Describe("in")));
|
|
var result = Assert.Single(proposal.Plates).Result;
|
|
Assert.True(result.IndependentlyReplayed);
|
|
Assert.DoesNotContain(result.Findings, f => f.Kind == PostVerificationKind.MissingLeadIn);
|
|
Assert.Contains("Plate 1: ready.", string.Join("\n", proposal.Describe("in")));
|
|
var output = Assert.Single(result.ProposedOrder).CopyProgram();
|
|
Assert.DoesNotContain(ExecutionMotionReader.ReadSupported(output, part.Location, null).Motions,
|
|
m => m.Layer == OpenNest.CNC.LayerType.Leadin && m.Length > 0);
|
|
Assert.True(ProgramContent.Equal(original, part.Program));
|
|
Assert.Equal(CuttingCommitStatus.Applied, proposal.Apply().Status);
|
|
}
|
|
|
|
[Fact]
|
|
public void Describe_LockedProgramWithoutLead_IsReadyAndKeepsItsProgram()
|
|
{
|
|
var part = new Part(new Drawing("locked sample", ExplicitContourTests.Square(false)), new Vector(1, 1))
|
|
{
|
|
LeadInsLocked = true,
|
|
};
|
|
var plate = BestEffortCuttingPlanTests.Plate(part);
|
|
var program = part.Program;
|
|
var proposal = CuttingPlanBatch.Capture([plate], ExplicitContourTests.Parameters(), false).Plan();
|
|
|
|
Assert.True(proposal.CanApply, string.Join("\n", proposal.Describe("in")));
|
|
Assert.False(Assert.Single(proposal.Plates[0].Result.ProposedOrder).IsRegenerated);
|
|
Assert.Equal(CuttingCommitStatus.Applied, proposal.Apply().Status);
|
|
Assert.True(part.LeadInsLocked);
|
|
Assert.Same(program, part.Program);
|
|
}
|
|
[Fact]
|
|
public void Describe_OnlyAnUnsteadyPart_SaysItMayTipNotThatLeadsNeedReview()
|
|
{
|
|
// Nothing of the grid runs under the strip, so no single tab can hold it level.
|
|
var plate = new Plate(20, 20) { SupportBed = new OpenNest.Support.SupportGrid(1, 1, 0, 0.9, stabilityMargin: 0.1) };
|
|
plate.Parts.Add(new Part(new Drawing("strip", LeadPathValidationTests.Rectangle(0, 0, 10, 0.8)), new Vector(2, 2)));
|
|
var settings = ExplicitContourTests.Parameters();
|
|
settings.SupportTabs = true;
|
|
settings.TabConfig = new NormalTab { Size = 0.2 };
|
|
|
|
var proposal = CuttingPlanBatch.Capture([plate], settings, false).Plan();
|
|
var text = string.Join("\n", proposal.Describe("in"));
|
|
|
|
Assert.True(proposal.RequiresWarningAcceptance, text);
|
|
Assert.StartsWith("Unverified plan available: a part may tip once cut free.", text);
|
|
Assert.Contains("Plate 1: unverified: a part may tip once cut free.", text);
|
|
Assert.DoesNotContain("fixed lead-ins", text);
|
|
}
|
|
|
|
[Fact]
|
|
public void Describe_LockedLeadHitsNeighbor_WarnsWithAdviceAndNeedsAcceptance()
|
|
{
|
|
var settings = ExplicitContourTests.Parameters();
|
|
var clean = LeadPathValidationTests.Rectangle(0, 0, 10, 10);
|
|
var prepared = PreparedContours.Capture(clean, settings);
|
|
var part = new Part(new Drawing("lead blocked", clean));
|
|
Assert.True(part.RestoreLeadInProgram(prepared.Emit([prepared.ClosestEntry(0, new Vector(-1, 5))]), true));
|
|
var obstacle = new Part(new Drawing("neighbor", LeadPathValidationTests.Rectangle(-0.3, 4.5, -0.1, 5.5)));
|
|
var plate = new Nest().CreatePlate();
|
|
plate.Parts.Add(part);
|
|
plate.Parts.Add(obstacle);
|
|
var original = OwnedProgramCopy.Copy(part.Program);
|
|
|
|
var proposal = CuttingPlanBatch.Capture([plate], settings, true).Plan();
|
|
var text = string.Join("\n", proposal.Describe("in"));
|
|
|
|
// D1a: the locked lead is a named warning on an UNVERIFIED plate, not a refusal.
|
|
Assert.False(proposal.CanApply);
|
|
Assert.True(proposal.RequiresWarningAcceptance);
|
|
Assert.StartsWith("Unverified plan available: fixed lead-ins could not be certified.", text);
|
|
Assert.Contains("Plate 1: unverified: fixed lead-ins need review.", text);
|
|
Assert.Contains("- Part 1 (lead blocked): Locked lead on cutting contour 1 contacts or enters another "
|
|
+ "part's material. The locked program is kept as given. Increase spacing, or edit or unlock "
|
|
+ "this part's lead-ins and replan. (with part 2 (neighbor))", text);
|
|
Assert.DoesNotContain("other than None", text);
|
|
Assert.Equal(CuttingCommitStatus.InvalidInput, proposal.Apply().Status);
|
|
Assert.True(ProgramContent.Equal(original, part.Program));
|
|
Assert.True(part.LeadInsLocked);
|
|
}
|
|
|
|
[Fact]
|
|
public void Describe_NoTestedLeadFits_SuggestsChangesButDoesNotClaimTheyWillWork()
|
|
{
|
|
var settings = ExplicitContourTests.Parameters();
|
|
var part = new Part(new Drawing("inside", LeadPathValidationTests.Rectangle(0, 0, 10, 10)));
|
|
var wrap = new Part(new Drawing("wrap", LeadPathValidationTests.Rectangle(-0.05, -0.05, 10.05, 10.05)));
|
|
var plate = new Nest().CreatePlate();
|
|
plate.Parts.Add(part);
|
|
plate.Parts.Add(wrap);
|
|
|
|
var proposal = CuttingPlanBatch.Capture([plate], settings, false).Plan();
|
|
var text = string.Join("\n", proposal.Describe("in"));
|
|
|
|
Assert.Contains("No tested lead-in fits", text);
|
|
Assert.Contains("Try reducing the lead-in length", text);
|
|
Assert.Contains("if nearby parts obstruct the lead-in, space the parts farther apart", text);
|
|
Assert.Contains("Replan to check the changes.", text);
|
|
Assert.False(proposal.CanApply);
|
|
Assert.Equal(CuttingCommitStatus.InvalidInput, proposal.Apply().Status);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData(PostVerificationKind.RapidCrossing)]
|
|
public void Describe_OtherRefusals_KeepTheirStatusAndFindings(PostVerificationKind? kind)
|
|
{
|
|
// The presentation uses the typed finding, not matching words in a diagnostic.
|
|
var finding = new CuttingPlanFinding(null, null, null, null, kind, "MissingLeadIn lookalike text");
|
|
var result = new CuttingPlanResult(CuttingPlanStatus.NoSolutionWithinBudget, findings: [finding]);
|
|
var proposal = new CuttingPlanProposal([new(new Nest().CreatePlate(), 7, result, result, null)],
|
|
ExplicitContourTests.Parameters());
|
|
|
|
var text = string.Join("\n", proposal.Describe("in"));
|
|
|
|
Assert.Contains("Plate 7: no complete plan was found within the search limit.", text);
|
|
Assert.Contains("planning with the current order was refused:", text);
|
|
Assert.Contains(finding.Message, text);
|
|
Assert.Contains(CuttingPlanProposal.SearchLimitHint, text);
|
|
Assert.Contains("it may still find no plan", CuttingPlanProposal.SearchLimitHint);
|
|
Assert.True(proposal.Plates[0].SearchLimitReached);
|
|
Assert.DoesNotContain("Open Cutting Settings...", text);
|
|
Assert.DoesNotContain("missing or zero-length lead-in", text);
|
|
Assert.False(proposal.CanApply);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(CuttingPlanStatus.UnsupportedGeometry)]
|
|
[InlineData(CuttingPlanStatus.Canceled)]
|
|
public void Describe_KeptOrderRefusalAMoreEffortCannotFix_OmitsTheSearchLimitHint(CuttingPlanStatus status)
|
|
{
|
|
var attempt = new CuttingPlanResult(CuttingPlanStatus.NoSolutionWithinBudget);
|
|
var proposal = new CuttingPlanProposal([new(new Nest().CreatePlate(), 1, new(status), attempt, null)],
|
|
ExplicitContourTests.Parameters());
|
|
|
|
Assert.False(proposal.Plates[0].SearchLimitReached);
|
|
Assert.DoesNotContain(CuttingPlanProposal.SearchLimitHint, string.Join("\n", proposal.Describe("in")));
|
|
}
|
|
}
|