Compare commits

...

57 Commits

Author SHA1 Message Date
Jedd Morgan 7c7260c603 Merge pull request #1157 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-20 15:39:33 +01:00
Oğuzhan Koral bae9e3e0f1 Prevent crashes on unnamed files (#1154)
Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com>
Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
2025-10-20 17:33:00 +03:00
Oğuzhan Koral 26b0394613 feat(revit): display value proxies (#1140)
* POC

* some fixes

* Handle autocad, rhino and sketchup receives

* Handle revit receive

* Fix transform issues

* fix: custom mesh id logic

* Hash function

* Merge pull request #1142 from specklesystems/jedd/cnx-2657-hashing-the-meshes

feat(revit)!: Use Hash function for mesh geometry instance ids

* Use v2 style transform

* extra comments

* experiment1

* correct transform logic and disposal

* corrected transform logic

* simplify (maybe) the transform combination

* refactor(revit): replace tuples with DisplayValueResult record for display values (#1145)

* Clear instance proxies per conversion

* fix: material assignment on revit receive (#1146)

* Fix: enable send caching (#1148)

* Enable caching without definition proxy noise

* Log element id relationship while creating cache to filter after conversion to attach root

* Update RevitRootObjectBuilder.cs

* Clear cache on document swap

* More clean up

* fix(revit): defer material proxy population to prevent duplicate instance mesh IDs (#1155)

* fix(revit): defer material proxy population to prevent duplicate instance mesh IDs

* chore: formatting

* chore: campsite

* refactor: throwing on cache error

* refactor: move material proxy population into cache singleton

* fix: di

* fix(rhino): match cleaned block names when purging instance definitions (#1156)

* fix(rhino): match cleaned block names when purging instance definitions

* refactor: simplification

* chore: comments

* refactor: use .Contains

---------

Co-authored-by: Björn <steinhagen.bjoern@gmail.com>
Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com>
2025-10-20 15:26:56 +01:00
Dogukan Karatas 689ef0bcbe Merge pull request #1149 from specklesystems/dogukan/cnx-2490-receive-property-sets-in-civil-3d
feat (civil): receive property sets
2025-10-17 18:09:25 +02:00
Dogukan Karatas 461585b782 adds additional cast 2025-10-17 17:58:01 +02:00
Claire Kuang ea33f35a7d removes unnecessary casting on send 2025-10-17 16:57:38 +01:00
Claire Kuang 7427f1a2f3 adds constants and better host object builder methods 2025-10-17 16:08:40 +01:00
Dogukan Karatas b7984bf97e add additional cleaning 2025-10-17 15:25:35 +02:00
Dogukan Karatas 9b24a45b6e property set pruge 2025-10-17 12:50:58 +02:00
Claire Kuang 4ace81a422 Merge branch 'dev' into dogukan/cnx-2490-receive-property-sets-in-civil-3d 2025-10-17 10:02:40 +01:00
Björn Steinhagen a60790c92c fix(grasshopper): account selection not respected when url input connected (#1150)
* fix: account switching on urlInput
* chore: good ol' comments
* chore: adding server url to exception message
2025-10-17 10:02:12 +01:00
Dogukan Karatas fd0d00cac3 Merge branch 'dev' into dogukan/cnx-2490-receive-property-sets-in-civil-3d 2025-10-16 18:48:01 +02:00
Dogukan Karatas 498396e611 Merge pull request #1153 from specklesystems/dogukan/build-package-version-bump
chore(build): bump Microsoft.Build version
2025-10-16 18:47:18 +02:00
Dogukan Karatas 5444377398 bump version 2025-10-16 18:29:20 +02:00
Dogukan Karatas 9d981f9800 ci check 2025-10-16 17:59:13 +02:00
Dogukan Karatas 14e17fb67d prefix and purge 2025-10-16 17:52:30 +02:00
Dogukan Karatas 0ffa7685fd rather cast than convert 2025-10-16 17:16:20 +02:00
Dogukan Karatas dc7d4671e4 default value simplification 2025-10-16 16:40:37 +02:00
Dogukan Karatas 10cb5cd66f removed the parser 2025-10-16 16:24:53 +02:00
Jedd Morgan cb15d9f77a Merge pull request #1152 from specklesystems/jrm/trigger-ci
Remove arcgis from readme
2025-10-16 15:05:53 +01:00
Jedd Morgan da74faef9b read me change to trigger ci 2025-10-16 15:02:58 +01:00
Claire Kuang 4368833c7e Merge branch 'dev' into dogukan/cnx-2490-receive-property-sets-in-civil-3d 2025-10-16 09:56:57 +01:00
Dogukan Karatas a20df41316 readonly dict 2025-10-15 22:56:50 +02:00
Dogukan Karatas ccf48dbad1 process property set definitions from root object 2025-10-15 22:42:33 +02:00
Björn Steinhagen 6700aa27bc fix(revit): handle level extraction for face-based family instances (#1151)
* fix: get levels for face-based instances

* fix: level unpacker
2025-10-15 22:06:01 +02:00
Dogukan Karatas df525eab63 moved the baker to the connector 2025-10-15 21:25:12 +02:00
Dogukan Karatas 275901626f created an abstract class 2025-10-15 16:35:19 +02:00
Claire Kuang fac0dc31b2 Merge branch 'dev' into dogukan/cnx-2490-receive-property-sets-in-civil-3d 2025-10-15 12:48:26 +01:00
Dogukan Karatas 8696eca1f0 use active transaction 2025-10-15 13:12:41 +02:00
Dogukan Karatas d647c71cf5 baker added 2025-10-15 12:25:01 +02:00
Dogukan Karatas 9b218dd808 Revert "created PropertySetConverter"
This reverts commit 112093f914.
2025-10-15 12:06:04 +02:00
Björn Steinhagen 9f39dc521d fix(revit): clamp PBR material properties to avoid receive failures (#1147)
* fix: clamping roughness vaues

* chore: extending to other 0 - 1 values
2025-10-14 16:15:52 +02:00
Dogukan Karatas 112093f914 created PropertySetConverter 2025-10-13 14:08:35 +02:00
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
167 changed files with 2613 additions and 5966 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);
+9 -9
View File
@@ -16,12 +16,12 @@
},
"Microsoft.Build": {
"type": "Direct",
"requested": "[17.11.4, )",
"resolved": "17.11.4",
"contentHash": "UMC7DfeFEHY2GGHHaghybUuUlLaByFHEFudR2PehMgDBuRuLAUePp1iaa4eFtVzepRzMtIbeSCVJCzzX3NV2Gg==",
"requested": "[17.11.48, )",
"resolved": "17.11.48",
"contentHash": "g8Kn575mNAKcuFotV3C7xvF+IbxuHennl67LH2shL2au1U6UqwReTDygCHyU04+koc2Yn7fHIbVQaC08HqEWow==",
"dependencies": {
"Microsoft.Build.Framework": "17.11.4",
"Microsoft.NET.StringTools": "17.11.4",
"Microsoft.Build.Framework": "17.11.48",
"Microsoft.NET.StringTools": "17.11.48",
"System.Collections.Immutable": "8.0.0",
"System.Configuration.ConfigurationManager": "8.0.0",
"System.Reflection.Metadata": "8.0.0",
@@ -82,8 +82,8 @@
},
"Microsoft.Build.Framework": {
"type": "Transitive",
"resolved": "17.11.4",
"contentHash": "u28uDihlqxtt8h2dL1ZJOZ7TRkxBK+HGr+3FgQpILVo7Q7gErkw8mYW9R+RM5PtxvZTdYb/4MWDL66vdIsANBQ=="
"resolved": "17.11.48",
"contentHash": "C3WIMt2wBl4++NX3jSEpTq5KXBhvAV154R4JrYHkfy9JSBcXWiL0mkgpspk5xSdOj+fS/uz7zluIy6bMM1fkkQ=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
@@ -97,8 +97,8 @@
},
"Microsoft.NET.StringTools": {
"type": "Transitive",
"resolved": "17.11.4",
"contentHash": "mudqUHhNpeqIdJoUx2YDWZO/I9uEDYVowan89R6wsomfnUJQk6HteoQTlNjZDixhT2B4IXMkMtgZtoceIjLRmA=="
"resolved": "17.11.48",
"contentHash": "0IQo089IGBEC4jgtishauZMVr9ZxOWNiGKeDvyzZlvw7p2r253lJh6IJCLLFWXvZnVrVO5mnsYIPamxFPzM08w=="
},
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
"type": "Transitive",
@@ -0,0 +1,296 @@
using Autodesk.AutoCAD.DatabaseServices;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Autocad.HostApp.Extensions;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Converters.Common;
using Speckle.Sdk.Common;
using Speckle.Sdk.Dependencies;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
using AutocadColor = Autodesk.AutoCAD.Colors.Color;
namespace Speckle.Connectors.Autocad.Operations.Receive;
/// <summary>
/// <para>Base class for AutoCAD host object builders. Expects to be a scoped dependency per receive operation.</para>
/// </summary>
public abstract class AutocadHostObjectBaseBuilder : IHostObjectBuilder
{
private readonly IRootToHostConverter _converter;
private readonly AutocadLayerBaker _layerBaker;
private readonly AutocadGroupBaker _groupBaker;
private readonly AutocadInstanceBaker _instanceBaker;
private readonly IAutocadMaterialBaker _materialBaker;
private readonly IAutocadColorBaker _colorBaker;
private readonly AutocadContext _autocadContext;
private readonly RootObjectUnpacker _rootObjectUnpacker;
private readonly IReceiveConversionHandler _conversionHandler;
protected AutocadHostObjectBaseBuilder(
IRootToHostConverter converter,
AutocadLayerBaker layerBaker,
AutocadGroupBaker groupBaker,
AutocadInstanceBaker instanceBaker,
IAutocadMaterialBaker materialBaker,
IAutocadColorBaker colorBaker,
AutocadContext autocadContext,
RootObjectUnpacker rootObjectUnpacker,
IReceiveConversionHandler conversionHandler
)
{
_converter = converter;
_layerBaker = layerBaker;
_groupBaker = groupBaker;
_instanceBaker = instanceBaker;
_materialBaker = materialBaker;
_colorBaker = colorBaker;
_autocadContext = autocadContext;
_rootObjectUnpacker = rootObjectUnpacker;
_conversionHandler = conversionHandler;
}
public Task<HostObjectBuilderResult> Build(
Base rootObject,
string projectName,
string modelName,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
{
// Prompt the UI conversion started. Progress bar will swoosh.
onOperationProgressed.Report(new("Converting", null));
// Layer filter for received commit with project and model name
_layerBaker.CreateLayerFilter(projectName, modelName);
// 0 - Clean then Rock n Roll!
string baseLayerPrefix = _autocadContext.RemoveInvalidChars($"SPK-{projectName}-{modelName}-");
PreReceiveDeepClean(baseLayerPrefix);
// 1 - Unpack objects and proxies from root commit object
var unpackedRoot = _rootObjectUnpacker.Unpack(rootObject);
// 2 - Split atomic objects and instance components with their path
var (atomicObjects, instanceComponents) = _rootObjectUnpacker.SplitAtomicObjectsAndInstances(
unpackedRoot.ObjectsToConvert
);
var atomicObjectsWithPath = _layerBaker.GetAtomicObjectsWithPath(atomicObjects);
var instanceComponentsWithPath = _layerBaker.GetInstanceComponentsWithPath(instanceComponents);
// POC: these are not captured by traversal, so we need to re-add them here
if (unpackedRoot.DefinitionProxies != null && unpackedRoot.DefinitionProxies.Count > 0)
{
var transformed = unpackedRoot.DefinitionProxies.Select(proxy =>
(Array.Empty<Collection>(), proxy as IInstanceComponent)
);
instanceComponentsWithPath.AddRange(transformed);
}
// 3 - Parse and bake proxies (materials and colors), as they are used later down the line by layers and objects
if (unpackedRoot.RenderMaterialProxies != null)
{
_materialBaker.ParseAndBakeRenderMaterials(
unpackedRoot.RenderMaterialProxies,
baseLayerPrefix,
onOperationProgressed
);
}
if (unpackedRoot.ColorProxies != null)
{
_colorBaker.ParseColors(unpackedRoot.ColorProxies, onOperationProgressed);
}
// 3.5 - Parse and bake additional proxies that are needed for conversion
ParseAndBakeAdditionalProxies(rootObject, baseLayerPrefix);
// 4 - Convert atomic objects
HashSet<ReceiveConversionResult> results = new();
HashSet<string> bakedObjectIds = new();
Dictionary<string, IReadOnlyCollection<Entity>> applicationIdMap = new();
var count = 0;
foreach (var (layerPath, atomicObject) in atomicObjectsWithPath)
{
onOperationProgressed.Report(new("Converting objects", (double)++count / atomicObjects.Count));
var ex = _conversionHandler.TryConvert(() =>
{
cancellationToken.ThrowIfCancellationRequested();
string objectId = atomicObject.applicationId ?? atomicObject.id.NotNull();
IReadOnlyCollection<Entity> convertedObjects = ConvertObject(atomicObject, layerPath, baseLayerPrefix);
applicationIdMap[objectId] = convertedObjects;
results.UnionWith(
convertedObjects.Select(e => new ReceiveConversionResult(
Status.SUCCESS,
atomicObject,
e.GetSpeckleApplicationId(),
e.GetType().ToString()
))
);
bakedObjectIds.UnionWith(convertedObjects.Select(e => e.GetSpeckleApplicationId()));
});
if (ex != null)
{
results.Add(new(Status.ERROR, atomicObject, null, null, ex));
}
}
// 5 - Convert instances
var (createdInstanceIds, consumedObjectIds, instanceConversionResults) = _instanceBaker.BakeInstances(
instanceComponentsWithPath,
applicationIdMap,
baseLayerPrefix,
onOperationProgressed
);
bakedObjectIds.RemoveWhere(id => consumedObjectIds.Contains(id));
bakedObjectIds.UnionWith(createdInstanceIds);
results.RemoveWhere(result => result.ResultId != null && consumedObjectIds.Contains(result.ResultId));
results.UnionWith(instanceConversionResults);
// 6 - Create groups
if (unpackedRoot.GroupProxies != null)
{
IReadOnlyCollection<ReceiveConversionResult> groupResults = _groupBaker.CreateGroups(
unpackedRoot.GroupProxies,
applicationIdMap
);
results.UnionWith(groupResults);
}
return Task.FromResult(new HostObjectBuilderResult(bakedObjectIds, results));
}
protected void PreReceiveDeepClean(string baseLayerPrefix)
{
_layerBaker.DeleteAllLayersByPrefix(baseLayerPrefix);
_instanceBaker.PurgeInstances(baseLayerPrefix);
_materialBaker.PurgeMaterials(baseLayerPrefix);
PreReceiveAdditionalDeepClean(baseLayerPrefix);
}
/// <summary>
/// Method for adding app-specific additional deep clean of the document prior to receiving.
/// </summary>
protected virtual void PreReceiveAdditionalDeepClean(string baseLayerPrefix) { }
/// <summary>
/// Method for parsing and baking additional app-specific proxies on the root prior to converting and baking objects
/// </summary>
protected virtual void ParseAndBakeAdditionalProxies(Base rootObject, string baseLayerPrefix) { }
private IReadOnlyCollection<Entity> ConvertObject(Base obj, Collection[] layerPath, string baseLayerNamePrefix)
{
string layerName = _layerBaker.CreateLayerForReceive(layerPath, baseLayerNamePrefix);
var convertedEntities = new HashSet<Entity>();
using var tr = Application.DocumentManager.CurrentDocument.Database.TransactionManager.StartTransaction();
// 1: convert
var converted = _converter.Convert(obj);
// 2: handle result
switch (converted)
{
case Entity entity:
var bakedEntity = BakeObject(entity, obj, layerName, tr);
convertedEntities.Add(bakedEntity);
break;
case List<(Entity, Base)> listConversionResult: // this is from fallback conversion for brep/brepx/subdx/extrusionx/polycurve
var bakedFallbackEntities = BakeObjectsAsGroup(listConversionResult, obj, layerName, baseLayerNamePrefix, tr);
convertedEntities.UnionWith(bakedFallbackEntities);
break;
default:
// TODO: capture defualt case with report object here? Same as in Rhino
break;
}
tr.Commit();
return convertedEntities.Freeze();
}
private Entity BakeObject(
Entity entity,
Base originalObject,
string layerName,
Transaction tr,
Base? parentObject = null
)
{
var objId = originalObject.applicationId ?? originalObject.id.NotNull();
if (_colorBaker.ObjectColorsIdMap.TryGetValue(objId, out AutocadColor? color))
{
entity.Color = color;
}
if (_materialBaker.TryGetMaterialId(originalObject, parentObject, out ObjectId matId))
{
entity.MaterialId = matId;
}
entity.AppendToDb(layerName);
// Hook for derived classes to perform additional operations after entity is added to database
PostBakeEntity(entity, originalObject, tr);
return entity;
}
/// <summary>
/// Method for additional app-specific operations on entities after the entity has been added to the document database.
/// Called after the entity is added to the database in an open transaction
/// </summary>
/// <param name="entity"></param>
/// <param name="originalObject"></param>
/// <param name="tr"></param>
protected virtual void PostBakeEntity(Entity entity, Base originalObject, Transaction tr)
{
// Default implementation does nothing - override in derived classes
}
private List<Entity> BakeObjectsAsGroup(
List<(Entity, Base)> fallbackConversionResult,
Base parentObject,
string layerName,
string baseLayerName,
Transaction tr
)
{
var ids = new ObjectIdCollection();
var entities = new List<Entity>();
foreach (var (conversionResult, originalObject) in fallbackConversionResult)
{
BakeObject(conversionResult, originalObject, layerName, tr, parentObject);
ids.Add(conversionResult.ObjectId);
entities.Add(conversionResult);
}
if (entities.Count <= 1) // return if empty list or only one, because we don't want to create empty or single item groups.
{
return entities;
}
var groupDictionary = (DBDictionary)
tr.GetObject(Application.DocumentManager.CurrentDocument.Database.GroupDictionaryId, OpenMode.ForWrite);
var groupName = _autocadContext.RemoveInvalidChars(
$@"{parentObject.speckle_type.Split('.').Last()} - {parentObject.applicationId ?? parentObject.id} ({baseLayerName})"
);
var newGroup = new Group(groupName, true);
newGroup.Append(ids);
groupDictionary.UpgradeOpen();
groupDictionary.SetAt(groupName, newGroup);
tr.AddNewlyCreatedDBObject(newGroup, true);
return entities;
}
}
@@ -1,238 +1,35 @@
using Autodesk.AutoCAD.DatabaseServices;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Autocad.HostApp.Extensions;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Converters.Common;
using Speckle.Sdk.Common;
using Speckle.Sdk.Dependencies;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
using AutocadColor = Autodesk.AutoCAD.Colors.Color;
namespace Speckle.Connectors.Autocad.Operations.Receive;
/// <summary>
/// <para>Expects to be a scoped dependency per receive operation.</para>
/// <para>AutoCAD-specific host object builder. Expects to be a scoped dependency per receive operation.</para>
/// </summary>
public class AutocadHostObjectBuilder(
IRootToHostConverter converter,
AutocadLayerBaker layerBaker,
AutocadGroupBaker groupBaker,
AutocadInstanceBaker instanceBaker,
IAutocadMaterialBaker materialBaker,
IAutocadColorBaker colorBaker,
AutocadContext autocadContext,
RootObjectUnpacker rootObjectUnpacker,
IReceiveConversionHandler conversionHandler
) : IHostObjectBuilder
public sealed class AutocadHostObjectBuilder : AutocadHostObjectBaseBuilder
{
public Task<HostObjectBuilderResult> Build(
Base rootObject,
string projectName,
string modelName,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
public AutocadHostObjectBuilder(
IRootToHostConverter converter,
AutocadLayerBaker layerBaker,
AutocadGroupBaker groupBaker,
AutocadInstanceBaker instanceBaker,
IAutocadMaterialBaker materialBaker,
IAutocadColorBaker colorBaker,
AutocadContext autocadContext,
RootObjectUnpacker rootObjectUnpacker,
IReceiveConversionHandler conversionHandler
)
{
// Prompt the UI conversion started. Progress bar will swoosh.
onOperationProgressed.Report(new("Converting", null));
// Layer filter for received commit with project and model name
layerBaker.CreateLayerFilter(projectName, modelName);
// 0 - Clean then Rock n Roll!
string baseLayerPrefix = autocadContext.RemoveInvalidChars($"SPK-{projectName}-{modelName}-");
PreReceiveDeepClean(baseLayerPrefix);
// 1 - Unpack objects and proxies from root commit object
var unpackedRoot = rootObjectUnpacker.Unpack(rootObject);
// 2 - Split atomic objects and instance components with their path
var (atomicObjects, instanceComponents) = rootObjectUnpacker.SplitAtomicObjectsAndInstances(
unpackedRoot.ObjectsToConvert
);
var atomicObjectsWithPath = layerBaker.GetAtomicObjectsWithPath(atomicObjects);
var instanceComponentsWithPath = layerBaker.GetInstanceComponentsWithPath(instanceComponents);
// POC: these are not captured by traversal, so we need to re-add them here
if (unpackedRoot.DefinitionProxies != null && unpackedRoot.DefinitionProxies.Count > 0)
{
var transformed = unpackedRoot.DefinitionProxies.Select(proxy =>
(Array.Empty<Collection>(), proxy as IInstanceComponent)
);
instanceComponentsWithPath.AddRange(transformed);
}
// 3 - Bake materials and colors, as they are used later down the line by layers and objects
if (unpackedRoot.RenderMaterialProxies != null)
{
materialBaker.ParseAndBakeRenderMaterials(
unpackedRoot.RenderMaterialProxies,
baseLayerPrefix,
onOperationProgressed
);
}
if (unpackedRoot.ColorProxies != null)
{
colorBaker.ParseColors(unpackedRoot.ColorProxies, onOperationProgressed);
}
// 4 - Convert atomic objects
HashSet<ReceiveConversionResult> results = new();
HashSet<string> bakedObjectIds = new();
Dictionary<string, IReadOnlyCollection<Entity>> applicationIdMap = new();
var count = 0;
foreach (var (layerPath, atomicObject) in atomicObjectsWithPath)
{
onOperationProgressed.Report(new("Converting objects", (double)++count / atomicObjects.Count));
var ex = conversionHandler.TryConvert(() =>
{
cancellationToken.ThrowIfCancellationRequested();
string objectId = atomicObject.applicationId ?? atomicObject.id.NotNull();
IReadOnlyCollection<Entity> convertedObjects = ConvertObject(atomicObject, layerPath, baseLayerPrefix);
applicationIdMap[objectId] = convertedObjects;
results.UnionWith(
convertedObjects.Select(e => new ReceiveConversionResult(
Status.SUCCESS,
atomicObject,
e.GetSpeckleApplicationId(),
e.GetType().ToString()
))
);
bakedObjectIds.UnionWith(convertedObjects.Select(e => e.GetSpeckleApplicationId()));
});
if (ex != null)
{
results.Add(new(Status.ERROR, atomicObject, null, null, ex));
}
}
// 5 - Convert instances
var (createdInstanceIds, consumedObjectIds, instanceConversionResults) = instanceBaker.BakeInstances(
instanceComponentsWithPath,
applicationIdMap,
baseLayerPrefix,
onOperationProgressed
);
bakedObjectIds.RemoveWhere(id => consumedObjectIds.Contains(id));
bakedObjectIds.UnionWith(createdInstanceIds);
results.RemoveWhere(result => result.ResultId != null && consumedObjectIds.Contains(result.ResultId));
results.UnionWith(instanceConversionResults);
// 6 - Create groups
if (unpackedRoot.GroupProxies != null)
{
IReadOnlyCollection<ReceiveConversionResult> groupResults = groupBaker.CreateGroups(
unpackedRoot.GroupProxies,
applicationIdMap
);
results.UnionWith(groupResults);
}
return Task.FromResult(new HostObjectBuilderResult(bakedObjectIds, results));
}
private void PreReceiveDeepClean(string baseLayerPrefix)
{
layerBaker.DeleteAllLayersByPrefix(baseLayerPrefix);
instanceBaker.PurgeInstances(baseLayerPrefix);
materialBaker.PurgeMaterials(baseLayerPrefix);
}
private IReadOnlyCollection<Entity> ConvertObject(Base obj, Collection[] layerPath, string baseLayerNamePrefix)
{
string layerName = layerBaker.CreateLayerForReceive(layerPath, baseLayerNamePrefix);
var convertedEntities = new HashSet<Entity>();
using var tr = Application.DocumentManager.CurrentDocument.Database.TransactionManager.StartTransaction();
// 1: convert
var converted = converter.Convert(obj);
// 2: handle result
switch (converted)
{
case Entity entity:
var bakedEntity = BakeObject(entity, obj, layerName);
convertedEntities.Add(bakedEntity);
break;
case List<(Entity, Base)> listConversionResult: // this is from fallback conversion for brep/brepx/subdx/extrusionx/polycurve
var bakedFallbackEntities = BakeObjectsAsGroup(listConversionResult, obj, layerName, baseLayerNamePrefix);
convertedEntities.UnionWith(bakedFallbackEntities);
break;
default:
// TODO: capture defualt case with report object here? Same as in Rhino
break;
}
tr.Commit();
return convertedEntities.Freeze();
}
private Entity BakeObject(Entity entity, Base originalObject, string layerName, Base? parentObject = null)
{
var objId = originalObject.applicationId ?? originalObject.id.NotNull();
if (colorBaker.ObjectColorsIdMap.TryGetValue(objId, out AutocadColor? color))
{
entity.Color = color;
}
if (materialBaker.TryGetMaterialId(originalObject, parentObject, out ObjectId matId))
{
entity.MaterialId = matId;
}
entity.AppendToDb(layerName);
return entity;
}
private List<Entity> BakeObjectsAsGroup(
List<(Entity, Base)> fallbackConversionResult,
Base parentObject,
string layerName,
string baseLayerName
)
{
var ids = new ObjectIdCollection();
var entities = new List<Entity>();
foreach (var (conversionResult, originalObject) in fallbackConversionResult)
{
BakeObject(conversionResult, originalObject, layerName, parentObject);
ids.Add(conversionResult.ObjectId);
entities.Add(conversionResult);
}
if (entities.Count <= 1) // return if empty list or only one, because we don't want to create empty or single item groups.
{
return entities;
}
var tr = Application.DocumentManager.CurrentDocument.Database.TransactionManager.TopTransaction;
var groupDictionary = (DBDictionary)
tr.GetObject(Application.DocumentManager.CurrentDocument.Database.GroupDictionaryId, OpenMode.ForWrite);
var groupName = autocadContext.RemoveInvalidChars(
$@"{parentObject.speckle_type.Split('.').Last()} - {parentObject.applicationId ?? parentObject.id} ({baseLayerName})"
);
var newGroup = new Group(groupName, true);
newGroup.Append(ids);
groupDictionary.UpgradeOpen();
groupDictionary.SetAt(groupName, newGroup);
tr.AddNewlyCreatedDBObject(newGroup, true);
return entities;
}
: base(
converter,
layerBaker,
groupBaker,
instanceBaker,
materialBaker,
colorBaker,
autocadContext,
rootObjectUnpacker,
conversionHandler
) { }
}
@@ -38,6 +38,7 @@
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Extensions\EntityExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Extensions\SpeckleApplicationIdExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\TransactionContext.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\AutocadHostObjectBaseBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\AutocadHostObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\AutocadRootObject.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\AutocadRootObjectBaseBuilder.cs" />
@@ -3,6 +3,8 @@ using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Autocad.DependencyInjection;
using Speckle.Connectors.Autocad.Operations.Send;
using Speckle.Connectors.Civil3dShared.Bindings;
using Speckle.Connectors.Civil3dShared.HostApp;
using Speckle.Connectors.Civil3dShared.Operations.Receive;
using Speckle.Connectors.Civil3dShared.Operations.Send;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.DUI.Bindings;
@@ -24,10 +26,12 @@ public static class Civil3dConnectorModule
// add receive
serviceCollection.LoadReceive();
serviceCollection.AddScoped<IHostObjectBuilder, Civil3dHostObjectBuilder>();
serviceCollection.AddSingleton<IBinding, Civil3dReceiveBinding>();
// additional classes
serviceCollection.AddScoped<PropertySetDefinitionHandler>();
serviceCollection.AddScoped<PropertySetBaker>();
// automatically detects the Class:IClass interface pattern to register all generated interfaces
serviceCollection.AddMatchingInterfacesAsTransient(Assembly.GetExecutingAssembly());
@@ -0,0 +1,404 @@
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Operations;
using Speckle.Converters.Civil3dShared;
using Speckle.Converters.Civil3dShared.Helpers;
using Speckle.Converters.Civil3dShared.ToSpeckle;
using Speckle.Converters.Common;
using Speckle.Sdk;
using Speckle.Sdk.Models;
using AAEC = Autodesk.Aec;
using AAECPDB = Autodesk.Aec.PropertyData.DatabaseServices;
using ADB = Autodesk.AutoCAD.DatabaseServices;
namespace Speckle.Connectors.Civil3dShared.HostApp;
/// <summary>
/// Helper class to bake property sets to entities on receive.
/// </summary>
public class PropertySetBaker
{
private const string PROP_SET_DEF_DICT_NAME = "AecPropertySetDefs";
private readonly IConverterSettingsStore<Civil3dConversionSettings> _settingsStore;
private readonly ILogger<PropertySetBaker> _logger;
private readonly PropertyHandler _propertyHandler;
/// <summary>
/// Map of property set definition name to its ObjectId. Populated during ParsePropertySetDefinitions.
/// </summary>
private readonly Dictionary<string, ADB.ObjectId> _propertySetDefinitionMap = new();
public PropertySetBaker(
IConverterSettingsStore<Civil3dConversionSettings> settingsStore,
ILogger<PropertySetBaker> logger
)
{
_settingsStore = settingsStore;
_logger = logger;
_propertyHandler = new PropertyHandler();
}
/// <summary>
/// Removes all property set definitions with a prefix before receive operation.
/// </summary>
public void PurgePropertySets(string namePrefix)
{
ADB.Database db = _settingsStore.Current.Document.Database;
using var tr = db.TransactionManager.StartTransaction();
List<ADB.ObjectId> definitionsToDelete = new();
// Access the property set definition dictionary from the named object dictionary
var nod = (ADB.DBDictionary)tr.GetObject(db.NamedObjectsDictionaryId, ADB.OpenMode.ForRead);
if (nod.Contains(PROP_SET_DEF_DICT_NAME))
{
ADB.ObjectId propSetDefsDictId = nod.GetAt(PROP_SET_DEF_DICT_NAME);
var propSetDefsDict = (ADB.DBDictionary)tr.GetObject(propSetDefsDictId, ADB.OpenMode.ForRead);
// Iterate through all property set definitions in the dictionary
foreach (ADB.DBDictionaryEntry entry in propSetDefsDict)
{
if (entry.Key.Contains(namePrefix))
{
definitionsToDelete.Add(entry.Value);
}
}
}
// Delete the matching definitions
foreach (ADB.ObjectId defId in definitionsToDelete)
{
try
{
var propSetDef = (AAECPDB.PropertySetDefinition)tr.GetObject(defId, ADB.OpenMode.ForWrite);
propSetDef.Erase();
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogWarning(ex, "Failed to purge property set definition");
}
}
tr.Commit();
}
/// <summary>
/// Parse and bake all property set definitions from the root object.
/// Should be called after purging and after materials/colors are parsed.
/// </summary>
public void ParseAndBakePropertySetDefinitions(Base rootObject, string namePrefix)
{
_propertySetDefinitionMap.Clear();
if (rootObject[ProxyKeys.PROPERTYSET_DEFINITIONS] is not Dictionary<string, object?> definitions)
{
return;
}
if (definitions.Count == 0)
{
return;
}
using var tr = _settingsStore.Current.Document.Database.TransactionManager.StartTransaction();
foreach (var definition in definitions)
{
string setName = definition.Key;
object? setDefObj = definition.Value;
if (setDefObj is not Dictionary<string, object?> setDefData)
{
_logger.LogWarning("Property set definition {SetName} has invalid data format", setName);
continue;
}
if (!setDefData.TryGetValue(PropertySetDefinitionHandler.PROP_SET_PROP_DEFS_KEY, out var propDefsObj))
{
_logger.LogWarning("Property set definition {SetName} missing propertyDefinitions", setName);
continue;
}
if (propDefsObj is not Dictionary<string, object?> propertyDefinitions)
{
_logger.LogWarning("Property set definition {SetName} propertyDefinitions has invalid format", setName);
continue;
}
ADB.ObjectId defId = CreatePropertySetDefinition(setName, propertyDefinitions, namePrefix, tr);
if (!defId.IsNull)
{
_propertySetDefinitionMap[setName] = defId;
}
}
tr.Commit();
}
/// <summary>
/// Try to bake property sets from a Speckle object to a Civil3D entity.
/// </summary>
public bool TryBakePropertySets(ADB.Entity entity, Base sourceObject, ADB.Transaction tr)
{
if (
sourceObject["properties"] is not Dictionary<string, object?> properties
|| !properties.TryGetValue("Property Sets", out var propertySetsObj)
|| propertySetsObj is not Dictionary<string, object?> propertySets
|| propertySets.Count == 0
)
{
return false;
}
try
{
foreach (var propertySet in propertySets)
{
string setName = propertySet.Key;
object? setDataObj = propertySet.Value;
if (setDataObj is not Dictionary<string, object?> setData)
{
_logger.LogWarning("Property set {SetName} has invalid data format", setName);
continue;
}
if (!TryBakePropertySet(entity, setName, setData, tr))
{
_logger.LogWarning("Failed to bake property set {SetName} onto entity", setName);
}
}
return true;
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogError(ex, "Failed to bake property sets onto entity {Handle}", entity.Handle);
return false;
}
}
private bool TryBakePropertySet(
ADB.Entity entity,
string setName,
Dictionary<string, object?> setData,
ADB.Transaction tr
)
{
try
{
if (!_propertySetDefinitionMap.TryGetValue(setName, out ADB.ObjectId propertySetDefId))
{
_logger.LogWarning("Property set definition {SetName} not found in definition map", setName);
return false;
}
if (propertySetDefId.IsNull)
{
return false;
}
if (ObjectHasPropertySet(entity, propertySetDefId))
{
throw new SpeckleException($"Property set '{setName}' already exists on entity.");
}
return AddPropertySetToEntity(entity, propertySetDefId, setData, tr);
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogWarning(ex, "Failed to process property set {SetName}", setName);
return false;
}
}
private ADB.ObjectId CreatePropertySetDefinition(
string setName,
Dictionary<string, object?> propertyDefinitions,
string namePrefix,
ADB.Transaction tr
)
{
var db = _settingsStore.Current.Document.Database;
using AAECPDB.DictionaryPropertySetDefinitions propSetDefs = new(db);
string prefixedName = $"{setName}-{namePrefix}";
AAECPDB.PropertySetDefinition propSetDef = new();
propSetDef.SetToStandard(db);
propSetDef.SubSetDatabaseDefaults(db);
//propSetDef.Description = "Property Set Definition added by Speckle"; // POC: should use the description that was published. can this back in if needed
propSetDef.AppliesToAll = true;
foreach (var propertyDefinition in propertyDefinitions)
{
string propertyName = propertyDefinition.Key;
object? propertyDefObj = propertyDefinition.Value;
if (propertyDefObj is not Dictionary<string, object?> propertyDefDict)
{
continue;
}
if (
!propertyDefDict.TryGetValue(PropertySetDefinitionHandler.PROP_DEF_TYPE_KEY, out var dataTypeStr)
|| dataTypeStr is not string dataTypeString
)
{
_logger.LogError(
"Property set definition {SetName} is invalid: property {PropertyName} missing or invalid dataType",
setName,
propertyName
);
return ADB.ObjectId.Null;
}
if (!Enum.TryParse(dataTypeString, out AAEC.PropertyData.DataType dataType))
{
_logger.LogError(
"Property set definition {SetName} is invalid: unsupported data type {DataType} for property {PropertyName}",
setName,
dataTypeString,
propertyName
);
return ADB.ObjectId.Null;
}
AAECPDB.PropertyDefinition propDef = new() { DataType = dataType, Name = propertyName };
propDef.SetToStandard(db);
propDef.SubSetDatabaseDefaults(db);
if (
propertyDefDict.TryGetValue(PropertySetDefinitionHandler.PROP_DEF_DEFAULT_VALUE_KEY, out object? defaultValue)
&& defaultValue != null
)
{
try
{
// Cast numeric types to avoid bad numeric value errors
var convertedValue = dataType switch
{
AAEC.PropertyData.DataType.Integer => (int)(long)defaultValue,
AAEC.PropertyData.DataType.AutoIncrement => (int)(long)defaultValue,
_ => defaultValue
};
propDef.DefaultData = convertedValue;
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogWarning(
ex,
"Failed to set default value for property {PropertyName}, continuing without default",
propertyName
);
}
}
propSetDef.Definitions.Add(propDef);
}
propSetDefs.AddNewRecord(prefixedName, propSetDef);
tr.AddNewlyCreatedDBObject(propSetDef, true);
return propSetDef.ObjectId;
}
private bool ObjectHasPropertySet(ADB.DBObject obj, ADB.ObjectId propertySetId)
{
try
{
ADB.ObjectId tempId = AAECPDB.PropertyDataServices.GetPropertySet(obj, propertySetId);
return !tempId.IsNull;
}
catch (Autodesk.AutoCAD.Runtime.Exception ex) when (!ex.IsFatal())
{
return false;
}
}
private bool AddPropertySetToEntity(
ADB.Entity entity,
ADB.ObjectId propertySetDefId,
Dictionary<string, object?> setData,
ADB.Transaction tr
)
{
try
{
if (!entity.IsWriteEnabled)
{
entity.UpgradeOpen();
}
AAECPDB.PropertyDataServices.AddPropertySet(entity, propertySetDefId);
return TrySetPropertyValues(entity, propertySetDefId, setData, tr);
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogWarning(ex, "Failed to add property set to entity");
return false;
}
}
private bool TrySetPropertyValues(
ADB.Entity entity,
ADB.ObjectId propertySetDefId,
Dictionary<string, object?> setData,
ADB.Transaction tr
)
{
try
{
ADB.ObjectId propertySetId = AAECPDB.PropertyDataServices.GetPropertySet(entity, propertySetDefId);
var propertySet = (AAECPDB.PropertySet)tr.GetObject(propertySetId, ADB.OpenMode.ForWrite);
var setDefinition = (AAECPDB.PropertySetDefinition)tr.GetObject(propertySetDefId, ADB.OpenMode.ForRead);
// Build a map of property names to definition IDs
Dictionary<string, int> propertyNameToId = new();
foreach (AAECPDB.PropertyDefinition propDef in setDefinition.Definitions)
{
propertyNameToId[propDef.Name] = propDef.Id;
}
foreach (var propertyEntry in setData)
{
string propertyName = propertyEntry.Key;
object? propertyDataObj = propertyEntry.Value;
if (propertyDataObj is not Dictionary<string, object?> propertyDataDict)
{
continue;
}
if (!propertyDataDict.TryGetValue("value", out var value) || value == null)
{
continue;
}
if (!propertyNameToId.TryGetValue(propertyName, out int propertyId))
{
continue;
}
_propertyHandler.TryGetValue(
() =>
{
propertySet.SetAt(propertyId, value);
return true;
},
out _
);
}
return true;
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogWarning(ex, "Failed to update property set values");
return false;
}
}
}
@@ -0,0 +1,60 @@
using Autodesk.AutoCAD.DatabaseServices;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Autocad.Operations.Receive;
using Speckle.Connectors.Civil3dShared.HostApp;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Converters.Common;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.Civil3dShared.Operations.Receive;
/// <summary>
/// <para>Civil3D specific host object builder with property set support. Expects to be a scoped dependency per receive operation.</para>
/// </summary>
public sealed class Civil3dHostObjectBuilder : AutocadHostObjectBaseBuilder
{
private readonly PropertySetBaker _propertySetBaker;
public Civil3dHostObjectBuilder(
IRootToHostConverter converter,
AutocadLayerBaker layerBaker,
AutocadGroupBaker groupBaker,
AutocadInstanceBaker instanceBaker,
IAutocadMaterialBaker materialBaker,
IAutocadColorBaker colorBaker,
AutocadContext autocadContext,
RootObjectUnpacker rootObjectUnpacker,
IReceiveConversionHandler conversionHandler,
PropertySetBaker propertySetBaker
)
: base(
converter,
layerBaker,
groupBaker,
instanceBaker,
materialBaker,
colorBaker,
autocadContext,
rootObjectUnpacker,
conversionHandler
)
{
_propertySetBaker = propertySetBaker;
}
protected override void PreReceiveAdditionalDeepClean(string baseLayerPrefix)
{
_propertySetBaker.PurgePropertySets(baseLayerPrefix);
}
protected override void ParseAndBakeAdditionalProxies(Base rootObject, string baseLayerPrefix)
{
_propertySetBaker.ParseAndBakePropertySetDefinitions(rootObject, baseLayerPrefix);
}
protected override void PostBakeEntity(Entity entity, Base originalObject, Transaction tr)
{
_propertySetBaker.TryBakePropertySets(entity, originalObject, tr);
}
}
@@ -11,11 +11,15 @@
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Bindings\Civil3dReceiveBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DependencyInjection\Civil3dConnectorModule.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\PropertySetBaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\Civil3dHostObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Civil3dRootObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\Civil3dSendBinding.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="$(MSBuildThisFileDirectory)DependencyInjection\" />
<Folder Include="$(MSBuildThisFileDirectory)HostApp\" />
<Folder Include="$(MSBuildThisFileDirectory)Operations\Receive\" />
<Folder Include="$(MSBuildThisFileDirectory)Operations\Send\" />
</ItemGroup>
</Project>
@@ -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
@@ -29,9 +29,11 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
private readonly DocumentModelStore _store;
private readonly ICancellationManager _cancellationManager;
private readonly ISendConversionCache _sendConversionCache;
private readonly ToSpeckleSettingsManager _toSpeckleSettingsManager;
private readonly ElementUnpacker _elementUnpacker;
private readonly IRevitConversionSettingsFactory _revitConversionSettingsFactory;
private readonly RevitToSpeckleCacheSingleton _revitToSpeckleCacheSingleton;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly LinkedModelHandler _linkedModelHandler;
private readonly IThreadContext _threadContext;
@@ -55,6 +57,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
ToSpeckleSettingsManager toSpeckleSettingsManager,
ElementUnpacker elementUnpacker,
IRevitConversionSettingsFactory revitConversionSettingsFactory,
RevitToSpeckleCacheSingleton revitToSpeckleCacheSingleton,
ITopLevelExceptionHandler topLevelExceptionHandler,
LinkedModelHandler linkedModelHandler,
IThreadContext threadContext,
@@ -71,6 +74,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
_toSpeckleSettingsManager = toSpeckleSettingsManager;
_elementUnpacker = elementUnpacker;
_revitConversionSettingsFactory = revitConversionSettingsFactory;
_revitToSpeckleCacheSingleton = revitToSpeckleCacheSingleton;
_topLevelExceptionHandler = topLevelExceptionHandler;
_linkedModelHandler = linkedModelHandler;
_threadContext = threadContext;
@@ -110,6 +114,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 +129,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 +152,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 +160,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 +191,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 +336,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);
}
}
@@ -444,6 +453,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
private async Task OnDocumentChanged()
{
_sendConversionCache.ClearCache();
_revitToSpeckleCacheSingleton.ClearCache();
if (_cancellationManager.NumberOfOperations > 0)
{
@@ -34,18 +34,22 @@ public class LevelUnpacker
Dictionary<string, LevelProxy> levelProxies = new();
foreach (var element in elements)
{
if (levelProxies.TryGetValue(element.LevelId.ToString(), out LevelProxy? levelProxy))
// NOTE: Use level.UniqueId (not element.LevelId) as key
// face-based instances don't have a valid element.LevelId, hence all the changes in the LevelExtractor
var level = _levelExtractor.GetLevel(element);
if (level is null)
{
continue;
}
string levelKey = level.UniqueId;
if (levelProxies.TryGetValue(levelKey, out LevelProxy? levelProxy))
{
levelProxy.objects.Add(element.UniqueId);
}
else
{
var level = _levelExtractor.GetLevel(element);
if (level is null)
{
continue;
}
var levelDataObject = new DataObject()
{
name = level.Name,
@@ -53,11 +57,11 @@ public class LevelUnpacker
properties = _propertiesExtractor.GetProperties(level)
};
var unitSettings = _converterSettings.Current.Document.GetUnits();
var lengthUnitType = unitSettings.GetFormatOptions(Autodesk.Revit.DB.SpecTypeId.Length).GetUnitTypeId();
var lengthUnitType = unitSettings.GetFormatOptions(SpecTypeId.Length).GetUnitTypeId();
levelDataObject["elevation"] = UnitUtils.ConvertFromInternalUnits(level.Elevation, lengthUnitType);
levelDataObject["units"] = _converterSettings.Current.SpeckleUnits;
levelProxies[element.LevelId.ToString()] = new LevelProxy()
levelProxies[levelKey] = new LevelProxy()
{
applicationId = level.UniqueId,
objects = [element.UniqueId],
@@ -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 doc.Title;
}
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);
}
}
@@ -120,9 +120,14 @@ public class RevitMaterialBaker
try
{
// all values assumed to be on the 0 - 1 scale need to pass through this validation and logging (if assumption wrong)
double roughness = ClampToUnitRange(speckleRenderMaterial.roughness, "roughness", speckleRenderMaterial.name);
double opacity = ClampToUnitRange(speckleRenderMaterial.opacity, "opacity", speckleRenderMaterial.name);
double metalness = ClampToUnitRange(speckleRenderMaterial.metalness, "metalness", speckleRenderMaterial.name);
var diffuse = System.Drawing.Color.FromArgb(speckleRenderMaterial.diffuse);
double transparency = 1 - speckleRenderMaterial.opacity;
double smoothness = 1 - speckleRenderMaterial.roughness;
double transparency = 1 - opacity;
double smoothness = 1 - roughness;
string materialId = speckleRenderMaterial.applicationId ?? speckleRenderMaterial.id.NotNull();
string matName = _revitUtils.RemoveInvalidChars($"{speckleRenderMaterial.name}-({materialId})-{baseLayerName}");
@@ -130,7 +135,7 @@ public class RevitMaterialBaker
var revitMaterial = (Material)_converterSettings.Current.Document.GetElement(newMaterialId);
revitMaterial.Color = new Color(diffuse.R, diffuse.G, diffuse.B);
revitMaterial.Transparency = (int)(transparency * 100);
revitMaterial.Shininess = (int)(speckleRenderMaterial.metalness * 128);
revitMaterial.Shininess = (int)(metalness * 128);
revitMaterial.Smoothness = (int)(smoothness * 128);
foreach (var objectId in proxy.objects)
@@ -163,4 +168,30 @@ public class RevitMaterialBaker
document.Delete(materialIds);
}
}
/// <summary>
/// After CNX-2661, we've seen some edge cases contradicting the expected 0 - 1 range for PRB properties.
/// Defensively, we'd rather clamp these values than throw.
/// </summary>
/// <remarks>
/// Created a method so that we can extend the checks to any numerical value potentially leading to a negative value,
/// which would throw an exception. Generalised method since Math.Clamp() only available since C# 8.0 and this method
/// handles logging (in the hope that we can get a better feel for these "weird" models, e.g. 0 - 100 scale??)
/// </remarks>
private double ClampToUnitRange(double value, string propertyName, string materialName)
{
if (value is < 0 or > 1)
{
_logger.LogWarning(
"Material '{MaterialName}' has an invalid {PropertyName} value of {Value} and was clamped to 0 - 1 range",
materialName,
propertyName,
value
);
value = Math.Min(Math.Max(0, value), 1);
}
return value;
}
}
@@ -110,7 +110,8 @@ public sealed class RevitHostObjectBuilder(
// TODO: TransformTo and material baking needs to be fixed in Revit!!
// create a mapping from original to modified IDs <- so that we can actually map ids in the proxies to the objects
Dictionary<string, string> originalToModifiedIds = new();
// as part of CNX-2677, we have a one-to-many problem. many instances share the same reference, so we use a list
Dictionary<string, List<string>> originalToModifiedIds = new();
// modify application IDs BEFORE material baking
foreach (LocalToGlobalMap localToGlobalMap in localToGlobalMaps)
@@ -139,7 +140,13 @@ public sealed class RevitHostObjectBuilder(
string modifiedAppId = $"{originalAppId}_{Guid.NewGuid().ToString("N")[..8]}";
if (originalAppId != null)
{
originalToModifiedIds[originalAppId] = modifiedAppId;
if (!originalToModifiedIds.TryGetValue(originalAppId, out List<string>? modifiedIds))
{
modifiedIds = new List<string>();
originalToModifiedIds[originalAppId] = modifiedIds;
}
modifiedIds.Add(modifiedAppId);
}
localToGlobalMap.AtomicObject.applicationId = modifiedAppId;
@@ -152,14 +159,20 @@ public sealed class RevitHostObjectBuilder(
{
foreach (var proxy in unpackedRoot.RenderMaterialProxies)
{
var updatedObjects = new List<string>();
var objectIdsToUse = new List<string>();
foreach (var objectId in proxy.objects)
{
// Use the modified ID if it exists, otherwise keep the original <- this SUCKS and we need to change
string idToUse = originalToModifiedIds.TryGetValue(objectId, out var modifiedId) ? modifiedId : objectId;
updatedObjects.Add(idToUse);
if (originalToModifiedIds.TryGetValue(objectId, out var modifiedIds))
{
objectIdsToUse.AddRange(modifiedIds);
}
else
{
objectIdsToUse.Add(objectId);
}
}
proxy.objects = updatedObjects;
proxy.objects = objectIdsToUse;
}
}
@@ -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)
@@ -183,6 +183,7 @@ public class RevitRootObjectBuilder(
// non-transformed elements can safely rely on cache
// TODO: Potential here to transform cached objects and NOT reconvert,
// TODO: we wont do !hasTransform here, and re-set application id before this
if (!hasTransform && sendConversionCache.TryGetValue(projectId, applicationId, out ObjectReference? value))
{
converted = value;
@@ -248,6 +249,17 @@ public class RevitRootObjectBuilder(
var levelProxies = levelUnpacker.Unpack(flatElements);
rootObject[ProxyKeys.LEVEL] = levelProxies;
rootObject[ProxyKeys.INSTANCE_DEFINITION] = revitToSpeckleCacheSingleton.GetInstanceDefinitionProxiesForObjects(
idsAndSubElementIds
);
rootObject.elements.Add(
new Collection()
{
elements = revitToSpeckleCacheSingleton.GetBaseObjectsForObjects(idsAndSubElementIds),
name = "revitInstancedObjects"
}
);
// NOTE: these are currently not used anywhere, we'll skip them until someone calls for it back
// rootObject[ProxyKeys.PARAMETER_DEFINITIONS] = _parameterDefinitionHandler.Definitions;
@@ -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);
}
}
@@ -87,6 +87,7 @@ public class SpeckleSelectModelComponent : GH_Component
string? urlInput = null;
// SCENARIO 1: Component has input wire connected
if (da.GetData(0, ref urlInput))
{
UrlInput = urlInput;
@@ -99,6 +100,11 @@ public class SpeckleSelectModelComponent : GH_Component
return;
}
if (_justPastedIn)
{
RestoreAccountFromStoredState();
}
try
{
// NOTE: once we split the logic in Sender and Receiver components, we need to set flag correctly
@@ -132,22 +138,9 @@ public class SpeckleSelectModelComponent : GH_Component
_storedUserId = SpeckleOperationWizard.SelectedAccount?.id;
}
if (_justPastedIn && _storedUserId != null && !string.IsNullOrEmpty(_storedUserId))
if (_justPastedIn)
{
try
{
SpeckleOperationWizard.SetAccountFromId(_storedUserId);
}
catch (SpeckleAccountManagerException e)
{
// Swallow and move onto checking server.
Console.WriteLine(e);
}
if (_storedServer != null && SpeckleOperationWizard.SelectedAccount == null)
{
SpeckleOperationWizard.SetAccountFromIdAndUrl(_storedUserId, _storedServer);
}
RestoreAccountFromStoredState();
}
// Validate backing data
@@ -396,4 +389,39 @@ public class SpeckleSelectModelComponent : GH_Component
VersionContextMenuButton.ExpirePreview(redraw);
base.ExpirePreview(redraw);
}
/// <summary>
/// Restores the account from stored state when the component is pasted or loaded from file.
/// </summary>
/// <remarks>
/// Attempts to restore account in two stages:
/// <list type="number">
/// <item>First tries to get account by stored user ID</item>
/// <item>If that fails and server url is available, falls back to getting any account matching the server</item>
/// </list>
/// Only executes when <see cref="_justPastedIn"/> is true and <see cref="_storedUserId"/> is not empty.
/// </remarks>
private void RestoreAccountFromStoredState()
{
if (_storedUserId is null || string.IsNullOrEmpty(_storedUserId))
{
return;
}
try
{
SpeckleOperationWizard.SetAccountFromId(_storedUserId);
}
catch (SpeckleAccountManagerException e)
{
Console.WriteLine(e);
}
// Fallback: if account wasn't found by ID but we have a server URL,
// try to find any account matching that server
if (_storedServer != null && SpeckleOperationWizard.SelectedAccount == null)
{
SpeckleOperationWizard.SetAccountFromIdAndUrl(_storedUserId, _storedServer);
}
}
}
@@ -79,22 +79,40 @@ public class SpeckleOperationWizard
var resources = SpeckleResourceBuilder.FromUrlString(input, token);
if (resources.Length == 0)
{
throw new SpeckleException($"Input url string was empty");
throw new SpeckleException("Input url string was empty");
}
if (resources.Length > 1)
{
throw new SpeckleException($"Input multi-model url is not supported");
throw new SpeckleException("Input multi-model url is not supported");
}
var resource = resources.First();
using var scope = PriorityLoader.CreateScopeForActiveDocument();
var account = resource.Account.GetAccount(scope);
SetAccount(account, false);
var urlDerivedAccount = resource.Account.GetAccount(scope);
// if no account is selected, happily go through the url derived account approach
if (SelectedAccount == null)
{
throw new SpeckleException("No account found for server URL");
SetAccount(urlDerivedAccount, false);
}
// if we have an account from right-click context-menu, we rely on that and just validate that it's actually applicable to that server
else if (urlDerivedAccount != null && SelectedAccount.serverInfo.url != urlDerivedAccount.serverInfo.url)
{
throw new SpeckleException(
$"Selected account is for '{SelectedAccount.serverInfo.url}' "
+ $"but URL requires '{urlDerivedAccount.serverInfo.url}'"
);
}
// we have both scenarios covered
// Scenario #1 - default account from url
// Scenario #2 - triggered by account switch on right-click context (and validated)
if (SelectedAccount == null)
{
throw new SpeckleException(
$"No appropriate account found for the given '{urlDerivedAccount?.serverInfo.url}' server"
);
}
IClient client = _clientFactory.Create(SelectedAccount);
@@ -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"/>
@@ -104,10 +104,10 @@ public class RhinoInstanceBaker : IInstanceBaker<IReadOnlyCollection<string>>
attributes
);
// POC: check on defIndex -1, means we haven't created anything - this is most likely an recoverable error at this stage
// POC: check on defIndex -1, means we haven't created anything - this is most likely an unrecoverable error at this stage
if (defIndex == -1)
{
throw new ConversionException("Failed to create an instance defintion object.");
throw new ConversionException("Failed to create an instance definition object.");
}
if (definitionProxy.applicationId != null)
@@ -170,9 +170,13 @@ public class RhinoInstanceBaker : IInstanceBaker<IReadOnlyCollection<string>>
public void PurgeInstances(string namePrefix)
{
var currentDoc = RhinoDoc.ActiveDoc; // POC: too much right now to interface around
// clean name prefix to match how block names are created
var cleanedPrefix = RhinoUtils.CleanBlockDefinitionName(namePrefix);
foreach (var definition in currentDoc.InstanceDefinitions)
{
if (!definition.IsDeleted && definition.Name.Contains(namePrefix))
if (!definition.IsDeleted && definition.Name.Contains(cleanedPrefix))
{
currentDoc.InstanceDefinitions.Delete(definition.Index, true, false);
}
@@ -218,13 +218,23 @@ public class RhinoMaterialUnpacker
? pbRenderMaterial.Material.EmissionColor
: pbRenderMaterial.Emission.AsSystemColor(); // pbRenderMaterial.emission gives wrong color for emission materials, and material.emissioncolor gives the wrong value for most others *shrug*
// NOTE: added after CNX-2661, without having file that caused issue hard to say what the issue is
// api bug / funny model (custom textures) / upgrade from old model (e.g. Rhino 6)? who knows.
// PBR standard is 0-1. Clamping to valid range. This may indicate texture data is in wrong scale.
double roughness = pbRenderMaterial.Roughness;
if (roughness < 0 || roughness > 1)
{
_logger.LogWarning("Material '{Name}' has invalid roughness value of {Value}", renderMaterial.Name, roughness);
roughness = Math.Min(Math.Max(0, roughness), 1); // Math.Clamp() only from C# 8.0
}
SpeckleRenderMaterial speckleRenderMaterial =
new()
{
name = renderMaterialName,
opacity = opacity,
metalness = pbRenderMaterial.Metallic,
roughness = pbRenderMaterial.Roughness,
roughness = roughness,
diffuse = diffuse.ToArgb(),
emissive = emissive.ToArgb(),
applicationId = renderMaterial.Id.ToString()
@@ -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>
@@ -14,6 +14,17 @@ public class PropertySetDefinitionHandler
/// POC: We're storing these by property set def name atm. There is a decent change different property sets can have the same name, need to validate this.
public Dictionary<string, Dictionary<string, object?>> Definitions { get; } = new();
// Keys used for the dictionary representing a single property set definition
public const string PROP_SET_DEF_NAME_KEY = "name"; // name of the property set definition
public const string PROP_SET_PROP_DEFS_KEY = "propertyDefinitions"; // property definitions in this property set definition
// Keys used for inidividual property definitions within a single property set definition
public const string PROP_DEF_NAME_KEY = "name";
public const string PROP_DEF_DESCRIPTION_KEY = "description";
public const string PROP_DEF_ID_KEY = "id";
public const string PROP_DEF_TYPE_KEY = "dataType";
public const string PROP_DEF_DEFAULT_VALUE_KEY = "defaultValue";
/// <summary>
/// Extracts out and stores in <see cref="Definitions"/> the property set definition.
/// </summary>
@@ -29,12 +40,11 @@ public class PropertySetDefinitionHandler
propertyDefinitionNames[propertyDefinition.Id] = propertyName;
var propertyDict = new Dictionary<string, object?>()
{
["name"] = propertyName,
["description"] = propertyDefinition.Description,
["id"] = propertyDefinition.Id,
["isReadOnly"] = propertyDefinition.IsReadOnly,
["dataType"] = propertyDefinition.DataType.ToString(),
["defaultValue"] = propertyDefinition.DefaultData
[PROP_DEF_NAME_KEY] = propertyName,
[PROP_DEF_DESCRIPTION_KEY] = propertyDefinition.Description,
[PROP_DEF_ID_KEY] = propertyDefinition.Id,
[PROP_DEF_TYPE_KEY] = propertyDefinition.DataType.ToString(),
[PROP_DEF_DEFAULT_VALUE_KEY] = propertyDefinition.DefaultData
};
// accessing unit type prop can be expected to throw if it's not applicable to the definition
@@ -53,8 +63,8 @@ public class PropertySetDefinitionHandler
Definitions[name] = new Dictionary<string, object?>()
{
["name"] = name,
["propertyDefinitions"] = propertyDefinitionsDict
[PROP_SET_DEF_NAME_KEY] = name,
[PROP_SET_PROP_DEFS_KEY] = propertyDefinitionsDict
};
return propertyDefinitionNames;
@@ -91,7 +91,8 @@ public class PropertySetExtractor
? propertyDefinitionName
: data.FieldBucketId;
var value = GetValue(data);
// POC: not sure how to support graphic types atm
var value = data.DataType is AAEC.PropertyData.DataType.Graphic ? null : data.GetData(data.UnitType);
Dictionary<string, object?> propertyValueDict = new() { ["value"] = value, ["name"] = dataName };
PropertyHandler propHandler = new();
@@ -109,31 +110,4 @@ public class PropertySetExtractor
return null;
}
private object? GetValue(AAECPDB.PropertySetData data)
{
object fieldData = data.GetData(data.UnitType);
switch (data.DataType)
{
case AAEC.PropertyData.DataType.Integer:
return fieldData as int?;
case AAEC.PropertyData.DataType.Real:
return fieldData as double?;
case AAEC.PropertyData.DataType.TrueFalse:
return fieldData as bool?;
case AAEC.PropertyData.DataType.Graphic: // POC: not sure how to support atm
return null;
case AAEC.PropertyData.DataType.List:
return fieldData as List<object>;
case AAEC.PropertyData.DataType.AutoIncrement:
return fieldData as int?;
case AAEC.PropertyData.DataType.AlphaIncrement: // POC: not sure what this is
return fieldData;
case AAEC.PropertyData.DataType.Text:
return fieldData as string;
default:
return fieldData;
}
}
}
@@ -1,8 +1,10 @@
using Microsoft.Extensions.Logging;
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Converters.Common.ToSpeckle;
using Speckle.Converters.RevitShared.Extensions;
using Speckle.Converters.RevitShared.Services;
using Speckle.Converters.RevitShared.Settings;
using Speckle.DoubleNumerics;
using Speckle.Objects;
using Speckle.Sdk;
using Speckle.Sdk.Common;
@@ -18,11 +20,11 @@ public sealed class DisplayValueExtractor
List<SOG.Mesh>
> _meshByMaterialConverter;
private readonly IScalingServiceToSpeckle _toSpeckleScalingService;
private readonly ITypedConverter<DB.Curve, ICurve> _curveConverter;
private readonly ITypedConverter<DB.PolyLine, SOG.Polyline> _polylineConverter;
private readonly ITypedConverter<DB.Point, SOG.Point> _pointConverter;
private readonly ITypedConverter<DB.PointCloudInstance, SOG.Pointcloud> _pointcloudConverter;
private readonly ILogger<DisplayValueExtractor> _logger;
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
public DisplayValueExtractor(
@@ -34,8 +36,8 @@ public sealed class DisplayValueExtractor
ITypedConverter<DB.PolyLine, SOG.Polyline> polylineConverter,
ITypedConverter<DB.Point, SOG.Point> pointConverter,
ITypedConverter<DB.PointCloudInstance, SOG.Pointcloud> pointcloudConverter,
ILogger<DisplayValueExtractor> logger,
IConverterSettingsStore<RevitConversionSettings> converterSettings
IConverterSettingsStore<RevitConversionSettings> converterSettings,
IScalingServiceToSpeckle toSpeckleScalingService
)
{
_meshByMaterialConverter = meshByMaterialConverter;
@@ -43,30 +45,30 @@ public sealed class DisplayValueExtractor
_polylineConverter = polylineConverter;
_pointConverter = pointConverter;
_pointcloudConverter = pointcloudConverter;
_logger = logger;
_converterSettings = converterSettings;
_toSpeckleScalingService = toSpeckleScalingService;
}
public List<Base> GetDisplayValue(DB.Element element)
public List<DisplayValueResult> GetDisplayValue(DB.Element element)
{
switch (element)
{
// get custom (anything not using element.get_geometry) display values
case DB.PointCloudInstance pointcloud:
return new() { _pointcloudConverter.Convert(pointcloud) };
return [DisplayValueResult.WithoutTransform(_pointcloudConverter.Convert(pointcloud))];
case DB.ModelCurve modelCurve:
return new() { GetCurveDisplayValue(modelCurve.GeometryCurve) };
return [DisplayValueResult.WithoutTransform(GetCurveDisplayValue(modelCurve.GeometryCurve))];
case DB.Grid grid:
return new() { GetCurveDisplayValue(grid.Curve) };
return [DisplayValueResult.WithoutTransform(GetCurveDisplayValue(grid.Curve))];
case DB.Area area:
List<Base> areaDisplay = new();
List<DisplayValueResult> areaDisplay = new();
using (var options = new DB.SpatialElementBoundaryOptions())
{
foreach (IList<DB.BoundarySegment> boundarySegmentGroup in area.GetBoundarySegments(options))
{
foreach (DB.BoundarySegment boundarySegment in boundarySegmentGroup)
{
areaDisplay.Add(GetCurveDisplayValue(boundarySegment.GetCurve()));
areaDisplay.Add(DisplayValueResult.WithoutTransform(GetCurveDisplayValue(boundarySegment.GetCurve())));
}
}
}
@@ -87,7 +89,7 @@ public sealed class DisplayValueExtractor
return wall.CurtainGrid is not null || wall.IsStackedWall ? new() : GetGeometryDisplayValue(element);
// railings should also include toprail which need to be retrieved separately
case DBA.Railing railing:
List<Base> railingDisplay = GetGeometryDisplayValue(railing);
List<DisplayValueResult> railingDisplay = GetGeometryDisplayValue(railing);
if (railing.TopRail != DB.ElementId.InvalidElementId)
{
var topRail = _converterSettings.Current.Document.GetElement(railing.TopRail);
@@ -105,10 +107,21 @@ public sealed class DisplayValueExtractor
private Base GetCurveDisplayValue(DB.Curve curve) => (Base)_curveConverter.Convert(curve);
private List<Base> GetGeometryDisplayValue(DB.Element element, DB.Options? options = null)
private List<DisplayValueResult> GetGeometryDisplayValue(DB.Element element, DB.Options? options = null)
{
var collections = GetSortedGeometryFromElement(element, options);
return ProcessGeometryCollections(element, collections);
using DB.Transform? localToDocument = GetTransform(element);
using DB.Transform? documentToLocal = localToDocument?.Inverse;
DB.Transform? documentToWorld = _converterSettings.Current.ReferencePointTransform?.Inverse;
using DB.Transform? compoundTransform =
localToDocument is not null && documentToWorld is not null
? documentToWorld.Multiply(localToDocument)
: localToDocument; // don't want to accidentally dispose of the ReferencePointTransform
DB.Transform? localToWorld = compoundTransform ?? documentToWorld;
var collections = GetSortedGeometryFromElement(element, options, documentToLocal);
return ProcessGeometryCollections(element, collections, localToWorld);
}
/// <summary>
@@ -119,7 +132,15 @@ public sealed class DisplayValueExtractor
/// Note: Some special element types (like Rebar) cannot use this method as their
/// get_Geometry() returns null, requiring specialized extraction methods.
/// </remarks>
private GeometryCollections GetSortedGeometryFromElement(DB.Element element, DB.Options? options)
/// <param name="element"></param>
/// <param name="options"></param>
/// <param name="worldToLocal"></param>
/// <returns></returns>
private GeometryCollections GetSortedGeometryFromElement(
DB.Element element,
DB.Options? options,
DB.Transform? worldToLocal
)
{
//options = ViewSpecificOptions ?? options ?? new Options() { DetailLevel = DetailLevelSetting };
options ??= new DB.Options { DetailLevel = _detailLevelMap[_converterSettings.Current.DetailLevel] };
@@ -142,7 +163,7 @@ public sealed class DisplayValueExtractor
if (geom != null && geom.Any())
{
// retrieves all meshes and solids from a geometry element
SortGeometry(element, collections, geom);
SortGeometry(element, collections, geom, worldToLocal);
}
return collections;
@@ -155,36 +176,83 @@ public sealed class DisplayValueExtractor
/// <remarks>
/// Essentially all the ensuing steps after the common get_Geometry element method
/// </remarks>
private List<Base> ProcessGeometryCollections(DB.Element element, GeometryCollections collections)
private List<DisplayValueResult> ProcessGeometryCollections(
DB.Element element,
GeometryCollections collections,
DB.Transform? localToWorld
)
{
List<Base> displayValue = new();
// handle all solids and meshes by their material
var meshesByMaterial = GetMeshesByMaterial(collections.Meshes, collections.Solids);
List<SOG.Mesh> displayMeshes = _meshByMaterialConverter.Convert(
(meshesByMaterial, element.Id, ShouldSetElementDisplayToTransparent(element))
);
displayValue.AddRange(displayMeshes);
// add rest of geometry
List<DisplayValueResult> displayValue = new(collections.TotalCount);
Matrix4x4? matrix = localToWorld is not null ? TransformToMatrix(localToWorld) : null;
foreach (SOG.Mesh mesh in displayMeshes)
{
displayValue.Add(
matrix.HasValue
? DisplayValueResult.WithTransform(mesh, matrix.Value)
: DisplayValueResult.WithoutTransform(mesh)
);
}
// add rest of geometry (always without transform)
foreach (var curve in collections.Curves)
{
displayValue.Add(GetCurveDisplayValue(curve));
displayValue.Add(DisplayValueResult.WithoutTransform(GetCurveDisplayValue(curve)));
}
foreach (var polyline in collections.Polylines)
{
displayValue.Add(_polylineConverter.Convert(polyline));
displayValue.Add(DisplayValueResult.WithoutTransform(_polylineConverter.Convert(polyline)));
}
foreach (var point in collections.Points)
{
displayValue.Add(_pointConverter.Convert(point));
displayValue.Add(DisplayValueResult.WithoutTransform(_pointConverter.Convert(point)));
}
return displayValue;
}
private Matrix4x4 TransformToMatrix(DB.Transform transform) =>
new()
{
M11 = transform.BasisX.X,
M21 = transform.BasisX.Y,
M31 = transform.BasisX.Z,
M41 = 0,
M12 = transform.BasisY.X,
M22 = transform.BasisY.Y,
M32 = transform.BasisY.Z,
M42 = 0,
M13 = transform.BasisZ.X,
M23 = transform.BasisZ.Y,
M33 = transform.BasisZ.Z,
M43 = 0,
M14 = _toSpeckleScalingService.ScaleLength(transform.Origin.X),
M24 = _toSpeckleScalingService.ScaleLength(transform.Origin.Y),
M34 = _toSpeckleScalingService.ScaleLength(transform.Origin.Z),
M44 = 1
};
private static DB.Transform? GetTransform(DB.Element element)
{
if (element is DB.Instance i)
{
return i.GetTotalTransform();
}
return null;
}
private static Dictionary<DB.ElementId, List<DB.Mesh>> GetMeshesByMaterial(
List<DB.Mesh> meshes,
List<DB.Solid> solids
@@ -249,7 +317,12 @@ public sealed class DisplayValueExtractor
///
/// Note: this is basically a geometry unpacker for all types of geometry
/// </summary>
private void SortGeometry(DB.Element element, GeometryCollections collections, DB.GeometryElement geom)
private void SortGeometry(
DB.Element element,
GeometryCollections collections,
DB.GeometryElement geom,
DB.Transform? worldToLocal
)
{
foreach (DB.GeometryObject geomObj in geom)
{
@@ -267,13 +340,22 @@ public sealed class DisplayValueExtractor
continue;
}
if (worldToLocal is not null)
{
solid = DB.SolidUtils.CreateTransformed(solid, worldToLocal);
}
collections.Solids.Add(solid);
break;
case DB.Mesh mesh:
if (worldToLocal is not null)
{
mesh = mesh.get_Transformed(worldToLocal);
}
collections.Meshes.Add(mesh);
break;
//Note, we're not applying transforms to curves/polylines/points because ProcessGeometryCollections expects them in world coordinates
case DB.Curve curve:
collections.Curves.Add(curve);
break;
@@ -288,12 +370,19 @@ public sealed class DisplayValueExtractor
case DB.GeometryInstance instance:
// element transforms should not be carried down into nested geometryInstances.
// Nested geomInstances should have their geom retreived with GetInstanceGeom, not GetSymbolGeom
SortGeometry(element, collections, instance.GetInstanceGeometry());
// Nested geomInstances should have their geom retrieved with GetInstanceGeom, not GetSymbolGeom
if (worldToLocal == null) //see remark on method for why this is safe to do...
{
SortGeometry(element, collections, instance.GetInstanceGeometry(), null);
}
else
{
SortGeometry(element, collections, instance.GetSymbolGeometry(), null);
}
break;
case DB.GeometryElement geometryElement:
SortGeometry(element, collections, geometryElement);
SortGeometry(element, collections, geometryElement, null);
break;
}
}
@@ -424,25 +513,23 @@ public sealed class DisplayValueExtractor
/// Instead, we use GetFullGeometryForView() to obtain the geometry and then process it
/// using the standard geometry sorting and conversion.
/// </remarks>
private List<Base> GetRebarVolumetricDisplayValue(DB.Structure.Rebar rebar)
private List<DisplayValueResult> GetRebarVolumetricDisplayValue(DB.Structure.Rebar rebar)
{
var collections = new GeometryCollections();
// Regular get_Geometry() returns null for rebar, so we need to use GetFullGeometryForView
// ❗NOTE: view detail level needs to be fine in order for this to work
// Same behaviour as sending structural frame though - consistent and therefore okay.
DB.GeometryElement geometryElements = rebar.GetFullGeometryForView(_converterSettings.Current.Document.ActiveView);
SortGeometry(rebar, collections, geometryElements);
DB.GeometryElement? geometryElements = rebar.GetFullGeometryForView(_converterSettings.Current.Document.ActiveView);
if (geometryElements != null)
{
SortGeometry(rebar, collections, geometryElements);
return ProcessGeometryCollections(rebar, collections);
SortGeometry(rebar, collections, geometryElements, null);
return ProcessGeometryCollections(rebar, collections, null);
}
// Return empty list if no geometry is found - imo not critical
return new List<Base>();
return new List<DisplayValueResult>();
}
/// <summary>
@@ -451,7 +538,7 @@ public sealed class DisplayValueExtractor
/// <remarks>
/// This method extracts the centerlines of rebar elements when a simplified representation is preferred.
/// </remarks>
private List<Base> GetRebarCenterlineDisplayValue(DB.Structure.Rebar rebar)
private List<DisplayValueResult> GetRebarCenterlineDisplayValue(DB.Structure.Rebar rebar)
{
bool isSingleLayout = rebar.LayoutRule == DB.Structure.RebarLayoutRule.Single;
int numberOfBarPositions = rebar.NumberOfBarPositions;
@@ -480,10 +567,10 @@ public sealed class DisplayValueExtractor
);
}
List<Base> displayValue = new();
List<DisplayValueResult> displayValue = new();
foreach (var curve in curves)
{
displayValue.Add(GetCurveDisplayValue(curve));
displayValue.Add(DisplayValueResult.WithoutTransform(GetCurveDisplayValue(curve)));
}
return displayValue;
@@ -494,6 +581,10 @@ public sealed class DisplayValueExtractor
/// Used to pass multiple geometry collections as a single parameter to improve code readability
/// and reduce the risk of parameter ordering errors.
/// </summary>
/// <remarks>
/// <see cref="Solids"/> and <see cref="Meshes"/> potentially in local coordinate space.
/// For now, <see cref="Curves"/>, <see cref="Polylines"/>, <see cref="Points"/> will always be in world space
/// </remarks>
private sealed record GeometryCollections
{
public List<DB.Solid> Solids { get; } = new();
@@ -501,5 +592,7 @@ public sealed class DisplayValueExtractor
public List<DB.Curve> Curves { get; } = new();
public List<DB.PolyLine> Polylines { get; } = new();
public List<DB.Point> Points { get; } = new();
public int TotalCount => Solids.Count + Meshes.Count + Curves.Count + Polylines.Count + Points.Count;
}
}
@@ -18,23 +18,74 @@ public sealed class LevelExtractor
return level.Name;
}
/// <summary>
/// Gets the level associated with an element. Handles face-based family instances and hosted elements.
/// </summary>
public DB.Level? GetLevel(DB.Element element)
{
// get level, if any
DB.ElementId? levelId = null;
// try direct LevelId first
if (element.LevelId != DB.ElementId.InvalidElementId)
{
if (_levelCache.TryGetValue(element.LevelId, out DB.Level? cachedLevel))
{
return cachedLevel;
}
levelId = element.LevelId;
}
// otherwise try FamilyInstance-specific sources
else if (element is DB.FamilyInstance familyInstance)
{
levelId = TryGetFamilyInstanceLevelId(familyInstance);
if (element.Document.GetElement(element.LevelId) is DB.Level level)
// couldn't find a direct level ID - recurse to host
if (levelId == null && familyInstance.Host != null)
{
_levelCache[element.LevelId] = level;
return level;
return GetLevel(familyInstance.Host);
}
}
// okay, no valid LevelId found and we've tried A LOT!
if (levelId == null || levelId == DB.ElementId.InvalidElementId)
{
return null;
}
// check if cache has seen this Level before
if (_levelCache.TryGetValue(levelId, out DB.Level? cached))
{
return cached;
}
// add to the cache if firs occurence of this level
if (element.Document.GetElement(levelId) is DB.Level level)
{
_levelCache[levelId] = level;
return level;
}
return null;
}
/// <summary>
/// Tries to get a level ID from a FamilyInstance via parameter or host.
/// Face-based instances store their level in INSTANCE_SCHEDULE_ONLY_LEVEL_PARAM.
/// </summary>
/// <remarks>
/// See: https://forums.autodesk.com/t5/revit-api-forum/newfamilyinstance-not-setting-level-of-family-instance/td-p/11405934
/// </remarks>
private DB.ElementId? TryGetFamilyInstanceLevelId(DB.FamilyInstance familyInstance)
{
// try parameter-based level first (face-based families)
var levelId = familyInstance.get_Parameter(DB.BuiltInParameter.INSTANCE_SCHEDULE_ONLY_LEVEL_PARAM)?.AsElementId();
if (levelId != null && levelId != DB.ElementId.InvalidElementId)
{
return levelId;
}
// try host if it's directly a level
if (familyInstance.Host is DB.Level hostLevel)
{
return hostLevel.Id;
}
return null;
}
}
@@ -1,4 +1,5 @@
using Autodesk.Revit.DB;
using Speckle.DoubleNumerics;
namespace Speckle.Converters.RevitShared.Helpers;
@@ -43,6 +44,30 @@ public static class ReferencePointHelper
};
}
public static Matrix4x4 TransformToMatrix(Transform transform) =>
new()
{
M11 = transform.BasisX.X,
M21 = transform.BasisX.Y,
M31 = transform.BasisX.Z,
M41 = 0,
M12 = transform.BasisY.X,
M22 = transform.BasisY.Y,
M32 = transform.BasisY.Z,
M42 = 0,
M13 = transform.BasisZ.X,
M23 = transform.BasisZ.Y,
M33 = transform.BasisZ.Z,
M43 = 0,
M14 = transform.Origin.X,
M24 = transform.Origin.Y,
M34 = transform.Origin.Z,
M44 = 1
};
/// <summary>
/// Extracts and reconstructs a transform from the matrix data stored on root object
/// </summary>
@@ -1,4 +1,9 @@
using Microsoft.Extensions.Logging;
using Speckle.Converters.Common.ToSpeckle;
using Speckle.Objects.Other;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Converters.RevitShared.Helpers;
@@ -11,7 +16,7 @@ namespace Speckle.Converters.RevitShared.Helpers;
/// Ask dim for more and he might start crying.
/// </para>
/// </summary>
public class RevitToSpeckleCacheSingleton
public class RevitToSpeckleCacheSingleton(ILogger<RevitToSpeckleCacheSingleton> logger)
{
/// <summary>
/// (DB.Material id, RenderMaterial). This can be generated from converting render materials or material quantities.
@@ -24,11 +29,31 @@ public class RevitToSpeckleCacheSingleton
/// </summary>
public Dictionary<string, Dictionary<string, RenderMaterialProxy>> ObjectRenderMaterialProxiesMap { get; } = new();
public Dictionary<
string,
(List<string> elementIds, InstanceDefinitionProxy definitionProxy)
> InstanceDefinitionProxiesMap { get; } = new();
public Dictionary<string, (List<string> elementIds, Base baseObj)> InstancedObjects { get; } = new();
/// <summary>
/// Returns the merged material proxy list for the given object ids. Use this to get post conversion a correct list of material proxies for setting on the root commit object.
/// Maps mesh application IDs to their material IDs for later proxy population.
/// Dictionary: elementId -> (meshAppId -> materialId)
/// </summary>
/// <param name="elementIds"></param>
/// <returns></returns>
public Dictionary<string, Dictionary<string, string>> MeshToMaterialMap { get; } = new();
/// <summary>
/// Returns the merged material proxy list for the given object IDs.
/// Use this post-conversion to get a correct list of material proxies for the root commit object.
/// </summary>
/// <returns>A deduplicated list of <see cref="RenderMaterialProxy"/> objects for all specified elements.</returns>
/// <remarks>
/// <para>
/// Material proxy objects lists should already be correctly populated at this point (with definition mesh IDs for instances
/// and individual mesh IDs for non-instances), so the merging primarily handles cross-element scenarios rather than
/// fixing incorrect data.
/// </para>
/// </remarks>
public List<RenderMaterialProxy> GetRenderMaterialProxyListForObjects(List<string> elementIds)
{
var proxiesToMerge = ObjectRenderMaterialProxiesMap
@@ -42,17 +67,130 @@ public class RevitToSpeckleCacheSingleton
{
if (!mergeTarget.TryGetValue(kvp.Key, out RenderMaterialProxy? value))
{
value = kvp.Value;
mergeTarget[kvp.Key] = value;
continue;
// first time seeing this material - add it
mergeTarget[kvp.Key] = kvp.Value;
}
else
{
// merge objects lists (should already be mostly correct now)
value.objects.AddRange(kvp.Value.objects);
}
value.objects.AddRange(kvp.Value.objects);
}
}
// final deduplication (should be minimal now)
foreach (var renderMaterialProxy in mergeTarget.Values)
{
renderMaterialProxy.objects = renderMaterialProxy.objects.Distinct().ToList();
}
return mergeTarget.Values.ToList();
}
/// <summary>
/// Gets instance definition proxies from session cache for the given element ids.
/// This is necessary because send caching only check against DB.Element since it is the managed object in Revit UI.
/// We need to filter already existant definition proxies from cache with their element id relationship.
/// Otherwise, we will end up with incomplete data in root.
/// </summary>
/// <param name="elementIds">Ids to get corresponding definition proxies that cached before.</param>
public List<InstanceDefinitionProxy> GetInstanceDefinitionProxiesForObjects(List<string> elementIds) =>
InstanceDefinitionProxiesMap
.Values.Where(v => v.elementIds.Any(id => elementIds.Contains(id)))
.Select(v => v.definitionProxy)
.ToList();
/// <summary>
/// Gets atomic objects (Base) that extracted out from display value of RevitDataObject.
/// We need to filter already existant atomic objects from cache with their element id relationship.
/// Otherwise, we will end up with incomplete data in root.
/// </summary>
/// <param name="elementIds">Element ids to get corresponding atomic objects (Base) that cached before.</param>
/// <returns></returns>
public List<Base> GetBaseObjectsForObjects(List<string> elementIds) =>
InstancedObjects.Values.Where(v => v.elementIds.Any(id => elementIds.Contains(id))).Select(v => v.baseObj).ToList();
/// <summary>
/// Adds a mesh ID to the appropriate material proxy.
/// For instances: adds the definition mesh ID.
/// For non-instances: adds the mesh's own ID.
/// </summary>
/// <remarks>
/// Cache navigation logic is encapsulated here. Failures are logged but do not throw exceptions,
/// allowing conversion to continue even if material assignment fails.
/// </remarks>
public void AddMeshToMaterialProxy(string elementId, SOG.Mesh mesh, bool isInstance)
{
// get mesh-to-material mapping
if (!MeshToMaterialMap.TryGetValue(elementId, out var meshMatMap))
{
logger.LogWarning("No mesh-to-material mapping found for element {ElementId}", elementId);
return;
}
// get material ID for this mesh
if (!meshMatMap.TryGetValue(mesh.applicationId.NotNull(), out var materialId))
{
logger.LogError(
"Cache inconsistency: Mesh {MeshId} not found in material mapping for element {ElementId}",
mesh.applicationId,
elementId
);
return;
}
// get material proxy map
if (!ObjectRenderMaterialProxiesMap.TryGetValue(elementId, out var proxyMap))
{
logger.LogError("Cache inconsistency: Material proxy map not found for element {ElementId}", elementId);
return;
}
// get specific material proxy
if (!proxyMap.TryGetValue(materialId, out var materialProxy))
{
logger.LogError(
"Cache inconsistency: Material proxy not found for material {MaterialId} in element {ElementId}",
materialId,
elementId
);
return;
}
// determine which mesh ID to add
string meshIdToAdd;
if (isInstance)
{
var instanceDefinitionId = MeshInstanceIdGenerator.GenerateUntransformedMeshId(mesh);
if (!InstancedObjects.TryGetValue(instanceDefinitionId, out var instancedObject))
{
throw new InvalidOperationException(
$"Instance definition '{instanceDefinitionId}' not found in cache for mesh '{mesh.applicationId}'"
);
}
meshIdToAdd = instancedObject.baseObj.applicationId.NotNull();
}
else
{
meshIdToAdd = mesh.applicationId.NotNull();
}
// add to proxy if not already present
if (!materialProxy.objects.Contains(meshIdToAdd))
{
materialProxy.objects.Add(meshIdToAdd);
}
}
public void ClearCache()
{
ObjectRenderMaterialProxiesMap.Clear();
SpeckleRenderMaterialCache.Clear();
InstanceDefinitionProxiesMap.Clear();
InstancedObjects.Clear();
MeshToMaterialMap.Clear();
}
}
@@ -2,7 +2,6 @@ using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Converters.Common;
using Speckle.Converters.Common.Registration;
using Speckle.Converters.Revit2023.ToSpeckle.Properties;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Converters.RevitShared.Services;
using Speckle.Converters.RevitShared.Settings;
@@ -19,7 +18,8 @@ public static class ServiceRegistration
var converterAssembly = Assembly.GetExecutingAssembly();
//register types by default
serviceCollection.AddMatchingInterfacesAsTransient(converterAssembly);
// Register single root
// register single root
serviceCollection.AddRootCommon<RevitRootToSpeckleConverter>(converterAssembly);
// register all application converters
@@ -29,7 +29,7 @@ public static class ServiceRegistration
serviceCollection.AddSingleton(new RevitContext());
serviceCollection.AddSingleton(new RevitToHostCacheSingleton());
serviceCollection.AddSingleton(new RevitToSpeckleCacheSingleton());
serviceCollection.AddSingleton<RevitToSpeckleCacheSingleton>();
// POC: do we need ToSpeckleScalingService as is, do we need to interface it out?
serviceCollection.AddScoped<ScalingServiceToSpeckle>();
@@ -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;
}
@@ -4,7 +4,7 @@ using Speckle.Converters.RevitShared.Settings;
using Speckle.Sdk;
using Speckle.Sdk.Common;
namespace Speckle.Converters.Revit2023.ToSpeckle.Properties;
namespace Speckle.Converters.RevitShared.ToSpeckle.Properties;
public readonly struct StructuralAssetProperties(
string name,
@@ -43,16 +43,22 @@ public class MeshByMaterialDictionaryToSpeckle
/// <summary>
/// Converts a dictionary of Revit meshes, where key is MaterialId, into a list of Speckle meshes.
/// </summary>
/// <param name="args">A tuple consisting of (1) a dictionary with DB.ElementId keys and List of DB.Mesh values and (2) the root element id (the one generating all the meshes).</param>
/// <returns>
/// Returns a list of <see cref="SOG.Mesh"/> objects where each mesh represents one unique material in the input dictionary.
/// </returns>
/// <remarks>
/// Be aware that this method internally creates a new instance of <see cref="SOG.Mesh"/> for each unique material in the input dictionary.
/// These meshes are created with an initial capacity based on the size of the vertex and face arrays to avoid unnecessary resizing.
/// Also note that, for each unique material, the method tries to retrieve the related DB.Material from the current document and convert it. If the conversion is successful,
/// the material is added to the corresponding Speckle mesh. If the conversion fails, the operation simply continues without the material.
/// TODO: update description
/// <para>
/// This method creates a new instance of <see cref="SOG.Mesh"/> for each unique material in the input dictionary.
/// </para>
/// <para>
/// For each unique material, the method retrieves the related DB.Material from the current document and converts it to a <see cref="RenderMaterial"/>.
/// Material proxies are created but their objects lists are NOT populated at this stage. The mesh-to-material relationship is stored
/// in <see cref="RevitToSpeckleCacheSingleton.MeshToMaterialMap"/> for later population during display value processing.
/// </para>
/// <para>
/// Deferred population of the object list to ensure that instance geometry references the definition mesh ID in material proxies,
/// rather than individual instance mesh IDs. We can only do this later, because proxification hasn't happened yet.
/// </para>
/// </remarks>
public List<SOG.Mesh> Convert(
(Dictionary<DB.ElementId, List<DB.Mesh>> target, DB.ElementId parentElementId, bool makeTransparent) args
@@ -62,7 +68,7 @@ public class MeshByMaterialDictionaryToSpeckle
var objectRenderMaterialProxiesMap = _revitToSpeckleCacheSingleton.ObjectRenderMaterialProxiesMap;
var materialProxyMap = new Dictionary<string, RenderMaterialProxy>();
var key = args.parentElementId.ToString().NotNull();
// ids are same in copy pasted linked models, otherwise we reset the materialProxyMap in cache and only one of the linked model is having the render materials
if (objectRenderMaterialProxiesMap.TryGetValue(key, out var cachedMaterialProxy))
{
materialProxyMap = cachedMaterialProxy;
@@ -85,32 +91,37 @@ public class MeshByMaterialDictionaryToSpeckle
: materialId.ToString().NotNull();
List<DB.Mesh> meshes = keyValuePair.Value;
// use the meshlist converter to convert the mesh values into a single speckle mesh
SOG.Mesh speckleMesh = _meshListConverter.Convert(meshes);
speckleMesh.applicationId = Guid.NewGuid().ToString(); // NOTE: as we are composing meshes out of multiple ones for the same material, we need to generate our own application id. c'est la vie.
speckleMesh.applicationId = Guid.NewGuid().ToString();
// store mesh-to-material mapping
if (!_revitToSpeckleCacheSingleton.MeshToMaterialMap.TryGetValue(key, out var meshMatMap))
{
meshMatMap = new Dictionary<string, string>();
_revitToSpeckleCacheSingleton.MeshToMaterialMap[key] = meshMatMap;
}
meshMatMap[speckleMesh.applicationId.NotNull()] = materialIdString;
// get the speckle render material
RenderMaterial? renderMaterial = args.makeTransparent
? _transparentMaterial
: _converterSettings.Current.Document.GetElement(materialId) is DB.Material material
? _speckleRenderMaterialConverter.Convert(material)
: null;
// get the render material if any
// Create proxy but DON'T populate objects list yet
if (renderMaterial is not null)
{
if (!materialProxyMap.TryGetValue(materialIdString, out RenderMaterialProxy? renderMaterialProxy))
if (!materialProxyMap.ContainsKey(materialIdString))
{
renderMaterialProxy = new RenderMaterialProxy()
{
value = renderMaterial,
applicationId = materialId.ToString(),
objects = []
};
RenderMaterialProxy? renderMaterialProxy =
new()
{
value = renderMaterial,
applicationId = materialId.ToString(),
objects = []
};
materialProxyMap[materialIdString] = renderMaterialProxy;
}
renderMaterialProxy.objects.Add(speckleMesh.applicationId);
}
result.Add(speckleMesh);
@@ -36,12 +36,9 @@ public class MeshListConversionToSpeckle : ITypedConverter<List<DB.Mesh>, SOG.Me
foreach (DB.XYZ vert in mesh.Vertices)
{
// We need this method to take into account reference point transforms
DB.XYZ extVert = _referencePointConverter.ConvertToExternalCoordinates(vert, true);
vertices.Add(_toSpeckleScalingService.ScaleLength(extVert.X));
vertices.Add(_toSpeckleScalingService.ScaleLength(extVert.Y));
vertices.Add(_toSpeckleScalingService.ScaleLength(extVert.Z));
vertices.Add(_toSpeckleScalingService.ScaleLength(vert.X));
vertices.Add(_toSpeckleScalingService.ScaleLength(vert.Y));
vertices.Add(_toSpeckleScalingService.ScaleLength(vert.Z));
}
for (int i = 0; i < mesh.NumTriangles; i++)
@@ -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
@@ -1,8 +1,8 @@
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Converters.Revit2023.ToSpeckle.Properties;
using Speckle.Converters.RevitShared.Services;
using Speckle.Converters.RevitShared.Settings;
using Speckle.Converters.RevitShared.ToSpeckle.Properties;
using Speckle.Sdk.Common.Exceptions;
using ApplicationException = Autodesk.Revit.Exceptions.ApplicationException;
@@ -1,12 +1,16 @@
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Converters.Common.ToSpeckle;
using Speckle.Converters.RevitShared.Extensions;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Converters.RevitShared.Settings;
using Speckle.Converters.RevitShared.ToSpeckle.Properties;
using Speckle.DoubleNumerics;
using Speckle.Objects.Data;
using Speckle.Sdk.Common;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Converters.RevitShared.ToSpeckle;
@@ -18,9 +22,11 @@ public class ElementTopLevelConverterToSpeckle : IToSpeckleTopLevelConverter
private readonly ITypedConverter<DB.Location, Base> _locationConverter;
private readonly LevelExtractor _levelExtractor;
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
private readonly RevitToSpeckleCacheSingleton _revitToSpeckleCacheSingleton;
public ElementTopLevelConverterToSpeckle(
DisplayValueExtractor displayValueExtractor,
RevitToSpeckleCacheSingleton revitToSpeckleCacheSingleton,
PropertiesExtractor propertiesExtractor,
LevelExtractor levelExtractor,
ITypedConverter<DB.Location, Base> locationConverter,
@@ -28,6 +34,7 @@ public class ElementTopLevelConverterToSpeckle : IToSpeckleTopLevelConverter
)
{
_displayValueExtractor = displayValueExtractor;
_revitToSpeckleCacheSingleton = revitToSpeckleCacheSingleton;
_propertiesExtractor = propertiesExtractor;
_levelExtractor = levelExtractor;
_locationConverter = locationConverter;
@@ -36,7 +43,7 @@ public class ElementTopLevelConverterToSpeckle : IToSpeckleTopLevelConverter
public Base Convert(object target) => Convert((DB.Element)target);
public RevitObject Convert(DB.Element target)
private RevitObject Convert(DB.Element target)
{
string category = target.Category?.Name ?? "none";
@@ -95,7 +102,10 @@ public class ElementTopLevelConverterToSpeckle : IToSpeckleTopLevelConverter
}
// get the display value
List<Base> displayValue = _displayValueExtractor.GetDisplayValue(target);
List<DisplayValueResult> displayValuesWithTransforms = _displayValueExtractor.GetDisplayValue(target);
// process display values and create instance proxies where applicable
List<Base> proxifiedDisplayValues = ProcessDisplayValues(target.Id.ToString(), displayValuesWithTransforms);
// get level
string? level = _levelExtractor.GetLevelName(target);
@@ -117,7 +127,7 @@ public class ElementTopLevelConverterToSpeckle : IToSpeckleTopLevelConverter
category = category,
location = convertedLocation,
elements = children,
displayValue = displayValue.Cast<Base>().ToList(),
displayValue = proxifiedDisplayValues,
properties = properties,
units = _converterSettings.Current.SpeckleUnits
};
@@ -184,4 +194,128 @@ public class ElementTopLevelConverterToSpeckle : IToSpeckleTopLevelConverter
yield return Convert(_converterSettings.Current.Document.GetElement(childId));
}
}
/// <summary>
/// Processes display values with transforms and creates instance proxies for meshes that can be instanced.
/// Also populates material proxy objects lists with the appropriate mesh IDs based on whether geometry is instanced or not.
/// </summary>
/// <returns>
/// List of processed display values, with meshes replaced by instance proxies where applicable.
/// Non-instance geometry is returned as-is.
/// </returns>
/// <remarks>
/// <para>
/// This is a bit of a code smell. This method is doing to much, "this ... AND this...".
/// </para>
/// <para>
/// But, given a mesh:
/// - if it has a transform, mesh is converted to instance proxy, and the definition mesh ID is added to material proxies
/// - if it doesn't have a transform, it remains as a regular mesh, and its own ID is added to material proxies
/// - other geometry types pass through unchanged
/// </para>
/// <para>
/// This is where material proxy population occurs (deferred from <see cref="MeshByMaterialDictionaryToSpeckle.Convert"/>)
/// to ensure we use definition mesh IDs for instances rather than individual instance mesh IDs.
/// </para>
/// </remarks>
private List<Base> ProcessDisplayValues(string elementId, List<DisplayValueResult> displayValues)
{
List<Base> proxifiedDisplayValues = new();
foreach (var displayValue in displayValues)
{
// check if this is a mesh with a transform - potential instance scenario
if (displayValue.Geometry is SOG.Mesh mesh && displayValue.Transform is not null)
{
var instanceProxy = CreateOrGetInstanceProxy(elementId, mesh, displayValue.Transform.Value);
proxifiedDisplayValues.Add(instanceProxy);
// add the definition mesh ID to material proxy, not the instance mesh
// method technically is a "Try" but logs internally, so we don't have a return to check
_revitToSpeckleCacheSingleton.AddMeshToMaterialProxy(elementId, mesh, isInstance: true);
}
else if (displayValue.Geometry is SOG.Mesh nonInstanceMesh)
{
// non-instance mesh - add its own ID to material proxy
// method technically is a "Try" but logs internally, so we don't have a return to check
_revitToSpeckleCacheSingleton.AddMeshToMaterialProxy(elementId, nonInstanceMesh, isInstance: false);
proxifiedDisplayValues.Add(nonInstanceMesh);
}
else
{
proxifiedDisplayValues.Add(displayValue.Geometry);
}
}
return proxifiedDisplayValues;
}
/// <summary>
/// Creates or retrieves an instance proxy for a mesh, managing instance definitions and caching.
/// </summary>
/// <remarks>
/// <para>
/// This method generates a deterministic instance definition ID based on the untransformed mesh geometry using
/// <see cref="MeshInstanceIdGenerator.GenerateUntransformedMeshId"/>. Multiple instances with identical geometry
/// will share the same definition.
/// </para>
/// <para>
/// The method manages two caches:
/// - <see cref="RevitToSpeckleCacheSingleton.InstanceDefinitionProxiesMap"/>: Tracks instance definitions and which elements use them
/// - <see cref="RevitToSpeckleCacheSingleton.InstancedObjects"/>: Stores the actual definition meshes for later serialization
/// </para>
/// </remarks>
private InstanceProxy CreateOrGetInstanceProxy(string elementId, SOG.Mesh mesh, Matrix4x4 transform)
{
var instanceDefinitionId = MeshInstanceIdGenerator.GenerateUntransformedMeshId(mesh);
// We need to attach element id relationship to proxy singleton for send caching.
// Send caching skips whole DB.Element that turn into RevitDataObject. since we have instance proxies in RevitDataObject but
// its definitions outside of caching mechanism, this elementId helps us to filter which definition proxies should be attached to the root
if (
_revitToSpeckleCacheSingleton.InstanceDefinitionProxiesMap.TryGetValue(
instanceDefinitionId,
out var instanceDefinition
)
)
{
instanceDefinition.elementIds.Add(elementId);
}
else
{
var newInstanceDefinition = new InstanceDefinitionProxy
{
applicationId = instanceDefinitionId,
objects = new List<string> { mesh.applicationId.NotNull() },
maxDepth = 0,
name = instanceDefinitionId,
};
_revitToSpeckleCacheSingleton.InstanceDefinitionProxiesMap.Add(
instanceDefinitionId,
([elementId], newInstanceDefinition)
);
}
// some comment valid here as above if statement, since we store original meshes outside of RevitDataObject, we need to know which of them will be attached.
if (_revitToSpeckleCacheSingleton.InstancedObjects.TryGetValue(instanceDefinitionId, out var instancedObject))
{
instancedObject.elementIds.Add(elementId);
}
else
{
_revitToSpeckleCacheSingleton.InstancedObjects.Add(instanceDefinitionId, ([elementId], mesh));
}
// create and return instance proxy with transform
var instanceProxy = new InstanceProxy
{
applicationId = Guid.NewGuid().ToString(),
definitionId = instanceDefinitionId,
transform = transform,
maxDepth = 0,
units = mesh.units
};
return instanceProxy;
}
}
@@ -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 -1
View File
@@ -16,7 +16,7 @@
<PackageVersion Include="LibTessDotNet" Version="1.1.15" />
<PackageVersion Include="Microsoft.VisualStudio.SolutionPersistence" Version="1.0.52" />
<PackageVersion Include="Moq" Version="4.20.70" />
<PackageVersion Include="Microsoft.Build" Version="17.11.4" />
<PackageVersion Include="Microsoft.Build" Version="17.11.48" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="Npgsql" Version="9.0.3" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.9" />
@@ -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();
}

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