using System.Linq; using System.Text; using Autodesk.Revit.DB; using EPPlus.Core.Extensions.Attributes; namespace Szmedi.RvKits.ModelManager { // ========================================== // 1. 数据模型定义 (Models) // ========================================== public enum DiffType { Added, Deleted, Modified } public class LocationDetail { public string OldValue { get; set; } // 修改前的坐标 (新增时为null) public string NewValue { get; set; } // 修改后的坐标 / 新增时的当前坐标 } public class ParamDetail { public string ParamName { get; set; } public string OldValue { get; set; } public string NewValue { get; set; } public ParamDetail(string name, string oldVal, string newVal) { ParamName = name; OldValue = oldVal; NewValue = newVal; } } /// /// 构件状态快照 (对应 Excel 表格中的半边数据) /// public class ComponentState { public string ElementId { get; set; } = "无"; public string TypeName { get; set; } = "无"; public string DesignProps { get; set; } = "无"; // 设计属性 (多行文本) public string QuantityProps { get; set; } = "无"; // 出量属性 (体积/面积) public string Coordinates { get; set; } = "无"; // 坐标 (X/Y/Z) public bool IsEmpty => ElementId == "无"; } /// /// 报表行数据 /// public class ReportRow { public int Index { get; set; } public string ChangeType { get; set; } // 新增/删除/修改 public ComponentState After { get; set; } = new ComponentState(); // 变更后 public ComponentState Before { get; set; } = new ComponentState(); // 变更前 } public class ElementDiffInfo { [ExcelTableColumn("图元ID")] public ElementId ElementId { get; set; } public string UniqueId { get; set; } public string CategoryName { get; set; } [ExcelTableColumn("类型")] public string Name { get; set; } // 族名称或类型名称 [ExcelTableColumn("修改类型")] public DiffType Status { get; set; } // 位置变更详情 (新增或修改) [ExcelTableColumn("坐标")] public LocationDetail LocationChange { get; set; } [ExcelTableColumn("设计属性")] // 参数变更详情列表 public List ParamChanges { get; set; } = new List(); [ExcelTableColumn("出量属性")] public string CalculatedParameter { get; set; } public bool HasChanges => LocationChange != null || ParamChanges.Count > 0; } // ========================================== // 2. 核心对比逻辑 (ProjectComparer) // ========================================== public class ProjectComparerTest { private List _targetParams; private const double Tolerance = 0.0001; // 几何容差 (英尺) public ProjectComparerTest(List targetParams) { _targetParams = targetParams; } public List CompareDocuments(Document oldDoc, Document newDoc) { var results = new List(); // 1. 获取构件字典,使用 UniqueId 进行匹配 var oldMap = GetElementMap(oldDoc); var newMap = GetElementMap(newDoc); // 2. 遍历新文档 -> 检查 [新增] 和 [修改] foreach (var kvp in newMap) { string uid = kvp.Key; Element newElem = kvp.Value; if (!oldMap.ContainsKey(uid)) { // ================= [新增] ================= var diffInfo = new ElementDiffInfo { ElementId = newElem.Id, UniqueId = uid, CategoryName = newElem.Category?.Name, Name = newElem.Name, Status = DiffType.Added }; // 获取坐标:优先定位点,其次包围盒底部中心 string locStr = GetSmartCoordinate(newElem); if (!string.IsNullOrEmpty(locStr)) { // 新增时,将坐标存入 NewValue diffInfo.LocationChange = new LocationDetail { OldValue = null, NewValue = locStr }; } results.Add(diffInfo); } else { // ================= [修改] ================= Element oldElem = oldMap[uid]; // A. 检查位置 (仅检查 LocationPoint 类型的变化) LocationDetail locDiff = CheckPointLocationChange(oldElem, newElem); // B. 检查参数 (包含 Name 和 用户自定义参数) List paramDiffs = CheckParamChanges(oldElem, newElem); // 只有当 位置变了 或 参数变了 时才记录 if (locDiff != null || paramDiffs.Count > 0) { results.Add(new ElementDiffInfo { ElementId = newElem.Id, UniqueId = uid, CategoryName = newElem.Category?.Name, Name = newElem.Name, Status = DiffType.Modified, LocationChange = locDiff, // 包含 OldValue 和 NewValue ParamChanges = paramDiffs }); } } } // 3. 遍历旧文档 -> 检查 [删除] foreach (var kvp in oldMap) { if (!newMap.ContainsKey(kvp.Key)) { // ================= [删除] ================= results.Add(new ElementDiffInfo { ElementId = kvp.Value.Id, UniqueId = kvp.Key, CategoryName = kvp.Value.Category?.Name, Name = kvp.Value.Name, Status = DiffType.Deleted }); } } return results; } // -------------------------------------------------------- // 辅助方法区域 // -------------------------------------------------------- /// /// 智能获取构件坐标 (用于新增构件的定位描述) /// private string GetSmartCoordinate(Element elem) { XYZ p = null; // 策略1: 如果是基于点的构件 (柱、门、窗、家具等) if (elem.Location is LocationPoint lp) { p = lp.Point; } // 策略2: 如果是基于线或轮廓的构件 (墙、梁、楼板),取包围盒底部中心 else { BoundingBoxXYZ box = elem.get_BoundingBox(null); if (box != null) { double cx = (box.Min.X + box.Max.X) / 2.0; double cy = (box.Min.Y + box.Max.Y) / 2.0; double cz = box.Min.Z; // 底部 Z p = new XYZ(cx, cy, cz); } } return p != null ? FormatPoint(p) : ""; } /// /// 检查位置变化 (严格模式:仅检查 Point 类型) /// private LocationDetail CheckPointLocationChange(Element oldElem, Element newElem) { // 只有当两个版本都是 LocationPoint 时才对比位置 // 避免对比墙体等 LocationCurve 导致的复杂性 if (oldElem.Location is LocationPoint lpOld && newElem.Location is LocationPoint lpNew) { // 1. 比较坐标 bool posChanged = !lpOld.Point.IsAlmostEqualTo(lpNew.Point, Tolerance); // 2. 比较旋转 (如果不关心旋转可注释掉) bool rotChanged = Math.Abs(lpOld.Rotation - lpNew.Rotation) > Tolerance; if (posChanged || rotChanged) { return new LocationDetail { OldValue = FormatPoint(lpOld.Point), NewValue = FormatPoint(lpNew.Point) }; } } return null; // 无变化或不支持的定位类型 } /// /// 检查参数变化 (包含构件名和自定义参数) /// private List CheckParamChanges(Element oldElem, Element newElem) { var list = new List(); // 1. 检查构件名 (Name) if (oldElem.Name != newElem.Name) { list.Add(new ParamDetail("构件名", oldElem.Name, newElem.Name)); } // 2. 检查自定义参数 foreach (var paramName in _targetParams) { string vOld = GetParamValueString(oldElem, paramName); string vNew = GetParamValueString(newElem, paramName); if (vOld != vNew) { list.Add(new ParamDetail(paramName, vOld, vNew)); } } return list; } /// /// 获取参数值的安全字符串形式 /// private string GetParamValueString(Element elem, string paramName) { Parameter p = elem.LookupParameter(paramName); if (p == null) return ""; // 或者返回 "" // 优先返回用户可见的格式化字符串 (如 "1200 mm") if (p.StorageType == StorageType.String) return p.AsString() ?? ""; string val = p.AsValueString(); if (!string.IsNullOrEmpty(val)) return val; // 如果 AsValueString 为空,读取底层数据 switch (p.StorageType) { case StorageType.Integer: return p.AsInteger().ToString(); case StorageType.Double: return p.AsDouble().ToString("F2"); case StorageType.ElementId: return p.AsElementId().ToString(); default: return ""; } } /// /// 获取需要对比的构件集合 /// private Dictionary GetElementMap(Document doc) { // 过滤规则:非类型、非视图相关、有材质(通常代表实体) return new FilteredElementCollector(doc) .WhereElementIsNotElementType() .WhereElementIsViewIndependent() .Where(e => e.Category != null && e.Category.HasMaterialQuantities) .ToDictionary(e => e.UniqueId); } /// /// 统一坐标格式化 /// private string FormatPoint(XYZ p) { // 内部单位是英尺,如需毫米显示请在此处转换 // 例如: double x = p.X * 304.8; return $"({p.X:F1}, {p.Y:F1}, {p.Z:F1})"; } } public class ProjectComparer { public ProjectComparer(List designParams, List quantityParams) { this.designParams = designParams; this.quantityParams = quantityParams; } //设计参数 private readonly List designParams; // 配置:需要在“出量属性”栏显示的参数 private readonly List quantityParams; public List CompareDocuments(Document oldDoc, Document newDoc) { var rows = new List(); var oldMap = GetElementMap(oldDoc); var newMap = GetElementMap(newDoc); int indexCounter = 1; // --- A. 遍历新文档 (查找 [新增] 和 [修改]) --- foreach (var kvp in newMap) { string uid = kvp.Key; Element newElem = kvp.Value; if (!oldMap.ContainsKey(uid)) { // === 新增构件 === rows.Add(new ReportRow { Index = indexCounter++, ChangeType = "新增构件", After = ExtractState(newElem), Before = new ComponentState { ElementId = "无" } // 空状态 }); } else { // === 修改构件 === Element oldElem = oldMap[uid]; // 判断是否有实质性修改 (参数或位置) if (IsElementModified(oldElem, newElem)) { rows.Add(new ReportRow { Index = indexCounter++, ChangeType = "修改构件", After = ExtractState(newElem), Before = ExtractState(oldElem) }); } } } // --- B. 遍历旧文档 (查找 [删除]) --- foreach (var kvp in oldMap) { if (!newMap.ContainsKey(kvp.Key)) { // === 删除构件 === rows.Add(new ReportRow { Index = indexCounter++, ChangeType = "删除构件", After = new ComponentState { ElementId = "无" }, // 空状态 Before = ExtractState(kvp.Value) }); } } return rows; } // --- 提取构件信息为状态对象 --- private ComponentState ExtractState(Element e) { var state = new ComponentState { ElementId = e.Id.ToString(), TypeName = e.Name // 这里的Name通常是族类型名称 }; // 1. 提取设计属性 (拼接成换行字符串) var sbDesign = new StringBuilder(); foreach (var paramName in designParams) { string val = GetParamValue(e, paramName); if (!string.IsNullOrEmpty(val)) { // Excel 单元格内换行使用 Environment.NewLine sbDesign.AppendLine($"{paramName}: {val}"); } } state.DesignProps = sbDesign.Length > 0 ? sbDesign.ToString().TrimEnd() : ""; // 2. 提取出量属性 var sbQty = new StringBuilder(); foreach (var paramName in quantityParams) { string val = GetParamValue(e, paramName); // 排除 0.00 的情况 if (!string.IsNullOrEmpty(val) && val != "0.00" && val != "0") { sbQty.AppendLine($"{paramName}: {val}"); } } state.QuantityProps = sbQty.Length > 0 ? sbQty.ToString().TrimEnd() : ""; // 3. 提取坐标 (竖排显示) state.Coordinates = GetFormattedCoordinates(e); return state; } // --- 辅助:坐标格式化 (核心逻辑:有定位点取点,没点取包围盒) --- private string GetFormattedCoordinates(Element e) { XYZ p = null; // 策略1: 基于点 (柱、门、窗、家具) if (e.Location is LocationPoint lp) { p = lp.Point; } // 策略2: 基于线或形状 (墙、梁、板) -> 取包围盒底部中心 else { var box = e.get_BoundingBox(null); if (box != null) { double cx = (box.Min.X + box.Max.X) / 2.0; double cy = (box.Min.Y + box.Max.Y) / 2.0; double cz = box.Min.Z; p = new XYZ(cx, cy, cz); } } if (p == null) return "无"; // 格式化为 Excel 竖排效果,并转换单位 (英尺 -> 毫米) double toMm = 304.8; return $"X: {(p.X * toMm):F2}\nY: {(p.Y * toMm):F2}\nZ: {(p.Z * toMm):F2}"; } // --- 辅助:判断是否修改 --- private bool IsElementModified(Element oldE, Element newE) { // 1. 名字变了 if (oldE.Name != newE.Name) return true; // 2. 坐标变了 if (GetFormattedCoordinates(oldE) != GetFormattedCoordinates(newE)) return true; // 3. 关键参数变了 (可选:遍历 _designParams 检查) // 这里简单示例:如果提取出的设计属性字符串不一致,就算修改 // 实际为了性能,建议在这里写单独的参数对比循环 var s1 = ExtractState(oldE).DesignProps; var s2 = ExtractState(newE).DesignProps; if (s1 != s2) return true; return false; } // --- 辅助:获取参数值 --- private string GetParamValue(Element e, string name) { Parameter p = e.LookupParameter(name); if (p == null) return ""; if (p.StorageType == StorageType.String) return p.AsString(); return p.AsValueString(); // AsValueString 会带单位 } // --- 辅助:获取元素字典 --- private Dictionary GetElementMap(Document doc) { return new FilteredElementCollector(doc) .WhereElementIsNotElementType() .WhereElementIsViewIndependent() .Where(e => e.Category != null && e.Category.HasMaterialQuantities) // 简单的实体过滤 .ToDictionary(e => e.UniqueId); } } }