using System.Collections.Generic; using CSMath; using OpenNest.Geometry; using OpenNest.IO; using Xunit; namespace OpenNest.Tests.IO { public class TitleBlockDetectorTests { private static Line MakeLine(double x1, double y1, double x2, double y2) => new Line(x1, y1, x2, y2); [Fact] public void DetectByLayerName_FlagsTitleLayer() { var line = MakeLine(0, 0, 10, 0); line.Layer = new Layer("TITLE"); var entities = new List { line }; var result = TitleBlockDetector.Detect(entities, null); Assert.Contains(line.Id, result); } [Fact] public void DetectByLayerName_CaseInsensitive() { var line = MakeLine(0, 0, 10, 0); line.Layer = new Layer("border"); var entities = new List { line }; var result = TitleBlockDetector.Detect(entities, null); Assert.Contains(line.Id, result); } [Fact] public void DetectByLayerName_IgnoresNonMatchingLayers() { var line = MakeLine(0, 0, 10, 0); line.Layer = new Layer("0"); var entities = new List { line }; var result = TitleBlockDetector.Detect(entities, null); Assert.DoesNotContain(line.Id, result); } [Theory] [InlineData("TITLE")] [InlineData("TITLEBLOCK")] [InlineData("TITLE_BLOCK")] [InlineData("BORDER")] [InlineData("FRAME")] [InlineData("TB")] [InlineData("INFO")] [InlineData("SHEET")] [InlineData("ANNOTATION")] public void DetectByLayerName_AllKnownNames(string layerName) { var line = MakeLine(0, 0, 10, 0); line.Layer = new Layer(layerName); var entities = new List { line }; var result = TitleBlockDetector.Detect(entities, null); Assert.Contains(line.Id, result); } [Fact] public void DetectBorder_FlagsLinesOnBoundingBoxEdges() { var entities = new List { new Line(0, 0, 86, 0) { Layer = new Layer("0") }, new Line(86, 0, 86, 134) { Layer = new Layer("0") }, new Line(86, 134, 0, 134) { Layer = new Layer("0") }, new Line(0, 134, 0, 0) { Layer = new Layer("0") }, new Line(30, 40, 50, 90) { Layer = new Layer("0") }, new Line(50, 90, 70, 40) { Layer = new Layer("0") }, new Line(70, 40, 30, 40) { Layer = new Layer("0") }, }; var result = TitleBlockDetector.Detect(entities, null); Assert.Contains(entities[0].Id, result); Assert.Contains(entities[1].Id, result); Assert.Contains(entities[2].Id, result); Assert.Contains(entities[3].Id, result); Assert.DoesNotContain(entities[4].Id, result); Assert.DoesNotContain(entities[5].Id, result); Assert.DoesNotContain(entities[6].Id, result); } [Fact] public void DetectBorder_FlagsZoneMarkerTicks() { var entities = new List { new Line(0, 0, 100, 0) { Layer = new Layer("0") }, new Line(100, 0, 100, 80) { Layer = new Layer("0") }, new Line(100, 80, 0, 80) { Layer = new Layer("0") }, new Line(0, 80, 0, 0) { Layer = new Layer("0") }, new Line(25, 80, 25, 77) { Layer = new Layer("0") }, new Line(50, 80, 50, 77) { Layer = new Layer("0") }, new Line(75, 80, 75, 77) { Layer = new Layer("0") }, new Line(40, 30, 60, 30) { Layer = new Layer("0") }, }; var result = TitleBlockDetector.Detect(entities, null); Assert.Contains(entities[4].Id, result); Assert.Contains(entities[5].Id, result); Assert.Contains(entities[6].Id, result); Assert.DoesNotContain(entities[7].Id, result); } [Fact] public void DetectBorder_IgnoresWhenNoBorderPresent() { var entities = new List { new Line(30, 40, 50, 90) { Layer = new Layer("0") }, new Line(50, 90, 70, 40) { Layer = new Layer("0") }, new Line(70, 40, 30, 40) { Layer = new Layer("0") }, }; var result = TitleBlockDetector.Detect(entities, null); Assert.Empty(result); } [Fact] public void DetectBorder_ToleratesSlightRotation() { var angleRad = OpenNest.Math.Angle.ToRadians(0.5); var endY = 86 * System.Math.Sin(angleRad); var entities = new List { new Line(0, 0, 86, endY) { Layer = new Layer("0") }, new Line(86, endY, 86, 134) { Layer = new Layer("0") }, new Line(86, 134, 0, 134) { Layer = new Layer("0") }, new Line(0, 134, 0, 0) { Layer = new Layer("0") }, new Line(30, 40, 50, 90) { Layer = new Layer("0") }, }; var result = TitleBlockDetector.Detect(entities, null); Assert.Contains(entities[0].Id, result); } [Fact] public void DetectTitleBlock_FlagsEntitiesInTextDenseCorner() { var partLine1 = new Line(5, 70, 25, 120) { Layer = new Layer("0") }; var partLine2 = new Line(25, 120, 45, 70) { Layer = new Layer("0") }; var partLine3 = new Line(45, 70, 5, 70) { Layer = new Layer("0") }; var tbLines = new List(); for (var x = 50; x <= 85; x += 5) tbLines.Add(new Line(x, 0, x, 30) { Layer = new Layer("0") }); for (var y = 0; y <= 30; y += 5) tbLines.Add(new Line(50, y, 85, y) { Layer = new Layer("0") }); var entities = new List { partLine1, partLine2, partLine3 }; entities.AddRange(tbLines); var doc = BuildDocWithTexts( (60, 5, "TITLE: Test Part"), (60, 10, "DWG NO: 12345"), (60, 15, "SCALE: 1:1"), (60, 20, "REV: A"), (60, 25, "MATERIAL: STEEL")); var result = TitleBlockDetector.Detect(entities, doc); foreach (var tb in tbLines) Assert.Contains(tb.Id, result); Assert.DoesNotContain(partLine1.Id, result); Assert.DoesNotContain(partLine2.Id, result); Assert.DoesNotContain(partLine3.Id, result); } [Fact] public void DetectTitleBlock_NoFalsePositivesWithoutText() { var entities = new List { new Line(30, 40, 50, 90) { Layer = new Layer("0") }, new Line(50, 90, 70, 40) { Layer = new Layer("0") }, new Line(70, 40, 30, 40) { Layer = new Layer("0") }, }; var result = TitleBlockDetector.Detect(entities, null); Assert.Empty(result); } [Fact] public void DetectTitleBlock_BottomEdgeStrip() { var partLine = new Line(20, 40, 80, 40) { Layer = new Layer("0") }; var tbLines = new List(); for (var x = 0; x <= 100; x += 10) tbLines.Add(new Line(x, 0, x, 20) { Layer = new Layer("0") }); for (var y = 0; y <= 20; y += 5) tbLines.Add(new Line(0, y, 100, y) { Layer = new Layer("0") }); var entities = new List { partLine }; entities.AddRange(tbLines); var doc = BuildDocWithTexts( (10, 5, "TITLE"), (30, 5, "DWG NO"), (50, 5, "SCALE"), (70, 5, "REV"), (90, 5, "DATE")); var result = TitleBlockDetector.Detect(entities, doc); foreach (var tb in tbLines) Assert.Contains(tb.Id, result); Assert.DoesNotContain(partLine.Id, result); } private static ACadSharp.CadDocument BuildDocWithTexts( params (double x, double y, string value)[] texts) { var doc = new ACadSharp.CadDocument(); foreach (var (x, y, value) in texts) { var mtext = new ACadSharp.Entities.MText { InsertPoint = new XYZ(x, y, 0), Value = value, Height = 2.0 }; doc.Entities.Add(mtext); } return doc; } } }