using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Geometry; namespace CadParamPluging.Cad { public sealed class ModelSheetCandidate { public string Name { get; set; } public List DeliveryStatuses { get; set; } public List ProcessMethods { get; set; } public List StructuralFeatures { get; set; } public List SpecialConditions { get; set; } public Extents3d Window { get; set; } public ModelSheetCandidate() { DeliveryStatuses = new List(); ProcessMethods = new List(); StructuralFeatures = new List(); SpecialConditions = new List(); } } public static class TemplateModelSheetExtractor { private static readonly Regex FieldRegex = new Regex( @"^\s*(交付状态|工艺方法|结构特征|特殊条件)\s*[::]\s*(.+?)\s*$", RegexOptions.Compiled); private static readonly Regex MTextFormatRegex = new Regex(@"\\[A-Za-z]+[^;]*;", RegexOptions.Compiled); private sealed class TextMatch { public string Field; public string Value; public Point3d Pos; } private sealed class VLine { public double X; public double YMin; public double YMax; } private sealed class HLine { public double Y; public double XMin; public double XMax; } public static List ExtractCandidates(string templatePath) { if (string.IsNullOrWhiteSpace(templatePath)) { throw new ArgumentNullException(nameof(templatePath)); } var matches = new List(); var vLines = new List(); var hLines = new List(); using (var db = new Database(false, true)) { ReadTemplateDatabase(db, templatePath); db.CloseInput(true); using (var tr = db.TransactionManager.StartTransaction()) { var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); var ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead); var visitedBlocks = new HashSet(); foreach (ObjectId id in ms) { var ent = tr.GetObject(id, OpenMode.ForRead) as Entity; if (ent != null) { CollectEntity(ent, Matrix3d.Identity, tr, visitedBlocks, matches, vLines, hLines); } } tr.Commit(); } } return BuildCandidates(matches, vLines, hLines); } private static void ReadTemplateDatabase(Database db, string templatePath) { try { db.ReadDwgFile(templatePath, FileShare.ReadWrite, true, string.Empty); } catch (MissingMethodException) { db.ReadDwgFile(templatePath, FileOpenMode.OpenForReadAndAllShare, true, string.Empty); } } private static void TryCollectLine(Line ln, List vLines, List hLines) { var a = ln.StartPoint; var b = ln.EndPoint; var dx = Math.Abs(a.X - b.X); var dy = Math.Abs(a.Y - b.Y); const double tol = 1e-3; if (dx <= tol && dy > tol) { vLines.Add(new VLine { X = (a.X + b.X) / 2.0, YMin = Math.Min(a.Y, b.Y), YMax = Math.Max(a.Y, b.Y) }); return; } if (dy <= tol && dx > tol) { hLines.Add(new HLine { Y = (a.Y + b.Y) / 2.0, XMin = Math.Min(a.X, b.X), XMax = Math.Max(a.X, b.X) }); } } private static void CollectEntity(Entity ent, Matrix3d transform, Transaction tr, HashSet visitedBlocks, List matches, List vLines, List hLines) { if (ent is Line ln) { var a = ln.StartPoint.TransformBy(transform); var b = ln.EndPoint.TransformBy(transform); TryCollectLinePoints(a, b, vLines, hLines); return; } if (ent is DBText t) { var p = t.Position.TransformBy(transform); TryCollectText(t.TextString, p, matches); return; } if (ent is MText mt) { var p = mt.Location.TransformBy(transform); var plain = SimplifyMText(mt.Contents); TryCollectText(plain, p, matches); return; } if (ent is BlockReference br) { // 属性 foreach (ObjectId attId in br.AttributeCollection) { var att = tr.GetObject(attId, OpenMode.ForRead) as AttributeReference; if (att == null) { continue; } var p = att.Position.TransformBy(transform); TryCollectText(att.TextString, p, matches); } // 块定义内部 var blockId = br.BlockTableRecord; if (blockId.IsNull || visitedBlocks.Contains(blockId)) { return; } visitedBlocks.Add(blockId); var btr = tr.GetObject(blockId, OpenMode.ForRead) as BlockTableRecord; if (btr == null) { return; } var nextTransform = br.BlockTransform * transform; foreach (ObjectId childId in btr) { var child = tr.GetObject(childId, OpenMode.ForRead) as Entity; if (child != null) { CollectEntity(child, nextTransform, tr, visitedBlocks, matches, vLines, hLines); } } } } private static void TryCollectLinePoints(Point3d a, Point3d b, List vLines, List hLines) { var dx = Math.Abs(a.X - b.X); var dy = Math.Abs(a.Y - b.Y); const double tol = 1e-3; if (dx <= tol && dy > tol) { vLines.Add(new VLine { X = (a.X + b.X) / 2.0, YMin = Math.Min(a.Y, b.Y), YMax = Math.Max(a.Y, b.Y) }); return; } if (dy <= tol && dx > tol) { hLines.Add(new HLine { Y = (a.Y + b.Y) / 2.0, XMin = Math.Min(a.X, b.X), XMax = Math.Max(a.X, b.X) }); } } private static void TryCollectText(string raw, Point3d pos, List matches) { if (matches == null) { return; } var lines = SplitLines(raw).ToList(); if (lines.Count == 0) { return; } var hasAnyField = false; foreach (var line in lines) { if (TryParseFieldLine(line, out var field, out var value)) { hasAnyField = true; matches.Add(new TextMatch { Field = field, Value = value, Pos = pos }); } } if (!hasAnyField) { return; } foreach (var line in lines) { if (IsSpecialConditionValueLine(line)) { var v = NormalizeValue(line); if (v.Length == 0) { continue; } matches.Add(new TextMatch { Field = "特殊条件", Value = v, Pos = pos }); } } } private static bool TryParseFieldLine(string line, out string field, out string value) { field = null; value = null; if (string.IsNullOrWhiteSpace(line)) { return false; } var m = FieldRegex.Match(line); if (!m.Success) { return false; } field = m.Groups[1].Value; value = NormalizeValue(m.Groups[2].Value); return !string.IsNullOrWhiteSpace(field) && !string.IsNullOrWhiteSpace(value); } private static bool IsSpecialConditionValueLine(string line) { if (string.IsNullOrWhiteSpace(line)) { return false; } if (FieldRegex.IsMatch(line)) { return false; } return line.IndexOf(':') < 0 && line.IndexOf(':') < 0; } private static IEnumerable SplitLines(string text) { if (string.IsNullOrWhiteSpace(text)) { return Enumerable.Empty(); } return (text ?? string.Empty) .Replace("\r\n", "\n") .Replace("\r", "\n") .Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries) .Select(s => (s ?? string.Empty).Trim()) .Where(s => s.Length > 0); } private static string SimplifyMText(string contents) { if (string.IsNullOrEmpty(contents)) { return string.Empty; } var s = contents .Replace("\\P", "\n") .Replace("\\p", "\n") .Replace("\\~", " "); s = s.Replace("{", string.Empty).Replace("}", string.Empty); s = MTextFormatRegex.Replace(s, string.Empty); return s; } private static string NormalizeValue(string s) { if (string.IsNullOrWhiteSpace(s)) { return string.Empty; } var trimmed = s.Trim(); var chars = trimmed.Where(c => !char.IsWhiteSpace(c)).ToArray(); return new string(chars); } private static List BuildCandidates(List matches, List vLines, List hLines) { if (matches == null || matches.Count == 0) { return new List(); } var clusters = Cluster(matches); var result = new List(); var idx = 1; foreach (var cluster in clusters) { var delivery = cluster.Where(m => m.Field == "交付状态").Select(m => m.Value).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); var process = cluster.Where(m => m.Field == "工艺方法").Select(m => m.Value).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); var structural = cluster.Where(m => m.Field == "结构特征").Select(m => m.Value).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); var special = cluster.Where(m => m.Field == "特殊条件").Select(m => m.Value).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); if (delivery.Count == 0 || process.Count == 0 || structural.Count == 0) { continue; } var textExt = ExtentsFromPoints(cluster.Select(m => m.Pos)); // 优先使用“最近的四边线”构建矩形,更适配你这种 4 条直线外框。 var rectExt = FindRectangleByNearestLines(textExt, vLines, hLines) ?? FindNearestRectangleExtents(textExt, vLines, hLines); var window = rectExt.HasValue ? Union(rectExt.Value, Expand(textExt, 200.0)) : Expand(textExt, 2000.0); result.Add(new ModelSheetCandidate { Name = $"Model#{idx}", DeliveryStatuses = delivery, ProcessMethods = process, StructuralFeatures = structural, SpecialConditions = special, Window = window }); idx++; } return result; } private static List> Cluster(List matches) { var n = matches.Count; var parent = Enumerable.Range(0, n).ToArray(); int Find(int x) { while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; } return x; } void UnionIdx(int a, int b) { var ra = Find(a); var rb = Find(b); if (ra != rb) parent[rb] = ra; } var threshold = EstimateThreshold(matches); var threshold2 = threshold * threshold; for (var i = 0; i < n; i++) { for (var j = i + 1; j < n; j++) { var dx = matches[i].Pos.X - matches[j].Pos.X; var dy = matches[i].Pos.Y - matches[j].Pos.Y; var d2 = dx * dx + dy * dy; if (d2 <= threshold2) { UnionIdx(i, j); } } } var groups = new Dictionary>(); for (var i = 0; i < n; i++) { var r = Find(i); if (!groups.TryGetValue(r, out var list)) { list = new List(); groups[r] = list; } list.Add(matches[i]); } return groups.Values.ToList(); } private static double EstimateThreshold(List matches) { if (matches.Count < 2) { return 1000.0; } var dists = new List(); for (var i = 0; i < matches.Count; i++) { var best = double.MaxValue; for (var j = 0; j < matches.Count; j++) { if (i == j) continue; var dx = matches[i].Pos.X - matches[j].Pos.X; var dy = matches[i].Pos.Y - matches[j].Pos.Y; var d = Math.Sqrt(dx * dx + dy * dy); if (d < best) best = d; } if (best < double.MaxValue) dists.Add(best); } dists.Sort(); var median = dists[dists.Count / 2]; if (median <= 0) median = 500.0; var thr = median * 4.0; if (thr < 500.0) thr = 500.0; if (thr > 20000.0) thr = 20000.0; return thr; } private static Extents3d ExtentsFromPoints(IEnumerable pts) { var arr = (pts ?? Enumerable.Empty()).ToArray(); if (arr.Length == 0) { return new Extents3d(new Point3d(0, 0, 0), new Point3d(0, 0, 0)); } var minX = arr.Min(p => p.X); var minY = arr.Min(p => p.Y); var maxX = arr.Max(p => p.X); var maxY = arr.Max(p => p.Y); return new Extents3d(new Point3d(minX, minY, 0), new Point3d(maxX, maxY, 0)); } private static Extents3d Expand(Extents3d e, double margin) { return new Extents3d( new Point3d(e.MinPoint.X - margin, e.MinPoint.Y - margin, 0), new Point3d(e.MaxPoint.X + margin, e.MaxPoint.Y + margin, 0)); } private static Extents3d Union(Extents3d a, Extents3d b) { return new Extents3d( new Point3d(Math.Min(a.MinPoint.X, b.MinPoint.X), Math.Min(a.MinPoint.Y, b.MinPoint.Y), 0), new Point3d(Math.Max(a.MaxPoint.X, b.MaxPoint.X), Math.Max(a.MaxPoint.Y, b.MaxPoint.Y), 0)); } private static Extents3d? FindNearestRectangleExtents(Extents3d anchor, List vLines, List hLines) { if (vLines == null || hLines == null || vLines.Count < 2 || hLines.Count < 2) { return null; } var cx = (anchor.MinPoint.X + anchor.MaxPoint.X) / 2.0; var cy = (anchor.MinPoint.Y + anchor.MaxPoint.Y) / 2.0; var vCand = vLines.OrderBy(l => Math.Abs(l.X - cx)).Take(30).ToList(); var hCand = hLines.OrderBy(l => Math.Abs(l.Y - cy)).Take(30).ToList(); const double tol = 5.0; Extents3d? best = null; double bestScore = double.MaxValue; for (var i = 0; i < vCand.Count; i++) { for (var j = i + 1; j < vCand.Count; j++) { var left = vCand[i].X < vCand[j].X ? vCand[i] : vCand[j]; var right = vCand[i].X < vCand[j].X ? vCand[j] : vCand[i]; var width = right.X - left.X; if (width < 1000) continue; for (var a = 0; a < hCand.Count; a++) { for (var b = a + 1; b < hCand.Count; b++) { var bottom = hCand[a].Y < hCand[b].Y ? hCand[a] : hCand[b]; var top = hCand[a].Y < hCand[b].Y ? hCand[b] : hCand[a]; var height = top.Y - bottom.Y; if (height < 1000) continue; // span check if (left.YMin > bottom.Y + tol || left.YMax < top.Y - tol) continue; if (right.YMin > bottom.Y + tol || right.YMax < top.Y - tol) continue; if (bottom.XMin > left.X + tol || bottom.XMax < right.X - tol) continue; if (top.XMin > left.X + tol || top.XMax < right.X - tol) continue; var rect = new Extents3d( new Point3d(left.X, bottom.Y, 0), new Point3d(right.X, top.Y, 0)); var rcx = (left.X + right.X) / 2.0; var rcy = (bottom.Y + top.Y) / 2.0; var dist = Math.Sqrt((rcx - cx) * (rcx - cx) + (rcy - cy) * (rcy - cy)); var area = width * height; var score = dist + area * 1e-9; // distance first, area tie-break if (score < bestScore) { bestScore = score; best = rect; } } } } } return best; } private static Extents3d? FindRectangleByNearestLines(Extents3d anchor, List vLines, List hLines) { if (vLines == null || hLines == null || vLines.Count == 0 || hLines.Count == 0) { return null; } var cx = (anchor.MinPoint.X + anchor.MaxPoint.X) / 2.0; var cy = (anchor.MinPoint.Y + anchor.MaxPoint.Y) / 2.0; const double tol = 50.0; var left = vLines .Where(l => l.X < cx - tol && l.YMin - tol <= cy && l.YMax + tol >= cy) .OrderByDescending(l => l.X) .FirstOrDefault(); var right = vLines .Where(l => l.X > cx + tol && l.YMin - tol <= cy && l.YMax + tol >= cy) .OrderBy(l => l.X) .FirstOrDefault(); var bottom = hLines .Where(l => l.Y < cy - tol && l.XMin - tol <= cx && l.XMax + tol >= cx) .OrderByDescending(l => l.Y) .FirstOrDefault(); var top = hLines .Where(l => l.Y > cy + tol && l.XMin - tol <= cx && l.XMax + tol >= cx) .OrderBy(l => l.Y) .FirstOrDefault(); if (left == null || right == null || bottom == null || top == null) { return null; } var width = right.X - left.X; var height = top.Y - bottom.Y; if (width < 1000 || height < 1000) { return null; } var rect = new Extents3d(new Point3d(left.X, bottom.Y, 0), new Point3d(right.X, top.Y, 0)); if (anchor.MinPoint.X < rect.MinPoint.X - tol || anchor.MaxPoint.X > rect.MaxPoint.X + tol || anchor.MinPoint.Y < rect.MinPoint.Y - tol || anchor.MaxPoint.Y > rect.MaxPoint.Y + tol) { return null; } return rect; } } }