// (Konrad) We are not going to process warnings here. #else /// <summary> /// Adds Warnings to a collection in database. If warnings exist it updates their status. /// </summary> public static void ProcessWarnings(ActionType action, Document doc, string centralPath) { var current = doc.GetWarnings().Select(x => new WarningItem(x, doc)).ToList(); switch (action) { case ActionType.CheckIn: if (!ServerUtilities.Post(current, "warnings/add", out ResponseCreated unused1)) { Log.AppendLog(LogMessageType.ERROR, "Failed to publish Views Data."); } break; case ActionType.Synch: var newW = AppCommand.Warnings.Values .Where(x => !string.IsNullOrEmpty(x.CreatedBy) && current.Any(y => y.UniqueId == x.UniqueId)).ToList(); var existingW = current.Except(newW).Select(x => x.UniqueId); var payload = new WarningData(Environment.UserName, centralPath, newW, existingW); if (!ServerUtilities.Post(payload, "warnings/update", out ResponseCreated unused)) { Log.AppendLog(LogMessageType.ERROR, "Failed to publish Views Data."); } else { CollectWarnings(doc); } break; default: throw new ArgumentOutOfRangeException(nameof(action), action, null); } }
/// <summary> /// Checks if Trigger Records collection exists and creates one if it doesn't. Since trigger /// records are published on demand by another tool (DTM) there is no need to publish them here. /// </summary> /// <param name="centralPath"></param> public static void ProcessTriggerRecords(string centralPath) { var project = MissionControlSetup.Projects[centralPath]; if (!ServerUtilities.GetByCentralPath(centralPath, "triggerrecords/centralpath", out TriggerRecordData trData)) { if (ServerUtilities.Post(new TriggerRecordData { CentralPath = centralPath.ToLower() }, "triggerrecords", out trData)) { ServerUtilities.Put(new { id = trData.Id }, "projects/" + project.Id + "/addtriggerrecord"); if (MissionControlSetup.TriggerRecords.ContainsKey(centralPath)) { MissionControlSetup.TriggerRecords.Remove(centralPath); } MissionControlSetup.TriggerRecords.Add(centralPath, trData.Id); // store TriggerRecords record } } if (trData != null) { if (MissionControlSetup.TriggerRecords.ContainsKey(centralPath)) { MissionControlSetup.TriggerRecords.Remove(centralPath); } MissionControlSetup.TriggerRecords.Add(centralPath, trData.Id); // store TriggerRecords record } }
/// <summary> /// Publishes data about Model Opening duration. /// </summary> /// <param name="filePath">Revit Document central file path.</param> public void PublishOpenTime(string filePath) { try { var from = AppCommand.OpenTime["from"]; var to = DateTime.UtcNow; var span = to - from; var ms = (int)span.TotalMilliseconds; var eventItem = new ModelEventData { CentralPath = filePath.ToLower(), Value = ms, User = Environment.UserName.ToLower() }; if (!ServerUtilities.Post(eventItem, "model/opentimes", out ModelEventData unused)) { Log.AppendLog(LogMessageType.ERROR, "Failed to publish Model Open Times Data."); } } catch (Exception ex) { Log.AppendLog(LogMessageType.EXCEPTION, ex.Message); } }
/// <summary> /// Adds Groups data to collection if such exists, otherwise creates a new one. /// </summary> private static void ProcessGroups(Document doc, string centralPath) { var project = MissionControlSetup.Projects[centralPath]; var data = new DataRangeRequest(centralPath.ToLower()); if (!ServerUtilities.Post(data, "groups/groupstats", out GroupsData gData)) { if (ServerUtilities.Post(new GroupsData { CentralPath = centralPath.ToLower() }, "groups", out gData)) { ServerUtilities.Put(new { id = gData.Id }, "projects/" + project.Id + "/addgroup"); } } if (gData != null) { if (MissionControlSetup.GroupsData.ContainsKey(centralPath)) { MissionControlSetup.GroupsData.Remove(centralPath); } MissionControlSetup.GroupsData.Add(centralPath, gData); // store groups record Messenger.Default.Send(new HealthReportSummaryAdded { Data = gData, Type = SummaryType.Groups }); new Thread(() => new GroupMonitor().PublishData(doc, gData.Id)) { Priority = ThreadPriority.BelowNormal, IsBackground = true }.Start(); } }
/// <summary> /// If Family is marked as Completed it will post the data to MC. /// </summary> /// <param name="app"></param> public void SubmitFamily(UIApplication app) { var doc = app.ActiveUIDocument.Document; if (doc == null || doc.IsFamilyDocument) { return; } var centralPath = FileInfoUtil.GetCentralFilePath(doc); var familyStatsId = MissionControlSetup.FamilyData[centralPath].Id; if (string.IsNullOrEmpty(familyStatsId)) { return; } FamilyTask.CompletedOn = DateTime.UtcNow; FamilyTask.CompletedBy = Environment.UserName.ToLower(); if (!ServerUtilities.Post(FamilyTask, "families/" + familyStatsId + "/family/" + FamilyItem.Name + "/updatetask/" + FamilyTask.Id, out FamilyData unused)) { Log.AppendLog(LogMessageType.ERROR, "Failed to submit Family Task Completed update."); } }
/// <summary> /// Adds Sheets data to collection if such exists, otherwise creates a new one. /// </summary> public static void ProcessSheets(ActionType action, Document doc, string centralPath) { var project = MissionControlSetup.Projects[centralPath]; switch (action) { case ActionType.CheckIn: if (!ServerUtilities.GetByCentralPath(centralPath, "sheets/centralpath", out SheetData sData)) { if (ServerUtilities.Post(new SheetData { CentralPath = centralPath.ToLower() }, "sheets", out sData)) { ServerUtilities.Put(new { id = sData.Id }, "projects/" + project.Id + "/addsheet"); if (MissionControlSetup.SheetsData.ContainsKey(centralPath)) { MissionControlSetup.SheetsData.Remove(centralPath); } MissionControlSetup.SheetsData.Add(centralPath, sData); // store sheets record } } if (sData != null) { if (MissionControlSetup.SheetsData.ContainsKey(centralPath)) { MissionControlSetup.SheetsData.Remove(centralPath); } MissionControlSetup.SheetsData.Add(centralPath, sData); // store sheets record Messenger.Default.Send(new CommunicatorDataDownloaded { CentralPath = centralPath, Type = DataType.Sheets }); new Thread(() => new SheetTracker.SheetTracker().SynchSheets(doc)) { Priority = ThreadPriority.BelowNormal, IsBackground = true }.Start(); } break; case ActionType.Synch: if (MissionControlSetup.SheetsData.ContainsKey(centralPath)) { new Thread(() => new SheetTracker.SheetTracker().SynchSheets(doc)) { Priority = ThreadPriority.BelowNormal, IsBackground = true }.Start(); } break; default: throw new ArgumentOutOfRangeException(nameof(action), action, null); } }
/// <summary> /// Adds Models data to collection if such exists, otherwise creates a new one. /// </summary> public static void ProcessModels(ActionType action, Document doc, string centralPath) { var data = new DataRangeRequest(centralPath.ToLower()) { From = null, To = null }; switch (action) { case ActionType.CheckIn: if (!ServerUtilities.Post(data, "model/getmodelsdata", out List <ModelStats> mData) || mData == null) { Log.AppendLog(LogMessageType.ERROR, "Failed to get Model Stats."); return; } if (mData.Any()) { if (MissionControlSetup.ModelsData.ContainsKey(centralPath)) { MissionControlSetup.ModelsData.Remove(centralPath); } MissionControlSetup.ModelsData.Add(centralPath, mData.First()); // store model record Messenger.Default.Send(new HealthReportSummaryAdded { Data = mData.First(), Type = SummaryType.Models }); new Thread(() => new ModelMonitor().PublishModelSize(doc, centralPath, doc.Application.VersionNumber)) { Priority = ThreadPriority.BelowNormal, IsBackground = true }.Start(); if (AppCommand.OpenTime.ContainsKey("from")) { new Thread(() => new ModelMonitor().PublishOpenTime(centralPath)) { Priority = ThreadPriority.BelowNormal, IsBackground = true }.Start(); } } break; case ActionType.Synch: if (AppCommand.SynchTime.ContainsKey("from")) { new Thread(() => new ModelMonitor().PublishSynchTime(centralPath)) { Priority = ThreadPriority.BelowNormal, IsBackground = true }.Start(); } break; default: throw new ArgumentOutOfRangeException(nameof(action), action, null); } }
/// <summary> /// Adds Worksets data to collection if such exists, otherwise creates a new one. /// </summary> public static void ProcessWorksets(ActionType action, Document doc, string centralPath) { var data = new DataRangeRequest(centralPath.ToLower()) { From = null, To = null }; switch (action) { case ActionType.CheckIn: if (!ServerUtilities.Post(data, "worksets/getworksetsdata", out List <WorksetStats> wData)) { Log.AppendLog(LogMessageType.ERROR, "Failed to get Workset Stats."); return; } if (wData != null && wData.Any()) { if (MissionControlSetup.WorksetsData.ContainsKey(centralPath)) { MissionControlSetup.WorksetsData.Remove(centralPath); } MissionControlSetup.WorksetsData.Add(centralPath, wData.First()); // store workset record Messenger.Default.Send(new HealthReportSummaryAdded { Data = wData.First(), Type = SummaryType.Worksets }); new Thread(() => new WorksetItemCount().PublishData(doc, centralPath)) { Priority = ThreadPriority.BelowNormal, IsBackground = true }.Start(); new Thread(() => new WorksetOpenSynch().PublishData(doc, centralPath, WorksetMonitorState.onopened)) { Priority = ThreadPriority.BelowNormal, IsBackground = true }.Start(); } break; case ActionType.Synch: new Thread(() => new WorksetOpenSynch().PublishData(doc, centralPath, WorksetMonitorState.onsynched)) { Priority = ThreadPriority.BelowNormal, IsBackground = true }.Start(); break; default: throw new ArgumentOutOfRangeException(nameof(action), action, null); } }
/// <summary> /// Publishes information about groups in the model. /// </summary> /// <param name="doc">Revit Document.</param> /// <param name="groupsId">Id of the Groups Document in MongoDB.</param> public void PublishData(Document doc, string groupsId) { try { var gTypes = new FilteredElementCollector(doc) .OfClass(typeof(GroupType)) .Cast <GroupType>() .ToDictionary(x => x.Id, x => new GroupItem { Name = x.Name, // (Konrad) If there is a Detail Group attached to Model Group // it will have the same name as Model Group but different Category. Type = x.Category.Name == "Attached Detail Groups" ? "Attached Detail Group" : x.FamilyName }); foreach (var gi in new FilteredElementCollector(doc).OfClass(typeof(Group)).Cast <Group>()) { var gTypeId = gi.GroupType.Id; if (!gTypes.ContainsKey(gTypeId)) { continue; } var gType = gTypes[gTypeId]; gType.Instances.Add(new GroupInstanceItem(gi)); gType.MemberCount = gi.GetMemberIds().Count; } var groupStats = new GroupDataItem { Groups = gTypes.Values.ToList() }; if (!ServerUtilities.Post(groupStats, "groups/" + groupsId + "/groupstats", out GroupsData unused)) { Log.AppendLog(LogMessageType.ERROR, "Failed to publish Groups Data."); } } catch (Exception ex) { Log.AppendLog(LogMessageType.EXCEPTION, ex.Message); } }
/// <summary> /// Posts approved changes to MongoDB. Mongo will fire socket event when complete. /// </summary> /// <param name="wrapper">Sheet Task Wrapper containing approved task.</param> /// <param name="centralPath"></param> public void Approve(SheetTaskWrapper wrapper, string centralPath) { var sheetsDataId = string.Empty; if (MissionControlSetup.SheetsData.ContainsKey(centralPath)) { sheetsDataId = MissionControlSetup.SheetsData[centralPath].Id; } if (string.IsNullOrEmpty(sheetsDataId)) { return; } var t = (SheetTask)wrapper.Task; var e = (SheetItem)wrapper.Element; t.CompletedBy = Environment.UserName.ToLower(); t.CompletedOn = DateTime.UtcNow; // body needs to be updated with a new identifier object or mongo side will fail. if (e.IsNewSheet) { // (Konrad) It's a create sheet task (element associated with task is null). Let's approve that. var newSheet = AppCommand.CommunicatorHandler.SheetItem; if (newSheet == null) { return; } wrapper.Element = newSheet; if (!ServerUtilities.Post(wrapper, "sheets/" + sheetsDataId + "/approvenewsheet", out SheetData unused)) { Log.AppendLog(LogMessageType.ERROR, "Failed to approve creation of new Sheet."); } } else { t.SheetId = e.Id; if (!ServerUtilities.Post(t, "sheets/" + sheetsDataId + "/updatetasks", out SheetData unused)) { Log.AppendLog(LogMessageType.ERROR, "Failed to approve changes to Sheet."); } } }
/// <summary> /// Publishes Workset Item Count data when Document is closed. /// </summary> /// <param name="doc">Revit Document.</param> /// <param name="filePath">Revit Document central file path.</param> public void PublishData(Document doc, string filePath) { try { var worksets = new FilteredWorksetCollector(doc) .OfKind(WorksetKind.UserWorkset) .ToWorksets(); var worksetInfo = new List <WorksetItem>(); foreach (var w in worksets) { // (Konrad) It turns out that for some reason Linked worksets // contain things like CopyMonitor options and Sketch Planes. var worksetFilter = new ElementWorksetFilter(w.Id, false); var planeFilter = new ElementClassFilter(typeof(SketchPlane), true); var filter = new LogicalAndFilter(worksetFilter, planeFilter); var count = new FilteredElementCollector(doc) .WherePasses(filter) .WhereElementIsNotElementType() .GetElementCount(); worksetInfo.Add(new WorksetItem { Name = w.Name, Count = count }); } var wItemData = new WorksetItemData { CentralPath = filePath.ToLower(), User = Environment.UserName.ToLower(), Worksets = worksetInfo }; if (!ServerUtilities.Post(wItemData, "worksets/itemcount", out WorksetItemData unused)) { Log.AppendLog(LogMessageType.ERROR, "Failed to publish Worksets Data."); } } catch (Exception ex) { Log.AppendLog(LogMessageType.EXCEPTION, ex.Message); } }
/// <summary> /// Publishes Workset Open Data to database for onOpened/onSynched events. /// </summary> /// <param name="doc">Revit Document.</param> /// <param name="filePath">Revit Document central file path.</param> /// <param name="state">State of the Revit model.</param> public void PublishData(Document doc, string filePath, WorksetMonitorState state) { try { var worksets = new FilteredWorksetCollector(doc) .OfKind(WorksetKind.UserWorkset) .ToWorksets(); var opened = 0; var closed = 0; foreach (var w in worksets) { if (w.IsOpen) { opened++; } else { closed++; } } var worksetInfo = new WorksetEvent { CentralPath = filePath.ToLower(), User = Environment.UserName.ToLower(), Opened = opened, Closed = closed }; var route = state == WorksetMonitorState.onopened ? "onopened" : "onsynched"; if (!ServerUtilities.Post(worksetInfo, "worksets/" + route, out WorksetEvent unused)) { Log.AppendLog(LogMessageType.ERROR, "Failed to publish Worksets Data: " + state + "."); } } catch (Exception ex) { Log.AppendLog(LogMessageType.EXCEPTION, ex.Message); } }
/// <summary> /// Checks if Families collection exists and creates one if it doesn't. Since families /// stats are published on demand by another tool there is no need to publish them here. /// </summary> private static void ProcessFamilies(string centralPath) { var project = MissionControlSetup.Projects[centralPath]; // (Konrad) For families we only need to make sure that we have the collection id. It will be used // by the Tasks. The actual data gets posted by FamilyPublish Tool. if (!ServerUtilities.GetByCentralPath(centralPath, "families/centralpath", out FamilyData fData)) { if (ServerUtilities.Post(new FamilyData { CentralPath = centralPath.ToLower() }, "families", out fData)) { ServerUtilities.Put(new { id = fData.Id }, "projects/" + project.Id + "/addfamilies"); if (MissionControlSetup.FamilyData.ContainsKey(centralPath)) { MissionControlSetup.FamilyData.Remove(centralPath); } MissionControlSetup.FamilyData.Add(centralPath, fData); // store families record } } else { if (MissionControlSetup.FamilyData.ContainsKey(centralPath)) { MissionControlSetup.FamilyData.Remove(centralPath); } MissionControlSetup.FamilyData.Add(centralPath, fData); // store families record Messenger.Default.Send(new HealthReportSummaryAdded { Data = fData, Type = SummaryType.Families }); Messenger.Default.Send(new CommunicatorDataDownloaded { CentralPath = centralPath, Type = DataType.Families }); } }
private void OnButtonEdit(Window window) { try { //update category trigger settings var settingsUpdated = false; var centralPath = reportingInfo.CentralPath; if (MissionControlSetup.Configurations.ContainsKey(centralPath)) { var config = MissionControlSetup.Configurations[centralPath]; var updaterFound = config.Updaters .FirstOrDefault(x => string.Equals(x.UpdaterId, reportingInfo.UpdaterId, StringComparison.OrdinalIgnoreCase)); if (updaterFound != null) { var updaterIndex = config.Updaters.IndexOf(updaterFound); var triggerFound = updaterFound.CategoryTriggers .FirstOrDefault(x => x.CategoryName == reportingInfo.CategoryName); if (triggerFound != null) { var triggerIndex = updaterFound.CategoryTriggers.IndexOf(triggerFound); config.Updaters[updaterIndex].CategoryTriggers[triggerIndex].IsEnabled = false; MissionControlSetup.Configurations.Remove(centralPath); MissionControlSetup.Configurations.Add(centralPath, config); //refresh category trigger AppCommand.Instance.DtmUpdaterInstance.Unregister(Doc); AppCommand.Instance.DtmUpdaterInstance.Register(Doc, config.Updaters[updaterIndex]); settingsUpdated = true; } } } if (settingsUpdated) { if (MissionControlSetup.TriggerRecords.ContainsKey(centralPath)) { var trId = MissionControlSetup.TriggerRecords[centralPath]; var record = new TriggerRecordItem { UpdaterId = reportingInfo.UpdaterId, CategoryName = reportingInfo.CategoryName, ElementUniqueId = reportingInfo.ReportingUniqueId, CreatedOn = DateTime.UtcNow, User = Environment.UserName }; if (!ServerUtilities.Post(record, "triggerrecords/" + trId + "/add", out TriggerRecordData unused)) { Log.AppendLog(LogMessageType.ERROR, "Failed to post Trigger Record Data."); } } window.DialogResult = false; } window.Close(); } catch (Exception ex) { Log.AppendLog(LogMessageType.EXCEPTION, ex.Message); } }
/// <summary> /// Method to "check into" mission control. It posts all initial data, and stores all needed references. /// </summary> /// <param name="doc"></param> public void CheckIn(Document doc) { try { var centralPath = FileInfoUtil.GetCentralFilePath(doc); // (Konrad) We can publish a file path to the DB. // That will make it easier to create Configurations. // Valid file is: // - not detached // - not a family // - is workshared // - is saved on the network, revit server or bim 360 server if (!doc.IsDetached && !doc.IsFamilyDocument && doc.IsWorkshared && FilePathItem.IsValidFilePath(centralPath)) { if (!ServerUtilities.Post(new FilePathItem(centralPath, doc.Application.VersionNumber), "filepaths/add", out FilePathItem unused)) { Log.AppendLog(LogMessageType.ERROR, "Failed to publish File Path: " + centralPath); } } // (Konrad) Get Configuration/Project. if (!ServerUtilities.GetByCentralPath(centralPath, "configurations/centralpath", out Configuration configFound)) { DisableMissionControl(); return; } if (!ServerUtilities.Get("projects/configid/" + configFound.Id, out Project projectFound)) { DisableMissionControl(); return; } if (MissionControlSetup.Configurations.ContainsKey(centralPath)) { MissionControlSetup.Configurations.Remove(centralPath); } MissionControlSetup.Configurations.Add(centralPath, configFound); if (MissionControlSetup.Projects.ContainsKey(centralPath)) { MissionControlSetup.Projects.Remove(centralPath); } MissionControlSetup.Projects.Add(centralPath, projectFound); // (Konrad) This might be a good time to let users know that Mission Control is ready to go. AppCommand.CommunicatorHandler.Status = Status.Success; AppCommand.CommunicatorHandler.Message = "Successfully connected to Mission Control!"; AppCommand.CommunicatorHandler.Request.Make(RequestId.ReportStatus); AppCommand.CommunicatorEvent.Raise(); // (Konrad) Register Updaters that are in the config file. CommunicatorUtilities.LaunchCommunicator(); ApplyConfiguration(doc); #if RELEASE2015 || RELEASE2016 || RELEASE2017 // (Konrad) We are not going to process warnings here. #else CollectWarnings(doc); #endif EnableMissionControl(); Log.AppendLog(LogMessageType.INFO, "Mission Control check in succeeded."); } catch (Exception ex) { Log.AppendLog(LogMessageType.EXCEPTION, ex.Message); } }
/// <summary> /// Publishes information about linked models/images/object styles in the model. /// </summary> /// <param name="doc">Revit Document.</param> /// <param name="filePath">Revit Document central file path.</param> /// <param name="version">Revit file version.</param> public void PublishModelSize(Document doc, string filePath, string version) { try { long fileSize; try { if (filePath.StartsWith("rsn://", StringComparison.OrdinalIgnoreCase)) { var server = string.Empty; var subfolder = string.Empty; // (Konrad) This is a Revit Server project. To get it's size one must use REST API, // and ping the server that it's hosted on. var splits = filePath.Split('/'); for (var i = 2; i < splits.Length; i++) { if (i == 2) { server = splits[i]; } else { subfolder = subfolder + splits[i] + (i == splits.Length - 1 ? string.Empty : "|"); } } var clientPath = "http://" + server; var requestPath = "RevitServerAdminRestService" + version + "/" + "AdminRestService.svc/" + subfolder + "/modelinfo"; var result = ServerUtilities.GetFileInfoFromRevitServer <RsFileInfo>(clientPath, requestPath); fileSize = result.ModelSize - result.SupportSize; } else if (filePath.StartsWith("bim 360://", StringComparison.OrdinalIgnoreCase)) { // (Konrad) For BIM 360 files, we can just pull the file size from local cached file. // It's pretty much what is stored in the web, so will work for our purpose. var fileName = doc.WorksharingCentralGUID + ".rvt"; var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); var collaborationDir = Path.Combine(localAppData, "Autodesk\\Revit\\Autodesk Revit " + doc.Application.VersionNumber, "CollaborationCache"); var file = Directory.GetFiles(collaborationDir, fileName, SearchOption.AllDirectories) .FirstOrDefault(x => new FileInfo(x).Directory?.Name != "CentralCache"); if (file == null) { return; } var fileInfo = new FileInfo(file); fileSize = fileInfo.Length; } else { var fileInfo = new FileInfo(filePath); fileSize = fileInfo.Length; } } catch (Exception ex) { fileSize = 0; Log.AppendLog(LogMessageType.EXCEPTION, ex.Message); } var eventItem = new ModelEventData { CentralPath = filePath.ToLower(), Value = fileSize, User = Environment.UserName.ToLower() }; if (!ServerUtilities.Post(eventItem, "model/modelsizes", out ModelEventData unused)) { Log.AppendLog(LogMessageType.ERROR, "Failed to publish Model Size Data."); } } catch (Exception ex) { Log.AppendLog(LogMessageType.EXCEPTION, ex.Message); } }
/// <summary> /// /// </summary> /// <param name="doc"></param> /// <param name="stylesId"></param> public void PublishData(Document doc, string stylesId) { try { _doc = doc; #region Text Note Type stats var textTypes = new FilteredElementCollector(doc) .OfClass(typeof(TextNoteType)) .ToDictionary(x => x.Id.IntegerValue, x => new Tuple <Element, int>(x, 0)); var textInstances = new FilteredElementCollector(doc) .OfClass(typeof(TextNote)) .WhereElementIsNotElementType(); foreach (var t in textInstances) { var key = t.GetTypeId().IntegerValue; if (textTypes.ContainsKey(key)) { // increment instance count textTypes[key] = new Tuple <Element, int>(textTypes[key].Item1, textTypes[key].Item2 + 1); } } var textStats = textTypes.Select(x => new TextNoteTypeInfo(x.Value.Item1) { Instances = x.Value.Item2 }) .ToList(); #endregion #region Dimension Type stats var dimTypes = new FilteredElementCollector(doc) .OfClass(typeof(DimensionType)) .Cast <DimensionType>() .Where(x => !string.IsNullOrEmpty(x.get_Parameter(BuiltInParameter.SYMBOL_NAME_PARAM).AsString())) .ToDictionary(x => x.Id.IntegerValue, x => new Tuple <DimensionType, int>(x, 0)); var dimInstances = new FilteredElementCollector(doc) .OfClass(typeof(Dimension)) .WhereElementIsNotElementType() .Cast <Dimension>(); // (Konrad) There is a user override in Configuration that controls what dimension overrides are ignored var centralPath = FileInfoUtil.GetCentralFilePath(doc); var config = MissionControlSetup.Configurations.ContainsKey(centralPath) ? MissionControlSetup.Configurations[centralPath] : null; var dimensionValueCheck = new List <string> { "EQ" }; //defaults if (config != null) { dimensionValueCheck = config.Updaters.First(x => string.Equals(x.UpdaterId, Properties.Resources.HealthReportTrackerGuid, StringComparison.OrdinalIgnoreCase)).UserOverrides.DimensionValueCheck.Values; } var units = _doc.GetUnits(); var dimSegmentStats = new List <DimensionSegmentInfo>(); foreach (var d in dimInstances) { var key = d.GetTypeId().IntegerValue; if (dimTypes.ContainsKey(key)) { // increment instance count dimTypes[key] = new Tuple <DimensionType, int>(dimTypes[key].Item1, dimTypes[key].Item2 + 1); } if (d.NumberOfSegments == 0) { if (string.IsNullOrEmpty(d.ValueOverride)) { continue; } if (dimensionValueCheck.Any(d.ValueOverride.Contains)) { continue; } // dim w/ zero segments dimSegmentStats.Add(new DimensionSegmentInfo(d) { #if RELEASE2021 ValueString = UnitFormatUtils.Format(units, d.DimensionType.GetSpecTypeId(), (double)d.Value, false), #else ValueString = UnitFormatUtils.Format(units, d.DimensionType.UnitType, (double)d.Value, false, false), #endif OwnerViewType = d.ViewSpecific ? ((View)doc.GetElement(d.OwnerViewId)).ViewType.ToString() : string.Empty, OwnerViewId = d.OwnerViewId.IntegerValue }); } else { // dim w/ multiple segments foreach (DimensionSegment s in d.Segments) { if (string.IsNullOrEmpty(s.ValueOverride)) { continue; } if (dimensionValueCheck.Any(s.ValueOverride.Contains)) { continue; } dimSegmentStats.Add(new DimensionSegmentInfo(s) { #if RELEASE2021 ValueString = UnitFormatUtils.Format(units, d.DimensionType.GetSpecTypeId(), (double)d.Value, false), #else ValueString = UnitFormatUtils.Format(units, d.DimensionType.UnitType, (double)d.Value, false, false), #endif OwnerViewType = d.ViewSpecific ? ((View)doc.GetElement(d.OwnerViewId)).ViewType.ToString() : string.Empty, OwnerViewId = d.OwnerViewId.IntegerValue }); } } } var dimStats = dimTypes.Select(x => new DimensionTypeInfo(x.Value.Item1) { Instances = x.Value.Item2 }) .ToList(); #endregion #region Line Style stats //TODO: Finish this out. #endregion var stylesStats = new StylesDataItem { User = Environment.UserName.ToLower(), TextStats = textStats, DimStats = dimStats, DimSegmentStats = dimSegmentStats }; if (!ServerUtilities.Post(stylesStats, "styles/" + stylesId + "/stylestats", out StylesDataItem unused)) { Log.AppendLog(LogMessageType.ERROR, "Failed to publish Styles Data."); } } catch (Exception e) { Log.AppendLog(LogMessageType.EXCEPTION, e.Message); } }
/// <summary> /// Publishes View count data when Document is closed. /// </summary> /// <param name="doc">Revit Document.</param> /// <param name="viewsId">Id of the Views Document in MongoDB.</param> public void PublishData(Document doc, string viewsId) { try { // (Konrad) Collect info about views. // Views on sheet, schedules on sheet, views total etc. var scheduleTotalCount = new FilteredElementCollector(doc) .OfClass(typeof(ViewSchedule)) .GetElementCount(); var viewTotalCount = 0; var unclippedViews = 0; var viewsOnSheet = 0; var viewsOnSheetWithTemplate = 0; var sheetTotalCount = 0; foreach (var v in new FilteredElementCollector(doc).OfClass(typeof(View)).Cast <View>().Where(x => !x.IsTemplate)) { viewTotalCount++; switch (v.ViewType) { case ViewType.DrawingSheet: sheetTotalCount++; var viewIds = ((ViewSheet)v).GetAllPlacedViews(); foreach (var id in viewIds) { viewsOnSheet++; var view = (View)doc.GetElement(id); if (view.ViewTemplateId != ElementId.InvalidElementId) { viewsOnSheetWithTemplate++; } } break; case ViewType.FloorPlan: case ViewType.EngineeringPlan: case ViewType.AreaPlan: case ViewType.CeilingPlan: if (v.get_Parameter(BuiltInParameter.VIEW_BACK_CLIPPING).AsInteger() == 0) { unclippedViews++; } break; case ViewType.Elevation: case ViewType.Section: if (v.get_Parameter(BuiltInParameter.VIEWER_BOUND_FAR_CLIPPING).AsInteger() == 0) { unclippedViews++; } break; case ViewType.ThreeD: if (v.get_Parameter(BuiltInParameter.VIEWER_BOUND_ACTIVE_FAR).AsInteger() == 0) { unclippedViews++; } break; } } var scheduleSet = new HashSet <ElementId>(); foreach (var ssi in new FilteredElementCollector(doc) .OfClass(typeof(ScheduleSheetInstance)) .Cast <ScheduleSheetInstance>()) { scheduleSet.Add(ssi.ScheduleId); } var schedulesOnSheet = scheduleSet.Count; viewsOnSheet += schedulesOnSheet; var viewStats = new ViewsDataItem { TotalViews = viewTotalCount, TotalSheets = sheetTotalCount, TotalSchedules = scheduleTotalCount, ViewsOnSheet = viewsOnSheet, ViewsOnSheetWithTemplate = viewsOnSheetWithTemplate, SchedulesOnSheet = schedulesOnSheet, UnclippedViews = unclippedViews }; if (!ServerUtilities.Post(viewStats, "views/" + viewsId + "/viewstats", out ViewsData unused)) { Log.AppendLog(LogMessageType.ERROR, "Failed to publish Views Data."); } } catch (Exception ex) { Log.AppendLog(LogMessageType.EXCEPTION, ex.Message); } }
/// <summary> /// Registers availble configuration based on Central Model path match. /// </summary> private static void ApplyConfiguration(Document doc) { try { var centralPath = FileInfoUtil.GetCentralFilePath(doc); var config = MissionControlSetup.Configurations[centralPath]; var launchSockets = false; foreach (var updater in config.Updaters) { if (!updater.IsUpdaterOn) { continue; } if (string.Equals(updater.UpdaterId, AppCommand.Instance.DoorUpdaterInstance.UpdaterGuid.ToString(), StringComparison.OrdinalIgnoreCase)) { // (Konrad) We need to register updaters from within Revit context. // That's why we are running this in the Idling Event. AppCommand.EnqueueTask(app => { AppCommand.Instance.DoorUpdaterInstance.Register(doc, updater); }); } else if (string.Equals(updater.UpdaterId, AppCommand.Instance.DtmUpdaterInstance.UpdaterGuid.ToString(), StringComparison.OrdinalIgnoreCase)) { ProcessTriggerRecords(centralPath); AppCommand.EnqueueTask(app => { AppCommand.Instance.DtmUpdaterInstance.Register(doc, updater); }); DTMTool.DtmSynchOverrides.CreateReloadLatestOverride(); DTMTool.DtmSynchOverrides.CreateSynchToCentralOverride(); } else if (string.Equals(updater.UpdaterId, Properties.Resources.LinkUnloadTrackerGuid, StringComparison.OrdinalIgnoreCase)) { AppCommand.EnqueueTask(app => { LinkUnloadMonitor.LinkUnloadMonitor.CreateLinkUnloadOverride(app); }); } else if (string.Equals(updater.UpdaterId, Properties.Resources.SheetsTrackerGuid, StringComparison.OrdinalIgnoreCase)) { ProcessSheets(ActionType.CheckIn, doc, centralPath); launchSockets = true; } else if (string.Equals(updater.UpdaterId, Properties.Resources.HealthReportTrackerGuid, StringComparison.OrdinalIgnoreCase)) { // (Konrad) These are read-only methods so they don't need to run in Revit context. ProcessModels(ActionType.CheckIn, doc, centralPath); ProcessWorksets(ActionType.CheckIn, doc, centralPath); #if RELEASE2015 || RELEASE2016 || RELEASE2017 // (Konrad) We are not going to process warnings here. #else ProcessWarnings(ActionType.CheckIn, doc, centralPath); #endif ProcessFamilies(centralPath); ProcessStyle(doc, centralPath); ProcessLinks(doc, centralPath); ProcessViews(doc, centralPath); ProcessGroups(doc, centralPath); launchSockets = true; } } // (Konrad) This tool will reset Shared Parameters Location to one specified in Mission Control if (config.GetType().GetProperty("sharedParamMonitor") != null && config.SharedParamMonitor.IsMonitorOn) { if (File.Exists(config.SharedParamMonitor.FilePath)) { doc.Application.SharedParametersFilename = config.SharedParamMonitor.FilePath; } else { Log.AppendLog(LogMessageType.ERROR, "Failed to reset Shared Parameter location. Could not find file specified."); } } if (launchSockets) { // (Konrad) in order not to become out of synch with the database we need a way // to communicate live updates from the database to task assistant/communicator var socket = new MissionControlSocket(doc); socket.Start(); AppCommand.Socket = socket; } // Publish user/machine info to be used by Zombie var userInfo = new UserItem { User = Environment.UserName.ToLower(), Machine = Environment.MachineName }; if (!ServerUtilities.Post(userInfo, "users/add", out ResponseCreated unused1)) { Log.AppendLog(LogMessageType.ERROR, "Failed to publish User/Machine Data."); } } catch (Exception ex) { Log.AppendLog(LogMessageType.EXCEPTION, ex.Message); } }
/// <summary> /// Publishes information about linked models/images/object styles in the model. /// </summary> /// <param name="doc">Revit Document.</param> /// <param name="linksId">Id of the Links Document in MongoDB.</param> public void PublishData(Document doc, string linksId) { try { var dwgStyles = new FilteredElementCollector(doc) .OfClass(typeof(GraphicsStyle)) .Cast <GraphicsStyle>() .Where(x => x.GraphicsStyleCategory.Name.Contains(".dwg")) .Select(x => x.GraphicsStyleCategory); var totalDwgStyles = dwgStyles.Sum(x => x.SubCategories.Size); var familyStyles = new FilteredElementCollector(doc) .OfClass(typeof(GraphicsStyle)) .Cast <GraphicsStyle>() .Where(x => x.GraphicsStyleCategory.Name == "Imports in Families") .Select(x => x.GraphicsStyleCategory); var totalImportedStyles = familyStyles.Sum(x => x.SubCategories.Size); // (Konrad) Collect info about Images var allPlacedImageIds = new FilteredElementCollector(doc) .OfCategory(BuiltInCategory.OST_RasterImages) .Select(x => x.GetTypeId()) .ToList(); var totalUnusedImages = new FilteredElementCollector(doc) .OfClass(typeof(ImageType)) .Excluding(allPlacedImageIds) .GetElementCount(); // (Konrad) Collect all Linked Model info var totalLinkedCad = new FilteredElementCollector(doc) .OfClass(typeof(CADLinkType)) .GetElementCount(); var totalLinkedRvt = new FilteredElementCollector(doc) .OfClass(typeof(RevitLinkType)) .GetElementCount(); var cadLinksDic = new FilteredElementCollector(doc) .OfClass(typeof(CADLinkType)) .Select(x => new DwgFileInfo { Name = x.Name, ElementId = x.Id.IntegerValue }) .ToDictionary(key => key.ElementId, value => value); var totalImportInstance = 0; foreach (var ii in new FilteredElementCollector(doc).OfClass(typeof(ImportInstance))) { totalImportInstance++; var id = ii.GetTypeId().IntegerValue; if (!cadLinksDic.ContainsKey(id)) { continue; } if (cadLinksDic[id].Instances == 0) { cadLinksDic[id].IsViewSpecific = ii.ViewSpecific; cadLinksDic[id].IsLinked = ((ImportInstance)ii).IsLinked; } cadLinksDic[id].Instances = cadLinksDic[id].Instances + 1; } var linkStats = new LinkDataItem { TotalImportedDwg = totalImportInstance, ImportedDwgFiles = cadLinksDic.Values.ToList(), UnusedLinkedImages = totalUnusedImages, TotalDwgStyles = totalDwgStyles, TotalImportedStyles = totalImportedStyles, TotalLinkedModels = totalLinkedCad + totalLinkedRvt, TotalLinkedDwg = totalLinkedCad }; if (!ServerUtilities.Post(linkStats, "links/" + linksId + "/linkstats", out LinkDataItem unused)) { Log.AppendLog(LogMessageType.ERROR, "Failed to publish Links Data."); } } catch (Exception ex) { Log.AppendLog(LogMessageType.EXCEPTION, ex.Message); } }
/// <summary> /// /// </summary> /// <param name="doc"></param> public void SynchSheets(Document doc) { try { var centralPath = FileInfoUtil.GetCentralFilePath(doc); var sheetsData = MissionControlSetup.SheetsData.ContainsKey(centralPath) ? MissionControlSetup.SheetsData[centralPath] : null; if (sheetsData == null) { return; } var sheets = new FilteredElementCollector(doc) .OfCategory(BuiltInCategory.OST_Sheets) .WhereElementIsNotElementType() .Cast <ViewSheet>() .Select(x => new SheetItem(x, centralPath)) .ToDictionary(key => key.UniqueId, value => value); var revisions = new FilteredElementCollector(doc) .OfCategory(BuiltInCategory.OST_Revisions) .WhereElementIsNotElementType() .Select(x => new RevisionItem(x)) .ToDictionary(key => key.UniqueId, value => value); var finalList = new List <SheetItem>(); foreach (var ms in sheetsData.Sheets) { if (sheets.ContainsKey(ms.UniqueId)) { // sheet still exists in our model // we can update it var localSheet = sheets[ms.UniqueId]; localSheet.Tasks = ms.Tasks; // preserve mongo tasks localSheet.Id = ms.Id; // preserve mongoIds localSheet.CollectionId = ms.CollectionId; // preserve mongoIds finalList.Add(localSheet); sheets.Remove(ms.UniqueId); } else { // TODO: Something about this is not right. When a new sheet is added from the web interface, then approved in Revit and synched var task = ms.Tasks.LastOrDefault(); if (task != null && task.IsNewSheet) { finalList.Add(ms); // this already has the ids } else { // sheet was deleted locally but still exists in mongo ms.IsDeleted = true; finalList.Add(ms); } } } finalList.AddRange(sheets.Values); // add whatever was not stored in mongo before var data = new SheetData { CentralPath = centralPath.ToLower(), Sheets = finalList, Revisions = revisions.Values.ToList(), Id = sheetsData.Id }; if (!ServerUtilities.Post(data, "sheets/" + sheetsData.Id, out SheetData unused)) { Log.AppendLog(LogMessageType.ERROR, "Failed to publish Sheets."); } if (MissionControlSetup.SheetsData.ContainsKey(centralPath)) { MissionControlSetup.SheetsData.Remove(centralPath); } MissionControlSetup.SheetsData.Add(centralPath, data); // store sheets record } catch (Exception e) { Log.AppendLog(LogMessageType.EXCEPTION, e.Message); } }