1498 lines
50 KiB
C#
1498 lines
50 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|