diff --git a/OpenNest.IO/TitleBlockDetector.cs b/OpenNest.IO/TitleBlockDetector.cs index ebab590..9093bd4 100644 --- a/OpenNest.IO/TitleBlockDetector.cs +++ b/OpenNest.IO/TitleBlockDetector.cs @@ -18,6 +18,7 @@ namespace OpenNest.IO { var flagged = new HashSet(); DetectByLayerName(entities, flagged); + DetectBorder(entities, flagged); return flagged; } @@ -29,5 +30,101 @@ namespace OpenNest.IO flagged.Add(entity.Id); } } + + private static void DetectBorder(List entities, HashSet flagged) + { + var lines = entities.OfType().Where(l => !flagged.Contains(l.Id)).ToList(); + if (lines.Count < 4) return; + + var bounds = entities.GetBoundingBox(); + if (bounds == null || bounds.Area() < OpenNest.Math.Tolerance.Epsilon) return; + + var borderCount = 0; + foreach (var line in lines) + { + if (IsBorderLine(line, bounds)) + { + flagged.Add(line.Id); + borderCount++; + } + } + + if (borderCount >= 2) + DetectZoneMarkers(lines, bounds, flagged); + } + + private static bool IsBorderLine(Line line, Box bounds) + { + var dx = line.EndPoint.X - line.StartPoint.X; + var dy = line.EndPoint.Y - line.StartPoint.Y; + var length = System.Math.Sqrt(dx * dx + dy * dy); + var angleRad = System.Math.Atan2(System.Math.Abs(dy), System.Math.Abs(dx)); + var angularTolerance = OpenNest.Math.Angle.ToRadians(2.0); + var positionTolerance = System.Math.Max(bounds.Length, bounds.Width) * 0.01; + + var isHorizontal = angleRad < angularTolerance; + var isVertical = System.Math.Abs(angleRad - System.Math.PI / 2) < angularTolerance; + + if (!isHorizontal && !isVertical) return false; + + var minSpan = isHorizontal ? bounds.Length * 0.8 : bounds.Width * 0.8; + if (length < minSpan) return false; + + if (isHorizontal) + { + var midY = (line.StartPoint.Y + line.EndPoint.Y) / 2; + return System.Math.Abs(midY - bounds.Bottom) < positionTolerance + || System.Math.Abs(midY - bounds.Top) < positionTolerance; + } + else + { + var midX = (line.StartPoint.X + line.EndPoint.X) / 2; + return System.Math.Abs(midX - bounds.Left) < positionTolerance + || System.Math.Abs(midX - bounds.Right) < positionTolerance; + } + } + + private static void DetectZoneMarkers(List lines, Box bounds, HashSet flagged) + { + var positionTolerance = System.Math.Max(bounds.Length, bounds.Width) * 0.01; + var maxTickLength = System.Math.Max(bounds.Length, bounds.Width) * 0.05; + var angularTolerance = OpenNest.Math.Angle.ToRadians(2.0); + + foreach (var line in lines) + { + if (flagged.Contains(line.Id)) continue; + + var dx = line.EndPoint.X - line.StartPoint.X; + var dy = line.EndPoint.Y - line.StartPoint.Y; + var length = System.Math.Sqrt(dx * dx + dy * dy); + + if (length > maxTickLength || length < OpenNest.Math.Tolerance.Epsilon) continue; + + var angleRad = System.Math.Atan2(System.Math.Abs(dy), System.Math.Abs(dx)); + var isVertical = System.Math.Abs(angleRad - System.Math.PI / 2) < angularTolerance; + var isHorizontal = angleRad < angularTolerance; + + if (!isVertical && !isHorizontal) continue; + + var touchesEdge = false; + if (isVertical) + { + var minY = System.Math.Min(line.StartPoint.Y, line.EndPoint.Y); + var maxY = System.Math.Max(line.StartPoint.Y, line.EndPoint.Y); + touchesEdge = System.Math.Abs(minY - bounds.Bottom) < positionTolerance + || System.Math.Abs(maxY - bounds.Top) < positionTolerance; + } + else if (isHorizontal) + { + var minX = System.Math.Min(line.StartPoint.X, line.EndPoint.X); + var maxX = System.Math.Max(line.StartPoint.X, line.EndPoint.X); + touchesEdge = System.Math.Abs(minX - bounds.Left) < positionTolerance + || System.Math.Abs(maxX - bounds.Right) < positionTolerance; + } + + if (touchesEdge) + flagged.Add(line.Id); + } + } } } diff --git a/OpenNest.Tests/IO/TitleBlockDetectorTests.cs b/OpenNest.Tests/IO/TitleBlockDetectorTests.cs index 173957b..c0eed17 100644 --- a/OpenNest.Tests/IO/TitleBlockDetectorTests.cs +++ b/OpenNest.Tests/IO/TitleBlockDetectorTests.cs @@ -66,5 +66,87 @@ namespace OpenNest.Tests.IO 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); + } } }