Skip to content

Commit

Permalink
Dogukan/etabs connector poc (#406)
Browse files Browse the repository at this point in the history
* dui3 integration

* registers necessary classes

* adds solution to local

* updates packages.lock

* v3 Kick-Off

- Migrated the proof-of-concept to a Shared project
- Some renaming headache
- Use of Speckle.CSI.API NGet package (thanks Jedd)
- Basic selection info works
- Ready for CNX-828 and CNX-835

* Renaming

- Renaming of the solution structure(s) outdated in the Local.sln

* Local.sln Updates

* SDK 3.1.0-dev.200 changes

* s_modality

Code style error

* Remove launchSettings.json from shared

* Removing null supression

---------

Co-authored-by: Björn <[email protected]>
  • Loading branch information
dogukankaratas and bjoernsteinhagen authored Nov 28, 2024
1 parent 85bd017 commit a99083f
Show file tree
Hide file tree
Showing 19 changed files with 886 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Sdk;

namespace Speckle.Connectors.CSiShared.Bindings;

public class CSiSharedBasicConnectorBinding : IBasicConnectorBinding
{
private readonly ISpeckleApplication _speckleApplication;
private readonly DocumentModelStore _store;

public string Name => "baseBinding";
public IBrowserBridge Parent { get; }
public BasicConnectorBindingCommands Commands { get; }

public CSiSharedBasicConnectorBinding(
IBrowserBridge parent,
ISpeckleApplication speckleApplication,
DocumentModelStore store
)
{
Parent = parent;
_speckleApplication = speckleApplication;
_store = store;
Commands = new BasicConnectorBindingCommands(parent);
}

public string GetConnectorVersion() => _speckleApplication.SpeckleVersion;

public string GetSourceApplicationName() => _speckleApplication.Slug;

public string GetSourceApplicationVersion() => _speckleApplication.HostApplicationVersion;

public DocumentInfo? GetDocumentInfo() => new DocumentInfo("ETABS Model", "ETABS Model", "1");

public DocumentModelStore GetDocumentState() => _store;

public void AddModel(ModelCard model) => _store.AddModel(model);

public void UpdateModel(ModelCard model) => _store.UpdateModel(model);

public void RemoveModel(ModelCard model) => _store.RemoveModel(model);

public Task HighlightModel(string modelCardId) => Task.CompletedTask;

public Task HighlightObjects(IReadOnlyList<string> objectIds) => Task.CompletedTask;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;

namespace Speckle.Connectors.CSiShared.Bindings;

public class CSiSharedSelectionBinding : ISelectionBinding
{
public string Name => "selectionBinding";
public IBrowserBridge Parent { get; }
private readonly ICSiApplicationService _csiApplicationService; // Update selection binding to centralized CSiSharedApplicationService instead of trying to maintain a reference to "sapModel"

public CSiSharedSelectionBinding(IBrowserBridge parent, ICSiApplicationService csiApplicationService)
{
Parent = parent;
_csiApplicationService = csiApplicationService;
}

public SelectionInfo GetSelection()
{
// TODO: Handle better. Enums? ObjectType same in ETABS and SAP
var objectTypeMap = new Dictionary<int, string>
{
{ 1, "Point" },
{ 2, "Frame" },
{ 3, "Cable" },
{ 4, "Tendon" },
{ 5, "Area" },
{ 6, "Solid" },
{ 7, "Link" }
};

int numberItems = 0;
int[] objectType = Array.Empty<int>();
string[] objectName = Array.Empty<string>();

_csiApplicationService.SapModel.SelectObj.GetSelected(ref numberItems, ref objectType, ref objectName);

var encodedIds = new List<string>(numberItems);
var typeCounts = new Dictionary<string, int>();

for (int i = 0; i < numberItems; i++)
{
var typeKey = objectType[i];
var typeName = objectTypeMap.TryGetValue(typeKey, out var name) ? name : $"Unknown ({typeKey})";

encodedIds.Add(EncodeObjectIdentifier(typeKey, objectName[i]));
typeCounts[typeName] = typeCounts.GetValueOrDefault(typeName) + 1;
}

var summary =
encodedIds.Count == 0
? "No objects selected."
: $"{encodedIds.Count} objects ({string.Join(", ",
typeCounts.Select(kv => $"{kv.Value} {kv.Key}"))})";

return new SelectionInfo(encodedIds, summary);
}

