using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Colors;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Collections.Concurrent;
using System.Reflection;
namespace ScaffoldPlugin
{
public class PluginInitialization : IExtensionApplication
{
public void Initialize()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
if (doc != null)
{
doc.Editor.WriteMessage("\n脚手架插件已加载。命令: PS(布置), CS(统计)");
}
}
public void Terminate() { }
}
public class ScaffoldCommands
{
private static readonly double[] BarLengths = { 300, 600, 900, 1200, 1500, 1800 };
private static readonly string BlockDirectory = Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
"ScaffoldBlocks");
private const double Tolerance = 0.001;
private const int BatchSize = 5000;
private const double SpacingTolerance = 150.0;
private const double LineTolerance = 10.0;
private const int MaxPoints = 50000;
private static Dictionary<string, ObjectId> _cachedBlockDefinitions = new Dictionary<string, ObjectId>();
private const string PoleBlockName = "ScaffoldPole立杆";
private const string HorizontalBarPrefix = "ScaffoldPole横杆";
private const string AxisLayer = "盘扣轴网"; // 轴网专用图层
// 脚手架构件图层
private const string PoleLayer = "Scaffold-Poles";
private const string HorizontalBarLayer = "Scaffold-Bars-X";
private const string VerticalBarLayer = "Scaffold-Bars-Y";
private struct Point3dKey
{
public readonly long X;
public readonly long Y;
private const double Scale = 1000.0;
public Point3dKey(Point3d pt)
{
X = (long)(pt.X * Scale);
Y = (long)(pt.Y * Scale);
}
public override bool Equals(object obj) => obj is Point3dKey other && X == other.X && Y == other.Y;
public override int GetHashCode() => (int)((X * 397) ^ Y);
}
[CommandMethod("PlaceScaffold", "PS", CommandFlags.Modal)]
public void PlaceScaffold()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
try
{
if (!Directory.Exists(BlockDirectory))
{
Directory.CreateDirectory(BlockDirectory);
ShowErrorDialog($"块目录已创建: {BlockDirectory}\n请添加块文件后重新运行命令");
return;
}
// 创建所需图层
CreateLayerIfNotExists(db, PoleLayer, 7); // 白色
CreateLayerIfNotExists(db, HorizontalBarLayer, 1); // 红色
CreateLayerIfNotExists(db, VerticalBarLayer, 5); // 蓝色
// 选择轴网线(只选择"盘扣轴网"图层)
var axisLineIds = SelectAxisLines(ed);
if (axisLineIds == null || axisLineIds.Count == 0)
{
ShowErrorDialog("未选择轴网线或操作已取消!");
return;
}
List<Line> axisLines = GetAxisLinesFromIds(db, axisLineIds);
if (axisLines.Count == 0)
{
ShowErrorDialog("未找到轴网线!");
return;
}
ed.WriteMessage("\n正在计算交点...");
List<Point3d> points = GetIntersectionPoints(axisLines);
if (points.Count == 0)
{
ShowErrorDialog("未找到有效交点!");
return;
}
ed.WriteMessage($"\n找到 {points.Count} 个交点");
if (points.Count > MaxPoints)
{
ShowErrorDialog($"交点数量超过上限 ({MaxPoints})!\n请缩小布置范围或简化轴网");
return;
}
ed.WriteMessage("\n正在加载块定义...");
if (!LoadBlockDefinitions(db, ed)) return;
ed.WriteMessage("\n开始清理重叠区域的现有脚手架...");
CleanOverlappingScaffold(db, points, ed);
ed.WriteMessage("\n开始布置立杆...");
int poleCount = PlacePoles(db, points, ed);
ed.WriteMessage($"\n已布置 {poleCount} 根立杆");
ed.WriteMessage("\n开始布置横杆...");
int barCount = PlaceBars(db, points, axisLines, ed);
ed.WriteMessage($"\n已布置 {barCount} 根横杆");
if (poleCount == 0 && barCount == 0)
{
ShowErrorDialog("脚手架布置失败,未布置任何构件!");
}
else
{
ed.WriteMessage($"\n成功布置 {poleCount}根立杆和{barCount}根横杆!");
}
}
catch (System.Exception ex)
{
ShowErrorDialog($"发生错误: {ex.Message}\n详细请查看命令行日志");
ed.WriteMessage($"\n错误: {ex.Message}\n{ex.StackTrace}");
}
}
[CommandMethod("CountScaffoldBlocks", "CS", CommandFlags.Modal)]
public void CountScaffoldBlocks()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
try
{
TypedValue[] filterValues = {
new TypedValue((int)DxfCode.Start, "INSERT"),
new TypedValue((int)DxfCode.Operator, "<or"),
new TypedValue((int)DxfCode.BlockName, PoleBlockName),
new TypedValue((int)DxfCode.BlockName, VerticalBarPrefix + "*"),
new TypedValue((int)DxfCode.BlockName, HorizontalBarPrefix + "*"),
new TypedValue((int)DxfCode.Operator, "or>")
};
SelectionFilter filter = new SelectionFilter(filterValues);
PromptSelectionResult selResult = ed.GetSelection(new PromptSelectionOptions(), filter);
if (selResult.Status != PromptStatus.OK) return;
Dictionary<(string type, int length), int> blockCounts = new Dictionary<(string, int), int>();
using (Transaction trans = db.TransactionManager.StartTransaction())
{
foreach (SelectedObject selObj in selResult.Value)
{
BlockReference blockRef = trans.GetObject(selObj.ObjectId, OpenMode.ForRead) as BlockReference;
if (blockRef == null) continue;
BlockTableRecord btr = trans.GetObject(blockRef.BlockTableRecord, OpenMode.ForRead) as BlockTableRecord;
string blockName = btr.Name;
string blockType = "";
int barLength = 0;
if (blockName == PoleBlockName) blockType = "立杆";
else if (blockName.StartsWith(VerticalBarPrefix))
{
blockType = "横杆";
barLength = ExtractBarLength(blockName, VerticalBarPrefix);
}
else if (blockName.StartsWith(HorizontalBarPrefix))
{
blockType = "横杆";
barLength = ExtractBarLength(blockName, HorizontalBarPrefix);
}
else continue;
var key = (type: blockType, length: barLength);
blockCounts[key] = blockCounts.ContainsKey(key) ? blockCounts[key] + 1 : 1;
}
trans.Commit();
}
CreateStatisticsTable(db, ed, blockCounts);
}
catch (System.Exception ex)
{
ed.WriteMessage($"\n统计错误: {ex.Message}");
}
}
private int ExtractBarLength(string blockName, string prefix)
{
try
{
string lengthPart = blockName.Substring(prefix.Length);
if (lengthPart.EndsWith("mm")) lengthPart = lengthPart.Substring(0, lengthPart.Length - 2);
if (int.TryParse(lengthPart, out int length)) return length;
}
catch { }
return 0;
}
private void CreateStatisticsTable(Database db, Editor ed, Dictionary<(string type, int length), int> blockCounts)
{
if (blockCounts.Count == 0)
{
ed.WriteMessage("\n未找到可统计的脚手架块!");
return;
}
PromptPointResult ppr = ed.GetPoint("\n指定表格插入点: ");
if (ppr.Status != PromptStatus.OK) return;
using (Transaction trans = db.TransactionManager.StartTransaction())
{
BlockTableRecord modelSpace = trans.GetObject(
SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForWrite) as BlockTableRecord;
Table table = new Table();
table.SetDatabaseDefaults();
table.Position = ppr.Value;
table.SetSize(blockCounts.Count + 4, 4);
for (int i = 0; i < 4; i++) table.Columns[i].Width = i == 0 ? 15 : 25;
table.MergeCells(CellRange.Create(table, 0, 0, 0, 3));
table.Cells[0, 0].TextString = "盘扣平面布置数量统计表";
table.Cells[0, 0].Alignment = CellAlignment.MiddleCenter;
table.Cells[1, 0].TextString = "序号";
table.Cells[1, 1].TextString = "杆件名称";
table.Cells[1, 2].TextString = "杆件长mm";
table.Cells[1, 3].TextString = "数量统计/根";
int rowIndex = 2;
int seqNo = 1;
foreach (var kvp in blockCounts)
{
table.Cells[rowIndex, 0].TextString = seqNo.ToString();
table.Cells[rowIndex, 0].Alignment = CellAlignment.MiddleCenter;
table.Cells[rowIndex, 1].TextString = kvp.Key.type;
table.Cells[rowIndex, 1].Alignment = CellAlignment.MiddleLeft;
table.Cells[rowIndex, 2].TextString =
kvp.Key.type == "立杆" ? "" : kvp.Key.length.ToString();
table.Cells[rowIndex, 2].Alignment = CellAlignment.MiddleCenter;
table.Cells[rowIndex, 3].TextString = kvp.Value.ToString();
table.Cells[rowIndex, 3].Alignment = CellAlignment.MiddleCenter;
rowIndex++;
seqNo++;
}
rowIndex++;
table.MergeCells(CellRange.Create(table, rowIndex, 0, rowIndex, 2));
table.Cells[rowIndex, 0].TextString = "总计";
table.Cells[rowIndex, 0].Alignment = CellAlignment.MiddleRight;
table.Cells[rowIndex, 3].TextString = blockCounts.Values.Sum().ToString();
modelSpace.AppendEntity(table);
trans.AddNewlyCreatedDBObject(table, true);
trans.Commit();
}
ed.WriteMessage($"\n已生成统计表格,总计 {blockCounts.Values.Sum()} 根。");
}
private List<ObjectId> SelectAxisLines(Editor ed)
{
var lineIds = new List<ObjectId>();
// 只选择"盘扣轴网"图层的线段
var filter = new SelectionFilter(new[] {
new TypedValue(0, "LINE"),
new TypedValue(8, AxisLayer) // 图层过滤
});
PromptSelectionResult selection = ed.GetSelection(filter);
if (selection.Status != PromptStatus.OK) return lineIds;
using (var trans = ed.Document.TransactionManager.StartTransaction())
{
foreach (SelectedObject selectedObject in selection.Value)
{
if (selectedObject != null) lineIds.Add(selectedObject.ObjectId);
}
trans.Commit();
}
return lineIds;
}
private List<Line> GetAxisLinesFromIds(Database db, List<ObjectId> axisLineIds)
{
List<Line> lines = new List<Line>();
using (Transaction trans = db.TransactionManager.StartTransaction())
{
foreach (ObjectId id in axisLineIds)
{
if (id.IsValid && !id.IsErased)
{
Line line = trans.GetObject(id, OpenMode.ForRead) as Line;
if (line != null) lines.Add(line);
}
}
trans.Commit();
}
return lines;
}
private List<Point3d> GetIntersectionPoints(List<Line> axisLines)
{
var points = new List<Point3d>();
var pointSet = new HashSet<Point3dKey>();
// 优化:先按方向分组
var horizontalLines = new List<Line>();
var verticalLines = new List<Line>();
foreach (var line in axisLines)
{
if (Math.Abs(line.StartPoint.Y - line.EndPoint.Y) < Tolerance)
horizontalLines.Add(line);
else if (Math.Abs(line.StartPoint.X - line.EndPoint.X) < Tolerance)
verticalLines.Add(line);
}
// 并行计算交点
var pointsLock = new object();
System.Threading.Tasks.Parallel.ForEach(horizontalLines, horizontalLine =>
{
foreach (var verticalLine in verticalLines)
{
Point3d? inters = GetIntersection(horizontalLine, verticalLine);
if (inters != null)
{
var key = new Point3dKey(inters.Value);
lock (pointsLock)
{
if (!pointSet.Contains(key))
{
points.Add(inters.Value);
pointSet.Add(key);
}
}
}
}
});
return points;
}
private Point3d? GetIntersection(Line line1, Line line2)
{
Point3dCollection points = new Point3dCollection();
line1.IntersectWith(line2, Intersect.OnBothOperands, points, IntPtr.Zero, IntPtr.Zero);
return points.Count > 0 ? (Point3d?)points[0] : null;
}
private bool LoadBlockDefinitions(Database db, Editor ed)
{
if (!Directory.Exists(BlockDirectory))
{
ed.WriteMessage($"\n错误: 块文件目录不存在 {BlockDirectory}");
return false;
}
using (var trans = db.TransactionManager.StartTransaction())
{
BlockTable blockTable = trans.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
foreach (var file in Directory.GetFiles(BlockDirectory, "*.dwg"))
{
string blockName = Path.GetFileNameWithoutExtension(file);
if (_cachedBlockDefinitions.ContainsKey(blockName)) continue;
try
{
using (var sourceDb = new Database(false, true))
{
sourceDb.ReadDwgFile(file, FileShare.Read, true, null);
ObjectId blockId = db.Insert(blockName, sourceDb, true);
_cachedBlockDefinitions[blockName] = blockId;
}
}
catch (System.Exception ex)
{
ed.WriteMessage($"\n加载块错误: {file} - {ex.Message}");
}
}
trans.Commit();
}
return true;
}
private void CleanOverlappingScaffold(Database db, List<Point3d> newPoints, Editor ed)
{
var pointSet = new HashSet<Point3dKey>(newPoints.Select(p => new Point3dKey(p)));
using (var trans = db.TransactionManager.StartTransaction())
{
BlockTableRecord modelSpace = trans.GetObject(
SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForWrite) as BlockTableRecord;
List<ObjectId> toErase = new List<ObjectId>();
foreach (ObjectId id in modelSpace)
{
BlockReference blockRef = trans.GetObject(id, OpenMode.ForRead) as BlockReference;
if (blockRef == null) continue;
BlockTableRecord btr = trans.GetObject(blockRef.BlockTableRecord, OpenMode.ForRead) as BlockTableRecord;
string blockName = btr.Name;
// 只清理脚手架图层上的对象
if (blockRef.Layer == PoleLayer ||
blockRef.Layer == HorizontalBarLayer ||
blockRef.Layer == VerticalBarLayer)
{
var blockKey = new Point3dKey(blockRef.Position);
if (pointSet.Contains(blockKey))
{
toErase.Add(id);
}
}
}
foreach (ObjectId id in toErase)
{
Entity entity = trans.GetObject(id, OpenMode.ForWrite) as Entity;
entity?.Erase();
}
trans.Commit();
ed.WriteMessage($"\n清理了 {toErase.Count} 个重叠的脚手架对象");
}
}
private int PlacePoles(Database db, List<Point3d> points, Editor ed)
{
int count = 0;
ObjectId poleBlockId = ObjectId.Null;
using (var trans = db.TransactionManager.StartTransaction())
{
BlockTable blockTable = trans.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
if (blockTable.Has(PoleBlockName)) poleBlockId = blockTable[PoleBlockName];
trans.Commit();
}
if (poleBlockId == ObjectId.Null)
{
ed.WriteMessage($"\n错误: 未找到立杆块定义: {PoleBlockName}");
return 0;
}
int batchCount = (int)Math.Ceiling((double)points.Count / BatchSize);
for (int batchIndex = 0; batchIndex < batchCount; batchIndex++)
{
using (var trans = db.TransactionManager.StartTransaction())
{
BlockTableRecord modelSpace = trans.GetObject(
SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForWrite) as BlockTableRecord;
int startIndex = batchIndex * BatchSize;
int endIndex = Math.Min(startIndex + BatchSize, points.Count);
for (int i = startIndex; i < endIndex; i++)
{
BlockReference blockRef = new BlockReference(points[i], poleBlockId);
blockRef.Layer = PoleLayer;
modelSpace.AppendEntity(blockRef);
trans.AddNewlyCreatedDBObject(blockRef, true);
count++;
}
trans.Commit();
}
}
return count;
}
private int PlaceBars(Database db, List<Point3d> points, List<Line> axisLines, Editor ed)
{
int count = 0;
var barsToPlace = new List<Tuple<Point3d, ObjectId, string>>();
var placedSegments = new HashSet<string>();
var pointSet = new HashSet<Point3dKey>(points.Select(p => new Point3dKey(p)));
// 1. 横杆布置
var horizontalPoints = points
.GroupBy(p => Math.Round(p.Y / Tolerance) * Tolerance)
.Where(g => g.Count() >= 2)
.ToList();
foreach (var group in horizontalPoints)
{
var sortedPoints = group.OrderBy(p => p.X).ToList();
for (int i = 0; i < sortedPoints.Count - 1; i++)
{
Point3d p1 = sortedPoints[i];
Point3d p2 = sortedPoints[i + 1];
double distance = p1.DistanceTo(p2);
if (!IsValidSpacing(distance)) continue;
string segKey = $"{Math.Round(p1.X,4)}_{Math.Round(p1.Y,4)}_{Math.Round(p2.X,4)}_{Math.Round(p2.Y,4)}";
if (placedSegments.Contains(segKey)) continue;
placedSegments.Add(segKey);
ObjectId barBlockId = FindClosestBlock(distance, HorizontalBarPrefix);
if (barBlockId == ObjectId.Null) continue;
Point3d midPoint = new Point3d(
(p1.X + p2.X) / 2.0,
(p1.Y + p2.Y) / 2.0,
0);
barsToPlace.Add(Tuple.Create(midPoint, barBlockId, HorizontalBarLayer));
count++;
}
}
// 2. 横杆布置
var verticalPoints = points
.GroupBy(p => Math.Round(p.X / Tolerance) * Tolerance)
.Where(g => g.Count() >= 2)
.ToList();
foreach (var group in verticalPoints)
{
var sortedPoints = group.OrderBy(p => p.Y).ToList();
for (int i = 0; i < sortedPoints.Count - 1; i++)
{
Point3d p1 = sortedPoints[i];
Point3d p2 = sortedPoints[i + 1];
double distance = p1.DistanceTo(p2);
if (!IsValidSpacing(distance)) continue;
string segKey = $"{Math.Round(p1.X,4)}_{Math.Round(p1.Y,4)}_{Math.Round(p2.X,4)}_{Math.Round(p2.Y,4)}";
if (placedSegments.Contains(segKey)) continue;
placedSegments.Add(segKey);
ObjectId barBlockId = FindClosestBlock(distance, VerticalBarPrefix);
if (barBlockId == ObjectId.Null) continue;
Point3d midPoint = new Point3d(
(p1.X + p2.X) / 2.0,
(p1.Y + p2.Y) / 2.0,
0);
barsToPlace.Add(Tuple.Create(midPoint, barBlockId, VerticalBarLayer));
count++;
}
}
// 批量放置横杆
int batchCount = (int)Math.Ceiling((double)barsToPlace.Count / BatchSize);
for (int batchIndex = 0; batchIndex < batchCount; batchIndex++)
{
using (var trans = db.TransactionManager.StartTransaction())
{
BlockTableRecord modelSpace = trans.GetObject(
SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForWrite) as BlockTableRecord;
int startIndex = batchIndex * BatchSize;
int endIndex = Math.Min(startIndex + BatchSize, barsToPlace.Count);
for (int i = startIndex; i < endIndex; i++)
{
var bar = barsToPlace[i];
BlockReference blockRef = new BlockReference(bar.Item1, bar.Item2);
blockRef.Layer = bar.Item3;
modelSpace.AppendEntity(blockRef);
trans.AddNewlyCreatedDBObject(blockRef, true);
}
trans.Commit();
}
}
return count;
}
private ObjectId FindClosestBlock(double distance, string prefix)
{
double minDiff = double.MaxValue;
ObjectId closestBlock = ObjectId.Null;
foreach (var kvp in _cachedBlockDefinitions)
{
if (!kvp.Key.StartsWith(prefix)) continue;
string lengthStr = kvp.Key.Replace(prefix, "").Replace("mm", "");
if (!double.TryParse(lengthStr, out double length)) continue;
double diff = Math.Abs(length - distance);
if (diff < minDiff && diff <= SpacingTolerance)
{
minDiff = diff;
closestBlock = kvp.Value;
}
}
return closestBlock;
}
private bool IsValidSpacing(double distance)
{
foreach (double spacing in BarLengths)
{
if (Math.Abs(distance - spacing) <= SpacingTolerance) return true;
}
return false;
}
private void CreateLayerIfNotExists(Database db, string layerName, short colorIndex)
{
using (Transaction tr = db.TransactionManager.StartTransaction())
{
LayerTable lt = tr.GetObject(db.LayerTableId, OpenMode.ForRead) as LayerTable;
if (!lt.Has(layerName))
{
lt.UpgradeOpen();
LayerTableRecord ltr = new LayerTableRecord
{
Name = layerName,
Color = Color.FromColorIndex(ColorMethod.ByAci, colorIndex)
};
lt.Add(ltr);
tr.AddNewlyCreatedDBObject(ltr, true);
tr.Commit();
}
}
}
}
// 点比较器(用于Distinct)
public class Point3dEqualityComparer : IEqualityComparer<Point3d>
{
private readonly double _tolerance;
public Point3dEqualityComparer(double tolerance)
{
_tolerance = tolerance;
}
public bool Equals(Point3d p1, Point3d p2)
{
return Math.Abs(p1.X - p2.X) < _tolerance &&
Math.Abs(p1.Y - p2.Y) < _tolerance &&
Math.Abs(p1.Z - p2.Z) < _tolerance;
}
public int GetHashCode(Point3d p)
{
return p.X.GetHashCode() ^ p.Y.GetHashCode() ^ p.Z.GetHashCode();
}
}
}
根据以上代码修改添加自动识别斜正交网格布置横杆