Skip to content

Commit 2cc09d4

Browse files
bjorn/cnx-835-add-converter-projects-and-top-level-converter (#429)
* Initial commit - Project setup and basic definitions - Waiting for SDK update * CSiObjectToSpeckleConverter - Abstract TopLevel converter - Requiring a lot of wrappers and addtional steps to get to converted CSiObject * ICSiWrapper with factory * raw conversion placeholders * service registration * root to speckle * type registration and resolution CSiWrapperBase instead of ICSiWrapper correctly resolves all types * Setting up object level converters * some basic conversions * units framework * raw conversion placeholders these are gross (just a poc for first send) * CollectionManager Simple organization * Comments * back to BASE-ics * local * csharpier Missing blank line * newline * AddObjectCollectionToRoot PR comments: - Updated GetAndCreateObjectHostCollection to more descriptive name of AddObjectCollectionToRoot - Removing unnecessary rootObject = childCollection line * cleaning locks --------- Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
1 parent e777a25 commit 2cc09d4

40 files changed

+1639
-81
lines changed

Connectors/CSi/Speckle.Connectors.CSiShared/Bindings/CSiSharedSelectionBinding.cs

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Speckle.Connectors.CSiShared.HostApp;
2+
using Speckle.Connectors.CSiShared.Utils;
23
using Speckle.Connectors.DUI.Bindings;
34
using Speckle.Connectors.DUI.Bridge;
45

@@ -8,17 +9,23 @@ public class CSiSharedSelectionBinding : ISelectionBinding
89
{
910
public string Name => "selectionBinding";
1011
public IBrowserBridge Parent { get; }
11-
private readonly ICSiApplicationService _csiApplicationService; // Update selection binding to centralized CSiSharedApplicationService instead of trying to maintain a reference to "sapModel"
12+
private readonly ICSiApplicationService _csiApplicationService;
1213

1314
public CSiSharedSelectionBinding(IBrowserBridge parent, ICSiApplicationService csiApplicationService)
1415
{
1516
Parent = parent;
1617
_csiApplicationService = csiApplicationService;
1718
}
1819

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

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

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

5764
return new SelectionInfo(encodedIds, summary);
5865
}
59-
60-
// NOTE: All API methods are based on the objectType and objectName, not the GUID
61-
// We will obviously manage the GUIDs but for all method calls we need a concatenated version of the objectType and objectName
62-
// Since objectType >= 1 and <= 7, we know first index will always be the objectType
63-
// Remaining string represents objectName and since the user can add any string (provided it is unique), this is safer
64-
// than using a delimiting character (which could clash with user string)
65-
private string EncodeObjectIdentifier(int objectType, string objectName)
66-
{
67-
// Just in case some weird objectType pops up
68-
if (objectType < 1 || objectType > 7)
69-
{
70-
throw new ArgumentException($"Invalid object type: {objectType}. Must be between 1 and 7.");
71-
}
72-
73-
// Simply prepend the object type as a single character
74-
return $"{objectType}{objectName}";
75-
}
7666
}

Connectors/CSi/Speckle.Connectors.CSiShared/Bindings/CSiSharedSendBinding.cs

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1-
using Microsoft.Extensions.Logging;
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Logging;
23
using Speckle.Connectors.Common.Cancellation;
4+
using Speckle.Connectors.Common.Operations;
5+
using Speckle.Connectors.CSiShared.HostApp;
6+
using Speckle.Connectors.CSiShared.Utils;
37
using Speckle.Connectors.DUI.Bindings;
48
using Speckle.Connectors.DUI.Bridge;
9+
using Speckle.Connectors.DUI.Exceptions;
10+
using Speckle.Connectors.DUI.Logging;
511
using Speckle.Connectors.DUI.Models;
12+
using Speckle.Connectors.DUI.Models.Card;
613
using Speckle.Connectors.DUI.Models.Card.SendFilter;
714
using Speckle.Connectors.DUI.Settings;
15+
using Speckle.Converters.Common;
16+
using Speckle.Converters.CSiShared;
17+
using Speckle.Sdk;
18+
using Speckle.Sdk.Common;
19+
using Speckle.Sdk.Logging;
820

921
namespace Speckle.Connectors.CSiShared.Bindings;
1022

@@ -21,6 +33,10 @@ public sealed class CSiSharedSendBinding : ISendBinding
2133
private readonly CancellationManager _cancellationManager;
2234
private readonly IOperationProgressManager _operationProgressManager;
2335
private readonly ILogger<CSiSharedSendBinding> _logger;
36+
private readonly ICSiApplicationService _csiApplicationService;
37+
private readonly ICSiConversionSettingsFactory _csiConversionSettingsFactory;
38+
private readonly ISpeckleApplication _speckleApplication;
39+
private readonly ISdkActivityFactory _activityFactory;
2440

