Compare commits

...

24 Commits

Author SHA1 Message Date
Oğuzhan Koral d174597770 Merge pull request #1141 from specklesystems/dev
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Update dev into main
2025-10-08 16:06:07 +03:00
Björn Steinhagen d9289787b7 fix(grasshopper): add auto-load persistence and reactive behavior to sync receive component (#1134)
* fix: poc `(Sync) Load` component reacting to new versions

* fix: persist setting between sessions

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-10-08 12:59:18 +02:00
Björn Steinhagen 77c1c3b511 fix(revit): handle null SelectedObjectIds in view filters on send (#1139) 2025-10-07 15:19:05 +02:00
Adam Hathcock 5a1c542832 Push doc to callers if possible (#1129)
* Push doc to callers if possible

* use check for active doc earlier.  Use doc instead of linked doc

* Use passed in current doc instead of getting active doc each time

* fmt

* Remove extra using
2025-10-07 07:25:36 +00:00
Adam Hathcock 091d7cc897 Stop exceptions we can't do anything about (#1138) 2025-10-06 17:19:30 +01:00
Adam Hathcock 21f4fb52a8 cache parameters by group and name instead of caching groups and assuming units are all the same (#1128)
* cache parameters by group and name

* fmt
2025-10-06 12:01:04 +01:00
Adam Hathcock 868ca8db66 Key for docs should fallback correctly and load when not found. (#1130)
* fix for revit 2024 and greater

* fallback to path name for 22/23

* adjust comment

---------

Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com>
Co-authored-by: Mucahit Bilal GOKER <51519350+bimgeek@users.noreply.github.com>
2025-10-06 10:22:19 +01:00
Björn Steinhagen a9360e5fac fix(grasshopper): optimize CreateSpeckleProperties for large data trees (#1135)
* perf: remove expensive tree flattening in CreateSpeckleProperties

* chore: format
2025-10-04 10:42:01 +01:00
Björn Steinhagen 3414599f72 fix(etabs): units not supported (#1132)
* fix: etabs units not supported

* chore: notes
2025-10-03 15:15:54 +00:00
Björn Steinhagen 84e92aa8a8 fix: prevent internal parameters from appearing in Params/Primitives tab (#1133) 2025-10-03 09:56:31 +01:00
Jedd Morgan edd842763f remove obsolete (#1131) 2025-10-02 16:44:26 +01:00
Björn Steinhagen 867ee0f928 feat(etabs): adds design data material props (#1111) 2025-10-02 16:50:39 +02:00
Jedd Morgan 29ee648d7f Swallow IOEx (#1125) 2025-10-01 22:38:56 +00:00
Jedd Morgan 5b9c610856 Only call IsConnected on Physical connectors (#1127) 2025-10-01 15:41:48 +00:00
Adam Hathcock 769ddf6407 merge update (#1121) 2025-10-01 15:37:35 +00:00
Claire Kuang dd7205f855 fix(rhino): adds area and volume props to breps (#1124)
* adds area and volume props to breps

* removes bbox props and adds area and volume to extrusion and mesh
2025-10-01 14:34:01 +01:00
Mucahit Bilal GOKER 30ee410309 fix(revit): Missing Type Mark parameter added (#1112)
* type mark fix

* fix: format

---------

Co-authored-by: Björn Steinhagen <steinhagen.bjoern@gmail.com>
2025-10-01 15:35:15 +03:00
Jedd Morgan 2d0b1a3a24 chore: Remove legacy .NET based IFC importer (#1118)
* remove IFC source files

* re-generate slnx files
2025-10-01 12:06:43 +01:00
Jedd Morgan 2cdf036172 Merge pull request #1123 from specklesystems/installer-test/linux-ci
chore(ci): Refactor out nuget publishing from CI, use linux for pr builds
2025-10-01 11:58:37 +01:00
Björn Steinhagen c14aa28e76 fix(grasshopper): type-specific outputs not refreshing in QuerySpeckleObjects component (#1114)
Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-09-29 16:50:27 +00:00
Björn Steinhagen 0e7d2554f8 fix(grasshopper): sync SpeckleGeometryWrapper Properties with Base["properties"] (#1113)
Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-09-29 18:45:27 +02:00
Adam Hathcock cdfc618bab Avoid a null points collection (#1116) 2025-09-29 07:55:29 +00:00
Adam Hathcock 1005edb609 feat(Navisworks) Expose send cache options to all (#1115) 2025-09-26 19:23:44 +02:00
Adam Hathcock 3b4da8de52 check connection before using? (#1109) 2025-09-25 08:23:08 +01:00
131 changed files with 915 additions and 5560 deletions
+14 -28
View File
@@ -1,29 +1,11 @@
name: .NET Build
name: .NET Test
on: pull_request
on:
pull_request: {}
push:
branches: ["main"]
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.0.4xx # Align with global.json (including roll forward rules)
- name: Cache Nuget
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
- name: ⚒️ Run build
run: ./build.ps1 test
test:
runs-on: ubuntu-latest
steps:
@@ -43,14 +25,18 @@ jobs:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
- name: ⚒️ Run Build on Linux
run: ./build.sh build-linux
- name: ⚒️ Run tests
run: ./build.sh test-only
- name: ⚒️ Run Test
run: ./build.sh test-and-pack
- name: Upload coverage reports to Codecov with GitHub Action
uses: codecov/codecov-action@v5
with:
files: Converters/**/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
# Disabling this code for now, since we no longer need to publish nugets from this repo
# But keeping it around incase we ever need in the future.
# Ideally, I'd also like to move the nuget token to be an environment secret, and to have tight package scopes
# - name: Push to nuget.org
# if: ${{ inputs.deployNugets }}
# run: dotnet nuget push output/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{ secrets.CONNECTORS_NUGET_TOKEN }}
+9 -43
View File
@@ -6,8 +6,8 @@ on:
tags: ["v3.*.*"] # Manual delivery on every 3.x tag
jobs:
build-windows:
runs-on: windows-latest
build-connectors:
runs-on: windows-latest # Keeping on windows for now, for cross platform building of exe projects, we need to use dotnet publish
env:
SEMVER: "unset"
FILE_VERSION: "unset"
@@ -31,7 +31,7 @@ jobs:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
- name: ⚒️ Run build on Windows
- name: ⚒️ Run build and zip connectors
run: ./build.ps1 zip
- name: ⬆️ Upload artifacts
@@ -48,10 +48,10 @@ jobs:
run: |
echo "semver=${{ env.SEMVER }}" >> "$Env:GITHUB_OUTPUT"
echo "file_version=${{ env.FILE_VERSION }}" >> "$Env:GITHUB_OUTPUT"
deploy-installers:
runs-on: ubuntu-latest
needs: build-windows
needs: build-connectors
env:
IS_PUBLIC_RELEASE: ${{ github.ref_type == 'tag' }}
steps:
@@ -63,8 +63,8 @@ jobs:
token: ${{ secrets.CONNECTORS_GH_TOKEN }}
inputs: '{
"run_id": "${{ github.run_id }}",
"semver": "${{ needs.build-windows.outputs.semver }}",
"file_version": "${{ needs.build-windows.outputs.file_version }}",
"semver": "${{ needs.build-connectors.outputs.semver }}",
"file_version": "${{ needs.build-connectors.outputs.file_version }}",
"repo": "${{ github.repository }}",
"is_public_release": ${{ env.IS_PUBLIC_RELEASE }}
}'
@@ -74,42 +74,8 @@ jobs:
wait-for-completion-timeout: 10m
display-workflow-run-url: true
display-workflow-run-url-interval: 10s
# Allows us to inspect the artifacts of failed builds, since this below step will be skipped if the above step fails
- uses: geekyeggo/delete-artifact@v5
with:
name: output-*
build-linux:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.0.4xx # Align with global.json (including roll forward rules)
- name: Cache Nuget
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
- name: ⚒️ Run tests on Linux
run: ./build.sh test-only
- name: ⚒️ Run Build and Pack on Linux
run: ./build.sh build-linux
- name: Upload coverage reports to Codecov with GitHub Action
uses: codecov/codecov-action@v5
with:
files: Converters/**/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
- name: Push to nuget.org
if: (github.ref_type == 'tag')
run: dotnet nuget push output/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{secrets.CONNECTORS_NUGET_TOKEN }} --skip-duplicate
+17 -21
View File
@@ -7,9 +7,9 @@ using static SimpleExec.Command;
const string CLEAN = "clean";
const string RESTORE = "restore";
const string BUILD = "build";
const string BUILD_LINUX = "build-linux";
const string PACK = "pack";
const string TEST_AFFECTED = "test-affected";
const string TEST = "test";
const string TEST_ONLY = "test-only";
const string FORMAT = "format";
const string ZIP = "zip";
const string RESTORE_TOOLS = "restore-tools";
@@ -19,6 +19,7 @@ const string GEN_SOLUTIONS = "generate-solutions";
const string DEEP_CLEAN = "deep-clean";
const string DEEP_CLEAN_LOCAL = "deep-clean-local";
const string DETECT_AFFECTED = "detect-affected";
const string TEST_AND_PACK = "test-and-pack";
//need to pass arguments
/*var arguments = new List<string>();
@@ -150,7 +151,7 @@ Target(
Target(
RESTORE,
DependsOn(FORMAT, DETECT_AFFECTED),
DependsOn(FORMAT),
Consts.Solutions,
async s =>
{
@@ -181,8 +182,8 @@ Target(CHECK_SOLUTIONS, Solutions.CompareConnectorsToLocal);
Target(GEN_SOLUTIONS, Solutions.GenerateSolutions);
Target(
TEST,
DependsOn(BUILD, CHECK_SOLUTIONS),
TEST_AFFECTED,
DependsOn(DETECT_AFFECTED, BUILD, CHECK_SOLUTIONS),
async () =>
{
foreach (var s in await Affected.GetTestProjects())
@@ -192,14 +193,12 @@ Target(
}
);
//all tests on purpose
Target(
TEST_ONLY,
DependsOn(FORMAT),
TEST,
DependsOn(BUILD, CHECK_SOLUTIONS),
Glob.Files(".", "**/*.Tests.csproj"),
file =>
{
Run("dotnet", $"build \"{file}\" -c Release --no-incremental");
Run(
"dotnet",
$"test \"{file}\" -c Release --no-build --verbosity=minimal /p:AltCover=true /p:AltCoverAttributeFilter=ExcludeFromCodeCoverage /p:AltCoverVerbosity=Warning"
@@ -207,31 +206,28 @@ Target(
}
);
Target(TEST_AND_PACK, DependsOn(TEST, PACK));
Target(
BUILD_LINUX,
DependsOn(FORMAT),
Glob.Files(".", "**/Speckle.Importers.Ifc.csproj"),
async file =>
PACK,
DependsOn(BUILD),
Consts.Solutions,
async solution =>
{
await RunAsync("dotnet", $"restore \"{file}\" --locked-mode");
var version = await Versions.ComputeVersion();
var fileVersion = await Versions.ComputeFileVersion();
Console.WriteLine($"Version: {version} & {fileVersion}");
await RunAsync(
"dotnet",
$"build \"{file}\" -c Release --no-restore -warnaserror -p:Version={version} -p:FileVersion={fileVersion} -v:m"
);
await RunAsync(
"dotnet",
$"pack \"{file}\" -c Release -o output --no-build -p:Version={version} -p:FileVersion={fileVersion} -v:m"
$"pack \"{solution}\" -c Release -o output --no-build -p:Version={version} -p:FileVersion={fileVersion} -v:m"
);
}
);
Target(
ZIP,
DependsOn(TEST),
DependsOn(TEST_AFFECTED),
async () =>
{
var version = await Versions.ComputeVersion();
@@ -282,6 +278,6 @@ Target(
}
);
Target("default", DependsOn(TEST), () => Console.WriteLine("Done!"));
Target("default", DependsOn(TEST_AFFECTED), () => Console.WriteLine("Done!"));
await RunTargetsAndExitAsync(args).ConfigureAwait(true);
@@ -11,7 +11,7 @@ namespace Speckle.Connectors.CSiShared.HostApp.Helpers;
/// Currently, all material property extraction can happen on a CsiShared level which simplifies things a lot.
/// Properties depend on the directional symmetry of the material, hence the switch statements.
/// </remarks>
public class CsiMaterialPropertyExtractor
public class CsiMaterialPropertyExtractor : IMaterialPropertyExtractor
{
/// <summary>
/// Property strings for all mechanical properties, used by numerous methods.
@@ -35,11 +35,11 @@ public class CsiMaterialPropertyExtractor
_settingsStore = settingsStore;
}
public void ExtractProperties(string materialName, Dictionary<string, object?> properties)
public void ExtractProperties(string name, Dictionary<string, object?> properties)
{
GetGeneralProperties(materialName, properties);
GetWeightAndMassProperties(materialName, properties); // TODO: Add units
GetMechanicalProperties(materialName, properties); // TODO: Add units
GetGeneralProperties(name, properties);
GetWeightAndMassProperties(name, properties);
GetMechanicalProperties(name, properties);
}
private void GetGeneralProperties(string materialName, Dictionary<string, object?> properties)
@@ -76,7 +76,7 @@ public class CsiMaterialPropertyExtractor
ref massPerUnitVolume
);
var weightAndMass = properties.EnsureNested("Weight and Mass");
var weightAndMass = properties.EnsureNested(SectionPropertyCategory.WEIGHT_AND_MASS);
weightAndMass["Weight per Unit Volume"] = weightPerUnitVolume;
weightAndMass["Mass per Unit Volume"] = massPerUnitVolume;
}
@@ -101,7 +101,7 @@ public class CsiMaterialPropertyExtractor
_ => throw new ArgumentException($"Unknown symmetry type: {materialDirectionalSymmetryKey}")
};
var mechanicalProperties = properties.EnsureNested("Mechanical Properties");
var mechanicalProperties = properties.EnsureNested(SectionPropertyCategory.MECHANICAL_DATA);
mechanicalProperties["Directional Symmetry Type"] = materialDirectionalSymmetryValue.ToString();
GetMechanicalPropertiesByType(materialName, materialDirectionalSymmetryValue, mechanicalProperties);
@@ -0,0 +1,13 @@
namespace Speckle.Connectors.CSiShared.HostApp.Helpers;
/// <summary>
/// Contract for host application specific material property extraction.
/// </summary>
/// <remarks>
/// Mirrors property extraction system pattern by composing with base extractor.
/// Enables both shared and application-specific property extraction in one call.
/// </remarks>
public interface IApplicationMaterialPropertyExtractor
{
void ExtractProperties(string name, Dictionary<string, object?> properties);
}
@@ -0,0 +1,9 @@
namespace Speckle.Connectors.CSiShared.HostApp.Helpers;
/// <summary>
/// Core contract for material property extraction common across CSi products.
/// </summary>
public interface IMaterialPropertyExtractor
{
void ExtractProperties(string name, Dictionary<string, object?> properties);
}
@@ -0,0 +1,10 @@
using Speckle.Sdk.Models.Proxies;
namespace Speckle.Connectors.CSiShared.HostApp.Helpers;
// NOTE: Interface because Etabs and Sap2000 material unpacking and extraction is different.
// At ServiceRegistration, we inject the correct implementation of the IMaterialUnpacker
public interface IMaterialUnpacker
{
IEnumerable<IProxyCollection> UnpackMaterials();
}
@@ -1,51 +0,0 @@
using Speckle.Connectors.CSiShared.HostApp.Helpers;
using Speckle.Converters.CSiShared.ToSpeckle.Helpers;
using Speckle.Sdk.Models.Proxies;
namespace Speckle.Connectors.CSiShared.HostApp;
/// <summary>
/// Creates material proxies based on stored entries from the materials cache
/// </summary>
public class MaterialUnpacker
{
private readonly CsiMaterialPropertyExtractor _propertyExtractor;
private readonly CsiToSpeckleCacheSingleton _csiToSpeckleCacheSingleton;
public MaterialUnpacker(
CsiMaterialPropertyExtractor propertyExtractor,
CsiToSpeckleCacheSingleton csiToSpeckleCacheSingleton
)
{
_propertyExtractor = propertyExtractor;
_csiToSpeckleCacheSingleton = csiToSpeckleCacheSingleton;
}
// Creates a list of material proxies from the csi materials cache
public IEnumerable<IProxyCollection> UnpackMaterials()
{
foreach (var kvp in _csiToSpeckleCacheSingleton.MaterialCache)
{
// get the cached entry
string materialName = kvp.Key;
List<string> sectionIds = kvp.Value;
// get the properties of the material
Dictionary<string, object?> properties = new(); // create empty dictionary
_propertyExtractor.ExtractProperties(materialName, properties); // dictionary mutated with respective properties
// create the material proxy
GroupProxy materialProxy =
new()
{
id = materialName,
name = materialName,
applicationId = materialName,
objects = sectionIds,
["properties"] = properties
};
yield return materialProxy;
}
}
}
@@ -34,7 +34,7 @@ public class CsiRootObjectBuilder : IRootObjectBuilder<ICsiWrapper>
private readonly IRootToSpeckleConverter _rootToSpeckleConverter;
private readonly IConverterSettingsStore<CsiConversionSettings> _converterSettings;
private readonly CsiSendCollectionManager _sendCollectionManager;
private readonly MaterialUnpacker _materialUnpacker;
private readonly IMaterialUnpacker _materialUnpacker;
private readonly ISectionUnpacker _sectionUnpacker;
private readonly ILogger<CsiRootObjectBuilder> _logger;
private readonly ISdkActivityFactory _activityFactory;
@@ -45,7 +45,7 @@ public class CsiRootObjectBuilder : IRootObjectBuilder<ICsiWrapper>
IRootToSpeckleConverter rootToSpeckleConverter,
IConverterSettingsStore<CsiConversionSettings> converterSettings,
CsiSendCollectionManager sendCollectionManager,
MaterialUnpacker materialUnpacker,
IMaterialUnpacker materialUnpacker,
ISectionUnpacker sectionUnpacker,
ILogger<CsiRootObjectBuilder> logger,
ISdkActivityFactory activityFactory,
@@ -83,8 +83,16 @@ public class CsiRootObjectBuilder : IRootObjectBuilder<ICsiWrapper>
using var activity = _activityFactory.Start("Build");
string modelFileName = _csiApplicationService.SapModel.GetModelFilename(false) ?? "Unnamed model";
(string forceUnit, string tempUnit) = GetForceAndTemperatureUnits();
Collection rootObjectCollection =
new() { name = modelFileName, ["units"] = _converterSettings.Current.SpeckleUnits };
new()
{
name = modelFileName,
["units"] = _converterSettings.Current.SpeckleUnits,
["forceUnits"] = forceUnit,
["temperatureUnits"] = tempUnit
};
List<SendConversionResult> results = new(csiObjects.Count);
int count = 0;
@@ -217,4 +225,20 @@ public class CsiRootObjectBuilder : IRootObjectBuilder<ICsiWrapper>
group => group.Key, // ModelObjectType (FRAME, JOINT, etc.)
group => group.Select(obj => obj.Name).ToList() // Extract Name from each ICsiWrapper and convert to List<string>
);
/// <summary>
/// Instantiates a Base object and pre-populates it with the models defined force units.
/// </summary>
/// <returns></returns>
/// <exception cref="SpeckleException"></exception>
private (string, string) GetForceAndTemperatureUnits()
{
var forceUnit = eForce.NotApplicable;
var lengthUnit = eLength.NotApplicable;
var temperatureUnit = eTemperature.NotApplicable;
_converterSettings.Current.SapModel.GetDatabaseUnits_2(ref forceUnit, ref lengthUnit, ref temperatureUnit);
return (forceUnit.ToString(), temperatureUnit.ToString());
}
}
@@ -47,7 +47,7 @@ public static class ServiceRegistration
services.AddScoped<CsiMaterialPropertyExtractor>();
services.AddScoped<CsiResultsExtractorFactory>();
services.AddScoped<MaterialUnpacker>();
services.AddScoped<IMaterialPropertyExtractor, CsiMaterialPropertyExtractor>();
services.AddScoped<IFrameSectionPropertyExtractor, CsiFrameSectionPropertyExtractor>();
services.AddScoped<IShellSectionPropertyExtractor, CsiShellSectionPropertyExtractor>();
services.AddScoped<AnalysisResultsExtractor>();
@@ -19,7 +19,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Bindings\CsiSharedSendBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Filters\CsiSharedSelectionFilter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\CsiResultsExtractorFactory.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\MaterialUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\IApplicationMaterialPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\IMaterialPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\IMaterialUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\CsiSendCollectionManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\CsiFrameSectionPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\CsiMaterialPropertyExtractor.cs" />
@@ -31,8 +31,8 @@ public class AnalysisResultsExtractor
Dictionary<ModelObjectType, List<string>> objectSelectionSummary
)
{
// Step 1: get analysis units
var analysisResults = CreateAnalysisResultsWithUnits();
// Step 1: create base object that will hold analysis results
var analysisResults = new Base();
// Step 2: configure and validate load cases
ConfigureAndValidateSelectedLoadCases(selectedCasesAndCombinations);
@@ -43,36 +43,6 @@ public class AnalysisResultsExtractor
return analysisResults;
}
/// <summary>
/// Instantiates a Base object and pre-populates it with the models defined force units.
/// </summary>
/// <returns></returns>
/// <exception cref="SpeckleException"></exception>
private Base CreateAnalysisResultsWithUnits()
{
var forceUnit = eForce.NotApplicable;
var lengthUnit = eLength.NotApplicable;
var temperatureUnit = eTemperature.NotApplicable;
int success = _converterSettingsStore.Current.SapModel.GetDatabaseUnits_2(
ref forceUnit,
ref lengthUnit,
ref temperatureUnit
);
if (success != 0)
{
throw new SpeckleException("Failed to retrieve units for analysis results");
}
return new Base
{
["forceUnit"] = forceUnit.ToString(),
["lengthUnit"] = lengthUnit.ToString(),
["temperatureUnit"] = temperatureUnit.ToString()
};
}
private void ExtractResults(
List<string> requestedResultTypes,
Dictionary<ModelObjectType, List<string>> objectSelectionSummary,
@@ -0,0 +1,50 @@
using Speckle.Connectors.CSiShared.HostApp.Helpers;
using Speckle.Converters.CSiShared.ToSpeckle.Helpers;
using Speckle.Sdk.Models.Proxies;
namespace Speckle.Connectors.ETABSShared.HostApp;
public class EtabsMaterialUnpacker : IMaterialUnpacker
{
private readonly CsiToSpeckleCacheSingleton _csiToSpeckleCacheSingleton;
private readonly IMaterialPropertyExtractor _csiMaterialPropertyExtractor;
private readonly IApplicationMaterialPropertyExtractor _etabsMaterialPropertyExtractor;
public EtabsMaterialUnpacker(
CsiToSpeckleCacheSingleton csiToSpeckleCacheSingleton,
IMaterialPropertyExtractor csiMaterialPropertyExtractor,
IApplicationMaterialPropertyExtractor etabsMaterialPropertyExtractor
)
{
_csiToSpeckleCacheSingleton = csiToSpeckleCacheSingleton;
_csiMaterialPropertyExtractor = csiMaterialPropertyExtractor;
_etabsMaterialPropertyExtractor = etabsMaterialPropertyExtractor;
}
public IEnumerable<IProxyCollection> UnpackMaterials()
{
foreach (var kvp in _csiToSpeckleCacheSingleton.MaterialCache)
{
string name = kvp.Key;
var sectionIds = kvp.Value;
// get the properties of the material
Dictionary<string, object?> properties = [];
_csiMaterialPropertyExtractor.ExtractProperties(name, properties);
_etabsMaterialPropertyExtractor.ExtractProperties(name, properties);
// create the material proxy
GroupProxy materialProxy =
new()
{
id = name,
name = name,
applicationId = name,
objects = sectionIds,
["properties"] = properties
};
yield return materialProxy;
}
}
}
@@ -0,0 +1,219 @@
using Speckle.Connectors.CSiShared.HostApp.Helpers;
using Speckle.Converters.Common;
using Speckle.Converters.CSiShared;
using Speckle.Converters.CSiShared.Utils;
namespace Speckle.Connectors.ETABS22.HostApp.Helpers;
public class EtabsMaterialPropertyExtractor : IApplicationMaterialPropertyExtractor
{
private readonly IConverterSettingsStore<CsiConversionSettings> _settingsStore;
private readonly Dictionary<int, string?> _ssTypeDict =
new()
{
{ 0, "User defined" },
{ 1, "Parametric - Simple" },
{ 2, "Parametric - Mander" }
};
private readonly Dictionary<int, string?> _ssHysTypeDict =
new()
{
{ 0, "Elastic" },
{ 1, "Kinematic" },
{ 2, "Takeda" },
{ 3, "Pivot" },
{ 4, "Concrete" },
{ 5, "BRB Hardening" },
{ 6, "Degrading" },
{ 7, "Isotropic" }
};
private const int TEMP = 0;
public EtabsMaterialPropertyExtractor(IConverterSettingsStore<CsiConversionSettings> settingsStore)
{
_settingsStore = settingsStore;
}
public void ExtractProperties(string name, Dictionary<string, object?> properties)
{
// we want to get some of the "other" material property data that is type specific
// csi extractor populates "type" string, but api query arguably simpler and more reliable than dict string access
int symType = 0;
eMatType matType = 0;
_settingsStore.Current.SapModel.PropMaterial.GetTypeOAPI(name, ref matType, ref symType);
// we don't have design data api queries for these, so early return to avoid creating that specific dictionary
if (matType is eMatType.NoDesign or eMatType.Aluminum or eMatType.ColdFormed or eMatType.Masonry)
{
return;
}
// ensure design data specific properties dictionary that will be mutated in switch expression
var designData = properties.EnsureNested(SectionPropertyCategory.DESIGN_DATA);
// can't do a switch expression here because not all enums have an api query (e.g. masonry, aluminium)
switch (matType)
{
case eMatType.Steel:
ExtractSteelProperties(name, designData);
break;
case eMatType.Concrete:
ExtractConcreteProperties(name, designData);
break;
case eMatType.Rebar:
ExtractRebarProperties(name, designData);
break;
case eMatType.Tendon:
ExtractTendonProperties(name, designData);
break;
}
}
private void ExtractSteelProperties(string name, Dictionary<string, object?> designData)
{
// step 1: stubs for api query
int ssType = 0,
ssHysType = 0;
double fy = 0,
fu = 0,
eFy = 0,
eFu = 0,
strainAtHardening = 0,
strainAtMaxStress = 0,
strainAtRupture = 0;
// step 2: api query
// NOTE: using the "older" method. Not sure if _1 is unsupported in etabs 21
// also, _1 doesn't give a lot more MEANINGFUL data
_settingsStore.Current.SapModel.PropMaterial.GetOSteel(
name,
ref fy,
ref fu,
ref eFy,
ref eFu,
ref ssType,
ref ssHysType,
ref strainAtHardening,
ref strainAtMaxStress,
ref strainAtRupture
);
// step 3: mutate properties dictionary
designData["Fy"] = fy;
designData["Fu"] = fu;
designData["EFy"] = eFy;
designData["EFu"] = eFu;
designData["SSType"] = _ssTypeDict.TryGetValue(ssType, out string? ssTypeValue) ? ssTypeValue : "";
designData["SSHysType"] = _ssHysTypeDict.TryGetValue(ssHysType, out string? ssHysTypeValue) ? ssHysTypeValue : "";
designData["StrainAtHardening"] = strainAtHardening;
designData["StrainAtMaxStress"] = strainAtMaxStress;
designData["StrainAtRupture"] = strainAtRupture;
designData["Temp"] = TEMP;
}
private void ExtractConcreteProperties(string name, Dictionary<string, object?> designData)
{
// step 1: stubs for api query
int ssType = 0,
ssHysType = 0;
bool isLightweight = false;
double fc = 0,
fcsFactor = 0,
strainAtFc = 0,
strainUltimate = 0,
frictionAngle = 0,
dilatationalAngle = 0;
// step 2: api query
// NOTE: using the "older" method. Not sure if _1 or _2 are unsupported in etabs 21
// also, _1 or _2 doesn't give a lot more MEANINGFUL data
_settingsStore.Current.SapModel.PropMaterial.GetOConcrete(
name,
ref fc,
ref isLightweight,
ref fcsFactor,
ref ssType,
ref ssHysType,
ref strainAtFc,
ref strainUltimate,
ref frictionAngle,
ref dilatationalAngle
);
// step 3: mutate properties dictionary
designData["Fc"] = fc;
designData["FcsFactor"] = fcsFactor;
designData["StrainAtFc"] = strainAtFc;
designData["StrainUltimate"] = strainUltimate;
designData["FrictionAngle"] = frictionAngle;
designData["DilatationalAngle"] = dilatationalAngle;
designData["IsLightweight"] = isLightweight.ToString();
designData["SSType"] = _ssTypeDict.TryGetValue(ssType, out string? ssTypeValue) ? ssTypeValue : "";
designData["SSHysType"] = _ssHysTypeDict.TryGetValue(ssHysType, out string? ssHysTypeValue) ? ssHysTypeValue : "";
designData["Temp"] = TEMP;
}
private void ExtractRebarProperties(string name, Dictionary<string, object?> designData)
{
// step 1: stubs for api query
bool useCaltransSsDefaults = false;
int ssType = 0,
ssHysType = 0;
double fy = 0,
fu = 0,
eFy = 0,
eFu = 0,
strainAtHardening = 0,
strainUltimate = 0;
// step 2: api query
// NOTE: using the "older" method. Not sure if _1 is unsupported in etabs 21
// also, _1 doesn't give a lot more MEANINGFUL data
_settingsStore.Current.SapModel.PropMaterial.GetORebar(
name,
ref fy,
ref fu,
ref eFy,
ref eFu,
ref ssType,
ref ssHysType,
ref strainAtHardening,
ref strainUltimate,
ref useCaltransSsDefaults
);
// step 3: mutate properties dictionary
designData["Fy"] = fy;
designData["Fu"] = fu;
designData["EFy"] = eFy;
designData["EFu"] = eFu;
designData["StrainAtHardening"] = strainAtHardening;
designData["StrainUltimate"] = strainUltimate;
designData["SSType"] = _ssTypeDict.TryGetValue(ssType, out string? ssTypeValue) ? ssTypeValue : "";
designData["SSHysType"] = _ssHysTypeDict.TryGetValue(ssHysType, out string? ssHysTypeValue) ? ssHysTypeValue : "";
designData["UseCaltransSsDefaults"] = useCaltransSsDefaults.ToString();
designData["Temp"] = TEMP;
}
private void ExtractTendonProperties(string name, Dictionary<string, object?> designData)
{
// step 1: stubs for api query
int ssType = 0,
ssHysType = 0;
double fy = 0,
fu = 0;
// step 2: api query
_settingsStore.Current.SapModel.PropMaterial.GetOTendon(name, ref fy, ref fu, ref ssType, ref ssHysType);
// step 3: mutate properties dictionary
designData["Fy"] = fy;
designData["Fu"] = fu;
designData["SSType"] = _ssTypeDict.TryGetValue(ssType, out string? ssTypeValue) ? ssTypeValue : "";
designData["SSHysType"] = _ssHysTypeDict.TryGetValue(ssHysType, out string? ssHysTypeValue) ? ssHysTypeValue : "";
designData["Temp"] = TEMP;
}
}
@@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.CSiShared.HostApp.Helpers;
using Speckle.Connectors.ETABS22.HostApp.Helpers;
using Speckle.Connectors.ETABSShared.HostApp;
using Speckle.Connectors.ETABSShared.HostApp.Helpers;
using Speckle.Connectors.ETABSShared.HostApp.Services;
@@ -14,12 +15,18 @@ public static class ServiceRegistration
{
services.AddEtabsConverters();
services.AddScoped<CsiSendCollectionManager, EtabsSendCollectionManager>();
// material services
services.AddScoped<IMaterialUnpacker, EtabsMaterialUnpacker>();
services.AddScoped<IApplicationMaterialPropertyExtractor, EtabsMaterialPropertyExtractor>();
// section services
services.AddScoped<ISectionUnpacker, EtabsSectionUnpacker>();
services.AddScoped<IApplicationFrameSectionPropertyExtractor, EtabsFrameSectionPropertyExtractor>();
services.AddScoped<IApplicationShellSectionPropertyExtractor, EtabsShellSectionPropertyExtractor>();
services.AddScoped<EtabsSectionPropertyDefinitionService>();
services.AddScoped<EtabsSectionPropertyExtractor>();
services.AddScoped<EtabsShellSectionResolver>();
services.AddScoped<IApplicationFrameSectionPropertyExtractor, EtabsFrameSectionPropertyExtractor>();
services.AddScoped<IApplicationShellSectionPropertyExtractor, EtabsShellSectionPropertyExtractor>();
services.AddScoped<ISectionUnpacker, EtabsSectionUnpacker>();
return services;
}
@@ -9,9 +9,11 @@
<Import_RootNamespace>Speckle.Connectors.ETABSShared</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)HostApp\EtabsMaterialUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\EtabsSectionUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\EtabsSendCollectionManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\EtabsFrameSectionPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\EtabsMaterialPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\EtabsSectionPropertyDefinitionService.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\EtabsSectionPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\EtabsShellSectionPropertyExtractor.cs" />
@@ -6,6 +6,7 @@ using Speckle.Connector.Navisworks.DependencyInjection;
using Speckle.Connector.Navisworks.HostApp;
using Speckle.Connector.Navisworks.Plugin.Tools;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.DUI;
using Speckle.Connectors.DUI.WebView;
using Speckle.Converter.Navisworks.DependencyInjection;
@@ -46,6 +47,8 @@ internal sealed class Connector : NAV.Plugins.DockPanePlugin
Container = services.BuildServiceProvider();
Container.UseDUI();
Container.GetRequiredService<NavisworksDocumentEvents>();
Container.GetRequiredService<ISerializationOptions>().SkipCacheRead = true;
Container.GetRequiredService<ISerializationOptions>().SkipCacheWrite = true;
var u = Container.GetRequiredService<DUI3ControlWebView>();
@@ -1,3 +1,4 @@
using System.Runtime.InteropServices;
using System.Windows.Controls;
using System.Windows.Threading;
using Autodesk.Revit.UI;
@@ -31,6 +32,10 @@ public partial class CefSharpPanel : Page, Autodesk.Revit.UI.IDockablePaneProvid
DispatcherPriority.Background
);
}
catch (SEHException)
{
//do nothing as we can't control external components
}
catch (OperationCanceledException)
{
//do nothing, happens when closing Revit while a script is being executed
@@ -106,7 +106,7 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding
if (senderModelCard.SendFilter is RevitViewsFilter revitViewsFilter)
{
var view = revitViewsFilter.GetView();
var view = revitViewsFilter.GetView(activeUIDoc.Document);
if (view is not null)
{
await _revitTask
@@ -110,6 +110,11 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
public async Task Send(string modelCardId)
{
var document = _revitContext.UIApplication?.ActiveUIDocument?.Document;
if (document == null)
{
throw new SpeckleException("No document is active for sending.");
}
using var manager = _sendOperationManagerFactory.Create();
await manager.Process<DocumentToConvert>(
@@ -120,24 +125,20 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
sp.GetRequiredService<IConverterSettingsStore<RevitConversionSettings>>()
.Initialize(
_revitConversionSettingsFactory.Create(
_toSpeckleSettingsManager.GetDetailLevelSetting(card),
_toSpeckleSettingsManager.GetReferencePointSetting(card),
_toSpeckleSettingsManager.GetSendParameterNullOrEmptyStringsSetting(card),
_toSpeckleSettingsManager.GetLinkedModelsSetting(card),
_toSpeckleSettingsManager.GetSendRebarsAsVolumetric(card)
_toSpeckleSettingsManager.GetDetailLevelSetting(document, card),
_toSpeckleSettingsManager.GetReferencePointSetting(document, card),
_toSpeckleSettingsManager.GetSendParameterNullOrEmptyStringsSetting(document, card),
_toSpeckleSettingsManager.GetLinkedModelsSetting(document, card),
_toSpeckleSettingsManager.GetSendRebarsAsVolumetric(document, card)
)
);
},
async x => await RefreshElementsIdsOnSender(x.NotNull())
async x => await RefreshElementsIdsOnSender(document, x.NotNull())
);
}
private async Task<List<DocumentToConvert>> RefreshElementsIdsOnSender(SenderModelCard modelCard)
private async Task<List<DocumentToConvert>> RefreshElementsIdsOnSender(Document document, SenderModelCard modelCard)
{
var activeUIDoc =
_revitContext.UIApplication?.ActiveUIDocument
?? throw new SpeckleException("Unable to retrieve active UI document");
if (modelCard.SendFilter.NotNull() is IRevitSendFilter viewFilter)
{
viewFilter.SetContext(_revitContext);
@@ -147,10 +148,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
() => Task.FromResult(modelCard.SendFilter.NotNull().RefreshObjectIds())
);
var allElements = selectedObjects
.Select(uid => activeUIDoc.Document.GetElement(uid))
.Where(el => el is not null)
.ToList();
var allElements = selectedObjects.Select(uid => document.GetElement(uid)).Where(el => el is not null).ToList();
// split elements between main model and linked models
var elementsOnMainModel = allElements.Where(el => el is not RevitLinkInstance).ToList();
@@ -158,14 +156,11 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
// should ideally reuse the initialized value from the scoped IConverterSettingsStore<RevitConversionSettings>.
// but, it's scoped and to avoid bigger scarier changes I'm re-fetching the setting here (inexpensive operation?)
Transform? mainModelTransform = _toSpeckleSettingsManager.GetReferencePointSetting(modelCard);
List<DocumentToConvert> documentElementContexts =
[
new(mainModelTransform, activeUIDoc.Document, elementsOnMainModel)
];
Transform? mainModelTransform = _toSpeckleSettingsManager.GetReferencePointSetting(document, modelCard);
List<DocumentToConvert> documentElementContexts = [new(mainModelTransform, document, elementsOnMainModel)];
// get the linked models setting - this decision belongs at this level
bool includeLinkedModels = _toSpeckleSettingsManager.GetLinkedModelsSetting(modelCard);
bool includeLinkedModels = _toSpeckleSettingsManager.GetLinkedModelsSetting(document, modelCard);
// ⚠️ process linked models - RevitSendBinding controls the flow based on settings!
// If setting not enabled, we won't unpack (see if-else block)
@@ -192,7 +187,12 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
if (includeLinkedModels)
{
// handler is only responsible for element collection mechanics
var linkedElements = _linkedModelHandler.GetLinkedModelElements(modelCard.SendFilter, linkedDoc, transform);
var linkedElements = _linkedModelHandler.GetLinkedModelElements(
document,
modelCard.SendFilter,
linkedDoc,
transform
);
linkedDocumentContexts.Add(new(transform, linkedDoc, linkedElements));
}
// ⚠️ when disabled, still adds empty contexts to maintain warning generation in RevitRootObjectBuilder
@@ -332,9 +332,14 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
private async Task PostSetObjectIds()
{
var document = _revitContext.UIApplication?.ActiveUIDocument?.Document;
if (document == null)
{
return;
}
foreach (var sender in _store.GetSenders().ToList())
{
await RefreshElementsIdsOnSender(sender);
await RefreshElementsIdsOnSender(document, sender);
}
}
@@ -3,7 +3,6 @@ using Autodesk.Revit.DB;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.RevitShared;
using Speckle.Connectors.RevitShared.Operations.Send.Filters;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk;
using Speckle.Sdk.Common;
@@ -17,20 +16,19 @@ namespace Speckle.Connectors.Revit.HostApp;
/// </summary>
public class LinkedModelHandler
{
private readonly RevitContext _revitContext;
public Dictionary<string, string> LinkedModelDisplayNames { get; } = new();
public LinkedModelHandler(RevitContext revitContext)
{
_revitContext = revitContext;
}
/// <summary>
/// Gets elements from a linked document based on the provided send filter.
/// This method handles the specifics of element collection but doesn't make decisions
/// about whether the linked model should be processed - that's the caller's responsibility.
/// </summary>
public List<Element> GetLinkedModelElements(ISendFilter sendFilter, Document linkedDocument, Transform? transform)
public List<Element> GetLinkedModelElements(
Document currentDocument,
ISendFilter sendFilter,
Document linkedDocument,
Transform? transform
)
{
// send mode → Categories
if (sendFilter is RevitCategoriesFilter categoryFilter && categoryFilter.SelectedCategories is not null)
@@ -48,19 +46,15 @@ public class LinkedModelHandler
}
// send mode → Views (taken from the legacy code)
if (sendFilter is RevitViewsFilter viewFilter && viewFilter.GetView() != null)
if (sendFilter is RevitViewsFilter viewFilter && viewFilter.GetView(currentDocument) != null)
{
RevitLinkInstance linkInstance = FindLinkInstanceForDocument(
linkedDocument.PathName,
_revitContext.UIApplication.NotNull().ActiveUIDocument.Document,
transform
);
RevitLinkInstance linkInstance = FindLinkInstanceForDocument(linkedDocument.PathName, currentDocument, transform);
#if REVIT2024_OR_GREATER
// revit 2024 and 2025 we can use the three-parameter constructor to get only visible elements
using var viewCollector = new FilteredElementCollector(
_revitContext.UIApplication.ActiveUIDocument.Document,
viewFilter.GetView().NotNull().Id,
currentDocument,
viewFilter.GetView(currentDocument).NotNull().Id,
linkInstance.Id
);
@@ -70,7 +64,7 @@ public class LinkedModelHandler
// 🚨 LIMITATION: in Revit 2023 and below, we can only check if the entire linked model is visible,
// not individual elements within it. If the linked model is visible, all its elements will be included.
// constructor overload pertaining to searching and filtering visible elements from a revit link only added 2024.
if (linkInstance.IsHidden(viewFilter.GetView().NotNull()))
if (linkInstance.IsHidden(viewFilter.GetView(currentDocument).NotNull()))
{
return new List<Element>(); // if the linked model is hidden, return no elements
}
@@ -97,10 +97,12 @@ internal sealed class RevitDocumentStore : DocumentModelStore
try
{
var key = GetKeyForDocument(document);
if (key != null)
if (key is null)
{
_jsonCacheManager.UpdateObject(key, modelCardState);
LoadFromString(null);
return;
}
_jsonCacheManager.UpdateObject(key, modelCardState);
}
catch (Exception ex) when (!ex.IsFatal())
{
@@ -111,11 +113,17 @@ internal sealed class RevitDocumentStore : DocumentModelStore
private string? GetKeyForDocument(Document doc)
{
string? id = doc?.ProjectInformation?.UniqueId; //ProjectInformation Should only be null for family docs
#if REVIT_2024_OR_GREATER
id ??= doc.CreationGUID.ToString(); //fallback for family docs
#if REVIT2024_OR_GREATER
return doc.CreationGUID.ToString();
#else
//basically, no document state will ever be saved when it's a new document. It must be saved first for path name to be a valid value.
var x = doc.PathName;
if (string.IsNullOrEmpty(x))
{
return null;
}
return x;
#endif
return id;
}
protected override void LoadState()
@@ -128,14 +136,12 @@ internal sealed class RevitDocumentStore : DocumentModelStore
}
var key = GetKeyForDocument(document);
if (key != null)
if (key is null)
{
var state = _jsonCacheManager.GetObject(key);
if (state == null)
{
return;
}
LoadFromString(state);
LoadFromString(null);
return;
}
var state = _jsonCacheManager.GetObject(key);
LoadFromString(state);
}
}
@@ -11,14 +11,13 @@ namespace Speckle.Connectors.RevitShared.Operations.Send.Filters;
public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilter
{
private RevitContext _revitContext;
private Document? _doc;
public string Id { get; set; } = "revitViews";
public string Name { get; set; } = "Views";
public string Type { get; set; } = "Custom";
public string? Summary { get; set; }
public bool IsDefault { get; set; }
public string? SelectedView { get; set; }
public List<string> SelectedObjectIds { get; set; }
public List<string> SelectedObjectIds { get; set; } = new();
public Dictionary<string, string>? IdMap { get; set; } = new();
public List<string>? AvailableViews { get; set; }
@@ -27,12 +26,14 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
public RevitViewsFilter(RevitContext revitContext)
{
_revitContext = revitContext;
_doc = _revitContext.UIApplication?.ActiveUIDocument?.Document;
GetViews();
var doc = _revitContext.UIApplication?.ActiveUIDocument?.Document;
if (doc is not null)
{
GetViews(doc);
}
}
public View? GetView()
public View? GetView(Document document)
{
if (SelectedView is null)
{
@@ -42,7 +43,7 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
var viewFamilyString = result[0];
var viewString = result[1];
using var collector = new FilteredElementCollector(_doc);
using var collector = new FilteredElementCollector(document);
return collector
.OfClass(typeof(View))
.Cast<View>()
@@ -56,7 +57,8 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
/// <exception cref="SpeckleSendFilterException">Whenever no view is found.</exception>
public List<string> RefreshObjectIds()
{
if (SelectedView is null)
var document = _revitContext.UIApplication?.ActiveUIDocument?.Document;
if (SelectedView is null || document is null)
{
return [];
}
@@ -66,7 +68,7 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
var viewFamilyString = result[0];
var viewString = result[1];
using var collector = new FilteredElementCollector(_doc);
using var collector = new FilteredElementCollector(document);
View? view = collector
.OfClass(typeof(View))
.Cast<View>()
@@ -78,7 +80,7 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
return [];
}
IEnumerable<Element> elementsInView = GetFilteredElementsForView(view);
IEnumerable<Element> elementsInView = GetFilteredElementsForView(document, view);
// NOTE: FilteredElementCollector() includes sweeps and reveals from a wall family's definition and includes them as additional objects
// on this return. displayValue for Wall already includes these, therefore we end up with duplicate elements on wall sweeps
@@ -94,9 +96,9 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
return objectIds;
}
private void GetViews()
private void GetViews(Document document)
{
using var collector = new FilteredElementCollector(_doc);
using var collector = new FilteredElementCollector(document);
var views = collector
.OfClass(typeof(View))
.Cast<View>()
@@ -125,7 +127,6 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
public void SetContext(RevitContext revitContext)
{
_revitContext = revitContext;
_doc = _revitContext.UIApplication?.ActiveUIDocument.Document;
}
// NOTE: Element collector returns parts and source elements even when Parts Visibility is set as "Show Parts" only.
@@ -161,9 +162,9 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
return elementsToExclude;
}
private IEnumerable<Element> GetFilteredElementsForView(View view)
private IEnumerable<Element> GetFilteredElementsForView(Document document, View view)
{
using var viewCollector = new FilteredElementCollector(_doc, view.Id);
using var viewCollector = new FilteredElementCollector(document, view.Id);
var allElements = viewCollector.ToElements();
// parts filtering when view is set to show Parts only (and overwrites allElements)
@@ -1,25 +1,21 @@
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Connectors.Revit.HostApp;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Converters.RevitShared.Settings;
using Speckle.InterfaceGenerator;
using Speckle.Sdk;
using Speckle.Sdk.Common;
namespace Speckle.Connectors.Revit.Operations.Send.Settings;
[GenerateAutoInterface]
public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
public class ToSpeckleSettingsManager(
ISendConversionCache sendConversionCache,
ElementUnpacker elementUnpacker,
ILogger<ToSpeckleSettingsManager> logger
) : IToSpeckleSettingsManager
{
private readonly RevitContext _revitContext;
private readonly ISendConversionCache _sendConversionCache;
private readonly ElementUnpacker _elementUnpacker;
private readonly ILogger<ToSpeckleSettingsManager> _logger;
// cache invalidation process run with ModelCardId since the settings are model specific
private readonly Dictionary<string, DetailLevelType> _detailLevelCache = [];
private readonly Dictionary<string, Transform?> _referencePointCache = [];
@@ -27,20 +23,7 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
private readonly Dictionary<string, bool?> _sendLinkedModelsCache = [];
private readonly Dictionary<string, bool?> _sendRebarsAsVolumetricCache = [];
public ToSpeckleSettingsManager(
RevitContext revitContext,
ISendConversionCache sendConversionCache,
ElementUnpacker elementUnpacker,
ILogger<ToSpeckleSettingsManager> logger
)
{
_revitContext = revitContext;
_elementUnpacker = elementUnpacker;
_sendConversionCache = sendConversionCache;
_logger = logger;
}
public DetailLevelType GetDetailLevelSetting(SenderModelCard modelCard)
public DetailLevelType GetDetailLevelSetting(Document document, SenderModelCard modelCard)
{
var fidelityString =
modelCard.Settings?.FirstOrDefault(s => s.Id == DetailLevelSetting.SETTING_ID)?.Value as string;
@@ -53,7 +36,7 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
{
if (previousType != fidelity)
{
EvictCacheForModelCard(modelCard);
EvictCacheForModelCard(document, modelCard);
}
}
_detailLevelCache[modelCard.ModelCardId.NotNull()] = fidelity;
@@ -61,7 +44,7 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
}
// log the issue
_logger.LogWarning(
logger.LogWarning(
"Invalid detail level setting received: '{FidelityString}' for model {ModelCardId}, using default: {DefaultValue}",
fidelityString,
modelCard.ModelCardId,
@@ -74,7 +57,7 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
return defaultValue;
}
public Transform? GetReferencePointSetting(ModelCard modelCard)
public Transform? GetReferencePointSetting(Document document, ModelCard modelCard)
{
var referencePointString =
modelCard.Settings?.FirstOrDefault(s => s.Id == SendReferencePointSetting.SETTING_ID)?.Value as string;
@@ -88,14 +71,14 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
{
// get the current transform from setting first
// we are doing this because we can't track if reference points were changed between send operations.
Transform? currentTransform = GetTransform(referencePoint);
Transform? currentTransform = GetTransform(document, referencePoint);
if (_referencePointCache.TryGetValue(modelCard.ModelCardId.NotNull(), out Transform? previousTransform))
{
// invalidate conversion cache if the transform has changed
if (modelCard is SenderModelCard senderModelCard && previousTransform != currentTransform)
{
EvictCacheForModelCard(senderModelCard);
EvictCacheForModelCard(document, senderModelCard);
}
}
@@ -104,7 +87,7 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
}
// log the issue
_logger.LogWarning(
logger.LogWarning(
"Invalid reference point setting received: '{ReferencePointString}' for model {ModelCardId}, using default: {DefaultValue}",
referencePointString,
modelCard.ModelCardId,
@@ -116,8 +99,9 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
return null;
}
public bool GetSendParameterNullOrEmptyStringsSetting(SenderModelCard modelCard) =>
public bool GetSendParameterNullOrEmptyStringsSetting(Document document, SenderModelCard modelCard) =>
GetBooleanSettingWithCache(
document,
SendParameterNullOrEmptyStringsSetting.SETTING_ID,
SendParameterNullOrEmptyStringsSetting.DEFAULT_VALUE,
modelCard,
@@ -127,8 +111,9 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
// NOTE: Cache invalidation currently a placeholder until we have more understanding on the sends
// TODO: Evaluate cache invalidation for GetLinkedModelsSetting
public bool GetLinkedModelsSetting(SenderModelCard modelCard) =>
public bool GetLinkedModelsSetting(Document document, SenderModelCard modelCard) =>
GetBooleanSettingWithCache(
document,
LinkedModelsSetting.SETTING_ID,
LinkedModelsSetting.DEFAULT_VALUE,
modelCard,
@@ -136,8 +121,9 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
"Linked models"
);
public bool GetSendRebarsAsVolumetric(SenderModelCard modelCard) =>
public bool GetSendRebarsAsVolumetric(Document document, SenderModelCard modelCard) =>
GetBooleanSettingWithCache(
document,
SendRebarsAsVolumetricSetting.SETTING_ID,
SendRebarsAsVolumetricSetting.DEFAULT_VALUE,
modelCard,
@@ -149,6 +135,7 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
/// Helper method to handle boolean settings with caching and logging
/// </summary>
private bool GetBooleanSettingWithCache(
Document document,
string settingId,
bool defaultValue,
SenderModelCard modelCard,
@@ -163,7 +150,7 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
{
if (previousValue != returnValue)
{
EvictCacheForModelCard(modelCard);
EvictCacheForModelCard(document, modelCard);
}
}
@@ -173,7 +160,7 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
// respected (linked models disabled but still sent linked models), I think we should note this occurence so we know
if (settingValue == null)
{
_logger.LogWarning(
logger.LogWarning(
"{SettingName} setting was null for model {ModelCardId}, using default: {DefaultValue}",
settingName,
modelCard.ModelCardId,
@@ -184,71 +171,59 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
return returnValue;
}
private void EvictCacheForModelCard(SenderModelCard modelCard)
private void EvictCacheForModelCard(Document document, SenderModelCard modelCard)
{
var doc = _revitContext.UIApplication?.ActiveUIDocument?.Document;
if (doc == null)
{
throw new SpeckleException("Unable to retrieve active UI document");
}
var objectIds = modelCard.SendFilter != null ? modelCard.SendFilter.NotNull().SelectedObjectIds : [];
var unpackedObjectIds = _elementUnpacker.GetUnpackedElementIds(objectIds, doc);
_sendConversionCache.EvictObjects(unpackedObjectIds);
var objectIds = modelCard.SendFilter?.SelectedObjectIds ?? [];
var unpackedObjectIds = elementUnpacker.GetUnpackedElementIds(objectIds, document);
sendConversionCache.EvictObjects(unpackedObjectIds);
}
private Transform? GetTransform(ReferencePointType referencePointType)
private Transform? GetTransform(Document document, ReferencePointType referencePointType)
{
Transform? referencePointTransform = null;
if (_revitContext.UIApplication is UIApplication uiApplication)
// first get the main doc base points and reference setting transform
using FilteredElementCollector filteredElementCollector = new(document);
var points = filteredElementCollector.OfClass(typeof(BasePoint)).Cast<BasePoint>().ToList();
BasePoint? projectPoint = points.FirstOrDefault(o => !o.IsShared);
BasePoint? surveyPoint = points.FirstOrDefault(o => o.IsShared);
switch (referencePointType)
{
// first get the main doc base points and reference setting transform
using FilteredElementCollector filteredElementCollector = new(uiApplication.ActiveUIDocument.Document);
var points = filteredElementCollector.OfClass(typeof(BasePoint)).Cast<BasePoint>().ToList();
BasePoint? projectPoint = points.FirstOrDefault(o => !o.IsShared);
BasePoint? surveyPoint = points.FirstOrDefault(o => o.IsShared);
// note that the project base (ui) rotation is registered on the survey pt, not on the base point
case ReferencePointType.ProjectBase:
if (projectPoint is not null)
{
referencePointTransform = Transform.CreateTranslation(projectPoint.Position);
}
else
{
throw new InvalidOperationException("Couldn't retrieve Project Point from document");
}
break;
switch (referencePointType)
{
// note that the project base (ui) rotation is registered on the survey pt, not on the base point
case ReferencePointType.ProjectBase:
if (projectPoint is not null)
{
referencePointTransform = Transform.CreateTranslation(projectPoint.Position);
}
else
{
throw new InvalidOperationException("Couldn't retrieve Project Point from document");
}
break;
// note that the project base (ui) rotation is registered on the survey pt, not on the base point
case ReferencePointType.Survey:
if (surveyPoint is not null && projectPoint is not null)
{
// POC: should a null angle resolve to 0?
// retrieve the survey point rotation from the project point
var angle = projectPoint.get_Parameter(BuiltInParameter.BASEPOINT_ANGLETON_PARAM)?.AsDouble() ?? 0;
// note that the project base (ui) rotation is registered on the survey pt, not on the base point
case ReferencePointType.Survey:
if (surveyPoint is not null && projectPoint is not null)
{
// POC: should a null angle resolve to 0?
// retrieve the survey point rotation from the project point
var angle = projectPoint.get_Parameter(BuiltInParameter.BASEPOINT_ANGLETON_PARAM)?.AsDouble() ?? 0;
// POC: following disposed incorrectly or early or maybe a false negative?
using Transform translation = Transform.CreateTranslation(surveyPoint.Position);
referencePointTransform = translation.Multiply(Transform.CreateRotation(XYZ.BasisZ, angle));
}
else
{
throw new InvalidOperationException("Couldn't retrieve Survey and Project Point from document");
}
break;
// POC: following disposed incorrectly or early or maybe a false negative?
using Transform translation = Transform.CreateTranslation(surveyPoint.Position);
referencePointTransform = translation.Multiply(Transform.CreateRotation(XYZ.BasisZ, angle));
}
else
{
throw new InvalidOperationException("Couldn't retrieve Survey and Project Point from document");
}
break;
case ReferencePointType.InternalOrigin:
break;
}
return referencePointTransform;
case ReferencePointType.InternalOrigin:
break;
}
throw new InvalidOperationException(
"Revit Context UI Application was null when retrieving reference point transform."
);
return referencePointTransform;
}
}
@@ -34,8 +34,7 @@ public class CreateSpeckleProperties : VariableParameterComponentBase
pManager.AddParameter(param);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
protected override void RegisterOutputParams(GH_OutputParamManager pManager) =>
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
@@ -43,7 +42,6 @@ public class CreateSpeckleProperties : VariableParameterComponentBase
"Properties for Speckle Objects",
GH_ParamAccess.item
);
}
protected override void SolveInstance(IGH_DataAccess da)
{
@@ -67,8 +65,7 @@ public class CreateSpeckleProperties : VariableParameterComponentBase
{
var paramName = Params.Input[i].NickName;
var data = Params.Input[i].VolatileData.AllData(true).ToList();
if (data.Count == 0)
if (Params.Input[i].VolatileDataCount == 0)
{
continue;
}
@@ -168,10 +165,8 @@ public class CreateSpeckleProperties : VariableParameterComponentBase
ExpireSolution(true);
}
protected override void WriteComponentSpecificData(GH_IWriter writer)
{
protected override void WriteComponentSpecificData(GH_IWriter writer) =>
writer.SetBoolean("CreateEmptyProperties", CreateEmptyProperties);
}
protected override void ReadComponentSpecificData(GH_IReader reader)
{
@@ -89,7 +89,7 @@ public class QuerySpeckleObjects : GH_Component, IGH_VariableParameterComponent
private List<int>? _outputFilterIndices;
// Caches the list of all objects by geometrybase type
private readonly Dictionary<ObjectType, List<SpeckleGeometryWrapper>> _filterDict = new();
private readonly Dictionary<ObjectType, List<SpeckleGeometryWrapper>> _filterDict = [];
protected override void SolveInstance(IGH_DataAccess dataAccess)
{
@@ -104,6 +104,9 @@ public class QuerySpeckleObjects : GH_Component, IGH_VariableParameterComponent
string path = "";
dataAccess.GetData(1, ref path);
// ensure fresh data for type-specific outputs
_filterDict.Clear();
// filter by collection path
// Note: the collection paths selector will omit the target collection from the path of nested collections.
// the discard ("_objects") will be used to indicate objects found directly in the target collection.
@@ -127,10 +130,7 @@ public class QuerySpeckleObjects : GH_Component, IGH_VariableParameterComponent
// sort geometry objects by filters
var geometryObjects = filteredObjects.OfType<SpeckleGeometryWrapper>().ToList();
if (_filterDict.Count == 0)
{
SortObjectsByGeometryBaseType(geometryObjects);
}
SortObjectsByGeometryBaseType(geometryObjects);
// Set output objects
for (int i = 0; i < Params.Output.Count; i++)
@@ -173,13 +173,13 @@ public class QuerySpeckleObjects : GH_Component, IGH_VariableParameterComponent
{
if (_filterDict.Count > 0)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Stored input objects are in an invalid state.");
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Stored input objects are in an invalid state");
return;
}
foreach (ObjectType filter in Filters)
{
_filterDict.Add(filter, new());
_filterDict.Add(filter, []);
}
foreach (var wrapper in objs)
@@ -233,7 +233,7 @@ public class QuerySpeckleObjects : GH_Component, IGH_VariableParameterComponent
// repopulate current output params if needed
if (_outputFilterIndices is null)
{
_outputFilterIndices = new();
_outputFilterIndices = [];
foreach (var output in Params.Output)
{
if (Enum.TryParse(output.Name, out ObjectType filter))
@@ -289,8 +289,5 @@ public class QuerySpeckleObjects : GH_Component, IGH_VariableParameterComponent
base.RemovedFromDocument(document);
}
private void OnParameterSourceChanged(object sender, GH_ParamServerEventArgs args) =>
// an empty filter dict will trigger the SortObjectsByGeometryBaseType method.
// we only want to re-sort objects if an input has changed, not on every trigger of solve instance.
_filterDict.Clear();
private void OnParameterSourceChanged(object sender, GH_ParamServerEventArgs args) { }
}
@@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using GH_IO.Serialization;
using Grasshopper.GUI;
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel;
@@ -297,6 +298,32 @@ public class ReceiveAsyncComponent : GH_AsyncComponent<ReceiveAsyncComponent>
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, e.ToFormattedString());
}
}
public override bool Write(GH_IWriter writer)
{
// call base implementation first
var result = base.Write(writer);
// persist AutoReceive setting
writer.SetBoolean("AutoReceive", AutoReceive);
return result;
}
public override bool Read(GH_IReader reader)
{
// call base implementation first
var result = base.Read(reader);
// restore AutoReceive setting
bool autoReceive = false;
if (reader.TryGetBoolean("AutoReceive", ref autoReceive))
{
AutoReceive = autoReceive;
}
return result;
}
}
public sealed class ReceiveComponentWorker : WorkerInstance<ReceiveAsyncComponent>
@@ -1,5 +1,6 @@
using Grasshopper.Kernel;
using Microsoft.Extensions.DependencyInjection;
using Rhino;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Analytics;
using Speckle.Connectors.Common.Operations;
@@ -12,6 +13,7 @@ using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Connectors.GrasshopperShared.Registration;
using Speckle.Sdk;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Models.Collections;
@@ -31,11 +33,20 @@ public class ReceiveComponentInput
public class ReceiveComponentOutput
{
public SpeckleCollectionWrapperGoo RootObject { get; set; }
/// <remarks>
/// Made nullable as output can be null when Run = false or on error
/// </remarks>
public SpeckleCollectionWrapperGoo? RootObject { get; set; }
}
public class ReceiveComponent : SpeckleTaskCapableComponent<ReceiveComponentInput, ReceiveComponentOutput>
{
private IClient? _apiClient;
private string? _lastVersionId;
private SpeckleUrlModelResource? _lastResource;
public override Guid ComponentGuid => new("74954F59-B1B7-41FD-97DE-4C6B005F2801");
protected override Bitmap Icon => Resources.speckle_operations_syncload;
public ReceiveComponent()
: base(
"(Sync) Load",
@@ -45,9 +56,6 @@ public class ReceiveComponent : SpeckleTaskCapableComponent<ReceiveComponentInpu
ComponentCategories.DEVELOPER
) { }
public override Guid ComponentGuid => new("74954F59-B1B7-41FD-97DE-4C6B005F2801");
protected override Bitmap Icon => Resources.speckle_operations_syncload;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam(GH_ParamAccess.item));
@@ -77,19 +85,28 @@ public class ReceiveComponent : SpeckleTaskCapableComponent<ReceiveComponentInpu
bool run = false;
da.GetData(1, ref run);
return new(url, run);
if (run)
{
SetupSubscription(url);
}
else
{
CleanupSubscription();
}
return new ReceiveComponentInput(url, run);
}
protected override void SetOutput(IGH_DataAccess da, ReceiveComponentOutput result)
{
if (result.RootObject is null)
{
Message = "Not Loaded";
Message = _apiClient != null ? "Monitoring" : "Not Loaded";
}
else
{
da.SetData(0, result.RootObject);
Message = "Done";
Message = _apiClient != null ? "Loaded" : "Done";
}
}
@@ -107,11 +124,11 @@ public class ReceiveComponent : SpeckleTaskCapableComponent<ReceiveComponentInpu
GH_RuntimeMessageLevel.Error,
"Only one model can be loaded at a time. To load to multiple models, please use different load components."
);
return new();
return new ReceiveComponentOutput();
}
if (!input.Run)
{
return new();
return new ReceiveComponentOutput();
}
using var scope = PriorityLoader.CreateScopeForActiveDocument();
@@ -129,6 +146,9 @@ public class ReceiveComponent : SpeckleTaskCapableComponent<ReceiveComponentInpu
using var client = clientFactory.Create(account);
var receiveInfo = await input.Resource.GetReceiveInfo(client, cancellationToken).ConfigureAwait(false);
// store version id for tracking
_lastVersionId = receiveInfo.SelectedVersionId;
var progress = new Progress<CardProgress>(_ =>
{
// TODO: Progress only makes sense in non-blocking async receive, which is not supported yet.
@@ -195,4 +215,116 @@ public class ReceiveComponent : SpeckleTaskCapableComponent<ReceiveComponentInpu
var goo = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
return new ReceiveComponentOutput { RootObject = goo };
}
private void SetupSubscription(SpeckleUrlModelResource resource)
{
// skip if already subscribed to this resource
if (_apiClient != null && _lastResource != null && _lastResource.Equals(resource))
{
return;
}
// only subscribe for Model URLs (not specific versions)
if (resource is SpeckleUrlModelVersionResource)
{
CleanupSubscription();
_lastResource = resource;
return;
}
try
{
CleanupSubscription(); // clean up old subscription first
using var scope = PriorityLoader.CreateScopeForActiveDocument();
var account = resource.Account.GetAccount(scope);
if (account == null)
{
return;
}
_apiClient = scope.Get<IClientFactory>().Create(account);
_apiClient.Subscription.CreateProjectVersionsUpdatedSubscription(resource.ProjectId).Listeners +=
OnVersionCreated;
_lastResource = resource;
Message = "Monitoring";
}
catch (Exception ex) when (!ex.IsFatal())
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Could not setup monitoring: {ex.Message}");
}
}
private void OnVersionCreated(object? sender, ProjectVersionsUpdatedMessage e) =>
// new version detected - trigger reload
RhinoApp.InvokeOnUiThread(
(Action)
delegate
{
ExpireSolution(true);
}
);
private void CleanupSubscription()
{
if (_apiClient != null && _lastResource != null)
{
try
{
_apiClient.Subscription.CreateProjectVersionsUpdatedSubscription(_lastResource.ProjectId).Listeners -=
OnVersionCreated;
}
catch (Exception ex) when (!ex.IsFatal())
{
// ignore cleanup errors
}
_apiClient.Dispose();
_apiClient = null;
}
}
// Cleanup on removal
public override void RemovedFromDocument(GH_Document document)
{
CleanupSubscription();
base.RemovedFromDocument(document);
}
// Handle document context changes
public override void DocumentContextChanged(GH_Document document, GH_DocumentContext context)
{
if (context == GH_DocumentContext.Unloaded)
{
CleanupSubscription();
}
else if (context == GH_DocumentContext.Loaded && _lastResource != null && _apiClient != null)
{
// Check for version changes when document reopens
Task.Run(async () =>
{
try
{
var receiveInfo = await _lastResource.GetReceiveInfo(_apiClient);
if (receiveInfo.SelectedVersionId != _lastVersionId)
{
RhinoApp.InvokeOnUiThread(
(Action)
delegate
{
ExpireSolution(true);
}
);
}
}
catch (Exception ex) when (!ex.IsFatal())
{
// ignore errors during background check
}
});
}
base.DocumentContextChanged(document, context);
}
}
@@ -11,6 +11,7 @@ namespace Speckle.Connectors.GrasshopperShared.Parameters;
/// </summary>
public class SpeckleOutputParam : Param_GenericObject
{
public override GH_Exposure Exposure => GH_Exposure.hidden;
public override Guid ComponentGuid => new("D2B4713D-FE8B-4EF0-8445-B6096DB15B24");
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
@@ -52,6 +52,7 @@ public class SpeckleVariableParam : Param_GenericObject
}
}
public override GH_Exposure Exposure => GH_Exposure.hidden;
public override Guid ComponentGuid => new("A1B2C3D4-E5F6-7890-ABCD-123456789ABC");
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
@@ -4,6 +4,7 @@ using Rhino;
using Rhino.Display;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
@@ -27,7 +28,25 @@ public class SpeckleGeometryWrapper : SpeckleWrapper, ISpeckleCollectionObject
// The list of layer/collection names that forms the full path to this object
public List<string> Path { get; set; } = new();
public SpeckleCollectionWrapper? Parent { get; set; }
public SpecklePropertyGroupGoo Properties { get; set; } = new();
/// <summary>
/// Properties that stay synchronized with Base["properties"]
/// </summary>
public SpecklePropertyGroupGoo Properties
{
get
{
// always create from Base["properties"] like SpeckleDataObjectWrapper does
if (Base[Constants.PROPERTIES_PROP] is Dictionary<string, object?> baseProps)
{
return new SpecklePropertyGroupGoo(baseProps);
}
return new SpecklePropertyGroupGoo(); // return empty if no properties
}
set =>
// unwrap and store in Base["properties"] like SpeckleDataObjectWrapper does
Base[Constants.PROPERTIES_PROP] = value.Unwrap();
}
/// <summary>
/// The color of the <see cref="Base"/>
@@ -5,7 +5,7 @@ namespace Speckle.Converters.CSiShared;
[GenerateAutoInterface]
public class CsiConversionSettingsFactory(
IHostToSpeckleUnitConverter<eUnits> unitsConverter,
IHostToSpeckleUnitConverter<eLength> unitsConverter,
IConverterSettingsStore<CsiConversionSettings> settingsStore
) : ICsiConversionSettingsFactory
{
@@ -15,11 +15,21 @@ public class CsiConversionSettingsFactory(
cSapModel document,
List<string>? selectedLoadCasesAndCombinations = null,
List<string>? selectedResultTypes = null
) =>
new(
)
{
// NOTE: only applicable to ETABS. If we bring in SAP2000 then we need to revert to GetPresentUnits
// NOTE: change from GetPresentUnits as this was linked to weird behaviour (see CNX-2621), returning "0" sometimes
// bug in the GetPresentUnits api call ...
eTemperature temperatureUnit = eTemperature.NotApplicable;
eLength lengthUnit = eLength.NotApplicable;
eForce forceUnit = eForce.NotApplicable;
document.GetPresentUnits_2(ref forceUnit, ref lengthUnit, ref temperatureUnit);
return new CsiConversionSettings(
document,
unitsConverter.ConvertOrThrow(document.GetPresentUnits()),
unitsConverter.ConvertOrThrow(lengthUnit),
selectedLoadCasesAndCombinations ?? [],
selectedResultTypes ?? []
);
}
}
@@ -5,37 +5,28 @@ using Speckle.Sdk.Common.Exceptions;
namespace Speckle.Converters.CSiShared;
/// <summary>
/// Convert CSi eUnits enumeration to Speckle units.
/// Convert CSi eLength enumeration to Speckle units.
/// </summary>
/// <remarks>
/// CSi GetPresentUnits() valid for both SAP 2000 and ETABS.
/// CSi GetPresentUnits_2() valid for ONLY ETABS. If we add SAP2000, this needs to be modified
/// Represents units transmitted through API calls and not necessarily those displayed in GUI.
/// </remarks>
public class CsiToSpeckleUnitConverter : IHostToSpeckleUnitConverter<eUnits>
public class CsiToSpeckleUnitConverter : IHostToSpeckleUnitConverter<eLength>
{
private readonly Dictionary<eUnits, string> _unitMapping = new Dictionary<eUnits, string>();
private readonly Dictionary<eLength, string> _unitMapping = [];
public CsiToSpeckleUnitConverter()
{
_unitMapping[eUnits.lb_in_F] = Units.Inches;
_unitMapping[eUnits.lb_ft_F] = Units.Feet;
_unitMapping[eUnits.kip_in_F] = Units.Inches;
_unitMapping[eUnits.kip_ft_F] = Units.Feet;
_unitMapping[eUnits.kN_mm_C] = Units.Millimeters;
_unitMapping[eUnits.kN_m_C] = Units.Meters;
_unitMapping[eUnits.kgf_mm_C] = Units.Millimeters;
_unitMapping[eUnits.kgf_m_C] = Units.Meters;
_unitMapping[eUnits.N_mm_C] = Units.Millimeters;
_unitMapping[eUnits.N_m_C] = Units.Meters;
_unitMapping[eUnits.Ton_mm_C] = Units.Millimeters;
_unitMapping[eUnits.Ton_m_C] = Units.Meters;
_unitMapping[eUnits.kN_cm_C] = Units.Centimeters;
_unitMapping[eUnits.kgf_cm_C] = Units.Centimeters;
_unitMapping[eUnits.N_cm_C] = Units.Centimeters;
_unitMapping[eUnits.Ton_cm_C] = Units.Centimeters;
_unitMapping[eLength.NotApplicable] = Units.None;
_unitMapping[eLength.inch] = Units.Inches;
_unitMapping[eLength.ft] = Units.Feet;
// _unitMapping[eLength.micron] = Units.None;
_unitMapping[eLength.mm] = Units.Millimeters;
_unitMapping[eLength.cm] = Units.Centimeters;
_unitMapping[eLength.m] = Units.Meters;
}
public string ConvertOrThrow(eUnits hostUnit) =>
public string ConvertOrThrow(eLength hostUnit) =>
_unitMapping.TryGetValue(hostUnit, out string? value)
? value
: throw new UnitNotSupportedException($"The Unit System \"{hostUnit}\" is unsupported.");
@@ -39,7 +39,7 @@ public static class ServiceRegistration
serviceCollection.AddScoped<CsiToSpeckleCacheSingleton>();
// Settings and unit conversions
serviceCollection.AddApplicationConverters<CsiToSpeckleUnitConverter, eUnits>(converterAssembly);
serviceCollection.AddApplicationConverters<CsiToSpeckleUnitConverter, eLength>(converterAssembly);
serviceCollection.AddScoped<
IConverterSettingsStore<CsiConversionSettings>,
ConverterSettingsStore<CsiConversionSettings>
@@ -30,11 +30,14 @@ public static class ObjectPropertyKey
/// </summary>
public static class SectionPropertyCategory
{
public const string DESIGN_DATA = "Design Data";
public const string GENERAL_DATA = "General Data";
public const string MECHANICAL_DATA = "Mechanical 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 WEIGHT_AND_MASS = "Weight and Mass";
}
/// <summary>
@@ -5,13 +5,15 @@ namespace Speckle.Converters.RevitShared.ToSpeckle;
/// </summary>
public class ParameterDefinitionHandler
{
private sealed record GroupDefinition(string Group, string? Units);
private sealed record ParameterDefinition(string GroupName, string? Units);
private sealed record ParameterKey(string InternalName, string Group);
/// <summary>
/// Keeps track of all parameter definitions used in the current send operation. This should be attached to the root commit object post conversion.
/// </summary>
/// POC: Note that we're abusing dictionaries in here because we've yet to have a simple way to serialize non-base derived classes (or structs?)
private readonly Dictionary<string, GroupDefinition> _groupDefinitions = new();
private readonly Dictionary<ParameterKey, ParameterDefinition> _parameterDefinitions = new();
public (string internalDefinitionName, string humanReadableName, string groupName, string? units) HandleDefinition(
DB.Parameter parameter
@@ -37,11 +39,12 @@ public class ParameterDefinitionHandler
internalDefinitionName = builtInParameter.ToString();
}
}
if (_groupDefinitions.TryGetValue(groupDefinitionId, out var def))
var key = new ParameterKey(internalDefinitionName, groupDefinitionId);
if (_parameterDefinitions.TryGetValue(key, out var parameterDefinition))
{
return (internalDefinitionName, humanReadableName, def.Group, def.Units);
return (internalDefinitionName, humanReadableName, parameterDefinition.GroupName, parameterDefinition.Units);
}
var group = DB.LabelUtils.GetLabelForGroup(definition.GetGroupTypeId());
string? units = null;
if (parameter.StorageType == DB.StorageType.Double)
@@ -49,10 +52,7 @@ public class ParameterDefinitionHandler
units = DB.LabelUtils.GetLabelForUnit(parameter.GetUnitTypeId());
}
var group = DB.LabelUtils.GetLabelForGroup(definition.GetGroupTypeId());
_groupDefinitions[groupDefinitionId] = new GroupDefinition(Group: group, Units: units);
_parameterDefinitions[key] = new ParameterDefinition(GroupName: group, Units: units);
return (internalDefinitionName, humanReadableName, group, units);
}
}
@@ -148,7 +148,7 @@ public class ParameterExtractor
{
foreach (DB.Connector conn in cm.Connectors)
{
if (conn.MEPSystem != null)
if (conn.ConnectorType == DB.ConnectorType.Physical && conn.IsConnected && conn.MEPSystem != null)
{
return conn.MEPSystem;
}
@@ -172,7 +172,8 @@ public class ParameterExtractor
// NOTE: general assumption is that ids don't really have much meaning. See [CNX-556: All ID Parameters are send as Name](https://linear.app/speckle/issue/CNX-556/all-id-parameters-are-send-as-name)
// NOTE: subsequent request resulting in certain IDs being brought back. See [CNX-1125](https://linear.app/speckle/issue/CNX-1125/publish-type-id-instead-of-name) in GetValue() method
// "Type ID" (associated with "SYMBOL_ID_PARAM") won't evaluate to true here which is intentional
if (internalDefinitionName.EndsWith("_ID") || internalDefinitionName.EndsWith("_PARAM_ID"))
// NOTE: It seems we are skipping Type Mark parameter because it is called WINDOW_TYPE_ID internally (WTF ADSK).
if (internalDefinitionName.EndsWith("_ID") && !internalDefinitionName.Equals("WINDOW_TYPE_ID"))
{
continue;
}
@@ -1,6 +1,7 @@
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Converters.RevitShared.Settings;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Models;
namespace Speckle.Converters.RevitShared.ToSpeckle;
@@ -32,7 +33,10 @@ public sealed class PointcloudToSpeckleConverter : ITypedConverter<DB.PointCloud
{
var filter = DB.PointClouds.PointCloudFilterFactory.CreateMultiPlaneFilter(new List<DB.Plane>() { minPlane });
var points = target.GetPoints(filter, 0.0001, 999999); // max limit is 1 mil but 1000000 throws error
if (points is null)
{
throw new ConversionException("No points found");
}
var specklePointCloud = new SOG.Pointcloud
{
points = points
@@ -38,6 +38,8 @@ public class BrepToSpeckleConverter : ITypedConverter<RG.Brep, SOG.BrepX>
{
displayValue = displayValue,
encodedValue = brepEncoding,
volume = target.IsSolid ? target.GetVolume() : 0,
area = target.GetArea(),
units = _settingsStore.Current.SpeckleUnits
};
@@ -1,4 +1,4 @@
using Speckle.Converters.Common;
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
namespace Speckle.Converters.Rhino.ToSpeckle.Raw;
@@ -6,17 +6,14 @@ namespace Speckle.Converters.Rhino.ToSpeckle.Raw;
public class EllipseToSpeckleConverter : ITypedConverter<RG.Ellipse, SOG.Ellipse>
{
private readonly ITypedConverter<RG.Plane, SOG.Plane> _planeConverter;
private readonly ITypedConverter<RG.Box, SOG.Box> _boxConverter;
private readonly IConverterSettingsStore<RhinoConversionSettings> _settingsStore;
public EllipseToSpeckleConverter(
ITypedConverter<RG.Plane, SOG.Plane> planeConverter,
ITypedConverter<RG.Box, SOG.Box> boxConverter,
IConverterSettingsStore<RhinoConversionSettings> settingsStore
)
{
_planeConverter = planeConverter;
_boxConverter = boxConverter;
_settingsStore = settingsStore;
}
@@ -39,8 +36,7 @@ public class EllipseToSpeckleConverter : ITypedConverter<RG.Ellipse, SOG.Ellipse
units = _settingsStore.Current.SpeckleUnits,
domain = SOP.Interval.UnitInterval,
length = nurbsCurve.GetLength(),
area = Math.PI * target.Radius1 * target.Radius2,
bbox = _boxConverter.Convert(new RG.Box(nurbsCurve.GetBoundingBox(true)))
area = Math.PI * target.Radius1 * target.Radius2
};
}
}
@@ -1,3 +1,4 @@
using Rhino.Geometry;
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Converters.Rhino.ToSpeckle.Encoding;
@@ -34,8 +35,19 @@ public class ExtrusionToSpeckleConverter : ITypedConverter<RG.Extrusion, SOG.Ext
_settingsStore.Current.Document
);
// get area and volume props
double area = AreaMassProperties.Compute(target).Area;
double volume = 0;
if (target.IsSolid)
{
var volProps = VolumeMassProperties.Compute(target);
volume = volProps.Volume;
}
var bx = new SOG.ExtrusionX()
{
area = area,
volume = volume,
displayValue = displayValue,
encodedValue = extrusionEncoding,
units = _settingsStore.Current.SpeckleUnits
@@ -1,4 +1,4 @@
using Speckle.Converters.Common;
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
namespace Speckle.Converters.Rhino.ToSpeckle.Raw;
@@ -6,17 +6,14 @@ namespace Speckle.Converters.Rhino.ToSpeckle.Raw;
public class LineToSpeckleConverter : ITypedConverter<RG.Line, SOG.Line>, ITypedConverter<RG.LineCurve, SOG.Line>
{
private readonly ITypedConverter<RG.Point3d, SOG.Point> _pointConverter;
private readonly ITypedConverter<RG.Box, SOG.Box> _boxConverter;
private readonly IConverterSettingsStore<RhinoConversionSettings> _settingsStore;
public LineToSpeckleConverter(
ITypedConverter<RG.Point3d, SOG.Point> pointConverter,
ITypedConverter<RG.Box, SOG.Box> boxConverter,
IConverterSettingsStore<RhinoConversionSettings> settingsStore
)
{
_pointConverter = pointConverter;
_boxConverter = boxConverter;
_settingsStore = settingsStore;
}
@@ -34,8 +31,7 @@ public class LineToSpeckleConverter : ITypedConverter<RG.Line, SOG.Line>, ITyped
start = _pointConverter.Convert(target.From),
end = _pointConverter.Convert(target.To),
units = _settingsStore.Current.SpeckleUnits,
domain = new SOP.Interval { start = 0, end = target.Length },
bbox = _boxConverter.Convert(new RG.Box(target.BoundingBox))
domain = new SOP.Interval { start = 0, end = target.Length }
};
public SOG.Line Convert(RG.LineCurve target) => Convert(target.Line);
@@ -1,3 +1,4 @@
using Rhino.Geometry;
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Sdk.Common.Exceptions;
@@ -7,15 +8,10 @@ namespace Speckle.Converters.Rhino.ToSpeckle.Raw;
[NameAndRankValue(typeof(RG.Mesh), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)]
public class MeshToSpeckleConverter : ITypedConverter<RG.Mesh, SOG.Mesh>
{
private readonly ITypedConverter<RG.Box, SOG.Box> _boxConverter;
private readonly IConverterSettingsStore<RhinoConversionSettings> _settingsStore;
public MeshToSpeckleConverter(
ITypedConverter<RG.Box, SOG.Box> boxConverter,
IConverterSettingsStore<RhinoConversionSettings> settingsStore
)
public MeshToSpeckleConverter(IConverterSettingsStore<RhinoConversionSettings> settingsStore)
{
_boxConverter = boxConverter;
_settingsStore = settingsStore;
}
@@ -103,8 +99,9 @@ public class MeshToSpeckleConverter : ITypedConverter<RG.Mesh, SOG.Mesh>
}
}
// get area and volume props
double area = AreaMassProperties.Compute(target).Area;
double volume = target.IsClosed ? target.Volume() : 0;
SOG.Box bbox = _boxConverter.Convert(new RG.Box(target.GetBoundingBox(false)));
return new SOG.Mesh
{
@@ -115,7 +112,7 @@ public class MeshToSpeckleConverter : ITypedConverter<RG.Mesh, SOG.Mesh>
vertexNormals = [.. vertexNormals], // this will be empty array when setting is false
units = _settingsStore.Current.SpeckleUnits,
volume = volume,
bbox = bbox
area = area
};
}
}
@@ -9,19 +9,16 @@ public class NurbsCurveConverter : ITypedConverter<RG.NurbsCurve, SOG.Curve>
{
private readonly ITypedConverter<RG.Polyline, SOG.Polyline> _polylineConverter;
private readonly ITypedConverter<RG.Interval, SOP.Interval> _intervalConverter;
private readonly ITypedConverter<RG.Box, SOG.Box> _boxConverter;
private readonly IConverterSettingsStore<RhinoConversionSettings> _settingsStore;
public NurbsCurveConverter(
ITypedConverter<RG.Polyline, SOG.Polyline> polylineConverter,
ITypedConverter<RG.Interval, SOP.Interval> intervalConverter,
ITypedConverter<RG.Box, SOG.Box> boxConverter,
IConverterSettingsStore<RhinoConversionSettings> settingsStore
)
{
_polylineConverter = polylineConverter;
_intervalConverter = intervalConverter;
_boxConverter = boxConverter;
_settingsStore = settingsStore;
}
@@ -76,8 +73,7 @@ public class NurbsCurveConverter : ITypedConverter<RG.NurbsCurve, SOG.Curve>
rational = nurbsCurve.IsRational,
domain = _intervalConverter.Convert(nurbsCurve.Domain),
closed = nurbsCurve.IsClosed,
length = nurbsCurve.GetLength(),
bbox = _boxConverter.Convert(new RG.Box(nurbsCurve.GetBoundingBox(true)))
length = nurbsCurve.GetLength()
};
return myCurve;
@@ -1,4 +1,4 @@
using Rhino.Geometry.Collections;
using Rhino.Geometry.Collections;
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
@@ -6,19 +6,16 @@ namespace Speckle.Converters.Rhino.ToSpeckle.Raw;
public class NurbsSurfaceToSpeckleConverter : ITypedConverter<RG.NurbsSurface, SOG.Surface>
{
private readonly ITypedConverter<RG.Box, SOG.Box> _boxConverter;
private readonly ITypedConverter<RG.Interval, SOP.Interval> _intervalConverter;
private readonly ITypedConverter<RG.ControlPoint, SOG.ControlPoint> _controlPointConverter;
private readonly IConverterSettingsStore<RhinoConversionSettings> _settingsStore;
public NurbsSurfaceToSpeckleConverter(
ITypedConverter<RG.Box, SOG.Box> boxConverter,
ITypedConverter<RG.Interval, SOP.Interval> intervalConverter,
ITypedConverter<RG.ControlPoint, SOG.ControlPoint> controlPointConverter,
IConverterSettingsStore<RhinoConversionSettings> settingsStore
)
{
_boxConverter = boxConverter;
_intervalConverter = intervalConverter;
_controlPointConverter = controlPointConverter;
_settingsStore = settingsStore;
@@ -43,8 +40,7 @@ public class NurbsSurfaceToSpeckleConverter : ITypedConverter<RG.NurbsSurface, S
domainV = _intervalConverter.Convert(target.Domain(1)),
knotsU = target.KnotsU.ToList(),
knotsV = target.KnotsV.ToList(),
units = _settingsStore.Current.SpeckleUnits,
bbox = _boxConverter.Convert(new RG.Box(target.GetBoundingBox(true))),
units = _settingsStore.Current.SpeckleUnits
};
return result;
@@ -1,4 +1,4 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Objects;
@@ -9,18 +9,15 @@ public class PolyCurveToSpeckleConverter : ITypedConverter<RG.PolyCurve, SOG.Pol
{
private readonly IServiceProvider _serviceProvider;
private readonly ITypedConverter<RG.Interval, SOP.Interval> _intervalConverter;
private readonly ITypedConverter<RG.Box, SOG.Box> _boxConverter;
private readonly IConverterSettingsStore<RhinoConversionSettings> _settingsStore;
public PolyCurveToSpeckleConverter(
ITypedConverter<RG.Interval, SOP.Interval> intervalConverter,
ITypedConverter<RG.Box, SOG.Box> boxConverter,
IConverterSettingsStore<RhinoConversionSettings> settingsStore,
IServiceProvider serviceProvider
)
{
_intervalConverter = intervalConverter;
_boxConverter = boxConverter;
_settingsStore = settingsStore;
_serviceProvider = serviceProvider;
}
@@ -44,7 +41,6 @@ public class PolyCurveToSpeckleConverter : ITypedConverter<RG.PolyCurve, SOG.Pol
closed = target.IsClosed,
domain = _intervalConverter.Convert(target.Domain),
length = target.GetLength(),
bbox = _boxConverter.Convert(new RG.Box(target.GetBoundingBox(true))),
segments = target.DuplicateSegments().Select(x => CurveConverter.Value.Convert(x)).ToList(),
units = _settingsStore.Current.SpeckleUnits
};
@@ -7,17 +7,14 @@ public class PolylineToSpeckleConverter
: ITypedConverter<RG.Polyline, SOG.Polyline>,
ITypedConverter<RG.PolylineCurve, SOG.Polyline>
{
private readonly ITypedConverter<RG.Box, SOG.Box> _boxConverter;
private readonly ITypedConverter<RG.Interval, SOP.Interval> _intervalConverter;
private readonly IConverterSettingsStore<RhinoConversionSettings> _settingsStore;
public PolylineToSpeckleConverter(
ITypedConverter<RG.Box, SOG.Box> boxConverter,
IConverterSettingsStore<RhinoConversionSettings> settingsStore,
ITypedConverter<RG.Interval, SOP.Interval> intervalConverter
)
{
_boxConverter = boxConverter;
_settingsStore = settingsStore;
_intervalConverter = intervalConverter;
}
@@ -30,8 +27,6 @@ public class PolylineToSpeckleConverter
/// <remarks>⚠️ This conversion assumes domain interval is (0,LENGTH) as Rhino Polylines have no domain. If needed, you may want to use PolylineCurve conversion instead. </remarks>
public SOG.Polyline Convert(RG.Polyline target)
{
var box = _boxConverter.Convert(new RG.Box(target.BoundingBox));
var count = target.IsClosed ? target.Count - 1 : target.Count;
List<double> points = new(count * 3);
for (int i = 0; i < count; i++)
@@ -46,7 +41,6 @@ public class PolylineToSpeckleConverter
{
value = points,
units = _settingsStore.Current.SpeckleUnits,
bbox = box,
length = target.Length,
domain = new() { start = 0, end = target.Length },
closed = target.IsClosed
@@ -1,4 +1,4 @@
using Speckle.Converters.Common;
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
namespace Speckle.Converters.Rhino.ToSpeckle.Raw;
@@ -6,15 +6,10 @@ namespace Speckle.Converters.Rhino.ToSpeckle.Raw;
public class RawPointCloudToSpeckle : ITypedConverter<RG.PointCloud, SOG.Pointcloud>
{
private readonly IConverterSettingsStore<RhinoConversionSettings> _settingsStore;
private readonly ITypedConverter<RG.Box, SOG.Box> _boxConverter;
public RawPointCloudToSpeckle(
IConverterSettingsStore<RhinoConversionSettings> settingsStore,
ITypedConverter<RG.Box, SOG.Box> boxConverter
)
public RawPointCloudToSpeckle(IConverterSettingsStore<RhinoConversionSettings> settingsStore)
{
_settingsStore = settingsStore;
_boxConverter = boxConverter;
}
/// <summary>
@@ -27,7 +22,6 @@ public class RawPointCloudToSpeckle : ITypedConverter<RG.PointCloud, SOG.Pointcl
{
points = target.GetPoints().SelectMany(pt => new[] { pt.X, pt.Y, pt.Z }).ToList(),
colors = target.GetColors().Select(o => o.ToArgb()).ToList(),
bbox = _boxConverter.Convert(new RG.Box(target.GetBoundingBox(true))),
units = _settingsStore.Current.SpeckleUnits
};
}
@@ -6,17 +6,14 @@ namespace Speckle.Converters.Rhino.ToSpeckle.Raw;
public class TextEntityToSpeckleConverter : ITypedConverter<RG.TextEntity, SA.Text>
{
private readonly ITypedConverter<RG.Point3d, SOG.Point> _pointConverter;
private readonly ITypedConverter<RG.Plane, SOG.Plane> _planeConverter;
private readonly IConverterSettingsStore<RhinoConversionSettings> _settingsStore;
public TextEntityToSpeckleConverter(
ITypedConverter<RG.Point3d, SOG.Point> pointConverter,
ITypedConverter<RG.Plane, SOG.Plane> planeConverter,
IConverterSettingsStore<RhinoConversionSettings> settingsStore
)
{
_pointConverter = pointConverter;
_planeConverter = planeConverter;
_settingsStore = settingsStore;
}
@@ -1,31 +0,0 @@
using Speckle.Sdk.SQLite;
namespace Speckle.Importers.Ifc.Tester;
public sealed class DummySendCacheManager(Dictionary<string, string> objects) : ISqLiteJsonCacheManager
{
public void Dispose() { }
public IReadOnlyCollection<(string, string)> GetAllObjects() => throw new NotImplementedException();
public void DeleteObject(string id) => throw new NotImplementedException();
public string? GetObject(string id) => null;
public void SaveObject(string id, string json) => throw new NotImplementedException();
public bool HasObject(string objectId) => false;
#pragma warning disable CA1065
public string Path => throw new NotImplementedException();
#pragma warning restore CA1065
public void SaveObjects(IEnumerable<(string id, string json)> items)
{
foreach (var (id, json) in items)
{
objects[id] = json;
}
}
public void UpdateObject(string id, string json) => throw new NotImplementedException();
}
@@ -1,43 +0,0 @@
using System.Text;
using Speckle.Sdk.Serialisation.V2;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.Transports;
namespace Speckle.Importers.Ifc.Tester;
public class DummyServerObjectManager : IServerObjectManager
{
public IAsyncEnumerable<(string, string)> DownloadObjects(
IReadOnlyCollection<string> objectIds,
IProgress<ProgressArgs>? progress,
CancellationToken cancellationToken
) => throw new NotImplementedException();
public Task<string?> DownloadSingleObject(
string objectId,
IProgress<ProgressArgs>? progress,
CancellationToken cancellationToken
) => throw new NotImplementedException();
public Task<Dictionary<string, bool>> HasObjects(
IReadOnlyCollection<string> objectIds,
CancellationToken cancellationToken
) => Task.FromResult(objectIds.ToDictionary(id => id, id => false));
public Task UploadObjects(
IReadOnlyList<BaseItem> objects,
bool compressPayloads,
IProgress<ProgressArgs>? progress,
CancellationToken cancellationToken
)
{
long totalBytes = 0;
foreach (var item in objects)
{
totalBytes += Encoding.Default.GetByteCount(item.Json.Value);
}
progress?.Report(new(ProgressEvent.UploadBytes, totalBytes, totalBytes));
return Task.CompletedTask;
}
}
@@ -1,66 +0,0 @@
#pragma warning disable CA1506
using System.Diagnostics;
using Ara3D.Utils;
//using JetBrains.Profiler.SelfApi;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Importers.Ifc;
using Speckle.Importers.Ifc.Ara3D.IfcParser;
using Speckle.Importers.Ifc.Converters;
using Speckle.Importers.Ifc.Tester;
using Speckle.Importers.Ifc.Types;
using Speckle.Sdk.Serialisation.V2;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.SQLite;
var serviceProvider = Import.GetServiceProvider();
//DotMemory.Init();
var filePath = new FilePath(
//"C:\\Users\\adam\\Git\\speckle-server\\packages\\fileimport-service\\ifc-dotnet\\ifcs\\20210221PRIMARK.ifc"
//"C:\\Users\\adam\\Git\\speckle-server\\packages\\fileimport-service\\ifc-dotnet\\ifcs\\231110ADT-FZK-Haus-2005-2006.ifc"
//"C:\\Users\\adam\\Downloads\\T03PV06IMPMI01C.ifc"
"C:\\Users\\adam\\Downloads\\20231128_HW_Bouwkosten.ifc"
);
var ifcFactory = serviceProvider.GetRequiredService<IIfcFactory>();
var stopwatch = Stopwatch.StartNew();
Console.WriteLine($"Opening with WebIFC: {filePath}");
var model = ifcFactory.Open(filePath);
var ms = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"Opened with WebIFC: {ms} ms");
var graph = IfcGraph.Load(new FilePath(filePath));
var ms2 = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"Loaded with StepParser: {ms2 - ms} ms");
var converter = serviceProvider.GetRequiredService<IGraphConverter>();
var b = converter.Convert(model, graph);
ms = ms2;
ms2 = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"Converted to Speckle Bases: {ms2 - ms} ms");
var factory = serviceProvider.GetRequiredService<ISerializeProcessFactory>();
var cache = $"C:\\Users\\adam\\Git\\temp\\{Guid.NewGuid()}.db";
using var sqlite = SqLiteJsonCacheManager.FromFilePath(cache, 2);
await using var process2 = factory.CreateSerializeProcess(
sqlite,
new DummyServerObjectManager(),
new Progress(true),
default,
new SerializeProcessOptions(SkipServer: true)
);
Console.WriteLine($"Caching to Speckle: {cache}");
/*var config = new DotMemory.Config();
config.OpenDotMemory();
config.SaveToDir("C:\\Users\\adam\\dotTraceSnapshots");
DotMemory.Attach(config);
DotMemory.GetSnapshot("Before");*/
var (rootId, _) = await process2.Serialize(b).ConfigureAwait(false);
Console.WriteLine(rootId);
ms2 = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"Converted to JSON: {ms2 - ms} ms");
//DotMemory.GetSnapshot("After");
//DotMemory.Detach();
#pragma warning restore CA1506
@@ -1,36 +0,0 @@
using Speckle.Sdk.Transports;
namespace Speckle.Importers.Ifc.Tester;
public class Progress(bool write) : IProgress<ProgressArgs>
{
private readonly TimeSpan _debounce = TimeSpan.FromMilliseconds(1000);
private DateTime _lastTime = DateTime.UtcNow;
private long _totalBytes;
public void Report(ProgressArgs value)
{
if (write)
{
if (value.ProgressEvent == ProgressEvent.DownloadBytes)
{
Interlocked.Add(ref _totalBytes, value.Count);
}
var now = DateTime.UtcNow;
if (now - _lastTime >= _debounce)
{
if (value.ProgressEvent == ProgressEvent.DownloadBytes)
{
Console.WriteLine(value.ProgressEvent + " t " + _totalBytes);
}
else
{
Console.WriteLine(value.ProgressEvent + " c " + value.Count + " t " + value.Total);
}
_lastTime = now;
}
}
}
}
@@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Configurations>Debug;Release;Local</Configurations>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Speckle.Importers.Ifc\Speckle.Importers.Ifc.csproj" />
</ItemGroup>
</Project>
@@ -1,316 +0,0 @@
{
"version": 2,
"dependencies": {
"net8.0": {
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"PolySharp": {
"type": "Direct",
"requested": "[1.14.1, )",
"resolved": "1.14.1",
"contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ=="
},
"Speckle.InterfaceGenerator": {
"type": "Direct",
"requested": "[0.9.6, )",
"resolved": "0.9.6",
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
},
"GraphQL.Client": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0",
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
"System.Reactive": "5.0.0"
}
},
"GraphQL.Client.Abstractions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==",
"dependencies": {
"GraphQL.Primitives": "6.0.0"
}
},
"GraphQL.Client.Abstractions.Websocket": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0"
}
},
"GraphQL.Primitives": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.Data.Sqlite": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "KGxbPeWsQMnmQy43DSBxAFtHz3l2JX8EWBSGUCvT3CuZ8KsuzbkqMIJMDOxWtG8eZSoCDI04aiVQjWuuV8HmSw==",
"dependencies": {
"Microsoft.Data.Sqlite.Core": "7.0.5",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
}
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "FTerRmQPqHrCrnoUzhBu+E+1DNGwyrAMLqHkAqOOOu5pGfyMOj8qQUBxI/gDtWtG11p49UxSfWmBzRNlwZqfUg==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==",
"dependencies": {
"Microsoft.Extensions.Primitives": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Binder": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==",
"dependencies": {
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Primitives": "2.2.0",
"System.ComponentModel.Annotations": "4.5.0"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==",
"dependencies": {
"System.Memory": "4.5.1",
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
}
},
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
"SQLitePCLRaw.provider.e_sqlite3": "2.1.4"
}
},
"SQLitePCLRaw.core": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==",
"dependencies": {
"System.Memory": "4.5.3"
}
},
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
},
"SQLitePCLRaw.provider.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
},
"System.Reactive": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
}
},
"speckle.connectors.logging": {
"type": "Project"
},
"speckle.importers.ifc": {
"type": "Project",
"dependencies": {
"Ara3D.Buffers": "[1.4.5, )",
"Ara3D.Logging": "[1.4.5, )",
"Ara3D.Utils": "[1.4.5, )",
"Microsoft.Extensions.DependencyInjection": "[8.0.0, )",
"Speckle.Connectors.Common": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )"
}
},
"Ara3D.Buffers": {
"type": "CentralTransitive",
"requested": "[1.4.5, )",
"resolved": "1.4.5",
"contentHash": "SKcQqgtXukyHTlTKFPCaUW4spSkue3XfBU/GmoA7KhH6H995v6TbJxtqjs0EfSgnXEkajL8U7X1NqktScRozXw==",
"dependencies": {
"System.Memory": "4.5.5"
}
},
"Ara3D.Logging": {
"type": "CentralTransitive",
"requested": "[1.4.5, )",
"resolved": "1.4.5",
"contentHash": "7HPCe5Dq21JoOBF1iclk9H37XFCoB2ZzCPqTMNgdg4PWFvuRsofNbiuMdiE/HKgMHCVhy1C5opB2KwDKcO7Axw==",
"dependencies": {
"Ara3D.Utils": "1.4.5"
}
},
"Ara3D.Utils": {
"type": "CentralTransitive",
"requested": "[1.4.5, )",
"resolved": "1.4.5",
"contentHash": "yba/E7PpbWP0+RDp+KbKw/vBXnXBSIheScdpVKuDnr8ytRg8pZ2Jd6nwKES+G0FcVEB9PeOVmEW7SGrFvAwRCg=="
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "8.0.0",
"contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
}
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==",
"dependencies": {
"Microsoft.Extensions.Configuration.Binder": "2.2.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging.Abstractions": "2.2.0",
"Microsoft.Extensions.Options": "2.2.0"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
}
}
}
}
@@ -1,75 +0,0 @@
using Ara3D.Utils;
using Speckle.Sdk.Api;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Models.Extensions;
namespace Speckle.Importers.Ifc.Tester2;
/// <summary>
/// This is a test file for testing the IFC importer locally
/// Except for the logic in the preview service, this is pretty much exactly the same as what is running when
/// you upload an ifc file.
/// </summary>
/// <param name="clientFactory"></param>
/// <param name="importer"></param>
/// <param name="accountManager"></param>
public sealed class IfcTester(IClientFactory clientFactory, Importer importer, IAccountManager accountManager)
{
// Settings, Change these to suit!
// private readonly ICollection<FilePath> _filePath = [new(@"C:\Users\Jedd\Desktop\GRAPHISOFT_Archicad_Sample_Project-S-Office_v1.0_AC25.ifc")]
private readonly IEnumerable<string> _filePaths = Directory.EnumerateFiles(
@"C:\Users\Jedd\Desktop\big files",
"*.ifc"
);
private readonly Uri _serverUrl = new("https://app.speckle.systems");
private const string PROJECT_ID = "f3a42bdf24";
public async Task Run(CancellationToken cancellationToken = default)
{
var account = accountManager.GetAccounts(_serverUrl).First();
using var speckleClient = clientFactory.Create(account);
foreach (var path in _filePaths)
{
try
{
await ImportFile(speckleClient, path, cancellationToken);
}
#pragma warning disable CA1031
catch (Exception ex)
#pragma warning restore CA1031
{
Console.WriteLine(ex.ToFormattedString());
}
}
}
private async Task ImportFile(IClient speckleClient, FilePath filePath, CancellationToken cancellationToken)
{
string modelName = filePath.GetFileName();
var existing = await speckleClient.Project.GetWithModels(
PROJECT_ID,
1,
modelsFilter: new(search: modelName),
cancellationToken: cancellationToken
);
string? existingModel = existing.models.items.Count >= 1 ? existing.models.items.First().id : null;
// Convert IFC to Speckle Objects
ImporterArgs args =
new()
{
ServerUrl = _serverUrl,
FilePath = filePath.ToString(),
ProjectId = PROJECT_ID,
ModelId = existingModel,
ModelName = filePath.GetFileName(),
VersionMessage = "",
Token = speckleClient.Account.token
};
var version = await importer.ImportIfc(args, null, cancellationToken);
Console.WriteLine($"File was successfully sent {version.id}");
}
}
@@ -1,12 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Importers.Ifc;
using Speckle.Importers.Ifc.Tester2;
// This is all DI setup, Look in IfcTester.cs for the real goodies
var serviceCollection = new ServiceCollection();
serviceCollection.AddIFCImporter();
serviceCollection.AddSingleton<IfcTester>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var tester = serviceProvider.GetRequiredService<IfcTester>();
await tester.Run();
@@ -1,11 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Speckle.Importers.Ifc\Speckle.Importers.Ifc.csproj" />
</ItemGroup>
</Project>
@@ -1,316 +0,0 @@
{
"version": 2,
"dependencies": {
"net8.0": {
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"PolySharp": {
"type": "Direct",
"requested": "[1.14.1, )",
"resolved": "1.14.1",
"contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ=="
},
"Speckle.InterfaceGenerator": {
"type": "Direct",
"requested": "[0.9.6, )",
"resolved": "0.9.6",
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
},
"GraphQL.Client": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0",
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
"System.Reactive": "5.0.0"
}
},
"GraphQL.Client.Abstractions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==",
"dependencies": {
"GraphQL.Primitives": "6.0.0"
}
},
"GraphQL.Client.Abstractions.Websocket": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0"
}
},
"GraphQL.Primitives": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.Data.Sqlite": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "KGxbPeWsQMnmQy43DSBxAFtHz3l2JX8EWBSGUCvT3CuZ8KsuzbkqMIJMDOxWtG8eZSoCDI04aiVQjWuuV8HmSw==",
"dependencies": {
"Microsoft.Data.Sqlite.Core": "7.0.5",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
}
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "FTerRmQPqHrCrnoUzhBu+E+1DNGwyrAMLqHkAqOOOu5pGfyMOj8qQUBxI/gDtWtG11p49UxSfWmBzRNlwZqfUg==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==",
"dependencies": {
"Microsoft.Extensions.Primitives": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Binder": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==",
"dependencies": {
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Primitives": "2.2.0",
"System.ComponentModel.Annotations": "4.5.0"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==",
"dependencies": {
"System.Memory": "4.5.1",
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
}
},
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
"SQLitePCLRaw.provider.e_sqlite3": "2.1.4"
}
},
"SQLitePCLRaw.core": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==",
"dependencies": {
"System.Memory": "4.5.3"
}
},
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
},
"SQLitePCLRaw.provider.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
},
"System.Reactive": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
}
},
"speckle.connectors.logging": {
"type": "Project"
},
"speckle.importers.ifc": {
"type": "Project",
"dependencies": {
"Ara3D.Buffers": "[1.4.5, )",
"Ara3D.Logging": "[1.4.5, )",
"Ara3D.Utils": "[1.4.5, )",
"Microsoft.Extensions.DependencyInjection": "[8.0.0, )",
"Speckle.Connectors.Common": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )"
}
},
"Ara3D.Buffers": {
"type": "CentralTransitive",
"requested": "[1.4.5, )",
"resolved": "1.4.5",
"contentHash": "SKcQqgtXukyHTlTKFPCaUW4spSkue3XfBU/GmoA7KhH6H995v6TbJxtqjs0EfSgnXEkajL8U7X1NqktScRozXw==",
"dependencies": {
"System.Memory": "4.5.5"
}
},
"Ara3D.Logging": {
"type": "CentralTransitive",
"requested": "[1.4.5, )",
"resolved": "1.4.5",
"contentHash": "7HPCe5Dq21JoOBF1iclk9H37XFCoB2ZzCPqTMNgdg4PWFvuRsofNbiuMdiE/HKgMHCVhy1C5opB2KwDKcO7Axw==",
"dependencies": {
"Ara3D.Utils": "1.4.5"
}
},
"Ara3D.Utils": {
"type": "CentralTransitive",
"requested": "[1.4.5, )",
"resolved": "1.4.5",
"contentHash": "yba/E7PpbWP0+RDp+KbKw/vBXnXBSIheScdpVKuDnr8ytRg8pZ2Jd6nwKES+G0FcVEB9PeOVmEW7SGrFvAwRCg=="
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "8.0.0",
"contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
}
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==",
"dependencies": {
"Microsoft.Extensions.Configuration.Binder": "2.2.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging.Abstractions": "2.2.0",
"Microsoft.Extensions.Options": "2.2.0"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
}
}
}
}
@@ -1,190 +0,0 @@
using System.Text;
using Ara3D.Buffers;
using Speckle.Importers.Ifc.Ara3D.StepParser;
namespace Speckle.Importers.Ifc.Ara3D.IfcParser;
public static class IfcExtensions
{
public static uint AsId(this StepValue value) => value is StepUnassigned ? 0u : ((StepId)value).Id;
public static string AsString(this StepValue value) =>
value is StepString ss
? ss.AsString()
: value is StepNumber sn
? sn.Value.ToString()
: value is StepId si
? si.Id.ToString()
: value is StepSymbol ssm
? ssm.Name.ToString()
: "";
public static double AsNumber(this StepValue value) => value is StepUnassigned ? 0 : ((StepNumber)value).Value;
public static List<StepValue> AsList(this StepValue value) =>
value is StepUnassigned ? new List<StepValue>() : ((StepList)value).Values;
public static List<uint> AsIdList(this StepValue value) =>
value is StepUnassigned ? new List<uint>() : value.AsList().Select(AsId).ToList();
// Uses Latin1 encoding (aka ISO-8859-1)
// Extended characters converted using an IFC specific system
public static string AsString(this ByteSpan span) => Encoding.Latin1.GetString(span.ToSpan()).IfcToUnicode();
// https://technical.buildingsmart.org/resources/ifcimplementationguidance/string-encoding/
public static string IfcToUnicode(this string input)
{
if (!input.Contains('\\'))
return input;
var output = new StringBuilder();
var i = 0;
var length = input.Length;
while (i < length)
{
if (input[i] != '\\')
{
// Regular character, append to output
output.Append(input[i++]);
continue;
}
i++; // Move past '\'
if (i >= length)
{
output.Append('\\');
break;
}
var escapeChar = input[i++];
if (escapeChar == 'S' && i < length && input[i] == '\\')
{
i++; // Move past '\'
if (i < length)
{
var c = input[i++];
var code = c + 128;
output.Append((char)code);
}
else
{
output.Append("\\S\\");
}
continue;
}
if (escapeChar == 'X')
{
if (i < length && input[i] == '\\')
{
// Handle \X\XX escape sequence (8-bit character code)
i++; // Move past '\'
if (i + 1 < length)
{
var hex = input.Substring(i, 2);
i += 2;
var code = Convert.ToInt32(hex, 16);
output.Append((char)code);
}
else
{
output.Append("\\X\\");
}
continue;
}
// Handle extended \Xn\...\X0\ escape sequence
// Skip 'n' until the next '\'
while (i < length && input[i] != '\\')
i++;
if (i < length)
i++; // Move past '\'
// Collect hex digits until '\X0\'
var hexDigits = new StringBuilder();
while (i + 3 <= length && input.Substring(i, 3) != "\\X0")
{
hexDigits.Append(input[i++]);
}
if (i + 3 <= length && input.Substring(i, 3) == "\\X0")
{
i += 3; // Move past '\X0'
if (i < length && input[i] == '\\')
i++; // Move past '\'
var hexStr = hexDigits.ToString();
// Process hex digits in chunks of 4 (representing Unicode code points)
for (var k = 0; k + 4 <= hexStr.Length; k += 4)
{
var codeHex = hexStr.Substring(k, 4);
var code = Convert.ToInt32(codeHex, 16);
output.Append(char.ConvertFromUtf32(code));
}
continue;
}
// Invalid format, append as is
output.Append("\\X");
continue;
}
// Unrecognized escape sequence, append as is
output.Append('\\').Append(escapeChar);
}
return output.ToString();
}
public static string AsString(this StepString ss) => ss.Value.AsString();
public static object? ToJsonObject(this StepValue sv)
{
switch (sv)
{
case StepEntity stepEntity:
{
var attr = stepEntity.Attributes;
if (attr.Values.Count == 0)
return stepEntity.ToString();
if (attr.Values.Count == 1)
return attr.Values[0].ToJsonObject();
return attr.Values.Select(ToJsonObject).ToList();
}
case StepId stepId:
return stepId.Id;
case StepList stepList:
return stepList.Values.Select(ToJsonObject).ToList();
case StepNumber stepNumber:
return stepNumber.AsNumber();
case StepRedeclared stepRedeclared:
return null;
case StepString stepString:
return stepString.AsString();
case StepSymbol stepSymbol:
var tmp = stepSymbol.Name.AsString();
if (tmp == "T")
return true;
if (tmp == "F")
return false;
return tmp;
case StepUnassigned stepUnassigned:
return null;
default:
throw new ArgumentOutOfRangeException(nameof(sv));
}
}
}
@@ -1,66 +0,0 @@
using Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
using Speckle.Importers.Ifc.Ara3D.StepParser;
namespace Speckle.Importers.Ifc.Ara3D.IfcParser;
/// <summary>
/// It represents an entity definition. It is usually a single line in a STEP file.
/// Many entity definitions are derived from IfcRoot (including relations).
/// IfcRoot has a GUID, OwnerId, optional Name, and optional Description
/// https://iaiweb.lbl.gov/Resources/IFC_Releases/R2x3_final/ifckernel/lexical/ifcroot.htm
/// </summary>
public class IfcEntity
{
public StepInstance LineData { get; }
public IfcGraph Graph { get; }
public uint Id => LineData.Id;
public string Type => LineData?.EntityType ?? "";
public IfcEntity(IfcGraph graph, StepInstance lineData)
{
Graph = graph;
LineData = lineData;
}
public override bool Equals(object? obj)
{
if (obj is IfcEntity other)
return Id == other.Id;
return false;
}
public override int GetHashCode() => (int)Id;
public override string ToString() => $"{Type}#{Id}";
public bool IsIfcRoot => Count >= 4 && this[0] is StepString && (this[1] is StepId) || (this[1] is StepUnassigned);
// Modern IFC files conform to this, but older ones have been observed to have different length IDs.
// Leaving as a comment for now.
//&& str.Value.Length == 22;
public string Guid => ((StepString)this[0]).Value.ToString();
public uint OwnerId => (this[1] as StepId)?.Id ?? 0;
public string? Name => (this[2] as StepString)?.AsString();
public string? Description => (this[3] as StepString)?.AsString();
public int Count => LineData.Count;
public StepValue this[int i] => LineData[i];
public IReadOnlyList<IfcRelation> GetOutgoingRelations() => Graph.GetRelationsFrom(Id);
public IEnumerable<IfcNode> GetAggregatedChildren() =>
GetOutgoingRelations().OfType<IfcRelationAggregate>().SelectMany(r => r.GetRelatedNodes());
public IEnumerable<IfcNode> GetSpatialChildren() =>
GetOutgoingRelations().OfType<IfcRelationSpatial>().SelectMany(r => r.GetRelatedNodes());
public IEnumerable<IfcNode> GetChildren() => GetAggregatedChildren().Concat(GetSpatialChildren()).Distinct();
public IReadOnlyList<IfcPropSet> GetPropSets() =>
Graph.PropertySetsByNode.TryGetValue(Id, out var list) ? list : Array.Empty<IfcPropSet>();
}
@@ -1,260 +0,0 @@
using System.Diagnostics;
using Ara3D.Logging;
using Ara3D.Utils;
using Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
using Speckle.Importers.Ifc.Ara3D.StepParser;
namespace Speckle.Importers.Ifc.Ara3D.IfcParser;
/// <summary>
/// This is a high-level representation of an IFC model as a graph of nodes and relations.
/// It also contains the properties, and property sets.
/// </summary>
public sealed class IfcGraph
{
public static IfcGraph Load(FilePath fp, ILogger? logger = null) =>
new IfcGraph(new StepDocument(fp, logger), logger);
public StepDocument Document { get; }
public Dictionary<uint, IfcNode> Nodes { get; } = new Dictionary<uint, IfcNode>();
public List<IfcRelation> Relations { get; } = new List<IfcRelation>();
public Dictionary<uint, List<IfcRelation>> RelationsByNode { get; } = new Dictionary<uint, List<IfcRelation>>();
public Dictionary<uint, List<IfcPropSet>> PropertySetsByNode { get; } = new Dictionary<uint, List<IfcPropSet>>();
public uint IfcProjectId { get; }
public IfcNode AddNode(IfcNode n) => Nodes[n.Id] = n;
public IfcRelation AddRelation(IfcRelation r)
{
Relations.Add(r);
var id = r.From.Id;
if (!RelationsByNode.ContainsKey(id))
RelationsByNode[id] = new();
RelationsByNode[id].Add(r);
return r;
}
public IfcGraph(StepDocument d, ILogger? logger = null)
{
Document = d;
uint ifcProjectId = 0;
logger?.Log("Computing entities");
foreach (var inst in Document.RawInstances)
{
if (!inst.IsValid())
continue;
// Property Values
if (inst.Type.Equals("IFCPROPERTYSINGLEVALUE"))
{
var e = d.GetInstanceWithData(inst);
AddNode(new IfcProp(this, e, e[2]));
}
else if (inst.Type.Equals("IFCPROPERTYENUMERATEDVALUE"))
{
var e = d.GetInstanceWithData(inst);
AddNode(new IfcProp(this, e, e[2]));
}
else if (inst.Type.Equals("IFCPROPERTYREFERENCEVALUE"))
{
var e = d.GetInstanceWithData(inst);
AddNode(new IfcProp(this, e, e[3]));
}
else if (inst.Type.Equals("IFCPROPERTYLISTVALUE"))
{
var e = d.GetInstanceWithData(inst);
AddNode(new IfcProp(this, e, e[2]));
}
else if (inst.Type.Equals("IFCCOMPLEXPROPERTY"))
{
var e = d.GetInstanceWithData(inst);
AddNode(new IfcProp(this, e, e[3]));
}
// Quantities which are a treated as a kind of prop
// https://iaiweb.lbl.gov/Resources/IFC_Releases/R2x3_final/ifcquantityresource/lexical/ifcphysicalquantity.htm
else if (inst.Type.Equals("IFCQUANTITYLENGTH"))
{
// https://iaiweb.lbl.gov/Resources/IFC_Releases/R2x3_final/ifcquantityresource/lexical/ifcquantitylength.htm
var e = d.GetInstanceWithData(inst);
AddNode(new IfcProp(this, e, e[3]));
}
else if (inst.Type.Equals("IFCQUANTITYAREA"))
{
// https://iaiweb.lbl.gov/Resources/IFC_Releases/R2x3_final/ifcquantityresource/lexical/ifcquantityarea.htm
var e = d.GetInstanceWithData(inst);
AddNode(new IfcProp(this, e, e[3]));
}
else if (inst.Type.Equals("IFCQUANTITYVOLUME"))
{
// https://iaiweb.lbl.gov/Resources/IFC_Releases/R2x3_final/ifcquantityresource/lexical/ifcquantityvolume.htm
var e = d.GetInstanceWithData(inst);
AddNode(new IfcProp(this, e, e[3]));
}
else if (inst.Type.Equals("IFCQUANTITYCOUNT"))
{
// https://iaiweb.lbl.gov/Resources/IFC_Releases/R2x3_final/ifcquantityresource/lexical/ifcquantitycount.htm
var e = d.GetInstanceWithData(inst);
AddNode(new IfcProp(this, e, e[3]));
}
else if (inst.Type.Equals("IFCQUANTITYWEIGHT"))
{
// https://iaiweb.lbl.gov/Resources/IFC_Releases/R2x3_final/ifcquantityresource/lexical/ifcquantityweight.htm
var e = d.GetInstanceWithData(inst);
AddNode(new IfcProp(this, e, e[3]));
}
else if (inst.Type.Equals("IFCQUANTITYTIME"))
{
// https://iaiweb.lbl.gov/Resources/IFC_Releases/R2x3_final/ifcquantityresource/lexical/ifcquantitytime.htm
var e = d.GetInstanceWithData(inst);
AddNode(new IfcProp(this, e, e[3]));
}
else if (inst.Type.Equals("IFCPHYSICALCOMPLEXQUANTITY"))
{
//https://iaiweb.lbl.gov/Resources/IFC_Releases/R2x3_final/ifcquantityresource/lexical/ifcphysicalcomplexquantity.htm
var e = d.GetInstanceWithData(inst);
AddNode(new IfcProp(this, e, e[2]));
}
// Property Set (or element quantity)
else if (inst.Type.Equals("IFCPROPERTYSET"))
{
var e = d.GetInstanceWithData(inst);
AddNode(new IfcPropSet(this, e, (StepList)e[4]));
}
else if (inst.Type.Equals("IFCELEMENTQUANTITY"))
{
var e = d.GetInstanceWithData(inst);
AddNode(new IfcPropSet(this, e, e[5] as StepList));
}
// Aggregate relation
else if (inst.Type.Equals("IFCRELAGGREGATES"))
{
var e = d.GetInstanceWithData(inst);
AddRelation(new IfcRelationAggregate(this, e, (StepId)e[4], (StepList)e[5]));
}
// Spatial relation
else if (inst.Type.Equals("IFCRELCONTAINEDINSPATIALSTRUCTURE"))
{
var e = d.GetInstanceWithData(inst);
AddRelation(new IfcRelationSpatial(this, e, (StepId)e[5], (StepList)e[4]));
}
// Property set relations
else if (inst.Type.Equals("IFCRELDEFINESBYPROPERTIES"))
{
var e = d.GetInstanceWithData(inst);
AddRelation(new IfcPropSetRelation(this, e, (StepId)e[5], (StepList)e[4]));
}
// Type relations
else if (inst.Type.Equals("IFCRELDEFINESBYTYPE"))
{
var e = d.GetInstanceWithData(inst);
AddRelation(new IfcRelationType(this, e, (StepId)e[5], (StepList)e[4]));
}
else if (inst.Type.Equals("IFCPROJECT"))
{
//Special case for IFC Projects, track them as a root node.
var e = d.GetInstanceWithData(inst);
ifcProjectId = inst.Id;
AddNode(new IfcProject(this, e));
}
else if (
inst.Type.Equals("IFCSITE")
|| inst.Type.Equals("IFCBUILDING")
|| inst.Type.Equals("IFCBUILDINGSTOREY")
|| inst.Type.Equals("IFCFACILITY")
|| inst.Type.Equals("IFCFACILITYPART")
|| inst.Type.Equals("IFCBRIDGE")
|| inst.Type.Equals("IFCROAD")
|| inst.Type.Equals("IFCRAILWAY")
|| inst.Type.Equals("IFCMARINEFACILITY")
)
{
var e = d.GetInstanceWithData(inst);
AddNode(new IfcSpatialStructureElement(this, e));
}
// Everything else
else
{
// Simple IFC node: without step entity data.
var e = d.GetInstanceWithData(inst);
AddNode(new IfcNode(this, e));
}
}
if (ifcProjectId <= 0)
throw new SpeckleIfcException("There was no IfcProject in the file");
IfcProjectId = ifcProjectId;
logger?.Log("Creating lookup of property sets");
foreach (var psr in Relations.OfType<IfcPropSetRelation>())
{
var ps = psr.PropSet;
foreach (var id in psr.GetRelatedIds())
{
if (!PropertySetsByNode.ContainsKey(id))
PropertySetsByNode[id] = [];
PropertySetsByNode[id].Add(ps);
}
}
logger?.Log("Completed creating model graph");
}
public IEnumerable<IfcNode> GetNodes() => Nodes.Values;
public IEnumerable<IfcNode> GetNodes(IEnumerable<uint> ids) => ids.Select(GetNode);
public IfcNode GetOrCreateNode(StepInstance lineData, int arg)
{
if (arg < 0 || arg >= lineData.AttributeValues.Count)
throw new SpeckleIfcException("Argument index out of range");
return GetOrCreateNode(lineData.AttributeValues[arg]);
}
public IfcNode GetOrCreateNode(StepValue o) =>
GetOrCreateNode(o is StepId id ? id.Id : throw new SpeckleIfcException($"Expected a StepId value, not {o}"));
public IfcNode GetOrCreateNode(uint id)
{
var r = Nodes.TryGetValue(id, out var node) ? node : AddNode(new IfcNode(this, Document.GetInstanceWithData(id)));
Debug.Assert(r.Id == id);
return r;
}
public List<IfcNode> GetOrCreateNodes(List<StepValue> list) => list.Select(GetOrCreateNode).ToList();
public List<IfcNode> GetOrCreateNodes(StepInstance line, int arg)
{
if (arg < 0 || arg >= line.AttributeValues.Count)
throw new SpeckleIfcException("Argument out of range");
if (line.AttributeValues[arg] is not StepList agg)
throw new SpeckleIfcException("Expected a list");
return GetOrCreateNodes(agg.Values);
}
public IfcNode GetNode(StepId id) => GetNode(id.Id);
public IfcNode GetNode(uint id)
{
var r = Nodes[id];
Debug.Assert(r.Id == id);
return r;
}
public IfcNode GetIfcProject() => GetNode(IfcProjectId);
public IEnumerable<IfcPropSet> GetPropSets() => GetNodes().OfType<IfcPropSet>();
public IEnumerable<IfcProp> GetProps() => GetNodes().OfType<IfcProp>();
public IEnumerable<IfcRelationSpatial> GetSpatialRelations() => Relations.OfType<IfcRelationSpatial>();
public IEnumerable<IfcRelationAggregate> GetAggregateRelations() => Relations.OfType<IfcRelationAggregate>();
public IReadOnlyList<IfcRelation> GetRelationsFrom(uint id) =>
RelationsByNode.TryGetValue(id, out var list) ? list : Array.Empty<IfcRelation>();
}
@@ -1,22 +0,0 @@
using Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
using Speckle.Importers.Ifc.Ara3D.StepParser;
namespace Speckle.Importers.Ifc.Ara3D.IfcParser;
// https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/TC1/HTML/ifckernel/lexical/ifcreldefinesbyproperties.htm
public class IfcPropSetRelation : IfcRelation
{
public IfcPropSetRelation(IfcGraph graph, StepInstance lineData, StepId from, StepList to)
: base(graph, lineData, from, to) { }
public IfcPropSet PropSet
{
get
{
var node = Graph.GetNode(From);
if (node is not IfcPropSet r)
throw new SpeckleIfcException($"Expected a property set not {node} from id {From}");
return r;
}
}
}
@@ -1,26 +0,0 @@
using Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
using Speckle.Importers.Ifc.Ara3D.StepParser;
namespace Speckle.Importers.Ifc.Ara3D.IfcParser;
/// <summary>
/// Always express a 1-to-many relation
/// </summary>
public class IfcRelation : IfcEntity
{
public StepId From { get; }
public StepList To { get; }
public IfcRelation(IfcGraph graph, StepInstance lineData, StepId from, StepList to)
: base(graph, lineData)
{
if (!IsIfcRoot)
throw new SpeckleIfcException("Expected relation to be an IFC root entity");
From = from;
To = to;
}
public IEnumerable<uint> GetRelatedIds() => To.Values.Select(v => v.AsId());
public IEnumerable<IfcNode> GetRelatedNodes() => Graph.GetNodes(GetRelatedIds());
}
@@ -1,9 +0,0 @@
using Speckle.Importers.Ifc.Ara3D.StepParser;
namespace Speckle.Importers.Ifc.Ara3D.IfcParser;
public class IfcRelationAggregate : IfcRelation
{
public IfcRelationAggregate(IfcGraph graph, StepInstance lineData, StepId from, StepList to)
: base(graph, lineData, from, to) { }
}
@@ -1,9 +0,0 @@
using Speckle.Importers.Ifc.Ara3D.StepParser;
namespace Speckle.Importers.Ifc.Ara3D.IfcParser;
public class IfcRelationSpatial : IfcRelation
{
public IfcRelationSpatial(IfcGraph graph, StepInstance lineData, StepId from, StepList to)
: base(graph, lineData, from, to) { }
}
@@ -1,9 +0,0 @@
using Speckle.Importers.Ifc.Ara3D.StepParser;
namespace Speckle.Importers.Ifc.Ara3D.IfcParser;
public class IfcRelationType : IfcRelation
{
public IfcRelationType(IfcGraph graph, StepInstance lineData, StepId from, StepList to)
: base(graph, lineData, from, to) { }
}
@@ -1,9 +0,0 @@
using Speckle.Importers.Ifc.Ara3D.StepParser;
namespace Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
public class IfcNode : IfcEntity
{
public IfcNode(IfcGraph graph, StepInstance lineData)
: base(graph, lineData) { }
}
@@ -1,10 +0,0 @@
using Speckle.Importers.Ifc.Ara3D.StepParser;
namespace Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
public sealed class IfcProject(IfcGraph graph, StepInstance lineData) : IfcNode(graph, lineData)
{
public string? ObjectType => (LineData[4] as StepString)?.AsString();
public string? LongName => (LineData[5] as StepString)?.AsString();
public string? Phase => (LineData[6] as StepString)?.AsString();
}
@@ -1,21 +0,0 @@
using Speckle.Importers.Ifc.Ara3D.StepParser;
namespace Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
public class IfcProp : IfcNode
{
public readonly StepValue Value;
public new string Name => this[0].AsString();
public new string Description => this[1].AsString();
public IfcProp(IfcGraph graph, StepInstance lineData, StepValue value)
: base(graph, lineData)
{
if (lineData.Count < 2)
throw new SpeckleIfcException("Expected at least two values in the line data");
if (lineData[0] is not StepString)
throw new SpeckleIfcException("Expected the first value to be a string (Name)");
Value = value;
}
}
@@ -1,45 +0,0 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Speckle.Importers.Ifc.Ara3D.StepParser;
using Speckle.Sdk.Common;
namespace Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
// This merges two separate entity types: IfcPropertySet and IfcElementQuantity.
// Both of which are derived from IfcPropertySetDefinition.
// This is something that can be referred to by a PropertySetRelation
// An IfcElementQuantity has an additional "method of measurement" property.
// https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/TC1/HTML/ifckernel/lexical/ifcpropertyset.htm
// https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/TC1/HTML/ifcproductextension/lexical/ifcelementquantity.htm
public class IfcPropSet : IfcNode
{
public readonly StepList? PropertyIdList;
public IfcPropSet(IfcGraph graph, StepInstance lineData, StepList? propertyIdList)
: base(graph, lineData)
{
Debug.Assert(IsIfcRoot);
Debug.Assert(lineData.AttributeValues.Count is 5 or 6);
Debug.Assert(Type is "IFCPROPERTYSET" or "IFCELEMENTQUANTITY");
PropertyIdList = propertyIdList;
}
public IEnumerable<IfcProp> GetProperties()
{
for (var i = 0; i < NumProperties; ++i)
{
var id = PropertyId(i);
var node = Graph.GetNode(id);
if (node is not IfcProp prop)
throw new SpeckleIfcException($"Expected a property not {node} from id {id}");
yield return prop;
}
}
public bool IsQuantity => LineData.AttributeValues.Count == 6;
public string? MethodOfMeasurement => IsQuantity ? this[4]?.AsString() : null;
public int NumProperties => PropertyIdList?.Values.Count ?? 0;
[MemberNotNull(nameof(PropertyIdList))]
public uint PropertyId(int i) => PropertyIdList.NotNull().Values[i].AsId();
}
@@ -1,15 +0,0 @@
using Speckle.Importers.Ifc.Ara3D.StepParser;
namespace Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
/// <summary>
/// Types like IfcSite, IfcBuilding, IfcBuildingStorey
/// </summary>
/// <param name="graph"></param>
/// <param name="lineData"></param>
public class IfcSpatialStructureElement(IfcGraph graph, StepInstance lineData) : IfcNode(graph, lineData)
{
public string? ObjectType => (LineData[4] as StepString)?.AsString();
public string? LongName => (LineData[7] as StepString)?.AsString();
public string? CompositionType => (LineData[8] as StepString)?.AsString();
}
@@ -1,31 +0,0 @@
using System.Diagnostics;
using Ara3D.Buffers;
namespace Speckle.Importers.Ifc.Ara3D.StepParser;
public static class AlignedMemoryReader
{
public static unsafe AlignedMemory ReadAllBytes(string path, int bufferSize = 1024 * 1024)
{
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, false);
var fileLength = fs.Length;
if (fileLength > int.MaxValue)
throw new IOException("File too big: > 2GB");
var count = (int)fileLength;
var r = new AlignedMemory(count);
var pBytes = r.BytePtr;
while (count > 0)
{
var span = new Span<byte>(pBytes, count);
var n = fs.Read(span);
if (n == 0)
break;
pBytes += n;
count -= n;
}
Debug.Assert(count == 0);
return r;
}
}
@@ -1,13 +0,0 @@
using System.Runtime.CompilerServices;
using Ara3D.Buffers;
namespace Speckle.Importers.Ifc.Ara3D.StepParser;
public static class ByteSpanExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double ToDouble(this ByteSpan self) => double.Parse(self.ToSpan());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ToInt(this ByteSpan self) => int.Parse(self.ToSpan());
}
@@ -1,102 +0,0 @@
using System.Runtime.Intrinsics;
using Ara3D.Buffers;
using Ara3D.Logging;
using Ara3D.Utils;
namespace Speckle.Importers.Ifc.Ara3D.StepParser;
public sealed unsafe class StepDocument : IDisposable
{
public readonly FilePath FilePath;
public readonly byte* DataStart;
public readonly byte* DataEnd;
public readonly AlignedMemory Data;
/// <summary>
/// This is a list of raw step instance information.
/// Each one has only a type and an ID.
/// </summary>
public readonly StepRawInstance[] RawInstances;
/// <summary>
/// The number of raw instance
/// </summary>
public readonly int NumRawInstances;
/// <summary>
/// This gives us a fast way to look up a StepInstance by their ID
/// </summary>
public readonly Dictionary<uint, int> InstanceIdToIndex = new();
/// <summary>
/// This tells us the byte offset of the start of each line in the file
/// </summary>
public readonly List<int> LineOffsets;
public StepDocument(FilePath filePath, ILogger? logger = null)
{
FilePath = filePath;
logger ??= Logger.Null;
logger.Log($"Loading {filePath.GetFileSizeAsString()} of data from {filePath.GetFileName()}");
Data = AlignedMemoryReader.ReadAllBytes(filePath);
DataStart = Data.BytePtr;
DataEnd = DataStart + Data.NumBytes;
logger.Log($"Computing the start of each line");
// NOTE: this estimates that the average line length is at least 32 characters.
// This minimize the number of allocations that happen
var cap = Data.NumBytes / 32;
LineOffsets = new List<int>(cap);
// We are going to report the beginning of the lines, while the "ComputeLines" function
// will compute the ends of lines.
var currentLine = 1;
for (var i = 0; i < Data.NumVectors; i++)
{
StepLineParser.ComputeOffsets(((Vector256<byte>*)Data.BytePtr)[i], ref currentLine, LineOffsets);
}
logger.Log($"Found {LineOffsets.Count} lines");
logger.Log($"Creating instance records");
RawInstances = new StepRawInstance[LineOffsets.Count];
for (var i = 0; i < LineOffsets.Count - 1; i++)
{
var lineStart = LineOffsets[i];
var lineEnd = LineOffsets[i + 1];
var inst = StepLineParser.ParseLine(DataStart + lineStart, DataStart + lineEnd);
if (inst.IsValid())
{
InstanceIdToIndex.Add(inst.Id, NumRawInstances);
RawInstances[NumRawInstances++] = inst;
}
}
logger.Log($"Completed creation of STEP document from {filePath.GetFileName()}");
}
public void Dispose() => Data.Dispose();
public StepInstance GetInstanceWithData(uint id) => GetInstanceWithDataFromIndex(InstanceIdToIndex[id]);
public StepInstance GetInstanceWithDataFromIndex(int index) => GetInstanceWithData(RawInstances[index]);
public StepInstance GetInstanceWithData(StepRawInstance inst)
{
var attr = inst.GetAttributes(DataEnd);
var se = new StepEntity(inst.Type, attr);
return new StepInstance(inst.Id, se);
}
public static StepDocument Create(FilePath fp) => new(fp);
public IEnumerable<StepRawInstance> GetRawInstances(string typeCode) =>
RawInstances.Where(inst => inst.Type.Equals(typeCode));
public IEnumerable<StepInstance> GetInstances() => RawInstances.Select(GetInstanceWithData);
public IEnumerable<StepInstance> GetInstances(string typeCode) =>
GetRawInstances(typeCode).Select(GetInstanceWithData);
}
@@ -1,90 +0,0 @@
namespace Speckle.Importers.Ifc.Ara3D.StepParser;
public static unsafe class StepFactory
{
public static StepList GetAttributes(this StepRawInstance inst, byte* lineEnd)
{
if (!inst.IsValid())
return StepList.CreateDefault();
var ptr = inst.Type.End();
var token = StepTokenizer.ParseToken(ptr, lineEnd);
// TODO: there is a potential bug here when the line is split across multiple line
return CreateAggregate(ref token, lineEnd);
}
public static StepValue Create(ref StepToken token, byte* end)
{
switch (token.Type)
{
case StepTokenType.String:
return StepString.Create(token);
case StepTokenType.Symbol:
return StepSymbol.Create(token);
case StepTokenType.Id:
return StepId.Create(token);
case StepTokenType.Redeclared:
return StepRedeclared.Create(token);
case StepTokenType.Unassigned:
return StepUnassigned.Create(token);
case StepTokenType.Number:
return StepNumber.Create(token);
case StepTokenType.Ident:
var span = token.Span;
StepTokenizer.ParseNextToken(ref token, end);
var attr = CreateAggregate(ref token, end);
return new StepEntity(span, attr);
case StepTokenType.BeginGroup:
return CreateAggregate(ref token, end);
case StepTokenType.None:
case StepTokenType.Whitespace:
case StepTokenType.Comment:
case StepTokenType.Unknown:
case StepTokenType.LineBreak:
case StepTokenType.EndOfLine:
case StepTokenType.Definition:
case StepTokenType.Separator:
case StepTokenType.EndGroup:
default:
throw new SpeckleIfcException($"Cannot convert token type {token.Type} to a StepValue");
}
}
public static StepList CreateAggregate(ref StepToken token, byte* end)
{
var values = new List<StepValue>();
StepTokenizer.EatWSpace(ref token, end);
if (token.Type != StepTokenType.BeginGroup)
throw new SpeckleIfcException("Expected '('");
while (StepTokenizer.ParseNextToken(ref token, end))
{
switch (token.Type)
{
// Advance past comments, whitespace, and commas
case StepTokenType.Comment:
case StepTokenType.Whitespace:
case StepTokenType.LineBreak:
case StepTokenType.Separator:
case StepTokenType.None:
continue;
// Expected end of group
case StepTokenType.EndGroup:
return new StepList(values);
}
var curValue = Create(ref token, end);
values.Add(curValue);
}
throw new SpeckleIfcException("Unexpected end of input");
}
}
@@ -1,59 +0,0 @@
using Ara3D.Utils;
using Speckle.Sdk.Common;
namespace Speckle.Importers.Ifc.Ara3D.StepParser;
public class StepGraph
{
public StepDocument Document { get; }
public readonly Dictionary<uint, StepNode> Lookup = new();
public StepNode GetNode(uint id) => Lookup[id];
public IEnumerable<StepNode> Nodes => Lookup.Values;
public StepGraph(StepDocument doc)
{
Document = doc;
foreach (var e in doc.GetInstances())
{
var node = new StepNode(this, e);
Lookup.Add(node.Entity.Id, node);
}
foreach (var n in Nodes)
n.Init();
}
public static StepGraph Create(StepDocument doc) => new(doc);
public string ToValString(StepNode node, int depth) => ToValString(node.Entity.Entity, depth - 1);
public string ToValString(StepValue value, int depth)
{
if (value == null)
return "";
switch (value)
{
case StepList stepAggregate:
return $"({stepAggregate.Values.Select(v => ToValString(v, depth)).JoinStringsWithComma()})";
case StepEntity stepEntity:
return $"{stepEntity.EntityType}{ToValString(stepEntity.Attributes, depth)}";
case StepId stepId:
return depth <= 0 ? "#" : ToValString(GetNode(stepId.Id), depth - 1);
case StepNumber stepNumber:
case StepRedeclared stepRedeclared:
case StepString stepString:
case StepSymbol stepSymbol:
case StepUnassigned stepUnassigned:
default:
return value.ToString().NotNull();
}
}
}
@@ -1,25 +0,0 @@
namespace Speckle.Importers.Ifc.Ara3D.StepParser;
public class StepInstance
{
public readonly StepEntity Entity;
public readonly uint Id;
public List<StepValue> AttributeValues => Entity.Attributes.Values;
public string EntityType => Entity?.EntityType.ToString() ?? "";
public StepInstance(uint id, StepEntity entity)
{
Id = id;
Entity = entity;
}
public bool IsEntityType(string str) => EntityType == str;
public override string ToString() => $"#{Id}={Entity};";
public int Count => AttributeValues.Count;
public StepValue this[int i] => AttributeValues[i];
}
@@ -1,151 +0,0 @@
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using Ara3D.Buffers;
namespace Speckle.Importers.Ifc.Ara3D.StepParser;
public static class StepLineParser
{
public static readonly Vector256<byte> Comma = Vector256.Create((byte)',');
public static readonly Vector256<byte> NewLine = Vector256.Create((byte)'\n');
public static readonly Vector256<byte> StartGroup = Vector256.Create((byte)'(');
public static readonly Vector256<byte> EndGroup = Vector256.Create((byte)')');
public static readonly Vector256<byte> Definition = Vector256.Create((byte)'=');
public static readonly Vector256<byte> Quote = Vector256.Create((byte)'\'');
public static readonly Vector256<byte> Id = Vector256.Create((byte)'#');
public static readonly Vector256<byte> SemiColon = Vector256.Create((byte)';');
public static readonly Vector256<byte> Unassigned = Vector256.Create((byte)'*');
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ComputeOffsets(in Vector256<byte> v, ref int index, List<int> offsets)
{
var r = Avx2.CompareEqual(v, NewLine);
var mask = (uint)Avx2.MoveMask(r);
if (mask == 0)
{
index += 32;
return;
}
// Fully unrolled handling of each bit
if ((mask & 0x00000001) != 0)
offsets.Add(index);
if ((mask & 0x00000002) != 0)
offsets.Add(index + 1);
if ((mask & 0x00000004) != 0)
offsets.Add(index + 2);
if ((mask & 0x00000008) != 0)
offsets.Add(index + 3);
if ((mask & 0x00000010) != 0)
offsets.Add(index + 4);
if ((mask & 0x00000020) != 0)
offsets.Add(index + 5);
if ((mask & 0x00000040) != 0)
offsets.Add(index + 6);
if ((mask & 0x00000080) != 0)
offsets.Add(index + 7);
if ((mask & 0x00000100) != 0)
offsets.Add(index + 8);
if ((mask & 0x00000200) != 0)
offsets.Add(index + 9);
if ((mask & 0x00000400) != 0)
offsets.Add(index + 10);
if ((mask & 0x00000800) != 0)
offsets.Add(index + 11);
if ((mask & 0x00001000) != 0)
offsets.Add(index + 12);
if ((mask & 0x00002000) != 0)
offsets.Add(index + 13);
if ((mask & 0x00004000) != 0)
offsets.Add(index + 14);
if ((mask & 0x00008000) != 0)
offsets.Add(index + 15);
if ((mask & 0x00010000) != 0)
offsets.Add(index + 16);
if ((mask & 0x00020000) != 0)
offsets.Add(index + 17);
if ((mask & 0x00040000) != 0)
offsets.Add(index + 18);
if ((mask & 0x00080000) != 0)
offsets.Add(index + 19);
if ((mask & 0x00100000) != 0)
offsets.Add(index + 20);
if ((mask & 0x00200000) != 0)
offsets.Add(index + 21);
if ((mask & 0x00400000) != 0)
offsets.Add(index + 22);
if ((mask & 0x00800000) != 0)
offsets.Add(index + 23);
if ((mask & 0x01000000) != 0)
offsets.Add(index + 24);
if ((mask & 0x02000000) != 0)
offsets.Add(index + 25);
if ((mask & 0x04000000) != 0)
offsets.Add(index + 26);
if ((mask & 0x08000000) != 0)
offsets.Add(index + 27);
if ((mask & 0x10000000) != 0)
offsets.Add(index + 28);
if ((mask & 0x20000000) != 0)
offsets.Add(index + 29);
if ((mask & 0x40000000) != 0)
offsets.Add(index + 30);
if ((mask & 0x80000000) != 0)
offsets.Add(index + 31);
// Update lineIndex to the next starting position
index += 32;
}
public static unsafe StepRawInstance ParseLine(byte* ptr, byte* end)
{
var start = ptr;
var cnt = end - ptr;
const int MIN_LINE_LENGTH = 5;
if (cnt < MIN_LINE_LENGTH)
return default;
// Parse the ID
if (*ptr++ != '#')
return default;
var id = 0u;
while (ptr < end)
{
if (*ptr < '0' || *ptr > '9')
break;
id = id * 10 + *ptr - '0';
ptr++;
}
var foundEquals = false;
while (ptr < end)
{
if (*ptr == '=')
foundEquals = true;
if (*ptr != (byte)' ' && *ptr != (byte)'=')
break;
ptr++;
}
if (!foundEquals)
return default;
// Parse the entity type name
var entityStart = ptr;
while (ptr < end)
{
if (!StepTokenizer.IsIdentLookup[*ptr])
break;
ptr++;
}
if (ptr == entityStart)
return default;
var entityType = new ByteSpan(entityStart, ptr);
return new(id, entityType, start);
}
}
@@ -1,52 +0,0 @@
using Ara3D.Utils;
namespace Speckle.Importers.Ifc.Ara3D.StepParser;
public class StepNode
{
public readonly StepGraph Graph;
public readonly StepInstance Entity;
public StepNode(StepGraph g, StepInstance e)
{
Graph = g;
Entity = e;
}
public List<StepNode> Nodes { get; } = new();
private void AddNodes(StepValue value)
{
if (value is StepId id)
{
var n = Graph.GetNode(id.Id);
Nodes.Add(n);
}
else if (value is StepList agg)
{
foreach (var v in agg.Values)
AddNodes(v);
}
}
public void Init()
{
foreach (var a in Entity.AttributeValues)
AddNodes(a);
}
public override string ToString() => Entity.ToString();
public string ToGraph(HashSet<StepNode>? prev = null)
{
prev ??= new HashSet<StepNode>();
if (prev.Contains(this))
return "_";
var nodeStr = Nodes.Select(n => n.ToGraph(prev)).JoinStringsWithComma();
return $"{EntityType}({nodeStr})";
}
public string EntityType => Entity.EntityType;
public string QuickHash() => $"{EntityType}:{Nodes.Count}";
}
@@ -1,20 +0,0 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Ara3D.Buffers;
namespace Speckle.Importers.Ifc.Ara3D.StepParser;
/// <summary>
/// Contains information about where an instance is within a file.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[method: MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly unsafe struct StepRawInstance(uint id, ByteSpan type, byte* ptr)
{
public readonly uint Id = id;
public readonly ByteSpan Type = type;
public readonly byte* Ptr = ptr;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsValid() => Id > 0;
}
@@ -1,17 +0,0 @@
using System.Diagnostics;
using Ara3D.Buffers;
namespace Speckle.Importers.Ifc.Ara3D.StepParser;
public readonly struct StepToken
{
public readonly ByteSpan Span;
public readonly StepTokenType Type;
public StepToken(ByteSpan span, StepTokenType type)
{
Span = span;
Debug.Assert(span.Length > 0);
Type = type;
}
}
@@ -1,22 +0,0 @@
namespace Speckle.Importers.Ifc.Ara3D.StepParser;
public enum StepTokenType : byte
{
None,
Ident,
String,
Whitespace,
Number,
Symbol,
Id,
Separator,
Unassigned,
Redeclared,
Comment,
Unknown,
BeginGroup,
EndGroup,
LineBreak,
EndOfLine,
Definition,
}
@@ -1,306 +0,0 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Ara3D.Buffers;
namespace Speckle.Importers.Ifc.Ara3D.StepParser;
public static class StepTokenizer
{
public static readonly StepTokenType[] TokenLookup = CreateTokenLookup();
public static readonly bool[] IsNumberLookup = CreateNumberLookup();
public static readonly bool[] IsIdentLookup = CreateIdentLookup();
public static StepTokenType[] CreateTokenLookup()
{
var r = new StepTokenType[256];
for (var i = 0; i < 256; i++)
r[i] = GetTokenType((byte)i);
return r;
}
public static bool[] CreateNumberLookup()
{
var r = new bool[256];
for (var i = 0; i < 256; i++)
r[i] = IsNumberChar((byte)i);
return r;
}
public static bool[] CreateIdentLookup()
{
var r = new bool[256];
for (var i = 0; i < 256; i++)
r[i] = IsIdentOrDigitChar((byte)i);
return r;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static StepTokenType LookupToken(byte b) => TokenLookup[b];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNumberChar(byte b)
{
switch (b)
{
case (byte)'0':
case (byte)'1':
case (byte)'2':
case (byte)'3':
case (byte)'4':
case (byte)'5':
case (byte)'6':
case (byte)'7':
case (byte)'8':
case (byte)'9':
case (byte)'E':
case (byte)'e':
case (byte)'+':
case (byte)'-':
case (byte)'.':
return true;
}
return false;
}
public static StepTokenType GetTokenType(byte b)
{
switch (b)
{
case (byte)'0':
case (byte)'1':
case (byte)'2':
case (byte)'3':
case (byte)'4':
case (byte)'5':
case (byte)'6':
case (byte)'7':
case (byte)'8':
case (byte)'9':
case (byte)'+':
case (byte)'-':
return StepTokenType.Number;
case (byte)' ':
case (byte)'\t':
return StepTokenType.Whitespace;
case (byte)'\n':
case (byte)'\r':
return StepTokenType.LineBreak;
case (byte)'\'':
case (byte)'"':
return StepTokenType.String;
case (byte)'.':
return StepTokenType.Symbol;
case (byte)'#':
return StepTokenType.Id;
case (byte)';':
return StepTokenType.EndOfLine;
case (byte)'(':
return StepTokenType.BeginGroup;
case (byte)'=':
return StepTokenType.Definition;
case (byte)')':
return StepTokenType.EndGroup;
case (byte)',':
return StepTokenType.Separator;
case (byte)'$':
return StepTokenType.Unassigned;
case (byte)'*':
return StepTokenType.Redeclared;
case (byte)'/':
return StepTokenType.Comment;
case (byte)'a':
case (byte)'b':
case (byte)'c':
case (byte)'d':
case (byte)'e':
case (byte)'f':
case (byte)'g':
case (byte)'h':
case (byte)'i':
case (byte)'j':
case (byte)'k':
case (byte)'l':
case (byte)'m':
case (byte)'n':
case (byte)'o':
case (byte)'p':
case (byte)'q':
case (byte)'r':
case (byte)'s':
case (byte)'t':
case (byte)'u':
case (byte)'v':
case (byte)'w':
case (byte)'x':
case (byte)'y':
case (byte)'z':
case (byte)'A':
case (byte)'B':
case (byte)'C':
case (byte)'D':
case (byte)'E':
case (byte)'F':
case (byte)'G':
case (byte)'H':
case (byte)'I':
case (byte)'J':
case (byte)'K':
case (byte)'L':
case (byte)'M':
case (byte)'N':
case (byte)'O':
case (byte)'P':
case (byte)'Q':
case (byte)'R':
case (byte)'S':
case (byte)'T':
case (byte)'U':
case (byte)'V':
case (byte)'W':
case (byte)'X':
case (byte)'Y':
case (byte)'Z':
case (byte)'_':
return StepTokenType.Ident;
default:
return StepTokenType.Unknown;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsWhiteSpace(byte b) => b == ' ' || b == '\t';
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsLineBreak(byte b) => b == '\n' || b == '\r';
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsIdent(byte b) => b >= 'A' && b <= 'Z' || b >= 'a' && b <= 'z' || b == '_';
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsDigit(byte b) => b >= '0' && b <= '9';
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsIdentOrDigitChar(byte b) => IsIdent(b) || IsDigit(b);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe byte* AdvancePast(byte* begin, byte* end, string s)
{
if (end - begin < s.Length)
return null;
foreach (var c in s)
if (*begin++ != (byte)c)
return null;
return begin;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe StepToken ParseToken(byte* begin, byte* end)
{
var cur = begin;
var tt = InternalParseToken(ref cur, end);
Debug.Assert(cur < end);
var span = new ByteSpan(begin, cur);
return new StepToken(span, tt);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool EatWSpace(ref StepToken cur, byte* end)
{
while (
cur.Type == StepTokenType.Comment || cur.Type == StepTokenType.Whitespace || cur.Type == StepTokenType.LineBreak
)
{
if (!ParseNextToken(ref cur, end))
return false;
}
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool ParseNextToken(ref StepToken prev, byte* end)
{
var cur = prev.Span.End();
if (cur >= end)
return false;
prev = ParseToken(cur, end);
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe StepTokenType InternalParseToken(ref byte* cur, byte* end)
{
var type = TokenLookup[*cur++];
switch (type)
{
case StepTokenType.Ident:
while (IsIdentLookup[*cur])
cur++;
break;
case StepTokenType.String:
// usually it is as single quote,
// but in rare cases it could be a double quote
var quoteChar = *(cur - 1);
while (cur < end)
{
if (*cur++ == quoteChar)
{
if (*cur != quoteChar)
break;
else
cur++;
}
}
break;
case StepTokenType.LineBreak:
while (IsLineBreak(*cur))
cur++;
break;
case StepTokenType.Number:
while (IsNumberLookup[*cur])
cur++;
break;
case StepTokenType.Symbol:
while (*cur++ != '.') { }
break;
case StepTokenType.Id:
while (IsNumberLookup[*cur])
cur++;
break;
case StepTokenType.Comment:
var prev = *cur++;
while (cur < end && (prev != '*' || *cur != '/'))
prev = *cur++;
cur++;
break;
}
return type;
}
}
@@ -1,154 +0,0 @@
using System.Diagnostics;
using Ara3D.Buffers;
using Ara3D.Utils;
namespace Speckle.Importers.Ifc.Ara3D.StepParser;
/// <summary>
/// The base class of the different type of value items that can be found in a STEP file.
/// * Entity
/// * List
/// * String
/// * Symbol
/// * Unassigned token
/// * Redeclared token
/// * Number
/// </summary>
public class StepValue;
public class StepEntity : StepValue
{
public readonly ByteSpan EntityType;
public readonly StepList Attributes;
public StepEntity(ByteSpan entityType, StepList attributes)
{
Debug.Assert(!entityType.IsNull());
EntityType = entityType;
Attributes = attributes;
}
public override string ToString() => $"{EntityType}{Attributes}";
}
public class StepList : StepValue
{
public readonly List<StepValue> Values;
public StepList(List<StepValue> values) => Values = values;
public override string ToString() => $"({Values.JoinStringsWithComma()})";
public static StepList CreateDefault() => new(new List<StepValue>());
}
public class StepString : StepValue
{
public readonly ByteSpan Value;
public static StepString Create(StepToken token)
{
var span = token.Span;
Debug.Assert(token.Type == StepTokenType.String);
Debug.Assert(span.Length >= 2);
Debug.Assert(span.First() == '\'' || span.First() == '"');
Debug.Assert(span.Last() == '\'' || span.Last() == '"');
return new StepString(span.Trim(1, 1));
}
public StepString(ByteSpan value) => Value = value;
public override string ToString() => $"'{Value}'";
}
public class StepSymbol : StepValue
{
public readonly ByteSpan Name;
public StepSymbol(ByteSpan name) => Name = name;
public override string ToString() => $".{Name}.";
public static StepSymbol Create(StepToken token)
{
Debug.Assert(token.Type == StepTokenType.Symbol);
var span = token.Span;
Debug.Assert(span.Length >= 2);
Debug.Assert(span.First() == '.');
Debug.Assert(span.Last() == '.');
return new StepSymbol(span.Trim(1, 1));
}
}
public class StepNumber : StepValue
{
public readonly ByteSpan Span;
public double Value => Span.ToDouble();
public StepNumber(ByteSpan span) => Span = span;
public override string ToString() => $"{Value}";
public static StepNumber Create(StepToken token)
{
Debug.Assert(token.Type == StepTokenType.Number);
var span = token.Span;
return new(span);
}
}
public class StepId : StepValue
{
public readonly uint Id;
public StepId(uint id) => Id = id;
public override string ToString() => $"#{Id}";
public static unsafe StepId Create(StepToken token)
{
Debug.Assert(token.Type == StepTokenType.Id);
var span = token.Span;
Debug.Assert(span.Length >= 2);
Debug.Assert(span.First() == '#');
var id = 0u;
for (var i = 1; i < span.Length; ++i)
{
Debug.Assert(span.Ptr[i] >= '0' && span.Ptr[i] <= '9');
id = id * 10 + span.Ptr[i] - '0';
}
return new StepId(id);
}
}
public class StepUnassigned : StepValue
{
public static readonly StepUnassigned Default = new();
public override string ToString() => "$";
public static StepUnassigned Create(StepToken token)
{
Debug.Assert(token.Type == StepTokenType.Unassigned);
var span = token.Span;
Debug.Assert(span.Length == 1);
Debug.Assert(span.First() == '$');
return Default;
}
}
public class StepRedeclared : StepValue
{
public static readonly StepRedeclared Default = new();
public override string ToString() => "*";
public static StepRedeclared Create(StepToken token)
{
Debug.Assert(token.Type == StepTokenType.Redeclared);
var span = token.Span;
Debug.Assert(span.Length == 1);
Debug.Assert(span.First() == '*');
return Default;
}
}
@@ -1,31 +0,0 @@
using Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
using Speckle.Importers.Ifc.Types;
using Speckle.InterfaceGenerator;
using Speckle.Objects.Data;
using Speckle.Sdk.Models;
namespace Speckle.Importers.Ifc.Converters;
[GenerateAutoInterface]
public sealed class DataObjectConverter(IGeometryConverter geometryConverter) : IDataObjectConverter
{
public DataObject Convert(IfcModel model, IfcNode node, INodeConverter childrenConverter)
{
// Even if there is no geometry, this will return an empty collection.
var geo = model.GetGeometry(node.Id);
List<Base> displayValue = geo != null ? geometryConverter.Convert(geo) : new();
return new DataObject()
{
applicationId = node.Guid, // Guid is null for property values, and other Ifc entities not derived from IfcRoot
properties = node.ConvertPropertySets(),
name = node.Name ?? node.Guid,
displayValue = displayValue,
["@elements"] = childrenConverter.ConvertChildren(model, node).ToList(),
["ifcType"] = node.Type,
["expressID"] = node.Id,
["ownerId"] = node.OwnerId,
["description"] = node.Description,
};
}
}
@@ -1,20 +0,0 @@
using Speckle.Importers.Ifc.Types;
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Models;
namespace Speckle.Importers.Ifc.Converters;
[GenerateAutoInterface]
public sealed class GeometryConverter(IMeshConverter meshConverter) : IGeometryConverter
{
public List<Base> Convert(IfcGeometry geometry)
{
List<Base> ret = new();
foreach (var mesh in geometry.GetMeshes())
{
ret.Add(meshConverter.Convert(mesh));
}
return ret;
}
}
@@ -1,28 +0,0 @@
using Speckle.Importers.Ifc.Ara3D.IfcParser;
using Speckle.Importers.Ifc.Services;
using Speckle.Importers.Ifc.Types;
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Models;
namespace Speckle.Importers.Ifc.Converters;
[GenerateAutoInterface]
public sealed class GraphConverter(INodeConverter nodeConverter, IRenderMaterialProxyManager proxyManager)
: IGraphConverter
{
public Base Convert(IfcModel model, IfcGraph graph)
{
try
{
Base rootCollection = nodeConverter.Convert(model, graph.GetIfcProject());
//Grabing materials from ProxyManager
rootCollection["renderMaterialProxies"] = proxyManager.RenderMaterialProxies.Values.ToList();
return rootCollection;
}
finally
{
proxyManager.Clear();
}
}
}
@@ -1,74 +0,0 @@
using System.Drawing;
using Speckle.Importers.Ifc.Services;
using Speckle.Importers.Ifc.Types;
using Speckle.InterfaceGenerator;
using Speckle.Objects.Geometry;
using Speckle.Objects.Other;
namespace Speckle.Importers.Ifc.Converters;
[GenerateAutoInterface]
public sealed class MeshConverter(IRenderMaterialProxyManager renderMaterialManager) : IMeshConverter
{
public Mesh Convert(IfcMesh mesh)
{
var m = mesh.Transform;
var vp = mesh.Vertices;
var ip = mesh.Indices;
var vertices = new List<double>(vp.Length * 3);
foreach (var vertex in vp)
{
var x = vertex.PX;
var y = vertex.PY;
var z = vertex.PZ;
vertices.Add(m[0] * x + m[4] * y + m[8] * z + m[12]);
vertices.Add(-(m[2] * x + m[6] * y + m[10] * z + m[14]));
vertices.Add(m[1] * x + m[5] * y + m[9] * z + m[13]);
}
var faces = new List<int>(ip.Length * 4);
for (var i = 0; i < ip.Length; i += 3)
{
var a = ip[i];
var b = ip[i + 1];
var c = ip[i + 2];
faces.Add(3);
faces.Add(a);
faces.Add(b);
faces.Add(c);
}
RenderMaterial renderMaterial = ConvertRenderMaterial(mesh);
Mesh converted =
new()
{
applicationId = Guid.NewGuid().ToString(),
vertices = vertices,
faces = faces,
units = "m",
};
renderMaterialManager.AddMeshMapping(renderMaterial, converted);
return converted;
}
private static RenderMaterial ConvertRenderMaterial(IfcMesh mesh)
{
var color = mesh.Color;
var diffuse = Color.FromArgb(1, To8BitValue(color.R), To8BitValue(color.G), To8BitValue(color.B));
var name = $"IFC_MATERIAL:{(color.A, color.R, color.G, color.B).GetHashCode()}";
return new RenderMaterial()
{
applicationId = name,
name = name,
diffuse = diffuse.ToArgb(),
opacity = color.A
};
static int To8BitValue(double value) => (int)(value * 255);
}
}
@@ -1,44 +0,0 @@
using Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
using Speckle.Importers.Ifc.Types;
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Models;
namespace Speckle.Importers.Ifc.Converters;
/// <summary>
/// This is the main "recursive" converter for converting all IfcTypes to Speckle
/// </summary>
[GenerateAutoInterface]
public sealed class NodeConverter(
IDataObjectConverter dataObjectConverter,
IIfcSpatialStructureElementConverter spatialStructureConverter,
IProjectConverter projectConverter
) : INodeConverter
{
/// <summary>
/// Converts Ifc nodes that inherits IfcRoot class To Speckle)
/// </summary>
/// <param name="model"></param>
/// <param name="node"></param>
/// <returns></returns>
public Base Convert(IfcModel model, IfcNode node)
{
if (!node.IsIfcRoot)
throw new ArgumentException("Expected to be an IfcRoot", paramName: nameof(node));
return node switch
{
IfcProject project => projectConverter.Convert(model, project, this),
//Note: we're only expecting IfcSite, IfcBuilding, and IfcBuildingStory's here...
//but I cba to add full classes + inheritance, so IfcSpatialStructureElements is the closest common class
IfcSpatialStructureElement structure => spatialStructureConverter.Convert(model, structure, this),
IfcPropSet => throw new NotImplementedException("We didn't expect IfcPropSets here!"),
_ => dataObjectConverter.Convert(model, node, this)
};
}
public IEnumerable<Base> ConvertChildren(IfcModel model, IfcNode node)
{
return node.GetChildren().Where(x => x.IsIfcRoot).Select(x => Convert(model, x));
}
}
@@ -1,41 +0,0 @@
using Speckle.Importers.Ifc.Ara3D.IfcParser;
using Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
namespace Speckle.Importers.Ifc.Converters;
public static class NodeExtensions
{
public static Dictionary<string, object?> ConvertPropertySets(this IfcNode node)
{
var result = new Dictionary<string, object?>();
foreach (var p in node.GetPropSets())
{
var name = p.Name;
if (string.IsNullOrWhiteSpace(name))
name = $"#{p.Id}";
var dict = ToSpeckleDictionary(p);
if (dict.Count > 0) //Ignore any empty psets, since they can bloat the data size
result[name] = dict;
}
return result;
}
public static Dictionary<string, object?> ToSpeckleDictionary(this IfcPropSet ps)
{
var d = new Dictionary<string, object?>();
foreach (var p in ps.GetProperties())
{
var value = p.Value.ToJsonObject();
if (value is not null)
{
// Ignoring null values since they'd otherwise bloat the data size of speckle models.
// Semantically, "null valued" and "not there" are different, but very few users care about the distinction.
d[p.Name] = value;
}
}
return d;
}
}
@@ -1,28 +0,0 @@
using Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
using Speckle.Importers.Ifc.Types;
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Importers.Ifc.Converters;
[GenerateAutoInterface]
public sealed class ProjectConverter : IProjectConverter
{
public Collection Convert(IfcModel model, IfcProject node, INodeConverter childrenConverter)
{
return new Collection
{
name = node.Name ?? node.Guid,
applicationId = node.Guid,
elements = childrenConverter.ConvertChildren(model, node).ToList(),
["expressID"] = node.Id,
["ownerId"] = node.OwnerId,
["ifcType"] = node.Type,
["description"] = node.Description,
["objectType"] = node.ObjectType,
["longName"] = node.LongName,
["phase"] = node.Phase,
["properties"] = node.ConvertPropertySets(),
};
}
}
@@ -1,52 +0,0 @@
using Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
using Speckle.Importers.Ifc.Types;
using Speckle.InterfaceGenerator;
using Speckle.Objects.Data;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Importers.Ifc.Converters;
[GenerateAutoInterface]
public sealed class IfcSpatialStructureElementConverter(IGeometryConverter geometryConverter)
: IIfcSpatialStructureElementConverter
{
public Collection Convert(IfcModel model, IfcSpatialStructureElement node, INodeConverter childrenConverter)
{
var directGeometry = ConvertAsDataObject(model, node);
var relationalChildren = childrenConverter.ConvertChildren(model, node);
var allChildren = relationalChildren.Prepend(directGeometry).ToList();
//We're preferring to keep IFC collections lightweight, and adding a DataObject with the properties
// 1. Spatial elements can can have direct geometry (mostly only common with IFC Site)
// 2. Keeps property access simpler
return new Collection
{
name = node.Name ?? node.LongName ?? node.Guid,
elements = allChildren,
["expressID"] = node.Id,
};
}
private DataObject ConvertAsDataObject(IfcModel model, IfcSpatialStructureElement node)
{
var geo = model.GetGeometry(node.Id);
List<Base> displayValue = geo != null ? geometryConverter.Convert(geo) : new();
return new DataObject
{
["expressID"] = node.Id,
["ownerId"] = node.OwnerId,
["ifcType"] = node.Type,
["description"] = node.Description,
["objectType"] = node.ObjectType,
["compositionType"] = node.CompositionType,
["longName"] = node.LongName,
name = node.Name ?? node.LongName ?? node.Guid,
applicationId = node.Guid,
properties = node.ConvertPropertySets(),
displayValue = displayValue,
};
}
}
@@ -1,56 +0,0 @@
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common;
using Speckle.Objects.Geometry;
using Speckle.Sdk;
using Speckle.Sdk.Transports;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Importers.Ifc;
/// <summary>
/// Static DI Wrapper around <see cref="Importer"/>
/// </summary>
public static class Import
{
public static ServiceProvider GetServiceProvider()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddIFCImporter();
return serviceCollection.BuildServiceProvider();
}
public static void AddIFCImporter(this ServiceCollection serviceCollection)
{
serviceCollection.AddSpeckleSdk(
new("IFC", "ifc"),
HostAppVersion.v2024.ToString(),
"IFC-Importer",
typeof(Point).Assembly
);
serviceCollection.AddSpeckleWebIfc();
serviceCollection.AddSingleton<Importer>();
serviceCollection.AddMatchingInterfacesAsTransient(Assembly.GetExecutingAssembly());
}
public static async Task<Version> Ifc(
ImporterArgs args,
IProgress<ProgressArgs>? progress = null,
CancellationToken cancellationToken = default
)
{
var serviceProvider = GetServiceProvider();
return await Ifc(serviceProvider, args, progress, cancellationToken);
}
public static async Task<Version> Ifc(
ServiceProvider serviceProvider,
ImporterArgs args,
IProgress<ProgressArgs>? progress = null,
CancellationToken cancellationToken = default
)
{
var importer = serviceProvider.GetRequiredService<Importer>();
return await importer.ImportIfc(args, progress, cancellationToken);
}
}
@@ -1,107 +0,0 @@
using System.Diagnostics;
using Ara3D.Utils;
using Speckle.Importers.Ifc.Ara3D.IfcParser;
using Speckle.Importers.Ifc.Converters;
using Speckle.Importers.Ifc.Types;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Serialisation.V2;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.Transports;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Importers.Ifc;
public sealed class ImporterArgs
{
public required Uri ServerUrl { get; init; }
public required string FilePath { get; init; }
public required string ProjectId { get; init; }
public required string? ModelId { get; init; }
public required string ModelName { get; init; }
public required string VersionMessage { get; init; }
public required string Token { get; init; }
}
public sealed class Importer(
IIfcFactory ifcFactory,
IClientFactory clientFactory,
IGraphConverter converter,
ISerializeProcessFactory serializeProcessFactory
)
{
public async Task<Version> ImportIfc(
ImporterArgs args,
IProgress<ProgressArgs>? progress,
CancellationToken cancellationToken
)
{
var filePath = new FilePath(args.FilePath);
var stopwatch = Stopwatch.StartNew();
Console.WriteLine($"Starting processing file : {filePath.GetFileName()}");
var ifcModel = ifcFactory.Open(filePath);
var ms = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"Opened with WebIFC: {ms} ms");
var graph = IfcGraph.Load(filePath);
var ms2 = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"Loaded with StepParser: {ms2 - ms} ms");
var b = converter.Convert(ifcModel, graph);
ms = ms2;
ms2 = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"Converted to Speckle Bases: {ms2 - ms} ms");
var process = serializeProcessFactory.CreateSerializeProcess(
args.ServerUrl,
args.ProjectId,
args.Token,
progress,
cancellationToken,
new SerializeProcessOptions(true, true, false, progress is null)
);
var (rootId, _) = await process.Serialize(b).ConfigureAwait(false);
Account account =
new()
{
token = args.Token,
serverInfo = new ServerInfo { url = args.ServerUrl.ToString() },
};
ms = ms2;
ms2 = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"Uploaded to Speckle: {ms2 - ms} ms. Root id: {rootId}");
// 8 - Create the version (commit)
using var apiClient = clientFactory.Create(account);
var modelId = args.ModelId;
if (string.IsNullOrEmpty(modelId))
{
// Project level import, currently we're expecting the parsers to create the branch
// Quite smelly imo...
var input = new CreateModelInput(args.ModelName, null, args.ProjectId);
var model = await apiClient.Model.Create(input, cancellationToken);
modelId = model.id;
}
var speckleVersion = await apiClient.Version.Create(
new CreateVersionInput(rootId, modelId, args.ProjectId, message: args.VersionMessage, sourceApplication: "IFC"),
cancellationToken
);
ms = ms2;
ms2 = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"Committed to Speckle: {ms2 - ms} ms - {GetUrl(speckleVersion, modelId)}");
Console.WriteLine();
return speckleVersion;
}
private static string GetUrl(Version version, string modelId)
{
return version.previewUrl.ToString().Replace("preview", "projects").Replace("commits/", $"models/{modelId}@");
}
}
@@ -1,107 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
namespace Speckle.Importers.Ifc.Native;
[SuppressMessage("Globalization", "CA2101:Specify marshaling for P/Invoke string arguments")]
[SuppressMessage("Security", "CA5393:Do not use unsafe DllImportSearchPath value")]
internal static class WebIfc
{
#if WINDOWS
private const string DllName = "Native/web-ifc.dll";
private const CharSet Set = CharSet.Ansi;
#else
private const string DllName = "libweb-ifc.so";
private const CharSet Set = CharSet.Auto;
#endif
private const DllImportSearchPath ImportSearchPath = DllImportSearchPath.AssemblyDirectory;
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern IntPtr InitializeApi();
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern void FinalizeApi(IntPtr api);
[DllImport(DllName, CharSet = Set)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern IntPtr LoadModel(IntPtr api, string fileName);
[DllImport(DllName, CharSet = Set)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern string GetVersion();
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern IntPtr GetMesh(IntPtr geometry, int index);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern int GetNumMeshes(IntPtr geometry);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern uint GetGeometryType(IntPtr geometry);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern uint GetGeometryId(IntPtr geometry);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern uint GetLineId(IntPtr line);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern uint GetLineType(IntPtr line);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern string GetLineArguments(IntPtr line);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern int GetNumVertices(IntPtr mesh);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern IntPtr GetVertices(IntPtr mesh);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern IntPtr GetTransform(IntPtr mesh);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern int GetNumIndices(IntPtr mesh);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern IntPtr GetIndices(IntPtr mesh);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern IntPtr GetColor(IntPtr mesh);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern IntPtr GetGeometryFromId(IntPtr model, uint id);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern int GetNumGeometries(IntPtr model);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern IntPtr GetGeometryFromIndex(IntPtr model, int index);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern uint GetMaxId(IntPtr model);
[DllImport(DllName)]
[DefaultDllImportSearchPaths(ImportSearchPath)]
public static extern IntPtr GetLineFromModel(IntPtr model, uint id);
}
@@ -1,33 +0,0 @@
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Speckle.Importers.Ifc.Services;
using Speckle.Importers.Ifc.Types;
using Speckle.Sdk;
namespace Speckle.Importers.Ifc;
public static class ServiceRegistration
{
public static void AddSpeckleWebIfc(this IServiceCollection services)
{
services.AddSingleton<IIfcFactory, IfcFactory>();
services.AddSingleton<IRenderMaterialProxyManager, RenderMaterialProxyManager>();
}
public static IServiceCollection AddMatchingInterfacesAsTransient(
this IServiceCollection serviceCollection,
Assembly assembly
)
{
foreach (var type in assembly.ExportedTypes.Where(t => t.IsNonAbstractClass()))
{
foreach (var matchingInterface in type.FindMatchingInterface())
{
serviceCollection.TryAddTransient(matchingInterface, type);
}
}
return serviceCollection;
}
}
@@ -1,29 +0,0 @@
using Speckle.InterfaceGenerator;
using Speckle.Objects.Geometry;
using Speckle.Objects.Other;
using Speckle.Sdk.Common;
namespace Speckle.Importers.Ifc.Services;
[GenerateAutoInterface]
public sealed class RenderMaterialProxyManager : IRenderMaterialProxyManager
{
public Dictionary<string, RenderMaterialProxy> RenderMaterialProxies { get; } = new();
public void Clear() => RenderMaterialProxies.Clear();
public void AddMeshMapping(RenderMaterial renderMaterial, Mesh mesh)
{
string materialId = renderMaterial.applicationId.NotNull();
string meshId = mesh.applicationId.NotNull();
if (RenderMaterialProxies.TryGetValue(materialId, out RenderMaterialProxy? proxy))
{
proxy.objects.Add(meshId);
}
else
{
RenderMaterialProxies.Add(materialId, new() { objects = [meshId], value = renderMaterial, });
}
}
}

Some files were not shown because too many files have changed in this diff Show More