Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Event Aggregator with Revit #513

Merged
merged 16 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding
public BasicConnectorBindingCommands Commands { get; }

private readonly DocumentModelStore _store;
private readonly RevitContext _revitContext;
private readonly IRevitContext _revitContext;
private readonly ISpeckleApplication _speckleApplication;

public BasicConnectorBindingRevit(
DocumentModelStore store,
IBrowserBridge parent,
RevitContext revitContext,
IRevitContext revitContext,
ISpeckleApplication speckleApplication,
IEventAggregator eventAggregator
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Converters.RevitShared.Helpers;

namespace Speckle.Connectors.Revit.Bindings;

Expand All @@ -11,14 +9,9 @@ internal abstract class RevitBaseBinding : IBinding
public string Name { get; }
public IBrowserBridge Parent { get; }

protected readonly DocumentModelStore Store;
protected readonly RevitContext RevitContext;

protected RevitBaseBinding(string name, DocumentModelStore store, IBrowserBridge parent, RevitContext revitContext)
protected RevitBaseBinding(string name, IBrowserBridge parent)
{
Name = name;
Parent = parent;
Store = store;
RevitContext = revitContext;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Speckle.Connectors.DUI.Settings;
using Speckle.Connectors.Revit.HostApp;
using Speckle.Connectors.Revit.Operations.Send.Settings;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Connectors.RevitShared.Operations.Send.Filters;
using Speckle.Converters.Common;
using Speckle.Converters.RevitShared.Helpers;
Expand All @@ -28,7 +29,8 @@ namespace Speckle.Connectors.Revit.Bindings;

internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
{
private readonly IAppIdleManager _idleManager;
private readonly IRevitContext _revitContext;
private readonly DocumentModelStore _store;
private readonly CancellationManager _cancellationManager;
private readonly IServiceProvider _serviceProvider;
private readonly ISendConversionCache _sendConversionCache;
Expand All @@ -38,6 +40,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
private readonly ElementUnpacker _elementUnpacker;
private readonly IRevitConversionSettingsFactory _revitConversionSettingsFactory;
private readonly ISpeckleApplication _speckleApplication;
private readonly IEventAggregator _eventAggregator;

/// <summary>
/// Used internally to aggregate the changed objects' id. Note we're using a concurrent dictionary here as the expiry check method is not thread safe, and this was causing problems. See:
Expand All @@ -48,8 +51,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
private ConcurrentDictionary<ElementId, byte> ChangedObjectIds { get; set; } = new();

public RevitSendBinding(
IAppIdleManager idleManager,
RevitContext revitContext,
IRevitContext revitContext,
DocumentModelStore store,
CancellationManager cancellationManager,
IBrowserBridge bridge,
Expand All @@ -61,12 +63,12 @@ public RevitSendBinding(
ElementUnpacker elementUnpacker,
IRevitConversionSettingsFactory revitConversionSettingsFactory,
ISpeckleApplication speckleApplication,
IEventAggregator eventAggregator,
ITopLevelExceptionHandler topLevelExceptionHandler
IEventAggregator eventAggregator
)
: base("sendBinding", store, bridge, revitContext)
: base("sendBinding", bridge)
{
_idleManager = idleManager;
_revitContext = revitContext;
_store = store;
_cancellationManager = cancellationManager;
_serviceProvider = serviceProvider;
_sendConversionCache = sendConversionCache;
Expand All @@ -76,13 +78,13 @@ ITopLevelExceptionHandler topLevelExceptionHandler
_elementUnpacker = elementUnpacker;
_revitConversionSettingsFactory = revitConversionSettingsFactory;
_speckleApplication = speckleApplication;
_eventAggregator = eventAggregator;

Commands = new SendBindingUICommands(bridge);
// TODO expiry events
// TODO filters need refresh events

revitContext.UIApplication.NotNull().Application.DocumentChanged += (_, e) =>
topLevelExceptionHandler.CatchUnhandled(() => DocChangeHandler(e));
eventAggregator.GetEvent<DocumentChangedEvent>().Subscribe(DocChangeHandler);
eventAggregator.GetEvent<DocumentStoreChangedEvent>().Subscribe(OnDocumentStoreChangedEvent);
}

Expand All @@ -91,8 +93,8 @@ ITopLevelExceptionHandler topLevelExceptionHandler
public List<ISendFilter> GetSendFilters() =>
[
new RevitSelectionFilter() { IsDefault = true },
new RevitViewsFilter(RevitContext),
new RevitCategoriesFilter(RevitContext)
new RevitViewsFilter(_revitContext),
new RevitCategoriesFilter(_revitContext)
];

public List<ICardSetting> GetSendSettings() =>
Expand All @@ -111,7 +113,7 @@ public async Task Send(string modelCardId)
// Note: removed top level handling thing as it was confusing me
try
{
if (Store.GetModelById(modelCardId) is not SenderModelCard modelCard)
if (_store.GetModelById(modelCardId) is not SenderModelCard modelCard)
{
// Handle as GLOBAL ERROR at BrowserBridge
throw new InvalidOperationException("No publish model card was found.");
Expand Down Expand Up @@ -171,12 +173,12 @@ public async Task Send(string modelCardId)
private async Task<List<Element>> RefreshElementsOnSender(SenderModelCard modelCard)
{
var activeUIDoc =
RevitContext.UIApplication?.ActiveUIDocument
_revitContext.UIApplication.ActiveUIDocument
?? throw new SpeckleException("Unable to retrieve active UI document");

if (modelCard.SendFilter is IRevitSendFilter viewFilter)
{
viewFilter.SetContext(RevitContext);
viewFilter.SetContext(_revitContext);
}

var selectedObjects = modelCard.SendFilter.NotNull().RefreshObjectIds();
Expand Down Expand Up @@ -247,13 +249,13 @@ private void DocChangeHandler(Autodesk.Revit.DB.Events.DocumentChangedEventArgs

if (addedElementIds.Count > 0)
{
_idleManager.SubscribeToIdle(nameof(PostSetObjectIds), PostSetObjectIds);
_eventAggregator.GetEvent<IdleEvent>().OneTimeSubscribe(nameof(PostSetObjectIds), PostSetObjectIds);
}

if (HaveUnitsChanged(e.GetDocument()))
{
var objectIds = new List<string>();
foreach (var sender in Store.GetSenders().ToList())
foreach (var sender in _store.GetSenders().ToList())
{
if (sender.SendFilter is null)
{
Expand All @@ -267,8 +269,8 @@ private void DocChangeHandler(Autodesk.Revit.DB.Events.DocumentChangedEventArgs
_sendConversionCache.EvictObjects(unpackedObjectIds);
}

_idleManager.SubscribeToIdle(nameof(CheckFilterExpiration), CheckFilterExpiration);
_idleManager.SubscribeToIdle(nameof(RunExpirationChecks), RunExpirationChecks);
_eventAggregator.GetEvent<IdleEvent>().OneTimeSubscribe(nameof(CheckFilterExpiration), CheckFilterExpiration);
_eventAggregator.GetEvent<IdleEvent>().OneTimeSubscribe(nameof(RunExpirationChecks), RunExpirationChecks);
}

// Keeps track of doc and current units
Expand Down Expand Up @@ -305,9 +307,9 @@ private bool HaveUnitsChanged(Document doc)
return false;
}

private async Task PostSetObjectIds()
private async Task PostSetObjectIds(object _)
adamhathcock marked this conversation as resolved.
Show resolved Hide resolved
{
foreach (var sender in Store.GetSenders().ToList())
foreach (var sender in _store.GetSenders().ToList())
{
await RefreshElementsOnSender(sender);
}
Expand All @@ -316,7 +318,7 @@ private async Task PostSetObjectIds()
/// <summary>
/// Notifies ui if any filters need refreshing. Currently, this only applies for view filters.
/// </summary>
private async Task CheckFilterExpiration()
private async Task CheckFilterExpiration(object _)
{
// NOTE: below code seems like more make sense in terms of performance but it causes unmanaged exception on Revit
// using var viewCollector = new FilteredElementCollector(RevitContext.UIApplication?.ActiveUIDocument.Document);
Expand All @@ -327,17 +329,17 @@ private async Task CheckFilterExpiration()
// await Commands.RefreshSendFilters();
// }

if (ChangedObjectIds.Keys.Any(e => RevitContext.UIApplication?.ActiveUIDocument.Document.GetElement(e) is View))
if (ChangedObjectIds.Keys.Any(e => _revitContext.UIApplication.ActiveUIDocument.Document.GetElement(e) is View))
{
await Commands.RefreshSendFilters();
}
}

private async Task RunExpirationChecks()
private async Task RunExpirationChecks(object _)
{
var senders = Store.GetSenders().ToList();
var senders = _store.GetSenders().ToList();
// string[] objectIdsList = ChangedObjectIds.Keys.ToArray();
var doc = RevitContext.UIApplication?.ActiveUIDocument.Document;
var doc = _revitContext.UIApplication.ActiveUIDocument.Document;

if (doc == null)
{
Expand Down Expand Up @@ -388,7 +390,7 @@ private async Task RunExpirationChecks()
{
if (modelCard.SendFilter is IRevitSendFilter viewFilter)
{
viewFilter.SetContext(RevitContext);
viewFilter.SetContext(_revitContext);
}

var selectedObjects = modelCard.SendFilter.NotNull().IdMap.NotNull().Values;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,27 @@
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Eventing;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk.Common;

namespace Speckle.Connectors.Revit.Bindings;

// POC: we need a base a RevitBaseBinding
internal sealed class SelectionBinding : RevitBaseBinding, ISelectionBinding, IDisposable
internal sealed class SelectionBinding : RevitBaseBinding, ISelectionBinding
{
#if REVIT2022
private readonly System.Timers.Timer _selectionTimer;
#endif
private readonly IRevitContext _revitContext;

public SelectionBinding(
RevitContext revitContext,
DocumentModelStore store,
IAppIdleManager revitIdleManager,
ITopLevelExceptionHandler topLevelExceptionHandler,
IBrowserBridge parent
)
: base("selectionBinding", store, parent, revitContext)
public SelectionBinding(IRevitContext revitContext, IBrowserBridge parent, IEventAggregator eventAggregator)
: base("selectionBinding", parent)
{
#if REVIT2022
// NOTE: getting the selection data should be a fast function all, even for '000s of elements - and having a timer hitting it every 1s is ok.
_selectionTimer = new System.Timers.Timer(1000);
_selectionTimer.Elapsed += (_, _) => topLevelExceptionHandler.CatchUnhandled(OnSelectionChanged);
_selectionTimer.Start();
#else

RevitContext.UIApplication.NotNull().SelectionChanged += (_, _) =>
revitIdleManager.SubscribeToIdle(nameof(SelectionBinding), OnSelectionChanged);
#endif
_revitContext = revitContext;
eventAggregator.GetEvent<SelectionChangedEvent>().Subscribe(OnSelectionChanged);
}

private void OnSelectionChanged()
private void OnSelectionChanged(object _)
{
if (RevitContext.UIApplication == null || RevitContext.UIApplication.ActiveUIDocument == null)
if (_revitContext.UIApplication.ActiveUIDocument == null)
{
return;
}
Expand All @@ -45,12 +30,12 @@ private void OnSelectionChanged()

public SelectionInfo GetSelection()
{
if (RevitContext.UIApplication == null || RevitContext.UIApplication.ActiveUIDocument == null)
if (_revitContext.UIApplication.ActiveUIDocument == null)
{
return new SelectionInfo(Array.Empty<string>(), "No objects selected.");
}

var activeUIDoc = RevitContext.UIApplication.ActiveUIDocument.NotNull();
var activeUIDoc = _revitContext.UIApplication.ActiveUIDocument.NotNull();

// POC: this was also being called on shutdown
// probably the bridge needs to be able to know if the plugin has been terminated
Expand All @@ -61,11 +46,4 @@ public SelectionInfo GetSelection()
.ToList();
return new SelectionInfo(selectionIds, $"{selectionIds.Count} objects selected.");
}

public void Dispose()
{
#if REVIT2022
_selectionTimer.Dispose();
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Speckle.Connectors.Revit.Operations.Send.Settings;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Converters.Common;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk.Models.GraphTraversal;

namespace Speckle.Connectors.Revit.DependencyInjection;
Expand All @@ -41,10 +42,10 @@ public static void AddRevit(this IServiceCollection serviceCollection)
serviceCollection.AddSingleton<IBinding, SelectionBinding>();
serviceCollection.AddSingleton<IBinding, RevitSendBinding>();
serviceCollection.AddSingleton<IBinding, RevitReceiveBinding>();
serviceCollection.AddSingleton<IAppIdleManager, RevitIdleManager>();

serviceCollection.AddSingleton<IBinding>(sp => sp.GetRequiredService<IBasicConnectorBinding>());
serviceCollection.AddSingleton<IBasicConnectorBinding, BasicConnectorBindingRevit>();
serviceCollection.AddSingleton<IRevitContext>(sp => sp.GetRequiredService<IRevitPlugin>());

// send operation and dependencies
serviceCollection.AddScoped<SendOperation<ElementId>>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ namespace Speckle.Connectors.Revit.HostApp;
/// </summary>
public class ElementUnpacker
{
private readonly RevitContext _revitContext;
private readonly IRevitContext _revitContext;

public ElementUnpacker(RevitContext revitContext)
public ElementUnpacker(IRevitContext revitContext)
{
_revitContext = revitContext;
}
Expand Down
Loading
Loading