Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bc18d3b494 | |||
| 7c7260c603 | |||
| bae9e3e0f1 | |||
| 26b0394613 | |||
| 689ef0bcbe | |||
| 461585b782 | |||
| ea33f35a7d | |||
| 7427f1a2f3 | |||
| b7984bf97e | |||
| 9b24a45b6e | |||
| 4ace81a422 | |||
| a60790c92c | |||
| fd0d00cac3 | |||
| 498396e611 | |||
| 5444377398 | |||
| 9d981f9800 | |||
| 14e17fb67d | |||
| 0ffa7685fd | |||
| dc7d4671e4 | |||
| 10cb5cd66f | |||
| cb15d9f77a | |||
| da74faef9b | |||
| 4368833c7e | |||
| a20df41316 | |||
| ccf48dbad1 | |||
| 6700aa27bc | |||
| df525eab63 | |||
| 275901626f | |||
| fac0dc31b2 | |||
| 8696eca1f0 | |||
| d647c71cf5 | |||
| 9b218dd808 | |||
| 9f39dc521d | |||
| 112093f914 | |||
| d174597770 | |||
| d9289787b7 | |||
| 77c1c3b511 | |||
| 5a1c542832 | |||
| 091d7cc897 | |||
| 21f4fb52a8 | |||
| 868ca8db66 | |||
| a9360e5fac | |||
| 3414599f72 | |||
| 84e92aa8a8 | |||
| edd842763f | |||
| 867ee0f928 | |||
| 29ee648d7f | |||
| 5b9c610856 | |||
| 769ddf6407 | |||
| dd7205f855 | |||
| 30ee410309 | |||
| 2d0b1a3a24 | |||
| 2cdf036172 | |||
| c14aa28e76 | |||
| 0e7d2554f8 | |||
| cdfc618bab | |||
| 1005edb609 | |||
| 3b4da8de52 |
+14
-28
@@ -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 }}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
+296
@@ -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;
|
||||
}
|
||||
}
|
||||
+23
-226
@@ -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
|
||||
) { }
|
||||
}
|
||||
|
||||
+1
@@ -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" />
|
||||
|
||||
+4
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -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);
|
||||
}
|
||||
}
|
||||
+4
@@ -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>
|
||||
+7
-7
@@ -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);
|
||||
|
||||
+13
@@ -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);
|
||||
}
|
||||
+9
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+219
@@ -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" />
|
||||
|
||||
+3
@@ -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
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+19
-6
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-15
@@ -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)
|
||||
|
||||
+12
@@ -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;
|
||||
|
||||
|
||||
+63
-88
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-8
@@ -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)
|
||||
{
|
||||
|
||||
+9
-12
@@ -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) { }
|
||||
}
|
||||
|
||||
+27
@@ -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>
|
||||
|
||||
+141
-9
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+43
-15
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-5
@@ -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)
|
||||
|
||||
+1
@@ -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)
|
||||
|
||||
+20
-1
@@ -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>
|
||||
|
||||
+18
-8
@@ -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;
|
||||
|
||||
+2
-28
@@ -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>
|
||||
|
||||
+146
-8
@@ -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>();
|
||||
|
||||
+9
-9
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -148,7 +148,7 @@ public class ParameterExtractor
|
||||
{
|
||||
foreach (DB.Connector conn in cm.Connectors)
|
||||
{
|
||||
if (conn.MEPSystem != null)
|
||||
if (conn.ConnectorType == DB.ConnectorType.Physical && conn.IsConnected && conn.MEPSystem != null)
|
||||
{
|
||||
return conn.MEPSystem;
|
||||
}
|
||||
@@ -172,7 +172,8 @@ public class ParameterExtractor
|
||||
// NOTE: general assumption is that ids don't really have much meaning. See [CNX-556: All ID Parameters are send as Name](https://linear.app/speckle/issue/CNX-556/all-id-parameters-are-send-as-name)
|
||||
// NOTE: subsequent request resulting in certain IDs being brought back. See [CNX-1125](https://linear.app/speckle/issue/CNX-1125/publish-type-id-instead-of-name) in GetValue() method
|
||||
// "Type ID" (associated with "SYMBOL_ID_PARAM") won't evaluate to true here which is intentional
|
||||
if (internalDefinitionName.EndsWith("_ID") || internalDefinitionName.EndsWith("_PARAM_ID"))
|
||||
// NOTE: It seems we are skipping Type Mark parameter because it is called WINDOW_TYPE_ID internally (WTF ADSK).
|
||||
if (internalDefinitionName.EndsWith("_ID") && !internalDefinitionName.Equals("WINDOW_TYPE_ID"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+31
-20
@@ -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);
|
||||
|
||||
+3
-6
@@ -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++)
|
||||
|
||||
+5
-1
@@ -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
-1
@@ -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;
|
||||
|
||||
|
||||
+137
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -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
|
||||
};
|
||||
|
||||
|
||||
+2
-6
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -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
|
||||
|
||||
+2
-6
@@ -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);
|
||||
|
||||
+5
-8
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+1
-5
@@ -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;
|
||||
|
||||
+2
-6
@@ -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
-5
@@ -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
|
||||
};
|
||||
|
||||
-6
@@ -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
|
||||
|
||||
+2
-8
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
-3
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user