using System; using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Data; using Autodesk.Revit.DB; using Autodesk.Revit.DB.Electrical; using Autodesk.Revit.DB.Mechanical; using Autodesk.Revit.DB.Plumbing; using Autodesk.Revit.UI; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using EPPlus.Core.Extensions; using Microsoft.Win32; using Nice3point.Revit.Toolkit.External.Handlers; namespace Szmedi.RvKits.ModelManager; public partial class ModelCheckViewModel : ObservableObject { public ModelCheckViewModel(UIApplication uiapp) { Items = []; var cv = CollectionViewSource.GetDefaultView(Items); cv.GroupDescriptions.Add(new PropertyGroupDescription("ErrorMessage")); this.uiapp = uiapp; FindBasePoint(); } private readonly ActionEventHandler modifyModel = new(); private readonly ActionEventHandler showElementsSectionBox = new(); private readonly UIApplication uiapp; [ObservableProperty] private bool isCheckEqual; [ObservableProperty] private bool isCheckLength; [ObservableProperty] private bool isCheckLevel; [ObservableProperty] private bool isCheckName; [ObservableProperty] private bool isCheckProps; [ObservableProperty] private bool isCheckSlope; [ObservableProperty] private bool isCheckSymbolGeometry; [ObservableProperty] private int errorCount; [ObservableProperty] private ObservableCollection items; /// /// 项目基点 /// public string ProjectBasePoint { get; set; } /// /// 测量点 /// public string SharedBasePoint { get; set; } private bool CanExport() { return Items.Any(); } private bool CanShowElement(object obj) { return obj is ErrorItem { IsInstance: true }; } [RelayCommand] private async Task CheckModel(CancellationToken token) { Items.Clear(); await Task.Delay(TimeSpan.FromSeconds(2), token); var uidoc = uiapp.ActiveUIDocument; if (uidoc == null) { return; } var doc = uidoc.Document; //if (doc.ActiveView is not View3D view3d) //{ // view3d = // doc.QueryElementsByType().FirstOrDefault(e => FilteredElementCollector.IsViewValidForElementIteration(doc, e.Id)) as View3D; //} //var view3d = // doc.QueryElementsByType() // .FirstOrDefault( // e => FilteredElementCollector.IsViewValidForElementIteration(doc, e.Id) // ) as View3D; var elements = doc.OfParentModelCollector(); var typeInstancesGroups = elements.GroupBy(e => e.GetTypeId()).ToList(); if (IsCheckLevel) { var levels = doc.OfType().OrderBy(l => l.Elevation).ToList(); var levelOutlines = BaseLevelHelpers.GetRegions(levels); 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) { bool isCorrect = new(); bool hasReferenceLevel = new(); foreach (Parameter param in elem.Parameters) { if (!param.Definition.Name.Contains("标高") && !param.Definition.Name.Contains("Level")) { continue; } hasReferenceLevel = param.StorageType == StorageType.ElementId; if (!hasReferenceLevel) { continue; } var value = param.AsElementId(); isCorrect = value == ElementId.InvalidElementId || param.AsElementId() == level.Id; //扶手等对象有宿主Host时为空 } if (hasReferenceLevel && !isCorrect) { Items.Add(new ErrorItem(elem, "参照标高有误")); } } } } if (IsCheckName) { foreach (var item in typeInstancesGroups) { var id = item.Key; var type = doc.GetElement(id); var array = type.Name.Split('-'); if (array.Length >= 3) { var prefix = array[0].Trim(); if ( prefix is "QQ" or "YT" or "XL" or "GJ" or "CL" or "JZ" or "JG" or "GP" or "DZ" or "TF" or "GD" or "TX" or "XH" or "ZS" or "HB" or "ZJ" or "HJ" or "CX" or "MJ" or "YK" or "ZK" or "ZT" or "CJ" or "XX" or "TC" or "NY" or "ZB" or "AF" or "ZX" or "ZT" ) { continue; } ErrorItem errorItem = new(type, "类型专业前缀错误"); Items.Add(errorItem); } else { ErrorItem errorItem = new(type, "类型名称不符合三段式"); Items.Add(errorItem); } } //var familyGroups = doc.QueryElementsByType().ToElements().Cast().GroupBy(instance => instance.Symbol.Family); //foreach (var ZoomRBGroup in familyGroups) //{ // var array = ZoomRBGroup.Key.Name.Split('-'); // if (array.Length >= 3) // { // var prefix = array[0].Trim(); // if (prefix is not ("QQ" or "YT" or "XL" or "GJ" or "CL" or "JZ" or "JG" or "GP" or "DZ" or "TF" or "GD" or "TX" or "XH" or "ZS" or "HB" or "ZJ" or "HJ" or "CX" or "MJ" or "YK" or "ZK" or "ZT" or "CJ" or "XX" or "TC" or "NY" or "ZB" or "AF")) // { // var errorItem = new ErrorItem(ZoomRBGroup.Key, "族名称专业前缀错误"); // Items.Add(errorItem); // } // } // else // { // var errorItem = new ErrorItem(ZoomRBGroup.Key, "族名称命名不符合三段式"); // Items.Add(errorItem); // } //} } if (IsCheckProps) { foreach (var group in typeInstancesGroups) { var typeCount = 0; var typeId = group.Key; var type = doc.GetElement(typeId); foreach (Parameter param in type.Parameters) { var array = param.Definition.Name.Split('-'); if (array.Length >= 3) { var prefix = array[0].Trim(); if (prefix is ("ID" or "LC" or "ST" or "GJ" or "MF" or "AM" or "FM" or "TM" or "CM")) { typeCount++; } } } foreach (var elem in group) { var instanceCount = 0; foreach (Parameter param in elem.Parameters) { var array = param.Definition.Name.Split('-'); if (array.Length >= 3) { var prefix = array[0].Trim(); if (prefix is ("ID" or "LC" or "ST" or "GJ" or "MF" or "AM" or "FM" or "TM" or "CM")) { instanceCount++; } } } if (typeCount + instanceCount < 60) { ErrorItem errorItem = new(elem, "标准要求参数不全"); Items.Add(errorItem); } } } } if (IsCheckSlope) { var mepCurves = doc.QueryElementsByType().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-8) || param.AsDouble() > Math.PI); //坡度过大或过小时 } else if (mepCurve is Duct) { var param = mepCurve.get_Parameter(BuiltInParameter.RBS_DUCT_SLOPE); isError = param.HasValue && ((param.AsDouble() < 0.25 && param.AsDouble() > 10E-8) || param.AsDouble() > Math.PI); //坡度过大或过小时 } 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-8 and < 0.25) || (slope > Math.PI && Math.Abs(radian - Math.PI / 2) > 10E-8); } if (isError) { ErrorItem errorItem = new(mepCurve, "管线坡度有误差"); Items.Add(errorItem); } } } if (IsCheckLength) { var mepCurves = doc.OfType().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) { ErrorItem errorItem = new(mepCurve, "管道孤立或长度有误"); Items.Add(errorItem); } } } if (IsCheckEqual) { var mepCurves = doc.OfType().Where(e => e is not InsulationLiningBase).ToList(); foreach (var c1 in mepCurves) { foreach (var c2 in mepCurves) { if (c1.Id != c2.Id) { var result = c1.GetLocCurve().Intersect(c2.GetLocCurve()); if (result == SetComparisonResult.Equal) { ErrorItem errorItem = new(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 (geomInstance.Symbol is CADLinkType) { ErrorItem errorItem = new(symbol, "构件类型包含dwg模型"); Items.Add(errorItem); } } if (geomObj is Solid _) { count++; } } if (count > 50) { ErrorItem errorItem = new(symbol, "构件类型几何需考虑简化(超过50个实体)"); Items.Add(errorItem); } } } ErrorCount = Items.Count; ExportToExcelCommand.NotifyCanExecuteChanged(); } [RelayCommand(CanExecute = nameof(CanExport))] private void ExportToExcel() { var fileName = uiapp.ActiveUIDocument.Document.Title; SaveFileDialog dialog = new() { Title = "导出结果", AddExtension = true, DefaultExt = ".xlsx", InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), Filter = WinDialogAssists.CreateFilter("Excel表格", "xlsx"), OverwritePrompt = true, FileName = $"{fileName.Substring(0, fileName.Length - 4)}_检查结果" }; if (dialog.ShowDialog() != true) { return; } //ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial; try { var groups = Items.GroupBy(e => e.ErrorMessage); WorksheetWrapper wrapper = null; foreach (var group in groups) { if (wrapper == null) { wrapper = group.ToWorksheet(group.Key); } else { //更新成最后一张表的封装 wrapper = wrapper .NextWorksheet(group, group.Key); } wrapper.WithColumn(x => x.Element.Id, "元素ID") .WithColumn(x => x.Element.Category.Name, "族类别") .WithColumn(x => x.Element.Name, "类型或实例名称"); } var package = wrapper.ToExcelPackage(); package.SaveAs(new FileInfo(dialog.FileName)); System.Diagnostics.Process.Start(dialog.FileName); } catch (Exception ex) { LogAssists.WriteLog(ex.Message); } } private void FindBasePoint() { var basePoints = uiapp.ActiveUIDocument.Document.QueryElementsByType().OfType().Where(p => p.get_BoundingBox(null) != null); 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"; } else { //正北角度 var angle = item.get_Parameter(BuiltInParameter.BASEPOINT_ANGLETON_PARAM).AsValueString(); ProjectBasePoint = $"南北:{ns}m;东西:{ew}m;高程:{elev}m;角度:{angle}"; } } } [RelayCommand] private void ModifyModel() { List errorItems = []; modifyModel.Raise(_ => { var uidoc = uiapp.ActiveUIDocument; var doc = uidoc.Document; doc.InvokeGroup( _ => { var levels = new FilteredElementCollector(doc) .OfClass(typeof(Level)) .OfCategory(BuiltInCategory.OST_Levels) .Cast() .OrderBy(l => l.Elevation) .ToList(); #region 参照标高 var errorItems1 = BaseLevelHelpers.SetWalls(levels); errorItems.AddRange(errorItems1); var errorItems2 = BaseLevelHelpers.SetInstances(levels); errorItems.AddRange(errorItems2); var errorItems3 = BaseLevelHelpers.SetMEPCurves(levels); 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; if (line == null) { continue; } 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.Any()) { return; } WinDialogAssists.ShowOrActivate(uiapp.ActiveUIDocument, errorItems, "未解决错误"); //var win = WpfSingletonHelper.GetInstance(out var isNewCreate); //if (isNewCreate) //{ // win.DataContext = new MessageViewModel(uiapp.ActiveUIDocument, errorItems, "未解决错误"); // win.ShowAhead(); //} //win.Activate(); }); } [RelayCommand(CanExecute = nameof(CanShowElement))] private void ShowElement(object obj) { if (obj is ErrorItem model) { var uidoc = uiapp.ActiveUIDocument; var doc = uidoc.Document; //if (uidoc.ActiveView.IsTemporaryHideIsolateActive()) //{ // uidoc.ActiveView.temporary //} if (model.Element.IsValidObject) { if (model.Element.IsHidden(uidoc.ActiveView)) { return; } List ids = [model.Element.Id]; //uidoc.ActiveView.IsolateElementTemporary(model.Element.ViewId); showElementsSectionBox.Raise(_ => { if (uidoc.ActiveView is not View3D view3d) { view3d = doc.QueryElementsByType() .FirstOrDefault(e => FilteredElementCollector.IsViewValidForElementIteration(doc, e.Id)) as View3D; } uidoc.ActiveView = view3d; doc.Invoke(_ => { doc.InvokeSub(_ => view3d.CreateSectionBox(ids, true)); var box = model.Element.get_BoundingBox(uidoc.ActiveGraphicalView); var uiView = uidoc.GetOpenUIViews().FirstOrDefault(v => v.ViewId == uidoc.ActiveGraphicalView.Id); XYZ extendXyz = new(1, 1, 1); uiView?.ZoomAndCenterRectangle(box.Min - extendXyz, box.Max + extendXyz); uidoc.Selection.SetElementIds(ids); }); }); } else { Items.Remove(model); } } } }