大量更新

This commit is contained in:
GG Z
2025-12-23 21:37:02 +08:00
parent 3fc465959b
commit b611efeed9
105 changed files with 5814 additions and 371 deletions

View File

@@ -0,0 +1,507 @@
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;
}
}
/// <summary>
/// 构件状态快照 (对应 Excel 表格中的半边数据)
/// </summary>
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 == "无";
}
/// <summary>
/// 报表行数据
/// </summary>
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<ParamDetail> ParamChanges { get; set; } = new List<ParamDetail>();
[ExcelTableColumn("出量属性")]
public string CalculatedParameter { get; set; }
public bool HasChanges => LocationChange != null || ParamChanges.Count > 0;
}
// ==========================================
// 2. 核心对比逻辑 (ProjectComparer)
// ==========================================
public class ProjectComparerTest
{
private List<string> _targetParams;
private const double Tolerance = 0.0001; // 几何容差 (英尺)
public ProjectComparerTest(List<string> targetParams)
{
_targetParams = targetParams;
}
public List<ElementDiffInfo> CompareDocuments(Document oldDoc, Document newDoc)
{
var results = new List<ElementDiffInfo>();
// 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<ParamDetail> 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;
}
// --------------------------------------------------------
// 辅助方法区域
// --------------------------------------------------------
/// <summary>
/// 智能获取构件坐标 (用于新增构件的定位描述)
/// </summary>
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) : "";
}
/// <summary>
/// 检查位置变化 (严格模式:仅检查 Point 类型)
/// </summary>
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; // 无变化或不支持的定位类型
}
/// <summary>
/// 检查参数变化 (包含构件名和自定义参数)
/// </summary>
private List<ParamDetail> CheckParamChanges(Element oldElem, Element newElem)
{
var list = new List<ParamDetail>();
// 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;
}
/// <summary>
/// 获取参数值的安全字符串形式
/// </summary>
private string GetParamValueString(Element elem, string paramName)
{
Parameter p = elem.LookupParameter(paramName);
if (p == null) return "<null>"; // 或者返回 ""
// 优先返回用户可见的格式化字符串 (如 "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 "";
}
}
/// <summary>
/// 获取需要对比的构件集合
/// </summary>
private Dictionary<string, Element> GetElementMap(Document doc)
{
// 过滤规则:非类型、非视图相关、有材质(通常代表实体)
return new FilteredElementCollector(doc)
.WhereElementIsNotElementType()
.WhereElementIsViewIndependent()
.Where(e => e.Category != null && e.Category.HasMaterialQuantities)
.ToDictionary(e => e.UniqueId);
}
/// <summary>
/// 统一坐标格式化
/// </summary>
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<string> designParams, List<string> quantityParams)
{
this.designParams = designParams;
this.quantityParams = quantityParams;
}
//设计参数
private readonly List<string> designParams;
// 配置:需要在“出量属性”栏显示的参数
private readonly List<string> quantityParams;
public List<ReportRow> CompareDocuments(Document oldDoc, Document newDoc)
{
var rows = new List<ReportRow>();
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<string, Element> GetElementMap(Document doc)
{
return new FilteredElementCollector(doc)
.WhereElementIsNotElementType()
.WhereElementIsViewIndependent()
.Where(e => e.Category != null && e.Category.HasMaterialQuantities) // 简单的实体过滤
.ToDictionary(e => e.UniqueId);
}
}
}