using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Interop;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace cad_tools
{
public class merge_dwg
{
/// <summary>合并目标图模型空间实体数缓存,避免每次 Insert 前全表扫描。</summary>
private static int _destModelSpaceEntityCount;
internal static void ResetDestEntityCount()
{
_destModelSpaceEntityCount = 0;
}
internal static void SyncDestEntityCount(Database destDb)
{
_destModelSpaceEntityCount = CountModelSpaceEntities(destDb);
}
/// <summary>合并前记录的尺寸标注显示参数,合并后写回。</summary>
private sealed class DimensionSnapshot
{
public double Dimtxt;
public double Dimscale;
public double Dimasz;
public double Dimexo;
public double Dimexe;
public double Dimgap;
public double Dimlfac;
public bool Dimtih;
public bool Dimtoh;
public int Dimtad;
public string DimStyleName;
public string DimtxstyName;
}
internal static int CountModelSpaceEntities(Database db)
{
int count = 0;
using Transaction tr = db.TransactionManager.StartTransaction();
BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead);
foreach (ObjectId id in ms)
{
if (id.IsValid && !id.IsErased)
{
count++;
}
}
tr.Commit();
return count;
}
internal static bool TryGetFullModelSpaceExtents(Database db, out Extents3d extents)
{
return TryGetModelSpaceExtents(db, out extents);
}
/// <summary>
/// 将源 DWG 模型空间实体插入目标图(非外部参照),返回占用区域右上角 [maxX, maxY]。
/// </summary>
static public double[]? run(string sourcePath, double x, double y, bool isDim, AcadDocument? targetDoc = null)
{
if (sourcePath.Contains(".dwl2") || sourcePath.Contains(".dwl"))
{
return null;
}
if (!File.Exists(sourcePath))
{
Console.WriteLine($"错误:源文件不存在:{sourcePath}");
return null;
}
AcadApplication? acadApp = CadConnect.GetOrCreateInstance();
if (acadApp == null)
{
Console.WriteLine("错误:无法连接到 AutoCAD 应用程序。");
return null;
}
Document? destDoc = draw_divider.GetMergeManagedDocument(acadApp);
if (destDoc == null)
{
draw_divider.EnsureMergeCanvas(acadApp);
destDoc = draw_divider.GetMergeManagedDocument(acadApp);
}
if (destDoc == null)
{
Console.WriteLine("错误:无法绑定合并画布 .NET 文档,实体与标签可能不在同一图纸。");
return null;
}
try
{
using DocumentLock docLock = destDoc.LockDocument();
Database destDb = destDoc.Database;
using Database sourceDb = new Database(false, true);
sourceDb.ReadDwgFile(sourcePath, FileShare.Read, true, null);
string stylePrefix = BuildUniqueStylePrefix(sourcePath, isDim);
string mergedDimStyleName = PrepareSourceForMerge(sourceDb, stylePrefix);
List<DimensionSnapshot> dimSnapshots = CollectDimensionSnapshotsOrdered(sourceDb, mergedDimStyleName);
if (!TryGetModelSpaceExtents(sourceDb, out Extents3d sourceExtents))
{
Console.WriteLine($"警告:无法获取源图边界,仍尝试插入:{sourcePath}");
InsertModelSpace(sourceDb, destDb, Matrix3d.Displacement(new Vector3d(x, y, 0)), dimSnapshots);
Console.WriteLine($"已插入(无边界):{Path.GetFileName(sourcePath)} @ ({x:F2}, {y:F2})");
return new double[] { x, y };
}
double offsetX = -sourceExtents.MinPoint.X;
double offsetY = -sourceExtents.MinPoint.Y;
Matrix3d xform = Matrix3d.Displacement(new Vector3d(offsetX + x, offsetY + y, 0));
InsertModelSpace(sourceDb, destDb, xform, dimSnapshots);
double relativeMaxX = sourceExtents.MaxPoint.X - sourceExtents.MinPoint.X + x;
double relativeMaxY = sourceExtents.MaxPoint.Y - sourceExtents.MinPoint.Y + y;
Console.WriteLine($"已插入:{Path.GetFileName(sourcePath)} @ ({x:F2}, {y:F2})");
return new double[] { relativeMaxX, relativeMaxY };
}
catch (Exception ex)
{
Console.WriteLine($"插入失败:{ex.Message}, {sourcePath}");
if (ex.InnerException != null)
{
Console.WriteLine($"内部异常:{ex.InnerException.Message}");
}
}
return null;
}
private static string BuildUniqueStylePrefix(string sourcePath, bool isDim)
{
string fullPath = Path.GetFullPath(sourcePath);
string baseName = SanitizeSymbolName(Path.GetFileNameWithoutExtension(fullPath));
if (string.IsNullOrEmpty(baseName))
{
baseName = "DWG";
}
if (!isDim)
{
return $"{baseName}_";
}
return $"{baseName}_";
}
private static string SanitizeSymbolName(string raw)
{
if (string.IsNullOrWhiteSpace(raw))
{
return "DWG";
}
var sb = new StringBuilder(raw.Length);
foreach (char c in raw.Trim())
{
if (char.IsLetterOrDigit(c) || c == '_' || c == '-' || c == '$')
{
sb.Append(c);
}
else if (c == ' ' || c == '.')
{
sb.Append('_');
}
}
string name = sb.ToString();
return string.IsNullOrEmpty(name) ? "DWG" : name;
}
/// <summary>
/// 每个源图只保留一个标注样式(+ 对应文字样式),全部尺寸统一指向它,避免合并后符号表膨胀卡顿。
/// </summary>
private static string PrepareSourceForMerge(Database db, string prefix)
{
string mergedDimStyleName = BuildPrefixedName(prefix, "DIM");
string mergedTextStyleName = BuildPrefixedName(prefix, "TXT");
using Transaction tr = db.TransactionManager.StartTransaction();
BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead);
var styleCounts = new Dictionary<ObjectId, int>();
var dimensionIds = new List<ObjectId>();
var layerNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (ObjectId id in ms)
{
if (!id.IsValid || id.IsErased)
{
continue;
}
Entity ent = (Entity)tr.GetObject(id, OpenMode.ForRead);
if (!string.IsNullOrEmpty(ent.Layer))
{
layerNames.Add(ent.Layer);
}
if (ent is Dimension dim && dim.DimensionStyle.IsValid)
{
dimensionIds.Add(id);
if (!styleCounts.TryGetValue(dim.DimensionStyle, out int count))
{
count = 0;
}
styleCounts[dim.DimensionStyle] = count + 1;
}
}
if (styleCounts.Count > 0)
{
ObjectId primaryStyleId = ObjectId.Null;
int maxCount = 0;
foreach (KeyValuePair<ObjectId, int> pair in styleCounts)
{
if (pair.Value > maxCount)
{
maxCount = pair.Value;
primaryStyleId = pair.Key;
}
}
DimStyleTableRecord primaryStyle =
(DimStyleTableRecord)tr.GetObject(primaryStyleId, OpenMode.ForWrite);
if (primaryStyle.Dimtxsty.IsValid)
{
TextStyleTableRecord txtStyle =
(TextStyleTableRecord)tr.GetObject(primaryStyle.Dimtxsty, OpenMode.ForWrite);
if (!txtStyle.Name.StartsWith("*", StringComparison.Ordinal))
{
txtStyle.Name = mergedTextStyleName;
}
}
if (!primaryStyle.Name.StartsWith("*", StringComparison.Ordinal))
{
primaryStyle.Name = mergedDimStyleName;
}
foreach (ObjectId dimId in dimensionIds)
{
Dimension dim = (Dimension)tr.GetObject(dimId, OpenMode.ForWrite);
dim.DimensionStyle = primaryStyleId;
}
}
LayerTable layerTable = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead);
foreach (string layerName in layerNames)
{
if (layerName == "0" || layerName.Equals("Defpoints", StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (!layerTable.Has(layerName))
{
continue;
}
LayerTableRecord layer = (LayerTableRecord)tr.GetObject(layerTable[layerName], OpenMode.ForWrite);
layer.Name = BuildPrefixedName(prefix, layer.Name);
}
tr.Commit();
return mergedDimStyleName;
}
private static string BuildPrefixedName(string prefix, string originalName)
{
string name = prefix + originalName;
return name.Length <= 255 ? name : name.Substring(0, 255);
}
private static List<DimensionSnapshot> CollectDimensionSnapshotsOrdered(Database db, string mergedDimStyleName)
{
var snapshots = new List<DimensionSnapshot>();
using Transaction tr = db.TransactionManager.StartTransaction();
BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead);
foreach (ObjectId id in ms)
{
if (!id.IsValid || id.IsErased)
{
continue;
}
Entity ent = (Entity)tr.GetObject(id, OpenMode.ForRead);
if (ent is Dimension dim)
{
snapshots.Add(CaptureDimensionSnapshot(dim, tr, mergedDimStyleName));
}
}
tr.Commit();
return snapshots;
}
private static DimensionSnapshot CaptureDimensionSnapshot(Dimension dim, Transaction tr, string mergedDimStyleName)
{
string dimtxstyName = string.Empty;
if (dim.DimensionStyle.IsValid)
{
try
{
DimStyleTableRecord style =
(DimStyleTableRecord)tr.GetObject(dim.DimensionStyle, OpenMode.ForRead);
if (style.Dimtxsty.IsValid)
{
TextStyleTableRecord txt =
(TextStyleTableRecord)tr.GetObject(style.Dimtxsty, OpenMode.ForRead);
dimtxstyName = txt.Name;
}
}
catch
{
// ignore
}
}
return new DimensionSnapshot
{
Dimtxt = dim.Dimtxt,
Dimscale = dim.Dimscale,
Dimasz = dim.Dimasz,
Dimexo = dim.Dimexo,
Dimexe = dim.Dimexe,
Dimgap = dim.Dimgap,
Dimlfac = dim.Dimlfac,
Dimtih = dim.Dimtih,
Dimtoh = dim.Dimtoh,
Dimtad = dim.Dimtad,
DimStyleName = mergedDimStyleName,
DimtxstyName = dimtxstyName,
};
}
private static void InsertModelSpace(
Database sourceDb,
Database destDb,
Matrix3d xform,
List<DimensionSnapshot> dimSnapshots)
{
int entityBefore = _destModelSpaceEntityCount;
int insertedEntityCount = CountModelSpaceEntities(sourceDb);
destDb.Insert(xform, sourceDb, true);
_destModelSpaceEntityCount = entityBefore + insertedEntityCount;
if (dimSnapshots.Count == 0)
{
return;
}
using Transaction tr = destDb.TransactionManager.StartTransaction();
BlockTable bt = (BlockTable)tr.GetObject(destDb.BlockTableId, OpenMode.ForRead);
BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead);
int entityIndex = 0;
int snapIndex = 0;
foreach (ObjectId id in ms)
{
if (!id.IsValid || id.IsErased)
{
continue;
}
if (entityIndex < entityBefore)
{
entityIndex++;
continue;
}
entityIndex++;
if (snapIndex >= dimSnapshots.Count)
{
break;
}
if (tr.GetObject(id, OpenMode.ForWrite) is not Dimension dim)
{
continue;
}
ApplyDimensionSnapshot(dim, dimSnapshots[snapIndex], tr);
snapIndex++;
}
tr.Commit();
}
private static void ApplyDimensionSnapshot(Dimension dim, DimensionSnapshot snap, Transaction tr)
{
try
{
if (!string.IsNullOrEmpty(snap.DimStyleName))
{
DimStyleTable dimTable = (DimStyleTable)tr.GetObject(
dim.Database.DimStyleTableId,
OpenMode.ForRead);
if (dimTable.Has(snap.DimStyleName))
{
dim.DimensionStyle = dimTable[snap.DimStyleName];
}
}
dim.Dimtxt = snap.Dimtxt;
dim.Dimscale = snap.Dimscale;
dim.Dimasz = snap.Dimasz;
dim.Dimexo = snap.Dimexo;
dim.Dimexe = snap.Dimexe;
dim.Dimgap = snap.Dimgap;
dim.Dimlfac = snap.Dimlfac;
dim.Dimtih = snap.Dimtih;
dim.Dimtoh = snap.Dimtoh;
dim.Dimtad = snap.Dimtad;
if (!string.IsNullOrEmpty(snap.DimtxstyName))
{
TextStyleTable textTable = (TextStyleTable)tr.GetObject(
dim.Database.TextStyleTableId,
OpenMode.ForRead);
if (textTable.Has(snap.DimtxstyName))
{
dim.TextStyleId = textTable[snap.DimtxstyName];
}
}
}
catch (Exception ex)
{
Console.WriteLine($"恢复标注尺寸失败:{ex.Message}");
}
}
private static bool TryGetModelSpaceExtents(Database db, out Extents3d extents)
{
extents = new Extents3d();
bool hasExtents = false;
using Transaction tr = db.TransactionManager.StartTransaction();
BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead);
foreach (ObjectId id in ms)
{
if (!id.IsValid || id.IsErased)
{
continue;
}
Entity ent = (Entity)tr.GetObject(id, OpenMode.ForRead);
try
{
Extents3d entExt = ent.GeometricExtents;
if (!hasExtents)
{
extents = entExt;
hasExtents = true;
}
else
{
extents.AddExtents(entExt);
}
}
catch
{
// 部分实体无几何边界
}
}
tr.Commit();
return hasExtents;
}
}
}