using System.Collections; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text; using System.Windows; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using EPPlus.Core.Extensions; using Microsoft.Win32; using Nice3point.Revit.Toolkit.External.Handlers; using OfficeOpenXml; using Szmedi.RvKits.Assists; namespace Szmedi.RvKits.InfoManager.EAMTools; public partial class EAMCodingViewModel : ObservableObject { public EAMCodingViewModel(UIDocument uidoc) { this.uidoc = uidoc; Collection = GetCategoriesFromDocument(); //Instances = (from familyInstance in ins let facility = new InstanceFacility(familyInstance) select facility).ToList(); } private readonly ActionEventHandler showFacilitySectionBox = new(); private readonly ActionEventHandler writeCodeByMappedInstances = new(); private readonly ActionEventHandler writeCodeInstances = new(); private readonly UIDocument uidoc; [ObservableProperty] private Dictionary> collection; [ObservableProperty] private bool isFuzzyMatch; /// /// 是否经过楼层映射 /// [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(WriteCodeToInstancesMappedCommand))] [NotifyCanExecuteChangedFor(nameof(ExportExcelCommand))] private bool isMapper; /// /// 是否经过比对匹配 /// [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(WriteCodeToInstancesMappedCommand))] [NotifyCanExecuteChangedFor(nameof(ExportExcelCommand))] private bool isComparison; [ObservableProperty] private List facilities = []; [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(MapModelEAMCodeCommand), nameof(ExportComponentsCommand))] private List instances = []; [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(MapModelEAMCodeCommand))] private string excelPath; [ObservableProperty] private List storyCodeMappers = []; [ObservableProperty] private string searchText; /// /// 楼层映射 /// [RelayCommand] private void ApplyMappers() { foreach (StoryCodeMapper mapper in StoryCodeMappers) { foreach (EAMFacility eamFacility in Facilities) { if (eamFacility.EAMCode.Contains(mapper.Story)) { eamFacility.EAMCode = eamFacility.EAMCode.Replace(mapper.Story, mapper.Code); } } } IsMapper = true; } private static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args) { if (!args.Name.Contains("ComponentModel.Annotations")) { return null; } string assemblyFile = Path.Combine(GlobalVariables.DirAssembly, "System.ComponentModel.Annotations.dll"); return File.Exists(assemblyFile) ? Assembly.LoadFrom(assemblyFile) : null; } /// /// 获取重命名的条目 /// /// [RelayCommand] private void GetInstances(IList items) { if (items.Count == 0) { Instances = null!; return; } List instanceFacilities = []; foreach (object item in items) { if (item is KeyValuePair> categoryItem) { //var familyType = new FilteredElementCollector(uidoc.doc) // .QueryElementsByType(typeof(FamilyInstance)) // .Cast() // .Where( // f => f.Category.Id.IntegerValue == (int)categoryItem.Key // ); foreach (FamilyInstance element in categoryItem.Value) { InstanceFacility instanceFacility = new(element); instanceFacilities.Add(instanceFacility); } } } Instances = instanceFacilities; } [RelayCommand] private void OpenEAMFile() { OpenFileDialog dialog = new() { CheckFileExists = true, Filter = WinDialogAssists.CreateFilter("Excel文件", "xlsx"), Multiselect = false, InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), }; if (dialog.ShowDialog() != true) { return; } StoryCodeMappers.Clear(); ExcelPath = dialog.FileName; FileInfo fi = new(ExcelPath); using ExcelPackage package = new(fi); ExcelWorksheet worksheet = package.Workbook.Worksheets[1]; ////获取worksheet的行数 //int rows = worksheet.Dimension.End.Row; ////获取worksheet的列数 //int cols = worksheet.Dimension.End.Column; worksheet.TrimLastEmptyRows(); AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve; try { Facilities = package.ToList(1, configuration => configuration.SkipCastingErrors()); foreach (EAMFacility eamFacility in Facilities) { eamFacility.EAMCode = $"EAM-{eamFacility.Story}-{eamFacility.FunctionLocation}"; } IEnumerable> groups = Facilities.GroupBy(x => x.Story); foreach (IGrouping group in groups) { if (group.Key == null) { continue; } StoryCodeMappers.Add(new() { Story = group.Key, }); } IsMapper = false; IsComparison = false; Instances.ForEach(ins => ins.IsMapped = false); } catch (EPPlus.Core.Extensions.Exceptions.ExcelValidationException ex) { System.Windows.MessageBox.Show("列名不存在或不匹配,请检查表头是否存在换行或多余文字。", ex.Message); } finally { AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomainOnAssemblyResolve; } } private bool CanWrite() { return Facilities.Any(f => f.IsMapped == true) && IsMapper && IsComparison; } private bool CanMap() { // ReSharper disable once ConditionIsAlwaysTrueOrFalse return ExcelPath != null && Instances.Any(); } [RelayCommand] private void ShowFacility(object obj) { if (obj is not IFacility model) { return; } Document doc = uidoc.Document; if (!model.Instance.IsValidObject) { return; } if (model.Instance.IsHidden(uidoc.ActiveView)) { return; } List ids = [model.Instance.Id]; showFacilitySectionBox.Raise(_ => { if (uidoc.ActiveView is not View3D view3d) { view3d = doc.QueryElementsByType().FirstElement() as View3D; } uidoc.ActiveView = view3d; doc.Invoke( _ => { view3d.CreateSectionBox(ids, true); BoundingBoxXYZ box = model.Instance.get_BoundingBox(uidoc.ActiveGraphicalView); UIView 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); }, "定位构件" ); }); } [RelayCommand(CanExecute = nameof(CanWrite))] private void WriteCodeToInstancesMapped() { IEnumerable mapFacilities = Facilities.Where(f => f.IsMapped == true); writeCodeByMappedInstances.Raise(_ => { uidoc.Document.Invoke( _ => { foreach (EAMFacility facility in mapFacilities) { facility.Instance.get_Parameter(BuiltInParameter.DOOR_NUMBER).Set(facility.EAMCode); } }, "EAM编码写入" ); }); } private bool CanExport() { return IsComparison && IsMapper; } [RelayCommand(CanExecute = nameof(CanExport))] private void ExportExcel() { SaveFileDialog dialog = new() { Filter = WinDialogAssists.CreateFilter("Excel文件", "xlsx"), InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), }; if (dialog.ShowDialog() != true) { return; } string excel = dialog.FileName; FileInfo fi = new(excel); //using ExcelPackage package = new(fi); //ExcelPackage excelPackage = Instances.ToWorksheet("< 1950") // .WithConfiguration(configuration => configuration.WithColumnConfiguration(x => x.AutoFit())) // .WithColumn(x => x.FirstName, "First Name") // .WithColumn(x => x.LastName, "Last Name") // .WithColumn(x => x.YearBorn, "Year of Birth") // .WithTitle("< 1950") // .NextWorksheet(post50, "> 1950") // .WithColumn(x => x.LastName, "Last Name") // .WithColumn(x => x.YearBorn, "Year of Birth") // .WithTitle("> 1950") // .ToExcelPackage(); ExcelPackage package = Facilities .ToWorksheet("EAM编码匹配表") .WithConfiguration(cfg => cfg.WithColumnConfiguration(c => c.AutoFit())) .NextWorksheet(Instances, "族实例匹配表") .WithConfiguration(cfg => cfg.WithColumnConfiguration(c => c.AutoFit())) .ToExcelPackage(); package.SaveAs(fi); MessageBoxResult result = MessageBox.Show("是否打开Excel表", "提示", MessageBoxButton.YesNo, MessageBoxImage.Question); if (result == MessageBoxResult.Yes) { Process.Start(excel); } } [RelayCommand(CanExecute = nameof(CanMap))] private void MapModelEAMCode() { foreach (InstanceFacility instanceFacility in Instances) { //标记已经写入EAM编码或类型名设备编号一致 IEnumerable mapEAMFacilities = Facilities.Where( eamFacility => IsFuzzyMatch ? instanceFacility.Instance.Name.Contains(eamFacility.FacilityNumber) || eamFacility.EAMCode == instanceFacility.Number || instanceFacility.Instance .GetParameters("ID-100-编号") .FirstOrDefault() ?.AsString() .Contains(eamFacility.FacilityNumber) == true : instanceFacility.Instance.Name == eamFacility.FacilityNumber || eamFacility.EAMCode == instanceFacility.Number || instanceFacility.Instance.GetParameters("ID-100-编号").FirstOrDefault()?.AsString() == eamFacility.FacilityNumber ); if (mapEAMFacilities.Count() > 1) { instanceFacility.IsMapped = null; instanceFacility.MapErrorMessage = "族类型名称与设备编号\n没有一一对应"; } if (mapEAMFacilities.Count() == 1) { instanceFacility.IsMapped = true; instanceFacility.EAMCode = mapEAMFacilities.FirstOrDefault().EAMCode; } } foreach (EAMFacility eamFacility in Facilities) { IEnumerable mapInstanceFacilities = Instances.Where( instanceFacility => IsFuzzyMatch ? instanceFacility.Instance.Name.Contains(eamFacility.FacilityNumber) || eamFacility.EAMCode == instanceFacility.Number : instanceFacility.Instance.Name == eamFacility.FacilityNumber || eamFacility.EAMCode == instanceFacility.Number ); if (mapInstanceFacilities.Count() > 1) { eamFacility.IsMapped = null; eamFacility.MapErrorMessage = "设备编号与族类型名称\n没有一一对应"; } if (mapInstanceFacilities.Count() == 1) { eamFacility.IsMapped = true; eamFacility.Instance = mapInstanceFacilities.FirstOrDefault().Instance; } } IsComparison = true; } public Dictionary> GetCategoriesFromDocument() { Document doc = uidoc.Document; //var view3D = doc.QueryElementsByType().Cast().FirstOrDefault(v => !v.IsTemplate); //Dictionary items = []; Dictionary> dict = new FilteredElementCollector(doc) .OfClass(typeof(FamilyInstance)) .Where(i => i.Category.AllowsBoundParameters && !Enum.GetName(typeof(BuiltInCategory), i.Category.Id.IntegerValue)!.Contains("Fitting") && i.Category.CategoryType == CategoryType.Model //风道末端 && i.Category.Id.IntegerValue != -2008013) .Cast() .GroupBy(i => i.Category.Id) .ToDictionary(g => Category.GetCategory(doc, g.Key).Name, g => g.ToList()); return dict; //var doc = uidoc.doc; //var view3D = doc.QueryElementsByType().Cast().FirstOrDefault(v => !v.IsTemplate); //var ins = view3D // .OfCollector() // .QueryElementsByType(typeof(FamilyInstance)) // .Cast() // .Where( // i => // i.StructuralType == StructuralType.NonStructural // && i.Category.CategoryType == CategoryType.Model // && !Enum.GetName(typeof(BuiltInCategory), i.Category.Id.IntegerValue)!.Contains("Accessory") // && !Enum.GetName(typeof(BuiltInCategory), i.Category.Id.IntegerValue)!.Contains("Fitting") // //风道末端 // && i.Category.Id.IntegerValue != -2008013 // ) // .ToList(); //foreach (var ZoomRBGroup in groupBy) //{ // items.Add(Category.GetCategory(doc, ZoomRBGroup.Key).Name(BuiltInCategory)ZoomRBGroup.Key.IntegerValue, ); //} //return items.OrderBy(kv => kv.Value).ToDictionary(kv => kv.Key, kv => kv.Value); } private bool CanExportComponent() { return Instances != null && Instances.Any(); } [RelayCommand(CanExecute = nameof(CanExportComponent))] private void ExportComponents() { SaveFileDialog dialog = new() { Filter = WinDialogAssists.CreateFilter("Excel文件", "xlsx"), InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), }; if (dialog.ShowDialog() != true) { return; } string excel = dialog.FileName; FileInfo fi = new(excel); //using ExcelPackage package = new(fi); //ExcelPackage excelPackage = Instances.ToWorksheet("< 1950") // .WithConfiguration(configuration => configuration.WithColumnConfiguration(x => x.AutoFit())) // .WithColumn(x => x.FirstName, "First Name") // .WithColumn(x => x.LastName, "Last Name") // .WithColumn(x => x.YearBorn, "Year of Birth") // .WithTitle("< 1950") // .NextWorksheet(post50, "> 1950") // .WithColumn(x => x.LastName, "Last Name") // .WithColumn(x => x.YearBorn, "Year of Birth") // .WithTitle("> 1950") // .ToExcelPackage(); ExcelPackage package = Facilities .ToWorksheet("族实例匹配表") .WithConfiguration(cfg => cfg.WithColumnConfiguration(c => c.AutoFit())) .ToExcelPackage(); package.SaveAs(fi); } [RelayCommand] private void QuickWriteEAMToInstances() { OpenFileDialog dialog = new() { CheckFileExists = true, Filter = WinDialogAssists.CreateFilter("Excel文件", "xlsx"), Multiselect = false, InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), }; if (dialog.ShowDialog() != true) { return; } StoryCodeMappers.Clear(); FileInfo fi = new(dialog.FileName); using ExcelPackage package = new(fi); ExcelWorksheet worksheet = package.Workbook.Worksheets[1]; worksheet.TrimLastEmptyRows(); AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve; try { List items = package.ToList(1, configuration => configuration.SkipCastingErrors()); StringBuilder sb = new(); writeCodeInstances.Raise(_ => { uidoc.Document.Invoke( _ => { foreach (WriteCodeItem item in items) { if (int.TryParse(item.Id, out int result)) { ElementId id = new(result); Element element = uidoc.Document.GetElement(id); try { element.get_Parameter(BuiltInParameter.DOOR_NUMBER).Set(item.EAMCode); } catch (Exception e) { sb.AppendLine($"异常:{e.Message} - 元素ID:{item.Id};EAM编码:{item.EAMCode};"); } } } }, "批量写入EAM编码" ); }); if (sb.Length > 0) { LogAssists.WriteTxtFile("EAM编码写入失败", sb.ToString()); } } catch (EPPlus.Core.Extensions.Exceptions.ExcelValidationException ex) { System.Windows.MessageBox.Show("列名不存在或不匹配,请检查表头是否存在换行或多余文字。", ex.Message); } finally { AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomainOnAssemblyResolve; } } }