Files
ShrlAlgoToolkit/ShrlAlgoToolkit.RevitAddins/Checker/ModelCheckViewModel.cs

657 lines
25 KiB
C#
Raw Normal View History

2024-09-22 11:05:41 +08:00
using System.Collections.ObjectModel;
2026-02-21 16:31:24 +08:00
using System.IO;
using System.Windows;
2024-09-22 11:05:41 +08:00
using System.Windows.Data;
2026-02-21 16:31:24 +08:00
2024-09-22 11:05:41 +08:00
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Architecture;
using Autodesk.Revit.DB.Electrical;
using Autodesk.Revit.DB.Mechanical;
using Autodesk.Revit.DB.Plumbing;
2026-02-21 16:31:24 +08:00
2024-09-22 11:05:41 +08:00
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
2026-02-21 16:31:24 +08:00
2024-09-22 11:05:41 +08:00
using Microsoft.Win32;
2026-02-21 16:31:24 +08:00
using MiniExcelLibs;
using MiniExcelLibs.OpenXml;
2024-09-22 11:05:41 +08:00
using Nice3point.Revit.Toolkit.External.Handlers;
2026-02-21 16:31:24 +08:00
2026-02-22 20:03:42 +08:00
using ShrlAlgoToolkit.RevitAddins.Common.Controls;
2024-09-22 11:05:41 +08:00
2025-04-24 20:56:44 +08:00
namespace ShrlAlgoToolkit.RevitAddins.ModelManager;
2024-09-22 11:05:41 +08:00
2026-02-12 21:29:00 +08:00
[UsedImplicitly]
2024-09-22 11:05:41 +08:00
public partial class ModelCheckViewModel : ObservableObject
{
2026-02-22 20:03:42 +08:00
private readonly Standardizer.CorrectReferLevelExecutes correctReferLevelExecutes;
2024-09-22 11:05:41 +08:00
private readonly ActionEventHandler modifyModel = new();
private readonly ActionEventHandler showElementsSectionBox = new();
2024-09-22 11:05:41 +08:00
private readonly UIApplication uiapp;
public ModelCheckViewModel(UIApplication uiapp)
{
Items = [];
var cv = CollectionViewSource.GetDefaultView(Items);
cv.GroupDescriptions.Add(new PropertyGroupDescription("ErrorMessage"));
2026-02-22 20:03:42 +08:00
correctReferLevelExecutes = new Standardizer.CorrectReferLevelExecutes(uiapp);
2024-09-22 11:05:41 +08:00
this.uiapp = uiapp;
FindBasePoint();
}
private bool CanExport()
{
return Items.Any();
}
private static bool CanShowElement(object obj)
{
return obj is MessageModel { IsInstance: true };
}
2024-09-22 11:05:41 +08:00
[RelayCommand]
private /*async Task*/ void CheckModel(/*CancellationToken token*/)
{
//if (Items.Any())
//{
// Items = [];
//}
Items.Clear();
var uidoc = uiapp.ActiveUIDocument;
var doc = uidoc.Document;
//await Task.Delay(TimeSpan.FromSeconds(2), token);
//if (doc.ActiveView is not View3D view3d)
//{
// view3d = doc.OfClass<View3D>().FirstOrDefault(e => FilteredElementCollector.IsViewValidForElementIteration(doc, e.Id)) as View3D;
//}
2024-12-22 10:26:12 +08:00
var elements = doc.OfParentModelElements();
2026-02-12 21:29:00 +08:00
var typeInstancesGroups = elements.GroupBy(e => e.GetTypeId()).ToList();
2024-09-22 11:05:41 +08:00
if (IsCheckLevel)
{
var levels = doc.OfClass<Level>().OfType<Level>().OrderBy(l => l.Elevation);
2026-02-22 20:03:42 +08:00
var levelOutlines = Standardizer.CorrectReferLevelExecutes.GetRegions(levels);
2024-09-22 11:05:41 +08:00
foreach (var keyPair in levelOutlines)
{
//得到在标高范围内的元素
BoundingBoxIsInsideFilter insideFilter = new(keyPair.Value);
var level = keyPair.Key;
var insideCollector = new FilteredElementCollector(doc)
.WherePasses(insideFilter)
.Where(elem => elem.get_BoundingBox(doc.ActiveView) != null)
.ToList();
foreach (var elem in insideCollector)
{
var isCorrect = false;
var hasReferenceLevel = false;
foreach (Parameter param in elem.Parameters)
{
if (param.Definition.Name.Contains("标高") || param.Definition.Name.Contains("Level"))
{
hasReferenceLevel = param.StorageType == StorageType.ElementId;
if (hasReferenceLevel)
{
var value = param.AsElementId();
isCorrect = value == ElementId.InvalidElementId || param.AsElementId() == level.Id; //扶手等对象有宿主Host时为空
}
}
}
if (hasReferenceLevel && !isCorrect)
{
Items.Add(new MessageModel(elem, "参照标高有误"));
}
}
}
}
if (IsCheckName)
{
foreach (var item in typeInstancesGroups)
{
var id = item.Key;
var type = doc.GetElement(id);
var array = type.Name.Split('-');
2026-02-22 20:03:42 +08:00
if (array.Length < 3)
2024-09-22 11:05:41 +08:00
{
var errorItem = new MessageModel(type, "类型名称不符合三段式");
Items.Add(errorItem);
}
2026-02-22 20:03:42 +08:00
2024-09-22 11:05:41 +08:00
}
2026-02-22 20:03:42 +08:00
2024-09-22 11:05:41 +08:00
}
if (IsCheckProps)
{
2026-02-22 20:03:42 +08:00
2024-09-22 11:05:41 +08:00
}
if (IsCheckSlope)
{
var mepCurves = doc.OfClass<MEPCurve>().Where(e => e is not InsulationLiningBase and not FlexDuct and not FlexPipe);
foreach (var mepCurve in mepCurves)
{
var isError = false;
if (mepCurve is Pipe)
{
var param = mepCurve.get_Parameter(BuiltInParameter.RBS_PIPE_SLOPE);
isError = param.HasValue && ((param.AsDouble() < 0.25 && param.AsDouble() > 10E-6) || param.AsDouble() > Math.PI); //坡度过大或过小时,90度时AsDouble为0
}
else if (mepCurve is Duct)
{
var param = mepCurve.get_Parameter(BuiltInParameter.RBS_DUCT_SLOPE);
isError = param.HasValue && ((param.AsDouble() < 0.25 && param.AsDouble() > 10E-6) || param.AsDouble() > Math.PI); //坡度过大或过小时,90度时AsDouble为0
}
else if (mepCurve is Conduit or CableTray)
{
var p1 = mepCurve.get_Parameter(BuiltInParameter.RBS_START_OFFSET_PARAM).AsDouble();
var p2 = mepCurve.get_Parameter(BuiltInParameter.RBS_END_OFFSET_PARAM).AsDouble();
var l = mepCurve.get_Parameter(BuiltInParameter.CURVE_ELEM_LENGTH).AsDouble();
var sin = Math.Abs(p1 - p2) / l;
var radian = Math.Asin(sin);
var slope = Math.Tan(radian);
isError = slope is (> 10E-6 and < 0.25) || (slope > Math.PI && Math.Abs(radian - Math.PI / 2) > 10E-6);
}
if (isError)
{
var errorItem = new MessageModel(mepCurve, "管线坡度有误差");
Items.Add(errorItem);
}
}
}
if (IsCheckLength)
{
var mepCurves = doc.OfClass<MEPCurve>().Where(e => e is not InsulationLiningBase);
foreach (var mepCurve in mepCurves)
{
var length = mepCurve.get_Parameter(BuiltInParameter.CURVE_ELEM_LENGTH).AsDouble();
var connectors = mepCurve.GetConnectors(true);
if (length < 500 / 304.8 && connectors.Size == 2)
{
var errorItem = new MessageModel(mepCurve, "管道孤立或长度有误");
Items.Add(errorItem);
}
}
}
if (IsCheckEqual)
{
var mepCurves = doc.OfClass<MEPCurve>().Where(e => e is not InsulationLiningBase).ToList();
foreach (var c1 in mepCurves)
{
foreach (var c2 in mepCurves)
{
if (c1.Id != c2.Id)
{
2024-12-22 10:26:12 +08:00
var result = c1.GetCurve().Intersect(c2.GetCurve());
2024-09-22 11:05:41 +08:00
if (result == SetComparisonResult.Equal)
{
var errorItem = new MessageModel(c1, "管线存在重叠");
Items.Add(errorItem);
}
}
}
}
}
if (IsCheckSymbolGeometry)
{
foreach (var grouping in typeInstancesGroups)
{
var symbolId = grouping.Key;
if (doc.GetElement(symbolId) is not FamilySymbol symbol)
{
continue;
}
symbol.get_Geometry(new Options());
Options option = new() { ComputeReferences = true, DetailLevel = ViewDetailLevel.Fine };
var geometryElement = symbol.get_Geometry(option);
if (geometryElement == null)
{
continue;
}
var count = 0;
foreach (var geomObj in geometryElement)
{
if (geomObj is GeometryInstance geomInstance)
{
#if REVIT2018 || REVIT2020
if (geomInstance.Symbol is CADLinkType)
{
var errorItem = new MessageModel(symbol, "构件类型包含dwg模型");
Items.Add(errorItem);
}
#elif REVIT2025
if (doc.GetElement(geomInstance.GetSymbolGeometryId().SymbolId) is CADLinkType)
{
var errorItem = new MessageModel(symbol, "构件类型包含dwg模型");
Items.Add(errorItem);
}
#endif
}
if (geomObj is Solid)
{
count++;
}
}
if (count > 50)
{
var errorItem = new MessageModel(symbol, "构件类型几何需考虑简化超过50个实体");
Items.Add(errorItem);
}
}
}
ErrorCount = Items.Count;
ExportToExcelCommand.NotifyCanExecuteChanged();
}
[RelayCommand(CanExecute = nameof(CanExport))]
private void ExportToExcel()
{
var fileName = uiapp.ActiveUIDocument.Document.Title;
var dialog = new SaveFileDialog()
{
Title = "导出结果",
AddExtension = true,
DefaultExt = ".xlsx",
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
Filter = "Excel表格(*.xlsx)|*.xlsx",
OverwritePrompt = true,
FileName = $"{fileName.Substring(0, fileName.Length - 4)}_检查结果"
};
if (dialog.ShowDialog() != true)
{
return;
}
//ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial;
try
{
2026-02-21 16:31:24 +08:00
var config = new OpenXmlConfiguration()
{
TableStyles = TableStyles.Default,
AutoFilter = true,
};
Dictionary<string, MessageModel[]> sheets = Items.GroupBy(e => e.ErrorMessage).ToDictionary(g => g.Key, g => g.ToArray());
MiniExcel.SaveAs(dialog.FileName, sheets);
2024-09-22 11:05:41 +08:00
2026-02-21 16:31:24 +08:00
//EPPlusHelper.WriteExcel(
// dialog.FileName,
// m =>
// {
// IEnumerable<IGrouping<string, MessageModel>> groups = Items.GroupBy(e => e.ErrorMessage);
// foreach (var group in groups)
// {
// var sheet = m.Workbook.Worksheets.Add(group.Key);
// for (var i = -1; i < group.Count(); i++)
// {
// if (i == -1)
// {
// sheet.Cells[1, 1].Value = "元素ID";
// sheet.Cells[1, 2].Value = "族类别";
// sheet.Cells[1, 3].Value = "类型或实例名称";
// sheet.Cells[1, 4].Value = "参照标高";
// sheet.Cells[1, 5].Value = "系统";
// sheet.Cells[1, 6].Value = "主体";
// sheet.Cells[1, 7].Value = "房间";
// }
// else
// {
// var elem = group.ElementAt(i).Element;
// var type = elem.GetType();
// var host = type.GetProperty("Host");
// var level = type.GetProperty("ReferenceLevel");
// var mepSystem = type.GetProperty("MEPSystem");
// var room = type.GetProperty("Room");
// sheet.Cells[i + 2, 1].Value = elem.Id.ToString();
// sheet.Cells[i + 2, 2].Value = elem.Category.Name;
// sheet.Cells[i + 2, 3].Value = elem.Name;
// if (level != null)
// {
// sheet.Cells[i + 2, 4].Value = (level.GetValue(elem, null) as Level)?.Name;
// }
// if (mepSystem != null)
// {
// sheet.Cells[i + 2, 5].Value = (mepSystem.GetValue(elem, null) as MEPSystem)?.Name;
// }
// if (host != null)
// {
// sheet.Cells[i + 2, 6].Value = (host.GetValue(elem, null) as Element)?.Name;
// }
// if (room != null)
// {
// sheet.Cells[i + 2, 7].Value = (room.GetValue(room, null) as Room)?.Name;
// }
// }
// }
// EPPlusHelper.SetTitle(sheet, 1, 1, 1, 7);
// EPPlusHelper.SetStyle(sheet, 2, 1, group.Count() + 1, 7);
// var range = sheet.Cells[1, 1, group.Count() + 1, 7];
// range.AutoFitColumns();
// }
// }
//);
ExportErrorMessageToExcel(dialog.FileName);
Common.Assists.WinDialogAssist.OpenFolderAndSelectFile(dialog.FileName);
2024-09-22 11:05:41 +08:00
}
catch (Exception ex)
{
2026-02-21 16:31:24 +08:00
Common.Assists.LogAssist.ToLog(ex.Message);
2024-09-22 11:05:41 +08:00
}
}
2026-02-21 16:31:24 +08:00
private void ExportErrorMessageToExcel(string filePath)
{
// 1. 准备大字典,用于存放所有 Sheet 数据
var allSheetsData = new Dictionary<string, object>();
// 2. 分组逻辑保持不变
IEnumerable<IGrouping<string, MessageModel>> groups = Items.GroupBy(e => e.ErrorMessage);
foreach (var group in groups)
{
// 3. 构造当前 Sheet 的行数据列表
var sheetRows = new List<Dictionary<string, object>>();
foreach (var item in group)
{
var elem = item.Element;
if (elem == null) continue;
var type = elem.GetType();
// 创建一行数据
var row = new Dictionary<string, object>();
// 直接赋值固定属性
row["元素ID"] = elem.Id.ToString();
row["族类别"] = elem.Category?.Name ?? "";
row["类型或实例名称"] = elem.Name;
// 使用反射获取动态属性(保留你原有的逻辑)
var levelProp = type.GetProperty("ReferenceLevel");
var mepProp = type.GetProperty("MEPSystem");
var hostProp = type.GetProperty("Host");
var roomProp = type.GetProperty("Room");
2024-09-22 11:05:41 +08:00
2026-02-21 16:31:24 +08:00
row["参照标高"] = levelProp != null ? (levelProp.GetValue(elem, null) as Level)?.Name : "";
row["系统"] = mepProp != null ? (mepProp.GetValue(elem, null) as MEPSystem)?.Name : "";
row["主体"] = hostProp != null ? (hostProp.GetValue(elem, null) as Element)?.Name : "";
// 注意:原代码中 room.GetValue(room, null) 应该是 roomProp.GetValue(elem, null) 的笔误
row["房间"] = roomProp != null ? (roomProp.GetValue(elem, null) as Room)?.Name : "";
sheetRows.Add(row);
}
// 4. 处理 Sheet 名称Excel 限制 31 个字符且不能有特殊字符)
string sheetName = group.Key ?? "未命名";
char[] invalidChars = { '\\', '/', '?', '*', '[', ']', ':' };
foreach (var c in invalidChars) sheetName = sheetName.Replace(c, '_');
if (sheetName.Length > 31) sheetName = sheetName.Substring(0, 31);
// 防止同名 Sheet 冲突
if (allSheetsData.ContainsKey(sheetName))
sheetName = sheetName.Substring(0, Math.Min(sheetName.Length, 25)) + "_" + Guid.NewGuid().ToString().Substring(0, 4);
allSheetsData.Add(sheetName, sheetRows);
}
// 5. 保存文件
try
{
// MiniExcel 默认会将 Dictionary 的 Key 作为表头并加粗
MiniExcel.SaveAs(filePath, allSheetsData, overwriteFile: true);
}
catch (IOException)
{
// 提示文件被占用
MessageBox.Show("请先关闭已打开的 Excel 文件后再尝试保存。");
}
}
private void FindBasePoint()
{
var basePoints = uiapp.ActiveUIDocument.Document.OfClass<BasePoint>().OfType<BasePoint>();
foreach (var item in basePoints)
{
//南北
var ns = Math.Round(
item.get_Parameter(BuiltInParameter.BASEPOINT_NORTHSOUTH_PARAM).AsDouble() * 0.3048,
4,
MidpointRounding.AwayFromZero
);
//东西
var ew = Math.Round(item.get_Parameter(BuiltInParameter.BASEPOINT_EASTWEST_PARAM).AsDouble() * 0.3048, 4, MidpointRounding.AwayFromZero);
//高程
var elev = Math.Round(
item.get_Parameter(BuiltInParameter.BASEPOINT_ELEVATION_PARAM).AsDouble() * 0.3048,
4,
MidpointRounding.AwayFromZero
);
if (item.IsShared)//测量点
{
SharedBasePoint = $"南北:{ns}m东西{ew}m高程{elev}m";
continue;
}
if (item.Category.GetHashCode() == -2001271)//项目基点
{
//正北角度
var angle = item.get_Parameter(BuiltInParameter.BASEPOINT_ANGLETON_PARAM).AsValueString();
ProjectBasePoint = $"南北:{ns}m东西{ew}m高程{elev}m角度{angle}";
}
}
}
2024-09-22 11:05:41 +08:00
[RelayCommand]
private void ModifyModel()
{
var errorItems = new List<MessageModel>();
modifyModel.Raise(_ =>
{
var uidoc = uiapp.ActiveUIDocument;
var doc = uidoc.Document;
doc.Invoke(
_ =>
{
#region
var errorItems1 = correctReferLevelExecutes.SetWalls();
errorItems.AddRange(errorItems1);
var errorItems2 = correctReferLevelExecutes.SetInstances();
errorItems.AddRange(errorItems2);
var errorItems3 = correctReferLevelExecutes.SetMEPCurves();
errorItems.AddRange(errorItems3);
#endregion
#region
foreach (var error in Items)
{
if (error.ErrorMessage != "管线坡度有误差")
{
continue;
}
var elem = error.Element;
if (elem is MEPCurve mep)
{
var loc = mep.Location as LocationCurve;
var line = loc!.Curve as Line;
var endPoint = line!.GetEndPoint(0);
var endPoint1 = line.GetEndPoint(1);
Line unboundLine;
Line l;
var xoy1 = endPoint.Flatten();
var xoy2 = endPoint1.Flatten();
var tan = Math.Abs(endPoint.Z - endPoint1.Z) / xoy1.DistanceTo(xoy2);
var dir = -XYZ.BasisZ;
if (endPoint.Z > endPoint1.Z) //以高点作为基准,修改低点
{
if (tan > 1)
{
unboundLine = Line.CreateUnbound(endPoint, dir);
var startPoint = unboundLine.Project(endPoint1).XYZPoint;
l = Line.CreateBound(endPoint, startPoint);
}
else
{
dir = xoy2 - xoy1;
unboundLine = Line.CreateUnbound(endPoint, dir);
var startPoint = unboundLine.Project(endPoint1).XYZPoint;
l = Line.CreateBound(endPoint, startPoint);
}
}
else
{
if (tan > 1)
{
unboundLine = Line.CreateUnbound(endPoint1, dir);
var startPoint = unboundLine.Project(endPoint).XYZPoint;
l = Line.CreateBound(startPoint, endPoint1);
}
else
{
dir = xoy2 - xoy1;
unboundLine = Line.CreateUnbound(endPoint1, dir);
var startPoint = unboundLine.Project(endPoint).XYZPoint;
l = Line.CreateBound(startPoint, endPoint1);
}
}
loc.Curve = l;
}
}
#endregion
},
"模型调整"
);
if (errorItems.Count == 0)
{
return;
}
2026-02-21 16:31:24 +08:00
Common.Assists.WinDialogAssist.ShowOrActivate<MessageWin, MessageViewModel>(uiapp.ActiveUIDocument, errorItems, "未解决错误");
2024-09-22 11:05:41 +08:00
});
}
[RelayCommand(CanExecute = nameof(CanShowElement))]
private void ShowElement(object obj)
{
if (obj is MessageModel model)
{
var uidoc = uiapp.ActiveUIDocument;
var doc = uidoc.Document;
//if (UiDocument.ActiveView.IsTemporaryHideIsolateActive())
//{
// UiDocument.ActiveView.temporary
//}
if (model.Element.IsValidObject)
{
if (model.Element.IsHidden(uidoc.ActiveView))
{
return;
}
List<ElementId> ids = [model.Element.Id];
//UiDocument.ActiveView.IsolateElementTemporary(model.ElementToMove.ViewId);
2024-09-22 11:05:41 +08:00
showElementsSectionBox.Raise(_ =>
{
if (uidoc.ActiveView is not View3D view3d)
{
view3d =
doc.OfClass<View3D>().FirstOrDefault(e => FilteredElementCollector.IsViewValidForElementIteration(doc, e.Id)) as View3D;
}
uidoc.ActiveView = view3d;
doc.Invoke(_ =>
{
if (UseSectionBox)
{
doc.InvokeSub(_ => view3d.SectionBoxElements(ids));
}
view3d.ZoomElements(uidoc, ids);
uidoc.Selection.SetElementIds(ids);
});
});
}
else
{
Items.Remove(model);
}
}
}
2026-02-22 20:03:42 +08:00
[ObservableProperty]
public partial string Properties { get; set; }
2024-09-22 11:05:41 +08:00
[ObservableProperty]
public partial int ErrorCount { get; set; }
[ObservableProperty]
public partial bool IsCheckEqual { get; set; }
[ObservableProperty]
public partial bool IsCheckLength { get; set; }
[ObservableProperty]
public partial bool IsCheckLevel { get; set; }
[ObservableProperty]
public partial bool IsCheckName { get; set; }
[ObservableProperty]
public partial bool IsCheckProps { get; set; }
[ObservableProperty]
public partial bool IsCheckSlope { get; set; }
[ObservableProperty]
public partial bool IsCheckSymbolGeometry { get; set; }
[ObservableProperty]
public partial ObservableCollection<MessageModel> Items { get; set; }
[ObservableProperty]
public partial int MyProperty { get; set; }
2024-09-22 11:05:41 +08:00
public string ProjectBasePoint { get; set; }
public string SharedBasePoint { get; set; }
[ObservableProperty]
public partial bool UseSectionBox { get; set; }
2024-09-22 11:05:41 +08:00
}
public enum ModelCheckType
{
/// <summary>
/// 命名
/// </summary>
Name,
/// <summary>
/// 坡度问题
/// </summary>
Slope,
/// <summary>
/// 参照标高
/// </summary>
ReferenceLevel,
/// <summary>
/// 属性检查
/// </summary>
Property,
/// <summary>
/// 管线重叠
/// </summary>
Equal,
/// <summary>
/// 管线过短,或孤立管线
/// </summary>
Length
}