/
ConnectorBindingsRevit.Send.cs
278 lines (230 loc) · 9.22 KB
/
ConnectorBindingsRevit.Send.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Autodesk.Revit.DB;
using Avalonia.Threading;
using DesktopUI2;
using DesktopUI2.Models;
using DesktopUI2.Models.Settings;
using DesktopUI2.ViewModels;
using RevitSharedResources.Interfaces;
using RevitSharedResources.Models;
using Serilog.Context;
using Speckle.Core.Api;
using Speckle.Core.Kits;
using Speckle.Core.Logging;
using Speckle.Core.Models;
using Speckle.Core.Transports;
namespace Speckle.ConnectorRevit.UI;
public partial class ConnectorBindingsRevit
{
// used to store the Stream State settings when sending/receiving
private List<ISetting>? CurrentSettings { get; set; }
/// <summary>
/// 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.
/// </summary>
/// <param name="state">StreamState passed by the UI</param>
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
public override async Task<string> SendStream(StreamState state, ProgressViewModel progress)
{
using var ctx = RevitConverterState.Push();
//make sure to instance a new copy so all values are reset correctly
var converter = (ISpeckleConverter)Activator.CreateInstance(Converter.GetType());
converter.SetContextDocument(CurrentDoc.Document);
converter.Report.ReportObjects.Clear();
// set converter settings as tuples (setting slug, setting selection)
var settings = new Dictionary<string, string>();
CurrentSettings = state.Settings;
foreach (var setting in state.Settings)
{
settings.Add(setting.Slug, setting.Selection);
}
converter.SetConverterSettings(settings);
var streamId = state.StreamId;
var client = state.Client;
// The selectedObjects needs to be collected inside the Revit API context or else, in rare cases,
// the filteredElementCollectors will throw a "modification forbidden" exception. This can be reproduced
// by opening Snowdon towers in R24 and immediately sending the default 3D view from the landing page
var selectedObjects = await APIContext
.Run(_ => GetSelectionFilterObjects(converter, state.Filter))
.ConfigureAwait(false);
selectedObjects = HandleSelectedObjectDescendants(selectedObjects).ToList();
state.SelectedObjectIds = selectedObjects.Select(x => x.UniqueId).Distinct().ToList();
if (!selectedObjects.Any())
{
throw new InvalidOperationException(
"There are zero objects to send. Please use a filter, or set some via selection."
);
}
converter.SetContextDocument(revitDocumentAggregateCache);
converter.SetContextObjects(
selectedObjects
.Select(x => new ApplicationObject(x.UniqueId, x.GetType().ToString()) { applicationId = x.UniqueId })
.ToList()
);
var commitObject = converter.ConvertToSpeckle(CurrentDoc.Document) ?? new Collection();
IRevitCommitObjectBuilder commitObjectBuilder;
if (converter is not IRevitCommitObjectBuilderExposer builderExposer)
{
throw new Exception(
$"Converter {converter.Name} by {converter.Author} does not provide the necessary object, {nameof(IRevitCommitObjectBuilder)}, needed to build the Speckle commit object."
);
}
else
{
commitObjectBuilder = builderExposer.commitObjectBuilder;
}
progress.Report = new ProgressReport();
progress.Max = selectedObjects.Count;
var conversionProgressDict = new ConcurrentDictionary<string, int> { ["Conversion"] = 0 };
var convertedCount = 0;
await APIContext
.Run(() =>
{
using var d0 = LogContext.PushProperty("converterName", converter.Name);
using var d1 = LogContext.PushProperty("converterAuthor", converter.Author);
using var d2 = LogContext.PushProperty("conversionDirection", nameof(ISpeckleConverter.ConvertToSpeckle));
using var d3 = LogContext.PushProperty("converterSettings", settings);
foreach (var revitElement in selectedObjects)
{
if (progress.CancellationToken.IsCancellationRequested)
{
break;
}
bool isAlreadyConverted = GetOrCreateApplicationObject(
revitElement,
converter.Report,
out ApplicationObject reportObj
);
if (isAlreadyConverted)
{
continue;
}
progress.Report.Log(reportObj);
//Add context to logger
using var _d3 = LogContext.PushProperty("fromType", revitElement.GetType());
using var _d4 = LogContext.PushProperty("elementCategory", revitElement.Category?.Name);
try
{
converter.Report.Log(reportObj); // Log object so converter can access
Base result = ConvertToSpeckle(revitElement, converter);
reportObj.Update(
status: ApplicationObject.State.Created,
logItem: $"Sent as {ConnectorRevitUtils.SimplifySpeckleType(result.speckle_type)}"
);
if (result.applicationId != reportObj.applicationId)
{
SpeckleLog.Logger.Information(
"Conversion result of type {elementType} has a different application Id ({actualId}) to the report object {expectedId}",
revitElement.GetType(),
result.applicationId,
reportObj.applicationId
);
result.applicationId = reportObj.applicationId;
}
commitObjectBuilder.IncludeObject(result, revitElement);
convertedCount++;
}
catch (Exception ex)
{
ConnectorHelpers.LogConversionException(ex);
var failureStatus = ConnectorHelpers.GetAppObjectFailureState(ex);
reportObj.Update(status: failureStatus, logItem: ex.Message);
}
conversionProgressDict["Conversion"]++;
progress.Update(conversionProgressDict);
YieldToUIThread(TimeSpan.FromMilliseconds(1));
}
})
.ConfigureAwait(false);
revitDocumentAggregateCache.InvalidateAll();
progress.Report.Merge(converter.Report);
progress.CancellationToken.ThrowIfCancellationRequested();
if (convertedCount == 0)
{
throw new SpeckleException("Zero objects converted successfully. Send stopped.");
}
commitObjectBuilder.BuildCommitObject(commitObject);
var transports = new List<ITransport>() { new ServerTransport(client.Account, streamId) };
var objectId = await Operations
.Send(
@object: commitObject,
cancellationToken: progress.CancellationToken,
transports: transports,
onProgressAction: dict => progress.Update(dict),
onErrorAction: ConnectorHelpers.DefaultSendErrorHandler,
disposeTransports: true
)
.ConfigureAwait(true);
progress.CancellationToken.ThrowIfCancellationRequested();
var actualCommit = new CommitCreateInput()
{
streamId = streamId,
objectId = objectId,
branchName = state.BranchName,
message = state.CommitMessage ?? $"Sent {convertedCount} objects from {ConnectorRevitUtils.RevitAppName}.",
sourceApplication = ConnectorRevitUtils.RevitAppName,
};
if (state.PreviousCommitId != null)
{
actualCommit.parents = new List<string>() { state.PreviousCommitId };
}
var commitId = await ConnectorHelpers
.CreateCommit(client, actualCommit, progress.CancellationToken)
.ConfigureAwait(false);
return commitId;
}
public static bool GetOrCreateApplicationObject(
Element revitElement,
ProgressReport report,
out ApplicationObject reportObj
)
{
if (report.ReportObjects.TryGetValue(revitElement.UniqueId, out var applicationObject))
{
reportObj = applicationObject;
return true;
}
string descriptor = ConnectorRevitUtils.ObjectDescriptor(revitElement);
reportObj = new(revitElement.UniqueId, descriptor) { applicationId = revitElement.UniqueId };
return false;
}
private DateTime timerStarted = DateTime.MinValue;
private void YieldToUIThread(TimeSpan delay)
{
var currentTime = DateTime.UtcNow;
if (currentTime.Subtract(timerStarted) < TimeSpan.FromSeconds(.15))
{
return;
}
using CancellationTokenSource s = new(delay);
Dispatcher.UIThread.MainLoop(s.Token);
timerStarted = currentTime;
}
private static Base ConvertToSpeckle(Element revitElement, ISpeckleConverter converter)
{
if (!converter.CanConvertToSpeckle(revitElement))
{
string skipMessage = revitElement switch
{
RevitLinkInstance => "Enable linked model support from the settings to send this object",
_ => "Sending this object type is not supported yet"
};
throw new ConversionSkippedException(skipMessage, revitElement);
}
Base conversionResult = converter.ConvertToSpeckle(revitElement);
if (conversionResult == null)
{
throw new SpeckleException(
$"Conversion of {revitElement.GetType().Name} with id {revitElement.Id} (ToSpeckle) returned null"
);
}
return conversionResult;
}
}