2541
public CSiSharedSendBinding(
2642
DocumentModelStore store,
@@ -30,7 +46,11 @@ public CSiSharedSendBinding(
3046
IServiceProvider serviceProvider,
3147
CancellationManager cancellationManager,
3248
IOperationProgressManager operationProgressManager,
33-
ILogger<CSiSharedSendBinding> logger
49+
ILogger<CSiSharedSendBinding> logger,
50+
ICSiConversionSettingsFactory csiConversionSettingsFactory,
51+
ISpeckleApplication speckleApplication,
52+
ISdkActivityFactory activityFactory,
53+
ICSiApplicationService csiApplicationService
3454
)
3555
{
3656
_store = store;
@@ -42,6 +62,10 @@ ILogger<CSiSharedSendBinding> logger
4262
_logger = logger;
4363
Parent = parent;
4464
Commands = new SendBindingUICommands(parent);
65+
_csiConversionSettingsFactory = csiConversionSettingsFactory;
66+
_speckleApplication = speckleApplication;
67+
_activityFactory = activityFactory;
68+
_csiApplicationService = csiApplicationService;
4569
}
4670

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

5175
public async Task Send(string modelCardId)
5276
{
53-
// placeholder for actual send implementation
54-
await Task.CompletedTask.ConfigureAwait(false);
77+
using var activity = _activityFactory.Start();
78+
79+
try
80+
{
81+
if (_store.GetModelById(modelCardId) is not SenderModelCard modelCard)
82+
{
83+
throw new InvalidOperationException("No publish model card was found.");
84+
}
85+
using var scope = _serviceProvider.CreateScope();
86+
scope
87+
.ServiceProvider.GetRequiredService<IConverterSettingsStore<CSiConversionSettings>>()
88+
.Initialize(_csiConversionSettingsFactory.Create(_csiApplicationService.SapModel));
89+
90+
CancellationToken cancellationToken = _cancellationManager.InitCancellationTokenSource(modelCardId);
91+
92+
List<ICSiWrapper> wrappers = modelCard
93+
.SendFilter.NotNull()
94+
.RefreshObjectIds()
95+
.Select(DecodeObjectIdentifier)
96+
.ToList();
97+
98+
if (wrappers.Count == 0)
99+
{
100+
throw new SpeckleSendFilterException("No objects were found to convert. Please update your publish filter!");
101+
}
102+
103+
var sendResult = await scope
104+
.ServiceProvider.GetRequiredService<SendOperation<ICSiWrapper>>()
105+
.Execute(
106+
wrappers,
107+
modelCard.GetSendInfo(_speckleApplication.Slug),
108+
_operationProgressManager.CreateOperationProgressEventHandler(Parent, modelCardId, cancellationToken),
109+
cancellationToken
110+
)
111+
.ConfigureAwait(false);
112+
113+
await Commands
114+
.SetModelSendResult(modelCardId, sendResult.RootObjId, sendResult.ConversionResults)
115+
.ConfigureAwait(false);
116+
}
117+
catch (OperationCanceledException)
118+
{
119+
return;
120+
}
121+
catch (Exception ex) when (!ex.IsFatal())
122+
{
123+
_logger.LogModelCardHandledError(ex);
124+
await Commands.SetModelError(modelCardId, ex).ConfigureAwait(false);
125+
}
126+
}
127+
128+
private ICSiWrapper DecodeObjectIdentifier(string encodedId)
129+
{
130+
var (type, name) = ObjectIdentifier.Decode(encodedId);
131+
return CSiWrapperFactory.Create(type, name);
55132
}
56133

57134
public void CancelSend(string modelCardId)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
namespace Speckle.Connectors.CSiShared.HostApp;
2+
3+
/// <summary>
4+
/// Create a centralized access point for ETABS and SAP APIs across the entire program.
5+
/// </summary>
6+
/// <remarks>
7+
/// All API methods are based on the objectType and objectName, not the GUID.
8+
/// CSi is already giving us the "sapModel" reference through the plugin interface. No need to attach to running instance.
9+
/// Since objectType is a single int (1, 2 ... 7) we know first index will always be the objectType.
10+
/// Prevent having to pass the "sapModel" around between classes and this ensures consistent access.
11+
/// Name "sapModel" is misleading since it doesn't only apply to SAP2000, but this is the convention in the API, so we keep it.
12+
/// </remarks>
13+
public interface ICSiApplicationService
14+
{
15+
cSapModel SapModel { get; }
16+
void Initialize(cSapModel sapModel, cPluginCallback pluginCallback);
17+
}
18+
19+
public class CSiApplicationService : ICSiApplicationService
20+
{
21+
public cSapModel SapModel { get; private set; }
22+
private cPluginCallback _pluginCallback;
23+
24+
public CSiApplicationService()
25+
{
26+
SapModel = null!;
27+
}
28+
29+
public void Initialize(cSapModel sapModel, cPluginCallback pluginCallback)
30+
{
31+
SapModel = sapModel;
32+
_pluginCallback = pluginCallback;
33+
}
34+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
using System.IO;
2+
using Microsoft.Extensions.Logging;
3+
using Speckle.Connectors.DUI.Models;
4+
using Speckle.Connectors.DUI.Utils;
5+
using Speckle.Sdk;
6+
using Speckle.Sdk.Helpers;
7+
using Speckle.Sdk.Logging;
8+
9+
namespace Speckle.Connectors.CSiShared.HostApp;
10+
11+
public class CSiDocumentModelStore : DocumentModelStore
12+
{
13+
private readonly ISpeckleApplication _speckleApplication;
14+
private readonly ILogger<CSiDocumentModelStore> _logger;
15+
private readonly ICSiApplicationService _csiApplicationService;
16+
private string HostAppUserDataPath { get; set; }
17+
private string DocumentStateFile { get; set; }
18+
private string ModelPathHash { get; set; }
19+
20+
public CSiDocumentModelStore(
21+
IJsonSerializer jsonSerializerSettings,
22+
ISpeckleApplication speckleApplication,
23+
ILogger<CSiDocumentModelStore> logger,
24+
ICSiApplicationService csiApplicationService
25+
)
26+
: base(jsonSerializerSettings)
27+
{
28+
_speckleApplication = speckleApplication;
29+
_logger = logger;
30+
_csiApplicationService = csiApplicationService;
31+
SetPaths();
32+
LoadState();
33+
}
34+
35+
private void SetPaths()
36+
{
37+
ModelPathHash = Crypt.Md5(_csiApplicationService.SapModel.GetModelFilepath(), length: 32);
38+
HostAppUserDataPath = Path.Combine(
39+
SpecklePathProvider.UserSpeckleFolderPath,
40+
"ConnectorsFileData",
41+
_speckleApplication.Slug
42+
);
43+
DocumentStateFile = Path.Combine(HostAppUserDataPath, $"{ModelPathHash}.json");
44+
}
45+
46+
protected override void HostAppSaveState(string modelCardState)
47+
{
48+
try
49+
{
50+
if (!Directory.Exists(HostAppUserDataPath))
51+
{
52+
Directory.CreateDirectory(HostAppUserDataPath);
53+
}
54+
File.WriteAllText(DocumentStateFile, modelCardState);
55+
}
56+
catch (Exception ex) when (!ex.IsFatal())
57+
{
58+
_logger.LogError(ex.Message);
59+
}
60+
}
61+
62+
protected override void LoadState()
63+
{
64+
if (!Directory.Exists(HostAppUserDataPath))
65+
{
66+
ClearAndSave();
67+
return;
68+
}
69+
70+
if (!File.Exists(DocumentStateFile))
71+
{
72+
ClearAndSave();
73+
return;
74+
}
75+
76+
string serializedState = File.ReadAllText(DocumentStateFile);
77+
LoadFromString(serializedState);
78+
}
79+
}

Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/CSiSharedIdleManager.cs renamed to Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/CSiIdleManager.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22

33
namespace Speckle.Connectors.CSiShared.HostApp;
44

5-
public sealed class CSiSharedIdleManager : AppIdleManager
5+
public sealed class CSiIdleManager : AppIdleManager
66
{
77
private readonly IIdleCallManager _idleCallManager;
88

9-
public CSiSharedIdleManager(IIdleCallManager idleCallManager)
9+
public CSiIdleManager(IIdleCallManager idleCallManager)
1010
: base(idleCallManager)
1111
{
1212
_idleCallManager = idleCallManager;
1313
}
1414

1515
protected override void AddEvent()
1616
{
17-
// ETABS specific idle handling can be added here if needed
17+
// TODO: CSi specific idle handling can be added here if needed
1818
_idleCallManager.AppOnIdle(() => { });
1919
}
2020
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Speckle.Converters.Common;
2+
using Speckle.Converters.CSiShared;
3+
using Speckle.Sdk.Models.Collections;
4+
5+
namespace Speckle.Connectors.CSiShared.HostApp;
6+
7+
/// <summary>
8+
/// We can use the CSiWrappers to create our collection structure.
9+
/// </summary>
10+
/// <remarks>
11+
/// This class manages the collections. If the key (from the path) already exists, this collection is returned.
12+
/// If it doesn't exist, a new collection is created and added to the rootObject.
13+
/// </remarks>
14+
public class CSiSendCollectionManager
15+
{
16+
private readonly IConverterSettingsStore<CSiConversionSettings> _converterSettings;
17+
private readonly Dictionary<string, Collection> _collectionCache = new();
18+
19+
public CSiSendCollectionManager(IConverterSettingsStore<CSiConversionSettings> converterSettings)
20+
{
21+
_converterSettings = converterSettings;
22+
}
23+
24+
// TODO: Frames could be further classified under Columns, Braces and Beams. Same for Shells which could be classified into walls, floors
25+
public Collection AddObjectCollectionToRoot(ICSiWrapper csiObject, Collection rootObject)
26+
{
27+
var path = csiObject.GetType().Name.Replace("Wrapper", ""); // CSiJointWrapper → CSiJoint, CSiFrameWrapper → CSiFrame etc.
28+
29+
if (_collectionCache.TryGetValue(path, out Collection? collection))
30+
{
31+
return collection;
32+
}
33+
34+
Collection childCollection = new(path);
35+
rootObject.elements.Add(childCollection);
36+
_collectionCache[path] = childCollection;
37+
return childCollection;
38+
}
39+
}

Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/CSiSharedApplicationService.cs

Lines changed: 0 additions & 27 deletions
This file was deleted.

0 commit comments

Comments
 (0)