Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 656ed709f3 | |||
| 58c6370cda | |||
| 0e72adba36 | |||
| d5084dc334 | |||
| 2c13c4ff79 |
+13
-55
@@ -20,23 +20,8 @@ public class CsiFrameSectionPropertyExtractor : IFrameSectionPropertyExtractor
|
||||
_settingsStore = settingsStore;
|
||||
}
|
||||
|
||||
public void ExtractProperties(string sectionName, Dictionary<string, object?> properties)
|
||||
{
|
||||
GetMaterialName(sectionName, properties);
|
||||
public void ExtractProperties(string sectionName, Dictionary<string, object?> properties) =>
|
||||
GetSectionProperties(sectionName, properties);
|
||||
GetPropertyModifiers(sectionName, properties);
|
||||
}
|
||||
|
||||
private void GetMaterialName(string sectionName, Dictionary<string, object?> properties)
|
||||
{
|
||||
// get material name
|
||||
string materialName = string.Empty;
|
||||
_settingsStore.Current.SapModel.PropFrame.GetMaterial(sectionName, ref materialName);
|
||||
|
||||
// append to General Data of properties dictionary
|
||||
Dictionary<string, object?> generalData = properties.EnsureNested(SectionPropertyCategory.GENERAL_DATA);
|
||||
generalData["Material"] = materialName;
|
||||
}
|
||||
|
||||
private void GetSectionProperties(string sectionName, Dictionary<string, object?> properties)
|
||||
{
|
||||
@@ -69,47 +54,20 @@ public class CsiFrameSectionPropertyExtractor : IFrameSectionPropertyExtractor
|
||||
ref radiusOfGyrationAboutMinorAxis
|
||||
);
|
||||
|
||||
string distanceUnit = _settingsStore.Current.SpeckleUnits;
|
||||
string areaUnit = $"{distanceUnit}²"; // // TODO: Formalize this better
|
||||
string modulusUnit = $"{distanceUnit}³"; // // TODO: Formalize this better
|
||||
string inertiaUnit = $"{distanceUnit}\u2074"; // TODO: Formalize this better
|
||||
|
||||
Dictionary<string, object?> mechanicalProperties = properties.EnsureNested(
|
||||
SectionPropertyCategory.SECTION_PROPERTIES
|
||||
);
|
||||
mechanicalProperties.AddWithUnits("Area", crossSectionalArea, areaUnit);
|
||||
mechanicalProperties.AddWithUnits("As2", shearAreaInMajorAxisDirection, areaUnit);
|
||||
mechanicalProperties.AddWithUnits("As3", shearAreaInMinorAxisDirection, areaUnit);
|
||||
mechanicalProperties.AddWithUnits("J", torsionalConstant, inertiaUnit);
|
||||
mechanicalProperties.AddWithUnits("I22", momentOfInertiaAboutMajorAxis, inertiaUnit);
|
||||
mechanicalProperties.AddWithUnits("I33", momentOfInertiaAboutMinorAxis, inertiaUnit);
|
||||
mechanicalProperties.AddWithUnits("S22", sectionModulusAboutMajorAxis, modulusUnit);
|
||||
mechanicalProperties.AddWithUnits("S33", sectionModulusAboutMinorAxis, modulusUnit);
|
||||
mechanicalProperties.AddWithUnits("Z22", plasticModulusAboutMajorAxis, modulusUnit);
|
||||
mechanicalProperties.AddWithUnits("Z33", plasticModulusAboutMinorAxis, modulusUnit);
|
||||
mechanicalProperties.AddWithUnits("R22", radiusOfGyrationAboutMajorAxis, distanceUnit);
|
||||
mechanicalProperties.AddWithUnits("R33", radiusOfGyrationAboutMinorAxis, distanceUnit);
|
||||
}
|
||||
|
||||
private void GetPropertyModifiers(string sectionName, Dictionary<string, object?> properties)
|
||||
{
|
||||
double[] stiffnessModifiersArray = [];
|
||||
_settingsStore.Current.SapModel.PropFrame.GetModifiers(sectionName, ref stiffnessModifiersArray);
|
||||
|
||||
Dictionary<string, object?> modifiers =
|
||||
new()
|
||||
{
|
||||
["Cross-section (Axial) Area"] = stiffnessModifiersArray[0],
|
||||
["Shear Area in 2 Direction"] = stiffnessModifiersArray[1],
|
||||
["Shear Area in 3 Direction"] = stiffnessModifiersArray[2],
|
||||
["Torsional Constant"] = stiffnessModifiersArray[3],
|
||||
["Moment of Inertia about 2 Axis"] = stiffnessModifiersArray[4],
|
||||
["Moment of Inertia about 3 Axis"] = stiffnessModifiersArray[5],
|
||||
["Mass"] = stiffnessModifiersArray[6],
|
||||
["Weight"] = stiffnessModifiersArray[7],
|
||||
};
|
||||
|
||||
Dictionary<string, object?> generalData = properties.EnsureNested(SectionPropertyCategory.GENERAL_DATA);
|
||||
generalData["Modifiers"] = modifiers;
|
||||
mechanicalProperties.Add("Area", crossSectionalArea);
|
||||
mechanicalProperties.Add("As2", shearAreaInMajorAxisDirection);
|
||||
mechanicalProperties.Add("As3", shearAreaInMinorAxisDirection);
|
||||
mechanicalProperties.Add("J", torsionalConstant);
|
||||
mechanicalProperties.Add("I22", momentOfInertiaAboutMajorAxis);
|
||||
mechanicalProperties.Add("I33", momentOfInertiaAboutMinorAxis);
|
||||
mechanicalProperties.Add("S22", sectionModulusAboutMajorAxis);
|
||||
mechanicalProperties.Add("S33", sectionModulusAboutMinorAxis);
|
||||
mechanicalProperties.Add("Z22", plasticModulusAboutMajorAxis);
|
||||
mechanicalProperties.Add("Z33", plasticModulusAboutMinorAxis);
|
||||
mechanicalProperties.Add("R22", radiusOfGyrationAboutMajorAxis);
|
||||
mechanicalProperties.Add("R33", radiusOfGyrationAboutMinorAxis);
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -23,6 +23,7 @@ public class CsiResultsExtractorFactory
|
||||
ResultsKey.PIER_FORCES => _serviceProvider.GetRequiredService<CsiPierForceResultsExtractor>(),
|
||||
ResultsKey.SPANDREL_FORCES => _serviceProvider.GetRequiredService<CsiSpandrelForceResultsExtractor>(),
|
||||
ResultsKey.STORY_DRIFTS => _serviceProvider.GetRequiredService<CsiStoryDriftsResultsExtractor>(),
|
||||
ResultsKey.STORY_FORCES => _serviceProvider.GetRequiredService<CsiStoryForceResultsExtractor>(),
|
||||
_ => throw new InvalidOperationException($"{resultsKey} not accounted for in CsiResultsExtractorFactory")
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,18 +47,17 @@ public class EtabsSectionUnpacker : ISectionUnpacker
|
||||
string sectionName = entry.Key;
|
||||
List<string> frameIds = entry.Value;
|
||||
|
||||
// Initialize properties outside the if statement
|
||||
Dictionary<string, object?> properties = new Dictionary<string, object?>();
|
||||
// initialize properties
|
||||
Dictionary<string, object?> properties = [];
|
||||
|
||||
// get the properties of the section
|
||||
// openings will have objects assigned to them, but won't have properties
|
||||
// sectionName is initialized with string.Empty, but api ref returns string "None"
|
||||
if (sectionName != "None")
|
||||
// Extract properties if valid section name
|
||||
// "None" is weird but api returns that string if an opening, null element etc.
|
||||
if (sectionName != "None" && !string.IsNullOrEmpty(sectionName))
|
||||
{
|
||||
properties = _propertyExtractor.ExtractFrameSectionProperties(sectionName);
|
||||
}
|
||||
|
||||
// create the section proxy
|
||||
// create section proxy
|
||||
GroupProxy sectionProxy =
|
||||
new()
|
||||
{
|
||||
@@ -66,8 +65,8 @@ public class EtabsSectionUnpacker : ISectionUnpacker
|
||||
name = sectionName,
|
||||
applicationId = sectionName,
|
||||
objects = frameIds,
|
||||
["type"] = "Frame Section", // since sectionProxies are a flat list, need some way to distinguish from shell
|
||||
["properties"] = properties // openings will just have an empty dict here
|
||||
["type"] = "Frame Section",
|
||||
["properties"] = properties
|
||||
};
|
||||
|
||||
yield return sectionProxy;
|
||||
@@ -81,8 +80,8 @@ public class EtabsSectionUnpacker : ISectionUnpacker
|
||||
string sectionName = entry.Key;
|
||||
List<string> frameIds = entry.Value;
|
||||
|
||||
// Initialize properties outside the if statement
|
||||
Dictionary<string, object?> properties = new Dictionary<string, object?>();
|
||||
// initialize properties outside the if statement
|
||||
Dictionary<string, object?> properties = [];
|
||||
|
||||
// get the properties of the section
|
||||
// openings will have objects assigned to them, but won't have properties
|
||||
@@ -92,7 +91,7 @@ public class EtabsSectionUnpacker : ISectionUnpacker
|
||||
properties = _propertyExtractor.ExtractShellSectionProperties(sectionName);
|
||||
}
|
||||
|
||||
// create the section proxy
|
||||
// create section proxy
|
||||
GroupProxy sectionProxy =
|
||||
new()
|
||||
{
|
||||
|
||||
+75
-47
@@ -1,4 +1,5 @@
|
||||
using Speckle.Connectors.CSiShared.HostApp.Helpers;
|
||||
using Speckle.Connectors.ETABSShared.HostApp.Services;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.CSiShared;
|
||||
using Speckle.Converters.CSiShared.Utils;
|
||||
@@ -8,69 +9,96 @@ namespace Speckle.Connectors.ETABSShared.HostApp.Helpers;
|
||||
/// <summary>
|
||||
/// Extracts ETABS-specific frame section properties.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The bulk loading strategy is necessary here because we can't know which database table contains which section
|
||||
/// beforehand - there are multiple tables like "Frame Section Property Definitions - Steel",
|
||||
/// "Frame Section Property Definitions - Concrete", etc.
|
||||
/// </remarks>
|
||||
public class EtabsFrameSectionPropertyExtractor : IApplicationFrameSectionPropertyExtractor
|
||||
{
|
||||
private readonly IConverterSettingsStore<CsiConversionSettings> _settingsStore;
|
||||
private readonly EtabsSectionPropertyDefinitionService _definitionService;
|
||||
|
||||
public EtabsFrameSectionPropertyExtractor(IConverterSettingsStore<CsiConversionSettings> settingsStore)
|
||||
public EtabsFrameSectionPropertyExtractor(
|
||||
IConverterSettingsStore<CsiConversionSettings> settingsStore,
|
||||
EtabsSectionPropertyDefinitionService definitionService
|
||||
)
|
||||
{
|
||||
_settingsStore = settingsStore;
|
||||
_definitionService = definitionService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets generalised frame section properties
|
||||
/// Gets frame section properties from preloaded database table data
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sap2000 doesn't support this method, unfortunately
|
||||
/// Alternative is to account for extraction according to section type - we're talking over 40 section types!
|
||||
/// This way, we get basic information with minimal computational costs.
|
||||
/// Property categorization is done heuristically - order matters in the parsing logic.
|
||||
/// </remarks>
|
||||
public void ExtractProperties(string sectionName, Dictionary<string, object?> properties)
|
||||
{
|
||||
// Get all frame properties
|
||||
int numberOfNames = 0;
|
||||
string[] names = [];
|
||||
eFramePropType[] propTypes = [];
|
||||
double[] t3 = [],
|
||||
t2 = [],
|
||||
tf = [],
|
||||
tw = [],
|
||||
t2b = [],
|
||||
tfb = [],
|
||||
area = [];
|
||||
|
||||
_settingsStore.Current.SapModel.PropFrame.GetAllFrameProperties_2(
|
||||
ref numberOfNames,
|
||||
ref names,
|
||||
ref propTypes,
|
||||
ref t3,
|
||||
ref t2,
|
||||
ref tf,
|
||||
ref tw,
|
||||
ref t2b,
|
||||
ref tfb,
|
||||
ref area
|
||||
);
|
||||
|
||||
// Find the index of the current section
|
||||
int sectionIndex = Array.IndexOf(names, sectionName);
|
||||
|
||||
if (sectionIndex != -1)
|
||||
// get frame definitions from the service (which uses database table extraction)
|
||||
// this is a fast dictionary lookup since all data is preloaded
|
||||
if (!_definitionService.FrameDefinitions.TryGetValue(sectionName, out var rawDatabaseTableProperties))
|
||||
{
|
||||
// General Data
|
||||
var generalData = properties.EnsureNested(SectionPropertyCategory.GENERAL_DATA);
|
||||
generalData["Section Shape"] = propTypes[sectionIndex].ToString();
|
||||
return; // no definitions found for this section
|
||||
}
|
||||
|
||||
// Section Dimensions
|
||||
string unit = _settingsStore.Current.SpeckleUnits;
|
||||
var sectionDimensions = properties.EnsureNested(SectionPropertyCategory.SECTION_DIMENSIONS);
|
||||
sectionDimensions.AddWithUnits("t3", t3[sectionIndex], unit);
|
||||
sectionDimensions.AddWithUnits("t2", t2[sectionIndex], unit);
|
||||
sectionDimensions.AddWithUnits("tf", tf[sectionIndex], unit);
|
||||
sectionDimensions.AddWithUnits("tw", tw[sectionIndex], unit);
|
||||
sectionDimensions.AddWithUnits("t2b", t2b[sectionIndex], unit);
|
||||
sectionDimensions.AddWithUnits("tfb", tfb[sectionIndex], unit);
|
||||
sectionDimensions.AddWithUnits("Area", area[sectionIndex], $"{unit}²");
|
||||
// define table keys that we don't want to include in the section proxy properties
|
||||
var keysToExclude = new HashSet<string>
|
||||
{
|
||||
"GUID",
|
||||
"Name",
|
||||
"Color",
|
||||
"Notes",
|
||||
"FileName",
|
||||
"FromFile",
|
||||
"SectInFile",
|
||||
"NotAutoFact"
|
||||
};
|
||||
|
||||
// get the section type / shape using the dedicated api query (exception to the database approach)
|
||||
// this specific property isn't available in the database table extraction
|
||||
eFramePropType framePropType = 0;
|
||||
_settingsStore.Current.SapModel.PropFrame.GetTypeOAPI(sectionName, ref framePropType);
|
||||
Dictionary<string, object?> generalProperties = properties.EnsureNested(SectionPropertyCategory.GENERAL_DATA);
|
||||
generalProperties.Add("Section Shape", framePropType.ToString());
|
||||
|
||||
// heuristic property categorization based on key patterns and parse-ability
|
||||
// NOTE: this is gross and quite dangerous 🤨 but beats specific frame prop sect. property extractions imo
|
||||
// order matters here! we check for known string props first, then modifiers, then assume doubles are dimensions
|
||||
foreach (KeyValuePair<string, string> rawDatabaseTableProperty in rawDatabaseTableProperties)
|
||||
{
|
||||
string key = rawDatabaseTableProperty.Key;
|
||||
string value = rawDatabaseTableProperty.Value;
|
||||
|
||||
// skip metadata fields we don't care about
|
||||
if (!keysToExclude.Contains(key))
|
||||
{
|
||||
// material is always a string, grab it first
|
||||
if (key == "Material")
|
||||
{
|
||||
generalProperties.Add(key, value);
|
||||
}
|
||||
// modifier properties end with "Mod" and should be numeric
|
||||
else if (key.EndsWith("Mod") && double.TryParse(value, out double parsedModValue))
|
||||
{
|
||||
Dictionary<string, object?> modificationProperties = properties.EnsureNested(
|
||||
SectionPropertyCategory.MODIFIERS
|
||||
);
|
||||
modificationProperties.Add(key, parsedModValue);
|
||||
}
|
||||
// anything else that parses as a double is assumed to be a section dimension
|
||||
// this covers things like t3, t2, tf, tw, area, etc. without having to enumerate them all
|
||||
else if (double.TryParse(value, out double parsedDimensionValue))
|
||||
{
|
||||
Dictionary<string, object?> sectionDimensions = properties.EnsureNested(
|
||||
SectionPropertyCategory.SECTION_DIMENSIONS
|
||||
);
|
||||
sectionDimensions.Add(key, parsedDimensionValue);
|
||||
}
|
||||
// if it doesn't parse as double and isn't a known string property, we skip it
|
||||
// this is acceptable - we'd rather miss some edge case properties than crash
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.CSiShared;
|
||||
using Speckle.Converters.CSiShared.ToSpeckle.Helpers;
|
||||
|
||||
namespace Speckle.Connectors.ETABSShared.HostApp.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Loads and caches section property definitions from database tables for both frame and shell sections.
|
||||
/// </summary>
|
||||
public class EtabsSectionPropertyDefinitionService
|
||||
{
|
||||
private readonly IConverterSettingsStore<CsiConversionSettings> _settingsStore;
|
||||
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> FrameDefinitions { get; }
|
||||
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> ShellDefinitions { get; }
|
||||
|
||||
public EtabsSectionPropertyDefinitionService(
|
||||
DatabaseTableExtractor databaseTableExtractor,
|
||||
IConverterSettingsStore<CsiConversionSettings> settingsStore
|
||||
)
|
||||
{
|
||||
_settingsStore = settingsStore;
|
||||
|
||||
var availableTableKeys = GetAvailableTableKeys();
|
||||
|
||||
FrameDefinitions = LoadFrameDefinitions(databaseTableExtractor, availableTableKeys);
|
||||
ShellDefinitions = LoadShellDefinitions(databaseTableExtractor, availableTableKeys);
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> LoadFrameDefinitions(
|
||||
DatabaseTableExtractor databaseTableExtractor,
|
||||
string[] availableTableKeys
|
||||
)
|
||||
{
|
||||
var frameTableKeys = GetFrameSectionPropertyDefinitionTableKeys(availableTableKeys);
|
||||
return LoadDefinitionsFromTables(databaseTableExtractor, frameTableKeys);
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> LoadShellDefinitions(
|
||||
DatabaseTableExtractor databaseTableExtractor,
|
||||
string[] availableTableKeys
|
||||
)
|
||||
{
|
||||
var shellTableKeys = GetShellSectionPropertyDefinitionTableKeys(availableTableKeys);
|
||||
return LoadDefinitionsFromTables(databaseTableExtractor, shellTableKeys);
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> LoadDefinitionsFromTables(
|
||||
DatabaseTableExtractor databaseTableExtractor,
|
||||
IEnumerable<string> tableKeys
|
||||
)
|
||||
{
|
||||
var definitions = new Dictionary<string, IReadOnlyDictionary<string, string>>();
|
||||
|
||||
foreach (string tableKey in tableKeys)
|
||||
{
|
||||
var tableData = databaseTableExtractor.GetTableData(tableKey, "Name");
|
||||
foreach (var row in tableData.Rows)
|
||||
{
|
||||
definitions[row.Key] = row.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetFrameSectionPropertyDefinitionTableKeys(string[] availableTableKeys)
|
||||
{
|
||||
var keysToExclude = new HashSet<string>
|
||||
{
|
||||
"Frame Section Property Definitions - Summary",
|
||||
"Frame Section Property Definitions - Concrete Beam Reinforcing",
|
||||
"Frame Section Property Definitions - Concrete Column Reinforcing"
|
||||
};
|
||||
|
||||
return availableTableKeys.Where(key =>
|
||||
key.StartsWith("Frame Section Property Definitions") && !keysToExclude.Contains(key)
|
||||
);
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetShellSectionPropertyDefinitionTableKeys(string[] availableTableKeys)
|
||||
{
|
||||
var keysToExclude = new HashSet<string> { "Area Section Property Definitions - Summary" };
|
||||
|
||||
return availableTableKeys.Where(key =>
|
||||
key.StartsWith("Area Section Property Definitions") && !keysToExclude.Contains(key)
|
||||
);
|
||||
}
|
||||
|
||||
private string[] GetAvailableTableKeys()
|
||||
{
|
||||
int numberTables = 0;
|
||||
string[] tableKey = [],
|
||||
tableName = [];
|
||||
int[] importType = [];
|
||||
|
||||
_ = _settingsStore.Current.SapModel.DatabaseTables.GetAvailableTables(
|
||||
ref numberTables,
|
||||
ref tableKey,
|
||||
ref tableName,
|
||||
ref importType
|
||||
);
|
||||
|
||||
return tableKey;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -34,7 +34,7 @@ public class EtabsSectionPropertyExtractor
|
||||
/// </summary>
|
||||
public Dictionary<string, object?> ExtractFrameSectionProperties(string sectionName)
|
||||
{
|
||||
Dictionary<string, object?> properties = new();
|
||||
Dictionary<string, object?> properties = [];
|
||||
_csiFrameExtractor.ExtractProperties(sectionName, properties);
|
||||
_etabsFrameExtractor.ExtractProperties(sectionName, properties);
|
||||
return properties;
|
||||
@@ -45,7 +45,7 @@ public class EtabsSectionPropertyExtractor
|
||||
/// </summary>
|
||||
public Dictionary<string, object?> ExtractShellSectionProperties(string sectionName)
|
||||
{
|
||||
Dictionary<string, object?> properties = new();
|
||||
Dictionary<string, object?> properties = [];
|
||||
_csiShellExtractor.ExtractProperties(sectionName, properties);
|
||||
_etabsShellExtractor.ExtractProperties(sectionName, properties);
|
||||
return properties;
|
||||
|
||||
@@ -3,6 +3,7 @@ using Speckle.Connectors.CSiShared.HostApp;
|
||||
using Speckle.Connectors.CSiShared.HostApp.Helpers;
|
||||
using Speckle.Connectors.ETABSShared.HostApp;
|
||||
using Speckle.Connectors.ETABSShared.HostApp.Helpers;
|
||||
using Speckle.Connectors.ETABSShared.HostApp.Services;
|
||||
using Speckle.Converters.ETABSShared;
|
||||
|
||||
namespace Speckle.Connectors.ETABSShared;
|
||||
@@ -12,11 +13,12 @@ public static class ServiceRegistration
|
||||
public static IServiceCollection AddEtabs(this IServiceCollection services)
|
||||
{
|
||||
services.AddEtabsConverters();
|
||||
services.AddScoped<IApplicationFrameSectionPropertyExtractor, EtabsFrameSectionPropertyExtractor>();
|
||||
services.AddScoped<IApplicationShellSectionPropertyExtractor, EtabsShellSectionPropertyExtractor>();
|
||||
services.AddScoped<CsiSendCollectionManager, EtabsSendCollectionManager>();
|
||||
services.AddScoped<EtabsSectionPropertyDefinitionService>();
|
||||
services.AddScoped<EtabsSectionPropertyExtractor>();
|
||||
services.AddScoped<EtabsShellSectionResolver>();
|
||||
services.AddScoped<CsiSendCollectionManager, EtabsSendCollectionManager>();
|
||||
services.AddScoped<IApplicationFrameSectionPropertyExtractor, EtabsFrameSectionPropertyExtractor>();
|
||||
services.AddScoped<IApplicationShellSectionPropertyExtractor, EtabsShellSectionPropertyExtractor>();
|
||||
services.AddScoped<ISectionUnpacker, EtabsSectionUnpacker>();
|
||||
|
||||
return services;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\EtabsSectionUnpacker.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\EtabsSendCollectionManager.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\EtabsFrameSectionPropertyExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\EtabsSectionPropertyDefinitionService.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\EtabsSectionPropertyExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\EtabsShellSectionPropertyExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\EtabsShellSectionResolver.cs" />
|
||||
|
||||
@@ -32,6 +32,7 @@ public static class ServiceRegistration
|
||||
serviceCollection.AddScoped<CsiPierForceResultsExtractor>();
|
||||
serviceCollection.AddScoped<CsiSpandrelForceResultsExtractor>();
|
||||
serviceCollection.AddScoped<CsiStoryDriftsResultsExtractor>();
|
||||
serviceCollection.AddScoped<CsiStoryForceResultsExtractor>();
|
||||
serviceCollection.AddScoped<ResultsArrayProcessor>();
|
||||
|
||||
// Register connector caches
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Helpers\CsiShellPropertiesExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Helpers\CsiSpandrelForceResultsExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Helpers\CsiStoryDriftsResultsExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Helpers\CsiStoryForceResultsExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Helpers\CsiToSpeckleCacheSingleton.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Helpers\DatabaseTableExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Helpers\IApplicationResultsExtractor.cs" />
|
||||
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.CSiShared.Utils;
|
||||
|
||||
namespace Speckle.Converters.CSiShared.ToSpeckle.Helpers;
|
||||
|
||||
public class CsiStoryForceResultsExtractor : IApplicationResultsExtractor
|
||||
{
|
||||
private readonly IConverterSettingsStore<CsiConversionSettings> _settingsStore;
|
||||
private readonly DatabaseTableExtractor _databaseTableExtractor;
|
||||
private readonly ResultsArrayProcessor _resultsArrayProcessor;
|
||||
|
||||
private const string AXIAL_FORCE = "P";
|
||||
private const string LOAD_CASE = "LoadCase";
|
||||
private const string LOCATION = "Location";
|
||||
private const string MAJOR_MOMENT = "MX";
|
||||
private const string MAJOR_SHEAR = "VX";
|
||||
private const string MINOR_MOMENT = "MY";
|
||||
private const string MINOR_SHEAR = "VY";
|
||||
private const string OUTPUT_CASE = "OutputCase";
|
||||
private const string STORY = "Story";
|
||||
private const string STORY_FORCES = "Story Forces";
|
||||
private const string TORSION = "T";
|
||||
|
||||
public string ResultsKey => "storyForces";
|
||||
public ModelObjectType TargetObjectType => ModelObjectType.NONE;
|
||||
public ResultsConfiguration Configuration { get; } =
|
||||
new([STORY, LOAD_CASE, LOCATION], [AXIAL_FORCE, MAJOR_SHEAR, MINOR_SHEAR, TORSION, MAJOR_MOMENT, MINOR_MOMENT]);
|
||||
|
||||
public CsiStoryForceResultsExtractor(
|
||||
IConverterSettingsStore<CsiConversionSettings> settingsStore,
|
||||
DatabaseTableExtractor databaseTableExtractor,
|
||||
ResultsArrayProcessor resultsArrayProcessor
|
||||
)
|
||||
{
|
||||
_settingsStore = settingsStore;
|
||||
_databaseTableExtractor = databaseTableExtractor;
|
||||
_resultsArrayProcessor = resultsArrayProcessor;
|
||||
}
|
||||
|
||||
// NOTE: these aren't object specific, they're independent of the user selection, therefore discared
|
||||
public Dictionary<string, object> GetResults(IEnumerable<string>? objectNames = null)
|
||||
{
|
||||
// Step 1: use DatabaseTableExtractor to get results
|
||||
// NOTE: this differs from other results since Story Forces doesn't have a SapModel.Results.StoryForces method
|
||||
var tableData = _databaseTableExtractor
|
||||
.GetTableData(STORY_FORCES, STORY, additionalKeyColumns: [OUTPUT_CASE, LOCATION])
|
||||
.Rows;
|
||||
|
||||
// Get user selected load cases and combinations for filtering
|
||||
var userSelectedLoadCases = _settingsStore.Current.SelectedLoadCasesAndCombinations?.ToHashSet();
|
||||
|
||||
if (userSelectedLoadCases == null)
|
||||
{
|
||||
// NOTE: this should never happen as we validate in root object builder
|
||||
throw new InvalidOperationException("No load cases or combinations selected");
|
||||
}
|
||||
|
||||
// Step 2: Filter out entries that don't match user's selected load cases/combinations
|
||||
// and organize arrays for dictionary processor
|
||||
var filteredEntries = tableData
|
||||
.Where(entry => userSelectedLoadCases.Count == 0 || userSelectedLoadCases.Contains(GetOutputCase(entry.Value)))
|
||||
.ToList();
|
||||
|
||||
if (filteredEntries.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"No load cases or combinations in database match user-selected load cases and combinations"
|
||||
); // shouldn't fail silently
|
||||
}
|
||||
|
||||
// Step 3: Extract arrays from the nested dictionary structure
|
||||
var stories = new string[filteredEntries.Count];
|
||||
var loadCases = new string[filteredEntries.Count];
|
||||
var locations = new string[filteredEntries.Count];
|
||||
var pValues = new double[filteredEntries.Count];
|
||||
var vxValues = new double[filteredEntries.Count];
|
||||
var vyValues = new double[filteredEntries.Count];
|
||||
var tValues = new double[filteredEntries.Count];
|
||||
var mxValues = new double[filteredEntries.Count];
|
||||
var myValues = new double[filteredEntries.Count];
|
||||
|
||||
for (int i = 0; i < filteredEntries.Count; i++)
|
||||
{
|
||||
var entry = filteredEntries[i];
|
||||
var nestedDict = entry.Value;
|
||||
|
||||
// Extract Story, OutputCase, and Location directly from the nested dictionary
|
||||
if (!nestedDict.TryGetValue(STORY, out var story) || string.IsNullOrEmpty(story))
|
||||
{
|
||||
throw new InvalidOperationException($"Missing or empty 'Story' column in database row {i}");
|
||||
}
|
||||
stories[i] = story;
|
||||
loadCases[i] = nestedDict.TryGetValue(OUTPUT_CASE, out var loadCase) ? loadCase : string.Empty;
|
||||
locations[i] = nestedDict.TryGetValue(LOCATION, out var location) ? location : string.Empty;
|
||||
|
||||
// Extract force values directly from nested dictionary using field names as keys
|
||||
pValues[i] = TryParseDouble(nestedDict.TryGetValue(AXIAL_FORCE, out var p) ? p : null);
|
||||
vxValues[i] = TryParseDouble(nestedDict.TryGetValue(MAJOR_SHEAR, out var vx) ? vx : null);
|
||||
vyValues[i] = TryParseDouble(nestedDict.TryGetValue(MINOR_SHEAR, out var vy) ? vy : null);
|
||||
tValues[i] = TryParseDouble(nestedDict.TryGetValue(TORSION, out var t) ? t : null);
|
||||
mxValues[i] = TryParseDouble(nestedDict.TryGetValue(MAJOR_MOMENT, out var mx) ? mx : null);
|
||||
myValues[i] = TryParseDouble(nestedDict.TryGetValue(MINOR_MOMENT, out var my) ? my : null);
|
||||
}
|
||||
|
||||
var rawArrays = new Dictionary<string, object>
|
||||
{
|
||||
[STORY] = stories,
|
||||
[LOAD_CASE] = loadCases,
|
||||
[LOCATION] = locations,
|
||||
[AXIAL_FORCE] = pValues,
|
||||
[MAJOR_SHEAR] = vxValues,
|
||||
[MINOR_SHEAR] = vyValues,
|
||||
[TORSION] = tValues,
|
||||
[MAJOR_MOMENT] = mxValues,
|
||||
[MINOR_MOMENT] = myValues
|
||||
};
|
||||
|
||||
// Step 4: return sorted and processed dictionary
|
||||
return _resultsArrayProcessor.ProcessArrays(rawArrays, Configuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the OutputCase from the nested dictionary structure.
|
||||
/// This is used for filtering against user selected load cases.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All database values are strings
|
||||
/// </remarks>
|
||||
private static string GetOutputCase(IReadOnlyDictionary<string, string> nestedDict) =>
|
||||
nestedDict.TryGetValue(OUTPUT_CASE, out var outputCase) ? outputCase : string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Safely parses a value to double, returning 0.0 if parsing fails.
|
||||
/// Database returns all values as strings, so conversion is needed.
|
||||
/// </summary>
|
||||
private static double TryParseDouble(object? value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot parse null value to double in story force results");
|
||||
}
|
||||
|
||||
var stringValue = value.ToString();
|
||||
if (string.IsNullOrEmpty(stringValue))
|
||||
{
|
||||
throw new InvalidOperationException("Cannot parse empty string to double in story force results");
|
||||
}
|
||||
|
||||
if (!double.TryParse(stringValue, out double result))
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to parse '{stringValue}' as double in story force results");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -5,15 +5,15 @@ public class CsiToSpeckleCacheSingleton
|
||||
/// <summary>
|
||||
/// A map of (material id, section ids). Assumes the material id is the unique name of the material
|
||||
/// </summary>
|
||||
public Dictionary<string, List<string>> MaterialCache { get; set; } = new();
|
||||
public Dictionary<string, List<string>> MaterialCache { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// A map of (section id, frame object id). Assumes the section id is the unique name of the section
|
||||
/// </summary>
|
||||
public Dictionary<string, List<string>> FrameSectionCache { get; set; } = new();
|
||||
public Dictionary<string, List<string>> FrameSectionCache { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// A map of (section id, shell object id). Assumes the section id is the unique name of the section
|
||||
/// </summary>
|
||||
public Dictionary<string, List<string>> ShellSectionCache { get; set; } = new();
|
||||
public Dictionary<string, List<string>> ShellSectionCache { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -31,9 +31,10 @@ public static class ObjectPropertyKey
|
||||
public static class SectionPropertyCategory
|
||||
{
|
||||
public const string GENERAL_DATA = "General Data";
|
||||
public const string MODIFIERS = "Modifiers";
|
||||
public const string PROPERTY_DATA = "Property Data";
|
||||
public const string SECTION_PROPERTIES = "Section Properties";
|
||||
public const string SECTION_DIMENSIONS = "Section Dimensions";
|
||||
public const string PROPERTY_DATA = "Property Data";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -65,6 +66,7 @@ public static class ResultsKey
|
||||
public const string PIER_FORCES = "Pier Forces";
|
||||
public const string SPANDREL_FORCES = "Spandrel Forces";
|
||||
public const string STORY_DRIFTS = "Story Drifts";
|
||||
public const string STORY_FORCES = "Story Forces";
|
||||
|
||||
// Used by ResultTypeSetting to get all defined result keys
|
||||
public static readonly string[] All =
|
||||
@@ -75,6 +77,7 @@ public static class ResultsKey
|
||||
MODAL_PERIOD,
|
||||
PIER_FORCES,
|
||||
SPANDREL_FORCES,
|
||||
STORY_DRIFTS
|
||||
STORY_DRIFTS,
|
||||
STORY_FORCES
|
||||
];
|
||||
}
|
||||
|
||||
@@ -10,16 +10,27 @@ namespace Speckle.Importers.JobProcessor;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static async Task Main(string[] args)
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
// Dapper doesn't understand how to handle JSON deserialization, so we need to tell it what types can be deserialzied
|
||||
SqlMapper.AddTypeHandler(new JsonHandler<FileimportPayload>());
|
||||
|
||||
var host = ConfigureAppHost(args);
|
||||
|
||||
ConfigureTopLevelLogs(host.Services.GetRequiredService<ILogger<object>>());
|
||||
var backgroundServiceTasks = host
|
||||
.Services.GetServices<IHostedService>()
|
||||
.OfType<BackgroundService>()
|
||||
.Select(s => s.ExecuteTask);
|
||||
|
||||
await host.RunAsync();
|
||||
|
||||
if (backgroundServiceTasks.Any(t => t?.IsFaulted == true))
|
||||
{
|
||||
//https://github.com/dotnet/runtime/issues/67146
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static IHost ConfigureAppHost(string[] args)
|
||||
|
||||
@@ -52,6 +52,8 @@ internal sealed class ImporterInstance(Sender sender, ILogger<ImporterInstance>
|
||||
try
|
||||
{
|
||||
using var config = GetConfig(Path.GetExtension(args.FilePath));
|
||||
logger.LogInformation("Opening file {FilePath}", args.FilePath);
|
||||
|
||||
_rhinoDoc = config.OpenInHeadlessDocument(args.FilePath);
|
||||
RhinoDoc.ActiveDoc = _rhinoDoc;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user