From b3cccd5e29620c792c3a5a995652b869592b73ad Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Mon, 1 Aug 2022 14:48:43 +0100 Subject: [PATCH] feat(dui2/rhino/revit): better reporting and previews (#1450) * added sending preview * added preview receive * refactored bake operation * conduit added * flattencommit logic fixed * adds preview button to dui * fixed direct receive * preview send finished * Updated Reporting * conversion error updates * updating converter methods * Consolidated Preview and Report object into ApplicationObject * updated desktopui * updated converter * clears stored connector application objects * added dui2 report page * Added report page * report updates * restyled report * formatting update * Improved preview button * Fixed block reporting * Added selectable preview objects * added report search * changed expanders to colorzone * Updated autocad converter * fixed flatten commit logic for reporting * all connectors updated with new bindings and application object * missed some applicationplaceholderobjects * removed all return values of List in revit converter * improved reporting backwards compatibility * report page scroll added also includes revit connector bindings selection * Update ConversionUtils.cs * Fix for Tekla Structures for Report preview * fix(dui): ld report not scrollable because hidden behind the new report component * chore(dui): fiddles and formatting * added new revit send report * fixed new connector binding exception removed not implemented exceptions to prevent crash across sharp connectors * revit hosted elements send report fix * skipping blocks and views in rhino preview receive * added report copy to clipboard * preview button implements IsReady check * feat(dui): disables preview button when InProgress Co-authored-by: Reynold Chan Co-authored-by: Matteo Cominetti --- .../ConnectorArchicad/ConnectorBinding.cs | 24 +- .../UI/ConnectorBindingsAutocadCivil.cs | 18 +- .../ConnectorBindingsCSI.ClientOperations.cs | 1 - .../UI/ConnectorBindingsCSI.Recieve.cs | 33 +- .../UI/ConnectorBindingsCSI.Selection.cs | 62 +- .../UI/ConnectorBindingsCSI.Send.cs | 34 +- .../UI/ConnectorBindingsCSI.cs | 11 +- .../Properties/AssemblyInfo.cs | 2 +- .../UI/Bindings.cs | 22 +- .../UI/Bindings2.cs | 42 +- .../ConnectorRevit/ConnectorRevitUtils.cs | 13 +- .../UI/ConnectorBindingsRevit.Receive.cs | 24 +- .../UI/ConnectorBindingsRevit.Send.cs | 20 +- .../UI/ConnectorBindingsRevit.cs | 2 +- .../ConnectorBindingsRevit2.Receive.cs | 81 +-- .../ConnectorBindingsRevit2.Selection.cs | 2 +- .../ConnectorBindingsRevit2.Send.cs | 81 ++- .../ConnectorBindingsRevit2.cs | 32 +- .../BIMElements/BIMElementFilter.cs | 4 - .../UI/ConnectorBindingsRhino.cs | 575 ++++++++++++------ .../ConnectorRhinoShared/Utils.cs | 128 +++- .../ConnectorBindingsTeklaStructure.Send.cs | 6 +- ...indingsTeklaStructures.ClientOperations.cs | 1 - ...nectorBindingsTeklaStructures.Selection.cs | 33 +- Core/Core/Kits/ISpeckleConverter.cs | 6 +- Core/Core/Models/Extras.cs | 164 ++++- DesktopUI/DesktopUI/Utils/DummyBindings.cs | 2 +- DesktopUI/DesktopUI/Utils/StreamState.cs | 2 +- DesktopUI2/DesktopUI2/ConnectorBindings.cs | 24 +- .../DesktopUI2/Controls/PreviewButton.xaml | 47 ++ .../DesktopUI2/Controls/PreviewButton.xaml.cs | 19 + DesktopUI2/DesktopUI2/DesktopUI2.csproj | 18 +- DesktopUI2/DesktopUI2/DummyBindings.cs | 107 +++- DesktopUI2/DesktopUI2/Models/StreamState.cs | 2 +- .../DesktopUI2/Styles/FloatingButton.xaml | 4 + .../ViewModels/ActivityViewModel.cs | 5 - .../ViewModels/ApplicationObjectViewModel.cs | 104 ++++ .../DesktopUI2/ViewModels/CommentViewModel.cs | 9 - .../DesignSavedStreamsViewModel.cs | 16 +- .../DesktopUI2/ViewModels/HomeViewModel.cs | 15 - .../ViewModels/ProgressViewModel.cs | 22 +- .../DesktopUI2/ViewModels/StreamViewModel.cs | 184 ++++-- .../Converters/RoleCanSendValueConverter.cs | 1 - .../Pages/HomeControls/SavedStreams.xaml | 50 -- .../Pages/SharedControls/StreamDetails.xaml | 5 - .../Pages/StreamEditControls/Receive.xaml | 5 +- .../Pages/StreamEditControls/Report.xaml | 70 +++ .../Pages/StreamEditControls/Report.xaml.cs | 19 + .../Views/Pages/StreamEditControls/Send.xaml | 7 +- .../Views/Pages/StreamEditView.xaml | 146 +++-- .../Views/Pages/StreamEditView.xaml.cs | 1 - .../DesktopUI2/Views/Windows/Report.xaml | 81 --- .../DesktopUI2/Views/Windows/Report.xaml.cs | 23 - .../ConverterAutocadCivil.Civil.cs | 6 +- .../ConverterAutocadCivil.Geometry.cs | 19 +- .../ConverterAutocadCivil.cs | 138 ++--- .../ConverterCSIShared/ConverterCSI.cs | 4 +- .../SpeckleDxfConverter.Document.cs | 4 +- .../ConverterDynamoShared/ConverterDynamo.cs | 38 +- .../ConverterMicroStationOpen.cs | 6 +- .../ConverterRevitShared/ConversionUtils.cs | 79 ++- .../ConverterRevit.DxfImport.cs | 126 ++-- .../ConverterRevitShared/ConverterRevit.cs | 63 +- .../ConvertAdaptiveComponent.cs | 33 +- .../Partial Classes/ConvertAnalyticalNode.cs | 22 +- .../Partial Classes/ConvertAnalyticalStick.cs | 41 +- .../ConvertAnalyticalSurface.cs | 14 +- .../Partial Classes/ConvertArea.cs | 13 +- .../Partial Classes/ConvertBeam.cs | 52 +- .../Partial Classes/ConvertBlock.cs | 5 +- .../Partial Classes/ConvertBrace.cs | 7 +- .../Partial Classes/ConvertBuildingPad.cs | 4 - .../Partial Classes/ConvertCableTray.cs | 39 +- .../Partial Classes/ConvertCeiling.cs | 47 +- .../Partial Classes/ConvertColumn.cs | 58 +- .../Partial Classes/ConvertCurves.cs | 179 +++--- .../Partial Classes/ConvertDirectShape.cs | 73 ++- .../Partial Classes/ConvertDuct.cs | 56 +- .../Partial Classes/ConvertFaceWall.cs | 65 +- .../Partial Classes/ConvertFamilyInstance.cs | 75 +-- .../Partial Classes/ConvertFloor.cs | 47 +- .../Partial Classes/ConvertFreeformElement.cs | 121 ++-- .../Partial Classes/ConvertGeometry.cs | 22 +- .../Partial Classes/ConvertGridLine.cs | 40 +- .../Partial Classes/ConvertLevel.cs | 40 +- .../Partial Classes/ConvertLocation.cs | 11 - .../ConvertMaterialQuantities.cs | 98 +-- .../Partial Classes/ConvertOpening.cs | 65 +- .../Partial Classes/ConvertPipe.cs | 56 +- .../Partial Classes/ConvertProfileWall.cs | 51 +- .../Partial Classes/ConvertRailing.cs | 57 +- .../Partial Classes/ConvertRebar.cs | 56 +- .../Partial Classes/ConvertRevitElement.cs | 9 +- .../Partial Classes/ConvertRoof.cs | 69 +-- .../Partial Classes/ConvertRoom.cs | 34 +- .../Partial Classes/ConvertSpace.cs | 30 +- .../Partial Classes/ConvertStair.cs | 1 - .../Partial Classes/ConvertStructuralModel.cs | 37 +- .../Partial Classes/ConvertTeklaObjects.cs | 11 +- .../Partial Classes/ConvertTopography.cs | 20 +- .../Partial Classes/ConvertView.cs | 5 +- .../Partial Classes/ConvertWall.cs | 77 +-- .../Partial Classes/ConvertWire.cs | 39 +- .../ConverterRevitTests/BrepTests.cs | 4 +- .../SpeckleConversionTest.cs | 24 +- .../ConverterRhinoGh.BuiltElements.cs | 70 ++- .../ConverterRhinoGh.Geometry.cs | 13 +- .../ConverterRhinoGh.Other.cs | 28 +- .../ConverterRhinoGh.cs | 210 +++---- .../ConverterTeklaStructures.cs | 21 +- 110 files changed, 2653 insertions(+), 2388 deletions(-) create mode 100644 DesktopUI2/DesktopUI2/Controls/PreviewButton.xaml create mode 100644 DesktopUI2/DesktopUI2/Controls/PreviewButton.xaml.cs create mode 100644 DesktopUI2/DesktopUI2/ViewModels/ApplicationObjectViewModel.cs create mode 100644 DesktopUI2/DesktopUI2/Views/Pages/StreamEditControls/Report.xaml create mode 100644 DesktopUI2/DesktopUI2/Views/Pages/StreamEditControls/Report.xaml.cs delete mode 100644 DesktopUI2/DesktopUI2/Views/Windows/Report.xaml delete mode 100644 DesktopUI2/DesktopUI2/Views/Windows/Report.xaml.cs diff --git a/ConnectorArchicad/ConnectorArchicad/ConnectorBinding.cs b/ConnectorArchicad/ConnectorArchicad/ConnectorBinding.cs index 17cb688404..691e8fd706 100644 --- a/ConnectorArchicad/ConnectorArchicad/ConnectorBinding.cs +++ b/ConnectorArchicad/ConnectorArchicad/ConnectorBinding.cs @@ -87,32 +87,46 @@ public override List GetStreamsInFile() return new List(); } + public override void ResetDocument() + { + // TODO! + } + public override List GetReceiveModes() { return new List { ReceiveMode.Create }; } + public override Task PreviewReceive(StreamState state, ProgressViewModel progress) + { + return null; + // TODO! + } + public override async Task ReceiveStream(StreamState state, ProgressViewModel progress) { Base commitObject = await Helpers.Receive(IdentifyStream(state)); if (commitObject is null) - { return null; - } state.SelectedObjectIds = await ElementConverterManager.Instance.ConvertToNative(commitObject, progress.CancellationTokenSource.Token); return state; } - public override void SelectClientObjects(string args) { } + public override void SelectClientObjects(List args, bool deselect = false) + { + // TODO! + } + public override void PreviewSend(StreamState state, ProgressViewModel progress) + { + // TODO! + } public override async Task SendStream(StreamState state, ProgressViewModel progress) { if (state.Filter is null) - { return null; - } state.SelectedObjectIds = state.Filter.Selection; diff --git a/ConnectorAutocadCivil/ConnectorAutocadCivil/UI/ConnectorBindingsAutocadCivil.cs b/ConnectorAutocadCivil/ConnectorAutocadCivil/UI/ConnectorBindingsAutocadCivil.cs index d2386343a4..bb7bd622d1 100644 --- a/ConnectorAutocadCivil/ConnectorAutocadCivil/UI/ConnectorBindingsAutocadCivil.cs +++ b/ConnectorAutocadCivil/ConnectorAutocadCivil/UI/ConnectorBindingsAutocadCivil.cs @@ -138,14 +138,24 @@ public override List GetCustomStreamMenuItems() return new List(); } - public override void SelectClientObjects(string args) + public override void SelectClientObjects(List args, bool deselect = false) { - throw new NotImplementedException(); + // TODO! + } + + public override void ResetDocument() + { + // TODO! } #endregion #region receiving + public override async Task PreviewReceive(StreamState state, ProgressViewModel progress) + { + return null; + // TODO! + } public override async Task ReceiveStream(StreamState state, ProgressViewModel progress) { var kit = KitManager.GetDefaultKit(); @@ -485,6 +495,10 @@ private bool GetOrMakeLayer(string layerName, Transaction tr, out string cleanNa #endregion #region sending + public override async void PreviewSend(StreamState state, ProgressViewModel progress) + { + // TODO! + } public override async Task SendStream(StreamState state, ProgressViewModel progress) { var kit = KitManager.GetDefaultKit(); diff --git a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.ClientOperations.cs b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.ClientOperations.cs index 377a639a70..232d109367 100644 --- a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.ClientOperations.cs +++ b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.ClientOperations.cs @@ -29,7 +29,6 @@ public override void WriteStreamsToFile(List streams) DocumentStreams.Add(s); WriteStateToFile(); } - //throw new NotImplementedException(); } //public override void AddNewStream(StreamState state) diff --git a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Recieve.cs b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Recieve.cs index 76107aacde..67f12684c4 100644 --- a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Recieve.cs +++ b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Recieve.cs @@ -15,9 +15,13 @@ namespace Speckle.ConnectorCSI.UI { public partial class ConnectorBindingsCSI : ConnectorBindings - { - #region receiving + public override Task PreviewReceive(StreamState state, ProgressViewModel progress) + { + return null; + // TODO! + } + public override async Task ReceiveStream(StreamState state, ProgressViewModel progress) { Exceptions.Clear(); @@ -41,9 +45,7 @@ public override async Task ReceiveStream(StreamState state, Progres var stream = await state.Client.StreamGet(state.StreamId); if (progress.CancellationTokenSource.Token.IsCancellationRequested) - { return null; - } var transport = new ServerTransport(state.Client.Account, state.StreamId); @@ -76,9 +78,7 @@ public override async Task ReceiveStream(StreamState state, Progres ); if (progress.Report.OperationErrorsCount != 0) - { return state; - } try { @@ -97,14 +97,10 @@ await state.Client.CommitReceived(new CommitReceivedInput if (progress.Report.OperationErrorsCount != 0) - { return state; - } if (progress.CancellationTokenSource.Token.IsCancellationRequested) - { return null; - } var conversionProgressDict = new ConcurrentDictionary(); conversionProgressDict["Conversion"] = 0; @@ -116,7 +112,6 @@ await state.Client.CommitReceived(new CommitReceivedInput progress.Update(conversionProgressDict); }; - var commitObjs = FlattenCommitObject(commitObject, converter); foreach (var commitObj in commitObjs) { @@ -124,8 +119,6 @@ await state.Client.CommitReceived(new CommitReceivedInput updateProgressAction?.Invoke(); } - - try { //await state.RefreshStream(); @@ -142,11 +135,6 @@ await state.Client.CommitReceived(new CommitReceivedInput return state; } - - - - - /// /// conversion to native /// @@ -157,7 +145,6 @@ private void BakeObject(Base obj, StreamState state, ISpeckleConverter converter { try { - converter.ConvertToNative(obj); } catch (Exception e) @@ -183,15 +170,12 @@ private List FlattenCommitObject(object obj, ISpeckleConverter converter) if (converter.CanConvertToNative(@base)) { objects.Add(@base); - return objects; } else { foreach (var prop in @base.GetDynamicMembers()) - { objects.AddRange(FlattenCommitObject(@base[prop], converter)); - } return objects; } } @@ -199,24 +183,19 @@ private List FlattenCommitObject(object obj, ISpeckleConverter converter) if (obj is List list) { foreach (var listObj in list) - { objects.AddRange(FlattenCommitObject(listObj, converter)); - } return objects; } if (obj is IDictionary dict) { foreach (DictionaryEntry kvp in dict) - { objects.AddRange(FlattenCommitObject(kvp.Value, converter)); - } return objects; } return objects; } - #endregion } } \ No newline at end of file diff --git a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Selection.cs b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Selection.cs index da570aa9ef..67adfb99ce 100644 --- a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Selection.cs +++ b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Selection.cs @@ -8,7 +8,6 @@ namespace Speckle.ConnectorCSI.UI { public partial class ConnectorBindingsCSI : ConnectorBindings - { public override List GetSelectedObjects() { @@ -20,14 +19,11 @@ public override List GetSelectedObjects() { (string typeName, string name) = item; if (ConnectorCSIUtils.IsTypeCSIAPIUsable(typeName)) - { names.Add(string.Concat(typeName, ": ", name)); - } } if (names.Count == 0) - { return new List() { }; - } + return names; } @@ -49,32 +45,31 @@ public override List GetSelectionFilters() } return new List() - { - new AllSelectionFilter {Slug="all", Name = "Everything", - Icon = "CubeScan", Description = "Selects all document objects." }, - new ListSelectionFilter {Slug="type", Name = "Categories", - Icon = "Category", Values = objectTypes, - Description="Adds all objects belonging to the selected types"}, - //new PropertySelectionFilter{ - // Slug="param", - // Name = "Param", - // Description="Adds all objects satisfying the selected parameter", - // Icon = "FilterList", - // HasCustomProperty = false, - // Values = objectNames, - // Operators = new List {"equals", "contains", "is greater than", "is less than"} - //}, - - new ManualSelectionFilter(), - new ListSelectionFilter { Slug = "group", Name = "Group", - Icon = "SelectGroup", Values = groups, Description = "Add all objects belonging to CSI Group" } - }; + { + new AllSelectionFilter {Slug="all", Name = "Everything", + Icon = "CubeScan", Description = "Selects all document objects." }, + new ListSelectionFilter {Slug="type", Name = "Categories", + Icon = "Category", Values = objectTypes, + Description="Adds all objects belonging to the selected types"}, + //new PropertySelectionFilter{ + // Slug="param", + // Name = "Param", + // Description="Adds all objects satisfying the selected parameter", + // Icon = "FilterList", + // HasCustomProperty = false, + // Values = objectNames, + // Operators = new List {"equals", "contains", "is greater than", "is less than"} + //}, + new ManualSelectionFilter(), + new ListSelectionFilter { Slug = "group", Name = "Group", + Icon = "SelectGroup", Values = groups, Description = "Add all objects belonging to CSI Group" } + }; } - public override void SelectClientObjects(string args) + public override void SelectClientObjects(List args, bool deselect = false) { - throw new NotImplementedException(); + // TODO! } private List GetSelectionFilterObjects(ISelectionFilter filter) @@ -89,20 +84,17 @@ private List GetSelectionFilterObjects(ISelectionFilter filter) return GetSelectedObjects(); case "all": if (ConnectorCSIUtils.ObjectIDsTypesAndNames == null) - { ConnectorCSIUtils.GetObjectIDsTypesAndNames(Model); - } + selection.AddRange(ConnectorCSIUtils.ObjectIDsTypesAndNames .Select(pair => pair.Key).ToList()); return selection; - case "type": var typeFilter = filter as ListSelectionFilter; if (ConnectorCSIUtils.ObjectIDsTypesAndNames == null) - { ConnectorCSIUtils.GetObjectIDsTypesAndNames(Model); - } + foreach (var type in typeFilter.Selection) { selection.AddRange(ConnectorCSIUtils.ObjectIDsTypesAndNames @@ -111,17 +103,15 @@ private List GetSelectionFilterObjects(ISelectionFilter filter) .ToList()); } return selection; + case "group": //Clear objects first Model.SelectObj.ClearSelection(); var groupFilter = filter as ListSelectionFilter; foreach (var group in groupFilter.Selection) - { Model.SelectObj.Group(group); - } - return GetSelectedObjects(); - + return GetSelectedObjects(); /// CSI doesn't list fields of different objects. /// For "param" search, maybe search over the name of diff --git a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Send.cs b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Send.cs index 9e8997a352..0b093b7b20 100644 --- a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Send.cs +++ b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Send.cs @@ -16,13 +16,14 @@ namespace Speckle.ConnectorCSI.UI { public partial class ConnectorBindingsCSI : ConnectorBindings - { - #region sending + public override void PreviewSend(StreamState state, ProgressViewModel progress) + { + // TODO! + } public override async Task SendStream(StreamState state, ProgressViewModel progress) { - //throw new NotImplementedException(); var kit = KitManager.GetDefaultKit(); //var converter = new ConverterCSI(); var appName = GetHostAppVersion(Model); @@ -34,9 +35,7 @@ public override async Task SendStream(StreamState state, ProgressViewMod int objCount = 0; if (state.Filter != null) - { state.SelectedObjectIds = GetSelectionFilterObjects(state.Filter); - } var totalObjectCount = state.SelectedObjectIds.Count(); @@ -51,7 +50,6 @@ public override async Task SendStream(StreamState state, ProgressViewMod conversionProgressDict["Conversion"] = 0; progress.Update(conversionProgressDict); - //if( commitObj["@Stories"] == null) //{ // commitObj["@Stories"] = converter.ConvertToSpeckle(("Stories", "CSI")); @@ -60,9 +58,7 @@ public override async Task SendStream(StreamState state, ProgressViewMod foreach (var applicationId in state.SelectedObjectIds) { if (progress.CancellationTokenSource.Token.IsCancellationRequested) - { return null; - } Base converted = null; string containerName = string.Empty; @@ -78,7 +74,6 @@ public override async Task SendStream(StreamState state, ProgressViewMod continue; } - var typeAndName = ConnectorCSIUtils.ObjectIDsTypesAndNames .Where(pair => pair.Key == applicationId) .Select(pair => pair.Value).FirstOrDefault(); @@ -92,7 +87,6 @@ public override async Task SendStream(StreamState state, ProgressViewMod continue; } - //if (converted != null) //{ // if (commitObj[selectedObjectType] == null) @@ -109,24 +103,15 @@ public override async Task SendStream(StreamState state, ProgressViewMod Base ElementCount = converter.ConvertToSpeckle(("ElementsCount", "CSI")); if (ElementCount.applicationId != null) - { objCount = Convert.ToInt32(ElementCount.applicationId); - } else - { objCount = 0; - } - if (commitObj["@Model"] == null) - { commitObj["@Model"] = converter.ConvertToSpeckle(("Model", "CSI")); - } - + if (commitObj["AnalysisResults"] == null) - { commitObj["AnalysisResults"] = converter.ConvertToSpeckle(("AnalysisResults", "CSI")); - } progress.Report.Merge(converter.Report); @@ -137,9 +122,7 @@ public override async Task SendStream(StreamState state, ProgressViewMod } if (progress.CancellationTokenSource.Token.IsCancellationRequested) - { return null; - } var streamId = state.StreamId; var client = state.Client; @@ -161,12 +144,8 @@ public override async Task SendStream(StreamState state, ProgressViewMod disposeTransports: true ); - if (progress.Report.OperationErrorsCount != 0) - { - //RaiseNotification($"Failed to send: \n {Exceptions.Last().Message}"); return null; - } var actualCommit = new CommitCreateInput { @@ -184,7 +163,6 @@ public override async Task SendStream(StreamState state, ProgressViewMod var commitId = await client.CommitCreate(actualCommit); state.PreviousCommitId = commitId; return commitId; - } catch (Exception e) { @@ -194,7 +172,5 @@ public override async Task SendStream(StreamState state, ProgressViewMod return null; //return state; } - - #endregion } } \ No newline at end of file diff --git a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.cs b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.cs index b26d87197c..ec5b1bc582 100644 --- a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.cs +++ b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.cs @@ -20,14 +20,11 @@ public ConnectorBindingsCSI(cSapModel model) Model = model; } - - public override List GetReceiveModes() { return new List { ReceiveMode.Create }; } - #region boilerplate public override string GetActiveViewName() { @@ -65,12 +62,12 @@ public override List GetObjectsInView() throw new NotImplementedException(); } + public override void ResetDocument() + { + // TODO! + } #endregion - - - - } } \ No newline at end of file diff --git a/ConnectorGrasshopper/ConnectorGrasshopper/Properties/AssemblyInfo.cs b/ConnectorGrasshopper/ConnectorGrasshopper/Properties/AssemblyInfo.cs index 11818d5502..ac7b5aec47 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopper/Properties/AssemblyInfo.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopper/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/ConnectorMicroStation/ConnectorMicroStationOpenShared/UI/Bindings.cs b/ConnectorMicroStation/ConnectorMicroStationOpenShared/UI/Bindings.cs index 6c32e7cf71..d47d1913fc 100644 --- a/ConnectorMicroStation/ConnectorMicroStationOpenShared/UI/Bindings.cs +++ b/ConnectorMicroStation/ConnectorMicroStationOpenShared/UI/Bindings.cs @@ -305,9 +305,9 @@ public override async Task ReceiveStream(StreamState state) var flattenedObjects = FlattenCommitObject(commitObject, converter); - List newPlaceholderObjects; + List newPlaceholderObjects; if (Control.InvokeRequired) - newPlaceholderObjects = (List)Control.Invoke(new NativeConversionAndBakeDelegate(ConvertAndBakeReceivedObjects), new object[] { flattenedObjects, converter, state }); + newPlaceholderObjects = (List)Control.Invoke(new NativeConversionAndBakeDelegate(ConvertAndBakeReceivedObjects), new object[] { flattenedObjects, converter, state }); else newPlaceholderObjects = ConvertAndBakeReceivedObjects(flattenedObjects, converter, state); @@ -347,7 +347,7 @@ public override async Task ReceiveStream(StreamState state) //delete previously sent object that are no more in this stream - private void DeleteObjects(List previouslyReceiveObjects, List newPlaceholderObjects) + private void DeleteObjects(List previouslyReceiveObjects, List newPlaceholderObjects) { foreach (var obj in previouslyReceiveObjects) { @@ -355,20 +355,18 @@ private void DeleteObjects(List previouslyReceiveO continue; // get the model object from id - ulong id = Convert.ToUInt64(obj.ApplicationGeneratedId); + ulong id = Convert.ToUInt64(obj.CreatedIds.FirstOrDefault()); var element = Model.FindElementById((ElementId)id); if (element != null) - { element.DeleteFromModel(); - } } } - delegate List NativeConversionAndBakeDelegate(List objects, ISpeckleConverter converter, StreamState state); + delegate List NativeConversionAndBakeDelegate(List objects, ISpeckleConverter converter, StreamState state); - private List ConvertAndBakeReceivedObjects(List objects, ISpeckleConverter converter, StreamState state) + private List ConvertAndBakeReceivedObjects(List objects, ISpeckleConverter converter, StreamState state) { - var placeholders = new List(); + var placeholders = new List(); var conversionProgressDict = new ConcurrentDictionary(); conversionProgressDict["Conversion"] = 1; @@ -392,11 +390,11 @@ private List ConvertAndBakeReceivedObjects(List placeholderList) + else if (convRes is List placeholderList) { placeholders.AddRange(placeholderList); } @@ -486,7 +484,7 @@ public override void RemoveStreamFromFile(string streamId) public override void SelectClientObjects(string args) { - throw new NotImplementedException(); + // TODO! } delegate void SetContextDelegate(object session); diff --git a/ConnectorMicroStation/ConnectorMicroStationOpenShared/UI/Bindings2.cs b/ConnectorMicroStation/ConnectorMicroStationOpenShared/UI/Bindings2.cs index 88ec1be511..d4c51f69e6 100644 --- a/ConnectorMicroStation/ConnectorMicroStationOpenShared/UI/Bindings2.cs +++ b/ConnectorMicroStation/ConnectorMicroStationOpenShared/UI/Bindings2.cs @@ -165,8 +165,6 @@ public override List GetSelectionFilters() filterList.Add(new ListSelectionFilter { Slug = "civilElementType", Name = "Civil Features", Icon = "RailroadVariant", Description = "Selects civil features based on their type.", Values = civilElementTypes }); #endif - - return filterList; } @@ -186,9 +184,9 @@ public override List GetCustomStreamMenuItems() return new List(); } - public override void SelectClientObjects(string args) + public override void SelectClientObjects(List args, bool deselect = false) { - throw new NotImplementedException(); + // TODO! } #endregion @@ -267,9 +265,9 @@ await state.Client.CommitReceived(new CommitReceivedInput // invoke conversions on the main thread via control int count = 0; var flattenedObjects = FlattenCommitObject(commitObject, converter, ref count); - List newPlaceholderObjects; + List newPlaceholderObjects; if (Control.InvokeRequired) - newPlaceholderObjects = (List)Control.Invoke(new NativeConversionAndBakeDelegate(ConvertAndBakeReceivedObjects), new object[] { flattenedObjects, converter, state, progress }); + newPlaceholderObjects = (List)Control.Invoke(new NativeConversionAndBakeDelegate(ConvertAndBakeReceivedObjects), new object[] { flattenedObjects, converter, state, progress }); else newPlaceholderObjects = ConvertAndBakeReceivedObjects(flattenedObjects, converter, state, progress); @@ -301,10 +299,10 @@ await state.Client.CommitReceived(new CommitReceivedInput return state; } - delegate List NativeConversionAndBakeDelegate(List objects, ISpeckleConverter converter, StreamState state, ProgressViewModel progress); - private List ConvertAndBakeReceivedObjects(List objects, ISpeckleConverter converter, StreamState state, ProgressViewModel progress) + delegate List NativeConversionAndBakeDelegate(List objects, ISpeckleConverter converter, StreamState state, ProgressViewModel progress); + private List ConvertAndBakeReceivedObjects(List objects, ISpeckleConverter converter, StreamState state, ProgressViewModel progress) { - var placeholders = new List(); + var placeholders = new List(); var conversionProgressDict = new ConcurrentDictionary(); conversionProgressDict["Conversion"] = 0; Execute.PostToUIThread(() => progress.Max = state.SelectedObjectIds.Count()); @@ -326,9 +324,9 @@ private List ConvertAndBakeReceivedObjects(List placeholderList) + else if (convRes is List placeholderList) placeholders.AddRange(placeholderList); // creating new elements, not updating existing! @@ -422,7 +420,7 @@ private List FlattenCommitObject(object obj, ISpeckleConverter converter, } //delete previously sent object that are no longer in this stream - private void DeleteObjects(List previouslyReceiveObjects, List newPlaceholderObjects) + private void DeleteObjects(List previouslyReceiveObjects, List newPlaceholderObjects) { foreach (var obj in previouslyReceiveObjects) { @@ -430,12 +428,10 @@ private void DeleteObjects(List previouslyReceiveO continue; // get the model object from id - ulong id = Convert.ToUInt64(obj.ApplicationGeneratedId); + ulong id = Convert.ToUInt64(obj.CreatedIds.FirstOrDefault()); var element = Model.FindElementById((ElementId)id); if (element != null) - { element.DeleteFromModel(); - } } } #endregion @@ -842,6 +838,22 @@ private void WriteStateToFile() else StreamStateManager2.WriteStreamStateList(File, DocumentStreams); } + + public override void ResetDocument() + { + // TODO! + } + + public override void PreviewSend(StreamState state, ProgressViewModel progress) + { + // TODO! + } + + public override Task PreviewReceive(StreamState state, ProgressViewModel progress) + { + return null; + // TODO! + } #endregion } } \ No newline at end of file diff --git a/ConnectorRevit/ConnectorRevit/ConnectorRevitUtils.cs b/ConnectorRevit/ConnectorRevit/ConnectorRevitUtils.cs index 09d85eb5e6..5df730d745 100644 --- a/ConnectorRevit/ConnectorRevit/ConnectorRevitUtils.cs +++ b/ConnectorRevit/ConnectorRevit/ConnectorRevitUtils.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Autodesk.Revit.DB; @@ -210,7 +211,15 @@ public static bool IsElementSupported(this Element e) return false; } - + /// + /// Removes all inherited classes from speckle type string + /// + /// + /// + public static string SimplifySpeckleType(string type) + { + return type.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); + } //list of currently supported Categories (for sending only) //exact copy of the one in the ConverterRevitShared.Categories diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Receive.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Receive.cs index dfad480fe1..1c77d1e7f4 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Receive.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Receive.cs @@ -97,7 +97,7 @@ public override async Task ReceiveStream(StreamState state) // needs to be set for editing to work converter.SetPreviousContextObjects(previouslyReceiveObjects); // needs to be set for openings in floors and roofs to work - converter.SetContextObjects(flattenedObjects.Select(x => new ApplicationPlaceholderObject { applicationId = x.applicationId, NativeObject = x }).ToList()); + converter.SetContextObjects(flattenedObjects.Select(x => new ApplicationObject(x.id, ConnectorRevitUtils.SimplifySpeckleType(x.speckle_type)) { applicationId = x.applicationId }).ToList()); var newPlaceholderObjects = ConvertReceivedObjects(flattenedObjects, converter, state); // receive was cancelled by user if (newPlaceholderObjects == null) @@ -148,25 +148,22 @@ public override async Task ReceiveStream(StreamState state) } //delete previously sent object that are no more in this stream - private void DeleteObjects(List previouslyReceiveObjects, List newPlaceholderObjects) + private void DeleteObjects(List previouslyReceiveObjects, List newPlaceholderObjects) { foreach (var obj in previouslyReceiveObjects) { if (newPlaceholderObjects.Any(x => x.applicationId == obj.applicationId)) continue; - var element = CurrentDoc.Document.GetElement(obj.ApplicationGeneratedId); + var element = CurrentDoc.Document.GetElement(obj.CreatedIds.FirstOrDefault()); if (element != null) - { CurrentDoc.Document.Delete(element.Id); - } - } } - private List ConvertReceivedObjects(List objects, ISpeckleConverter converter, StreamState state) + private List ConvertReceivedObjects(List objects, ISpeckleConverter converter, StreamState state) { - var placeholders = new List(); + var placeholders = new List(); var conversionProgressDict = new ConcurrentDictionary(); conversionProgressDict["Conversion"] = 1; @@ -188,14 +185,10 @@ private List ConvertReceivedObjects(List obj }, System.Windows.Threading.DispatcherPriority.Background); var convRes = converter.ConvertToNative(@base); - if (convRes is ApplicationPlaceholderObject placeholder) - { + if (convRes is ApplicationObject placeholder) placeholders.Add(placeholder); - } - else if (convRes is List placeholderList) - { + else if (convRes is List placeholderList) placeholders.AddRange(placeholderList); - } } catch (Exception e) { @@ -254,8 +247,5 @@ private List FlattenCommitObject(object obj, ISpeckleConverter converter) return objects; } - - - } } diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Send.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Send.cs index 029d42702d..57a16df910 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Send.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Send.cs @@ -18,8 +18,6 @@ namespace Speckle.ConnectorRevit.UI { public partial class ConnectorBindingsRevit { - - /// /// Converts the Revit elements that have been added to the stream by the user, sends them to /// the Server and the local DB, and creates a commit with the objects. @@ -50,14 +48,13 @@ public override async Task SendStream(StreamState state) selectedObjects = state.SelectedObjectIds.Select(x => CurrentDoc.Document.GetElement(x)).Where(x => x != null).ToList(); } - if (!selectedObjects.Any()) { state.Errors.Add(new Exception("There are zero objects to send. Please use a filter, or set some via selection.")); return state; } - converter.SetContextObjects(selectedObjects.Select(x => new ApplicationPlaceholderObject { applicationId = x.UniqueId }).ToList()); + converter.SetContextObjects(selectedObjects.Select(x => new ApplicationObject(x.UniqueId, x.GetType().ToString()) { applicationId = x.UniqueId }).ToList()); var commitObject = new Base(); @@ -72,9 +69,7 @@ public override async Task SendStream(StreamState state) try { if (revitElement == null) - { continue; - } if (!converter.CanConvertToSpeckle(revitElement)) { @@ -87,7 +82,7 @@ public override async Task SendStream(StreamState state) conversionProgressDict["Conversion"]++; UpdateProgress(conversionProgressDict, state.Progress); - placeholders.Add(new ApplicationPlaceholderObject { applicationId = revitElement.UniqueId, ApplicationGeneratedId = revitElement.UniqueId }); + placeholders.Add(new ApplicationObject(revitElement.UniqueId, revitElement.GetType().ToString()) { applicationId = revitElement.UniqueId }); convertedCount++; @@ -97,18 +92,15 @@ public override async Task SendStream(StreamState state) { var category = $"@{revitElement.Category.Name}"; if (commitObject[category] == null) - { commitObject[category] = new List(); - } - ((List)commitObject[category]).Add(conversionResult); - } + ((List)commitObject[category]).Add(conversionResult); + } } catch (Exception e) { state.Errors.Add(e); } - } if (converter.Report.ConversionErrorsCount != 0) @@ -127,9 +119,7 @@ public override async Task SendStream(StreamState state) Execute.PostToUIThread(() => state.Progress.Maximum = (int)commitObject.GetTotalChildrenCount()); if (state.CancellationTokenSource.Token.IsCancellationRequested) - { return state; - } var transports = new List() { new ServerTransport(client.Account, streamId) }; @@ -155,9 +145,7 @@ public override async Task SendStream(StreamState state) } if (state.CancellationTokenSource.Token.IsCancellationRequested) - { return null; - } var actualCommit = new CommitCreateInput() { diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.cs index f1dc6fbdc2..8b7d9cc0b7 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.cs @@ -77,7 +77,7 @@ private void SelectionTimer_Elapsed(object sender, ElapsedEventArgs e) public override void SelectClientObjects(string args) { - throw new NotImplementedException(); + // TODO! } #region app events diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit2/ConnectorBindingsRevit2.Receive.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit2/ConnectorBindingsRevit2.Receive.cs index 0ccd25d9ac..00da38ba4e 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit2/ConnectorBindingsRevit2.Receive.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit2/ConnectorBindingsRevit2.Receive.cs @@ -19,6 +19,15 @@ namespace Speckle.ConnectorRevit.UI { public partial class ConnectorBindingsRevit2 { + public List Preview { get; set; } = new List(); + public Dictionary StoredObjects = new Dictionary(); + + public override Task PreviewReceive(StreamState state, ProgressViewModel progress) + { + return null; + // TODO! + } + /// /// Receives a stream and bakes into the existing revit file. /// @@ -43,9 +52,7 @@ public override async Task ReceiveStream(StreamState state, Progres var stream = await state.Client.StreamGet(state.StreamId); if (progress.CancellationTokenSource.Token.IsCancellationRequested) - { return null; - } Commit myCommit = null; //if "latest", always make sure we get the latest commit when the user clicks "receive" @@ -90,16 +97,13 @@ await state.Client.CommitReceived(new CommitReceivedInput } if (progress.Report.OperationErrorsCount != 0) - { return state; - } if (progress.CancellationTokenSource.Token.IsCancellationRequested) - { return null; - } - + Preview.Clear(); + StoredObjects.Clear(); await RevitTask.RunAsync(app => { @@ -111,17 +115,20 @@ await RevitTask.RunAsync(app => t.SetFailureHandlingOptions(failOpts); t.Start(); - var flattenedObjects = FlattenCommitObject(commitObject, converter); + Preview = FlattenCommitObject(commitObject, converter); + foreach (var previewObj in Preview) + progress.Report.Log(previewObj); + converter.ReceiveMode = state.ReceiveMode; // needs to be set for editing to work converter.SetPreviousContextObjects(previouslyReceiveObjects); // needs to be set for openings in floors and roofs to work - converter.SetContextObjects(flattenedObjects.Select(x => new ApplicationPlaceholderObject { applicationId = x.applicationId, NativeObject = x }).ToList()); - var newPlaceholderObjects = ConvertReceivedObjects(flattenedObjects, converter, state, progress); + converter.SetContextObjects(Preview); + var newPlaceholderObjects = ConvertReceivedObjects(converter, progress); // receive was cancelled by user if (newPlaceholderObjects == null) { - progress.Report.LogConversionError(new Exception("fatal error: receive cancelled by user")); + progress.Report.LogOperationError(new Exception("fatal error: receive cancelled by user")); t.RollBack(); return; } @@ -132,48 +139,43 @@ await RevitTask.RunAsync(app => state.ReceivedObjects = newPlaceholderObjects; t.Commit(); - progress.Report.Merge(converter.Report); } }); if (converter.Report.ConversionErrors.Any(x => x.Message.Contains("fatal error"))) - { - // the commit is being rolled back - return null; - } + return null; // the commit is being rolled back return state; } //delete previously sent object that are no more in this stream - private void DeleteObjects(List previouslyReceiveObjects, List newPlaceholderObjects) + private void DeleteObjects(List previouslyReceiveObjects, List newPlaceholderObjects) { foreach (var obj in previouslyReceiveObjects) { if (newPlaceholderObjects.Any(x => x.applicationId == obj.applicationId)) continue; - var element = CurrentDoc.Document.GetElement(obj.ApplicationGeneratedId); + var element = CurrentDoc.Document.GetElement(obj.CreatedIds.FirstOrDefault()); if (element != null) - { CurrentDoc.Document.Delete(element.Id); - } } } - private List ConvertReceivedObjects(List objects, ISpeckleConverter converter, StreamState state, ProgressViewModel progress) + private List ConvertReceivedObjects(ISpeckleConverter converter, ProgressViewModel progress) { - var placeholders = new List(); + var placeholders = new List(); var conversionProgressDict = new ConcurrentDictionary(); conversionProgressDict["Conversion"] = 1; // Get setting to skip linked model elements if necessary - var receiveLinkedModelsSetting = (CurrentSettings.FirstOrDefault(x => x.Slug == "linkedmodels-receive") as CheckBoxSetting); + var receiveLinkedModelsSetting = CurrentSettings.FirstOrDefault(x => x.Slug == "linkedmodels-receive") as CheckBoxSetting; var receiveLinkedModels = receiveLinkedModelsSetting != null ? receiveLinkedModelsSetting.IsChecked : false; - foreach (var @base in objects) + foreach (var obj in Preview) { + var @base = StoredObjects[obj.OriginalId]; if (progress.CancellationTokenSource.Token.IsCancellationRequested) { placeholders = null; @@ -190,13 +192,15 @@ private List ConvertReceivedObjects(List obj continue; var convRes = converter.ConvertToNative(@base); - if (convRes is ApplicationPlaceholderObject placeholder) + if (convRes is ApplicationObject placeholder) { placeholders.Add(placeholder); + obj.Update(status: placeholder.Status, createdIds: placeholder.CreatedIds, converted: placeholder.Converted, log: placeholder.Log); + progress.Report.Log(obj); } - else if (convRes is List placeholderList) + else { - placeholders.AddRange(placeholderList); + } } catch (Exception e) @@ -214,24 +218,25 @@ private List ConvertReceivedObjects(List obj /// /// /// - private List FlattenCommitObject(object obj, ISpeckleConverter converter) + private List FlattenCommitObject(object obj, ISpeckleConverter converter) { - List objects = new List(); + var objects = new List(); if (obj is Base @base) { + var appObj = new ApplicationObject(@base.id, ConnectorRevitUtils.SimplifySpeckleType(@base.speckle_type)) { applicationId = @base.applicationId, Status = ApplicationObject.State.Unknown }; + if (converter.CanConvertToNative(@base)) { - objects.Add(@base); - + appObj.Convertible = true; + objects.Add(appObj); + StoredObjects.Add(@base.id, @base); return objects; } else { foreach (var prop in @base.GetDynamicMembers()) - { objects.AddRange(FlattenCommitObject(@base[prop], converter)); - } return objects; } } @@ -239,25 +244,25 @@ private List FlattenCommitObject(object obj, ISpeckleConverter converter) if (obj is List list) { foreach (var listObj in list) - { objects.AddRange(FlattenCommitObject(listObj, converter)); - } return objects; } if (obj is IDictionary dict) { foreach (DictionaryEntry kvp in dict) - { objects.AddRange(FlattenCommitObject(kvp.Value, converter)); - } return objects; } else { if (obj != null && !obj.GetType().IsPrimitive && !(obj is string)) - converter.Report.Log($"Skipped object of type {obj.GetType()}, not supported."); + { + var appObj = new ApplicationObject(obj.GetHashCode().ToString(), obj.GetType().ToString()); + appObj.Update(status: ApplicationObject.State.Skipped, logItem: $"Receiving objects of type {obj.GetType()} not supported in Revit"); + objects.Add(appObj); + } } return objects; diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit2/ConnectorBindingsRevit2.Selection.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit2/ConnectorBindingsRevit2.Selection.cs index d05a3001a4..39daece7f3 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit2/ConnectorBindingsRevit2.Selection.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit2/ConnectorBindingsRevit2.Selection.cs @@ -82,7 +82,7 @@ private List GetLinkedDocuments() var docs = new List(); // Get settings and return empty list if we should not send linked models - var sendLinkedModels = CurrentSettings.FirstOrDefault(x => x.Slug == "linkedmodels-send") as CheckBoxSetting; + var sendLinkedModels = CurrentSettings?.FirstOrDefault(x => x.Slug == "linkedmodels-send") as CheckBoxSetting; if (sendLinkedModels == null || !sendLinkedModels.IsChecked) return docs; diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit2/ConnectorBindingsRevit2.Send.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit2/ConnectorBindingsRevit2.Send.cs index 89f5069493..a0e102b98e 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit2/ConnectorBindingsRevit2.Send.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit2/ConnectorBindingsRevit2.Send.cs @@ -17,6 +17,23 @@ public partial class ConnectorBindingsRevit2 { // used to store the Stream State settings when sending/receiving private List CurrentSettings { get; set; } + + public override void PreviewSend(StreamState state, ProgressViewModel progress) + { + var filterObjs = GetSelectionFilterObjects(state.Filter); + foreach (var filterObj in filterObjs) + { + var type = filterObj.GetType().ToString(); + var reportObj = new ApplicationObject(filterObj.UniqueId, type); + if (!Converter.CanConvertToSpeckle(filterObj)) + reportObj.Update(status: ApplicationObject.State.Skipped, logItem: $"Sending objects of type {type} not supported in Revit"); + else + reportObj.Update(status: ApplicationObject.State.Created); + progress.Report.Log(reportObj); + } + SelectClientObjects(filterObjs.Select(o => o.UniqueId).ToList()); + } + /// /// Converts the Revit elements that have been added to the stream by the user, sends them to /// the Server and the local DB, and creates a commit with the objects. @@ -24,17 +41,15 @@ public partial class ConnectorBindingsRevit2 /// StreamState passed by the UI public override async Task SendStream(StreamState state, ProgressViewModel progress) { - - var kit = KitManager.GetDefaultKit(); - var converter = kit.LoadConverter(ConnectorRevitUtils.RevitAppName); - converter.SetContextDocument(CurrentDoc.Document); + Converter.SetContextDocument(CurrentDoc.Document); + Converter.Report.ReportObjects.Clear(); // set converter settings as tuples (setting slug, setting selection) var settings = new Dictionary(); CurrentSettings = state.Settings; foreach (var setting in state.Settings) settings.Add(setting.Slug, setting.Selection); - converter.SetConverterSettings(settings); + Converter.SetConverterSettings(settings); var streamId = state.StreamId; var client = state.Client; @@ -48,66 +63,78 @@ public override async Task SendStream(StreamState state, ProgressViewMod return null; } - converter.SetContextObjects(selectedObjects.Select(x => new ApplicationPlaceholderObject { applicationId = x.UniqueId }).ToList()); - - var commitObject = converter.ConvertToSpeckle(CurrentDoc.Document) ?? new Base(); + Converter.SetContextObjects(selectedObjects.Select(x => new ApplicationObject(x.UniqueId, x.GetType().ToString()) { applicationId = x.UniqueId }).ToList()); + var commitObject = Converter.ConvertToSpeckle(CurrentDoc.Document) ?? new Base(); var conversionProgressDict = new ConcurrentDictionary(); conversionProgressDict["Conversion"] = 0; progress.Max = selectedObjects.Count(); var convertedCount = 0; - - var placeholders = new List(); foreach (var revitElement in selectedObjects) { + var type = revitElement.GetType().ToString(); + // get the report object + // for hosted elements, they may have already been converted and added to the converter report + bool alreadyConverted = Converter.Report.GetReportObject(revitElement.UniqueId, out int index); + var reportObj = alreadyConverted ? + Converter.Report.ReportObjects[index] : + new ApplicationObject(revitElement.UniqueId, type) { applicationId = revitElement.UniqueId }; + if (alreadyConverted) + { + progress.Report.Log(reportObj); + continue; + } try { if (revitElement == null) continue; - if (!converter.CanConvertToSpeckle(revitElement)) + if (!Converter.CanConvertToSpeckle(revitElement)) { - progress.Report.Log($"Skipped not supported type: {revitElement.GetType()}, name {revitElement.Name}"); + reportObj.Update(status: ApplicationObject.State.Skipped, logItem: $"Sending objects of type {type} not supported in Revit"); + progress.Report.Log(reportObj); continue; } if (progress.CancellationTokenSource.Token.IsCancellationRequested) return null; - var conversionResult = converter.ConvertToSpeckle(revitElement); + Converter.Report.Log(reportObj); // Log object so converter can access + var conversionResult = Converter.ConvertToSpeckle(revitElement); conversionProgressDict["Conversion"]++; progress.Update(conversionProgressDict); - - placeholders.Add(new ApplicationPlaceholderObject { applicationId = revitElement.UniqueId, ApplicationGeneratedId = revitElement.UniqueId }); - convertedCount++; - //hosted elements will be returned as `null` by the ConvertToSpeckle method - //since they are handled when converting their parents - if (conversionResult != null) + if (conversionResult == null) { - var category = $"@{revitElement.Category.Name}"; - if (commitObject[category] == null) - commitObject[category] = new List(); - - ((List)commitObject[category]).Add(conversionResult); + reportObj.Update(status: ApplicationObject.State.Failed, logItem: $"Conversion returned null"); + progress.Report.Log(reportObj); + continue; } + var category = $"@{revitElement.Category.Name}"; + if (commitObject[category] == null) + commitObject[category] = new List(); + + ((List)commitObject[category]).Add(conversionResult); + + reportObj.Update(status: ApplicationObject.State.Created, logItem: $"Sent as {ConnectorRevitUtils.SimplifySpeckleType(conversionResult.speckle_type)}"); } catch (Exception e) { - progress.Report.LogConversionError(e); + reportObj.Update(status: ApplicationObject.State.Failed, logItem: $"{e.Message}"); } + progress.Report.Log(reportObj); } - progress.Report.Merge(converter.Report); + progress.Report.Merge(Converter.Report); if (convertedCount == 0) { - progress.Report.LogConversionError(new Exception("Zero objects converted successfully. Send stopped.")); + progress.Report.LogOperationError(new Exception("Zero objects converted successfully. Send stopped.")); return null; } diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit2/ConnectorBindingsRevit2.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit2/ConnectorBindingsRevit2.cs index 0cbdc9297c..9d62bda0a6 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit2/ConnectorBindingsRevit2.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit2/ConnectorBindingsRevit2.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Timers; using Autodesk.Revit.DB; using Autodesk.Revit.UI; @@ -20,6 +21,7 @@ public partial class ConnectorBindingsRevit2 : ConnectorBindings public Timer SelectionTimer; + public ISpeckleConverter Converter { get; set; } = KitManager.GetDefaultKit().LoadConverter(ConnectorRevitUtils.RevitAppName); public List ConversionErrors { get; set; } = new List(); @@ -54,9 +56,30 @@ private void SelectionTimer_Elapsed(object sender, ElapsedEventArgs e) public override string GetFileName() => CurrentDoc.Document.Title; - public override void SelectClientObjects(string args) + public override void SelectClientObjects(List args, bool deselect = false) { - throw new NotImplementedException(); + var selection = args.Select(x => CurrentDoc.Document.GetElement(x))?.Where(x => x != null)?.Select(x => x.Id)?.ToList(); + if (selection != null) + { + if (!deselect) + { + var currentSelection = CurrentDoc.Selection.GetElementIds().ToList(); + if (currentSelection != null) currentSelection.AddRange(selection); + else currentSelection = selection; + try + { + CurrentDoc.Selection.SetElementIds(currentSelection); + CurrentDoc.ShowElements(currentSelection); + } + catch (Exception e) { } + } + else + { + var updatedSelection = CurrentDoc.Selection.GetElementIds().Where(x => !selection.Contains(x)).ToList(); + CurrentDoc.Selection.SetElementIds(updatedSelection); + if (updatedSelection.Any()) CurrentDoc.ShowElements(updatedSelection); + } + } } public override List GetStreamsInFile() @@ -78,5 +101,10 @@ public override List GetCustomStreamMenuItems() { return new List(); } + + public override void ResetDocument() + { + // TODO! + } } } diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/BIMElements/BIMElementFilter.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/BIMElements/BIMElementFilter.cs index 13db1b6ff4..e96ec828aa 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/BIMElements/BIMElementFilter.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/BIMElements/BIMElementFilter.cs @@ -107,9 +107,7 @@ private void ApplyGeomFilter(List objs, string inputSchema = null) List schemasToTest = GetValidSchemas(obj); foreach (var testSchema in schemasToTest) if (IsViableSchemaObject(testSchema, obj)) - { SchemaDictionary[testSchema.ToString()].Add(obj); break; - } } } } @@ -190,9 +188,7 @@ private bool IsViableSchemaObject(SupportedSchema schema, RhinoObject obj) { Mesh mesh = obj.Geometry as Mesh; if (!mesh.IsClosed) - { return true; - } } catch { } break; diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.cs index d83ff4c0d8..e01967876f 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.cs @@ -7,23 +7,27 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using System.Timers; -using DesktopUI2; -using DesktopUI2.Models; -using DesktopUI2.Models.Filters; -using DesktopUI2.Models.Settings; -using DesktopUI2.ViewModels; + using Rhino; +using Rhino.Display; using Rhino.DocObjects; using Rhino.Geometry; using Rhino.Render; + using Speckle.Core.Api; using Speckle.Core.Kits; using Speckle.Core.Logging; using Speckle.Core.Models; using Speckle.Core.Transports; using Speckle.Newtonsoft.Json; -using Timer = System.Timers.Timer; + +using DesktopUI2; +using DesktopUI2.Models; +using DesktopUI2.Models.Filters; +using DesktopUI2.Models.Settings; +using DesktopUI2.ViewModels; + +using ApplicationObject = Speckle.Core.Models.ApplicationObject; namespace SpeckleRhino { @@ -31,27 +35,20 @@ public partial class ConnectorBindingsRhino : ConnectorBindings { public RhinoDoc Doc { get => RhinoDoc.ActiveDoc; } - public Timer SelectionTimer; - private static string SpeckleKey = "speckle2"; private static string UserStrings = "userStrings"; private static string UserDictionary = "userDictionary"; + public ISpeckleConverter Converter { get; set; } = KitManager.GetDefaultKit().LoadConverter(Utils.RhinoAppName); + public Dictionary StoredObjects = new Dictionary(); + public List Preview { get; set; } = new List(); + public PreviewConduit PreviewConduit { get; set; } + private string SelectedReceiveCommit { get; set; } + public ConnectorBindingsRhino() { RhinoDoc.EndOpenDocument += RhinoDoc_EndOpenDocument; RhinoDoc.LayerTableEvent += RhinoDoc_LayerChange; - - SelectionTimer = new Timer(2000) { AutoReset = true, Enabled = true }; - SelectionTimer.Elapsed += SelectionTimer_Elapsed; - SelectionTimer.Start(); - } - - private void SelectionTimer_Elapsed(object sender, ElapsedEventArgs e) - { - if (Doc == null) - return; - var selection = GetSelectedObjects(); } private void RhinoDoc_EndOpenDocument(object sender, DocumentOpenEventArgs e) @@ -75,7 +72,6 @@ private void RhinoDoc_LayerChange(object sender, Rhino.DocObjects.Tables.LayerTa UpdateSelectedStream(); } - public override List GetReceiveModes() { return new List { ReceiveMode.Create }; @@ -128,10 +124,42 @@ public override string GetDocumentId() public override string GetFileName() => Doc?.Name; + //improve this to add to log?? + private void LogUnsupportedObjects(List objs, ISpeckleConverter converter) + { + var reportLog = new Dictionary(); + foreach (var obj in objs) + { + var type = obj.ObjectType.ToString(); + if (reportLog.ContainsKey(type)) reportLog[type] = reportLog[type]++; + else reportLog.Add(type, 1); + } + //converter.Report.LogOperationError(); + RhinoApp.WriteLine("Deselected unsupported objects:"); + foreach (var entry in reportLog) + Rhino.RhinoApp.WriteLine($"{entry.Value} of type {entry.Key}"); + } public override List GetSelectedObjects() { - var objs = Doc?.Objects.GetSelectedObjects(true, false).Select(obj => obj.Id.ToString()).ToList(); - return objs; + var objs = new List(); + if (Doc == null) return objs; + + Converter.SetContextDocument(Doc); + + var selected = Doc.Objects.GetSelectedObjects(true, false).ToList(); + if (selected.Count == 0) return objs; + var supportedObjs = selected.Where(o => Converter.CanConvertToSpeckle(o) == true)?.ToList(); + var unsupportedObjs = selected.Where(o => Converter.CanConvertToSpeckle(o) == false)?.ToList(); + + // handle any unsupported objects and modify doc selection if so + if (unsupportedObjs.Count > 0) + { + LogUnsupportedObjects(unsupportedObjs, Converter); + Doc.Objects.UnselectAll(false); + supportedObjs.ForEach(o => o.Select(true, true)); + } + + return supportedObjs.Select(o => o.Id.ToString())?.ToList(); } public override List GetSelectionFilters() @@ -161,152 +189,297 @@ public override List GetSettings() return new List(); } - public override void SelectClientObjects(string args) + public override void SelectClientObjects(List objs, bool deselect = false) + { + foreach (var id in objs) + { + RhinoObject obj = Doc.Objects.FindId(new Guid(id)); + if (obj != null) + { + if (deselect) obj.Select(false, true, false, true, true, true); + else obj.Select(true, true, true, true, true, true); + } + else + { + // this may be a receive select: try finding the preview object + if (PreviewConduit != null && PreviewConduit.Enabled) + { + PreviewConduit.Enabled = false; + PreviewConduit.SelectPreviewObject(id, deselect); + PreviewConduit.Enabled = true; + } + } + } + Doc.Views.ActiveView.ActiveViewport.ZoomExtentsSelected(); + Doc.Views.Redraw(); + } + + public override void ResetDocument() { - throw new NotImplementedException(); + if (PreviewConduit != null) + PreviewConduit.Enabled = false; + + Doc.Objects.UnselectAll(true); // TODO: consider instead of unselecting, storing doc visibility state and restoring to this point + Doc.Views.Redraw(); } #endregion #region receiving - - public override async Task ReceiveStream(StreamState state, ProgressViewModel progress) + public override async Task PreviewReceive(StreamState state, ProgressViewModel progress) { - var kit = KitManager.GetDefaultKit(); - var converter = kit.LoadConverter(Utils.RhinoAppName); + // first check if commit is the same and preview objects have already been generated + Commit commit = await GetCommitFromState(state, progress); - if (converter == null) + if (commit.id != SelectedReceiveCommit) { - throw new Exception("Could not find any Kit!"); - progress.CancellationTokenSource.Cancel(); - } - - converter.SetContextDocument(Doc); + var commitObject = await GetCommit(commit, state, progress); + if (commitObject == null) + { + progress.Report.LogOperationError(new Exception($"Could not retrieve commit {commit.id} from server")); + progress.CancellationTokenSource.Cancel(); + } + + SelectedReceiveCommit = commit.id; + Preview.Clear(); + StoredObjects.Clear(); + + int count = 0; + var commitLayerName = DesktopUI2.Formatting.CommitInfo(state.CachedStream.name, state.BranchName, commit.id); // get commit layer name + Preview = FlattenCommitObject(commitObject, commitLayerName, ref count); + Doc.Notes += "%%%" + commitLayerName; // give converter a way to access commit layer info + + // Convert preview objects + foreach (var previewObj in Preview) + { + previewObj.CreatedIds = new List() { previewObj.OriginalId }; // temporary store speckle id as created id for Preview report selection to work - var transport = new ServerTransport(state.Client.Account, state.StreamId); + var storedObj = StoredObjects[previewObj.OriginalId]; + if (storedObj.speckle_type.Contains("Block") || storedObj.speckle_type.Contains("View")) + { + var status = previewObj.Convertible ? ApplicationObject.State.Created : ApplicationObject.State.Skipped; + previewObj.Update(status: status, logItem: "No preview available"); + progress.Report.Log(previewObj); + continue; + } + previewObj.Converted = previewObj.Convertible ? ConvertObject(previewObj) : previewObj.Fallback.SelectMany(o => ConvertObject(o)).ToList(); - if (progress.CancellationTokenSource.Token.IsCancellationRequested) - return null; + if (previewObj.Converted == null || previewObj.Converted.Count == 0) + { + previewObj.Update(status: ApplicationObject.State.Failed, logItem: $"Couldn't convert object or any fallback values"); + } + else + { + previewObj.Status = ApplicationObject.State.Created; + if (!previewObj.Convertible) + previewObj.Update(logItem: $"Converted unsupported object to {previewObj.Converted.Count} fallback values"); + } + progress.Report.Log(previewObj); + } + progress.Report.Merge(Converter.Report); - //if "latest", always make sure we get the latest commit when the user clicks "receive" - Commit commit = null; - if (state.CommitId == "latest") - { - var res = await state.Client.BranchGet(progress.CancellationTokenSource.Token, state.StreamId, state.BranchName, 1); - commit = res.commits.items.FirstOrDefault(); + // undo notes edit + var segments = Doc.Notes.Split(new string[] { "%%%" }, StringSplitOptions.None).ToList(); + Doc.Notes = segments[0]; } - else - { - var res = await state.Client.CommitGet(progress.CancellationTokenSource.Token, state.StreamId, state.CommitId); - commit = res; - } - string referencedObject = commit.referencedObject; + + // create display conduit + PreviewConduit = new PreviewConduit(Preview); + PreviewConduit.Enabled = true; + Doc.Views.ActiveView.ActiveViewport.ZoomBoundingBox(PreviewConduit.bbox); + Doc.Views.Redraw(); if (progress.CancellationTokenSource.Token.IsCancellationRequested) + { + PreviewConduit.Enabled = false; return null; + } - var contex = SynchronizationContext.Current; - - //var commit = state.Commit; - var commitObject = await Operations.Receive( - referencedObject, - progress.CancellationTokenSource.Token, - transport, - onProgressAction: dict => progress.Update(dict), - onErrorAction: (s, e) => - { - progress.Report.LogOperationError(e); - progress.CancellationTokenSource.Cancel(); - }, - onTotalChildrenCountKnown: (c) => progress.Max = c, - disposeTransports: true - ); - - if (progress.Report.OperationErrorsCount != 0) - return state; + return state; + } - if (progress.CancellationTokenSource.Token.IsCancellationRequested) - return null; + public override async Task ReceiveStream(StreamState state, ProgressViewModel progress) + { + var kit = KitManager.GetDefaultKit(); + if (Converter == null) + throw new Exception("Could not find any Kit!"); + Converter.SetContextDocument(Doc); - var undoRecord = Doc.BeginUndoRecord($"Speckle bake operation for {state.CachedStream.name}"); + Commit commit = await GetCommitFromState(state, progress); + if (commit == null) return null; + if (SelectedReceiveCommit != commit.id) + { + Preview.Clear(); + StoredObjects.Clear(); + SelectedReceiveCommit = commit.id; + } var conversionProgressDict = new ConcurrentDictionary(); conversionProgressDict["Conversion"] = 0; + var undoRecord = Doc.BeginUndoRecord($"Speckle bake operation for {state.CachedStream.name}"); - // get commit layer name + // get commit layer name var commitLayerName = DesktopUI2.Formatting.CommitInfo(state.CachedStream.name, state.BranchName, commit.id); + Base commitObject = null; + if (Preview.Count == 0) + commitObject = await GetCommit(commit, state, progress); + if (progress.Report.OperationErrorsCount != 0) + return null; + RhinoApp.InvokeOnUiThread((Action)delegate - { - // give converter a way to access the base commit layer name - RhinoDoc.ActiveDoc.Notes += "%%%" + commitLayerName; + { + RhinoDoc.ActiveDoc.Notes += "%%%" + commitLayerName; // give converter a way to access commit layer info - // flatten the commit object to retrieve children objs - int count = 0; - var commitObjs = FlattenCommitObject(commitObject, converter, commitLayerName, state, ref count); + // create preview objects if they don't already exist + if (Preview.Count == 0) + { + // flatten the commit object to retrieve children objs + int count = 0; + Preview = FlattenCommitObject(commitObject, commitLayerName, ref count); - if (progress.CancellationTokenSource.Token.IsCancellationRequested) - return; + // convert + foreach (var previewObj in Preview) + { + previewObj.Converted = previewObj.Convertible ? ConvertObject(previewObj) : previewObj.Fallback.SelectMany(o => ConvertObject(o)).ToList(); + + if (previewObj.Converted == null || previewObj.Converted.Count == 0) + previewObj.Update(status: ApplicationObject.State.Failed, logItem: $"Couldn't convert object or any fallback values"); + else + if (!previewObj.Convertible) + previewObj.Update(logItem: $"Converted unsupported object to {previewObj.Converted.Count} fallback values"); + + progress.Report.Log(previewObj); + if (progress.CancellationTokenSource.Token.IsCancellationRequested) + return; + } + progress.Report.Merge(Converter.Report); + } - foreach (var commitObj in commitObjs) - { - var (obj, layerPath) = commitObj; - BakeObject(obj, layerPath, state, converter); - if (progress.CancellationTokenSource.Token.IsCancellationRequested) - return; - conversionProgressDict["Conversion"]++; - progress.Update(conversionProgressDict); - } + if (progress.Report.OperationErrorsCount != 0) + return; - progress.Report.Merge(converter.Report); - Doc.Views.Redraw(); - Doc.EndUndoRecord(undoRecord); + foreach (var previewObj in Preview) + { + // bake + previewObj.CreatedIds.Clear(); // clear created ids before bake because these may be speckle ids from the preview + BakeObject(previewObj); + progress.Report.Log(previewObj); + + if (progress.CancellationTokenSource.Token.IsCancellationRequested) + return; + conversionProgressDict["Conversion"]++; + progress.Update(conversionProgressDict); + } - // undo notes edit - var segments = Doc.Notes.Split(new string[] { "%%%" }, StringSplitOptions.None).ToList(); - Doc.Notes = segments[0]; - }); + // undo notes edit + var segments = Doc.Notes.Split(new string[] { "%%%" }, StringSplitOptions.None).ToList(); + Doc.Notes = segments[0]; + }); + Doc.Views.Redraw(); + Doc.EndUndoRecord(undoRecord); + return state; + } + + // gets the state commit + private async Task GetCommitFromState(StreamState state, ProgressViewModel progress) + { + Commit commit = null; + if (state.CommitId == "latest") //if "latest", always make sure we get the latest commit + { + var res = await state.Client.BranchGet(progress.CancellationTokenSource.Token, state.StreamId, state.BranchName, 1); + commit = res.commits.items.FirstOrDefault(); + } + else + { + var res = await state.Client.CommitGet(progress.CancellationTokenSource.Token, state.StreamId, state.CommitId); + commit = res; + } if (progress.CancellationTokenSource.Token.IsCancellationRequested) return null; + return commit; + } + private async Task GetCommit(Commit commit, StreamState state, ProgressViewModel progress) + { + var transport = new ServerTransport(state.Client.Account, state.StreamId); - return state; + var commitObject = await Operations.Receive( + commit.referencedObject, + progress.CancellationTokenSource.Token, + transport, + onProgressAction: dict => progress.Update(dict), + onErrorAction: (s, e) => + { + progress.Report.LogOperationError(e); + progress.CancellationTokenSource.Cancel(); + }, + onTotalChildrenCountKnown: (c) => progress.Max = c, + disposeTransports: true + ); + + if (progress.Report.OperationErrorsCount != 0) + return null; + + return commitObject; } - // Recurses through the commit object and flattens it. Returns list of Base objects with their bake layers - private List> FlattenCommitObject(object obj, ISpeckleConverter converter, string layer, StreamState state, ref int count, bool foundConvertibleMember = false) + // Recurses through the commit object and flattens it. Returns list of Preview objects + private List FlattenCommitObject(object obj, string layer, ref int count, bool foundConvertibleMember = false) { - var objects = new List>(); + var objects = new List(); if (obj is Base @base) { - if (converter.CanConvertToNative(@base)) + var speckleType = @base.speckle_type.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); + var appObj = new ApplicationObject(@base.id, speckleType) { applicationId = @base.applicationId, Container = layer }; + if (Converter.CanConvertToNative(@base)) { - objects.Add(new Tuple(@base, layer)); + appObj.Convertible = true; + objects.Add(appObj); + StoredObjects.Add(@base.id, @base); return objects; } else { - List props = @base.GetDynamicMembers().ToList(); + appObj.Convertible = false; + + // handle fallback display separately + // NOTE: deprecated displayMesh!! + bool hasFallback = false; if (@base.GetMembers().ContainsKey("displayValue")) - props.Add("displayValue"); - else if (@base.GetMembers().ContainsKey("displayMesh")) // add display mesh to member list if it exists. this will be deprecated soon - props.Add("displayMesh"); + { + var fallbackObjects = FlattenCommitObject(@base["displayValue"], layer, ref count, foundConvertibleMember); + if (fallbackObjects.Count > 0) + { + appObj.Fallback.AddRange(fallbackObjects); + foundConvertibleMember = true; + hasFallback = true; + } + } + if (hasFallback) + { + objects.Add(appObj); + StoredObjects.Add(@base.id, @base); + } + + // handle any children elements, these are added as separate previewObjects + List props = @base.GetDynamicMembers().ToList(); if (@base.GetMembers().ContainsKey("elements")) // this is for builtelements like roofs, walls, and floors. props.Add("elements"); int totalMembers = props.Count; - foreach (var prop in props) { count++; // get bake layer name string objLayerName = prop.StartsWith("@") ? prop.Remove(0, 1) : prop; - string rhLayerName = (objLayerName.StartsWith($"{layer}{Layer.PathSeparator}")) ? objLayerName : $"{layer}{Layer.PathSeparator}{objLayerName}"; + string rhLayerName = objLayerName.StartsWith($"{layer}{Layer.PathSeparator}") ? objLayerName : $"{layer}{Layer.PathSeparator}{objLayerName}"; - var nestedObjects = FlattenCommitObject(@base[prop], converter, rhLayerName, state, ref count, foundConvertibleMember); - if (nestedObjects.Count > 0) + var nestedObjects = FlattenCommitObject(@base[prop], rhLayerName, ref count, foundConvertibleMember); + var validNestedObjects = nestedObjects.Where(o => o.Convertible == true || o.Fallback.Count > 0)?.ToList(); + if (validNestedObjects != null && validNestedObjects.Count > 0) { objects.AddRange(nestedObjects); foundConvertibleMember = true; @@ -314,7 +487,10 @@ private List> FlattenCommitObject(object obj, ISpeckleConver } if (!foundConvertibleMember && count == totalMembers) // this was an unsupported geo - converter.Report.Log($"Skipped not supported type: { @base.speckle_type }. Object {@base.id} not baked."); + { + appObj.Update(status: ApplicationObject.State.Skipped, logItem: $"Receiving objects of type {@base.speckle_type} not supported in Rhino"); + objects.Add(appObj); + } return objects; } @@ -324,7 +500,7 @@ private List> FlattenCommitObject(object obj, ISpeckleConver { count = 0; foreach (var listObj in list) - objects.AddRange(FlattenCommitObject(listObj, converter, layer, state, ref count)); + objects.AddRange(FlattenCommitObject(listObj, layer, ref count)); return objects; } @@ -332,7 +508,7 @@ private List> FlattenCommitObject(object obj, ISpeckleConver { count = 0; foreach (DictionaryEntry kvp in dict) - objects.AddRange(FlattenCommitObject(kvp.Value, converter, layer, state, ref count)); + objects.AddRange(FlattenCommitObject(kvp.Value, layer, ref count)); return objects; } @@ -340,18 +516,15 @@ private List> FlattenCommitObject(object obj, ISpeckleConver } // conversion and bake - private void BakeObject(Base obj, string layerPath, StreamState state, ISpeckleConverter converter) + private List ConvertObject(ApplicationObject previewObj) { - var converted = converter.ConvertToNative(obj); // This may be a GeometryBase, an Array (eg. hatches), or a nested array (e.g. direct shape) - if (converted == null) - { - var exception = new Exception($"Failed to convert object {obj.id} of type {obj.speckle_type}."); - converter.Report.LogConversionError(exception); - return; - } - + var obj = StoredObjects[previewObj.OriginalId]; var convertedList = new List(); + var converted = Converter.ConvertToNative(obj); + if (converted == null) + return convertedList; + //Iteratively flatten any lists void FlattenConvertedObject(object item) { @@ -361,65 +534,81 @@ void FlattenConvertedObject(object item) else convertedList.Add(item); } - FlattenConvertedObject(converted); - foreach (var convertedItem in convertedList) + return convertedList; + } + private void BakeObject(ApplicationObject previewObj) + { + var obj = StoredObjects[previewObj.OriginalId]; + int bakedCount = 0; + foreach (var convertedItem in previewObj.Converted) { - if (!(convertedItem is GeometryBase convertedRH)) continue; - - if (!convertedRH.IsValidWithLog(out string log)) - { - var exception = - new Exception($"Failed to bake object {obj.id} of type {obj.speckle_type}: {log.Replace("\n", "").Replace("\r", "")}"); - converter.Report.LogConversionError(exception); - continue; - } - - Layer bakeLayer = Doc.GetLayer(layerPath, true); - if (bakeLayer == null) + switch (convertedItem) { - var exception = new Exception($"Could not create layer {layerPath} to bake objects into."); - converter.Report.LogConversionError(exception); - continue; - } - - var attributes = new ObjectAttributes(); + case GeometryBase o: + string layerPath = previewObj.Container; + if (!o.IsValidWithLog(out string log)) + { + previewObj.Update(logItem: $"{log.Replace("\n", "").Replace("\r", "")}"); + continue; + } + Layer bakeLayer = Doc.GetLayer(layerPath, true); + if (bakeLayer == null) + { + previewObj.Update(logItem: $"Could not create layer {layerPath}."); + continue; + } + var attributes = new ObjectAttributes(); - // handle display style - if (obj[@"displayStyle"] is Base display) - if (converter.ConvertToNative(display) is ObjectAttributes displayAttribute) - attributes = displayAttribute; - else if (obj[@"renderMaterial"] is Base renderMaterial) - attributes.ColorSource = ObjectColorSource.ColorFromMaterial; + // handle display style + if (obj[@"displayStyle"] is Base display) + if (Converter.ConvertToNative(display) is ObjectAttributes displayAttribute) + attributes = displayAttribute; + else if (obj[@"renderMaterial"] is Base renderMaterial) + attributes.ColorSource = ObjectColorSource.ColorFromMaterial; - // assign layer - attributes.LayerIndex = bakeLayer.Index; + // assign layer + attributes.LayerIndex = bakeLayer.Index; - // handle user info - SetUserInfo(obj, attributes); + // handle user info + SetUserInfo(obj, attributes); - Guid id = Doc.Objects.Add(convertedRH, attributes); + Guid id = Doc.Objects.Add(o, attributes); + if (id == Guid.Empty) + { + previewObj.Update(logItem: $"Could not add to document."); + continue; + } + previewObj.Update(createdId: id.ToString()); - if (id == Guid.Empty) - { - var exception = new Exception($"Failed to bake object {obj.id} of type {obj.speckle_type}."); - converter.Report.LogConversionError(exception); - continue; - } + bakedCount++; - // handle render material - if (obj[@"renderMaterial"] is Base render) - { - var convertedMaterial = converter.ConvertToNative(render); //Maybe wrap in try catch in case no conversion exists? - if (convertedMaterial is RenderMaterial rm) - { - var rhinoObject = Doc.Objects.FindId(id); - rhinoObject.RenderMaterial = rm; - rhinoObject.CommitChanges(); - } + // handle render material + if (obj[@"renderMaterial"] is Base render) + { + var convertedMaterial = Converter.ConvertToNative(render); //Maybe wrap in try catch in case no conversion exists? + if (convertedMaterial is RenderMaterial rm) + { + var rhinoObject = Doc.Objects.FindId(id); + rhinoObject.RenderMaterial = rm; + rhinoObject.CommitChanges(); + } + } + break; + case RhinoObject o: + previewObj.Update(status: ApplicationObject.State.Created, createdId: o.Id.ToString()); + bakedCount++; + break; + default: + break; } } + + if (bakedCount == 0) + previewObj.Update(status: ApplicationObject.State.Failed, logItem: $"Could not bake object"); + else + previewObj.Update(status: ApplicationObject.State.Created); } private void SetUserInfo(Base obj, ObjectAttributes attributes) @@ -438,18 +627,21 @@ private void SetUserInfo(Base obj, ObjectAttributes attributes) #endregion #region sending - + public override void PreviewSend(StreamState state, ProgressViewModel progress) + { + // TODO: instead of selection, consider saving current visibility of objects in doc, hiding everything except selected, and restoring original states on cancel + Doc.Objects.UnselectAll(false); + SelectClientObjects(GetObjectsFromFilter(state.Filter)); + Doc.Views.Redraw(); + } public override async Task SendStream(StreamState state, ProgressViewModel progress) { - var kit = KitManager.GetDefaultKit(); - var converter = kit.LoadConverter(Utils.RhinoAppName); - converter.SetContextDocument(Doc); + Converter.SetContextDocument(Doc); var streamId = state.StreamId; var client = state.Client; int objCount = 0; - bool renamedlayers = false; state.SelectedObjectIds = GetObjectsFromFilter(state.Filter); var commitObject = new Base(); @@ -484,19 +676,23 @@ public override async Task SendStream(StreamState state, ProgressViewMod { viewIndex = Doc.NamedViews.FindByName(applicationId); // try get view } + ApplicationObject reportObj = new ApplicationObject(applicationId, obj.ObjectType.ToString()); if (obj != null) { - if (!converter.CanConvertToSpeckle(obj)) + if (!Converter.CanConvertToSpeckle(obj)) { - progress.Report.Log($"Skipped not supported type: ${obj.Geometry.ObjectType}"); + reportObj.Update(status: ApplicationObject.State.Skipped, logItem: $"Sending objects of type {obj.ObjectType} not supported in Rhino"); + progress.Report.Log(reportObj); continue; } - converted = converter.ConvertToSpeckle(obj); + + Converter.Report.Log(reportObj); // Log object so converter can access + converted = Converter.ConvertToSpeckle(obj); if (converted == null) { - var exception = new Exception($"Failed to convert object ${applicationId} of type ${obj.Geometry.ObjectType}."); - progress.Report.LogConversionError(exception); + reportObj.Update(status: ApplicationObject.State.Failed, logItem: $"Conversion returned null"); + progress.Report.Log(reportObj); continue; } @@ -507,17 +703,17 @@ public override async Task SendStream(StreamState state, ProgressViewMod var layerPath = Doc.Layers[obj.Attributes.LayerIndex].FullPath; string cleanLayerPath = RemoveInvalidDynamicPropChars(layerPath); containerName = cleanLayerPath; - if (!cleanLayerPath.Equals(layerPath)) - renamedlayers = true; } } else if (viewIndex != -1) { ViewInfo view = Doc.NamedViews[viewIndex]; - converted = converter.ConvertToSpeckle(view); + Converter.Report.Log(reportObj); // Log object so converter can access + converted = Converter.ConvertToSpeckle(view); if (converted == null) { - converter.Report.LogConversionError(new Exception($"Failed to convert object ${applicationId} of type ${view.GetType()}.")); + reportObj.Update(status: ApplicationObject.State.Failed, logItem: $"Creation of {view.GetType()} returned Null"); + progress.Report.Log(reportObj); continue; } containerName = "Named Views"; @@ -544,10 +740,14 @@ public override async Task SendStream(StreamState state, ProgressViewMod converted["@SpeckleSchema"] = newSchemaBase; } + // log report object + reportObj.Update(status: ApplicationObject.State.Created, logItem: $"Sent as {converted.speckle_type}"); + progress.Report.Log(reportObj); + objCount++; } - progress.Report.Merge(converter.Report); + progress.Report.Merge(Converter.Report); if (objCount == 0) { @@ -555,9 +755,6 @@ public override async Task SendStream(StreamState state, ProgressViewMod return null; } - if (renamedlayers) - progress.Report.Log("Replaced illegal chars ./ with - in one or more layer names."); - if (progress.CancellationTokenSource.Token.IsCancellationRequested) return null; @@ -592,7 +789,7 @@ public override async Task SendStream(StreamState state, ProgressViewMod streamId = streamId, objectId = objectId, branchName = state.BranchName, - message = state.CommitMessage != null ? state.CommitMessage : $"Pushed {objCount} elements from Rhino.", + message = state.CommitMessage != null ? state.CommitMessage : $"Sent {objCount} elements from Rhino.", sourceApplication = Utils.RhinoAppName }; diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/Utils.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/Utils.cs index f65e94ccc5..0650e90878 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/Utils.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/Utils.cs @@ -1,15 +1,19 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Speckle.Core.Kits; +using System.Text.RegularExpressions; +using System.Diagnostics; using Rhino; using Rhino.DocObjects; -using System.Text.RegularExpressions; -using System.Diagnostics; +using Rhino.Geometry; +using Rhino.Display; + +using Speckle.Core.Kits; +using Speckle.Core.Models; + +using DesktopUI2.ViewModels; +using System.Drawing; +using System.Linq; namespace SpeckleRhino { @@ -75,7 +79,115 @@ private static Layer MakeLayer(RhinoDoc doc, string name, Layer parentLayer = nu #endregion } - + #region Preview + public class PreviewConduit : DisplayConduit + { + private Dictionary> Preview { get; set; } = new Dictionary>(); + private List Selected = new List(); + public BoundingBox bbox; + private Color color = Color.FromArgb(200, 59, 130, 246); + private Color selectedColor = Color.FromArgb(200, 255, 255, 0); + private DisplayMaterial material; + + public PreviewConduit(List preview) + { + material = new DisplayMaterial(); + material.Transparency = 0.8; + material.Diffuse = color; + bbox = new BoundingBox(); + + foreach (var previewObj in preview) + { + var converted = new List(); + foreach (var obj in previewObj.Converted) + { + switch (obj) + { + case GeometryBase o: + bbox.Union(o.GetBoundingBox(false)); + break; + case Text3d o: + bbox.Union(o.BoundingBox); + break; + case InstanceObject o: + // todo: this needs to be handled, including how block defs are created during preview + //obj.Rollback = true; + break; + default: + break; + } + converted.Add(obj); + } + Preview.Add(previewObj.OriginalId, converted); + } + } + + public void SelectPreviewObject(string id, bool unselect = false) + { + if (Preview.ContainsKey(id)) + { + if (unselect) + Selected.Remove(id); + else + if (!Selected.Contains(id)) Selected.Add(id); + } + } + + // reference: https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_Display_DisplayConduit_CalculateBoundingBox.htm + protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e) + { + base.CalculateBoundingBox(e); + e.IncludeBoundingBox(bbox); + } + + protected override void CalculateBoundingBoxZoomExtents(CalculateBoundingBoxEventArgs e) + { + this.CalculateBoundingBox(e); + } + + protected override void PreDrawObjects(Rhino.Display.DrawEventArgs e) + { + // draw preview objects + var display = e.Display; + + foreach (var previewobj in Preview) + { + var drawColor = Selected.Contains(previewobj.Key) ? selectedColor : color; + var drawMaterial = material; + drawMaterial.Diffuse = drawColor; + foreach (var obj in previewobj.Value) + { + switch (obj) + { + case Brep o: + display.DrawBrepShaded(o, drawMaterial); + break; + case Mesh o: + display.DrawMeshShaded(o, drawMaterial); + break; + case Curve o: + display.DrawCurve(o, drawColor); + break; + case Rhino.Geometry.Point o: + display.DrawPoint(o.Location, drawColor); + break; + case Point3d o: + display.DrawPoint(o,drawColor); + break; + case PointCloud o: + display.DrawPointCloud(o, 5, drawColor); + break; + default: + break; + } + } + } + } + + } + + #endregion + public static class Formatting { public static string TimeAgo(string timestamp) diff --git a/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructure.Send.cs b/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructure.Send.cs index 7a0b3482fa..c024a6f863 100644 --- a/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructure.Send.cs +++ b/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructure.Send.cs @@ -25,9 +25,13 @@ public partial class ConnectorBindingsTeklaStructures : ConnectorBindings private List CurrentSettings { get; set; } + public override void PreviewSend(StreamState state, ProgressViewModel progress) + { + // TODO! + } + public override async System.Threading.Tasks.Task SendStream(StreamState state, ProgressViewModel progress) { - //throw new NotImplementedException(); var kit = KitManager.GetDefaultKit(); //var converter = new ConverterTeklaStructures(); var converter = kit.LoadConverter(ConnectorTeklaStructuresUtils.TeklaStructuresAppName); diff --git a/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructures.ClientOperations.cs b/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructures.ClientOperations.cs index 2ba2d8a20f..e343affc43 100644 --- a/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructures.ClientOperations.cs +++ b/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructures.ClientOperations.cs @@ -27,7 +27,6 @@ public override void WriteStreamsToFile(List streams) DocumentStreams.Add(s); WriteStateToFile(); } - //throw new NotImplementedException(); } //public override void AddNewStream(StreamState state) diff --git a/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructures.Selection.cs b/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructures.Selection.cs index b1ced86c28..162c0e3d3f 100644 --- a/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructures.Selection.cs +++ b/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructures.Selection.cs @@ -1,8 +1,11 @@ using DesktopUI2; +using DesktopUI2.Models; using DesktopUI2.Models.Filters; +using DesktopUI2.ViewModels; using Speckle.ConnectorTeklaStructures.Util; using System; using System.Collections.Generic; +using System.Threading.Tasks; using Tekla.Structures.Model; namespace Speckle.ConnectorTeklaStructures.UI @@ -15,9 +18,8 @@ public override List GetSelectedObjects() var names = new List(); ModelObjectEnumerator myEnum = new Tekla.Structures.Model.UI.ModelObjectSelector().GetSelectedObjects(); while (myEnum.MoveNext()) - { names.Add(myEnum.Current.Identifier.GUID.ToString()); - } + return names; } @@ -31,9 +33,7 @@ public override List GetSelectionFilters() { var phaseCollection = Model.GetPhases(); foreach (Phase p in phaseCollection) - { phases.Add(p.PhaseName); - } //selectionCount = Model.Selection.GetElementIds().Count(); categories = ConnectorTeklaStructuresUtils.GetCategoryNames(Model); @@ -56,9 +56,20 @@ public override List GetSelectionFilters() }; } - public override void SelectClientObjects(string args) + public override Task PreviewReceive(StreamState state, ProgressViewModel progress) + { + return null; + // TODO! + } + + public override void ResetDocument() { - throw new NotImplementedException(); + // TODO! + } + + public override void SelectClientObjects(List args, bool deselect = false) + { + // TODO! } private List GetSelectionFilterObjects(ISelectionFilter filter) @@ -72,17 +83,13 @@ private List GetSelectionFilterObjects(ISelectionFilter filter) case "manual": ModelObjectEnumerator myEnum = new Tekla.Structures.Model.UI.ModelObjectSelector().GetSelectedObjects(); while (myEnum.MoveNext()) - { selection.Add(myEnum.Current); - } return selection; // return GetSelectedObjects(); case "all": myEnum = Model.GetModelObjectSelector().GetAllObjects(); while (myEnum.MoveNext()) - { selection.Add(myEnum.Current); - } return selection; @@ -96,9 +103,7 @@ private List GetSelectionFilterObjects(ISelectionFilter filter) Phase phaseTemp = new Phase(); myEnum.Current.GetPhase(out phaseTemp); if (phaseTemp.PhaseName == phase) - { selection.Add(myEnum.Current); - } } } @@ -113,14 +118,10 @@ private List GetSelectionFilterObjects(ISelectionFilter filter) { myEnum = Model.GetModelObjectSelector().GetAllObjectsWithType(categories[cat]); while (myEnum.MoveNext()) - { selection.Add(myEnum.Current); - } } } return selection; - - } return selection; diff --git a/Core/Core/Kits/ISpeckleConverter.cs b/Core/Core/Kits/ISpeckleConverter.cs index 28f6f63a55..6274001cab 100644 --- a/Core/Core/Kits/ISpeckleConverter.cs +++ b/Core/Core/Kits/ISpeckleConverter.cs @@ -3,7 +3,6 @@ namespace Speckle.Core.Kits { - public interface ISpeckleConverter { string Description { get; } @@ -80,13 +79,13 @@ public interface ISpeckleConverter /// Some converters need to know which other objects are being converted, in order to sort relationships between them (ie, Revit). Use this method to set them. /// /// - public void SetContextObjects(List objects); + public void SetContextObjects(List objects); /// /// Some converters need to know which objects have been converted before in order to update them (ie, Revit). Use this method to set them. /// /// - public void SetPreviousContextObjects(List objects); + public void SetPreviousContextObjects(List objects); /// /// Some converters need to be able to receive some settings to modify their internal behaviour (i.e. Rhino's Brep Meshing options). Use this method to set them. @@ -96,7 +95,6 @@ public interface ISpeckleConverter } - public enum ReceiveMode { Update, diff --git a/Core/Core/Models/Extras.cs b/Core/Core/Models/Extras.cs index f72a8db510..c0cd9cacf1 100644 --- a/Core/Core/Models/Extras.cs +++ b/Core/Core/Models/Extras.cs @@ -81,26 +81,132 @@ public ProgressEventArgs(int current, int total, string scope) /// /// A simple wrapper to keep track of the relationship between speckle objects and their host-application siblings in cases where the - /// cannot correspond with the (ie, on receiving operations). + /// cannot correspond with the (ie, on receiving operations). /// - public class ApplicationPlaceholderObject : Base + public class ApplicationObject : Base { - public ApplicationPlaceholderObject() { } + public enum State + { + Created, // Speckle object created on send, or native objects created on receive + Skipped, // Speckle or Application object is not going to be sent or received + Updated, // Application object is replacing an existing object in the application + Failed, // Tried to convert & send or convert & bake but something went wrong + Removed, //Removed object from application + Unknown + } + + /// + /// The container for the object in the native application + /// + public string Container { get; set; } - public string ApplicationGeneratedId { get; set; } + /// + /// Indicates if conversion is supported by the converter + /// + public bool Convertible { get; set; } + /// + /// The fallback values if direct conversion is not available, typically displayValue + /// [JsonIgnore] - public object NativeObject; + public List Fallback { get; set; } = new List(); + + /// + /// The Speckle id (on receive) or native id (on send) + /// + /// + /// Used to retrieve this object in ProgressReport.GetReportObject(), typically to pass between connectors and converters + /// + public string OriginalId { get; set; } + + /// + /// A descriptive string to describe the object. Use the object type as default. + /// + public string Descriptor { get; set; } + + /// + /// The created object ids associated with this object + /// + /// + /// On send, this is currently left empty as generating Speckle ids would be performance expensive + /// + public List CreatedIds { get; set; } = new List(); + + /// + /// Conversion status of object + /// + public State Status { get; set; } + + /// + /// Conversion notes or other important information to expose to the user + /// + public List Log { get; set; } = new List(); + + /// + /// Converted objects corresponding to this object + /// + /// + /// Used during receive for convenience, corresponds to CreatedIds + /// + [JsonIgnore] + public List Converted { get; set; } = new List(); + + public ApplicationObject(string id, string type) + { + OriginalId = id; + Descriptor = type; + Status = State.Unknown; + } + + public void Update(string createdId = null, List createdIds = null, State? status = null, List log = null, string logItem = null, List converted = null, object convertedItem = null) + { + if (createdIds != null) createdIds.Where(o => !string.IsNullOrEmpty(o) && !CreatedIds.Contains(o))?.ToList().ForEach(o => CreatedIds.Add(o)); + if (createdId != null && !CreatedIds.Contains(createdId)) CreatedIds.Add(createdId); + if (status.HasValue) Status = status.Value; + if (log != null) log.Where(o => !string.IsNullOrEmpty(o) && !Log.Contains(o))?.ToList().ForEach(o => Log.Add(o)); + if (!string.IsNullOrEmpty(logItem) && !Log.Contains(logItem)) Log.Add(logItem); + if (convertedItem != null) Converted.Add(convertedItem); + if (converted != null) converted.Where(o => o != null)?.ToList().ForEach(o => Converted.Add(o)); + } } public class ProgressReport { + public List ReportObjects { get; set; } = new List(); + + public void Log(ApplicationObject obj) + { + var _reportObject = UpdateReportObject(obj); + if (_reportObject == null) + ReportObjects.Add(obj); + } + + public ApplicationObject UpdateReportObject(ApplicationObject obj) + { + if (GetReportObject(obj.OriginalId, out int index)) + { + ReportObjects[index].Update(createdIds: obj.CreatedIds, log: obj.Log); + if (obj.Status != ApplicationObject.State.Unknown) + ReportObjects[index].Update(status: obj.Status); + return ReportObjects[index]; + } + else return null; + } + + public bool GetReportObject(string id, out int index) + { + var _reportObject = ReportObjects.Where(o => o.OriginalId == id)?.FirstOrDefault(); + index = _reportObject != null ? ReportObjects.IndexOf(_reportObject) : -1; + return index == -1 ? false : true; + } + + #region Conversion /// /// Keeps track of the conversion process /// public List ConversionLog { get; } = new List(); - private readonly object ConversionLogLock = new object(); + private readonly object ConversionLogLock = new object(); public string ConversionLogString { get @@ -132,17 +238,17 @@ public void Log(string text) lock (ConversionLogLock) ConversionLog.Add(time + " " + text); } - + /// /// Keeps track of errors in the conversions. /// public List ConversionErrors { get; } = new List(); - private readonly object ConversionErrorsLock = new object(); + private readonly object ConversionErrorsLock = new object(); public string ConversionErrorsString { get { - lock(ConversionErrorsLock) + lock (ConversionErrorsLock) return string.Join("\n", ConversionErrors.Select(x => x.Message).Distinct()); } } @@ -151,17 +257,18 @@ public string ConversionErrorsString public void LogConversionError(Exception exception) { - lock(ConversionErrorsLock) + lock (ConversionErrorsLock) ConversionErrors.Add(exception); Log(exception.Message); } + #endregion - + #region Operation /// /// Keeps track of errors in the operations of send/receive. /// public List OperationErrors { get; } = new List(); - private readonly object OperationErrorsLock = new object(); + private readonly object OperationErrorsLock = new object(); public string OperationErrorsString { get @@ -171,24 +278,47 @@ public string OperationErrorsString } } - public int OperationErrorsCount => OperationErrors.Count; public void LogOperationError(Exception exception) { - lock(OperationErrorsLock) + lock (OperationErrorsLock) OperationErrors.Add(exception); - Log(exception.Message); } + #endregion public void Merge(ProgressReport report) { - lock(ConversionErrorsLock) - ConversionErrors.AddRange(report.ConversionErrors); lock(OperationErrorsLock) OperationErrors.AddRange(report.OperationErrors); + lock (ConversionLogLock) ConversionLog.AddRange(report.ConversionLog); + + // update report object notes + foreach (var item in ReportObjects) + { + var ids = new List { item.OriginalId }; + if (item.Fallback.Count > 0) ids.AddRange(item.Fallback.Select(o => o.OriginalId)); + + if (item.Status == ApplicationObject.State.Unknown) + if (report.GetReportObject(item.OriginalId, out int originalIndex)) + item.Status = report.ReportObjects[originalIndex].Status; + + foreach (var id in ids) + if (report.GetReportObject(id, out int index)) + { + foreach (var logItem in report.ReportObjects[index].Log) + if (!item.Log.Contains(logItem)) + item.Log.Add(logItem); + foreach (var createdId in report.ReportObjects[index].CreatedIds) + if (!item.CreatedIds.Contains(createdId)) + item.CreatedIds.Add(createdId); + foreach (var convertedItem in report.ReportObjects[index].Converted) + if (!item.Converted.Contains(convertedItem)) + item.Converted.Add(convertedItem); + } + } } } } diff --git a/DesktopUI/DesktopUI/Utils/DummyBindings.cs b/DesktopUI/DesktopUI/Utils/DummyBindings.cs index aeb5be50e1..59a48403c1 100644 --- a/DesktopUI/DesktopUI/Utils/DummyBindings.cs +++ b/DesktopUI/DesktopUI/Utils/DummyBindings.cs @@ -318,7 +318,7 @@ public override void RemoveStreamFromFile(string args) public override void SelectClientObjects(string args) { - throw new NotImplementedException(); + // TODO! } public override void PersistAndUpdateStreamInFile(StreamState state) diff --git a/DesktopUI/DesktopUI/Utils/StreamState.cs b/DesktopUI/DesktopUI/Utils/StreamState.cs index 9e6dd2e2e3..634fe6b0e3 100644 --- a/DesktopUI/DesktopUI/Utils/StreamState.cs +++ b/DesktopUI/DesktopUI/Utils/StreamState.cs @@ -351,7 +351,7 @@ public List SelectedObjectIds } [JsonProperty] - public List ReceivedObjects { get; set; } = new List(); + public List ReceivedObjects { get; set; } = new List(); private ProgressReport _progress = new ProgressReport(); public ProgressReport Progress diff --git a/DesktopUI2/DesktopUI2/ConnectorBindings.cs b/DesktopUI2/DesktopUI2/ConnectorBindings.cs index 893039e70e..8ee15d4751 100644 --- a/DesktopUI2/DesktopUI2/ConnectorBindings.cs +++ b/DesktopUI2/DesktopUI2/ConnectorBindings.cs @@ -77,6 +77,11 @@ public virtual bool CanTogglePreview() /// public abstract string GetDocumentLocation(); + /// + /// Clears the document state of selections and previews + /// + public abstract void ResetDocument(); + /// /// Gets the current opened/focused file's view, if applicable. /// @@ -89,7 +94,6 @@ public virtual bool CanTogglePreview() /// public abstract List GetStreamsInFile(); - /// /// Writes serialised clients to the current open host file. /// @@ -112,6 +116,14 @@ public virtual bool CanTogglePreview() /// public abstract Task SendStream(StreamState state, ProgressViewModel progress); + /// + /// Previews a send operation + /// + /// + /// + /// + public abstract void PreviewSend(StreamState state, ProgressViewModel progress); + /// /// Receives stream data from the server /// @@ -119,6 +131,14 @@ public virtual bool CanTogglePreview() /// public abstract Task ReceiveStream(StreamState state, ProgressViewModel progress); + /// + /// Previews a receive operation + /// + /// + /// + /// + public abstract Task PreviewReceive(StreamState state, ProgressViewModel progress); + /// /// Adds the current selection to the provided client. /// @@ -140,7 +160,7 @@ public virtual bool CanTogglePreview() /// clients should be able to select/preview/hover one way or another their associated objects /// /// - public abstract void SelectClientObjects(string args); + public abstract void SelectClientObjects(List objs, bool deselect = false); /// /// Should return a list of filters that the application supports. diff --git a/DesktopUI2/DesktopUI2/Controls/PreviewButton.xaml b/DesktopUI2/DesktopUI2/Controls/PreviewButton.xaml new file mode 100644 index 0000000000..ef1e455977 --- /dev/null +++ b/DesktopUI2/DesktopUI2/Controls/PreviewButton.xaml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + diff --git a/DesktopUI2/DesktopUI2/Controls/PreviewButton.xaml.cs b/DesktopUI2/DesktopUI2/Controls/PreviewButton.xaml.cs new file mode 100644 index 0000000000..eeed19432d --- /dev/null +++ b/DesktopUI2/DesktopUI2/Controls/PreviewButton.xaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace DesktopUI2.Controls +{ + public partial class PreviewButton : UserControl + { + public PreviewButton() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/DesktopUI2/DesktopUI2/DesktopUI2.csproj b/DesktopUI2/DesktopUI2/DesktopUI2.csproj index f9411c8336..28e2534179 100644 --- a/DesktopUI2/DesktopUI2/DesktopUI2.csproj +++ b/DesktopUI2/DesktopUI2/DesktopUI2.csproj @@ -30,6 +30,7 @@ + @@ -55,6 +56,7 @@ + @@ -72,7 +74,6 @@ - MSBuild:Compile @@ -114,6 +115,9 @@ MSBuild:Compile + + MSBuild:Compile + MSBuild:Compile @@ -157,6 +161,9 @@ MSBuild:Compile + + MSBuild:Compile + MSBuild:Compile @@ -205,9 +212,6 @@ MSBuild:Compile - - MSBuild:Compile - Always @@ -233,6 +237,9 @@ App.xaml + + PreviewButton.xaml + ReceiveButton.xaml @@ -302,9 +309,6 @@ QuickOpsDialog.xaml - - Report.xaml - diff --git a/DesktopUI2/DesktopUI2/DummyBindings.cs b/DesktopUI2/DesktopUI2/DummyBindings.cs index 180320d65b..1b98fda8f5 100644 --- a/DesktopUI2/DesktopUI2/DummyBindings.cs +++ b/DesktopUI2/DesktopUI2/DummyBindings.cs @@ -54,6 +54,11 @@ public override string GetDocumentLocation() return "C:/Wow/Some/Document/Here"; } + public override void ResetDocument() + { + return; + } + public override string GetFileName() { return "Some Random File"; @@ -253,12 +258,12 @@ public override List GetStreamsInFile() return collection; } - public override void SelectClientObjects(string args) + public override void SelectClientObjects(List objs, bool deselect = false) { - throw new NotImplementedException(); + // TODO! } - public override async Task ReceiveStream(StreamState state, ProgressViewModel progress) + public override async Task PreviewReceive(StreamState state, ProgressViewModel progress) { var pd = new ConcurrentDictionary(); pd["A1"] = 1; @@ -305,10 +310,102 @@ public override async Task ReceiveStream(StreamState state, Progres return state; } + public override async Task ReceiveStream(StreamState state, ProgressViewModel progress) + { + var pd = new ConcurrentDictionary(); + pd["A1"] = 1; + pd["A2"] = 1; + progress.Max = 100; + progress.Update(pd); + + for (int i = 1; i < 100; i += 10) + { + if (progress.CancellationTokenSource.IsCancellationRequested) + return state; + + await Task.Delay(TimeSpan.FromMilliseconds(rnd.Next(200, 1000))); + pd["A1"] = i; + pd["A2"] = i + 2; + + try + { + if (i % 7 == 0) + throw new Exception($"Something happened."); + } + catch (Exception e) + { + //TODO + progress.Report.LogConversionError(e); + } + + progress.Update(pd); + } + + // Mock some errors + for (int i = 0; i < 100; i++) + { + progress.Report.Log($"Hello this is a sample line {i}"); + try + { + throw new Exception($"Number {i} fail"); + } + catch (Exception e) + { + progress.Report.LogOperationError(e); + } + } + + return state; + } + + public override async void PreviewSend(StreamState state, ProgressViewModel progress) + { + // Let's fake some progress barsssss + //progress.Report.Log("Starting fake sending"); + var pd = new ConcurrentDictionary(); + pd["A1"] = 1; + pd["A2"] = 1; + + progress.Max = 100; + progress.Update(pd); + + for (int i = 1; i < 100; i += 10) + { + if (progress.CancellationTokenSource.Token.IsCancellationRequested) + { + //progress.Report.Log("Fake sending was cancelled"); + return; + } + + progress.Report.Log("Done fake task " + i); + await Task.Delay(TimeSpan.FromMilliseconds(rnd.Next(200, 1000))); + pd["A1"] = i; + pd["A2"] = i + 2; + + progress.Update(pd); + } + + // Mock "some" errors + for (int i = 0; i < 10; i++) + { + try + { + throw new Exception($"Number {i} failed"); + } + catch (Exception e) + { + progress.Report.LogOperationError(e); + //TODO + //state.Errors.Add(e); + } + } + return; + } + public override async Task SendStream(StreamState state, ProgressViewModel progress) { // Let's fake some progress barsssss - progress.Report.Log("Starting fake sending"); + //progress.Report.Log("Starting fake sending"); var pd = new ConcurrentDictionary(); pd["A1"] = 1; pd["A2"] = 1; @@ -320,7 +417,7 @@ public override async Task SendStream(StreamState state, ProgressViewMod { if (progress.CancellationTokenSource.Token.IsCancellationRequested) { - progress.Report.Log("Fake sending was cancelled"); + //progress.Report.Log("Fake sending was cancelled"); return null; } diff --git a/DesktopUI2/DesktopUI2/Models/StreamState.cs b/DesktopUI2/DesktopUI2/Models/StreamState.cs index fe39fe9417..d66aa67fce 100644 --- a/DesktopUI2/DesktopUI2/Models/StreamState.cs +++ b/DesktopUI2/DesktopUI2/Models/StreamState.cs @@ -140,7 +140,7 @@ public Stream CachedStream public List SelectedObjectIds { get; set; } = new List(); [JsonProperty] - public List ReceivedObjects { get; set; } = new List(); + public List ReceivedObjects { get; set; } = new List(); public StreamState(StreamAccountWrapper streamAccountWrapper) { diff --git a/DesktopUI2/DesktopUI2/Styles/FloatingButton.xaml b/DesktopUI2/DesktopUI2/Styles/FloatingButton.xaml index 70c92d6804..de1360709f 100644 --- a/DesktopUI2/DesktopUI2/Styles/FloatingButton.xaml +++ b/DesktopUI2/DesktopUI2/Styles/FloatingButton.xaml @@ -160,6 +160,10 @@ + diff --git a/DesktopUI2/DesktopUI2/ViewModels/ActivityViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/ActivityViewModel.cs index ffe1ec811f..5a6a00c25c 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/ActivityViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/ActivityViewModel.cs @@ -29,11 +29,9 @@ private async Task GetAuthorAsync() public ActivityViewModel(ActivityItem item, Client client) { - _activity = item; _client = client; - switch (item.actionType) { case "commit_receive": @@ -64,8 +62,5 @@ public ActivityViewModel(ActivityItem item, Client client) //this.RaisePropertyChanged("Icon"); //this.RaisePropertyChanged("Align"); } - - - } } diff --git a/DesktopUI2/DesktopUI2/ViewModels/ApplicationObjectViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/ApplicationObjectViewModel.cs new file mode 100644 index 0000000000..b2da065e17 --- /dev/null +++ b/DesktopUI2/DesktopUI2/ViewModels/ApplicationObjectViewModel.cs @@ -0,0 +1,104 @@ +using Avalonia; +using Avalonia.Layout; +using ReactiveUI; +using Speckle.Core.Api; +using Speckle.Core.Logging; +using Speckle.Core.Models; +using System.Collections.Generic; +using System.Linq; +using Splat; +using System.Threading.Tasks; +using System; + +namespace DesktopUI2.ViewModels +{ + public class ApplicationObjectViewModel : ReactiveObject + { + public string Log { get; set; } + public List ApplicationIds { get; set; } + public string Name { get; set; } + public string Id { get; set; } + public string Icon { get; set; } + public string Color { get; set; } = "Gray"; + public string Status { get; set; } + public double Opacity { get; set; } = 1; + public bool PreviewEnabled { get; set; } = true; + + public string SearchText { get; set; } + + private ConnectorBindings Bindings; + + private bool previewOn = false; + public bool PreviewOn + { + get => previewOn; + set => this.RaiseAndSetIfChanged(ref previewOn, value); + } + + public ApplicationObjectViewModel(ApplicationObject item, bool isReceiver) + { + //use dependency injection to get bindings + Bindings = Locator.Current.GetService(); + var cleanLog = item.Log.Where(o => !string.IsNullOrEmpty(o)).ToList(); + + Id = item.OriginalId; + Name = item.Descriptor; + Log = cleanLog.Count == 0 ? null : string.Join("\n", cleanLog); + Status = item.Status.ToString(); + ApplicationIds = isReceiver ? item.CreatedIds : new List() { item.OriginalId }; + + var logString = cleanLog.Count() != 0 ? String.Join(" ", Log) : ""; + var createdIdsString = String.Join(" ", item.CreatedIds); + SearchText = $"{Id} {Name} {Status} {logString} {createdIdsString}"; + + if (ApplicationIds.Count == 0) PreviewEnabled = false; + + switch (item.Status) + { + case ApplicationObject.State.Created: + Icon = "PlusThick"; + break; + case ApplicationObject.State.Updated: + Icon = "Refresh"; + break; + case ApplicationObject.State.Skipped: + Icon = "Cancel"; + Opacity = 0.6; + break; + case ApplicationObject.State.Removed: + Icon = "MinusThick"; + Opacity = 0.6; + break; + case ApplicationObject.State.Failed: + Icon = "Cancel"; + Color = "Red"; + Opacity = 0.6; + break; + default: + Icon = "Help"; + Opacity = 0.6; + break; + } + } + + public string GetSummary() + { + var summary = new string[2] { $"{Name} {Id} {Status}", Log }; + return string.Join("\n", summary); + } + + public async void PreviewCommand() + { + PreviewOn = !PreviewOn; + if (PreviewOn) + { + Bindings.SelectClientObjects(ApplicationIds); + Analytics.TrackEvent(null, Analytics.Events.DUIAction, new Dictionary() { { "name", "Viewed Report Item" } }); + } + else + { + Bindings.SelectClientObjects(ApplicationIds, true); + } + } + } +} diff --git a/DesktopUI2/DesktopUI2/ViewModels/CommentViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/CommentViewModel.cs index 60562b4a6b..0eff138eec 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/CommentViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/CommentViewModel.cs @@ -22,12 +22,9 @@ public string Text get { return string.Join("", Comment.text.Doc.Content.Select(x => string.Join("", x.Content.Select(x => x.Text).ToList())).ToList()); - - } } - private async Task GetAuthorAsync() { return await ApiUtils.GetAccount(Comment.authorId, _client); @@ -57,8 +54,6 @@ public string RepliesCount } } - - public CommentViewModel(CommentItem item, string streamId, Client client) { Comment = item; @@ -72,7 +67,6 @@ public CommentViewModel(CommentItem item, string streamId, Client client) Replies.Add(reply); } } - } public void OpenComment() @@ -90,8 +84,5 @@ public void OpenComment() Process.Start(new ProcessStartInfo($"{_client.Account.serverInfo.url}/streams/{StreamId}/{r0.resourceType}s/{r0.resourceId}?cId={Comment.id}{overlay}") { UseShellExecute = true }); Analytics.TrackEvent(null, Analytics.Events.DUIAction, new Dictionary() { { "name", "Comment View" } }); } - - - } } diff --git a/DesktopUI2/DesktopUI2/ViewModels/DesignViewModels/DesignSavedStreamsViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/DesignViewModels/DesignSavedStreamsViewModel.cs index 3525d9bbc2..947660ea1b 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/DesignViewModels/DesignSavedStreamsViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/DesignViewModels/DesignSavedStreamsViewModel.cs @@ -5,10 +5,8 @@ namespace DesktopUI2.ViewModels.DesignViewModels { public class DesignSavedStreamsViewModel { - public List SavedStreams { get; set; } - public DesignSavedStreamsViewModel() { SavedStreams = new List @@ -16,20 +14,16 @@ public DesignSavedStreamsViewModel() new DesignSavedStreamViewModel(), new DesignSavedStreamViewModel() { - Stream = new DesignStream { name = "BIM data is cool" }, StreamState = new DesignStreamState() { BranchName = "main", IsReceiver = false, Filter = new ListSelectionFilter { Icon = "Mouse", Name = "Category" }, - }, ShowNotification = false, - ShowReport = false, NoAccess = true, } - }; } } @@ -40,7 +34,6 @@ public class DesignStreamState public string CommitId { get; set; } public string CommitMessage { get; set; } public bool IsReceiver { get; set; } - public bool SchedulerEnabled { get; set; } public ListSelectionFilter Filter { get; set; } public object Client { get; internal set; } @@ -58,7 +51,6 @@ public class DesignStream public string role { get; set; } } - public class DesignSavedStreamViewModel { public DesignStreamState StreamState { get; set; } @@ -68,14 +60,13 @@ public class DesignSavedStreamViewModel public string LastUsed { get; set; } = "Never"; public string Notification { get; set; } = "Hello"; public bool ShowNotification { get; set; } = true; - public bool NoAccess { get; set; } = false; - public bool ShowReport { get; set; } = true; + public List MenuItems = new List(); public List Activity { get; set; } + public List Report { get; set; } public List Comments { get; set; } - public DesignSavedStreamViewModel() { Stream = new DesignStream { name = "Sample stream x", id = "1324235", role = "owner" }; @@ -87,7 +78,6 @@ public DesignSavedStreamViewModel() SchedulerEnabled = true, IsReceiver = true, Filter = new ListSelectionFilter { Icon = "Mouse", Name = "Category" } - }; Activity = new List() { @@ -100,8 +90,6 @@ public DesignSavedStreamViewModel() Margin = new Avalonia.Thickness(10,10,50,0), Icon = "ArrowTopRight", Message = "Commit created on branch main: 0ae5a01ad7 (Sent 148 objects from Revit2022.)" } }; - - } public void ReceiveCommand() diff --git a/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs index 871ba2bd50..f987351a55 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs @@ -385,11 +385,8 @@ private async Task GetStreams() { } - } - - internal async void Init() { try @@ -435,7 +432,6 @@ private void RemoveSavedStream(string id) } } - public async void RemoveAccountCommand(Account account) { try @@ -554,12 +550,9 @@ public void ReceiveCommand(object parameter) public async void NewStreamCommand() { - var dialog = new NewStreamDialog(Accounts); var result = await dialog.ShowDialog(); - - if (result) { try @@ -683,7 +676,6 @@ public void ToggleDarkThemeCommand() var config = ConfigManager.Load(); config.DarkTheme = isDark; ConfigManager.Save(config); - } private void ChangeTheme(bool isDark) @@ -701,20 +693,13 @@ private void ChangeTheme(bool isDark) theme.SetBaseTheme(Theme.Dark); materialTheme.CurrentTheme = theme; - - } - public void RefreshCommand() { Analytics.TrackEvent(Analytics.Events.DUIAction, new Dictionary() { { "name", "Refresh" } }); ApiUtils.ClearCache(); Init(); } - - } - - } \ No newline at end of file diff --git a/DesktopUI2/DesktopUI2/ViewModels/ProgressViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/ProgressViewModel.cs index 77b4d25308..daeb68ee79 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/ProgressViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/ProgressViewModel.cs @@ -1,4 +1,5 @@ -using DesktopUI2.Views; +using Avalonia.Controls.Selection; +using DesktopUI2.Views; using DesktopUI2.Views.Windows; using ReactiveUI; using Speckle.Core.Logging; @@ -12,8 +13,6 @@ namespace DesktopUI2.ViewModels { - - public class ProgressViewModel : ReactiveObject { public CancellationTokenSource CancellationTokenSource { get; set; } = new CancellationTokenSource(); @@ -50,7 +49,6 @@ public string ProgressTitle } } - private string _progressSummary; public string ProgressSummary { @@ -112,13 +110,6 @@ public void GetHelpCommand() report += Report.OperationErrorsString; } - if (Report.ConversionErrorsCount > 0) - { - if (Report.OperationErrorsCount > 0) - report += "\n\n"; - report += "CONVERSION ERRORS\n\n"; - report += Report.ConversionErrorsString; - } var safeReport = HttpUtility.UrlEncode(report); Process.Start(new ProcessStartInfo($"https://speckle.community/new-topic?title=I%20need%20help%20with...&body={safeReport}&category=help") { UseShellExecute = true }); } @@ -128,14 +119,5 @@ public void CancelCommand() CancellationTokenSource.Cancel(); Analytics.TrackEvent(Analytics.Events.DUIAction, new Dictionary() { { "name", "Cancelled Quick Op" } }); } - - public async void OpenReportCommand() - { - var report = new Report(); - report.DataContext = this; - Analytics.TrackEvent(Analytics.Events.DUIAction, new Dictionary() { { "name", "Open Report" } }); - await report.ShowDialog(); - - } } } diff --git a/DesktopUI2/DesktopUI2/ViewModels/StreamViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/StreamViewModel.cs index 2279c3443a..b5cf8c4f1c 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/StreamViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/StreamViewModel.cs @@ -40,6 +40,13 @@ public class StreamViewModel : ReactiveObject, IRoutableViewModel public ICommand RemoveSavedStreamCommand { get; } + private bool previewOn = false; + public bool PreviewOn + { + get => previewOn; + set => this.RaiseAndSetIfChanged(ref previewOn, value); + } + #region bindings private Stream _stream; public Stream Stream @@ -105,19 +112,7 @@ public bool ShowNotification get => !string.IsNullOrEmpty(Notification); } - private bool _showReport; - - public bool ShowReport - { - get => _showReport; - private set - { - this.RaiseAndSetIfChanged(ref _showReport, value); - } - } - private bool _isRemovingStream; - public bool IsRemovingStream { get => _isRemovingStream; @@ -128,7 +123,6 @@ private set } private bool _isExpanded; - public bool IsExpanded { get => _isExpanded; @@ -232,6 +226,55 @@ public List Activity private set => this.RaiseAndSetIfChanged(ref _activity, value); } + private List _report; + public List Report + { + get => _report; + private set + { + this.RaiseAndSetIfChanged(ref _report, value); + this.RaisePropertyChanged("FilteredReport"); + this.RaisePropertyChanged("HasReportItems"); + this.RaisePropertyChanged("Log"); + } + } + public List FilteredReport + { + get + { + if (SearchQuery == "") + return Report; + else + return Report.Where(o => o.SearchText.ToLower().Contains(SearchQuery.ToLower())).ToList(); + } + } + public bool HasReportItems + { + get { return (Progress.Report.ReportObjects == null || Progress.Report.ReportObjects.Count == 0) ? false : true; } + } + public string Log + { + get + { + return Progress.Report.ConversionLogString; + } + } + + private string _searchQuery = ""; + public string SearchQuery + { + get => _searchQuery; + set + { + this.RaiseAndSetIfChanged(ref _searchQuery, value); + this.RaisePropertyChanged("FilteredReport"); + } + } + public void ClearSearchCommand() + { + SearchQuery = ""; + } + private List _comments; public List Comments { @@ -389,6 +432,7 @@ private void Init() GetBranchesAndRestoreState(); GetActivity(); + GetReport(); GetComments(); } catch (Exception ex) @@ -409,10 +453,8 @@ private void GenerateMenuItems() { var menu = new MenuItemViewModel { Header = new MaterialIcon { Kind = MaterialIconKind.EllipsisVertical, Foreground = Avalonia.Media.Brushes.Gray } }; menu.Items = new List { - //new MenuItemViewModel (EditSavedStreamCommand, "Edit", MaterialIconKind.Cog), new MenuItemViewModel (ViewOnlineSavedStreamCommand, "View online", MaterialIconKind.ExternalLink), new MenuItemViewModel (CopyStreamURLCommand, "Copy URL to clipboard", MaterialIconKind.ContentCopy), - new MenuItemViewModel (OpenReportCommand, "Open Report", MaterialIconKind.TextBox) }; var customMenues = Bindings.GetCustomStreamMenuItems(); if (customMenues != null) @@ -454,7 +496,6 @@ internal async void GetBranchesAndRestoreState() //by default the first available receive mode is selected SelectedReceiveMode = ReceiveModes.Contains(StreamState.ReceiveMode) ? StreamState.ReceiveMode : ReceiveModes[0]; - //get available settings from our bindings Settings = Bindings.GetSettings(); @@ -487,7 +528,6 @@ internal async void GetBranchesAndRestoreState() SelectedFilter = selectionFilter; SelectedFilter.AddObjectSelection(); } - } if (StreamState.Settings != null) { @@ -496,7 +536,6 @@ internal async void GetBranchesAndRestoreState() var savedSetting = StreamState.Settings.FirstOrDefault(o => o.Slug == setting.Slug); if (savedSetting != null) setting.Selection = savedSetting.Selection; - } } } @@ -506,6 +545,17 @@ internal async void GetBranchesAndRestoreState() } } + private void GetReport() + { + var report = new List(); + foreach (var applicationObject in Progress.Report.ReportObjects) + { + var rvm = new ApplicationObjectViewModel(applicationObject, StreamState.IsReceiver); + report.Add(rvm); + } + Report = report; + } + private async void GetActivity() { try @@ -549,7 +599,6 @@ private async void GetComments() } } - private async void ScrollToBottom() { try @@ -686,6 +735,14 @@ private void DownloadComplete(object sender, DownloadDataCompletedEventArgs e) } #region commands + public async void CopyReportCommand() + { + var reportObjectSummaries = FilteredReport.Select(o => o.GetSummary()).ToArray(); + var summary = string.Join("\n", reportObjectSummaries); + + await Avalonia.Application.Current.Clipboard.SetTextAsync(summary); + Analytics.TrackEvent(Analytics.Events.DUIAction, new Dictionary() { { "name", "Copy Report" } }); + } public void ShareCommand() { @@ -698,13 +755,6 @@ public void CloseNotificationCommand() Analytics.TrackEvent(null, Analytics.Events.DUIAction, new Dictionary() { { "name", "Notification Dismiss" } }); } - public void CloseReportNotificationCommand() - { - ShowReport = false; - Analytics.TrackEvent(null, Analytics.Events.DUIAction, new Dictionary() { { "name", "Report Dismiss" } }); - } - - public void LaunchNotificationCommand() { Analytics.TrackEvent(null, Analytics.Events.DUIAction, new Dictionary() { { "name", "Notification Click" } }); @@ -765,11 +815,8 @@ public async void SendCommand() Notification = "Nothing sent!"; } - if (Progress.Report.ConversionErrorsCount > 0 || Progress.Report.OperationErrorsCount > 0) - ShowReport = true; - GetActivity(); - + GetReport(); } catch (Exception ex) { @@ -777,6 +824,40 @@ public async void SendCommand() } } + public async void PreviewCommand() + { + PreviewOn = !PreviewOn; + if (PreviewOn) + { + try + { + UpdateStreamState(); + Reset(); + if (IsReceiver) + { + Analytics.TrackEvent(Analytics.Events.DUIAction, new Dictionary() { { "name", "Preview Receive" } }); + await Task.Run(() => Bindings.PreviewReceive(StreamState, Progress)); + } + if (!IsReceiver) + { + Analytics.TrackEvent(Analytics.Events.DUIAction, new Dictionary() { { "name", "Preview Send" } }); + await Task.Run(() => Bindings.PreviewSend(StreamState, Progress)); + } + GetReport(); + } + catch (Exception ex) + { + + } + } + else + { + Progress.CancellationTokenSource.Cancel(); + Bindings.ResetDocument(); + Reset(); + } + } + public async void ReceiveCommand() { try @@ -796,11 +877,8 @@ public async void ReceiveCommand() Analytics.TrackEvent(StreamState.Client.Account, Analytics.Events.Receive, new Dictionary() { { "mode", StreamState.ReceiveMode }, { "auto", StreamState.AutoReceive } }); } - if (Progress.Report.ConversionErrorsCount > 0 || Progress.Report.OperationErrorsCount > 0) - ShowReport = true; - - GetActivity(); + GetReport(); } catch (Exception ex) { @@ -812,7 +890,6 @@ private void Reset() { Notification = ""; NotificationUrl = ""; - ShowReport = false; Progress = new ProgressViewModel(); } @@ -825,26 +902,6 @@ public void CancelSendOrReceiveCommand() Notification = IsReceiver ? "Cancelled Receive" : "Cancelled Send"; } - public async void OpenReportCommand() - { - try - { - //ensure click transition has finished - await Task.Delay(1000); - Analytics.TrackEvent(Analytics.Events.DUIAction, new Dictionary() { { "name", "Open Report" } }); - ShowReport = true; - var report = new Report(); - //report.Title = $"Report of the last operation, {LastUsed.ToLower()}"; - report.DataContext = Progress; - await report.ShowDialog(); - - } - catch (Exception ex) - { - - } - } - private void SaveCommand() { try @@ -869,7 +926,6 @@ private void SaveCommand() } } - private void OpenSettingsCommand() { try @@ -878,14 +934,10 @@ private void OpenSettingsCommand() var settingsPageViewModel = new SettingsPageViewModel(HostScreen, Settings.Select(x => new SettingViewModel(x)).ToList(), this); MainViewModel.RouterInstance.Navigate.Execute(settingsPageViewModel); Analytics.TrackEvent(null, Analytics.Events.DUIAction, new Dictionary() { { "name", "Settings Open" } }); - - } catch (Exception e) { } - - } private void AskRemoveSavedStreamCommand() @@ -898,7 +950,6 @@ private void CancelRemoveSavedStreamCommand() IsRemovingStream = false; } - [DependsOn(nameof(SelectedBranch))] [DependsOn(nameof(SelectedFilter))] [DependsOn(nameof(SelectedCommit))] @@ -908,8 +959,6 @@ private bool CanSaveCommand(object parameter) return true; } - - [DependsOn(nameof(SelectedBranch))] [DependsOn(nameof(SelectedFilter))] [DependsOn(nameof(IsReceiver))] @@ -926,6 +975,15 @@ private bool CanReceiveCommand(object parameter) return IsReady(); } + [DependsOn(nameof(SelectedBranch))] + [DependsOn(nameof(SelectedCommit))] + [DependsOn(nameof(SelectedFilter))] + [DependsOn(nameof(IsReceiver))] + private bool CanPreviewCommand(object parameter) + { + return IsReady(); + } + private bool IsReady() { if (NoAccess) diff --git a/DesktopUI2/DesktopUI2/Views/Converters/RoleCanSendValueConverter.cs b/DesktopUI2/DesktopUI2/Views/Converters/RoleCanSendValueConverter.cs index 5893625e60..c42375b754 100644 --- a/DesktopUI2/DesktopUI2/Views/Converters/RoleCanSendValueConverter.cs +++ b/DesktopUI2/DesktopUI2/Views/Converters/RoleCanSendValueConverter.cs @@ -5,7 +5,6 @@ namespace DesktopUI2.Views.Converters { - public class RoleCanSendValueConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) diff --git a/DesktopUI2/DesktopUI2/Views/Pages/HomeControls/SavedStreams.xaml b/DesktopUI2/DesktopUI2/Views/Pages/HomeControls/SavedStreams.xaml index 8a4b94f093..6ab6adbc79 100644 --- a/DesktopUI2/DesktopUI2/Views/Pages/HomeControls/SavedStreams.xaml +++ b/DesktopUI2/DesktopUI2/Views/Pages/HomeControls/SavedStreams.xaml @@ -292,56 +292,6 @@ - - - - - - - - - - - - - diff --git a/DesktopUI2/DesktopUI2/Views/Pages/SharedControls/StreamDetails.xaml b/DesktopUI2/DesktopUI2/Views/Pages/SharedControls/StreamDetails.xaml index 3ea1fb05fd..bd8b2eeb3c 100644 --- a/DesktopUI2/DesktopUI2/Views/Pages/SharedControls/StreamDetails.xaml +++ b/DesktopUI2/DesktopUI2/Views/Pages/SharedControls/StreamDetails.xaml @@ -69,10 +69,5 @@ Classes="Overline" Text="{Binding Stream.id}" TextTrimming="CharacterEllipsis" /> - - - - - diff --git a/DesktopUI2/DesktopUI2/Views/Pages/StreamEditControls/Receive.xaml b/DesktopUI2/DesktopUI2/Views/Pages/StreamEditControls/Receive.xaml index 276c3db48a..bf0b590b23 100644 --- a/DesktopUI2/DesktopUI2/Views/Pages/StreamEditControls/Receive.xaml +++ b/DesktopUI2/DesktopUI2/Views/Pages/StreamEditControls/Receive.xaml @@ -195,7 +195,7 @@ assists:ShadowAssist.ShadowDepth="Depth0" Background="Transparent" Command="{Binding SaveCommand}" - IsEnabled="{Binding Progress.IsProgressing, Converter={x:Static BoolConverters.Not}}" + IsEnabled="{Binding !Progress.IsProgressing}" ToolTip.Tip="Save this sender to the file without sending"> + + + diff --git a/DesktopUI2/DesktopUI2/Views/Pages/StreamEditControls/Report.xaml b/DesktopUI2/DesktopUI2/Views/Pages/StreamEditControls/Report.xaml new file mode 100644 index 0000000000..b0f5a4dd9f --- /dev/null +++ b/DesktopUI2/DesktopUI2/Views/Pages/StreamEditControls/Report.xaml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DesktopUI2/DesktopUI2/Views/Pages/StreamEditControls/Report.xaml.cs b/DesktopUI2/DesktopUI2/Views/Pages/StreamEditControls/Report.xaml.cs new file mode 100644 index 0000000000..5b12f01bc2 --- /dev/null +++ b/DesktopUI2/DesktopUI2/Views/Pages/StreamEditControls/Report.xaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace DesktopUI2.Views.Pages.StreamEditControls +{ + public partial class Report : UserControl + { + public Report() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/DesktopUI2/DesktopUI2/Views/Pages/StreamEditControls/Send.xaml b/DesktopUI2/DesktopUI2/Views/Pages/StreamEditControls/Send.xaml index 2ccf127906..2d3c8e9aa4 100644 --- a/DesktopUI2/DesktopUI2/Views/Pages/StreamEditControls/Send.xaml +++ b/DesktopUI2/DesktopUI2/Views/Pages/StreamEditControls/Send.xaml @@ -98,7 +98,7 @@ - + + + + diff --git a/DesktopUI2/DesktopUI2/Views/Pages/StreamEditView.xaml b/DesktopUI2/DesktopUI2/Views/Pages/StreamEditView.xaml index 2ec2be87f2..56cffa7645 100644 --- a/DesktopUI2/DesktopUI2/Views/Pages/StreamEditView.xaml +++ b/DesktopUI2/DesktopUI2/Views/Pages/StreamEditView.xaml @@ -44,7 +44,6 @@ Command="{Binding GoBack}" ToolTip.Tip="Back"> - - @@ -100,10 +98,7 @@ - - - @@ -111,7 +106,7 @@ Grid.Row="1" Margin="15,0" RowDefinitions="*,auto,auto"> - + @@ -155,6 +150,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -201,58 +283,6 @@ Height="15" Foreground="White" Kind="Close" /> - - - - - - - - - - - - - - diff --git a/DesktopUI2/DesktopUI2/Views/Pages/StreamEditView.xaml.cs b/DesktopUI2/DesktopUI2/Views/Pages/StreamEditView.xaml.cs index 133464562e..a8340ee95a 100644 --- a/DesktopUI2/DesktopUI2/Views/Pages/StreamEditView.xaml.cs +++ b/DesktopUI2/DesktopUI2/Views/Pages/StreamEditView.xaml.cs @@ -15,7 +15,6 @@ public StreamEditView() private void InitializeComponent() { AvaloniaXamlLoader.Load(this); - } public static StreamEditView Instance { get; private set; } diff --git a/DesktopUI2/DesktopUI2/Views/Windows/Report.xaml b/DesktopUI2/DesktopUI2/Views/Windows/Report.xaml deleted file mode 100644 index 3387373e55..0000000000 --- a/DesktopUI2/DesktopUI2/Views/Windows/Report.xaml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - -