using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Geometry; 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); // 匹配MText中残留的格式代码片段(如 Xt31.278; 或 qj,tz; 等) // 匹配模式:1-4个字母开头,后面可以是数字/点/逗号/字母的组合,最后以分号结尾 private static readonly Regex MTextResidualFormatRegex = new Regex(@"^[A-Za-z]{1,4}[\d.,A-Za-z]*;", RegexOptions.Compiled); // 外框图层名称集合 private static readonly HashSet OuterFrameLayerNames = new HashSet(StringComparer.OrdinalIgnoreCase) { "图框", "边框", "外框", "FRAME", "BORDER", "粗实线" }; private static readonly Regex CaxaLayerRegex = new Regex(@"^CAXA\d$", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly HashSet DimensionLayerNames = new HashSet(StringComparer.OrdinalIgnoreCase) { "尺寸标注", "标注", "DIM", "DIMENSION", "DIMENSIONS" }; public static Document OpenTemplateDrawing(TemplateInfo template) { if (template == null || string.IsNullOrWhiteSpace(template.FilePath)) { throw new ArgumentException("Template path is invalid"); } var docMgr = Application.DocumentManager; foreach (Document doc in docMgr) { if (string.Equals(doc.Name, template.FilePath, StringComparison.OrdinalIgnoreCase)) { docMgr.MdiActiveDocument = doc; return doc; } } var newDoc = docMgr.Open(template.FilePath, false); docMgr.MdiActiveDocument = newDoc; return newDoc; } /// /// 移除模板中用于“匹配模板/图纸”的参数标注文本(交付状态/工艺方法/结构特征/特殊条件)。 /// 仅清理当前生成图纸中目标空间(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); // 处理每行开头可能残留的格式代码片段(如 Xt31.278; 或 qj,tz;) var lines = s.Split(new[] { '\n' }, StringSplitOptions.None); for (var i = 0; i < lines.Length; i++) { lines[i] = CleanLineResidualFormat(lines[i]); } s = string.Join("\n", lines); return s; } private static string CleanLineResidualFormat(string line) { if (string.IsNullOrEmpty(line)) { return line; } // 循环清理行首的格式代码残留,直到没有匹配为止 var result = line; while (true) { var trimmed = result.TrimStart(); var match = MTextResidualFormatRegex.Match(trimmed); if (match.Success && match.Index == 0) { // 移除匹配的格式代码 var leadingSpaces = result.Length - result.TrimStart().Length; result = new string(' ', leadingSpaces) + trimmed.Substring(match.Length); } else { break; } } return result; } 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 ObjectId TargetId; // Added for persistence tracking public string PlainText; public string OriginalContents; public Action Apply; } /// /// 在实体扩展字典中保存原始模板文本,以便下次重新生成时可以再次使用 /// private static void SetNoteTemplateData(Transaction tr, ObjectId id, string template) { if (string.IsNullOrEmpty(template)) return; var ent = tr.GetObject(id, OpenMode.ForWrite) as Entity; if (ent == null) return; if (ent.ExtensionDictionary.IsNull) { ent.CreateExtensionDictionary(); } var dict = (DBDictionary)tr.GetObject(ent.ExtensionDictionary, OpenMode.ForWrite); const string DictName = "CadParamPluging_NoteData"; // XRecord limits text to 255 characters per element. // Split string into 250 character chunks using DxfCode.XTextString (301) var rb = new ResultBuffer(); var temp = template; while (temp.Length > 0) { var chunk = temp.Length > 250 ? temp.Substring(0, 250) : temp; rb.Add(new TypedValue(301, chunk)); // 301 = DxfCode.XTextString temp = temp.Length > 250 ? temp.Substring(250) : ""; } var xrec = new Xrecord { Data = rb }; if (dict.Contains(DictName)) { var oldId = dict.GetAt(DictName); var oldXrec = (Xrecord)tr.GetObject(oldId, OpenMode.ForWrite); oldXrec.Data = rb; } else { dict.SetAt(DictName, xrec); tr.AddNewlyCreatedDBObject(xrec, true); } } private static string GetNoteTemplateData(Transaction tr, ObjectId id) { var ent = tr.GetObject(id, OpenMode.ForRead) as Entity; if (ent == null || ent.ExtensionDictionary.IsNull) return null; var dict = (DBDictionary)tr.GetObject(ent.ExtensionDictionary, OpenMode.ForRead); const string DictName = "CadParamPluging_NoteData"; if (!dict.Contains(DictName)) return null; var xrec = (Xrecord)tr.GetObject(dict.GetAt(DictName), OpenMode.ForRead); var rb = xrec.Data; if (rb == null) return null; var sb = new System.Text.StringBuilder(); bool hasCode301 = false; foreach (var tv in rb) { if (tv.TypeCode == 301) { hasCode301 = true; if (tv.Value is string s) sb.Append(s); } } if (hasCode301) return sb.ToString(); // Fallback for old code format foreach (var tv in rb) { if (tv.TypeCode == (int)DxfCode.Text) { return tv.Value as string; } } return null; } private sealed class FeatureCategoryData { public string OriginalTemplate; public string GroupId; public string Role; } private const string FeatureCategoryDictName = "CadParamPluging_FeatureCategory"; private static void SetFeatureCategoryData(Transaction tr, ObjectId id, string originalTemplate, string groupId, string role) { var ent = tr.GetObject(id, OpenMode.ForWrite) as Entity; if (ent == null) return; if (ent.ExtensionDictionary.IsNull) { ent.CreateExtensionDictionary(); } var dict = (DBDictionary)tr.GetObject(ent.ExtensionDictionary, OpenMode.ForWrite); var rb = new ResultBuffer( new TypedValue((int)DxfCode.Text, originalTemplate ?? string.Empty), new TypedValue((int)DxfCode.Text, groupId ?? string.Empty), new TypedValue((int)DxfCode.Text, role ?? string.Empty) ); var xrec = new Xrecord { Data = rb }; if (dict.Contains(FeatureCategoryDictName)) { var oldId = dict.GetAt(FeatureCategoryDictName); var oldXrec = (Xrecord)tr.GetObject(oldId, OpenMode.ForWrite); oldXrec.Data = rb; } else { dict.SetAt(FeatureCategoryDictName, xrec); tr.AddNewlyCreatedDBObject(xrec, true); } } private static FeatureCategoryData GetFeatureCategoryData(Transaction tr, ObjectId id) { var ent = tr.GetObject(id, OpenMode.ForRead) as Entity; if (ent == null || ent.ExtensionDictionary.IsNull) return null; var dict = (DBDictionary)tr.GetObject(ent.ExtensionDictionary, OpenMode.ForRead); if (!dict.Contains(FeatureCategoryDictName)) return null; var xrec = (Xrecord)tr.GetObject(dict.GetAt(FeatureCategoryDictName), OpenMode.ForRead); var rb = xrec.Data; if (rb == null) return null; var texts = new List(); foreach (var tv in rb) { if (tv.TypeCode == (int)DxfCode.Text) { texts.Add(tv.Value as string); } } if (texts.Count == 0) return null; return new FeatureCategoryData { OriginalTemplate = texts.Count >= 1 ? texts[0] : null, GroupId = texts.Count >= 2 ? texts[1] : null, Role = texts.Count >= 3 ? texts[2] : null }; } private static string ExtractMTextPlainText(string contents) { if (string.IsNullOrEmpty(contents)) return string.Empty; var s = contents; // Common pattern we generate: {\fSimSun|...;TEXT} var semi = s.LastIndexOf(';'); if (semi >= 0) { s = s.Substring(semi + 1); } s = s.Trim(); if (s.EndsWith("}", StringComparison.Ordinal)) { s = s.Substring(0, s.Length - 1); } s = s.Replace("\\P", "\n"); return s.Trim(); } 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 db = ctx.Database; var tr = ctx.Transaction; var space = GetTargetSpace(tr, db, layoutName, scanModelSpace); if (space == null) { return new NoteApplyResult { Applied = false, Message = "未找到目标空间,跳过附注替换。" }; } Func getValueW = (k) => { var val = bag.GetString(k); if (k != null && k.StartsWith("MarkingContent", StringComparison.OrdinalIgnoreCase)) { if (bag.GetString(k + "_ShowInNote") == "0") return "__SKIP_NOTE__"; } return val; }; // 1. 优先查找已经标记过的 Note Entity (重生成场景) foreach (ObjectId id in space) { var storedTemplate = GetNoteTemplateData(tr, id); if (!string.IsNullOrEmpty(storedTemplate)) { // 找到了之前的附注,使用存储的模板重新渲染 var effectiveStored = NoteTemplateEngine.BuildEffectiveValueKeyBindings(schema.NoteBindings); var renderedStored = NoteTemplateEngine.Render(storedTemplate, effectiveStored, getValueW); ApplyNoteTextToObject(tr, id, "ExistingTaggedNote", renderedStored, null); return new NoteApplyResult { Applied = true, TargetKind = "ExistingTaggedNote", PlaceholderCountInDwg = 0, RenderedText = renderedStored, 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) { // 尝试基于白框定位自动创建附注 if (!string.IsNullOrWhiteSpace(schema.NoteTemplateText)) { // 基于当前 Space 查找白框,以支持 Layout var spaceEntList = space.Cast() .Select(id => { try { return tr.GetObject(id, OpenMode.ForRead, false) as Entity; } catch { return null; } }) .Where(e => e != null && !e.IsErased) .ToList(); var frame = ComputeWhiteFrameExtentsFromEntities(tr, spaceEntList); if (frame.HasValue) { var f = frame.Value; var w = f.MaxPoint.X - f.MinPoint.X; var h = f.MaxPoint.Y - f.MinPoint.Y; // 改为左下角锚定 (BottomLeft),实现"贴合左边和下边"的效果 // MText 以 BottomLeft 对齐时,插入点位于文字块左下角,内容向上生长 var insertX = f.MinPoint.X + w * 0.005; // 左边距 0.5% var insertY = f.MinPoint.Y + h * 0.005; // 下边距 0.5% var insertPoint = new Point3d(insertX, insertY, 0); var effectiveForCreation = NoteTemplateEngine.BuildEffectiveValueKeyBindings(schema.NoteBindings); var renderedForCreation = NoteTemplateEngine.Render(schema.NoteTemplateText, effectiveForCreation, getValueW); var mt = new MText(); mt.Contents = ToMTextContents(renderedForCreation); mt.Location = insertPoint; mt.TextHeight = 3.0; mt.Attachment = AttachmentPoint.BottomLeft; // 关键:锚点在左下,文字向上延伸 // 智能动态宽度:尝试检测右下角标题栏(表格)的左边界 // 修正:搜索区域扩大,防止表格过宽(超过50%)导致检测不到左边界,从而产生重叠 // X方向:从左侧 25% 开始扫描(涵盖右侧 75% 区域) // Y方向:从底部扫描至 50% 高度(防止标题栏上方有较高的更改栏) var searchMinX = f.MinPoint.X + w * 0.25; var searchMaxX = f.MaxPoint.X - 5.0; var searchMinY = f.MinPoint.Y; var searchMaxY = f.MinPoint.Y + h * 0.5; var candidatesX = new List(); foreach (var ent in spaceEntList) { if (ent is Line ln) { // 垂直线 if (Math.Abs(ln.StartPoint.X - ln.EndPoint.X) < 1.0 && ln.StartPoint.X > searchMinX && ln.StartPoint.X < searchMaxX && Math.Max(ln.StartPoint.Y, ln.EndPoint.Y) > searchMinY && Math.Min(ln.StartPoint.Y, ln.EndPoint.Y) < searchMaxY) { candidatesX.Add(ln.StartPoint.X); } } else if (ent is Polyline pl) { for (int i = 0; i < pl.NumberOfVertices; i++) { var pt = pl.GetPoint3dAt(i); if (pt.X > searchMinX && pt.X < searchMaxX && pt.Y > searchMinY && pt.Y < searchMaxY) { candidatesX.Add(pt.X); } } } else if (ent is BlockReference br) { try { var ext = br.GeometricExtents; // 只要块的一部分落在搜索区域内,我们就考虑其最左边界 if (ext.MaxPoint.X > searchMinX && ext.MinPoint.X < searchMaxX && ext.MaxPoint.Y > searchMinY && ext.MinPoint.Y < searchMaxY) { // 确保最左边界在合理范围内(不小于搜索起点) // 如果块非常大(如整个图框块),我们要小心不要把图框左边当成表格左边 // 假设表格块通常比起点 (0.25w) 要靠右 if (ext.MinPoint.X > searchMinX) { candidatesX.Add(ext.MinPoint.X); } } } catch { } } } // 默认最左边界为图框右边界 double boundaryX = f.MaxPoint.X; bool foundTable = false; if (candidatesX.Count > 0) { // 取最小值作为表格左边界 boundaryX = candidatesX.Min(); foundTable = true; } double availableWidth; if (foundTable) { // 如果找到了表格,宽度 = 表格左边界 - 插入点 - 留白 availableWidth = boundaryX - insertX - 5.0; } else { // 没找到表格 -> 使用更保守的默认比例 (33%),避免覆盖 availableWidth = w * 0.33; } // 安全检查:如果计算出的宽度太小(比如小于总宽 10%),保留最小可读宽度或回退 if (availableWidth < w * 0.10) { // 空间过小,强行给予 30% 宽度,可能会重叠但保证文字显示 availableWidth = w * 0.30; } mt.Width = availableWidth; mt.ColorIndex = 7; mt.Layer = "0"; try { var layerTbl = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead); if (layerTbl.Has("TEXT")) { mt.Layer = "TEXT"; } else if (layerTbl.Has("文字")) { mt.Layer = "文字"; } } catch { } space.AppendEntity(mt); tr.AddNewlyCreatedDBObject(mt, true); // 关键:标记该实体,保存模板文本 SetNoteTemplateData(tr, mt.ObjectId, schema.NoteTemplateText); return new NoteApplyResult { Applied = true, TargetKind = "CreatedMText", PlaceholderCountInDwg = 0, RenderedText = renderedForCreation, Message = "未找到附注定位,已自动创建并标记。" }; } } 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, getValueW); best.Apply(rendered); if (!best.TargetId.IsNull) { SetNoteTemplateData(tr, best.TargetId, templateText); } 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; } // Default to ModelSpace if scanModelSpace requested OR layoutName is null/empty implicitly aiming for Model if (scanModelSpace || string.IsNullOrWhiteSpace(layoutName) || string.Equals(layoutName, "Model", StringComparison.OrdinalIgnoreCase)) { var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); return (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); } 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.ForWrite); } 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, mt.Contents); 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, string originalContents = null) { if (!IsLikelyNoteText(plainText)) { return; } var score = ComputeNoteScore(plainText); if (score <= 0) { return; } candidates.Add(new NoteCandidate { Score = score, Kind = kind, TargetId = id, // Store ID PlainText = plainText, OriginalContents = originalContents, Apply = rendered => ApplyNoteTextToObject(tr, id, kind, rendered, originalContents) }); } 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, string originalContents = null) { 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) { // 如果有原始内容,在原始内容中直接替换占位符以保留格式 if (!string.IsNullOrEmpty(originalContents)) { mt.Contents = ReplaceStarsInOriginalContents(originalContents, rendered); } else { mt.Contents = ToMTextContents(rendered); } return; } if (obj is DBText t) { var collapsed = CollapseToSingleLine(rendered); if (collapsed.Length > 250) { // DBText 在 CAD 中有硬性的 255 长度限制,长文本需升级为 MText var newMt = new MText(); newMt.Contents = ToMTextContents(rendered); // 恢复原有折行 newMt.Location = t.Position; newMt.TextHeight = t.Height; newMt.Layer = t.Layer; newMt.ColorIndex = t.ColorIndex; newMt.TextStyleId = t.TextStyleId; newMt.Rotation = t.Rotation; if (!t.BlockId.IsNull) { var btr = (BlockTableRecord)tr.GetObject(t.BlockId, OpenMode.ForWrite); btr.AppendEntity(newMt); tr.AddNewlyCreatedDBObject(newMt, true); t.Erase(true); } else { t.TextString = collapsed.Substring(0, 250); } } else { t.TextString = collapsed; } return; } if (obj is AttributeReference att) { var collapsed = CollapseToSingleLine(rendered); if (collapsed.Length > 250) { // AttributeReference 同样受 255 长度限制,用同等 MText 挂载并隐去原文本 var newMt = new MText(); newMt.Contents = ToMTextContents(rendered); newMt.Location = att.Position; newMt.TextHeight = att.Height; newMt.Layer = att.Layer; newMt.ColorIndex = att.ColorIndex; newMt.TextStyleId = att.TextStyleId; newMt.Rotation = att.Rotation; if (!att.OwnerId.IsNull) { var blk = tr.GetObject(att.OwnerId, OpenMode.ForRead) as BlockReference; if (blk != null && !blk.OwnerId.IsNull) { var space = (BlockTableRecord)tr.GetObject(blk.OwnerId, OpenMode.ForWrite); space.AppendEntity(newMt); tr.AddNewlyCreatedDBObject(newMt, true); att.TextString = ""; att.Invisible = true; } else { att.TextString = collapsed.Substring(0, 250); } } else { att.TextString = collapsed.Substring(0, 250); } } else { att.TextString = collapsed; } } } private static string ReplaceStarsInOriginalContents(string originalContents, string renderedPlainText) { if (string.IsNullOrEmpty(originalContents) || string.IsNullOrEmpty(renderedPlainText)) { return ToMTextContents(renderedPlainText); } // 从SimplifyMText处理后的纯文本和渲染后的纯文本中提取*被替换成什么 var plainTemplate = SimplifyMText(originalContents); // 提取每个占位符被替换成的值 var replacements = ExtractPlaceholderReplacements(plainTemplate, renderedPlainText); if (replacements == null || replacements.Count == 0) { string newContents = ToMTextContents(renderedPlainText); if (originalContents.StartsWith("{") && originalContents.Contains(";")) { var match = System.Text.RegularExpressions.Regex.Match(originalContents, @"^(\{\\[^;]+;)"); if (match.Success) { newContents = match.Groups[1].Value + newContents + "}"; } } return newContents; } // 在原始MText内容中直接替换*占位符 var result = new System.Text.StringBuilder(); var replacementIndex = 0; for (var i = 0; i < originalContents.Length;) { var c = originalContents[i]; // 检查是否是占位符* if (c == '*') { // 计算连续的*数量 var starCount = 0; var j = i; while (j < originalContents.Length && originalContents[j] == '*') { starCount++; j++; } if (starCount == 4) { // 保留**** result.Append("****"); i += 4; } else { // 替换每个* for (var k = 0; k < starCount; k++) { if (replacementIndex < replacements.Count) { result.Append(replacements[replacementIndex]); replacementIndex++; } else { result.Append('*'); } } i += starCount; } } else { result.Append(c); i++; } } return result.ToString(); } private static List ExtractPlaceholderReplacements(string plainTemplate, string renderedText) { var replacements = new List(); // 将两个文本按相同方式处理 var template = (plainTemplate ?? string.Empty) .Replace("\r\n", "\n") .Replace("\r", "\n"); var rendered = (renderedText ?? string.Empty) .Replace("\r\n", "\n") .Replace("\r", "\n"); var ti = 0; // template index var ri = 0; // rendered index while (ti < template.Length && ri < rendered.Length) { var tc = template[ti]; if (tc == '*') { // 检查是否是**** var starCount = 0; var j = ti; while (j < template.Length && template[j] == '*') { starCount++; j++; } if (starCount == 4) { // ****保持不变,rendered中也应该有**** ti += 4; if (ri + 4 <= rendered.Length && rendered.Substring(ri, 4) == "****") { ri += 4; } } else { // 提取替换内容 int nextTi = ti + starCount; string nextLiteral = ""; while (nextTi < template.Length && template[nextTi] != '*') { nextLiteral += template[nextTi]; nextTi++; } if (string.IsNullOrEmpty(nextLiteral)) { // 如果后面没有文字了,那么直接把 rendered 中剩下的所有文字都给第一颗 * string rep = rendered.Substring(ri); for (int k = 0; k < starCount; k++) { replacements.Add(k == 0 ? rep : ""); } ri = rendered.Length; ti += starCount; } else { // 在 rendered 中寻找 nextLiteral int foundIndex = rendered.IndexOf(nextLiteral, ri); if (foundIndex >= 0) { string rep = rendered.Substring(ri, foundIndex - ri); for (int k = 0; k < starCount; k++) { replacements.Add(k == 0 ? rep : ""); } ri = foundIndex; ti += starCount; } else { // 找不到说明因为换行、标点清理或重编号导致了较大差异,直接回退整段格式 return new List(); } } } } else { if (tc == rendered[ri]) { ti++; ri++; } else { // 不匹配说明有由于处理(如空行、或者清理了符号)导致文字不一致 // 直接返回以使用完全的 ToMTextContents(rendered) 包含 MText 的原字体格式 return new List(); } } } return replacements; } 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); } /// /// 确保特性分类文字周围有框体,如果没有则创建 /// private static void EnsureFeatureCategoryBox(Transaction tr, Database db, BlockTableRecord space, List spaceEntList, ObjectId textEntityId, Action logAction) { // 获取文字实体的最新范围 Extents3d textExt; try { var textEnt = tr.GetObject(textEntityId, OpenMode.ForRead, false) as Entity; if (textEnt == null || textEnt.IsErased) return; // 针对 MText,使用实际文字范围而非控制框范围 if (textEnt is MText mt) { // MText 的 Location 是插入点,根据 Attachment 不同位置不同 // 使用 ActualWidth 和 ActualHeight 获取实际文字尺寸 var loc = mt.Location; var actualW = mt.ActualWidth; var actualH = mt.ActualHeight; // 根据 Attachment 计算实际边界 double minX, maxX, minY, maxY; switch (mt.Attachment) { case AttachmentPoint.TopLeft: minX = loc.X; maxX = loc.X + actualW; minY = loc.Y - actualH; maxY = loc.Y; break; case AttachmentPoint.TopCenter: minX = loc.X - actualW / 2; maxX = loc.X + actualW / 2; minY = loc.Y - actualH; maxY = loc.Y; break; case AttachmentPoint.TopRight: minX = loc.X - actualW; maxX = loc.X; minY = loc.Y - actualH; maxY = loc.Y; break; case AttachmentPoint.MiddleLeft: minX = loc.X; maxX = loc.X + actualW; minY = loc.Y - actualH / 2; maxY = loc.Y + actualH / 2; break; case AttachmentPoint.MiddleCenter: minX = loc.X - actualW / 2; maxX = loc.X + actualW / 2; minY = loc.Y - actualH / 2; maxY = loc.Y + actualH / 2; break; case AttachmentPoint.MiddleRight: minX = loc.X - actualW; maxX = loc.X; minY = loc.Y - actualH / 2; maxY = loc.Y + actualH / 2; break; case AttachmentPoint.BottomLeft: minX = loc.X; maxX = loc.X + actualW; minY = loc.Y; maxY = loc.Y + actualH; break; case AttachmentPoint.BottomCenter: minX = loc.X - actualW / 2; maxX = loc.X + actualW / 2; minY = loc.Y; maxY = loc.Y + actualH; break; case AttachmentPoint.BottomRight: minX = loc.X - actualW; maxX = loc.X; minY = loc.Y; maxY = loc.Y + actualH; break; default: // 兜底使用 GeometricExtents textExt = textEnt.GeometricExtents; goto afterExtents; } textExt = new Extents3d(new Point3d(minX, minY, 0), new Point3d(maxX, maxY, 0)); logAction?.Invoke($"[DEBUG] MText: Loc=({loc.X:F1},{loc.Y:F1}), W={actualW:F1}, H={actualH:F1}, Attach={mt.Attachment}"); logAction?.Invoke($"[DEBUG] 计算范围: ({minX:F1},{minY:F1})->({maxX:F1},{maxY:F1})"); } else { textExt = textEnt.GeometricExtents; } afterExtents:; logAction?.Invoke($"[DEBUG] 最终范围: Min=({textExt.MinPoint.X:F1},{textExt.MinPoint.Y:F1}), Max=({textExt.MaxPoint.X:F1},{textExt.MaxPoint.Y:F1})"); } catch (System.Exception ex) { logAction?.Invoke($"[DEBUG] 获取范围失败: {ex.Message}"); return; } // 检查是否已经有包围该文字的闭合 Polyline(大小匹配) bool hasBox = false; var textWidth = textExt.MaxPoint.X - textExt.MinPoint.X; var textHeight = textExt.MaxPoint.Y - textExt.MinPoint.Y; // 框体最大允许尺寸 = 文字尺寸 + 最大 padding (5mm) var maxBoxWidth = textWidth + 10.0; // 两边各5mm var maxBoxHeight = textHeight + 10.0; // 上下各5mm foreach (var ent in spaceEntList) { if (ent is Polyline pl && pl.Closed && !pl.IsErased) { try { var ex = pl.GeometricExtents; var boxWidth = ex.MaxPoint.X - ex.MinPoint.X; var boxHeight = ex.MaxPoint.Y - ex.MinPoint.Y; // 检查 Polyline 是否包围文字(有 0.1 的容差) // 并且大小接近文字大小(不能太大,避免误判表格边框) if (ex.MinPoint.X <= textExt.MinPoint.X - 0.1 && ex.MaxPoint.X >= textExt.MaxPoint.X + 0.1 && ex.MinPoint.Y <= textExt.MinPoint.Y - 0.1 && ex.MaxPoint.Y >= textExt.MaxPoint.Y + 0.1 && boxWidth <= maxBoxWidth && boxHeight <= maxBoxHeight) { hasBox = true; logAction?.Invoke($"[DEBUG] 已找到匹配框体: 框体尺寸({boxWidth:F1}x{boxHeight:F1}), 文字尺寸({textWidth:F1}x{textHeight:F1}), 图层={pl.Layer}"); // 将框体移动到安全图层,防止被 RemoveTemplateOriginalDrawing 删除 try { var plWrite = (Polyline)tr.GetObject(pl.ObjectId, OpenMode.ForWrite, false); var layerTbl = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead); string safeLayer = "0"; if (layerTbl.Has("TEXT")) safeLayer = "TEXT"; else if (layerTbl.Has("文字")) safeLayer = "文字"; plWrite.Layer = safeLayer; plWrite.ColorIndex = 7; // 设置为白色 logAction?.Invoke($"[DEBUG] 已将框体移动到安全图层: {safeLayer}, 颜色已设置为白色"); // 标记此框体,便于后续识别 var groupId = Guid.NewGuid().ToString("N"); SetFeatureCategoryData(tr, pl.ObjectId, string.Empty, groupId, "FallbackBox"); } catch { } break; } } catch { } } } // 如果没有框体,则创建一个 if (!hasBox) { double paddingH = 1.5; double paddingV = 1.5; var boxMinX = textExt.MinPoint.X - paddingH; var boxMaxX = textExt.MaxPoint.X + paddingH; var boxMinY = textExt.MinPoint.Y - paddingV; var boxMaxY = textExt.MaxPoint.Y + paddingV; logAction?.Invoke($"[DEBUG] 创建框体: ({boxMinX:F1},{boxMinY:F1})->({boxMaxX:F1},{boxMaxY:F1})"); var poly = new Polyline(); poly.AddVertexAt(0, new Point2d(boxMinX, boxMinY), 0, 0, 0); poly.AddVertexAt(1, new Point2d(boxMaxX, boxMinY), 0, 0, 0); poly.AddVertexAt(2, new Point2d(boxMaxX, boxMaxY), 0, 0, 0); poly.AddVertexAt(3, new Point2d(boxMinX, boxMaxY), 0, 0, 0); poly.Closed = true; poly.ColorIndex = 7; // White try { var layerTbl = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead); string targetLayer = "0"; if (layerTbl.Has("TEXT")) targetLayer = "TEXT"; else if (layerTbl.Has("文字")) targetLayer = "文字"; poly.Layer = targetLayer; } catch { } space.AppendEntity(poly); tr.AddNewlyCreatedDBObject(poly, true); var groupId = Guid.NewGuid().ToString("N"); try { SetFeatureCategoryData(tr, poly.ObjectId, string.Empty, groupId, "FallbackBox"); } catch { } } } public static int ApplyFeatureCategory(CadContext ctx, string layoutName, bool scanModelSpace, ParamBag bag, Action logAction = null) { if (ctx == null || bag == null) { return 0; } var category = bag.GetString("FeatureCategory"); // Logic: Is "一般件" -> show blank. // Is "关键件"/"重要件" -> show text. var textToShow = category; if (string.IsNullOrWhiteSpace(textToShow) || string.Equals(textToShow, "一般件", StringComparison.OrdinalIgnoreCase)) { textToShow = string.Empty; // Show blank } logAction?.Invoke($"[DEBUG] ApplyFeatureCategory: category='{category}', textToShow='{textToShow}'"); var db = ctx.Database; var tr = ctx.Transaction; var space = GetTargetSpace(tr, db, layoutName, scanModelSpace); if (space == null) return 0; var ids = space.Cast().ToArray(); logAction?.Invoke($"[DEBUG] Space实体数量: {ids.Length}"); int count = 0; // 0) If we previously created fallback feature-category texts, update or remove them (prevents duplication). var fallbackGroupIdsToRemove = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var id in ids) { FeatureCategoryData fcData = null; try { fcData = GetFeatureCategoryData(tr, id); } catch { } if (fcData == null || string.IsNullOrWhiteSpace(fcData.Role)) { continue; } if (string.Equals(fcData.Role, "FallbackText", StringComparison.OrdinalIgnoreCase)) { var ent = tr.GetObject(id, OpenMode.ForWrite, false) as Entity; if (ent == null) continue; if (string.IsNullOrWhiteSpace(textToShow)) { if (!ent.IsErased) ent.Erase(); if (!string.IsNullOrWhiteSpace(fcData.GroupId)) fallbackGroupIdsToRemove.Add(fcData.GroupId); continue; } if (ent is MText mt) { mt.Contents = @"{\fSimSun|b0|i0|c134|p2;" + ToMTextContents(textToShow) + "}"; count++; } } else if (string.Equals(fcData.Role, "FallbackBox", StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrWhiteSpace(textToShow) && !string.IsNullOrWhiteSpace(fcData.GroupId)) { fallbackGroupIdsToRemove.Add(fcData.GroupId); } } } if (fallbackGroupIdsToRemove.Count > 0) { foreach (var id in ids) { FeatureCategoryData fcData = null; try { fcData = GetFeatureCategoryData(tr, id); } catch { } if (fcData == null || string.IsNullOrWhiteSpace(fcData.GroupId)) continue; if (!fallbackGroupIdsToRemove.Contains(fcData.GroupId)) continue; var ent = tr.GetObject(id, OpenMode.ForWrite, false) as Entity; if (ent == null) continue; if (!ent.IsErased) ent.Erase(); } } // 1) Update previously replaced placeholder entities using stored original template. // 收集被替换的占位符文字实体ID,以便后续检查框体 var replacedPlaceholderIds = new List(); foreach (var id in ids) { var ent = tr.GetObject(id, OpenMode.ForWrite, false) as Entity; if (ent == null) continue; if (ent is DBText t) { FeatureCategoryData fcData = null; try { fcData = GetFeatureCategoryData(tr, id); } catch { } if (fcData != null && string.Equals(fcData.Role, "Placeholder", StringComparison.OrdinalIgnoreCase)) { var original = fcData.OriginalTemplate ?? string.Empty; t.TextString = original.Contains("******") ? original.Replace("******", textToShow) : textToShow; count++; replacedPlaceholderIds.Add(id); continue; } if (t.TextString != null && t.TextString.Contains("******")) { var original = t.TextString; t.TextString = original.Replace("******", textToShow); try { SetFeatureCategoryData(tr, id, original, null, "Placeholder"); } catch { } count++; replacedPlaceholderIds.Add(id); } } else if (ent is MText mt) { FeatureCategoryData fcData = null; try { fcData = GetFeatureCategoryData(tr, id); } catch { } if (fcData != null && string.Equals(fcData.Role, "Placeholder", StringComparison.OrdinalIgnoreCase)) { var original = fcData.OriginalTemplate ?? string.Empty; mt.Contents = original.Contains("******") ? original.Replace("******", textToShow) : textToShow; count++; replacedPlaceholderIds.Add(id); continue; } if (mt.Contents != null && mt.Contents.Contains("******")) { var original = mt.Contents; mt.Contents = original.Replace("******", textToShow); try { SetFeatureCategoryData(tr, id, original, null, "Placeholder"); } catch { } count++; replacedPlaceholderIds.Add(id); } } else 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; FeatureCategoryData fcData = null; try { fcData = GetFeatureCategoryData(tr, attId); } catch { } if (fcData != null && string.Equals(fcData.Role, "Placeholder", StringComparison.OrdinalIgnoreCase)) { var original = fcData.OriginalTemplate ?? string.Empty; att.TextString = original.Contains("******") ? original.Replace("******", textToShow) : textToShow; count++; replacedPlaceholderIds.Add(attId); continue; } if (att.TextString != null && att.TextString.Contains("******")) { var original = att.TextString; att.TextString = original.Replace("******", textToShow); try { SetFeatureCategoryData(tr, attId, original, null, "Placeholder"); } catch { } count++; replacedPlaceholderIds.Add(attId); } } } } logAction?.Invoke($"[DEBUG] 替换占位符数量: {replacedPlaceholderIds.Count}, count: {count}"); // 2) 如果有被替换的占位符且内容不为空(非"一般件"),检查并确保每个占位符都有框体 if (replacedPlaceholderIds.Count > 0 && !string.IsNullOrWhiteSpace(textToShow)) { logAction?.Invoke($"[DEBUG] 进入框体检查逻辑,textToShow='{textToShow}'"); // 获取实体列表用于框体检查 var spaceEntListForBox = ids .Select(x => { try { return tr.GetObject(x, OpenMode.ForRead, false) as Entity; } catch { return null; } }) .Where(e => e != null && !e.IsErased) .ToList(); foreach (var placeholderId in replacedPlaceholderIds) { logAction?.Invoke($"[DEBUG] 调用 EnsureFeatureCategoryBox, placeholderId={placeholderId}"); EnsureFeatureCategoryBox(tr, db, space, spaceEntListForBox, placeholderId, logAction); } } // If the category is blank ("一般件"), also try to remove previously inserted bottom-right category texts/boxes (legacy, unmarked). if (string.IsNullOrWhiteSpace(textToShow)) { try { var spaceEntList = ids .Select(x => { try { return tr.GetObject(x, OpenMode.ForRead, false) as Entity; } catch { return null; } }) .Where(e => e != null && !e.IsErased) .ToList(); var frame = ComputeWhiteFrameExtentsFromEntities(tr, spaceEntList); if (frame.HasValue) { var f = frame.Value; var known = new HashSet(StringComparer.OrdinalIgnoreCase) { "关键件", "重要件" }; var regionMinX = f.MaxPoint.X - 220.0; var regionMaxX = f.MaxPoint.X + 5.0; var regionMinY = f.MinPoint.Y - 5.0; var regionMaxY = f.MinPoint.Y + 140.0; var candidates = new List>(); foreach (var e in spaceEntList) { try { var ex = e.GeometricExtents; if (ex.MaxPoint.X < regionMinX || ex.MinPoint.X > regionMaxX || ex.MaxPoint.Y < regionMinY || ex.MinPoint.Y > regionMaxY) { continue; } if (e is DBText dt) { var txt = (dt.TextString ?? string.Empty).Trim(); if (known.Contains(txt)) candidates.Add(Tuple.Create(e.ObjectId, ex)); } else if (e is MText mtt) { var txt = ExtractMTextPlainText(mtt.Contents); if (known.Contains(txt)) candidates.Add(Tuple.Create(e.ObjectId, ex)); } } catch { } } if (candidates.Count > 0) { foreach (var c in candidates) { try { var w = tr.GetObject(c.Item1, OpenMode.ForWrite, false) as Entity; if (w != null && !w.IsErased) w.Erase(); } catch { } } foreach (var e in spaceEntList) { if (e is Polyline pl) { try { if (!pl.Closed) continue; var ex = pl.GeometricExtents; var matchesAny = candidates.Any(c => ex.MinPoint.X <= c.Item2.MinPoint.X - 0.1 && ex.MaxPoint.X >= c.Item2.MaxPoint.X + 0.1 && ex.MinPoint.Y <= c.Item2.MinPoint.Y - 0.1 && ex.MaxPoint.Y >= c.Item2.MaxPoint.Y + 0.1); if (matchesAny) { var w = tr.GetObject(pl.ObjectId, OpenMode.ForWrite, false) as Entity; if (w != null && !w.IsErased) w.Erase(); } } catch { } } } } } } catch { // ignore } return count; } // Fallback: If no placeholder found AND we have text to show (e.g. "关键件"), create it manually. if (count == 0 && !string.IsNullOrWhiteSpace(textToShow)) { // Try to find white frame to determine position var spaceEntList = space.Cast() .Select(id => { try { return tr.GetObject(id, OpenMode.ForRead, false) as Entity; } catch { return null; } }) .Where(e => e != null && !e.IsErased) .ToList(); var frame = ComputeWhiteFrameExtentsFromEntities(tr, spaceEntList); if (frame.HasValue) { var f = frame.Value; // 1) Heuristic: update/cleanup existing (older) feature-category texts in the bottom-right area var known = new HashSet(StringComparer.OrdinalIgnoreCase) { "关键件", "重要件" }; var regionMinX = f.MaxPoint.X - 220.0; var regionMaxX = f.MaxPoint.X + 5.0; var regionMinY = f.MinPoint.Y - 5.0; var regionMaxY = f.MinPoint.Y + 140.0; var candidates = new List>(); foreach (var ent in spaceEntList) { if (ent == null) continue; try { var ex = ent.GeometricExtents; if (ex.MaxPoint.X < regionMinX || ex.MinPoint.X > regionMaxX || ex.MaxPoint.Y < regionMinY || ex.MinPoint.Y > regionMaxY) { continue; } if (ent is DBText dt) { var txt = (dt.TextString ?? string.Empty).Trim(); if (known.Contains(txt)) { candidates.Add(Tuple.Create(ent.ObjectId, ex, txt)); } } else if (ent is MText mtt) { var txt = ExtractMTextPlainText(mtt.Contents); if (known.Contains(txt)) { candidates.Add(Tuple.Create(ent.ObjectId, ex, txt)); } } } catch { // ignore } } if (candidates.Count > 0) { // Keep the right-most (then top-most) one, erase the rest. var keep = candidates .OrderByDescending(c => c.Item2.MaxPoint.X) .ThenByDescending(c => c.Item2.MaxPoint.Y) .First(); var keepId = keep.Item1; var keepExt = keep.Item2; foreach (var c in candidates) { if (c.Item1 == keepId) continue; try { var e = tr.GetObject(c.Item1, OpenMode.ForWrite, false) as Entity; if (e != null && !e.IsErased) e.Erase(); } catch { } } // Remove boxes that belong to removed texts (keep the one containing kept text) foreach (var ent in spaceEntList) { if (ent is Polyline pl) { try { if (!pl.Closed) continue; var ex = pl.GeometricExtents; var containsKept = ex.MinPoint.X <= keepExt.MinPoint.X - 0.1 && ex.MaxPoint.X >= keepExt.MaxPoint.X + 0.1 && ex.MinPoint.Y <= keepExt.MinPoint.Y - 0.1 && ex.MaxPoint.Y >= keepExt.MaxPoint.Y + 0.1; if (containsKept) continue; var matchesAnyRemoved = candidates.Any(c => c.Item1 != keepId && ex.MinPoint.X <= c.Item2.MinPoint.X - 0.1 && ex.MaxPoint.X >= c.Item2.MaxPoint.X + 0.1 && ex.MinPoint.Y <= c.Item2.MinPoint.Y - 0.1 && ex.MaxPoint.Y >= c.Item2.MaxPoint.Y + 0.1); if (matchesAnyRemoved) { var w = tr.GetObject(pl.ObjectId, OpenMode.ForWrite, false) as Entity; if (w != null && !w.IsErased) w.Erase(); } } catch { } } } // Update kept text and mark it so next run won't fall back. try { var e = tr.GetObject(keepId, OpenMode.ForWrite, false) as Entity; if (e is DBText dt) { dt.TextString = textToShow; SetFeatureCategoryData(tr, keepId, "******", null, "Placeholder"); // 检查是否有包围该文字的框体,如果没有则创建 EnsureFeatureCategoryBox(tr, db, space, spaceEntList, keepId, logAction); return 1; } if (e is MText mtt) { // Preserve its formatting if any; otherwise use our default. var plain = ExtractMTextPlainText(mtt.Contents); if (string.Equals(plain, mtt.Contents, StringComparison.Ordinal)) { mtt.Contents = @"{\fSimSun|b0|i0|c134|p2;" + ToMTextContents(textToShow) + "}"; } else { mtt.Contents = mtt.Contents.Replace(plain, textToShow); } SetFeatureCategoryData(tr, keepId, "******", null, "Placeholder"); // 检查是否有包围该文字的框体,如果没有则创建 EnsureFeatureCategoryBox(tr, db, space, spaceEntList, keepId, logAction); return 1; } } catch { // ignore } } // Strategy: // 1. Align Right: Use f.MaxX as the right boundary anchor. // 2. Find Table Top: Look for horizontal lines in the bottom-right quadrant to find the top of the title block. // If found, place text above that line. If not, use a safe default from bottom. var searchRegionMinX = f.MaxPoint.X - 200.0; // Assume title block width < 200 var searchRegionMinY = f.MinPoint.Y; var searchRegionMaxY = f.MinPoint.Y + 80.0; // Reduce to avoid picking up drawing lines (Title block usually < 80mm) double tableTopY = f.MinPoint.Y + 60.0; // Default fallback (e.g. 60mm from bottom) // Scan lines to find the highest horizontal line in this region which represents the table top var candidatesY = new List(); foreach (var ent in spaceEntList) { if (ent is Line ln) { if (ln.StartPoint.X > searchRegionMinX && ln.EndPoint.X > searchRegionMinX && ln.StartPoint.Y > searchRegionMinY && ln.StartPoint.Y < searchRegionMaxY && Math.Abs(ln.StartPoint.Y - ln.EndPoint.Y) < 1.0) // Horizontal { candidatesY.Add(ln.StartPoint.Y); } } else if (ent is Polyline pl) { for (int i = 0; i < pl.NumberOfVertices; i++) { var pt = pl.GetPoint3dAt(i); if (pt.X > searchRegionMinX && pt.Y > searchRegionMinY && pt.Y < searchRegionMaxY) { candidatesY.Add(pt.Y); } } } } if (candidatesY.Count > 0) { // The "Title Block" usually consists of many lines. // We want the top-most line of the block, but "below" the drawing area. // Let's take the max Y found in that bottom-right corner zone. tableTopY = candidatesY.Max(); } // Position: // X: f.MaxX - 5 (margin) - (TextWidth/2 treated by Attachment) => Let's use TopRight or MiddleRight alignment // Anchor at f.MaxX - 2.0 (small margin from right border line) // Y: tableTopY + 2.0 (small gap above the table line) // Anchor BottomRight or BottomLeft? // User image shows text centered in a box-like area ABOVE the table rows. // Let's align BottomRight to (MaxX - margin, TableTop + margin) var insertX = f.MaxPoint.X - 5.0; var insertY = tableTopY + 2.0; var insertPoint = new Point3d(insertX, insertY, 0); var groupId = Guid.NewGuid().ToString("N"); var textHeight = 3.2; // 9号字 (interpreted as 9pt approx 3.2mm) var mt = new MText(); // Set font to SimSun (宋体) mt.Contents = @"{\fSimSun|b0|i0|c134|p2;" + ToMTextContents(textToShow) + "}"; mt.Location = insertPoint; mt.TextHeight = textHeight; mt.Attachment = AttachmentPoint.BottomRight; // Anchor at bottom-right mt.Width = 0; // No wrap width, auto mt.ColorIndex = 7; // White // Create White Border // Estimate dimensions: Chinese char width approx equals height // Adding padding to visually center the text in the box double charWidthFactor = 1.0; double estimatedTextWidth = textToShow.Length * textHeight * charWidthFactor; double paddingH = 1.5; // Horizontal padding double paddingV = 1.5; // Vertical padding // Since Attachment is BottomRight: // Text occupies roughly: [X - Width, X] x [Y, Y + Height] // We draw box around this area with padding var boxMinX = insertX - estimatedTextWidth - paddingH; var boxMaxX = insertX + paddingH; var boxMinY = insertY - paddingV; var boxMaxY = insertY + textHeight + paddingV; var poly = new Polyline(); poly.AddVertexAt(0, new Point2d(boxMinX, boxMinY), 0, 0, 0); poly.AddVertexAt(1, new Point2d(boxMaxX, boxMinY), 0, 0, 0); poly.AddVertexAt(2, new Point2d(boxMaxX, boxMaxY), 0, 0, 0); poly.AddVertexAt(3, new Point2d(boxMinX, boxMaxY), 0, 0, 0); poly.Closed = true; poly.ColorIndex = 7; // White (Index 7 is White/Black depending on bg, usually White for plot) try { var layerTbl = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead); string targetLayer = "0"; if (layerTbl.Has("TEXT")) targetLayer = "TEXT"; else if (layerTbl.Has("文字")) targetLayer = "文字"; // Assign layer to both MText and Box mt.Layer = targetLayer; poly.Layer = targetLayer; } catch { } space.AppendEntity(poly); tr.AddNewlyCreatedDBObject(poly, true); try { SetFeatureCategoryData(tr, poly.ObjectId, string.Empty, groupId, "FallbackBox"); } catch { } space.AppendEntity(mt); tr.AddNewlyCreatedDBObject(mt, true); try { SetFeatureCategoryData(tr, mt.ObjectId, string.Empty, groupId, "FallbackText"); } catch { } count++; } } return count; } 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; } public sealed class RemoveOriginalDrawingResult { public int CaxaLayerErased { get; set; } public int DimensionLayerErased { get; set; } public int DimensionLayerKept { get; set; } public int OuterFrameErased { get; set; } public Point3d OriginalCenter { get; set; } public Extents3d? OriginalExtents { get; set; } public Extents3d? WhiteFrameExtents { get; set; } } private static readonly Regex DimensionPlaceholderRegex = new Regex( @"(%%c|[Φφ∅Ø]|\\U\+03A6|\\U\+2205|\\U\+00D8)\s*\*", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// /// 删除模板中原有的图纸图形,保留右上角区域的内容(如粗糙度标注)。 /// /// CAD上下文 /// 右上角保留区域阈值,0.70表示X和Y都超过70%的位置 /// 删除结果,包含原图形中心点位置 public static RemoveOriginalDrawingResult RemoveTemplateOriginalDrawing(CadContext ctx, double topRightThreshold = 0.70) { if (ctx == null) { throw new ArgumentNullException(nameof(ctx)); } var result = new RemoveOriginalDrawingResult(); var db = ctx.Database; var tr = ctx.Transaction; var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); var ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); var allEntities = ms.Cast() .Select(id => tr.GetObject(id, OpenMode.ForRead, false) as Entity) .Where(e => e != null) .ToList(); var layoutExtents = ComputeLayoutExtents(allEntities); var caxaExtents = ComputeCaxaLayerExtents(tr, allEntities); // Calculate white frame extents var whiteFrameExtents = ComputeWhiteFrameExtentsFromEntities(tr, allEntities) ?? ComputeLayoutExtents(allEntities); result.WhiteFrameExtents = whiteFrameExtents; var titleBlockExtents = whiteFrameExtents.HasValue ? ComputeTitleBlockExtentsFromEntities(tr, allEntities, whiteFrameExtents.Value) : (Extents3d?)null; if (caxaExtents.HasValue) { result.OriginalExtents = caxaExtents; result.OriginalCenter = new Point3d( (caxaExtents.Value.MinPoint.X + caxaExtents.Value.MaxPoint.X) / 2, (caxaExtents.Value.MinPoint.Y + caxaExtents.Value.MaxPoint.Y) / 2, 0); } else if (whiteFrameExtents.HasValue) { // Fallback to center of the upper half of the white frame if no CAXA layer found var frame = whiteFrameExtents.Value; var centerX = (frame.MinPoint.X + frame.MaxPoint.X) / 2.0; var height = frame.MaxPoint.Y - frame.MinPoint.Y; // Default to 70% height (center of upper area approximately) or just center? // User said "upper half center". Top half is [0.5, 1.0], center is 0.75. var centerY = frame.MinPoint.Y + height * 0.75; result.OriginalCenter = new Point3d(centerX, centerY, 0); } // 使用CAXA图层范围作为图形区域,用于判断右上角 var graphicExtents = caxaExtents ?? layoutExtents; // 先计算红色线框的范围 var redFrameExtents = ComputeRedFrameExtents(tr, allEntities); foreach (var ent in allEntities) { if (ent.IsErased) { continue; } var layerName = GetLayerName(tr, ent.LayerId); // 删除红色外框线(红色的Line/Polyline,且在红色框的边界上) if (IsOuterFrameEntity(ent, redFrameExtents, tr)) { // [Modified] Skip erasing outer frame to keep the template's original frame and title block // Explicitly continue to prevent it being caught by subsequent checks (e.g. CAXA layer) continue; /* Original deletion logic: var entForWrite = tr.GetObject(ent.ObjectId, OpenMode.ForWrite) as Entity; if (entForWrite != null) { entForWrite.Erase(true); result.OuterFrameErased++; } continue; */ } if (IsWithinTitleBlock(ent, titleBlockExtents)) { continue; } if (IsCaxaLayer(layerName)) { var entForWrite = tr.GetObject(ent.ObjectId, OpenMode.ForWrite) as Entity; if (entForWrite != null) { entForWrite.Erase(true); result.CaxaLayerErased++; } continue; } if (IsDimensionLayer(layerName) || IsDimensionEntity(ent)) { if (IsWithinTitleBlock(ent, titleBlockExtents)) { result.DimensionLayerKept++; continue; } // 基于CAXA图形区域判断是否在右上角 if (graphicExtents.HasValue && IsInTopRightCorner(ent, graphicExtents.Value, topRightThreshold)) { result.DimensionLayerKept++; continue; } var entForWrite = tr.GetObject(ent.ObjectId, OpenMode.ForWrite) as Entity; if (entForWrite != null) { entForWrite.Erase(true); result.DimensionLayerErased++; } } } try { // Keep the template's white frame but enforce the desired lineweight. ApplyWhiteFrameLineWeight(ctx, LineWeight.LineWeight015); } catch { // ignore } return result; } private static int ApplyWhiteFrameLineWeight(CadContext ctx, LineWeight lineWeight) { if (ctx == null) { return 0; } var db = ctx.Database; var tr = ctx.Transaction; var frameExtents = ComputeWhiteFrameExtents(ctx); if (!frameExtents.HasValue) { return 0; } var frame = frameExtents.Value; var w = frame.MaxPoint.X - frame.MinPoint.X; var h = frame.MaxPoint.Y - frame.MinPoint.Y; var tol = Math.Max(w, h) * 0.01; // 1% var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); var ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); var updated = 0; foreach (ObjectId id in ms) { var ent = tr.GetObject(id, OpenMode.ForWrite, false) as Entity; if (ent == null || ent.IsErased) { continue; } if (!(ent is Line) && !(ent is Polyline)) { continue; } if (!IsWhiteColor(ent, tr)) { continue; } if (!IsOnFrameBoundary(ent, frame, tol)) { continue; } try { ent.LineWeight = lineWeight; } catch { } try { var layer = tr.GetObject(ent.LayerId, OpenMode.ForWrite) as LayerTableRecord; if (layer != null) { layer.LineWeight = lineWeight; } } catch { // ignore } updated++; } return updated; } private static bool IsOnFrameBoundary(Entity ent, Extents3d frame, double tol) { try { if (ent is Line line) { var sp = line.StartPoint; var ep = line.EndPoint; var minX = Math.Min(sp.X, ep.X); var maxX = Math.Max(sp.X, ep.X); var minY = Math.Min(sp.Y, ep.Y); var maxY = Math.Max(sp.Y, ep.Y); var isHorizontal = Math.Abs(sp.Y - ep.Y) <= tol; if (isHorizontal) { var y = (sp.Y + ep.Y) / 2.0; var onTop = Math.Abs(y - frame.MaxPoint.Y) <= tol; var onBottom = Math.Abs(y - frame.MinPoint.Y) <= tol; if ((onTop || onBottom) && minX <= frame.MinPoint.X + tol && maxX >= frame.MaxPoint.X - tol) { return true; } } var isVertical = Math.Abs(sp.X - ep.X) <= tol; if (isVertical) { var x = (sp.X + ep.X) / 2.0; var onLeft = Math.Abs(x - frame.MinPoint.X) <= tol; var onRight = Math.Abs(x - frame.MaxPoint.X) <= tol; if ((onLeft || onRight) && minY <= frame.MinPoint.Y + tol && maxY >= frame.MaxPoint.Y - tol) { return true; } } return false; } var ext = ent.GeometricExtents; var matchX = Math.Abs(ext.MinPoint.X - frame.MinPoint.X) <= tol && Math.Abs(ext.MaxPoint.X - frame.MaxPoint.X) <= tol; var matchY = Math.Abs(ext.MinPoint.Y - frame.MinPoint.Y) <= tol && Math.Abs(ext.MaxPoint.Y - frame.MaxPoint.Y) <= tol; return matchX && matchY; } catch { return false; } } /// /// 删除模板中残留的“尺寸占位符”文本(例如 (Φ*)),避免与新生成的真实尺寸标注混淆。 /// /// /// 仅清理文本类实体(DBText/MText/块属性),不影响真正的 Dimension 实体。 /// public static int RemoveTemplateDimensionPlaceholderTexts( CadContext ctx, Extents3d? graphicExtents = null, double topRightThreshold = 0.70, bool scanBlockDefinitions = false) { if (ctx == null) { throw new ArgumentNullException(nameof(ctx)); } var db = ctx.Database; var tr = ctx.Transaction; var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); var ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); var allEntities = ms.Cast() .Select(id => tr.GetObject(id, OpenMode.ForRead, false) as Entity) .Where(e => e != null) .ToList(); var targetExtents = graphicExtents ?? ComputeWhiteFrameExtentsFromEntities(tr, allEntities) ?? ComputeLayoutExtents(allEntities); var titleBlockExtents = targetExtents.HasValue ? ComputeTitleBlockExtentsFromEntities(tr, allEntities, targetExtents.Value) : (Extents3d?)null; var removed = 0; foreach (ObjectId id in ms) { var ent = tr.GetObject(id, OpenMode.ForWrite, false) as Entity; if (ent == null || ent.IsErased) { continue; } if (targetExtents.HasValue && IsInTopRightCorner(ent, targetExtents.Value, topRightThreshold)) { continue; } if (IsWithinTitleBlock(ent, titleBlockExtents)) { continue; } if (!IsWithin(ent, targetExtents)) { continue; } if (ent is DBText t) { if (LooksLikeDimensionPlaceholder(t.TextString)) { ent.Erase(true); removed++; } continue; } if (ent is MText mt) { var plain = SimplifyMText(mt.Contents); if (LooksLikeDimensionPlaceholder(plain) || LooksLikeDimensionPlaceholder(mt.Contents)) { ent.Erase(true); removed++; } continue; } if (ent is BlockReference br) { removed += RemovePlaceholderAttributes(tr, br, targetExtents, topRightThreshold); // Keep default behavior conservative: do not edit block definitions unless explicitly enabled. if (scanBlockDefinitions && IsWithin(br, targetExtents)) { removed += RemovePlaceholderTextsInBlockDefinition(tr, br.BlockTableRecord, new HashSet()); } } } return removed; } private static int RemovePlaceholderAttributes(Transaction tr, BlockReference br, Extents3d? targetExtents, double topRightThreshold) { if (tr == null || br == null) { return 0; } var removed = 0; foreach (ObjectId attId in br.AttributeCollection) { var att = tr.GetObject(attId, OpenMode.ForWrite, false) as AttributeReference; if (att == null || att.IsErased) { continue; } if (targetExtents.HasValue && IsInTopRightCorner(att, targetExtents.Value, topRightThreshold)) { continue; } if (!IsWithin(att, targetExtents)) { continue; } if (LooksLikeDimensionPlaceholder(att.TextString)) { try { att.Erase(true); removed++; } catch { // ignore } } } return removed; } private static int RemovePlaceholderTextsInBlockDefinition(Transaction tr, ObjectId blockId, HashSet visited) { if (tr == null || blockId.IsNull) { return 0; } if (visited == null) { visited = new HashSet(); } if (visited.Contains(blockId)) { return 0; } visited.Add(blockId); var btr = tr.GetObject(blockId, OpenMode.ForWrite, false) as BlockTableRecord; if (btr == null) { return 0; } var removed = 0; foreach (ObjectId childId in btr) { var child = tr.GetObject(childId, OpenMode.ForWrite, false) as Entity; if (child == null || child.IsErased) { continue; } if (child is DBText t) { if (LooksLikeDimensionPlaceholder(t.TextString)) { child.Erase(true); removed++; } continue; } if (child is MText mt) { var plain = SimplifyMText(mt.Contents); if (LooksLikeDimensionPlaceholder(plain) || LooksLikeDimensionPlaceholder(mt.Contents)) { child.Erase(true); removed++; } continue; } if (child is BlockReference br) { removed += RemovePlaceholderAttributes(tr, br, null, 1.0); removed += RemovePlaceholderTextsInBlockDefinition(tr, br.BlockTableRecord, visited); } } return removed; } private static bool LooksLikeDimensionPlaceholder(string raw) { if (string.IsNullOrWhiteSpace(raw)) { return false; } var s = (raw ?? string.Empty).Replace(" ", string.Empty).Replace(" ", string.Empty); if (!s.Contains("*")) { return false; } // Typical placeholder examples: (Φ*), Φ*, %%c*, (\U+03A6*) return DimensionPlaceholderRegex.IsMatch(s); } /// /// 删除白色线框范围内上半部分的所有内容(用于清理模板残留的图形/占位符)。 /// 保留:右上角区域(如粗糙度标注)与下半部分(附注/表格)。 /// /// 保留下半部比例(0.5 表示保留下半部分) /// 右上角保留阈值,0.70表示X和Y都超过70%的位置 public static int RemoveWhiteFrameUpperContent(CadContext ctx, double keepBottomRatio = 0.50, double topRightThreshold = 0.70) { if (ctx == null) { throw new ArgumentNullException(nameof(ctx)); } keepBottomRatio = Math.Max(0.0, Math.Min(1.0, keepBottomRatio)); var db = ctx.Database; var tr = ctx.Transaction; var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); var ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); var allEntities = ms.Cast() .Select(id => tr.GetObject(id, OpenMode.ForRead, false) as Entity) .Where(e => e != null && !e.IsErased) .ToList(); var whiteFrame = ComputeWhiteFrameExtentsFromEntities(tr, allEntities) ?? ComputeLayoutExtents(allEntities); if (!whiteFrame.HasValue) { return 0; } var titleBlockExtents = ComputeTitleBlockExtentsFromEntities(tr, allEntities, whiteFrame.Value); var frame = whiteFrame.Value; var height = frame.MaxPoint.Y - frame.MinPoint.Y; if (height <= 0) { return 0; } var cutY = frame.MinPoint.Y + height * keepBottomRatio; // [Modified] Compute Red Frame Extents to protect it from deletion var redFrameExtents = ComputeRedFrameExtents(tr, allEntities); var removed = 0; foreach (ObjectId id in ms) { var ent = tr.GetObject(id, OpenMode.ForWrite, false) as Entity; if (ent == null || ent.IsErased) { continue; } // Keep white frame boundary itself. if (IsWhiteFrameEntity(ent, frame, tr)) { continue; } // [Modified] Keep Red Outer Frame if (IsOuterFrameEntity(ent, redFrameExtents, tr)) { continue; } if (!IsWithin(ent, frame)) { continue; } // Keep top-right corner content. if (IsInTopRightCorner(ent, frame, topRightThreshold)) { continue; } if (IsWithinTitleBlock(ent, titleBlockExtents)) { continue; } if (!TryGetEntityCenterY(ent, out var centerY)) { continue; } // Keep bottom area (notes/table). if (centerY < cutY) { continue; } try { ent.Erase(true); removed++; } catch { // ignore } } return removed; } private static bool TryGetEntityCenterY(Entity ent, out double centerY) { centerY = 0; if (ent == null) { return false; } try { var ext = ent.GeometricExtents; centerY = (ext.MinPoint.Y + ext.MaxPoint.Y) / 2.0; return true; } catch { return false; } } private static bool IsWithin(Entity ent, Extents3d frame) { if (ent == null) { return false; } try { var ext = ent.GeometricExtents; return Intersects(ext, frame); } catch { return true; } } private static bool IsWhiteFrameEntity(Entity ent, Extents3d frame, Transaction tr) { if (ent == null) { return false; } if (!(ent is Line) && !(ent is Polyline)) { return false; } if (!IsWhiteColor(ent, tr)) { return false; } try { var ext = ent.GeometricExtents; var frameW = frame.MaxPoint.X - frame.MinPoint.X; var frameH = frame.MaxPoint.Y - frame.MinPoint.Y; var tol = Math.Max(frameW, frameH) * 0.01; // 1% // On outer border lines. var nearLeft = Math.Abs(ext.MinPoint.X - frame.MinPoint.X) < tol; var nearRight = Math.Abs(ext.MaxPoint.X - frame.MaxPoint.X) < tol; var nearBottom = Math.Abs(ext.MinPoint.Y - frame.MinPoint.Y) < tol; var nearTop = Math.Abs(ext.MaxPoint.Y - frame.MaxPoint.Y) < tol; // Rough heuristic: if it touches two sides or spans most of a side, treat as frame. if ((nearLeft && nearBottom) || (nearLeft && nearTop) || (nearRight && nearBottom) || (nearRight && nearTop)) { return true; } if (ent is Line) { var spansWidth = (ext.MaxPoint.X - ext.MinPoint.X) > frameW * 0.90; var spansHeight = (ext.MaxPoint.Y - ext.MinPoint.Y) > frameH * 0.90; if ((nearTop || nearBottom) && spansWidth) { return true; } if ((nearLeft || nearRight) && spansHeight) { return true; } } if (ent is Polyline) { var spansWidth = (ext.MaxPoint.X - ext.MinPoint.X) > frameW * 0.90; var spansHeight = (ext.MaxPoint.Y - ext.MinPoint.Y) > frameH * 0.90; if (spansWidth && spansHeight) { return true; } } } catch { return false; } return false; } private static bool IsWithin(Entity ent, Extents3d? targetExtents) { if (ent == null) { return false; } if (!targetExtents.HasValue) { return true; } try { var ext = ent.GeometricExtents; return Intersects(ext, targetExtents.Value); } catch { return true; } } private static bool IsOuterFrameEntity(Entity ent, Extents3d? layoutExtents, Transaction tr = null) { if (ent == null || !layoutExtents.HasValue) { return false; } // 必须是Line或Polyline if (!(ent is Line) && !(ent is Polyline)) { return false; } // 检查颜色是否为红色(ColorIndex=1,或者通过图层颜色) if (!IsRedColor(ent, tr)) { return false; } // 检查是否位于最外围(实体的范围接近或等于整体布局范围) try { var ext = ent.GeometricExtents; var layout = layoutExtents.Value; var layoutWidth = layout.MaxPoint.X - layout.MinPoint.X; var layoutHeight = layout.MaxPoint.Y - layout.MinPoint.Y; // 容差值(放宽到5%) var tolerance = Math.Max(layoutWidth, layoutHeight) * 0.05; // 检查Line是否位于布局边界上 if (ent is Line line) { var startPt = line.StartPoint; var endPt = line.EndPoint; // 检查是否是水平线(上边或下边) var isHorizontal = Math.Abs(startPt.Y - endPt.Y) < tolerance; if (isHorizontal) { // 检查Y坐标是否在布局的上边界或下边界 var isTopEdge = Math.Abs(startPt.Y - layout.MaxPoint.Y) < tolerance; var isBottomEdge = Math.Abs(startPt.Y - layout.MinPoint.Y) < tolerance; if (isTopEdge || isBottomEdge) { // 增加长度判断:防止误删位于边界上的短线(如标题栏边框) // 只有长度超过布局宽度 50% 的线才认为是外框 if (Math.Abs(startPt.X - endPt.X) > layoutWidth * 0.5) { return true; } } } // 检查是否是垂直线(左边或右边) var isVertical = Math.Abs(startPt.X - endPt.X) < tolerance; if (isVertical) { // 检查X坐标是否在布局的左边界或右边界 var isLeftEdge = Math.Abs(startPt.X - layout.MinPoint.X) < tolerance; var isRightEdge = Math.Abs(startPt.X - layout.MaxPoint.X) < tolerance; if (isLeftEdge || isRightEdge) { // 增加长度判断 if (Math.Abs(startPt.Y - endPt.Y) > layoutHeight * 0.5) { return true; } } } } // Polyline可能是完整的矩形外框 if (ent is Polyline poly) { var entWidth = ext.MaxPoint.X - ext.MinPoint.X; var entHeight = ext.MaxPoint.Y - ext.MinPoint.Y; if (entWidth > layoutWidth * 0.90 && entHeight > layoutHeight * 0.90) { return true; } } } catch { return false; } return false; } private static bool IsRedColor(Entity ent, Transaction tr) { if (ent == null) { return false; } // 检查实体自身颜色 var colorIndex = ent.ColorIndex; // ColorIndex=1 是红色 if (colorIndex == 1) { return true; } // ColorIndex=256 表示 ByLayer,需要检查图层颜色 if (colorIndex == 256 && tr != null) { try { var layer = tr.GetObject(ent.LayerId, OpenMode.ForRead) as LayerTableRecord; if (layer != null && layer.Color.ColorIndex == 1) { return true; } } catch { // 忽略 } } // 检查Color属性 try { var color = ent.Color; if (color != null) { // 红色的RGB大约是(255, 0, 0)或接近 if (color.ColorIndex == 1) { return true; } if (color.ColorMethod == Autodesk.AutoCAD.Colors.ColorMethod.ByColor) { var r = color.Red; var g = color.Green; var b = color.Blue; // 红色:R高,G和B低 if (r > 200 && g < 100 && b < 100) { return true; } } } } catch { // 忽略 } return false; } private static string GetLayerName(Transaction tr, ObjectId layerId) { if (layerId.IsNull) { return string.Empty; } try { var layer = tr.GetObject(layerId, OpenMode.ForRead) as LayerTableRecord; return layer?.Name ?? string.Empty; } catch { return string.Empty; } } private static bool IsCaxaLayer(string layerName) { if (string.IsNullOrWhiteSpace(layerName)) { return false; } return CaxaLayerRegex.IsMatch(layerName); } private static bool IsDimensionLayer(string layerName) { if (string.IsNullOrWhiteSpace(layerName)) { return false; } return DimensionLayerNames.Contains(layerName); } private static bool IsDimensionEntity(Entity ent) { return ent is Dimension || ent is RotatedDimension || ent is AlignedDimension || ent is RadialDimension || ent is DiametricDimension || ent is ArcDimension || ent is OrdinateDimension || ent is Leader; } private static Extents3d? ComputeLayoutExtents(IEnumerable entities) { Extents3d? result = null; foreach (var ent in entities) { if (ent == null || ent.IsErased) { continue; } try { var ext = ent.GeometricExtents; if (result == null) { result = ext; } else { result = new Extents3d( new Point3d( Math.Min(result.Value.MinPoint.X, ext.MinPoint.X), Math.Min(result.Value.MinPoint.Y, ext.MinPoint.Y), Math.Min(result.Value.MinPoint.Z, ext.MinPoint.Z)), new Point3d( Math.Max(result.Value.MaxPoint.X, ext.MaxPoint.X), Math.Max(result.Value.MaxPoint.Y, ext.MaxPoint.Y), Math.Max(result.Value.MaxPoint.Z, ext.MaxPoint.Z))); } } catch { // 忽略无法获取范围的实体 } } return result; } private static Extents3d? ComputeRedFrameExtents(Transaction tr, IEnumerable entities) { Extents3d? result = null; foreach (var ent in entities) { if (ent == null || ent.IsErased) { continue; } // 只处理红色的Line或Polyline if (!(ent is Line) && !(ent is Polyline)) { continue; } if (!IsRedColor(ent, tr)) { continue; } try { var ext = ent.GeometricExtents; if (result == null) { result = ext; } else { result = new Extents3d( new Point3d( Math.Min(result.Value.MinPoint.X, ext.MinPoint.X), Math.Min(result.Value.MinPoint.Y, ext.MinPoint.Y), Math.Min(result.Value.MinPoint.Z, ext.MinPoint.Z)), new Point3d( Math.Max(result.Value.MaxPoint.X, ext.MaxPoint.X), Math.Max(result.Value.MaxPoint.Y, ext.MaxPoint.Y), Math.Max(result.Value.MaxPoint.Z, ext.MaxPoint.Z))); } } catch { // 忽略 } } return result; } private static Extents3d? ComputeCaxaLayerExtents(Transaction tr, IEnumerable entities) { Extents3d? result = null; foreach (var ent in entities) { if (ent == null || ent.IsErased) { continue; } var layerName = GetLayerName(tr, ent.LayerId); if (!IsCaxaLayer(layerName)) { continue; } try { var ext = ent.GeometricExtents; if (result == null) { result = ext; } else { result = new Extents3d( new Point3d( Math.Min(result.Value.MinPoint.X, ext.MinPoint.X), Math.Min(result.Value.MinPoint.Y, ext.MinPoint.Y), Math.Min(result.Value.MinPoint.Z, ext.MinPoint.Z)), new Point3d( Math.Max(result.Value.MaxPoint.X, ext.MaxPoint.X), Math.Max(result.Value.MaxPoint.Y, ext.MaxPoint.Y), Math.Max(result.Value.MaxPoint.Z, ext.MaxPoint.Z))); } } catch { // 忽略 } } return result; } private static Extents3d? ComputeTitleBlockExtentsFromEntities(Transaction tr, IEnumerable entities, Extents3d frame) { if (entities == null) { return null; } var w = frame.MaxPoint.X - frame.MinPoint.X; var h = frame.MaxPoint.Y - frame.MinPoint.Y; if (w <= 0 || h <= 0) { return null; } var searchMinX = frame.MinPoint.X + w * 0.25; var searchMaxX = frame.MaxPoint.X - 5.0; var searchMinY = frame.MinPoint.Y; var searchMaxY = frame.MinPoint.Y + h * 0.50; var candidatesX = new List(); var candidatesY = new List(); foreach (var ent in entities) { if (ent == null || ent.IsErased) { continue; } if (ent is Line ln) { if (Math.Abs(ln.StartPoint.X - ln.EndPoint.X) < 1.0 && ln.StartPoint.X > searchMinX && ln.StartPoint.X < searchMaxX && Math.Max(ln.StartPoint.Y, ln.EndPoint.Y) > searchMinY && Math.Min(ln.StartPoint.Y, ln.EndPoint.Y) < searchMaxY) { candidatesX.Add(ln.StartPoint.X); } if (ln.StartPoint.X > frame.MaxPoint.X - Math.Min(200.0, w * 0.5) && ln.EndPoint.X > frame.MaxPoint.X - Math.Min(200.0, w * 0.5) && ln.StartPoint.Y > searchMinY && ln.StartPoint.Y < frame.MinPoint.Y + Math.Min(80.0, h * 0.40) && Math.Abs(ln.StartPoint.Y - ln.EndPoint.Y) < 1.0) { candidatesY.Add(ln.StartPoint.Y); } } else if (ent is Polyline pl) { for (int i = 0; i < pl.NumberOfVertices; i++) { var pt = pl.GetPoint3dAt(i); if (pt.X > searchMinX && pt.X < searchMaxX && pt.Y > searchMinY && pt.Y < searchMaxY) { candidatesX.Add(pt.X); } if (pt.X > frame.MaxPoint.X - Math.Min(200.0, w * 0.5) && pt.Y > searchMinY && pt.Y < frame.MinPoint.Y + Math.Min(80.0, h * 0.40)) { candidatesY.Add(pt.Y); } } } else if (ent is BlockReference br) { try { var ext = br.GeometricExtents; if (ext.MaxPoint.X > searchMinX && ext.MinPoint.X < searchMaxX && ext.MaxPoint.Y > searchMinY && ext.MinPoint.Y < searchMaxY) { if (ext.MinPoint.X > searchMinX) { candidatesX.Add(ext.MinPoint.X); } if (ext.MaxPoint.Y < frame.MinPoint.Y + Math.Min(80.0, h * 0.40)) { candidatesY.Add(ext.MaxPoint.Y); } } } catch { // ignore } } } if (candidatesY.Count == 0) { return null; } var tableTopY = candidatesY.Max(); var boundaryX = candidatesX.Count > 0 ? candidatesX.Min() : (frame.MaxPoint.X - Math.Min(200.0, w * 0.5)); if (boundaryX >= frame.MaxPoint.X - 1.0 || tableTopY <= frame.MinPoint.Y + 1.0) { return null; } return new Extents3d( new Point3d(boundaryX, frame.MinPoint.Y, 0), new Point3d(frame.MaxPoint.X, tableTopY, 0)); } private static bool IsWithinTitleBlock(Entity ent, Extents3d? titleBlock) { if (ent == null || !titleBlock.HasValue) { return false; } try { return Intersects(ent.GeometricExtents, titleBlock.Value); } catch { return true; } } private static bool IsInTopRightCorner(Entity ent, Extents3d layoutExtents, double threshold) { try { var ext = ent.GeometricExtents; var centerX = (ext.MinPoint.X + ext.MaxPoint.X) / 2; var centerY = (ext.MinPoint.Y + ext.MaxPoint.Y) / 2; var layoutWidth = layoutExtents.MaxPoint.X - layoutExtents.MinPoint.X; var layoutHeight = layoutExtents.MaxPoint.Y - layoutExtents.MinPoint.Y; var thresholdX = layoutExtents.MinPoint.X + layoutWidth * threshold; var thresholdY = layoutExtents.MinPoint.Y + layoutHeight * threshold; return centerX > thresholdX && centerY > thresholdY; } catch { return true; } } /// /// 检测白色外框(图纸边界)的范围 /// 白色外框通常是 ColorIndex=7 的 Line 或 Polyline,构成图纸的可绘图区域 /// public static Extents3d? ComputeWhiteFrameExtents(CadContext ctx) { if (ctx == null) { return null; } var db = ctx.Database; var tr = ctx.Transaction; var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); var ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead); var allEntities = ms.Cast() .Select(id => tr.GetObject(id, OpenMode.ForRead, false) as Entity) .Where(e => e != null && !e.IsErased) .ToList(); return ComputeWhiteFrameExtentsFromEntities(tr, allEntities); } private static Extents3d? ComputeWhiteFrameExtentsFromEntities(Transaction tr, IEnumerable entities) { Extents3d? result = null; foreach (var ent in entities) { if (ent == null || ent.IsErased) { continue; } // 只处理 Line 或 Polyline if (!(ent is Line) && !(ent is Polyline)) { continue; } // 检查是否是白色(ColorIndex=7) if (!IsWhiteColor(ent, tr)) { continue; } try { var ext = ent.GeometricExtents; if (result == null) { result = ext; } else { result = new Extents3d( new Point3d( Math.Min(result.Value.MinPoint.X, ext.MinPoint.X), Math.Min(result.Value.MinPoint.Y, ext.MinPoint.Y), Math.Min(result.Value.MinPoint.Z, ext.MinPoint.Z)), new Point3d( Math.Max(result.Value.MaxPoint.X, ext.MaxPoint.X), Math.Max(result.Value.MaxPoint.Y, ext.MaxPoint.Y), Math.Max(result.Value.MaxPoint.Z, ext.MaxPoint.Z))); } } catch { // 忽略 } } return result; } private static bool IsWhiteColor(Entity ent, Transaction tr) { if (ent == null) { return false; } // ColorIndex=7 是白色 var colorIndex = ent.ColorIndex; if (colorIndex == 7) { return true; } // ColorIndex=256 表示 ByLayer,需要检查图层颜色 if (colorIndex == 256 && tr != null) { try { var layer = tr.GetObject(ent.LayerId, OpenMode.ForRead) as LayerTableRecord; if (layer != null && layer.Color.ColorIndex == 7) { return true; } } catch { // 忽略 } } return false; } /// /// 图形尺寸检查结果 /// public sealed class DrawingSizeCheckResult { public bool NeedsScaling { get; set; } public double ScaleFactor { get; set; } = 1.0; public double DrawingWidth { get; set; } public double DrawingHeight { get; set; } public double AvailableWidth { get; set; } public double AvailableHeight { get; set; } public string Message { get; set; } } /// /// 检查图形是否会超出图纸边界,如果超出则计算缩放比例 /// /// 白色外框范围 /// 预期绘制图形的宽度 /// 预期绘制图形的高度 /// 边距(默认50) /// 检查结果,包含是否需要缩放和缩放比例 public static DrawingSizeCheckResult CheckDrawingSize(Extents3d? whiteFrameExtents, double drawingWidth, double drawingHeight, double margin = 50) { var result = new DrawingSizeCheckResult { DrawingWidth = drawingWidth, DrawingHeight = drawingHeight }; if (!whiteFrameExtents.HasValue) { result.Message = "未检测到白色外框,跳过尺寸检查"; return result; } var frame = whiteFrameExtents.Value; var availableWidth = frame.MaxPoint.X - frame.MinPoint.X - margin * 2; var availableHeight = frame.MaxPoint.Y - frame.MinPoint.Y - margin * 2; result.AvailableWidth = availableWidth; result.AvailableHeight = availableHeight; if (availableWidth <= 0 || availableHeight <= 0) { result.Message = "白色外框范围无效"; return result; } // 计算宽度和高度的缩放比例,取较小值(等比例缩放) var scaleX = availableWidth / drawingWidth; var scaleY = availableHeight / drawingHeight; if (scaleX >= 1.0 && scaleY >= 1.0) { result.NeedsScaling = false; result.ScaleFactor = 1.0; result.Message = $"图形尺寸正常,无需缩放 (图形: {drawingWidth:F1}x{drawingHeight:F1}, 可用: {availableWidth:F1}x{availableHeight:F1})"; return result; } // 需要缩放,取较小的比例以确保两个方向都不超出 result.NeedsScaling = true; result.ScaleFactor = Math.Min(scaleX, scaleY); result.Message = $"图形超出边界,将等比例缩放至 {result.ScaleFactor:P1} (图形: {drawingWidth:F1}x{drawingHeight:F1}, 可用: {availableWidth:F1}x{availableHeight:F1})"; return result; } /// /// 更新图纸标题栏中的比例 - 查找"比例"标题文字并在其下方创建比例值文本 /// public static bool UpdateScaleAttribute(CadContext ctx, string layoutName, bool scanModelSpace, double scaleFactor) { if (ctx == null) { return false; } var db = ctx.Database; var tr = ctx.Transaction; // 格式化比例文本 var scaleText = FormatScaleText(scaleFactor); // 在ModelSpace中查找"比例"标题文字 var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); var ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); Point3d? scaleLabelPos = null; double textHeight = 2.5; string textStyle = "STANDARD"; // 遍历所有实体,查找"比例"文字 foreach (ObjectId id in ms) { var ent = tr.GetObject(id, OpenMode.ForRead, false) as Entity; if (ent == null) { continue; } if (ent is DBText dbText) { var content = dbText.TextString?.Trim() ?? string.Empty; var contentNoSpace = content.Replace(" ", "").Replace(" ", ""); // 匹配:去掉空格后是"比例" if (contentNoSpace == "比例") { scaleLabelPos = dbText.Position; textHeight = dbText.Height; textStyle = dbText.TextStyleName ?? "STANDARD"; break; } } else if (ent is MText mText) { var content = mText.Contents?.Trim() ?? string.Empty; var contentNoSpace = content.Replace(" ", "").Replace(" ", ""); if (contentNoSpace == "比例") { scaleLabelPos = mText.Location; textHeight = mText.TextHeight; textStyle = mText.TextStyleName ?? "STANDARD"; break; } } } if (!scaleLabelPos.HasValue) { return false; } // 在"比例"文字下方创建比例值文本 var valuePos = new Point3d( scaleLabelPos.Value.X, scaleLabelPos.Value.Y - textHeight * 2.5, scaleLabelPos.Value.Z ); var newText = new DBText { Position = valuePos, TextString = scaleText, Height = textHeight, ColorIndex = 7, Layer = "0" }; // 设置文字样式 var textStyleTbl = (TextStyleTable)tr.GetObject(db.TextStyleTableId, OpenMode.ForRead); if (textStyleTbl.Has(textStyle)) { newText.TextStyleId = textStyleTbl[textStyle]; } ms.AppendEntity(newText); tr.AddNewlyCreatedDBObject(newText, true); return true; } private static string FormatScaleText(double scaleFactor) { if (scaleFactor >= 0.9999 && scaleFactor <= 1.0001) { return "1:1"; } var ratio = 1.0 / scaleFactor; if (Math.Abs(ratio - Math.Round(ratio)) < 0.05) { return $"1:{Math.Round(ratio)}"; } return $"1:{ratio:F1}"; } /// /// 更新图纸标题栏中的检验类别 - 查找"检验类别"标题文字并在其下方创建检验类别值文本 /// public static bool UpdateInspectionCategoryAttribute(CadContext ctx, string layoutName, bool scanModelSpace, string inspectionCategory) { if (ctx == null || string.IsNullOrWhiteSpace(inspectionCategory)) { return false; } var db = ctx.Database; var tr = ctx.Transaction; var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); var ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); Point3d? labelPos = null; double textHeight = 2.5; string textStyle = "STANDARD"; foreach (ObjectId id in ms) { var ent = tr.GetObject(id, OpenMode.ForRead, false) as Entity; if (ent == null) { continue; } if (ent is DBText dbText) { var content = dbText.TextString?.Trim() ?? string.Empty; var contentNoSpace = content.Replace(" ", "").Replace(" ", ""); if (contentNoSpace == "检验类别") { labelPos = dbText.Position; textHeight = dbText.Height; textStyle = dbText.TextStyleName ?? "STANDARD"; break; } } else if (ent is MText mText) { var content = mText.Contents?.Trim() ?? string.Empty; var contentNoSpace = content.Replace(" ", "").Replace(" ", ""); if (contentNoSpace == "检验类别") { labelPos = mText.Location; textHeight = mText.TextHeight; textStyle = mText.TextStyleName ?? "STANDARD"; break; } } } if (!labelPos.HasValue) { return false; } var valuePos = new Point3d( labelPos.Value.X, labelPos.Value.Y - textHeight * 2.5, labelPos.Value.Z ); var newText = new DBText { Position = valuePos, TextString = inspectionCategory, Height = textHeight, ColorIndex = 7, Layer = "0" }; var textStyleTbl = (TextStyleTable)tr.GetObject(db.TextStyleTableId, OpenMode.ForRead); if (textStyleTbl.Has(textStyle)) { newText.TextStyleId = textStyleTbl[textStyle]; } ms.AppendEntity(newText); tr.AddNewlyCreatedDBObject(newText, true); return true; } /// /// 在红色边框与白色边框之间的左上角添加"内部资料,控制范围"文字标记 /// /// CAD上下文 /// 布局名称 /// 是否为模型空间 /// 是否成功添加 public static bool AddInternalMaterialLabel(CadContext ctx, string layoutName, bool isModelSpace) { if (ctx == null) { return false; } var db = ctx.Database; var tr = ctx.Transaction; var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); var ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); var allEntities = ms.Cast() .Select(id => tr.GetObject(id, OpenMode.ForRead, false) as Entity) .Where(e => e != null && !e.IsErased) .ToList(); // 获取红色边框和白色边框范围 var redFrameExtents = ComputeRedFrameExtents(tr, allEntities); var whiteFrameExtents = ComputeWhiteFrameExtentsFromEntities(tr, allEntities); if (!redFrameExtents.HasValue || !whiteFrameExtents.HasValue) { return false; } var redFrame = redFrameExtents.Value; var whiteFrame = whiteFrameExtents.Value; // 计算插入点:在红框和白框之间的左上角 // X: 红框左边界稍内侧 // Y: 红框顶部和白框顶部之间,偏上方 var insertX = redFrame.MinPoint.X + 2.0; // 距红框左边2mm var insertY = (redFrame.MaxPoint.Y + whiteFrame.MaxPoint.Y) / 2.0; // 红框与白框顶部中间 var insertPoint = new Point3d(insertX, insertY, 0); // 创建 MText var mt = new MText(); // 使用宋体格式 mt.Contents = @"{\fSimSun|b0|i0|c134|p2;内部资料,控制范围}"; mt.Location = insertPoint; mt.TextHeight = 3.5; mt.Attachment = AttachmentPoint.MiddleLeft; // 左对齐,垂直居中 mt.Width = 0; // 不换行 mt.ColorIndex = 4; // 青色 // 尝试设置图层 try { var layerTbl = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead); string targetLayer = "0"; if (layerTbl.Has("TEXT")) targetLayer = "TEXT"; else if (layerTbl.Has("文字")) targetLayer = "文字"; mt.Layer = targetLayer; } catch { } ms.AppendEntity(mt); tr.AddNewlyCreatedDBObject(mt, true); return true; } } }