using System; using System.Collections.Generic; using System.Linq; using ACadSharp; using OpenNest.Geometry; namespace OpenNest.IO { public static class TitleBlockDetector { private static readonly HashSet TitleBlockLayerNames = new(StringComparer.OrdinalIgnoreCase) { "TITLE", "TITLEBLOCK", "TITLE_BLOCK", "BORDER", "FRAME", "TB", "INFO", "SHEET", "ANNOTATION" }; public static HashSet Detect(List entities, CadDocument document) { var flagged = new HashSet(); DetectByLayerName(entities, flagged); DetectBorder(entities, flagged); return flagged; } private static void DetectByLayerName(List entities, HashSet flagged) { foreach (var entity in entities) { if (entity.Layer?.Name != null && TitleBlockLayerNames.Contains(entity.Layer.Name)) 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); } } } }