197 lines
6.7 KiB
C#
197 lines
6.7 KiB
C#
using Autodesk.AutoCAD.DatabaseServices;
|
|
using Speckle.Connectors.Autocad.HostApp;
|
|
using Speckle.Connectors.Autocad.HostApp.Extensions;
|
|
using Speckle.Connectors.DUI.Bindings;
|
|
using Speckle.Connectors.DUI.Bridge;
|
|
using Speckle.Connectors.DUI.Models;
|
|
using Speckle.Connectors.DUI.Models.Card;
|
|
using Speckle.Connectors.Utils.Cancellation;
|
|
using Speckle.Autofac.DependencyInjection;
|
|
using Speckle.Connectors.Autocad.Operations.Send;
|
|
using Speckle.Connectors.DUI.Exceptions;
|
|
using Speckle.Connectors.Utils.Operations;
|
|
using Speckle.Connectors.DUI.Models.Card.SendFilter;
|
|
using Speckle.Connectors.Utils;
|
|
using Speckle.Connectors.Utils.Caching;
|
|
|
|
namespace Speckle.Connectors.Autocad.Bindings;
|
|
|
|
public sealed class AutocadSendBinding : ISendBinding
|
|
{
|
|
public string Name => "sendBinding";
|
|
public SendBindingUICommands Commands { get; }
|
|
public IBridge Parent { get; }
|
|
|
|
private readonly DocumentModelStore _store;
|
|
private readonly AutocadIdleManager _idleManager;
|
|
private readonly List<ISendFilter> _sendFilters;
|
|
private readonly CancellationManager _cancellationManager;
|
|
private readonly IUnitOfWorkFactory _unitOfWorkFactory;
|
|
private readonly AutocadSettings _autocadSettings;
|
|
private readonly ISendConversionCache _sendConversionCache;
|
|
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
|
|
|
|
/// <summary>
|
|
/// Used internally to aggregate the changed objects' id.
|
|
/// </summary>
|
|
private HashSet<string> ChangedObjectIds { get; set; } = new();
|
|
|
|
public AutocadSendBinding(
|
|
DocumentModelStore store,
|
|
AutocadIdleManager idleManager,
|
|
IBridge parent,
|
|
IEnumerable<ISendFilter> sendFilters,
|
|
CancellationManager cancellationManager,
|
|
AutocadSettings autocadSettings,
|
|
IUnitOfWorkFactory unitOfWorkFactory,
|
|
ISendConversionCache sendConversionCache,
|
|
ITopLevelExceptionHandler topLevelExceptionHandler
|
|
)
|
|
{
|
|
_store = store;
|
|
_idleManager = idleManager;
|
|
_unitOfWorkFactory = unitOfWorkFactory;
|
|
_autocadSettings = autocadSettings;
|
|
_cancellationManager = cancellationManager;
|
|
_sendFilters = sendFilters.ToList();
|
|
_sendConversionCache = sendConversionCache;
|
|
_topLevelExceptionHandler = topLevelExceptionHandler;
|
|
Parent = parent;
|
|
Commands = new SendBindingUICommands(parent);
|
|
|
|
Application.DocumentManager.DocumentActivated += (_, args) =>
|
|
topLevelExceptionHandler.CatchUnhandled(() => SubscribeToObjectChanges(args.Document));
|
|
|
|
if (Application.DocumentManager.CurrentDocument != null)
|
|
{
|
|
// catches the case when autocad just opens up with a blank new doc
|
|
SubscribeToObjectChanges(Application.DocumentManager.CurrentDocument);
|
|
}
|
|
}
|
|
|
|
private readonly List<string> _docSubsTracker = new();
|
|
|
|
private void SubscribeToObjectChanges(Document doc)
|
|
{
|
|
if (doc == null || doc.Database == null || _docSubsTracker.Contains(doc.Name))
|
|
{
|
|
return;
|
|
}
|
|
|
|
_docSubsTracker.Add(doc.Name);
|
|
doc.Database.ObjectAppended += (_, e) => OnObjectChanged(e.DBObject);
|
|
doc.Database.ObjectErased += (_, e) => OnObjectChanged(e.DBObject);
|
|
doc.Database.ObjectModified += (_, e) => OnObjectChanged(e.DBObject);
|
|
}
|
|
|
|
void OnObjectChanged(DBObject dbObject)
|
|
{
|
|
_topLevelExceptionHandler.CatchUnhandled(() => OnChangeChangedObjectIds(dbObject));
|
|
}
|
|
|
|
private void OnChangeChangedObjectIds(DBObject dBObject)
|
|
{
|
|
ChangedObjectIds.Add(dBObject.Handle.Value.ToString());
|
|
_idleManager.SubscribeToIdle(RunExpirationChecks);
|
|
}
|
|
|
|
private void RunExpirationChecks()
|
|
{
|
|
var senders = _store.GetSenders();
|
|
string[] objectIdsList = ChangedObjectIds.ToArray();
|
|
List<string> expiredSenderIds = new();
|
|
|
|
_sendConversionCache.EvictObjects(objectIdsList);
|
|
|
|
foreach (SenderModelCard modelCard in senders)
|
|
{
|
|
var intersection = modelCard.SendFilter.NotNull().GetObjectIds().Intersect(objectIdsList).ToList();
|
|
bool isExpired = intersection.Count != 0;
|
|
if (isExpired)
|
|
{
|
|
expiredSenderIds.Add(modelCard.ModelCardId.NotNull());
|
|
}
|
|
}
|
|
|
|
Commands.SetModelsExpired(expiredSenderIds);
|
|
ChangedObjectIds = new HashSet<string>();
|
|
}
|
|
|
|
public List<ISendFilter> GetSendFilters() => _sendFilters;
|
|
|
|
public Task Send(string modelCardId)
|
|
{
|
|
Parent.RunOnMainThread(async () => await SendInternal(modelCardId).ConfigureAwait(false));
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private async Task SendInternal(string modelCardId)
|
|
{
|
|
try
|
|
{
|
|
if (_store.GetModelById(modelCardId) is not SenderModelCard modelCard)
|
|
{
|
|
// Handle as GLOBAL ERROR at BrowserBridge
|
|
throw new InvalidOperationException("No publish model card was found.");
|
|
}
|
|
|
|
using var uow = _unitOfWorkFactory.Resolve<SendOperation<AutocadRootObject>>();
|
|
|
|
// Init cancellation token source -> Manager also cancel it if exist before
|
|
CancellationTokenSource cts = _cancellationManager.InitCancellationTokenSource(modelCardId);
|
|
|
|
// Disable document activation (document creation and document switch)
|
|
// Not disabling results in DUI model card being out of sync with the active document
|
|
// The DocumentActivated event isn't usable probably because it is pushed to back of main thread queue
|
|
Application.DocumentManager.DocumentActivationEnabled = false;
|
|
|
|
// Get elements to convert
|
|
List<AutocadRootObject> autocadObjects = Application.DocumentManager.CurrentDocument.GetObjects(
|
|
modelCard.SendFilter.NotNull().GetObjectIds()
|
|
);
|
|
|
|
if (autocadObjects.Count == 0)
|
|
{
|
|
// Handle as CARD ERROR in this function
|
|
throw new SpeckleSendFilterException("No objects were found to convert. Please update your publish filter!");
|
|
}
|
|
|
|
var sendInfo = new SendInfo(
|
|
modelCard.AccountId.NotNull(),
|
|
modelCard.ProjectId.NotNull(),
|
|
modelCard.ModelId.NotNull(),
|
|
_autocadSettings.HostAppInfo.Name
|
|
);
|
|
|
|
var sendResult = await uow.Service
|
|
.Execute(
|
|
autocadObjects,
|
|
sendInfo,
|
|
(status, progress) =>
|
|
Commands.SetModelProgress(modelCardId, new ModelCardProgress(modelCardId, status, progress), cts),
|
|
cts.Token
|
|
)
|
|
.ConfigureAwait(false);
|
|
|
|
Commands.SetModelSendResult(modelCardId, sendResult.RootObjId, sendResult.ConversionResults);
|
|
}
|
|
// Catch here specific exceptions if they related to model card.
|
|
catch (OperationCanceledException)
|
|
{
|
|
// SWALLOW -> UI handles it immediately, so we do not need to handle anything
|
|
return;
|
|
}
|
|
catch (SpeckleSendFilterException e)
|
|
{
|
|
Commands.SetModelError(modelCardId, e);
|
|
}
|
|
finally
|
|
{
|
|
// renable document activation
|
|
Application.DocumentManager.DocumentActivationEnabled = true;
|
|
}
|
|
}
|
|
|
|
public void CancelSend(string modelCardId) => _cancellationManager.CancelOperation(modelCardId);
|
|
}
|