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 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); // 处理每行开头可能残留的格式代码片段(如 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 string PlainText; public string OriginalContents; 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, 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, 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) { t.TextString = CollapseToSingleLine(rendered); return; } if (obj is AttributeReference att) { att.TextString = CollapseToSingleLine(rendered); } } 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.Count == 0) { return ToMTextContents(renderedPlainText); } // 在原始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; ri += 4; } else { // 每个*对应rendered中的一个字符 for (var k = 0; k < starCount && ri < rendered.Length; k++) { replacements.Add(rendered[ri].ToString()); ti++; ri++; } } } else { // 非*字符,两边应该相同,直接跳过 ti++; ri++; } } 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); } 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; } } /// /// 删除模板中原有的图纸图形,保留右上角区域的内容(如粗糙度标注)。 /// /// 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); 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); } // 使用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)) { var entForWrite = tr.GetObject(ent.ObjectId, OpenMode.ForWrite) as Entity; if (entForWrite != null) { entForWrite.Erase(true); result.OuterFrameErased++; } 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)) { // 基于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++; } } } return result; } 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) { 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) { 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 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}"; } } }