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