using Autodesk.AutoCAD.ApplicationServices;
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.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace ScaffoldMaterialStatistics
{
public class ScaffoldStatsCommands
{
// 定义材料类型枚举
private enum MaterialType
{
HorizontalBar, // 横杆
VerticalBar, // 立杆
DiagonalBrace, // 水平斜拉杆
SteelTread, // 钢踏板
SteelSection, // 型钢
WallConnector, // 连墙件
Fastener // 扣件
}
// 定义材料信息结构
private struct MaterialInfo
{
public MaterialType Type;
public string Name;
public double Length; // 单位:毫米
public int Quantity;
}
// 预编译正则表达式以提高性能
private static readonly Regex _lengthRegex = new Regex(
@"(\d+\.?\d*)\s*(mm|米|m)|^(\d+\.?\d*)$",
RegexOptions.Compiled | RegexOptions.IgnoreCase
);
private static readonly Regex _diagonalBraceRegex = new Regex(
@"水平斜拉杆",
RegexOptions.Compiled | RegexOptions.IgnoreCase
);
// 缓存常用字符串比较
private static readonly string[] FastenerKeywords = { "扣件" };
private static readonly string[] SteelTreadKeywords = { "钢踏板" };
private static readonly string[] SteelSectionKeywords = { "型钢", "工字钢", "XCG", "悬挑", "90度", "90°", "45度", "45°", "角型钢" };
private static readonly string[] HorizontalBarKeywords = { "横杆" };
private static readonly string[] VerticalBarKeywords = { "立杆" };
private static readonly string[] WallConnectorKeywords = { "连墙件" };
[CommandMethod("MCS", CommandFlags.Modal)]
public void ScaffoldMaterialStatistics()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
try
{
// 提示用户选择对象
PromptSelectionOptions opts = new PromptSelectionOptions();
opts.MessageForAdding = "请框选外架材料: ";
PromptSelectionResult selRes = ed.GetSelection(opts);
if (selRes.Status != PromptStatus.OK)
{
ed.WriteMessage("\n操作已取消。");
return;
}
// 显示处理进度
ed.WriteMessage("\n正在处理选中的对象,请稍候...");
// 使用Stopwatch监控性能
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
List<MaterialInfo> materials;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
// 获取选择集
SelectionSet selSet = selRes.Value;
// 根据对象数量选择合适的处理方法
if (selSet.Count > 10000)
{
ed.WriteMessage("\n检测到大量对象,启用批量处理模式...");
materials = CountMaterialsBatch(selSet, tr, ed);
}
else
{
materials = CountMaterials(selSet, tr, ed);
}
// 检查是否有统计数据
if (materials.Count == 0)
{
ed.WriteMessage("\n未找到可统计的材料!");
return;
}
stopwatch.Stop();
ed.WriteMessage($"\n材料统计完成,耗时: {stopwatch.Elapsed.TotalSeconds:F2}秒");
// 创建统计表格
CreateStatisticsTable(doc, materials, ed);
tr.Commit();
}
ed.WriteMessage("\n外架材料统计完成!");
}
catch (System.Exception ex)
{
ed.WriteMessage($"\n错误: {ex.Message}\n{ex.StackTrace}");
}
}
private List<MaterialInfo> CountMaterials(SelectionSet selSet, Transaction tr, Editor ed)
{
// 使用字典直接存储MaterialInfo结构
Dictionary<string, MaterialInfo> materialDict = new Dictionary<string, MaterialInfo>();
int unrecognizedCount = 0;
int processedCount = 0;
int totalCount = selSet.Count;
// 使用Stopwatch控制进度更新频率
Stopwatch progressTimer = new Stopwatch();
progressTimer.Start();
// 批量处理对象,减少事务开销
ObjectId[] objectIds = selSet.GetObjectIds();
// 预处理图层和块名映射
var layerCache = new Dictionary<ObjectId, string>();
var blockNameCache = new Dictionary<ObjectId, string>();
// 预先获取所有图层和块名
foreach (ObjectId id in objectIds)
{
if (!id.IsValid) continue;
Entity entity = tr.GetObject(id, OpenMode.ForRead) as Entity;
if (entity == null) continue;
layerCache[id] = entity.Layer.ToUpper();
if (entity is BlockReference blockRef)
{
BlockTableRecord btr = tr.GetObject(blockRef.BlockTableRecord, OpenMode.ForRead) as BlockTableRecord;
blockNameCache[id] = btr.Name.ToUpper();
}
}
// 处理每个对象
foreach (ObjectId id in objectIds)
{
if (!id.IsValid) continue;
processedCount++;
// 每处理1000个对象或每2秒更新一次进度(减少频繁更新)
if (processedCount % 1000 == 0 || progressTimer.ElapsedMilliseconds > 2000)
{
ed.WriteMessage($"\n已处理 {processedCount}/{totalCount} 个对象...");
progressTimer.Restart();
}
string layerName = layerCache.ContainsKey(id) ? layerCache[id] : "";
string blockName = blockNameCache.ContainsKey(id) ? blockNameCache[id] : "";
MaterialInfo? info = null;
if (!string.IsNullOrEmpty(blockName))
{
// 解析块名和图层名并获取材料信息
info = ParseBlockAndLayerName(blockName, layerName);
}
else
{
// 非块对象
info = ParseLayerName(layerName);
}
if (info.HasValue)
{
MaterialInfo material = info.Value;
// 应用乘数规则
if (material.Type == MaterialType.SteelTread && !material.Name.Contains("单片"))
{
// 非单片钢踏板数量×3
material.Quantity = 3;
}
else if (material.Type == MaterialType.Fastener && material.Name == "900mm扣件")
{
// 900mm扣件数量×2
material.Quantity = 2;
}
else
{
material.Quantity = 1;
}
// 创建唯一标识键
string key = $"{material.Type}_{material.Name}_{material.Length:F1}";
// 更新材料数量
if (materialDict.TryGetValue(key, out MaterialInfo existing))
{
existing.Quantity += material.Quantity;
materialDict[key] = existing;
}
else
{
materialDict[key] = material;
}
}
else
{
unrecognizedCount++;
}
}
// 输出未识别对象数量
if (unrecognizedCount > 0)
{
ed.WriteMessage($"\n警告: 有 {unrecognizedCount} 个对象未被识别");
}
return materialDict.Values.ToList();
}
// 批量处理方法,用于处理超大量数据
private List<MaterialInfo> CountMaterialsBatch(SelectionSet selSet, Transaction tr, Editor ed, int batchSize = 5000)
{
Dictionary<string, MaterialInfo> materialDict = new Dictionary<string, MaterialInfo>();
int unrecognizedCount = 0;
ObjectId[] allObjectIds = selSet.GetObjectIds();
int totalCount = allObjectIds.Length;
// 使用Stopwatch控制进度更新频率
Stopwatch progressTimer = new Stopwatch();
progressTimer.Start();
for (int i = 0; i < totalCount; i += batchSize)
{
int endIndex = Math.Min(i + batchSize, totalCount);
int batchCount = endIndex - i;
ed.WriteMessage($"\n正在处理批次 {i / batchSize + 1}/{(totalCount + batchSize - 1) / batchSize} ({batchCount}个对象)...");
// 处理当前批次
for (int j = i; j < endIndex; j++)
{
ObjectId id = allObjectIds[j];
if (!id.IsValid) continue;
Entity entity = tr.GetObject(id, OpenMode.ForRead) as Entity;
if (entity == null) continue;
string layerName = entity.Layer.ToUpper();
string blockName = "";
if (entity is BlockReference blockRef)
{
BlockTableRecord btr = tr.GetObject(blockRef.BlockTableRecord, OpenMode.ForRead) as BlockTableRecord;
blockName = btr.Name.ToUpper();
}
MaterialInfo? info = !string.IsNullOrEmpty(blockName) ?
ParseBlockAndLayerName(blockName, layerName) :
ParseLayerName(layerName);
if (info.HasValue)
{
MaterialInfo material = info.Value;
// 应用乘数规则
if (material.Type == MaterialType.SteelTread && !material.Name.Contains("单片"))
{
// 非单片钢踏板数量×3
material.Quantity = 3;
}
else if (material.Type == MaterialType.Fastener && material.Name == "900mm扣件")
{
// 900mm扣件数量×3
material.Quantity = 3;
}
else
{
material.Quantity = 1;
}
// 创建唯一标识键
string key = $"{material.Type}_{material.Name}_{material.Length:F1}";
// 更新材料数量
if (materialDict.TryGetValue(key, out MaterialInfo existing))
{
existing.Quantity += material.Quantity;
materialDict[key] = existing;
}
else
{
materialDict[key] = material;
}
}
else
{
unrecognizedCount++;
}
}
// 每处理完一个批次,更新进度
if (progressTimer.ElapsedMilliseconds > 2000)
{
ed.WriteMessage($"\n已处理 {endIndex}/{totalCount} 个对象...");
progressTimer.Restart();
}
// 可选:定期提交事务以释放内存(但会降低整体性能)
// if (i % 10000 == 0) { tr.Commit(); tr.Start(); }
}
// 输出未识别对象数量
if (unrecognizedCount > 0)
{
ed.WriteMessage($"\n警告: 有 {unrecognizedCount} 个对象未被识别");
}
return materialDict.Values.ToList();
}
// 主要解析函数(块和图层)
private MaterialInfo? ParseBlockAndLayerName(string blockName, string layerName)
{
// 1. 处理扣件 (包含900mm扣件识别)
if (ContainsAny(blockName, FastenerKeywords) || ContainsAny(layerName, FastenerKeywords))
{
bool is900mm = blockName.Contains("900") || layerName.Contains("900");
return new MaterialInfo
{
Type = MaterialType.Fastener,
Name = is900mm ? "900mm扣件" : "标准扣件",
Length = 0
};
}
// 2. 处理钢踏板 (包含单片识别)
if (ContainsAny(blockName, SteelTreadKeywords) || ContainsAny(layerName, SteelTreadKeywords))
{
double length = ExtractLengthFromString(blockName);
if (length <= 0)
{
length = ExtractLengthFromString(layerName);
}
bool isSingle = blockName.Contains("单片") || layerName.Contains("单片");
return new MaterialInfo
{
Type = MaterialType.SteelTread,
Name = isSingle ? "单片钢踏板" : "钢踏板",
Length = length
};
}
// 3. 增强型钢识别
if (IsSteelSection(blockName, layerName))
{
double length = ExtractSteelLength(blockName, layerName);
string name = "型钢";
if (blockName.Contains("工字钢连梁") || layerName.Contains("工字钢连梁"))
name = "工字钢连梁";
else if (blockName.Contains("悬挑") || layerName.Contains("悬挑"))
name = "悬挑型钢";
else if (blockName.Contains("90") || layerName.Contains("90"))
name = "90度型钢";
else if (blockName.Contains("右45") || layerName.Contains("右45"))
name = "右45°角型钢";
else if (blockName.Contains("左45") || layerName.Contains("左45"))
name = "左45°角型钢";
return new MaterialInfo
{
Type = MaterialType.SteelSection,
Name = name,
Length = length
};
}
// 4. 其他材料类型
if (ContainsAny(blockName, HorizontalBarKeywords) || ContainsAny(layerName, HorizontalBarKeywords))
{
double length = ExtractLengthFromString(blockName);
if (length <= 0)
{
length = ExtractLengthFromString(layerName);
}
return new MaterialInfo
{
Type = MaterialType.HorizontalBar,
Name = "横杆",
Length = length
};
}
if (_diagonalBraceRegex.IsMatch(blockName) || _diagonalBraceRegex.IsMatch(layerName))
{
double length = ExtractLengthFromString(blockName);
if (length <= 0)
{
length = ExtractLengthFromString(layerName);
}
return new MaterialInfo
{
Type = MaterialType.DiagonalBrace,
Name = "水平斜拉杆",
Length = length
};
}
if (ContainsAny(blockName, VerticalBarKeywords) || ContainsAny(layerName, VerticalBarKeywords))
{
return new MaterialInfo
{
Type = MaterialType.VerticalBar,
Name = "立杆",
Length = 0
};
}
if (ContainsAny(blockName, WallConnectorKeywords) || ContainsAny(layerName, WallConnectorKeywords))
{
double length = ExtractWallConnectorLength(blockName, layerName);
return new MaterialInfo
{
Type = MaterialType.WallConnector,
Name = "连墙件",
Length = length
};
}
// 未识别
return null;
}
// 图层解析(独立对象)
private MaterialInfo? ParseLayerName(string layerName)
{
// 1. 钢踏板
if (ContainsAny(layerName, SteelTreadKeywords))
{
bool isSingle = layerName.Contains("单片");
return new MaterialInfo
{
Type = MaterialType.SteelTread,
Name = isSingle ? "单片钢踏板" : "钢踏板",
Length = ExtractLengthFromString(layerName)
};
}
// 2. 型钢
if (IsSteelSection("", layerName))
{
double length = ExtractSteelLength("", layerName);
string name = "型钢";
if (layerName.Contains("工字钢连梁")) name = "工字钢连梁";
else if (layerName.Contains("悬挑")) name = "悬挑型钢";
else if (layerName.Contains("90")) name = "90度型钢";
else if (layerName.Contains("右45")) name = "右45°角型钢";
else if (layerName.Contains("左45")) name = "左45°角型钢";
return new MaterialInfo
{
Type = MaterialType.SteelSection,
Name = name,
Length = length
};
}
// 3. 连墙件
if (ContainsAny(layerName, WallConnectorKeywords))
{
return new MaterialInfo
{
Type = MaterialType.WallConnector,
Name = "连墙件",
Length = ExtractWallConnectorLength("", layerName)
};
}
// 4. 扣件
if (ContainsAny(layerName, FastenerKeywords))
{
bool is900mm = layerName.Contains("900");
return new MaterialInfo
{
Type = MaterialType.Fastener,
Name = is900mm ? "900mm扣件" : "标准扣件",
Length = 0
};
}
return null;
}
// 判断是否为型钢
private bool IsSteelSection(string blockName, string layerName)
{
string combined = $"{blockName} {layerName}".ToUpper();
return ContainsAny(combined, SteelSectionKeywords);
}
// 从字符串中提取长度信息
private double ExtractLengthFromString(string input)
{
if (string.IsNullOrEmpty(input)) return 0;
var match = _lengthRegex.Match(input);
if (!match.Success) return 0;
// 处理带单位的数字
if (double.TryParse(match.Groups[1].Value, out double result))
{
string unit = match.Groups[2].Value.ToLower();
// 使用传统的switch语句替换switch表达式
switch (unit)
{
case "米":
case "m":
return result * 1000; // 米转毫米
default:
return result;
}
}
// 处理纯数字
if (double.TryParse(match.Groups[3].Value, out result))
return result;
return 0;
}
// 提取型钢长度(优先考虑图层名)
private double ExtractSteelLength(string blockName, string layerName)
{
// 优先从图层名提取
double length = ExtractLengthFromString(layerName);
if (length > 0) return length;
// 然后从块名提取
return ExtractLengthFromString(blockName);
}
// 提取连墙件长度
private double ExtractWallConnectorLength(string blockName, string layerName)
{
// 优先从图层名提取
double length = ExtractLengthFromString(layerName);
if (length > 0) return length;
// 然后从块名提取
return ExtractLengthFromString(blockName);
}
// 创建统计表格
private void CreateStatisticsTable(Document doc, List<MaterialInfo> materials, Editor ed)
{
Database db = doc.Database;
// 获取插入点
PromptPointResult ppr = ed.GetPoint("\n请指定统计表插入点: ");
if (ppr.Status != PromptStatus.OK)
{
ed.WriteMessage("\n未指定插入点,取消创建表格。");
return;
}
Point3d insertPoint = ppr.Value;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
// 创建表格
Table table = new Table();
table.SetDatabaseDefaults(db);
table.Position = insertPoint;
// 设置表格尺寸
table.NumRows = materials.Count + 3; // 标题+表头+数据行+说明行
table.NumColumns = 4;
// 设置列宽
table.SetColumnWidth(0, 10); // 序号
table.SetColumnWidth(1, 25); // 材料名称
table.SetColumnWidth(2, 25); // 杆件长度
table.SetColumnWidth(3, 15); // 数量
// 设置标题行
table.Cells[0, 0].Value = "外架材料统计表";
table.Cells[0, 0].Alignment = CellAlignment.MiddleCenter;
table.Cells[0, 0].TextHeight = 3.0;
// 合并标题行单元格
table.MergeCells(CellRange.Create(table, 0, 0, 0, 3));
// 设置表头
string[] headers = { "序号", "材料名称", "杆件长度(mm)", "数量" };
for (int i = 0; i < headers.Length; i++)
{
table.Cells[1, i].Value = headers[i];
table.Cells[1, i].Alignment = CellAlignment.MiddleCenter;
table.Cells[1, i].TextHeight = 2.5;
}
// 填充数据
int row = 2;
int index = 1;
foreach (var material in materials.OrderBy(m => m.Type).ThenBy(m => m.Length))
{
table.Cells[row, 0].Value = index++.ToString(); // 序号
table.Cells[row, 1].Value = material.Name; // 材料名称
table.Cells[row, 2].Value = material.Length > 0 ?
material.Length.ToString("F0") : ""; // 杆件长度(取整)
table.Cells[row, 3].Value = material.Quantity; // 数量
// 设置数据单元格样式
for (int j = 0; j < 4; j++)
{
table.Cells[row, j].Alignment = CellAlignment.MiddleCenter;
table.Cells[row, j].TextHeight = 2.0;
}
row++;
}
// 添加说明行
table.Cells[row, 0].Value = "说明:①本表中钢踏板除单钢踏板外其余钢踏板已乘3计算数量;本表中钢踏板按一步一设计算数量。②连墙件按楼层计算数量;扣件按每根连墙件4个扣件计算数量;③本表中横杆计算数量含外侧两道护腰横杆内侧未涉及在内;本表中材料用量为预估算用量;具体实际用量按现场实际施工用量为准。";
table.Cells[row, 0].Alignment = CellAlignment.MiddleLeft;
table.Cells[row, 0].TextHeight = 1.2;
// 合并说明行单元格
table.MergeCells(CellRange.Create(table, row, 0, row, 3));
// 将表格添加到当前空间
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
BlockTableRecord btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
btr.AppendEntity(table);
tr.AddNewlyCreatedDBObject(table, true);
tr.Commit();
}
}
// 辅助方法:检查字符串是否包含任何关键字
private bool ContainsAny(string input, string[] keywords)
{
if (string.IsNullOrEmpty(input)) return false;
foreach (string keyword in keywords)
{
if (input.Contains(keyword)) return true;
}
return false;
}
}
}
1、增加统计步骤:
步骤1:命令栏显示:“请框选外架材料”改成“请框选平面已布置构件”
步骤2:命令栏显示:“请框选第一组立杆”点击鼠标右键可忽略这一步(组件图块名称:ScaffoldPole立杆200mm.dwg,ScaffoldPole立杆350mm.dwg,ScaffoldPole立杆500mm.dwg,ScaffoldPole立杆1000mm.dwg,ScaffoldPole立杆1500mm.dwg,ScaffoldPole立杆2000mm.dwg,ScaffoldPole立杆2500mm.dwg,ScaffoldPole固定桩.dwg,ScaffoldPole底托.dwg)
步骤3:命令栏显示:“请框选第二组立杆”点击鼠标右键可忽略这一步(组件图块名称:ScaffoldPole立杆200mm.dwg,ScaffoldPole立杆350mm.dwg,ScaffoldPole立杆500mm.dwg,ScaffoldPole立杆1000mm.dwg,ScaffoldPole立杆1500mm.dwg,ScaffoldPole立杆2000mm.dwg,ScaffoldPole立杆2500mm.dwg,ScaffoldPole固定桩.dwg,ScaffoldPole底托.dwg)
步骤4:命令栏显示:“请框选立横杆”;点击鼠标右键可忽略这一步
步骤5:命令栏显示:“请框选外架斜拉杆”;点击鼠标右键可忽略这一步
步骤6:命令栏显示:“请框选或点选楼层线”;点击鼠标右键可忽略这一步
2、以上原代码严禁更改只修改以上相关要求的修改部分;统计计算要求如下:
①步骤2和步骤3统计计算要求:按步骤1统计的立杆数量乘以步骤2框选的第一组立杆如2500mm两根除以2倍加上步骤3框选的第二组立杆如2500mm两根除以2倍,按规格统计到表中;举例:
材料名称 杆件长度(mm) 数量
立杆 2500 100
②步骤4统计计算要求:按步骤1统计的横杆数量乘以(步骤4框选立横杆数量除以3)
③步骤5统计计算要求:按步骤1统计的水平斜拉杆数量乘以步骤5框选外架斜拉杆数量
④步骤6统计计算要求:按步骤1统计的连墙件及扣件数量乘以步骤6框选或点选的楼层线数量
⑤若步骤2至步骤6点击鼠标右键忽略后只统计步骤1平面布置的构件数量;如原代码中计算
最新发布