// NOTE: All API methods are based on the objectType and objectName, not the GUID
// We will obviously manage the GUIDs but for all method calls we need a concatenated version of the objectType and objectName
// Since objectType >= 1 and <= 7, we know first index will always be the objectType
// Remaining string represents objectName and since the user can add any string (provided it is unique), this is safer
// than using a delimiting character (which could clash with user string)
private string EncodeObjectIdentifier(int objectType, string objectName)
{
// Just in case some weird objectType pops up
if (objectType < 1 || objectType > 7)
{
throw new ArgumentException($"Invalid object type: {objectType}. Must be between 1 and 7.");
}

// Simply prepend the object type as a single character
return $"{objectType}{objectName}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.Settings;

namespace Speckle.Connectors.CSiShared.Bindings;

public sealed class CSiSharedSendBinding : ISendBinding
{
public string Name => "sendBinding";
public SendBindingUICommands Commands { get; }
public IBrowserBridge Parent { get; }

private readonly DocumentModelStore _store;
private readonly IAppIdleManager _idleManager;
private readonly IServiceProvider _serviceProvider;
private readonly List<ISendFilter> _sendFilters;
private readonly CancellationManager _cancellationManager;
private readonly IOperationProgressManager _operationProgressManager;
private readonly ILogger<CSiSharedSendBinding> _logger;

public CSiSharedSendBinding(
DocumentModelStore store,
IAppIdleManager idleManager,
IBrowserBridge parent,
IEnumerable<ISendFilter> sendFilters,
IServiceProvider serviceProvider,
CancellationManager cancellationManager,
IOperationProgressManager operationProgressManager,
ILogger<CSiSharedSendBinding> logger
)
{
_store = store;
_idleManager = idleManager;
_serviceProvider = serviceProvider;
_sendFilters = sendFilters.ToList();
_cancellationManager = cancellationManager;
_operationProgressManager = operationProgressManager;
_logger = logger;
Parent = parent;
Commands = new SendBindingUICommands(parent);
}

public List<ISendFilter> GetSendFilters() => _sendFilters;

public List<ICardSetting> GetSendSettings() => [];

public async Task Send(string modelCardId)
{
// placeholder for actual send implementation
await Task.CompletedTask.ConfigureAwait(false);
}

public void CancelSend(string modelCardId)
{
_cancellationManager.CancelOperation(modelCardId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Speckle.Connectors.DUI.Models.Card.SendFilter;

namespace Speckle.Connectors.CSiShared.Filters;

public class CSiSharedSelectionFilter : DirectSelectionSendFilter
{
public CSiSharedSelectionFilter()
{
IsDefault = true;
}

public override List<string> RefreshObjectIds() => SelectedObjectIds;
}
57 changes: 57 additions & 0 deletions Connectors/CSi/Speckle.Connectors.CSiShared/Form1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Windows.Forms.Integration;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common;
using Speckle.Connectors.CSiShared;
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.DUI.WebView;
using Speckle.Sdk.Host;

// NOTE: Plugin entry point must match the assembly name, otherwise hits you with a "Not found" error when loading plugin
// TODO: Move ETABS implementation to csproj as part of CNX-835 and/or CNX-828
namespace Speckle.Connectors.ETABS22;

public class Form1 : Form
{
private ElementHost Host { get; set; }
public static new ServiceProvider? Container { get; set; }
private cSapModel _sapModel;
private cPluginCallback _pluginCallback;

public Form1()
{
this.Text = "Speckle (Beta)";

var services = new ServiceCollection();
services.Initialize(HostApplications.ETABS, GetVersion());
services.AddETABS();

Container = services.BuildServiceProvider();

var webview = Container.GetRequiredService<DUI3ControlWebView>();
Host = new() { Child = webview, Dock = DockStyle.Fill };
Controls.Add(Host);
FormClosing += Form1Closing;
}

public void SetSapModel(ref cSapModel sapModel, ref cPluginCallback pluginCallback)
{
_sapModel = sapModel;
_pluginCallback = pluginCallback;

// NOTE: Update the form to initialize the CSiSharedApplicationService when we receive "sapModel"
// Ensures service ready to use by other components
var csiService = Container.GetRequiredService<ICSiApplicationService>();
csiService.Initialize(sapModel, pluginCallback);
}

public void Form1Closing(object? sender, FormClosingEventArgs e)
{
Host.Dispose();
_pluginCallback.Finish(0);
}

private static HostAppVersion GetVersion()
{
return HostAppVersion.v2022;
}
}
1 change: 1 addition & 0 deletions Connectors/CSi/Speckle.Connectors.CSiShared/GlobalUsing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using CSiAPIv1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace Speckle.Connectors.CSiShared.HostApp;

// NOTE: Create a centralized access point for ETABS and SAP APIs across the entire program
// CSi is already giving us the "sapModel" reference through the plugin interface. No need to attach to running instance
// Prevent having to pass the "sapModel" around between classes and this ensures consistent access
public interface ICSiApplicationService
{
cSapModel SapModel { get; }
void Initialize(cSapModel sapModel, cPluginCallback pluginCallback);
}

public class CSiApplicationService : ICSiApplicationService
{
public cSapModel SapModel { get; private set; }
private cPluginCallback _pluginCallback;

public CSiApplicationService()
{
SapModel = null!;
}

public void Initialize(cSapModel sapModel, cPluginCallback pluginCallback)
{
SapModel = sapModel;
_pluginCallback = pluginCallback;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Utils;

namespace Speckle.Connectors.CSiShared.HostApp;

public class CSiSharedDocumentModelStore : DocumentModelStore
{
public CSiSharedDocumentModelStore(IJsonSerializer jsonSerializerSettings)
: base(jsonSerializerSettings) { }

protected override void HostAppSaveState(string modelCardState) => throw new NotImplementedException();

protected override void LoadState() => throw new NotImplementedException();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Speckle.Connectors.DUI.Bridge;

namespace Speckle.Connectors.CSiShared.HostApp;

public sealed class CSiSharedIdleManager : AppIdleManager
{
private readonly IIdleCallManager _idleCallManager;

public CSiSharedIdleManager(IIdleCallManager idleCallManager)
: base(idleCallManager)
{
_idleCallManager = idleCallManager;
}

protected override void AddEvent()
{
// ETABS specific idle handling can be added here if needed
_idleCallManager.AppOnIdle(() => { });
}
}
46 changes: 46 additions & 0 deletions Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common;
using Speckle.Connectors.CSiShared.Bindings;
using Speckle.Connectors.CSiShared.Filters;
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.DUI;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.WebView;

namespace Speckle.Connectors.CSiShared;

public static class ServiceRegistration
{
// TODO: AddCSi and AddETABS for shared and specific implementations respectively. To do with CNX-828
public static IServiceCollection AddETABS(this IServiceCollection services)
{
services.AddSingleton<IBrowserBridge, BrowserBridge>();
services.AddSingleton<ICSiApplicationService, CSiApplicationService>();

services.AddConnectorUtils();
services.AddDUI<CSiSharedDocumentModelStore>();
services.AddDUIView();

services.AddSingleton<DocumentModelStore, CSiSharedDocumentModelStore>();

services.AddSingleton<IBinding, TestBinding>();
services.AddSingleton<IBinding, ConfigBinding>();
services.AddSingleton<IBinding, AccountBinding>();

services.AddSingleton<IBinding>(sp => sp.GetRequiredService<IBasicConnectorBinding>());
services.AddSingleton<IBasicConnectorBinding, CSiSharedBasicConnectorBinding>();
services.AddSingleton<IAppIdleManager, CSiSharedIdleManager>();

services.AddSingleton<IBinding, CSiSharedSelectionBinding>();
services.AddSingleton<IBinding, CSiSharedSendBinding>();

services.AddScoped<ISendFilter, CSiSharedSelectionFilter>();

services.RegisterTopLevelExceptionHandler();

return services;
}
}
Loading

0 comments on commit a99083f

Please sign in to comment.