Skip to content

Commit

Permalink
Merge branch 'dev' into dim/type-change-cach-invalidation
Browse files Browse the repository at this point in the history
  • Loading branch information
didimitrie authored Dec 3, 2024
2 parents d75c91a + 792bd93 commit f39d028
Show file tree
Hide file tree
Showing 41 changed files with 1,657 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.CSiShared.Utils;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;

Expand All @@ -8,17 +9,23 @@ 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"
private readonly ICSiApplicationService _csiApplicationService;

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

/// <summary>
/// Gets the selection and creates an encoded ID (objectType and objectName).
/// </summary>
/// <remarks>
/// Refer to ObjectIdentifier.cs for more info.
/// </remarks>
public SelectionInfo GetSelection()
{
// TODO: Handle better. Enums? ObjectType same in ETABS and SAP
// TODO: Since this is standard across CSi Suite - better stored in an enum?
var objectTypeMap = new Dictionary<int, string>
{
{ 1, "Point" },
Expand All @@ -44,8 +51,8 @@ public SelectionInfo GetSelection()
var typeKey = objectType[i];
var typeName = objectTypeMap.TryGetValue(typeKey, out var name) ? name : $"Unknown ({typeKey})";

encodedIds.Add(EncodeObjectIdentifier(typeKey, objectName[i]));
typeCounts[typeName] = (typeCounts.TryGetValue(typeName, out var count) ? count : 0) + 1; // NOTE: Cross-framework compatibility
encodedIds.Add(ObjectIdentifier.Encode(typeKey, objectName[i]));
typeCounts[typeName] = (typeCounts.TryGetValue(typeName, out var count) ? count : 0) + 1; // NOTE: Cross-framework compatibility (net 48 and net8)
}

var summary =
Expand All @@ -56,21 +63,4 @@ public SelectionInfo GetSelection()

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
@@ -1,10 +1,22 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.CSiShared.Utils;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Exceptions;
using Speckle.Connectors.DUI.Logging;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.Settings;
using Speckle.Converters.Common;
using Speckle.Converters.CSiShared;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Logging;

namespace Speckle.Connectors.CSiShared.Bindings;

Expand All @@ -21,6 +33,10 @@ public sealed class CSiSharedSendBinding : ISendBinding
private readonly CancellationManager _cancellationManager;
private readonly IOperationProgressManager _operationProgressManager;
private readonly ILogger<CSiSharedSendBinding> _logger;
private readonly ICSiApplicationService _csiApplicationService;
private readonly ICSiConversionSettingsFactory _csiConversionSettingsFactory;
private readonly ISpeckleApplication _speckleApplication;
private readonly ISdkActivityFactory _activityFactory;

public CSiSharedSendBinding(
DocumentModelStore store,
Expand All @@ -30,7 +46,11 @@ public CSiSharedSendBinding(
IServiceProvider serviceProvider,
CancellationManager cancellationManager,
IOperationProgressManager operationProgressManager,
ILogger<CSiSharedSendBinding> logger
ILogger<CSiSharedSendBinding> logger,
ICSiConversionSettingsFactory csiConversionSettingsFactory,
ISpeckleApplication speckleApplication,
ISdkActivityFactory activityFactory,
ICSiApplicationService csiApplicationService
)
{
_store = store;
Expand All @@ -42,6 +62,10 @@ ILogger<CSiSharedSendBinding> logger
_logger = logger;
Parent = parent;
Commands = new SendBindingUICommands(parent);
_csiConversionSettingsFactory = csiConversionSettingsFactory;
_speckleApplication = speckleApplication;
_activityFactory = activityFactory;
_csiApplicationService = csiApplicationService;
}

public List<ISendFilter> GetSendFilters() => _sendFilters;
Expand All @@ -50,8 +74,61 @@ ILogger<CSiSharedSendBinding> logger

public async Task Send(string modelCardId)
{
// placeholder for actual send implementation
await Task.CompletedTask.ConfigureAwait(false);
using var activity = _activityFactory.Start();

try
{
if (_store.GetModelById(modelCardId) is not SenderModelCard modelCard)
{
throw new InvalidOperationException("No publish model card was found.");
}
using var scope = _serviceProvider.CreateScope();
scope
.ServiceProvider.GetRequiredService<IConverterSettingsStore<CSiConversionSettings>>()
.Initialize(_csiConversionSettingsFactory.Create(_csiApplicationService.SapModel));

CancellationToken cancellationToken = _cancellationManager.InitCancellationTokenSource(modelCardId);

List<ICSiWrapper> wrappers = modelCard
.SendFilter.NotNull()
.RefreshObjectIds()
.Select(DecodeObjectIdentifier)
.ToList();

if (wrappers.Count == 0)
{
throw new SpeckleSendFilterException("No objects were found to convert. Please update your publish filter!");
}

var sendResult = await scope
.ServiceProvider.GetRequiredService<SendOperation<ICSiWrapper>>()
.Execute(
wrappers,
modelCard.GetSendInfo(_speckleApplication.Slug),
_operationProgressManager.CreateOperationProgressEventHandler(Parent, modelCardId, cancellationToken),
cancellationToken
)
.ConfigureAwait(false);

await Commands
.SetModelSendResult(modelCardId, sendResult.RootObjId, sendResult.ConversionResults)
.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
return;
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogModelCardHandledError(ex);
await Commands.SetModelError(modelCardId, ex).ConfigureAwait(false);
}
}

private ICSiWrapper DecodeObjectIdentifier(string encodedId)
{
var (type, name) = ObjectIdentifier.Decode(encodedId);
return CSiWrapperFactory.Create(type, name);
}

public void CancelSend(string modelCardId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace Speckle.Connectors.CSiShared.HostApp;

/// <summary>
/// Create a centralized access point for ETABS and SAP APIs across the entire program.
/// </summary>
/// <remarks>
/// All API methods are based on the objectType and objectName, not the GUID.
/// CSi is already giving us the "sapModel" reference through the plugin interface. No need to attach to running instance.
/// Since objectType is a single int (1, 2 ... 7) we know first index will always be the objectType.
/// Prevent having to pass the "sapModel" around between classes and this ensures consistent access.
/// Name "sapModel" is misleading since it doesn't only apply to SAP2000, but this is the convention in the API, so we keep it.
/// </remarks>
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,79 @@
using System.IO;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Utils;
using Speckle.Sdk;
using Speckle.Sdk.Helpers;
using Speckle.Sdk.Logging;

namespace Speckle.Connectors.CSiShared.HostApp;

public class CSiDocumentModelStore : DocumentModelStore
{
private readonly ISpeckleApplication _speckleApplication;
private readonly ILogger<CSiDocumentModelStore> _logger;
private readonly ICSiApplicationService _csiApplicationService;
private string HostAppUserDataPath { get; set; }
private string DocumentStateFile { get; set; }
private string ModelPathHash { get; set; }

public CSiDocumentModelStore(
IJsonSerializer jsonSerializerSettings,
ISpeckleApplication speckleApplication,
ILogger<CSiDocumentModelStore> logger,
ICSiApplicationService csiApplicationService
)
: base(jsonSerializerSettings)
{
_speckleApplication = speckleApplication;
_logger = logger;
_csiApplicationService = csiApplicationService;
SetPaths();
LoadState();
}

private void SetPaths()
{
ModelPathHash = Crypt.Md5(_csiApplicationService.SapModel.GetModelFilepath(), length: 32);
HostAppUserDataPath = Path.Combine(
SpecklePathProvider.UserSpeckleFolderPath,
"ConnectorsFileData",
_speckleApplication.Slug
);
DocumentStateFile = Path.Combine(HostAppUserDataPath, $"{ModelPathHash}.json");
}

protected override void HostAppSaveState(string modelCardState)
{
try
{
if (!Directory.Exists(HostAppUserDataPath))
{
Directory.CreateDirectory(HostAppUserDataPath);
}
File.WriteAllText(DocumentStateFile, modelCardState);
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogError(ex.Message);
}
}

protected override void LoadState()
{
if (!Directory.Exists(HostAppUserDataPath))
{
ClearAndSave();
return;
}

if (!File.Exists(DocumentStateFile))
{
ClearAndSave();
return;
}

string serializedState = File.ReadAllText(DocumentStateFile);
LoadFromString(serializedState);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

namespace Speckle.Connectors.CSiShared.HostApp;

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

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

protected override void AddEvent()
{
// ETABS specific idle handling can be added here if needed
// TODO: CSi specific idle handling can be added here if needed
_idleCallManager.AppOnIdle(() => { });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Speckle.Converters.Common;
using Speckle.Converters.CSiShared;
using Speckle.Sdk.Models.Collections;

namespace Speckle.Connectors.CSiShared.HostApp;

/// <summary>
/// We can use the CSiWrappers to create our collection structure.
/// </summary>
/// <remarks>
/// This class manages the collections. If the key (from the path) already exists, this collection is returned.
/// If it doesn't exist, a new collection is created and added to the rootObject.
/// </remarks>
public class CSiSendCollectionManager
{
private readonly IConverterSettingsStore<CSiConversionSettings> _converterSettings;
private readonly Dictionary<string, Collection> _collectionCache = new();

public CSiSendCollectionManager(IConverterSettingsStore<CSiConversionSettings> converterSettings)
{
_converterSettings = converterSettings;
}

// TODO: Frames could be further classified under Columns, Braces and Beams. Same for Shells which could be classified into walls, floors
public Collection AddObjectCollectionToRoot(ICSiWrapper csiObject, Collection rootObject)
{
var path = csiObject.GetType().Name.Replace("Wrapper", ""); // CSiJointWrapper → CSiJoint, CSiFrameWrapper → CSiFrame etc.

if (_collectionCache.TryGetValue(path, out Collection? collection))
{
return collection;
}

Collection childCollection = new(path);
rootObject.elements.Add(childCollection);
_collectionCache[path] = childCollection;
return childCollection;
}
}

This file was deleted.

Loading

0 comments on commit f39d028

Please sign in to comment.