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

Dogukan/etabs connector poc #406

Merged
merged 13 commits into from
Nov 28, 2024
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong namespace?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as cPlugin.cs :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also rename this form to "SpeckleForm" or something more descriptive

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This caused us headache 😆 It needs to (for some reason) be named Form1.cs @dogukankaratas and I can't explain yet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is gross


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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this not throw yet?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, not yet


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
Loading