CadParamPluging/Cad/TemplateDrawingService.cs
2025-12-18 14:47:17 +08:00

1498 lines
50 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string> OuterFrameLayerNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"图框", "边框", "外框", "FRAME", "BORDER", "粗实线"
};
private static readonly Regex CaxaLayerRegex = new Regex(@"^CAXA\d$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly HashSet<string> DimensionLayerNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"尺寸标注", "标注", "DIM", "DIMENSION", "DIMENSIONS"
};
public static Document CreateDocumentFromTemplate(TemplateInfo template)
{
var doc = Application.DocumentManager.Add(template.FilePath);
Application.DocumentManager.MdiActiveDocument = doc;
return doc;
}
/// <summary>
/// 移除模板中用于“匹配模板/图纸”的参数标注文本(交付状态/工艺方法/结构特征/特殊条件)。
/// 仅清理当前生成图纸中目标空间Layout 或 ModelSpace里的文本实体/块属性。
/// </summary>
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<ObjectId>().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<string> 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<NoteCandidate>();
var visitedBlocks = new HashSet<ObjectId>();
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<ObjectId> visitedBlocks, List<NoteCandidate> 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<ObjectId>();
}
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<NoteCandidate> 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<string> SplitLines(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return Enumerable.Empty<string>();
}
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<string> ExtractPlaceholderReplacements(string plainTemplate, string renderedText)
{
var replacements = new List<string>();
// 将两个文本按相同方式处理
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<string> layoutNames;
using (var tr = db.TransactionManager.StartTransaction())
{
var layoutDict = (DBDictionary)tr.GetObject(db.LayoutDictionaryId, OpenMode.ForRead);
layoutNames = layoutDict.Cast<DBDictionaryEntry>().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; }
}
/// <summary>
/// 删除模板中原有的图纸图形,保留右上角区域的内容(如粗糙度标注)。
/// </summary>
/// <param name="ctx">CAD上下文</param>
/// <param name="topRightThreshold">右上角保留区域阈值0.70表示X和Y都超过70%的位置</param>
/// <returns>删除结果,包含原图形中心点位置</returns>
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<ObjectId>()
.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<Entity> 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<Entity> 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<Entity> 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;
}
}
/// <summary>
/// 检测白色外框(图纸边界)的范围
/// 白色外框通常是 ColorIndex=7 的 Line 或 Polyline构成图纸的可绘图区域
/// </summary>
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<ObjectId>()
.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<Entity> 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;
}
/// <summary>
/// 图形尺寸检查结果
/// </summary>
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; }
}
/// <summary>
/// 检查图形是否会超出图纸边界,如果超出则计算缩放比例
/// </summary>
/// <param name="whiteFrameExtents">白色外框范围</param>
/// <param name="drawingWidth">预期绘制图形的宽度</param>
/// <param name="drawingHeight">预期绘制图形的高度</param>
/// <param name="margin">边距默认50</param>
/// <returns>检查结果,包含是否需要缩放和缩放比例</returns>
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;
}
}
}