using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Runtime; using CadParamPluging.Common; using CadParamPluging.Domain.Models; namespace CadParamPluging.Cad { public static class TemplateDrawingService { private static readonly Regex MatchFieldRegex = new Regex( @"^\s*(交付状态|工艺方法|结构特征|特殊条件)\s*[::]\s*(.+?)\s*$", RegexOptions.Compiled); private static readonly Regex MTextFormatRegex = new Regex(@"\\[A-Za-z]+[^;]*;", RegexOptions.Compiled); public static Document CreateDocumentFromTemplate(TemplateInfo template) { var doc = Application.DocumentManager.Add(template.FilePath); Application.DocumentManager.MdiActiveDocument = doc; return doc; } /// /// 移除模板中用于“匹配模板/图纸”的参数标注文本(交付状态/工艺方法/结构特征/特殊条件)。 /// 仅清理当前生成图纸中目标空间(Layout 或 ModelSpace)里的文本实体/块属性。 /// public static int RemoveMatchParameterAnnotations(CadContext ctx, string layoutName, bool scanModelSpace) { if (ctx == null) { throw new ArgumentNullException(nameof(ctx)); } var db = ctx.Database; var tr = ctx.Transaction; if (scanModelSpace) { var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); var ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); return EraseMatchTextEntities(tr, ms); } if (string.IsNullOrWhiteSpace(layoutName)) { return 0; } var layoutDict = (DBDictionary)tr.GetObject(db.LayoutDictionaryId, OpenMode.ForRead); ObjectId layoutId = ObjectId.Null; foreach (DBDictionaryEntry entry in layoutDict) { if (string.Equals(entry.Key, layoutName, StringComparison.OrdinalIgnoreCase)) { layoutId = entry.Value; break; } } if (layoutId.IsNull) { return 0; } var layout = (Layout)tr.GetObject(layoutId, OpenMode.ForRead); var btr = (BlockTableRecord)tr.GetObject(layout.BlockTableRecordId, OpenMode.ForWrite); return EraseMatchTextEntities(tr, btr); } private static int EraseMatchTextEntities(Transaction tr, BlockTableRecord btr) { if (tr == null || btr == null) { return 0; } var removed = 0; var ids = btr.Cast().ToList(); foreach (var id in ids) { var ent = tr.GetObject(id, OpenMode.ForWrite, false) as Entity; if (ent == null) { continue; } if (ent is DBText t) { if (ContainsMatchField(t.TextString)) { ent.Erase(true); removed++; } continue; } if (ent is MText mt) { var plain = SimplifyMText(mt.Contents); if (ContainsMatchField(plain)) { ent.Erase(true); removed++; } continue; } if (ent is BlockReference br) { foreach (ObjectId attId in br.AttributeCollection) { var att = tr.GetObject(attId, OpenMode.ForWrite, false) as AttributeReference; if (att == null) { continue; } if (ContainsMatchField(att.TextString)) { try { att.Erase(true); removed++; } catch { // ignore } } } } } return removed; } private static bool ContainsMatchField(string raw) { if (string.IsNullOrWhiteSpace(raw)) { return false; } var lines = raw .Replace("\r\n", "\n") .Replace("\r", "\n") .Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries) .Select(s => (s ?? string.Empty).Trim()) .Where(s => s.Length > 0); foreach (var line in lines) { if (MatchFieldRegex.IsMatch(line)) { return true; } } return false; } 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; } public sealed class NoteApplyResult { public bool Applied { get; set; } public string RenderedText { get; set; } public int PlaceholderCountInDwg { get; set; } public string TargetKind { get; set; } public string Message { get; set; } } private sealed class NoteCandidate { public int Score; public string Kind; public string PlainText; public Action Apply; } public static NoteApplyResult ApplyNoteTemplate(CadContext ctx, string layoutName, bool scanModelSpace, TemplateSchemaDefinition schema, ParamBag bag) { if (ctx == null) { throw new ArgumentNullException(nameof(ctx)); } if (schema == null) { return new NoteApplyResult { Applied = false, Message = "Schema is null" }; } if (bag == null) { return new NoteApplyResult { Applied = false, Message = "ParamBag is null" }; } if (schema.NoteBindings == null || schema.NoteBindings.Count == 0) { return new NoteApplyResult { Applied = false, Message = "未配置附注绑定,跳过。" }; } var tr = ctx.Transaction; var db = ctx.Database; var space = GetTargetSpace(tr, db, layoutName, scanModelSpace); if (space == null) { return new NoteApplyResult { Applied = false, Message = "未找到目标空间,跳过附注替换。" }; } var candidates = new List(); var visitedBlocks = new HashSet(); foreach (ObjectId id in space) { var ent = tr.GetObject(id, OpenMode.ForRead, false) as Entity; if (ent != null) { CollectNoteCandidates(tr, ent, visitedBlocks, candidates); } } if (candidates.Count == 0) { return new NoteApplyResult { Applied = false, Message = "未找到附注文本目标(包含‘附注’且包含占位符*),跳过。" }; } var best = candidates.OrderByDescending(c => c.Score).FirstOrDefault(); if (best == null || best.Apply == null) { return new NoteApplyResult { Applied = false, Message = "未找到可替换的附注文本目标,跳过。" }; } var templateText = best.PlainText ?? string.Empty; var placeholderCount = NoteTemplateEngine.CountPlaceholders(templateText); if (placeholderCount <= 0) { return new NoteApplyResult { Applied = false, TargetKind = best.Kind, PlaceholderCountInDwg = placeholderCount, Message = "附注目标中未检测到有效占位符(*,忽略****),跳过。" }; } var effective = NoteTemplateEngine.BuildEffectiveValueKeyBindings(schema.NoteBindings); var rendered = NoteTemplateEngine.Render(templateText, effective, bag.GetString); best.Apply(rendered); return new NoteApplyResult { Applied = true, TargetKind = best.Kind, PlaceholderCountInDwg = placeholderCount, RenderedText = rendered, Message = "附注已替换。" }; } private static BlockTableRecord GetTargetSpace(Transaction tr, Database db, string layoutName, bool scanModelSpace) { if (tr == null || db == null) { return null; } if (scanModelSpace) { var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); return (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead); } if (string.IsNullOrWhiteSpace(layoutName)) { return null; } var layoutDict = (DBDictionary)tr.GetObject(db.LayoutDictionaryId, OpenMode.ForRead); ObjectId layoutId = ObjectId.Null; foreach (DBDictionaryEntry entry in layoutDict) { if (string.Equals(entry.Key, layoutName, StringComparison.OrdinalIgnoreCase)) { layoutId = entry.Value; break; } } if (layoutId.IsNull) { return null; } var layout = (Layout)tr.GetObject(layoutId, OpenMode.ForRead); return (BlockTableRecord)tr.GetObject(layout.BlockTableRecordId, OpenMode.ForRead); } private static void CollectNoteCandidates(Transaction tr, Entity ent, HashSet visitedBlocks, List candidates) { if (tr == null || ent == null || candidates == null) { return; } if (ent is DBText t) { AddNoteCandidateIfMatch(t.TextString, "DBText", t.ObjectId, tr, candidates); return; } if (ent is MText mt) { var plain = SimplifyMText(mt.Contents); AddNoteCandidateIfMatch(plain, "MText", mt.ObjectId, tr, candidates); return; } if (ent is BlockReference br) { foreach (ObjectId attId in br.AttributeCollection) { var att = tr.GetObject(attId, OpenMode.ForRead, false) as AttributeReference; if (att == null) { continue; } AddNoteCandidateIfMatch(att.TextString, "Attribute", attId, tr, candidates); } var blockId = br.BlockTableRecord; if (blockId.IsNull) { return; } if (visitedBlocks == null) { visitedBlocks = new HashSet(); } if (visitedBlocks.Contains(blockId)) { return; } visitedBlocks.Add(blockId); var btr = tr.GetObject(blockId, OpenMode.ForRead, false) as BlockTableRecord; if (btr == null) { return; } foreach (ObjectId childId in btr) { var child = tr.GetObject(childId, OpenMode.ForRead, false) as Entity; if (child != null) { CollectNoteCandidates(tr, child, visitedBlocks, candidates); } } } } private static void AddNoteCandidateIfMatch(string plainText, string kind, ObjectId id, Transaction tr, List candidates) { if (!IsLikelyNoteText(plainText)) { return; } var score = ComputeNoteScore(plainText); if (score <= 0) { return; } candidates.Add(new NoteCandidate { Score = score, Kind = kind, PlainText = plainText, Apply = rendered => ApplyNoteTextToObject(tr, id, kind, rendered) }); } private static bool IsLikelyNoteText(string plain) { if (string.IsNullOrWhiteSpace(plain)) { return false; } if (!plain.Contains("*")) { return false; } if (plain.Contains("附注")) { return true; } // fallback: numbered note line return plain.Contains("1*") || plain.Contains("1 *"); } private static int ComputeNoteScore(string plain) { if (string.IsNullOrWhiteSpace(plain)) { return 0; } var score = 0; var lines = SplitLines(plain).ToList(); if (plain.Contains("附注")) { score += 10; } if (lines.Count > 0 && string.Equals(lines[0].Trim(), "附注", StringComparison.OrdinalIgnoreCase)) { score += 10; } if (plain.Contains("*")) { score += 5; } if (plain.Contains("1*")) { score += 3; } return score; } 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()); } private static void ApplyNoteTextToObject(Transaction tr, ObjectId id, string kind, string rendered) { if (tr == null || id.IsNull) { return; } rendered = rendered ?? string.Empty; // We try multiple types regardless of recorded kind to be safe. var obj = tr.GetObject(id, OpenMode.ForWrite, false); if (obj is MText mt) { mt.Contents = ToMTextContents(rendered); return; } if (obj is DBText t) { t.TextString = CollapseToSingleLine(rendered); return; } if (obj is AttributeReference att) { att.TextString = CollapseToSingleLine(rendered); } } private static string ToMTextContents(string plainText) { if (string.IsNullOrEmpty(plainText)) { return string.Empty; } var s = plainText .Replace("\r\n", "\n") .Replace("\r", "\n") .Replace("\n", "\\P"); return s; } private static string CollapseToSingleLine(string text) { if (string.IsNullOrWhiteSpace(text)) { return string.Empty; } var parts = (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) .ToArray(); return string.Join(" ", parts); } public static void KeepOnlyLayout(Document doc, string layoutName) { if (doc == null) { throw new ArgumentNullException(nameof(doc)); } if (string.IsNullOrWhiteSpace(layoutName)) { return; } using (doc.LockDocument()) { var db = doc.Database; var prevDb = HostApplicationServices.WorkingDatabase; HostApplicationServices.WorkingDatabase = db; try { List layoutNames; using (var tr = db.TransactionManager.StartTransaction()) { var layoutDict = (DBDictionary)tr.GetObject(db.LayoutDictionaryId, OpenMode.ForRead); layoutNames = layoutDict.Cast().Select(e => e.Key).ToList(); tr.Commit(); } if (!layoutNames.Any(n => string.Equals(n, layoutName, StringComparison.OrdinalIgnoreCase))) { return; } var lm = LayoutManager.Current; lm.CurrentLayout = layoutName; foreach (var name in layoutNames) { if (string.Equals(name, "Model", StringComparison.OrdinalIgnoreCase)) { continue; } if (string.Equals(name, layoutName, StringComparison.OrdinalIgnoreCase)) { continue; } try { lm.DeleteLayout(name); } catch { // ignore } } } finally { HostApplicationServices.WorkingDatabase = prevDb; } } } public static void KeepOnlyModelWindow(Document doc, Extents3d window) { if (doc == null) { throw new ArgumentNullException(nameof(doc)); } using (doc.LockDocument()) { var db = doc.Database; using (var tr = db.TransactionManager.StartTransaction()) { var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); var ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); var erased = 0; var kept = 0; foreach (ObjectId id in ms) { var ent = tr.GetObject(id, OpenMode.ForWrite) as Entity; if (ent == null) { continue; } try { var ext = ent.GeometricExtents; if (!Intersects(ext, window)) { ent.Erase(true); erased++; } else { kept++; } } catch { // 无法获取范围的对象默认保留,避免误删关键对象 kept++; } } tr.Commit(); } } } private static bool Intersects(Extents3d a, Extents3d b) { return a.MinPoint.X <= b.MaxPoint.X && a.MaxPoint.X >= b.MinPoint.X && a.MinPoint.Y <= b.MaxPoint.Y && a.MaxPoint.Y >= b.MinPoint.Y; } } }