using OpenNest.CNC; using OpenNest.Diagnostics; using OpenNest.Geometry; namespace OpenNest.Tests.Diagnostics; public class OverlapReportStateTests { [Fact] public void RestartRejectsOldSuccessAndFailureWithoutDisturbingNewRequest() { var plate = PlateWithParts(); var state = new OverlapReportState(); var old = state.Begin(plate); var current = state.Begin(plate); Assert.False(state.TryPublish(old, plate, Analyze(plate))); Assert.False(state.TryFail(old, plate)); Assert.Equal(OverlapCheckStatus.Checking, state.Status); Assert.True(state.TryPublish(current, plate, Analyze(plate))); Assert.Equal(OverlapCheckStatus.Current, state.Status); Assert.Equal("Overlaps: 1 pairs", state.Message); } [Theory] [InlineData("move-x")] [InlineData("move-y")] [InlineData("rotate")] [InlineData("reorder")] [InlineData("replace-part")] [InlineData("replace-drawing")] [InlineData("placed-program")] [InlineData("clean-program")] [InlineData("add")] [InlineData("remove")] [InlineData("plate")] public void ExactOrderedStampRejectsEditsBeforePublishAndAfterCompletion(string edit) { foreach (var publishFirst in new[] { false, true }) { var plate = PlateWithParts(); var state = new OverlapReportState(); var request = state.Begin(plate); var report = Analyze(plate); if (publishFirst) Assert.True(state.TryPublish(request, plate, report)); var part = plate.Parts[0]; switch (edit) { case "move-x": part.Offset(1e-10, 0); break; case "move-y": part.Offset(0, 1e-10); break; case "rotate": part.Rotate(0.01); break; case "reorder": (plate.Parts[0], plate.Parts[1]) = (plate.Parts[1], plate.Parts[0]); break; case "replace-part": plate.Parts[0] = part.CloneAtOffset(new Vector()); break; case "replace-drawing": plate.Parts[0] = Rectangle(); break; case "placed-program": part.Update(); break; case "clean-program": part.BaseDrawing.Program = (Program)part.BaseDrawing.Program.Clone(); break; case "add": plate.Parts.Add(Rectangle()); break; case "remove": plate.Parts.RemoveAt(0); break; case "plate": var replacement = new Plate(); replacement.Parts.AddRange(plate.Parts); plate = replacement; break; } Assert.False(state.EnsureFresh(plate)); Assert.False(state.TryPublish(request, plate, report)); Assert.Null(state.Report); Assert.Equal(OverlapCheckStatus.Stale, state.Status); Assert.Equal("Overlap check out of date — run Check Overlaps again", state.Message); } } [Fact] public void CancelFailureAndIncompleteAreNeverClear() { var plate = new Plate(); var state = new OverlapReportState(); var request = state.Begin(plate); state.Cancel(); Assert.False(state.TryPublish(request, plate, Analyze(plate))); Assert.Equal(OverlapCheckStatus.Canceled, state.Status); Assert.Null(state.Report); request = state.Begin(plate); Assert.True(state.TryFail(request, plate)); Assert.Equal(OverlapCheckStatus.Failed, state.Status); Assert.Contains("failed", state.Message); Assert.Null(state.Report); plate.Parts.Add(new Part(new Drawing("open", new Program()))); request = state.Begin(plate); Assert.True(state.TryPublish(request, plate, Analyze(plate))); Assert.Equal(OverlapCheckStatus.Incomplete, state.Status); Assert.Equal("Overlap check incomplete: 0 overlapping pairs; 1 parts could not be checked", state.Message); state.Invalidate(); Assert.Equal(OverlapCheckStatus.Stale, state.Status); Assert.Null(state.Report); } [Fact] public void IssueCountIncludesBothPairIdsAndDeduplicatesAcrossAllIssues() { var issues = new[] { new PlateOverlapIssue(2, null, "input"), new PlateOverlapIssue(2, 4, "pair"), new PlateOverlapIssue(4, 7, "pair"), new PlateOverlapIssue(7, null, "input") }; Assert.Equal(3, OverlapReportState.CountUncheckedParts(issues)); } [Fact] public void DisplayAndNonGeometryChangesRetainReportAndGeneration() { var plate = PlateWithParts(); var state = new OverlapReportState(); Assert.Equal(OverlapDisplayMode.Areas, state.DisplayMode); var request = state.Begin(plate); var report = Analyze(plate); Assert.True(state.TryPublish(request, plate, report)); state.DisplayMode = OverlapDisplayMode.Off; plate.Parts[0].BaseDrawing.Name = "renamed"; plate.Quantity = 2; plate.PartSpacing = 123; Assert.True(state.EnsureFresh(plate)); Assert.Same(report, state.Report); Assert.Equal(request, state.Generation); state.DisplayMode = OverlapDisplayMode.Areas; Assert.Same(report, state.Report); Assert.Equal(request, state.Generation); state.DisplayMode = OverlapDisplayMode.Off; state.Begin(plate); Assert.Equal(OverlapDisplayMode.Areas, state.DisplayMode); Assert.Null(state.Report); } [Theory] [InlineData(OverlapDisplayMode.Off, OverlapDisplayMode.Areas)] [InlineData(OverlapDisplayMode.Areas, OverlapDisplayMode.Areas)] [InlineData(OverlapDisplayMode.Centroids, OverlapDisplayMode.Centroids)] [InlineData(OverlapDisplayMode.Both, OverlapDisplayMode.Both)] public void CheckAndRecheckPreserveVisibleMode(OverlapDisplayMode chosen, OverlapDisplayMode expected) { var plate = PlateWithParts(); var state = new OverlapReportState { DisplayMode = chosen }; var request = state.Begin(plate); Assert.Equal(expected, state.DisplayMode); Assert.True(state.TryPublish(request, plate, Analyze(plate))); state.Begin(plate); Assert.Equal(expected, state.DisplayMode); } [Fact] public void AutomaticCheckKeepsDisplayOff() { var plate = PlateWithParts(); var state = new OverlapReportState { DisplayMode = OverlapDisplayMode.Off }; Assert.True(state.TryPublish(state.Begin(plate, automatic: true), plate, Analyze(plate))); Assert.Equal(OverlapDisplayMode.Off, state.DisplayMode); Assert.Single(state.Report.Pairs); } [Fact] public void NewPlateResetsStateButRetainsDocumentDisplayPreference() { var plate = PlateWithParts(); var state = new OverlapReportState(); var request = state.Begin(plate); state.DisplayMode = OverlapDisplayMode.Off; state.Reset(); Assert.Equal(OverlapCheckStatus.NotChecked, state.Status); Assert.Null(state.Report); Assert.Equal(OverlapDisplayMode.Off, state.DisplayMode); Assert.False(state.TryPublish(request, plate, Analyze(plate))); } [Fact] public void CutoffSlotsRemainPartOfTheOrderedIdentityStamp() { var plate = PlateWithParts(); var cutoff = Rectangle(); cutoff.BaseDrawing.IsCutOff = true; plate.Parts.Insert(0, cutoff); var state = new OverlapReportState(); Assert.True(state.TryPublish(state.Begin(plate), plate, Analyze(plate))); var pair = Assert.Single(state.Report.Pairs); Assert.Equal((1, 2), (pair.PartAId, pair.PartBId)); plate.Parts.RemoveAt(0); Assert.False(state.EnsureFresh(plate)); Assert.Null(state.Report); } [Fact] public void InPlaceGeometryEditorsMustExplicitlyInvalidate() { var plate = PlateWithParts(); var state = new OverlapReportState(); var request = state.Begin(plate); var report = Analyze(plate); // Deliberately not serialized or converted by the stamp. plate.Parts[0].BaseDrawing.Program.Codes.Clear(); Assert.True(state.EnsureFresh(plate)); state.Invalidate(); Assert.False(state.TryPublish(request, plate, report)); Assert.Equal(OverlapCheckStatus.Stale, state.Status); } [Fact] public void EmptyCompleteReportAloneCanSayClear() { var plate = new Plate(); var state = new OverlapReportState(); Assert.Equal(OverlapCheckStatus.NotChecked, state.Status); Assert.True(state.TryPublish(state.Begin(plate), plate, Analyze(plate))); Assert.Equal("No material overlaps detected", state.Message); } private static PlateOverlapReport Analyze(Plate plate) => PlateOverlapAnalyzer.Analyze(plate.Parts.ToArray()); private static Plate PlateWithParts() { var plate = new Plate(); plate.Parts.Add(Rectangle()); plate.Parts.Add(Rectangle()); return plate; } private static Part Rectangle() { var program = new Program(Mode.Absolute); program.Codes.Add(new RapidMove(0, 0)); program.Codes.Add(new LinearMove(4, 0)); program.Codes.Add(new LinearMove(4, 4)); program.Codes.Add(new LinearMove(0, 4)); program.Codes.Add(new LinearMove(0, 0)); return new Part(new Drawing("same name", program)); } }