using OpenNest.CNC; using OpenNest.Geometry; namespace OpenNest.Tests.CutOffs; /// /// A cut-off's trimmed segments are materialized as one part each, with its own place in the /// cut sequence; each stands for the span of the line between its neighboring segments. /// public class CutOffSegmentsTests { private const double Clearance = 0.02; [Fact] public void Of_GivesEachSegmentTheSpanBetweenItsNeighbors() { var (plate, cutOff, _, _) = ThreeSegments(); var segments = CutOffSegments.Of(plate, cutOff); Assert.Equal(3, segments.Count); AssertSegment(segments[0], 0, 10 - Clearance, 0, 20 + Clearance); AssertSegment(segments[1], 20 + Clearance, 30 - Clearance, 10 - Clearance, 40 + Clearance); AssertSegment(segments[2], 40 + Clearance, 60, 30 - Clearance, 60); Assert.Equal(segments.Select(s => s.Part), plate.Parts.Where(p => p.BaseDrawing.IsCutOff)); } [Fact] public void Of_LimitedLine_SpansEndAtItsLimits() { var plate = TestHelpers.MakePlate(60, 120, TestHelpers.MakePartAt(10, 10, 10)); var cutOff = new CutOff(new Vector(15, 0), CutOffAxis.Vertical) { StartLimit = 5, EndLimit = 45 }; plate.CutOffs.Add(cutOff); plate.RegenerateCutOffs(new CutOffSettings()); var segments = CutOffSegments.Of(plate, cutOff); Assert.Equal(2, segments.Count); AssertSegment(segments[0], 5, 10 - Clearance, 5, 20 + Clearance); AssertSegment(segments[1], 20 + Clearance, 45, 10 - Clearance, 45); } [Fact] public void Of_PartThatCannotBeMeasured_StandsForTheWholeLine() { var plate = TestHelpers.MakePlate(60, 120); var cutOff = new CutOff(new Vector(15, 0), CutOffAxis.Vertical); var program = new Program(Mode.Incremental); program.Codes.Add(new RapidMove(15, 0)); program.Codes.Add(new LinearMove(0, 60)); cutOff.Drawing.Program = program; plate.CutOffs.Add(cutOff); plate.Parts.Add(new Part(cutOff.Drawing)); var segment = Assert.Single(CutOffSegments.Of(plate, cutOff)); AssertSegment(segment, 0, 60, 0, 60); } [Fact] public void RegenerateCutOffs_KeepsEachSegmentsPlaceInTheSequence() { var (plate, cutOff, lower, upper) = ThreeSegments(); var segments = CutOffSegments.Of(plate, cutOff).Select(s => s.Part).ToArray(); plate.Parts.Reorder([segments[0], lower, segments[1], upper, segments[2]]); var settings = new CutOffSettings(); plate.RegenerateCutOffs(settings); AssertArrangement(plate, cutOff, lower, upper); // A part moved along the line still sits in the same gap: its segments keep their places. lower.Offset(1, 0); plate.RegenerateCutOffs(settings); AssertArrangement(plate, cutOff, lower, upper); } [Fact] public void RegenerateCutOffs_SegmentWithoutASavedPlace_FollowsTheOneBeforeIt() { var (plate, cutOff, lower, upper) = ThreeSegments(); var segments = CutOffSegments.Of(plate, cutOff).Select(s => s.Part).ToArray(); plate.Parts.Reorder([segments[0], lower, segments[1], upper, segments[2]]); // A part dropped onto the top segment splits it in two. var added = TestHelpers.MakePartAt(10, 48, 5); plate.Parts.Add(added); plate.RegenerateCutOffs(new CutOffSettings()); var regenerated = CutOffSegments.Of(plate, cutOff); Assert.Equal(4, regenerated.Count); Assert.Equal(new[] { regenerated[0].Part, lower, regenerated[1].Part, upper, regenerated[2].Part, regenerated[3].Part, added }, plate.Parts); } [Theory] [InlineData(CutDirection.AwayFromOrigin)] [InlineData(CutDirection.TowardOrigin)] public void RegenerateCutOffs_ListOverload_PlacesSegmentsByAscendingPosition(CutDirection direction) { var lower = TestHelpers.MakePartAt(10, 10, 10); var upper = TestHelpers.MakePartAt(10, 30, 10); var plate = TestHelpers.MakePlate(60, 120, lower, upper); var cutOff = new CutOff(new Vector(15, 0), CutOffAxis.Vertical); plate.CutOffs.Add(cutOff); plate.RegenerateCutOffs(new CutOffSettings { CutDirection = direction }, new Dictionary> { [cutOff] = [0, 2, 4] }); AssertArrangement(plate, cutOff, lower, upper); // The program runs toward the origin, but the file lists segments ascending. var segments = CutOffSegments.Of(plate, cutOff); Assert.True(segments[0].Start < segments[1].Start && segments[1].Start < segments[2].Start); } [Fact] public void RegenerateCutOffs_ListOfWrongLength_PlacesSegmentsConsecutively() { var lower = TestHelpers.MakePartAt(10, 10, 10); var upper = TestHelpers.MakePartAt(10, 30, 10); var plate = TestHelpers.MakePlate(60, 120, lower, upper); var cutOff = new CutOff(new Vector(15, 0), CutOffAxis.Vertical); plate.CutOffs.Add(cutOff); plate.RegenerateCutOffs(new CutOffSettings(), new Dictionary> { [cutOff] = [1, 7] }); var segments = CutOffSegments.Of(plate, cutOff).Select(s => s.Part).ToArray(); Assert.Equal(new[] { lower, segments[0], segments[1], segments[2], upper }, plate.Parts); } [Fact] public void RegenerateCutOffs_MoreSegmentsThanSaved_KeepTheLaterPartsAndCutOffsInPlace() { // Saved with settings that left one segment of the line (place 1) before the upper part // (place 2) and a second cut-off (place 3); the reader's settings regenerate three. var lower = TestHelpers.MakePartAt(10, 10, 10); var upper = TestHelpers.MakePartAt(10, 30, 10); var plate = TestHelpers.MakePlate(60, 120, lower, upper); var line = new CutOff(new Vector(15, 0), CutOffAxis.Vertical); var clear = new CutOff(new Vector(40, 0), CutOffAxis.Vertical) { EndLimit = 50 }; plate.CutOffs.Add(line); plate.CutOffs.Add(clear); plate.RegenerateCutOffs(new CutOffSettings(), new Dictionary> { [line] = [1], [clear] = [3], }); var segments = CutOffSegments.Of(plate, line).Select(s => s.Part).ToArray(); Assert.Equal(3, segments.Length); var other = Assert.Single(CutOffSegments.Of(plate, clear)).Part; Assert.Equal(new[] { lower, segments[0], segments[1], segments[2], upper, other }, plate.Parts); } [Fact] public void RegenerateCutOffs_FirstPlacesOnly_KeepTheSheetSeparatorLast() { // An older file recorded first places only. The line held one place when saved, before // the upper part and the separator; three segments now would put the separator's place // before the upper part, so a line splitting the sheet goes last instead. var lower = TestHelpers.MakePartAt(10, 10, 10); var upper = TestHelpers.MakePartAt(10, 30, 10); var plate = TestHelpers.MakePlate(60, 120, lower, upper); var line = new CutOff(new Vector(15, 0), CutOffAxis.Vertical) { EndLimit = 50 }; var separator = new CutOff(new Vector(0, 50), CutOffAxis.Horizontal); plate.CutOffs.Add(line); plate.CutOffs.Add(separator); plate.RegenerateCutOffs(new CutOffSettings(), null, new Dictionary { [line] = 1, [separator] = 3, }); var segments = CutOffSegments.Of(plate, line).Select(s => s.Part).ToArray(); Assert.Equal(3, segments.Length); var last = Assert.Single(CutOffSegments.Of(plate, separator)).Part; Assert.Equal(new[] { lower, segments[0], segments[1], segments[2], upper, last }, plate.Parts); } [Fact] public void RegenerateCutOffs_IndexOverload_PlacesSegmentsConsecutively() { var lower = TestHelpers.MakePartAt(10, 10, 10); var upper = TestHelpers.MakePartAt(10, 30, 10); var plate = TestHelpers.MakePlate(60, 120, lower, upper); var cutOff = new CutOff(new Vector(15, 0), CutOffAxis.Vertical); plate.CutOffs.Add(cutOff); plate.RegenerateCutOffs(new CutOffSettings(), new Dictionary { [cutOff] = 1 }); var segments = CutOffSegments.Of(plate, cutOff).Select(s => s.Part).ToArray(); Assert.Equal(new[] { lower, segments[0], segments[1], segments[2], upper }, plate.Parts); Assert.All(segments, s => Assert.Equal(2, s.Program.Codes.Count)); } [Fact] public void SegmentPrograms_SplitTheLineAtEachPierce() { var (plate, cutOff, _, _) = ThreeSegments(); var programs = cutOff.SegmentPrograms(); Assert.Equal(3, programs.Count); Assert.All(programs, p => { Assert.IsType(p.Codes[0]); Assert.IsType(p.Codes[1]); Assert.Equal(2, p.Codes.Count); }); Assert.Equal(cutOff.Drawing.Program.Codes, programs.SelectMany(p => p.Codes)); Assert.Equal(programs.Count, plate.Parts.Count(p => p.BaseDrawing.IsCutOff)); } // Two squares one above the other with a vertical line through both: a segment below, // between and above them. internal static (Plate Plate, CutOff CutOff, Part Lower, Part Upper) ThreeSegments() { var lower = TestHelpers.MakePartAt(10, 10, 10); var upper = TestHelpers.MakePartAt(10, 30, 10); var plate = TestHelpers.MakePlate(60, 120, lower, upper); var cutOff = new CutOff(new Vector(15, 0), CutOffAxis.Vertical); plate.CutOffs.Add(cutOff); plate.RegenerateCutOffs(new CutOffSettings()); Assert.Equal(5, plate.Parts.Count); return (plate, cutOff, lower, upper); } private static void AssertArrangement(Plate plate, CutOff cutOff, Part lower, Part upper) { var segments = CutOffSegments.Of(plate, cutOff); Assert.Equal(3, segments.Count); Assert.Equal(new[] { segments[0].Part, lower, segments[1].Part, upper, segments[2].Part }, plate.Parts); } // The clearance outline is offset conservatively, so a trimmed end can sit a few ten // thousandths farther from the part than the nominal clearance. private static void AssertSegment(CutOffSegment segment, double start, double end, double spanStart, double spanEnd) { Assert.Equal(start, segment.Start, 0.001); Assert.Equal(end, segment.End, 0.001); Assert.Equal(spanStart, segment.SpanStart, 0.001); Assert.Equal(spanEnd, segment.SpanEnd, 0.001); Assert.True(segment.Part.BaseDrawing.IsCutOff); } }