Compare commits

..

8 Commits

Author SHA1 Message Date
Alan Rynne c719cfd66f Merge pull request #383 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
.NET Build and Publish / test (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Update `release/3.0.0` with changes from `dev`
2024-11-12 15:56:02 +01:00
Oğuzhan Koral 92435ce1a1 Merge pull request #376 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
.NET Build and Publish / test (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Update release/3.0.0 with changes from dev
2024-11-08 18:16:16 +00:00
Oğuzhan Koral 71409e8af8 Merge pull request #374 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
.NET Build and Publish / test (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Update release/3.0.0 with changes from dev
2024-11-08 15:13:47 +00:00
Alan Rynne 8060f46882 Merge pull request #359 from specklesystems/dev
Update `release/3.0.0` with changes from `dev`
2024-11-06 18:49:52 +01:00
Alan Rynne 6671edd36e Merge pull request #354 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
.NET Build and Publish / test (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Update `release/3.0.0` with changes from `dev`
2024-11-06 14:22:08 +01:00
Alan Rynne bc15ff3a34 Merge pull request #347 from specklesystems/dev
Update `release/3.0.0` with changes from `dev`
2024-11-05 10:30:20 +01:00
Alan Rynne de275dcf02 Merge pull request #337 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
.NET Build and Publish / test (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Update `release/3.0.0` with changes from `dev`
2024-10-31 12:17:57 +01:00
Alan Rynne 6158739df0 Merge pull request #331 from specklesystems/dev
Update `release/3.0.0` with changes from `dev`
2024-10-28 13:05:26 +01:00
961 changed files with 12616 additions and 56759 deletions
+3 -3
View File
@@ -9,10 +9,10 @@
],
"rollForward": false
},
"dotnet-affected": {
"version": "5.0.0",
"gitversion.tool": {
"version": "6.0.2",
"commands": [
"dotnet-affected"
"dotnet-gitversion"
],
"rollForward": false
}
+2 -6
View File
@@ -254,10 +254,9 @@ dotnet_diagnostic.ca1508.severity = warning # Avoid dead conditional code
dotnet_diagnostic.ca1509.severity = warning # Invalid entry in code metrics configuration file
dotnet_diagnostic.ca1861.severity = none # Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861)
# CA2007: Consider calling ConfigureAwait on the awaited task (this is not needed for application code, in fact we don't want to call anything but ConfigureAwait(true) which is the default)
dotnet_diagnostic.CA2007.severity = none
dotnet_diagnostic.cs8618.severity = suggestion # nullable problem
dotnet_diagnostic.CS0809.severity = suggestion # obsolete errors
dotnet_diagnostic.CS0618.severity = suggestion # obsolete errors
# Performance rules
@@ -314,9 +313,6 @@ dotnet_diagnostic.NUnit2037.severity = warning # Consider using Assert.That(coll
dotnet_diagnostic.NUnit2038.severity = warning # Consider using Assert.That(actual, Is.InstanceOf(expected)) instead of Assert.IsInstanceOf(expected, actual)
dotnet_diagnostic.NUnit2039.severity = warning # Consider using Assert.That(actual, Is.Not.InstanceOf(expected)) instead of Assert.IsNotInstanceOf(expected, actual)
# note: added to allow the copy paste from rhino inside of the ValueSet component
dotnet_diagnostic.CA1033.severity = none
[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,cc,cginc,compute,cp,cpp,cs,cshtml,cu,cuh,cxx,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,vb,xaml,xamlx,xoml,xsd}]
indent_style = space
indent_size = 2
+26 -29
View File
@@ -4,36 +4,33 @@
# * @specklesystems/connectors
# Core
# Not needed, falls back on team approval
# Objects
# Converters require product owner approval, anything else falls back to team approval
Objects/ConverterAutocadCivil/* @clairekuang @connorivy
Objects/ConverterCSI/* @connorivy
Objects/ConverterDynamo/* @teocomi @alanrynne
Objects/ConverterMicrostation/* @connorivy
Objects/ConverterRevit/* @connorivy @teocomi
Objects/ConverterRhinoGh/* @alanrynne @clairekuang
Objects/ConverterTeklaStructures/* @connorivy
Objects/StructuralUtilities/PolygonMesher/* @connorivy
# Connectors
/Connectors/ArcGIS/* @KatKatKateryna
/Connectors/Autocad/* @clairekuang @oguzhankoral @didimitrie
/Connectors/Civil3d/* @clairekuang @oguzhankoral @didimitrie
/Connectors/CSi/* @bjoernsteinhagen @dogukankaratas
/Connectors/Navisworks/* @jsdbroughton
/Connectors/Revit/* @clairekuang @oguzhankoral @didimitrie
/Connectors/Rhino/* @clairekuang @oguzhankoral @didimitrie
/Connectors/Tekla/* @bjoernsteinhagen @dogukankaratas
ConnectorAutocadCivil/* @clairekuang
ConnectorArchicad/* @jozseflkiss
ConnectorCSI/* @connorivy
ConnectorDynamo/* @teocomi @alanrynne
ConnectorGrasshopper/* @alanrynne @clairekuang
ConnectorMicrostation/* @clairekuang
ConnectorRevit/* @teocomi @connorivy
ConnectorRhino/* @clairekuang @alanrynne
ConnectorTeklaStructures/* @connorivy
# Converters
/Convertors/ArcGIS/* @KatKatKateryna
/Convertors/Autocad/* @clairekuang @oguzhankoral @didimitrie
/Convertors/Civil3d/* @clairekuang @oguzhankoral @didimitrie
/Convertors/CSi/* @bjoernsteinhagen @dogukankaratas
/Convertors/Navisworks/* @jsdbroughton
/Convertors/Revit/* @clairekuang @oguzhankoral @didimitrie
/Convertors/Rhino/* @clairekuang @oguzhankoral @didimitrie
/Convertors/Tekla/* @bjoernsteinhagen @dogukankaratas
# DesktopUI
# DUI
/DUI3/* @clairekuang @oguzhankoral @didimitrie
# Importers
/Importers/* @JR-Morgan @didimitrie @oguzhankoral @adamhathcock
# SDK
/SDK/* @JR-Morgan @clairekuang @didimitrie @oguzhankoral @adamhathcock
# Build
/Build/* @JR-Morgan @oguzhankoral @adamhathcock
DesktopUI2/* @teocomi @clairekuang
+38 -29
View File
@@ -1,46 +1,30 @@
<!---
Provide a short summary in the Title above. Use the following template:
Provide a short summary in the Title above. Examples of good PR titles:
category(project): summary
* "Feature: adds metrics to component"
Categories:
* "Fix: resolves duplication in comment thread"
* feat: (new feature for the user, not a new feature for build script)
* fix: (bug fix for the user, not a fix to a build script)
* docs: (changes to the documentation)
* style: (formatting, missing semi colons, etc; no production code change)
* refactor: (refactoring production code, eg. renaming a variable)
* test: (adding missing tests, refactoring tests; no production code change)
* chore: (updating grunt tasks etc; no production code change)
Example:
feat(revit): added category filter to send
* "Update: apollo v2.34.0"
-->
## Description
## Description & motivation
<!---
Describe your changes, and why you're making them.
Describe your changes, and why you're making them. What benefit will this have to others?
Link related github issues here ->
Fixes #85, Fixes #22, Connects #123
Is this linked to an open Github issue, a thread in Speckle community,
or another pull request? Link it here.
If it is related to a Github issue, and resolves it, please link to the issue number, e.g.:
Fixes #85, Fixes #22, Fixes username/repo#123
Connects #123
-->
## User Value
<!---
Describe in 1 sentence the user value.
This can also be a link to the relevant thread in Speckle community, or a link to the Linear issue.
-->
## Changes:
<!---
@@ -84,10 +68,35 @@ Describe what tests have been added or amended, and why these demonstrate it wor
<!---
This checklist is a useful reminder of related tasks to uphold our repo quality. Amend this list as needed for the pr.
This checklist is mostly useful as a reminder of small things that can easily be
forgotten it is meant as a helpful tool rather than hoops to jump through.
Put an `x` between the square brackets, e.g. [x], for all the items that apply,
make notes next to any that haven't been addressed, and remove any items that are not relevant to this PR.
-->
- [ ] My pull request follows the guidelines in the [Contributing guide](https://github.com/specklesystems/speckle-server/blob/main/CONTRIBUTING.md)?
- [ ] My pull request does not duplicate any other open [Pull Requests](../../pulls) for the same update/change?
- [ ] My commits are related to the pull request and do not amend unrelated code or documentation.
- [ ] My code follows a similar style to existing code.
- [ ] I have added appropriate tests.
- [ ] I have updated or added relevant documentation.
## References
<!---
(Optional -- remove this section if not needed )
Include **important** links regarding the implementation of this PR.
This usually includes a RFC or an aggregation of issues and/or individual conversations
that helped put this solution together. This helps ensure we retain and share knowledge
regarding the implementation, and may help others understand motivation and design decisions etc..
-->
@@ -1,10 +1,15 @@
name: .NET Build
on: pull_request
on:
pull_request: # Run build on every pull request that is not to main
branches-ignore:
- main
jobs:
jobs:
build:
runs-on: windows-latest
outputs:
version: ${{ steps.set-version.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -14,9 +19,9 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.4xx # Align with global.json (including roll forward rules)
- name: Cache Nuget
dotnet-version: 8.0.2xx # Align with global.json (including roll forward rules)
- name: Cache Nuget
uses: actions/cache@v4
with:
path: ~/.nuget/packages
@@ -29,28 +34,23 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.4xx # Align with global.json (including roll forward rules)
- name: Cache Nuget
dotnet-version: 8.0.2xx # 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 on Linux
run: ./build.sh build-linux
- name: ⚒️ Run tests
- name: ⚒️ Run build
run: ./build.sh test-only
- name: Upload coverage reports to Codecov with GitHub Action
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v4
with:
files: Converters/**/coverage.xml
file: Converters/**/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
@@ -2,18 +2,14 @@ name: .NET Build and Publish
on:
push:
branches: ["main", "installer-test/**"]
tags: ["v3.*.*"] # Manual delivery on every 3.x tag
branches: ["main", "dev", "release/*"] # Continuous delivery on every long-lived branch
tags: ["v3.*"] # Manual delivery on every 3.x tag
jobs:
build-windows:
build:
runs-on: windows-latest
env:
SEMVER: "unset"
FILE_VERSION: "unset"
outputs:
semver: ${{ steps.set-version.outputs.semver }}
file_version: ${{ steps.set-version.outputs.file_version }}
version: ${{ steps.set-version.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -23,7 +19,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.4xx # Align with global.json (including roll forward rules)
dotnet-version: 8.0.2xx # Align with global.json (including roll forward rules)
- name: Cache Nuget
uses: actions/cache@v4
@@ -31,43 +27,38 @@ jobs:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
- name: ⚒️ Run build on Windows
run: ./build.ps1 zip
- name: ⚒️ Run GitVersion
run: ./build.ps1 build-server-version
- name: ⚒️ Run build
run: ./build.ps1
- name: ⬆️ Upload artifacts
uses: actions/upload-artifact@v4
with:
name: output-${{ env.SEMVER }}
name: output-${{ env.GitVersion_FullSemVer }}
path: output/*.*
if-no-files-found: error
retention-days: 1
compression-level: 0 # no compression
- id: set-version
name: Set version to output
run: |
echo "semver=${{ env.SEMVER }}" >> "$Env:GITHUB_OUTPUT"
echo "file_version=${{ env.FILE_VERSION }}" >> "$Env:GITHUB_OUTPUT"
run: echo "version=${{ env.GitVersion_FullSemVer }}" >> "$Env:GITHUB_OUTPUT"
deploy-installers:
runs-on: ubuntu-latest
needs: build-windows
needs: build
env:
IS_PUBLIC_RELEASE: ${{ github.ref_type == 'tag' }}
IS_TAG_BUILD: ${{ github.ref_type == 'tag' }}
IS_RELEASE_BRANCH: ${{ startsWith(github.ref_name, 'release/') || github.ref_name == 'main'}}
steps:
- name: 🔫 Trigger Build Installers
uses: the-actions-org/workflow-dispatch@v4.0.0
uses: ALEEF02/workflow-dispatch@v3.0.0
continue-on-error: true
with:
workflow: Build Installers
repo: specklesystems/connector-installers
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 }}",
"repo": "${{ github.repository }}",
"is_public_release": ${{ env.IS_PUBLIC_RELEASE }}
}'
inputs: '{ "run_id": "${{ github.run_id }}", "version": "${{ needs.build.outputs.version }}", "public_release": ${{ env.IS_TAG_BUILD }}, "store_artifacts": ${{ env.IS_RELEASE_BRANCH }} }'
ref: main
wait-for-completion: true
wait-for-completion-interval: 10s
@@ -79,18 +70,16 @@ jobs:
with:
name: output-*
build-linux:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.4xx # Align with global.json (including roll forward rules)
dotnet-version: 8.0.2xx # Align with global.json (including roll forward rules)
- name: Cache Nuget
uses: actions/cache@v4
@@ -98,18 +87,11 @@ jobs:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
- name: ⚒️ Run tests on Linux
- name: ⚒️ Run build
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
uses: codecov/codecov-action@v4
with:
files: Converters/**/coverage.xml
file: 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
-116
View File
@@ -1,116 +0,0 @@
using GlobExpressions;
using Microsoft.Build.Construction;
using static SimpleExec.Command;
namespace Build;
public static class Affected
{
public static readonly string Root = Environment.CurrentDirectory;
public const string AFFECTED_PROJECT = "affected.proj";
private static IEnumerable<string> GetAffectedProjects()
{
var projFile = Path.Combine(Root, AFFECTED_PROJECT);
Console.WriteLine("Affected project file: " + projFile);
var project = ProjectRootElement.Open(projFile) ?? throw new InvalidOperationException();
var references = project.ItemGroups.SelectMany(x => x.Items).Where(x => x.ItemType == "ProjectReference");
foreach (var refe in references)
{
var referencePath = refe.Include[(Root.Length + 1)..];
referencePath = Path.GetDirectoryName(referencePath) ?? throw new InvalidOperationException();
if (Path.DirectorySeparatorChar != '/')
{
referencePath = referencePath.Replace(Path.DirectorySeparatorChar, '/');
}
yield return referencePath;
}
}
public static async Task<IEnumerable<string>> GetTestProjects()
{
await ComputeAffected();
var projFile = Path.Combine(Root, AFFECTED_PROJECT);
if (File.Exists(projFile))
{
var references = GetAffectedProjects();
return references.Where(x => x.Contains("Tests"));
}
return Glob.Files(Root, "**/*.Tests.csproj");
}
public static async Task<ProjectGroup[]> GetAffectedProjectGroups()
{
await ComputeAffected();
var projFile = Path.Combine(Root, AFFECTED_PROJECT);
if (File.Exists(projFile))
{
var references = GetAffectedProjects().ToList();
var groups = new List<ProjectGroup>();
foreach (var projectGroup in Consts.ProjectGroups)
{
foreach (var referencePath in references)
{
if (projectGroup.Projects.Any(x => x.ProjectPath.Contains(referencePath)))
{
groups.Add(projectGroup);
break;
}
}
}
foreach (var group in groups)
{
Console.WriteLine("Affected project group being built: " + group.HostAppSlug);
}
return groups.ToArray();
}
Console.WriteLine("Using all project groups: " + string.Join(',', Consts.ProjectGroups));
return Consts.ProjectGroups;
}
private static bool s_affectedComputed;
public static async Task ComputeAffected()
{
if (s_affectedComputed)
{
return;
}
var currentTag = await Versions.GetCurrentTag();
var currentVersion = await Versions.ComputeVersion();
var lastTag = await Versions.GetPreviousTag(currentTag);
var lastVersion = await Versions.ComputePreviousVersion(currentTag);
Console.WriteLine($"Last tag: {lastTag}, Current tag: {currentTag}");
Console.WriteLine($"Last parsed version: {lastVersion}, Current parsed version: {currentVersion}");
var sort = currentVersion.CompareSortOrderTo(lastVersion);
if (sort == -1)
{
Console.WriteLine($"Current version {currentVersion} is less than: {lastVersion}");
s_affectedComputed = true;
return;
}
var majorEquals = currentVersion.Major == lastVersion.Major;
if (!majorEquals)
{
Console.WriteLine($"Current version {currentVersion} is not matching major version: {lastVersion}");
s_affectedComputed = true;
return;
}
//use tags no matter the version if major versions match
var (currentCommit, _) = await ReadAsync("git", $"rev-list -n 1 {currentTag}");
var (lastCommit, _) = await ReadAsync("git", $"rev-list -n 1 {lastTag}");
await RunAsync("dotnet", $"affected -v --from {currentCommit.Trim()} --to {lastCommit.Trim()}", Root);
s_affectedComputed = true;
}
}
-2
View File
@@ -10,8 +10,6 @@
<PackageReference Include="Bullseye" />
<PackageReference Include="Glob" />
<PackageReference Include="Microsoft.Build" />
<PackageReference Include="Microsoft.VisualStudio.SolutionPersistence" />
<PackageReference Include="Semver" />
<PackageReference Include="SimpleExec" />
</ItemGroup>
</Project>
+7 -29
View File
@@ -4,14 +4,14 @@ public static class Consts
{
public static readonly string[] Solutions = ["Speckle.Connectors.sln"];
public static readonly ProjectGroup[] ProjectGroups =
public static readonly InstallerProject[] InstallerManifests =
{
new("arcgis", [new("Connectors/ArcGIS/Speckle.Connectors.ArcGIS3", "net6.0-windows")]),
new(
"rhino",
[
new("Connectors/Rhino/Speckle.Connectors.Rhino7", "net48"),
new("Connectors/Rhino/Speckle.Connectors.Rhino8", "net48"),
new("Connectors/Rhino/Speckle.Connectors.Rhino8", "net48")
]
),
new(
@@ -20,8 +20,7 @@ public static class Consts
new("Connectors/Revit/Speckle.Connectors.Revit2022", "net48"),
new("Connectors/Revit/Speckle.Connectors.Revit2023", "net48"),
new("Connectors/Revit/Speckle.Connectors.Revit2024", "net48"),
new("Connectors/Revit/Speckle.Connectors.Revit2025", "net8.0-windows"),
new("Connectors/Revit/Speckle.Connectors.Revit2026", "net8.0-windows")
new("Connectors/Revit/Speckle.Connectors.Revit2025", "net8.0-windows")
]
),
new(
@@ -30,8 +29,7 @@ public static class Consts
new("Connectors/Autocad/Speckle.Connectors.Autocad2022", "net48"),
new("Connectors/Autocad/Speckle.Connectors.Autocad2023", "net48"),
new("Connectors/Autocad/Speckle.Connectors.Autocad2024", "net48"),
new("Connectors/Autocad/Speckle.Connectors.Autocad2025", "net8.0-windows"),
new("Connectors/Autocad/Speckle.Connectors.Autocad2026", "net8.0-windows")
new("Connectors/Autocad/Speckle.Connectors.Autocad2025", "net8.0-windows")
]
),
new(
@@ -40,40 +38,20 @@ public static class Consts
new("Connectors/Autocad/Speckle.Connectors.Civil3d2022", "net48"),
new("Connectors/Autocad/Speckle.Connectors.Civil3d2023", "net48"),
new("Connectors/Autocad/Speckle.Connectors.Civil3d2024", "net48"),
new("Connectors/Autocad/Speckle.Connectors.Civil3d2025", "net8.0-windows"),
new("Connectors/Autocad/Speckle.Connectors.Civil3d2026", "net8.0-windows")
new("Connectors/Autocad/Speckle.Connectors.Civil3d2025", "net8.0-windows")
]
),
new(
"navisworks",
[
new("Connectors/Navisworks/Speckle.Connectors.Navisworks2020", "net48"),
new("Connectors/Navisworks/Speckle.Connectors.Navisworks2021", "net48"),
new("Connectors/Navisworks/Speckle.Connectors.Navisworks2022", "net48"),
new("Connectors/Navisworks/Speckle.Connectors.Navisworks2023", "net48"),
new("Connectors/Navisworks/Speckle.Connectors.Navisworks2024", "net48"),
new("Connectors/Navisworks/Speckle.Connectors.Navisworks2025", "net48"),
new("Connectors/Navisworks/Speckle.Connectors.Navisworks2026", "net48")
]
),
new(
"teklastructures",
"tekla-structures",
[
new("Connectors/Tekla/Speckle.Connector.Tekla2023", "net48"),
new("Connectors/Tekla/Speckle.Connector.Tekla2024", "net48")
]
),
new(
"etabs",
[
new("Connectors/CSi/Speckle.Connectors.ETABS21", "net48"),
new("Connectors/CSi/Speckle.Connectors.ETABS22", "net8.0-windows"),
]
)
};
}
public readonly record struct ProjectGroup(string HostAppSlug, IReadOnlyList<InstallerAsset> Projects)
public readonly record struct InstallerProject(string HostAppSlug, IReadOnlyList<InstallerAsset> Projects)
{
public override string ToString() => $"{HostAppSlug}";
}
+2 -2
View File
@@ -28,11 +28,11 @@ public static class Github
Content = content
};
request.Headers.Add("X-GitHub-Api-Version", "2022-11-28");
var response = await client.SendAsync(request);
var response = await client.SendAsync(request).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
throw new InvalidOperationException(
$"{response.StatusCode} {response.ReasonPhrase} {await response.Content.ReadAsStringAsync()}"
$"{response.StatusCode} {response.ReasonPhrase} {await response.Content.ReadAsStringAsync().ConfigureAwait(false)}"
);
}
}
+93 -151
View File
@@ -7,18 +7,16 @@ using static SimpleExec.Command;
const string CLEAN = "clean";
const string RESTORE = "restore";
const string BUILD = "build";
const string BUILD_LINUX = "build-linux";
const string TEST = "test";
const string TEST_ONLY = "test-only";
const string FORMAT = "format";
const string ZIP = "zip";
const string VERSION = "version";
const string RESTORE_TOOLS = "restore-tools";
const string BUILD_SERVER_VERSION = "build-server-version";
const string CLEAN_LOCKS = "clean-locks";
const string CHECK_SOLUTIONS = "check-solutions";
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";
//need to pass arguments
/*var arguments = new List<string>();
@@ -29,67 +27,44 @@ if (args.Length > 1)
//arguments = arguments.Skip(1).ToList();
}*/
void Restore(string solution)
{
Console.WriteLine();
Console.WriteLine($"Restoring solution '{solution}'");
Console.WriteLine();
Run("dotnet", $"restore \".\\{solution}\" --no-cache");
}
void DeleteFiles(string pattern)
{
foreach (var f in Glob.Files(".", pattern))
{
Console.WriteLine("Found and will delete: " + f);
File.Delete(f);
}
}
void DeleteDirectories(string pattern)
{
foreach (var f in Glob.Directories(".", pattern))
{
if (f.StartsWith("Build"))
{
continue;
}
Console.WriteLine("Found and will delete: " + f);
Directory.Delete(f, true);
}
}
void CleanSolution(string solution, string configuration)
{
Console.WriteLine("Cleaning solution: " + solution);
DeleteDirectories("**/bin");
DeleteDirectories("**/obj");
DeleteFiles("**/*.lock.json");
Restore(solution);
}
Target(
CLEAN_LOCKS,
Consts.Solutions,
s =>
() =>
{
DeleteFiles("**/*.lock.json");
Restore(s);
foreach (var f in Glob.Files(".", "**/*.lock.json"))
{
Console.WriteLine("Found and will delete: " + f);
File.Delete(f);
}
Console.WriteLine("Running restore now.");
Run("dotnet", "restore .\\Speckle.Connectors.sln --no-cache");
}
);
Target(
DEEP_CLEAN,
Consts.Solutions,
s =>
{
CleanSolution(s, "debug");
}
);
Target(
DEEP_CLEAN_LOCAL,
() =>
{
CleanSolution("Local.sln", "Local");
foreach (var f in Glob.Directories(".", "**/bin"))
{
if (f.StartsWith("Build"))
{
continue;
}
Console.WriteLine("Found and will delete: " + f);
Directory.Delete(f, true);
}
foreach (var f in Glob.Directories(".", "**/obj"))
{
if (f.StartsWith("Build"))
{
continue;
}
Console.WriteLine("Found and will delete: " + f);
Directory.Delete(f, true);
}
Console.WriteLine("Running restore now.");
Run("dotnet", "restore .\\Speckle.Connectors.sln --no-cache");
}
);
@@ -120,22 +95,21 @@ Target(
);
Target(
RESTORE_TOOLS,
() =>
VERSION,
async () =>
{
Run("dotnet", "tool restore");
var (output, _) = await ReadAsync("dotnet", "minver -v w").ConfigureAwait(false);
output = output.Trim();
Console.WriteLine($"Version: {output}");
Run("echo", $"\"version={output}\" >> $GITHUB_OUTPUT");
}
);
Target(
DETECT_AFFECTED,
DependsOn(RESTORE_TOOLS),
async () =>
RESTORE_TOOLS,
() =>
{
foreach (var group in await Affected.GetAffectedProjectGroups())
{
Console.WriteLine("Affected project group being built: " + group.HostAppSlug);
}
Run("dotnet", "tool restore");
}
);
@@ -150,14 +124,20 @@ Target(
Target(
RESTORE,
DependsOn(FORMAT, DETECT_AFFECTED),
DependsOn(FORMAT),
Consts.Solutions,
async s =>
s =>
{
var version = await Versions.ComputeVersion();
var fileVersion = await Versions.ComputeFileVersion();
Console.WriteLine($"Restoring: {s} - Version: {version} & {fileVersion}");
await RunAsync("dotnet", $"restore \"{s}\" --locked-mode");
Run("dotnet", $"restore {s} --locked-mode");
}
);
Target(
BUILD_SERVER_VERSION,
DependsOn(RESTORE_TOOLS),
() =>
{
Run("dotnet", "tool run dotnet-gitversion /output json /output buildserver");
}
);
@@ -165,66 +145,37 @@ Target(
BUILD,
DependsOn(RESTORE),
Consts.Solutions,
async s =>
s =>
{
var version = await Versions.ComputeVersion();
var fileVersion = await Versions.ComputeFileVersion();
Console.WriteLine($"Restoring: {s} - Version: {version} & {fileVersion}");
await RunAsync(
"dotnet",
$"build \"{s}\" -c Release --no-restore -warnaserror -p:Version={version} -p:FileVersion={fileVersion} -v:m"
);
var version = Environment.GetEnvironmentVariable("GitVersion_FullSemVer") ?? "3.0.0-localBuild";
var fileVersion = Environment.GetEnvironmentVariable("GitVersion_AssemblySemFileVer") ?? "3.0.0.0";
Console.WriteLine($"Version: {version} & {fileVersion}");
Run("dotnet", $"build {s} -c Release --no-restore -p:Version={version} -p:FileVersion={fileVersion} -v:m");
}
);
Target(CHECK_SOLUTIONS, Solutions.CompareConnectorsToLocal);
Target(GEN_SOLUTIONS, Solutions.GenerateSolutions);
Target(
TEST,
DependsOn(BUILD, CHECK_SOLUTIONS),
async () =>
Glob.Files(".", "**/*.Tests.csproj"),
file =>
{
foreach (var s in await Affected.GetTestProjects())
{
await RunAsync("dotnet", $"test \"{s}\" -c Release --no-build --no-restore --verbosity=minimal");
}
Run("dotnet", $"test {file} -c Release --no-build --no-restore --verbosity=minimal");
}
);
//all tests on purpose
Target(
TEST_ONLY,
DependsOn(FORMAT),
Glob.Files(".", "**/*.Tests.csproj"),
file =>
{
Run("dotnet", $"build \"{file}\" -c Release --no-incremental");
Run("dotnet", $"restore {file} --locked-mode");
Run(
"dotnet",
$"test \"{file}\" -c Release --no-build --verbosity=minimal /p:AltCover=true /p:AltCoverAttributeFilter=ExcludeFromCodeCoverage /p:AltCoverVerbosity=Warning"
);
}
);
Target(
BUILD_LINUX,
DependsOn(FORMAT),
Glob.Files(".", "**/Speckle.Importers.Ifc.csproj"),
async file =>
{
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"
$"test {file} -c Release --no-restore --verbosity=minimal /p:AltCover=true /p:AltCoverAttributeFilter=ExcludeFromCodeCoverage /p:AltCoverVerbosity=Warning"
);
}
);
@@ -232,56 +183,47 @@ Target(
Target(
ZIP,
DependsOn(TEST),
async () =>
Consts.InstallerManifests,
x =>
{
var version = await Versions.ComputeVersion();
var fileVersion = await Versions.ComputeFileVersion();
foreach (var group in await Affected.GetAffectedProjectGroups())
var outputDir = Path.Combine(".", "output");
var slugDir = Path.Combine(outputDir, x.HostAppSlug);
Directory.CreateDirectory(outputDir);
Directory.CreateDirectory(slugDir);
foreach (var asset in x.Projects)
{
Console.WriteLine($"Zipping: {group.HostAppSlug} as {version}");
var outputDir = Path.Combine(".", "output");
var slugDir = Path.Combine(outputDir, group.HostAppSlug);
Directory.CreateDirectory(outputDir);
Directory.CreateDirectory(slugDir);
foreach (var asset in group.Projects)
var fullPath = Path.Combine(".", asset.ProjectPath, "bin", "Release", asset.TargetName);
if (!Directory.Exists(fullPath))
{
var fullPath = Path.Combine(".", asset.ProjectPath, "bin", "Release", asset.TargetName);
if (!Directory.Exists(fullPath))
{
throw new InvalidOperationException("Could not find: " + fullPath);
}
var assetName = Path.GetFileName(asset.ProjectPath);
var connectorDir = Path.Combine(slugDir, assetName);
Directory.CreateDirectory(connectorDir);
foreach (var directory in Directory.EnumerateDirectories(fullPath, "*", SearchOption.AllDirectories))
{
Directory.CreateDirectory(directory.Replace(fullPath, connectorDir));
}
foreach (var file in Directory.EnumerateFiles(fullPath, "*", SearchOption.AllDirectories))
{
Console.WriteLine(file);
File.Copy(file, file.Replace(fullPath, connectorDir), true);
}
throw new InvalidOperationException("Could not find: " + fullPath);
}
var outputPath = Path.Combine(outputDir, $"{group.HostAppSlug}.zip");
File.Delete(outputPath);
Console.WriteLine($"Zipping: '{slugDir}' to '{outputPath}'");
ZipFile.CreateFromDirectory(slugDir, outputPath);
var assetName = Path.GetFileName(asset.ProjectPath);
var connectorDir = Path.Combine(slugDir, assetName);
Directory.CreateDirectory(connectorDir);
foreach (var directory in Directory.EnumerateDirectories(fullPath, "*", SearchOption.AllDirectories))
{
Directory.CreateDirectory(directory.Replace(fullPath, connectorDir));
}
foreach (var file in Directory.EnumerateFiles(fullPath, "*", SearchOption.AllDirectories))
{
Console.WriteLine(file);
File.Copy(file, file.Replace(fullPath, connectorDir), true);
}
}
string githubEnv = Environment.GetEnvironmentVariable("GITHUB_ENV") ?? "Unset";
Console.WriteLine($"GITHUB_ENV: {githubEnv}");
File.AppendAllText(githubEnv, $"SEMVER={version}{Environment.NewLine}");
File.AppendAllText(githubEnv, $"FILE_VERSION={fileVersion}{Environment.NewLine}");
var outputPath = Path.Combine(outputDir, $"{x.HostAppSlug}.zip");
File.Delete(outputPath);
Console.WriteLine($"Zipping: '{slugDir}' to '{outputPath}'");
ZipFile.CreateFromDirectory(slugDir, outputPath);
// Directory.Delete(slugDir, true);
}
);
Target("default", DependsOn(TEST), () => Console.WriteLine("Done!"));
Target("default", DependsOn(FORMAT, ZIP), () => Console.WriteLine("Done!"));
await RunTargetsAndExitAsync(args).ConfigureAwait(true);
-51
View File
@@ -1,6 +1,4 @@
using Microsoft.Build.Construction;
using Microsoft.VisualStudio.SolutionPersistence.Model;
using Microsoft.VisualStudio.SolutionPersistence.Serializer;
namespace Build;
@@ -55,53 +53,4 @@ public static class Solutions
);
}
}
public static async Task GenerateSolutions()
{
await GenerateLocalSlnx();
foreach (var group in Consts.ProjectGroups)
{
var path = group.Projects[0].ProjectPath.Split('/');
var folder = $"/{path[0]}/{path[1]}/";
await GenerateConnector(group.HostAppSlug, folder);
}
}
public static async Task GenerateLocalSlnx()
{
var connectors = await GetFullSlnx();
connectors.AddProject("..\\speckle-sharp-sdk\\src\\Speckle.Objects\\Speckle.Objects.csproj");
connectors.AddProject("..\\speckle-sharp-sdk\\src\\Speckle.Sdk\\Speckle.Sdk.csproj");
connectors.AddProject("..\\speckle-sharp-sdk\\src\\Speckle.Sdk.Dependencies\\Speckle.Sdk.Dependencies.csproj");
var sln = Path.Combine("C:\\Users\\adam\\Git\\speckle-sharp-connectors", "Local.slnx");
await SolutionSerializers.SlnXml.SaveAsync(sln, connectors, default);
sln = Path.Combine("C:\\Users\\adam\\Git\\speckle-sharp-connectors", "Local.sln");
await SolutionSerializers.SlnFileV12.SaveAsync(sln, connectors, default);
}
public static async Task GenerateConnector(string slug, string folder)
{
slug = string.Concat(slug[0].ToString().ToUpper(), slug.AsSpan(1));
var connectors = await GetFullSlnx();
var foldersToRemove = connectors
.SolutionFolders.Where(x =>
//need base folder
!x.Path.Equals("/Connectors/")
//don't grab all
&& (x.Path.StartsWith("/Connectors/") && !x.Path.StartsWith(folder))
)
.ToList();
foreach (var folderToRemove in foldersToRemove)
{
connectors.RemoveFolder(folderToRemove);
}
var sln = Path.Combine("C:\\Users\\adam\\Git\\speckle-sharp-connectors", $"Speckle.{slug}.slnx");
await SolutionSerializers.SlnXml.SaveAsync(sln, connectors, default);
}
public static async Task<SolutionModel> GetFullSlnx()
{
var connectorsSln = Path.Combine("C:\\Users\\adam\\Git\\speckle-sharp-connectors", "Speckle.Connectors.slnx");
return await SolutionSerializers.SlnXml.OpenAsync(connectorsSln, default);
}
}
-79
View File
@@ -1,79 +0,0 @@
using Semver;
using static SimpleExec.Command;
namespace Build;
public static class Versions
{
private static string? s_currentTag;
private static SemVersion? s_currentVersion;
public static async Task<string> GetCurrentTag()
{
if (s_currentTag is not null)
{
return s_currentTag;
}
//finds current tag or makes one
var (currentTag, _) = await ReadAsync("git", "describe --tags");
currentTag = currentTag.Trim();
s_currentTag = currentTag;
return s_currentTag;
}
public static async Task<SemVersion> ComputeVersion()
{
if (s_currentVersion is not null)
{
return s_currentVersion;
}
var currentTag = await GetCurrentTag();
if (!SemVersion.TryParse(currentTag, SemVersionStyles.AllowLowerV, out var currentVersion))
{
throw new InvalidOperationException($"Could not parse version: '{currentTag}'");
}
s_currentVersion = currentVersion;
return s_currentVersion;
}
private static string? s_currentFileVersion;
public static async Task<string> ComputeFileVersion()
{
if (s_currentFileVersion is not null)
{
return s_currentFileVersion;
}
var currentVersion = await ComputeVersion();
s_currentFileVersion = currentVersion.WithoutPrereleaseOrMetadata() + ".0";
return s_currentFileVersion;
}
public static async Task<string> GetPreviousTag(string currentTag)
{
//finds a tag starting with current tag and adds no abbrevation
var (lastTag, _) = await ReadAsync("git", $"describe --abbrev=0 --tags {currentTag}^");
lastTag = lastTag.Trim();
return lastTag;
}
private static SemVersion? s_previousVersion;
public static async Task<SemVersion> ComputePreviousVersion(string currentTag)
{
if (s_previousVersion is not null)
{
return s_previousVersion;
}
var lastTag = await GetPreviousTag(currentTag);
if (!SemVersion.TryParse(lastTag, SemVersionStyles.AllowLowerV, out var lastVersion))
{
throw new InvalidOperationException($"Could not parse version: '{lastTag}'");
}
s_previousVersion = lastVersion;
return s_previousVersion;
}
}
-20
View File
@@ -47,27 +47,12 @@
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"Microsoft.VisualStudio.SolutionPersistence": {
"type": "Direct",
"requested": "[1.0.52, )",
"resolved": "1.0.52",
"contentHash": "oNv2JtYXhpdJrX63nibx1JT3uCESOBQ1LAk7Dtz/sr0+laW0KRM6eKp4CZ3MHDR2siIkKsY8MmUkeP5DKkQQ5w=="
},
"PolySharp": {
"type": "Direct",
"requested": "[1.14.1, )",
"resolved": "1.14.1",
"contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ=="
},
"Semver": {
"type": "Direct",
"requested": "[3.0.0, )",
"resolved": "3.0.0",
"contentHash": "9jZCicsVgTebqkAujRWtC9J1A5EQVlu0TVKHcgoCuv345ve5DYf4D1MjhKEnQjdRZo6x/vdv6QQrYFs7ilGzLA==",
"dependencies": {
"Microsoft.Extensions.Primitives": "5.0.1"
}
},
"SimpleExec": {
"type": "Direct",
"requested": "[12.0.0, )",
@@ -90,11 +75,6 @@
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "5.0.1",
"contentHash": "5WPSmL4YeP7eW+Vc8XZ4DwjYWBAiSwDV9Hm63JJWcz1Ie3Xjv4KuJXzgCstj48LkLfVCYa7mLcx7y+q6yqVvtw=="
},
"Microsoft.NET.StringTools": {
"type": "Transitive",
"resolved": "17.11.4",
@@ -19,7 +19,7 @@ namespace Speckle.Connectors.ArcGIS.Bindings;
public sealed class ArcGISReceiveBinding : IReceiveBinding
{
public string Name { get; } = "receiveBinding";
private readonly ICancellationManager _cancellationManager;
private readonly CancellationManager _cancellationManager;
private readonly DocumentModelStore _store;
private readonly IServiceProvider _serviceProvider;
private readonly IOperationProgressManager _operationProgressManager;
@@ -32,7 +32,7 @@ public sealed class ArcGISReceiveBinding : IReceiveBinding
public ArcGISReceiveBinding(
DocumentModelStore store,
IBrowserBridge parent,
ICancellationManager cancellationManager,
CancellationManager cancellationManager,
IServiceProvider serviceProvider,
IOperationProgressManager operationProgressManager,
ILogger<ArcGISReceiveBinding> logger,
@@ -60,7 +60,7 @@ public sealed class ArcGISReceiveBinding : IReceiveBinding
throw new InvalidOperationException("No download model card was found.");
}
using var cancellationItem = _cancellationManager.GetCancellationItem(modelCardId);
CancellationToken cancellationToken = _cancellationManager.InitCancellationTokenSource(modelCardId);
using var scope = _serviceProvider.CreateScope();
scope
.ServiceProvider.GetRequiredService<IConverterSettingsStore<ArcGISConversionSettings>>()
@@ -76,16 +76,19 @@ public sealed class ArcGISReceiveBinding : IReceiveBinding
.ServiceProvider.GetRequiredService<ReceiveOperation>()
.Execute(
modelCard.GetReceiveInfo("ArcGIS"), // POC: get host app name from settings? same for GetSendInfo
_operationProgressManager.CreateOperationProgressEventHandler(Parent, modelCardId, cancellationItem.Token),
cancellationItem.Token
);
_operationProgressManager.CreateOperationProgressEventHandler(Parent, modelCardId, cancellationToken),
cancellationToken
)
.ConfigureAwait(false);
modelCard.BakedObjectIds = receiveOperationResults.BakedObjectIds.ToList();
await Commands.SetModelReceiveResult(
modelCardId,
receiveOperationResults.BakedObjectIds,
receiveOperationResults.ConversionResults
);
await Commands
.SetModelReceiveResult(
modelCardId,
receiveOperationResults.BakedObjectIds,
receiveOperationResults.ConversionResults
)
.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -97,7 +100,7 @@ public sealed class ArcGISReceiveBinding : IReceiveBinding
catch (Exception ex) when (!ex.IsFatal()) // UX reasons - we will report operation exceptions as model card error. We may change this later when we have more exception documentation
{
_logger.LogModelCardHandledError(ex);
await Commands.SetModelError(modelCardId, ex);
await Commands.SetModelError(modelCardId, ex).ConfigureAwait(false);
}
}
@@ -12,18 +12,15 @@ public class ArcGISSelectionBinding : ISelectionBinding
public string Name => "selectionBinding";
public IBrowserBridge Parent { get; }
public ArcGISSelectionBinding(
IBrowserBridge parent,
MapMembersUtils mapMemberUtils,
ITopLevelExceptionHandler topLevelExceptionHandler
)
public ArcGISSelectionBinding(IBrowserBridge parent, MapMembersUtils mapMemberUtils)
{
_mapMemberUtils = mapMemberUtils;
Parent = parent;
var topLevelHandler = parent.TopLevelExceptionHandler;
// example: https://github.com/Esri/arcgis-pro-sdk-community-samples/blob/master/Map-Authoring/QueryBuilderControl/DefinitionQueryDockPaneViewModel.cs
// MapViewEventArgs args = new(MapView.Active);
TOCSelectionChangedEvent.Subscribe(_ => topLevelExceptionHandler.CatchUnhandled(OnSelectionChanged), true);
TOCSelectionChangedEvent.Subscribe(_ => topLevelHandler.CatchUnhandled(OnSelectionChanged), true);
}
private void OnSelectionChanged()
@@ -56,8 +53,11 @@ public class ArcGISSelectionBinding : ISelectionBinding
selectedMembers.AddRange(mapView.GetSelectedStandaloneTables());
List<MapMember> allNestedMembers = new();
var layerMapMembers = _mapMemberUtils.UnpackMapLayers(selectedMembers);
allNestedMembers.AddRange(layerMapMembers);
foreach (MapMember member in selectedMembers)
{
var layerMapMembers = _mapMemberUtils.UnpackMapLayers(selectedMembers);
allNestedMembers.AddRange(layerMapMembers);
}
List<string> objectTypes = allNestedMembers
.Select(o => o.GetType().ToString().Split(".").Last())
@@ -13,7 +13,6 @@ using Speckle.Connectors.ArcGIS.Utils;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Exceptions;
@@ -39,14 +38,12 @@ public sealed class ArcGISSendBinding : ISendBinding
private readonly DocumentModelStore _store;
private readonly IServiceProvider _serviceProvider;
private readonly List<ISendFilter> _sendFilters;
private readonly ICancellationManager _cancellationManager;
private readonly CancellationManager _cancellationManager;
private readonly ISendConversionCache _sendConversionCache;
private readonly IOperationProgressManager _operationProgressManager;
private readonly ILogger<ArcGISSendBinding> _logger;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly IArcGISConversionSettingsFactory _arcGISConversionSettingsFactory;
private readonly IThreadContext _threadContext;
private readonly ISpeckleApplication _speckleApplication;
/// <summary>
/// Used internally to aggregate the changed objects' id. Note we're using a concurrent dictionary here as the expiry check method is not thread safe, and this was causing problems. See:
@@ -65,15 +62,12 @@ public sealed class ArcGISSendBinding : ISendBinding
IBrowserBridge parent,
IEnumerable<ISendFilter> sendFilters,
IServiceProvider serviceProvider,
ICancellationManager cancellationManager,
CancellationManager cancellationManager,
ISendConversionCache sendConversionCache,
IOperationProgressManager operationProgressManager,
ILogger<ArcGISSendBinding> logger,
IArcGISConversionSettingsFactory arcGisConversionSettingsFactory,
MapMembersUtils mapMemberUtils,
IThreadContext threadContext,
ISpeckleApplication speckleApplication,
ITopLevelExceptionHandler topLevelExceptionHandler
MapMembersUtils mapMemberUtils
)
{
_store = store;
@@ -83,11 +77,9 @@ public sealed class ArcGISSendBinding : ISendBinding
_sendConversionCache = sendConversionCache;
_operationProgressManager = operationProgressManager;
_logger = logger;
_topLevelExceptionHandler = topLevelExceptionHandler;
_topLevelExceptionHandler = parent.TopLevelExceptionHandler;
_arcGISConversionSettingsFactory = arcGisConversionSettingsFactory;
_mapMemberUtils = mapMemberUtils;
_threadContext = threadContext;
_speckleApplication = speckleApplication;
Parent = parent;
Commands = new SendBindingUICommands(parent);
@@ -98,22 +90,18 @@ public sealed class ArcGISSendBinding : ISendBinding
};
}
private void OnDocumentStoreChangedEvent(object _) => _sendConversionCache.ClearCache();
private void SubscribeToArcGISEvents()
{
LayersRemovedEvent.Subscribe(
a =>
_topLevelExceptionHandler.FireAndForget(
async () => await QueuedTask.Run(async () => await GetIdsForLayersRemovedEvent(a))
),
_topLevelExceptionHandler.FireAndForget(async () => await GetIdsForLayersRemovedEvent(a).ConfigureAwait(false)),
true
);
StandaloneTablesRemovedEvent.Subscribe(
a =>
_topLevelExceptionHandler.FireAndForget(
async () => await QueuedTask.Run(async () => await GetIdsForStandaloneTablesRemovedEvent(a))
async () => await GetIdsForStandaloneTablesRemovedEvent(a).ConfigureAwait(false)
),
true
);
@@ -121,7 +109,7 @@ public sealed class ArcGISSendBinding : ISendBinding
MapPropertyChangedEvent.Subscribe(
a =>
_topLevelExceptionHandler.FireAndForget(
async () => await QueuedTask.Run(async () => await GetIdsForMapPropertyChangedEvent(a))
async () => await GetIdsForMapPropertyChangedEvent(a).ConfigureAwait(false)
),
true
); // Map units, CRS etc.
@@ -129,17 +117,13 @@ public sealed class ArcGISSendBinding : ISendBinding
MapMemberPropertiesChangedEvent.Subscribe(
a =>
_topLevelExceptionHandler.FireAndForget(
async () => await QueuedTask.Run(async () => await GetIdsForMapMemberPropertiesChangedEvent(a))
async () => await GetIdsForMapMemberPropertiesChangedEvent(a).ConfigureAwait(false)
),
true
); // e.g. Layer name
ActiveMapViewChangedEvent.Subscribe(
_ =>
_topLevelExceptionHandler.FireAndForget(async () =>
{
await QueuedTask.Run(SubscribeToMapMembersDataSourceChange);
}),
_ => _topLevelExceptionHandler.CatchUnhandled(SubscribeToMapMembersDataSourceChange),
true
);
@@ -155,24 +139,28 @@ public sealed class ArcGISSendBinding : ISendBinding
private void SubscribeToMapMembersDataSourceChange()
{
if (MapView.Active == null)
var task = QueuedTask.Run(() =>
{
return;
}
// subscribe to layers
foreach (Layer layer in MapView.Active.Map.Layers)
{
if (layer is FeatureLayer featureLayer)
if (MapView.Active == null)
{
SubscribeToFeatureLayerDataSourceChange(featureLayer);
return;
}
}
// subscribe to tables
foreach (StandaloneTable table in MapView.Active.Map.StandaloneTables)
{
SubscribeToTableDataSourceChange(table);
}
// subscribe to layers
foreach (Layer layer in MapView.Active.Map.Layers)
{
if (layer is FeatureLayer featureLayer)
{
SubscribeToFeatureLayerDataSourceChange(featureLayer);
}
}
// subscribe to tables
foreach (StandaloneTable table in MapView.Active.Map.StandaloneTables)
{
SubscribeToTableDataSourceChange(table);
}
});
task.Wait();
}
private void SubscribeToFeatureLayerDataSourceChange(FeatureLayer layer)
@@ -207,25 +195,25 @@ public sealed class ArcGISSendBinding : ISendBinding
{
RowCreatedEvent.Subscribe(
(args) =>
_topLevelExceptionHandler.FireAndForget(async () =>
Parent.TopLevelExceptionHandler.FireAndForget(async () =>
{
await OnRowChanged(args);
await OnRowChanged(args).ConfigureAwait(false);
}),
layerTable
);
RowChangedEvent.Subscribe(
(args) =>
_topLevelExceptionHandler.FireAndForget(async () =>
Parent.TopLevelExceptionHandler.FireAndForget(async () =>
{
await OnRowChanged(args);
await OnRowChanged(args).ConfigureAwait(false);
}),
layerTable
);
RowDeletedEvent.Subscribe(
(args) =>
_topLevelExceptionHandler.FireAndForget(async () =>
Parent.TopLevelExceptionHandler.FireAndForget(async () =>
{
await OnRowChanged(args);
await OnRowChanged(args).ConfigureAwait(false);
}),
layerTable
);
@@ -270,7 +258,7 @@ public sealed class ArcGISSendBinding : ISendBinding
}
}
await RunExpirationChecks(false);
await RunExpirationChecks(false).ConfigureAwait(false);
}
private async Task GetIdsForLayersRemovedEvent(LayerEventsArgs args)
@@ -279,7 +267,7 @@ public sealed class ArcGISSendBinding : ISendBinding
{
ChangedObjectIds[layer.URI] = 1;
}
await RunExpirationChecks(true);
await RunExpirationChecks(true).ConfigureAwait(false);
}
private async Task GetIdsForStandaloneTablesRemovedEvent(StandaloneTableEventArgs args)
@@ -288,7 +276,7 @@ public sealed class ArcGISSendBinding : ISendBinding
{
ChangedObjectIds[table.URI] = 1;
}
await RunExpirationChecks(true);
await RunExpirationChecks(true).ConfigureAwait(false);
}
private async Task GetIdsForMapPropertyChangedEvent(MapPropertyChangedEventArgs args)
@@ -301,7 +289,7 @@ public sealed class ArcGISSendBinding : ISendBinding
ChangedObjectIds[member.URI] = 1;
}
}
await RunExpirationChecks(false);
await RunExpirationChecks(false).ConfigureAwait(false);
}
private void GetIdsForLayersAddedEvent(LayerEventsArgs args)
@@ -351,7 +339,7 @@ public sealed class ArcGISSendBinding : ISendBinding
{
ChangedObjectIds[member.URI] = 1;
}
await RunExpirationChecks(false);
await RunExpirationChecks(false).ConfigureAwait(false);
}
}
@@ -376,61 +364,66 @@ public sealed class ArcGISSendBinding : ISendBinding
throw new InvalidOperationException("No publish model card was found.");
}
using var cancellationItem = _cancellationManager.GetCancellationItem(modelCardId);
CancellationToken cancellationToken = _cancellationManager.InitCancellationTokenSource(modelCardId);
using var scope = _serviceProvider.CreateScope();
List<MapMember> mapMembers = await QueuedTask.Run(() =>
{
scope
.ServiceProvider.GetRequiredService<IConverterSettingsStore<ArcGISConversionSettings>>()
.Initialize(
_arcGISConversionSettingsFactory.Create(
Project.Current,
MapView.Active.Map,
new CRSoffsetRotation(MapView.Active.Map)
)
);
return modelCard
.SendFilter.NotNull()
.RefreshObjectIds()
.Select(id => (MapMember)MapView.Active.Map.FindLayer(id) ?? MapView.Active.Map.FindStandaloneTable(id))
.Where(obj => obj != null)
.ToList();
});
if (mapMembers.Count == 0)
{
// Handle as CARD ERROR in this function
throw new SpeckleSendFilterException("No objects were found to convert. Please update your publish filter!");
}
await QueuedTask.Run(() =>
{
// subscribe to the selected layer events
foreach (MapMember mapMember in mapMembers)
var sendResult = await QueuedTask
.Run(async () =>
{
if (mapMember is FeatureLayer featureLayer)
{
SubscribeToFeatureLayerDataSourceChange(featureLayer);
}
else if (mapMember is StandaloneTable table)
{
SubscribeToTableDataSourceChange(table);
}
}
});
using var scope = _serviceProvider.CreateScope();
scope
.ServiceProvider.GetRequiredService<IConverterSettingsStore<ArcGISConversionSettings>>()
.Initialize(
_arcGISConversionSettingsFactory.Create(
Project.Current,
MapView.Active.Map,
new CRSoffsetRotation(MapView.Active.Map)
)
);
List<MapMember> mapMembers = modelCard
.SendFilter.NotNull()
.RefreshObjectIds()
.Select(id => (MapMember)MapView.Active.Map.FindLayer(id) ?? MapView.Active.Map.FindStandaloneTable(id))
.Where(obj => obj != null)
.ToList();
var sendResult = await scope
.ServiceProvider.GetRequiredService<SendOperation<MapMember>>()
.Execute(
mapMembers,
modelCard.GetSendInfo(_speckleApplication.ApplicationAndVersion),
_operationProgressManager.CreateOperationProgressEventHandler(Parent, modelCardId, cancellationItem.Token),
cancellationItem.Token
);
if (mapMembers.Count == 0)
{
// Handle as CARD ERROR in this function
throw new SpeckleSendFilterException(
"No objects were found to convert. Please update your publish filter!"
);
}
await Commands.SetModelSendResult(modelCardId, sendResult.RootObjId, sendResult.ConversionResults);
// subscribe to the selected layer events
foreach (MapMember mapMember in mapMembers)
{
if (mapMember is FeatureLayer featureLayer)
{
SubscribeToFeatureLayerDataSourceChange(featureLayer);
}
else if (mapMember is StandaloneTable table)
{
SubscribeToTableDataSourceChange(table);
}
}
var result = await scope
.ServiceProvider.GetRequiredService<SendOperation<MapMember>>()
.Execute(
mapMembers,
modelCard.GetSendInfo("ArcGIS"), // POC: get host app name from settings? same for GetReceiveInfo
_operationProgressManager.CreateOperationProgressEventHandler(Parent, modelCardId, cancellationToken),
cancellationToken
)
.ConfigureAwait(false);
return result;
})
.ConfigureAwait(false);
await Commands
.SetModelSendResult(modelCardId, sendResult.RootObjId, sendResult.ConversionResults)
.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -442,7 +435,7 @@ public sealed class ArcGISSendBinding : ISendBinding
catch (Exception ex) when (!ex.IsFatal()) // UX reasons - we will report operation exceptions as model card error. We may change this later when we have more exception documentation
{
_logger.LogModelCardHandledError(ex);
await Commands.SetModelError(modelCardId, ex);
await Commands.SetModelError(modelCardId, ex).ConfigureAwait(false);
}
}
@@ -477,7 +470,7 @@ public sealed class ArcGISSendBinding : ISendBinding
}
}
await Commands.SetModelsExpired(expiredSenderIds);
await Commands.SetModelsExpired(expiredSenderIds).ConfigureAwait(false);
ChangedObjectIds = new();
}
}
@@ -21,25 +21,18 @@ public class BasicConnectorBinding : IBasicConnectorBinding
public BasicConnectorBindingCommands Commands { get; }
private readonly DocumentModelStore _store;
private readonly ISpeckleApplication _speckleApplication;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
public BasicConnectorBinding(
DocumentModelStore store,
IBrowserBridge parent,
ISpeckleApplication speckleApplication,
ITopLevelExceptionHandler topLevelExceptionHandler
)
public BasicConnectorBinding(DocumentModelStore store, IBrowserBridge parent, ISpeckleApplication speckleApplication)
{
_store = store;
_speckleApplication = speckleApplication;
_topLevelExceptionHandler = topLevelExceptionHandler;
Parent = parent;
Commands = new BasicConnectorBindingCommands(parent);
_store.DocumentChanged += (_, _) =>
_topLevelExceptionHandler.FireAndForget(async () =>
parent.TopLevelExceptionHandler.FireAndForget(async () =>
{
await Commands.NotifyDocumentChanged();
await Commands.NotifyDocumentChanged().ConfigureAwait(false);
});
}
@@ -61,18 +54,14 @@ public class BasicConnectorBinding : IBasicConnectorBinding
public DocumentModelStore GetDocumentState() => _store;
public void AddModel(ModelCard model) => _store.AddModel(model);
public void AddModel(ModelCard model) => _store.Models.Add(model);
public void UpdateModel(ModelCard model) => _store.UpdateModel(model);
public void RemoveModel(ModelCard model) => _store.RemoveModel(model);
public void RemoveModels(List<ModelCard> models) => _store.RemoveModels(models);
public async Task HighlightObjects(IReadOnlyList<string> objectIds)
{
await HighlightObjectsOnView(objectIds.Select(x => new ObjectID(x)).ToList());
}
public async Task HighlightObjects(IReadOnlyList<string> objectIds) =>
await HighlightObjectsOnView(objectIds.Select(x => new ObjectID(x)).ToList()).ConfigureAwait(false);
public async Task HighlightModel(string modelCardId)
{
@@ -99,22 +88,24 @@ public class BasicConnectorBinding : IBasicConnectorBinding
{
return;
}
await HighlightObjectsOnView(objectIds);
await HighlightObjectsOnView(objectIds).ConfigureAwait(false);
}
private async Task HighlightObjectsOnView(IReadOnlyList<ObjectID> objectIds)
{
await QueuedTask.Run(() =>
{
MapView mapView = MapView.Active;
MapView mapView = MapView.Active;
List<MapMemberFeature> mapMembersFeatures = GetMapMembers(objectIds, mapView);
ClearSelectionInTOC();
ClearSelection();
SelectMapMembersInTOC(mapMembersFeatures);
SelectMapMembersAndFeatures(mapMembersFeatures);
mapView.ZoomToSelected();
});
await QueuedTask
.Run(async () =>
{
List<MapMemberFeature> mapMembersFeatures = GetMapMembers(objectIds, mapView);
ClearSelectionInTOC();
ClearSelection();
await SelectMapMembersInTOC(mapMembersFeatures).ConfigureAwait(false);
SelectMapMembersAndFeatures(mapMembersFeatures);
mapView.ZoomToSelected();
})
.ConfigureAwait(false);
}
private List<MapMemberFeature> GetMapMembers(IReadOnlyList<ObjectID> objectIds, MapView mapView)
@@ -180,7 +171,7 @@ public class BasicConnectorBinding : IBasicConnectorBinding
}
}
private void SelectMapMembersInTOC(IReadOnlyList<MapMemberFeature> mapMembersFeatures)
private async Task SelectMapMembersInTOC(IReadOnlyList<MapMemberFeature> mapMembersFeatures)
{
List<Layer> layers = new();
List<StandaloneTable> tables = new();
@@ -196,7 +187,7 @@ public class BasicConnectorBinding : IBasicConnectorBinding
}
else
{
layer.SetExpanded(true);
await QueuedTask.Run(() => layer.SetExpanded(true)).ConfigureAwait(false);
}
}
else if (member is StandaloneTable table)
@@ -20,7 +20,7 @@
<ArcGIS defaultAssembly="Speckle.Connectors.ArcGIS3.dll" defaultNamespace="Speckle.Connectors.ArcGIS" xmlns="http://schemas.esri.com/DADF/Registry" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.esri.com/DADF/Registry file:///C:/Program%20Files/ArcGIS/Pro/bin/ArcGIS.Desktop.Framework.xsd">
<AddInInfo id="{6CB1D25C-B8BF-4A33-9099-C1F8D1B32EFC}" version="1.0" desktopVersion="3.0.34047">
<Name>Speckle</Name>
<Description>Next Gen Speckle Connector (Beta) for ArcGIS</Description>
<Description>Speckle connector for ArcGIS</Description>
<Image>Images\AddinDesktop32.png</Image>
<Author>Speckle Systems</Author>
<Company>Speckle Systems</Company>
@@ -33,14 +33,14 @@
<insertModule id="ConnectorArcGIS_Module" className="SpeckleModule" autoLoad="false" caption="SpeckleModule">
<!-- uncomment to have the control hosted on a separate tab-->
<tabs>
<tab id="Speckle_Tab1" caption="Speckle">
<!--<tab id="Speckle_Tab1" caption="New Tab">
<group refID="Speckle_Group1"/>
</tab>
</tab>-->
</tabs>
<groups>
<!-- comment this out if you have no controls on the Addin tab to avoid
an empty group. change appearsOnAddinTab to "True" if control is to be in the addin tab-->
<group id="Speckle_Group1" caption="Speckle" appearsOnAddInTab="false" keytip="G1">
an empty group-->
<group id="Speckle_Group1" caption="Speckle" appearsOnAddInTab="true" keytip="G1">
<!-- host controls within groups -->
<button refID="SpeckleDUI3_SpeckleDUI3OpenButton" size="large" />
</group>
@@ -59,7 +59,7 @@
</controls>
<dockPanes>
<dockPane id="SpeckleDUI3_SpeckleDUI3" caption="Speckle (Beta)" className="SpeckleDUI3ViewModel" keytip="DockPane" initiallyVisible="true" dock="group" dockWith="esri_core_projectDockPane">
<dockPane id="SpeckleDUI3_SpeckleDUI3" caption="Speckle (Beta) for ArcGIS" className="SpeckleDUI3ViewModel" keytip="DockPane" initiallyVisible="true" dock="group" dockWith="esri_core_projectDockPane">
<content className="SpeckleDUI3Wrapper" />
</dockPane>
</dockPanes>
@@ -10,9 +10,9 @@ using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.WebView;
using Speckle.Converters.Common;
@@ -27,39 +27,42 @@ public static class ArcGISConnectorModule
{
public static void AddArcGIS(this IServiceCollection serviceCollection)
{
serviceCollection.AddConnectors();
serviceCollection.AddDUI<DefaultThreadContext, ArcGISDocumentStore>();
serviceCollection.AddConnectorUtils();
serviceCollection.AddDUI();
serviceCollection.AddDUIView();
serviceCollection.AddSingleton<DocumentModelStore, ArcGISDocumentStore>();
// Register bindings
serviceCollection.AddSingleton<IBinding, TestBinding>();
serviceCollection.AddSingleton<IBinding, ConfigBinding>();
serviceCollection.AddSingleton<IBinding, AccountBinding>();
serviceCollection.RegisterTopLevelExceptionHandler();
serviceCollection.AddSingleton<IBinding>(sp => sp.GetRequiredService<IBasicConnectorBinding>());
serviceCollection.AddSingleton<IBasicConnectorBinding, BasicConnectorBinding>();
serviceCollection.AddSingleton<IBinding, ArcGISSelectionBinding>();
serviceCollection.AddSingleton<IBinding, ArcGISSendBinding>();
serviceCollection.AddSingleton<IBinding, ArcGISReceiveBinding>();
serviceCollection.AddTransient<ISendFilter, ArcGISSelectionFilter>();
serviceCollection.AddScoped<IHostObjectBuilder, ArcGISHostObjectBuilder>();
serviceCollection.AddSingleton(DefaultTraversal.CreateTraversalFunc());
// register send operation and dependencies
serviceCollection.AddSingleton<IBinding, ArcGISSendBinding>();
serviceCollection.AddScoped<SendOperation<MapMember>>();
serviceCollection.AddSingleton<IBinding, ArcGISSelectionBinding>();
serviceCollection.AddTransient<ISendFilter, ArcGISSelectionFilter>();
serviceCollection.AddScoped<ArcGISRootObjectBuilder>();
serviceCollection.AddScoped<IRootObjectBuilder<MapMember>, ArcGISRootObjectBuilder>();
serviceCollection.AddScoped<ArcGISLayerUnpacker>();
serviceCollection.AddScoped<ArcGISColorUnpacker>();
serviceCollection.AddScoped<LocalToGlobalConverterUtils>();
serviceCollection.AddScoped<ArcGISColorManager>();
serviceCollection.AddScoped<MapMembersUtils>();
// register send conversion cache
serviceCollection.AddSingleton<ISendConversionCache, SendConversionCache>();
// register receive operation and dependencies
// serviceCollection.AddSingleton<IBinding, ArcGISReceiveBinding>(); // POC: disabled until receive code is refactored
serviceCollection.AddScoped<LocalToGlobalConverterUtils>();
serviceCollection.AddScoped<ArcGISColorManager>();
serviceCollection.AddScoped<IHostObjectBuilder, ArcGISHostObjectBuilder>();
serviceCollection.AddScoped<MapMembersUtils>();
// operation progress manager
serviceCollection.AddSingleton<IOperationProgressManager, OperationProgressManager>();
}
@@ -154,6 +154,7 @@
Log.LogErrorFromException(ex);
return false;
}
return Success;
]]>
</Code>
</Task>
@@ -189,7 +190,7 @@
{
Log.LogMessage(MessageImportance.Low, "RelativePaths: " + i.ToString());
}
Success = true;
return true;
]]>
</Code>
</Task>
@@ -265,6 +266,7 @@
CleanInfo = AssemblyName + extension;
}
Success = true;
return Success;
]]>
</Code>
</Task>
@@ -1,3 +0,0 @@
global using AC = ArcGIS.Core;
global using ACD = ArcGIS.Core.Data;
global using ADM = ArcGIS.Desktop.Mapping;
@@ -1,31 +1,62 @@
using System.Drawing;
using ArcGIS.Core.CIM;
using ArcGIS.Core.Data;
using ArcGIS.Desktop.Mapping;
using Speckle.Connectors.Common.Operations;
using Speckle.Converters.ArcGIS3.Utils;
using Speckle.Objects;
using Speckle.Objects.Other;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Extensions;
using Speckle.Sdk.Models.GraphTraversal;
using Speckle.Sdk.Models.Proxies;
namespace Speckle.Connectors.ArcGIS.HostApp;
/// <summary>
/// TODO: definitely need to refactor this, probably will collect colors during layer iteration in the root object builder.
/// </summary>
public class ArcGISColorManager
{
private Dictionary<string, ColorProxy> ColorProxies { get; set; } = new();
public Dictionary<string, Color> ObjectColorsIdMap { get; set; } = new();
public Dictionary<string, Color> ObjectMaterialsIdMap { get; set; } = new();
/// <summary>
/// Iterates through a given set of arcGIS map members (layers containing objects) and collects their colors.
/// </summary>
/// <param name="mapMembersWithDisplayPriority"></param>
/// <returns>A list of color proxies, where the application Id is argb value + display priority</returns>
/// <remarks>
/// In ArcGIS, map members contain a formula, which individual features contained in map members will use to calculate their color.
/// Since display priority is important for ArcGIS layers, we are creating different Color Proxies for eg the same argb color value but different display priority.
/// </remarks>
public List<ColorProxy> UnpackColors(List<(MapMember, int)> mapMembersWithDisplayPriority)
{
// injected as Singleton, so we need to clean existing proxies first
ColorProxies = new();
foreach ((MapMember mapMember, int priority) in mapMembersWithDisplayPriority)
{
switch (mapMember)
{
// FeatureLayer colors will be processed per feature object
case FeatureLayer featureLayer:
ProcessFeatureLayerColors(featureLayer, priority);
break;
// RasterLayer object colors are converted as mesh vertex colors, but we need to store displayPriority on the raster layer. Default color is used for all rasters.
case RasterLayer rasterLayer:
ProcessRasterLayerColors(rasterLayer, priority);
break;
}
}
return ColorProxies.Values.ToList();
}
/// <summary>
/// Parse Color Proxies and stores in ObjectColorsIdMap the relationship between object ids and colors
/// </summary>
/// <param name="colorProxies"></param>
/// <param name="onOperationProgressed"></param>
public void ParseColors(List<ColorProxy> colorProxies, IProgress<CardProgress> onOperationProgressed)
public async Task ParseColors(List<ColorProxy> colorProxies, IProgress<CardProgress> onOperationProgressed)
{
// injected as Singleton, so we need to clean existing proxies first
ObjectColorsIdMap = new();
@@ -33,6 +64,7 @@ public class ArcGISColorManager
foreach (ColorProxy colorProxy in colorProxies)
{
onOperationProgressed.Report(new("Converting colors", (double)++count / colorProxies.Count));
await Task.Yield();
foreach (string objectId in colorProxy.objects)
{
Color convertedColor = Color.FromArgb(colorProxy.value);
@@ -46,7 +78,10 @@ public class ArcGISColorManager
/// </summary>
/// <param name="materialProxies"></param>
/// <param name="onOperationProgressed"></param>
public void ParseMaterials(List<RenderMaterialProxy> materialProxies, IProgress<CardProgress> onOperationProgressed)
public async Task ParseMaterials(
List<RenderMaterialProxy> materialProxies,
IProgress<CardProgress> onOperationProgressed
)
{
// injected as Singleton, so we need to clean existing proxies first
ObjectMaterialsIdMap = new();
@@ -54,6 +89,7 @@ public class ArcGISColorManager
foreach (RenderMaterialProxy colorProxy in materialProxies)
{
onOperationProgressed.Report(new("Converting materials", (double)++count / materialProxies.Count));
await Task.Yield();
foreach (string objectId in colorProxy.objects)
{
Color convertedColor = Color.FromArgb(colorProxy.value.diffuse);
@@ -62,14 +98,6 @@ public class ArcGISColorManager
}
}
public int CIMColorToInt(CIMColor color)
{
return (255 << 24)
| ((int)Math.Round(color.Values[0]) << 16)
| ((int)Math.Round(color.Values[1]) << 8)
| (int)Math.Round(color.Values[2]);
}
/// <summary>
/// Create a new CIMUniqueValueClass for UniqueRenderer per each object ID
/// </summary>
@@ -83,7 +111,6 @@ public class ArcGISColorManager
{
// declare default white color
Color color = Color.FromArgb(255, 255, 255, 255);
bool colorFound = false;
// get color moving upwards from the object
foreach (var parent in tc.GetAscendants())
@@ -93,43 +120,16 @@ public class ArcGISColorManager
if (ObjectMaterialsIdMap.TryGetValue(appId, out Color objColorMaterial))
{
color = objColorMaterial;
colorFound = true;
break;
}
if (ObjectColorsIdMap.TryGetValue(appId, out Color objColor))
{
color = objColor;
colorFound = true;
break;
}
}
}
// handling Revit case, where child objects have separate colors/materials
if (!colorFound && tc.Current is IDataObject)
{
var displayable = tc.Current.TryGetDisplayValue();
if (displayable != null)
{
foreach (var childObj in displayable)
{
if (childObj.applicationId is string appId)
{
if (ObjectMaterialsIdMap.TryGetValue(appId, out Color objColorMaterial))
{
color = objColorMaterial;
break;
}
if (ObjectColorsIdMap.TryGetValue(appId, out Color objColor))
{
color = objColor;
break;
}
}
}
}
}
CIMSymbolReference symbol = CreateSymbol(speckleGeometryType, color);
// First create a "CIMUniqueValueClass"
@@ -196,7 +196,7 @@ public class ArcGISColorManager
}
// declare default grey color, create default symbol for the given layer geometry type
var color = Color.FromArgb(CIMColorToInt(ColorFactory.Instance.GreyRGB));
var color = Color.FromArgb(ColorFactory.Instance.GreyRGB.CIMColorToInt());
CIMSymbolReference defaultSymbol = CreateSymbol(fLayer.ShapeType, color);
// get existing renderer classes
@@ -227,10 +227,7 @@ public class ArcGISColorManager
foreach (var tContext in traversalContexts)
{
// get unique label
string? uniqueLabel = tContext.Current?.id;
// remove any GIS-specific classes for now
/*
string uniqueLabel = tContext.Current.id;
if (tContext.Current is IGisFeature gisFeat)
{
var existingLabel = gisFeat.attributes["Speckle_ID"];
@@ -238,9 +235,9 @@ public class ArcGISColorManager
{
uniqueLabel = stringLabel;
}
}*/
}
if (uniqueLabel is not null && !listUniqueValueClasses.Select(x => x.Label).Contains(uniqueLabel))
if (!listUniqueValueClasses.Select(x => x.Label).Contains(uniqueLabel))
{
CIMUniqueValueClass newUniqueValueClass = CreateColorCategory(tContext, fLayer.ShapeType, uniqueLabel);
listUniqueValueClasses.Add(newUniqueValueClass);
@@ -262,4 +259,376 @@ public class ArcGISColorManager
};
return uvr;
}
private string GetColorApplicationId(int argb, double order) => $"{argb}_{order}";
// Adds the element id to the color proxy based on colorId if it exists in ColorProxies,
// otherwise creates a new Color Proxy with the element id in the objects property
private void AddElementIdToColorProxy(string elementAppId, int colorValue, string colorId, int displayPriority)
{
if (ColorProxies.TryGetValue(colorId, out ColorProxy? colorProxy))
{
colorProxy.objects.Add(elementAppId);
}
else
{
ColorProxy newProxy =
new()
{
value = colorValue,
applicationId = colorId,
objects = new() { elementAppId },
name = colorId
};
newProxy["displayOrder"] = displayPriority; // 0 - top layer (top display priority), 1,2,3.. decreasing priority
ColorProxies.Add(colorId, newProxy);
}
}
private void ProcessRasterLayerColors(RasterLayer rasterLayer, int displayPriority)
{
string elementAppId = $"{rasterLayer.URI}_0"; // POC: explain why count = 0 here
int argb = -1;
string colorId = GetColorApplicationId(argb, displayPriority); // We are using a default color of -1 for all raster layers
AddElementIdToColorProxy(elementAppId, argb, colorId, displayPriority);
}
/// <summary>
/// Record colors from every feature of the layer into ColorProxies
/// </summary>
/// <param name="layer"></param>
/// <param name="displayPriority"></param>
private void ProcessFeatureLayerColors(FeatureLayer layer, int displayPriority)
{
// first get a list of layer fields
// field names are unique, but often their alias is used instead by renderer headings
// so we are storing both names and alieas in this dictionary for fast lookup
// POC: adding aliases are not optimal, because they do not need to be unique && they can be the same as the name of another field
Dictionary<string, FieldDescription> layerFieldDictionary = new();
foreach (FieldDescription field in layer.GetFieldDescriptions())
{
layerFieldDictionary.TryAdd(field.Name, field);
layerFieldDictionary.TryAdd(field.Alias, field);
}
CIMRenderer layerRenderer = layer.GetRenderer();
int count = 1;
using (RowCursor rowCursor = layer.Search())
{
// if layer doesn't have a valid data source (and the conversion likely failed), don't create a colorProxy
if (rowCursor is null)
{
return;
}
while (rowCursor.MoveNext())
{
string elementAppId = $"{layer.URI}_{count}";
using (Row row = rowCursor.Current)
{
// get row color
int argb = GetLayerColorByRendererAndRow(layerRenderer, row, layerFieldDictionary);
string colorId = GetColorApplicationId(argb, displayPriority);
AddElementIdToColorProxy(elementAppId, argb, colorId, displayPriority);
}
count++;
}
}
}
// Attempts to retrieve the color from a CIMSymbol
private bool TryGetSymbolColor(CIMSymbol symbol, out int symbolColor)
{
symbolColor = -1;
if (symbol.GetColor() is CIMColor cimColor)
{
switch (cimColor)
{
case CIMRGBColor rgbColor:
symbolColor = rgbColor.CIMColorToInt();
return true;
case CIMHSVColor hsvColor:
symbolColor = RgbFromHsv(hsvColor);
return true;
case CIMCMYKColor cmykColor:
symbolColor = RgbFromCmyk(cmykColor);
return true;
default:
return false;
}
}
else
{
return false;
}
}
private int RbgToInt(int a, int r, int g, int b)
{
return (a << 24) | (r << 16) | (g << 8) | b;
}
private int RgbFromCmyk(CIMCMYKColor cmykColor)
{
float c = cmykColor.C;
float m = cmykColor.M;
float y = cmykColor.Y;
float k = cmykColor.K;
int r = Convert.ToInt32(255 * (1 - c) * (1 - k));
int g = Convert.ToInt32(255 * (1 - m) * (1 - k));
int b = Convert.ToInt32(255 * (1 - y) * (1 - k));
return RbgToInt(255, r, g, b);
}
private int RgbFromHsv(CIMHSVColor hsvColor)
{
// Translates HSV color to RGB color
// H: 0.0 - 360.0, S: 0.0 - 100.0, V: 0.0 - 100.0
// R, G, B: 0.0 - 1.0
float hue = hsvColor.H;
float saturation = hsvColor.S;
float value = hsvColor.V;
float c = (value / 100) * (saturation / 100);
float x = c * (1 - Math.Abs(((hue / 60) % 2) - 1));
float m = (value / 100) - c;
float r = 0;
float g = 0;
float b = 0;
if (hue >= 0 && hue < 60)
{
r = c;
g = x;
b = 0;
}
else if (hue >= 60 && hue < 120)
{
r = x;
g = c;
b = 0;
}
else if (hue >= 120 && hue < 180)
{
r = 0;
g = c;
b = x;
}
else if (hue >= 180 && hue < 240)
{
r = 0;
g = x;
b = c;
}
else if (hue >= 240 && hue < 300)
{
r = x;
g = 0;
b = c;
}
else if (hue >= 300 && hue < 360)
{
r = c;
g = 0;
b = x;
}
r += m;
g += m;
b += m;
// convert rgb 0.0-1.0 float to int
int red = (int)Math.Round(r * 255);
int green = (int)Math.Round(g * 255);
int blue = (int)Math.Round(b * 255);
return RbgToInt(255, red, green, blue);
}
private bool TryGetUniqueRendererColor(
CIMUniqueValueRenderer uniqueRenderer,
Row row,
Dictionary<string, FieldDescription> fields,
out int color
)
{
if (uniqueRenderer.DefaultSymbol is null)
{
color = RbgToInt(255, 255, 255, 255);
return false;
}
if (!TryGetSymbolColor(uniqueRenderer.DefaultSymbol.Symbol, out color)) // get default color
{
return false;
}
// note: usually there is only 1 group
foreach (CIMUniqueValueGroup group in uniqueRenderer.Groups)
{
string[] fieldNames = uniqueRenderer.Fields;
List<string> usedFields = new();
foreach (string fieldName in fieldNames)
{
if (fields.TryGetValue(fieldName, out FieldDescription? headingField))
{
usedFields.Add(headingField.Name);
}
}
// loop through all values in groups to see if any have met conditions that result in a different color
foreach (CIMUniqueValueClass groupClass in group.Classes)
{
bool groupConditionsMet = true;
foreach (CIMUniqueValue value in groupClass.Values)
{
// all field values have to match the row values
for (int i = 0; i < usedFields.Count; i++)
{
string groupValue = value.FieldValues[i].Replace("<Null>", "");
object? rowValue = row[usedFields[i]];
(string newRowValue, string newGroupValue) = MakeValuesComparable(rowValue, groupValue);
if (newGroupValue != newRowValue)
{
groupConditionsMet = false;
break;
}
}
}
// set the group color to class symbol color if conditions are met
if (groupConditionsMet)
{
if (groupClass.Symbol is null)
{
color = RbgToInt(255, 255, 255, 255);
return false;
}
if (!TryGetSymbolColor(groupClass.Symbol.Symbol, out color))
{
return false;
}
}
}
}
return true;
}
/// <summary>
/// Make comparable the Label string of a UniqueValueRenderer (groupValue), and a Feature Attribute value (rowValue)
/// </summary>
/// <param name="rowValue"></param>
/// <param name="groupValue"></param>
private (string, string) MakeValuesComparable(object? rowValue, string groupValue)
{
string newGroupValue = groupValue;
string newRowValue = Convert.ToString(rowValue) ?? "";
// int, doubles are tricky to compare with strings, trimming both to 5 digits
if (rowValue is int or short or long)
{
newRowValue = newRowValue.Split(".")[0];
newGroupValue = newGroupValue.Split(".")[0];
}
else if (rowValue is double || rowValue is float)
{
newRowValue = string.Concat(
newRowValue.Split(".")[0],
".",
newRowValue.Split(".")[^1].AsSpan(0, Math.Min(5, newRowValue.Split(".")[^1].Length))
);
newGroupValue = string.Concat(
newGroupValue.Split(".")[0],
".",
newGroupValue.Split(".")[^1].AsSpan(0, Math.Min(5, newGroupValue.Split(".")[^1].Length))
);
}
return (newRowValue, newGroupValue);
}
private bool TryGetGraduatedRendererColor(
CIMClassBreaksRenderer graduatedRenderer,
Row row,
Dictionary<string, FieldDescription> fields,
out int color
)
{
if (graduatedRenderer.DefaultSymbol is null)
{
color = RbgToInt(255, 255, 255, 255);
return false;
}
if (!TryGetSymbolColor(graduatedRenderer.DefaultSymbol.Symbol, out color)) // get default color
{
return false;
}
string? usedField = null;
if (fields.TryGetValue(graduatedRenderer.Field, out FieldDescription? field))
{
usedField = field.Name;
}
List<CIMClassBreak> reversedBreaks = new(graduatedRenderer.Breaks);
reversedBreaks.Reverse();
foreach (var rBreak in reversedBreaks)
{
// keep looping until the last matching condition
if (Convert.ToDouble(row[usedField]) <= rBreak.UpperBound)
{
if (!TryGetSymbolColor(rBreak.Symbol.Symbol, out color)) // get default color
{
return false;
}
}
}
return true;
}
// Tries to retrieve the feature layer color by renderer and row, or a default color of -1
private int GetLayerColorByRendererAndRow(CIMRenderer renderer, Row row, Dictionary<string, FieldDescription> fields)
{
// default color to white. this will be used if the renderer is not supported.
int color = -1;
// get color depending on renderer type
switch (renderer)
{
case CIMSimpleRenderer simpleRenderer:
if (!TryGetSymbolColor(simpleRenderer.Symbol.Symbol, out color))
{
// POC: report CONVERTED WITH WARNING when implemented
}
break;
// unique renderers have groups of conditions that may affect the color of a feature
// resulting in a different color than the default renderer symbol color
case CIMUniqueValueRenderer uniqueRenderer:
if (!TryGetUniqueRendererColor(uniqueRenderer, row, fields, out color)) // get default color
{
// POC: report CONVERTED WITH WARNING when implemented
}
break;
case CIMClassBreaksRenderer graduatedRenderer:
if (!TryGetGraduatedRendererColor(graduatedRenderer, row, fields, out color)) // get default color
{
// POC: report CONVERTED WITH WARNING when implemented
}
break;
default:
// POC: report CONVERTED WITH WARNING when implemented, unsupported renderer e.g. CIMProportionalRenderer
break;
}
return color;
}
}
@@ -1,461 +0,0 @@
using ArcGIS.Desktop.Mapping;
using Speckle.Sdk.Models.Proxies;
namespace Speckle.Connectors.ArcGIS.HostApp;
public class ArcGISColorUnpacker
{
/// <summary>
/// Cache of all color proxies for converted features. Key is the Color proxy argb value.
/// </summary>
public Dictionary<int, ColorProxy> ColorProxyCache { get; } = new();
/// <summary>
/// Stores the current renderer (determined by mapMember)
/// </summary>
private AC.CIM.CIMRenderer? StoredRenderer { get; set; }
/// <summary>
/// Stores the current renderer (determined by tin mapmember)
/// </summary>
private AC.CIM.CIMTinRenderer? StoredTinRenderer { get; set; }
/// <summary>
/// Stores the used renderer fields from the layer
/// </summary>
private List<string> StoredRendererFields { get; set; }
/// <summary>
/// Stores an already processed color for current mapMember, to dbe used by all mapMember objects. Only applies to simple type renderers
/// </summary>
private int? StoredColor { get; set; }
/// <summary>
/// Stores a feature layer renderer to be used by <see cref="ProcessFeatureLayerColor"/> in <see cref="StoredRenderer"/>, any fields used by the renderer from the layer, and resets the <see cref="StoredColor"/> and <see cref="StoredRendererFields"/>
/// </summary>
/// <param name="featureLayer"></param>
/// <exception cref="AC.CalledOnWrongThreadException">Must be called on MCT.</exception>
public void StoreRendererAndFields(ADM.FeatureLayer featureLayer)
{
// field names are unique, but often their alias is used instead by renderer headings
// so we are storing both names and alias in this dictionary for fast lookup
// POC: adding aliases are not optimal, because they do not need to be unique && they can be the same as the name of another field
Dictionary<string, string> layerFieldDictionary = new();
foreach (ADM.FieldDescription field in featureLayer.GetFieldDescriptions())
{
layerFieldDictionary.TryAdd(field.Name, field.Name);
layerFieldDictionary.TryAdd(field.Alias, field.Name);
}
// clear stored values
StoredRendererFields = new();
StoredColor = null;
StoredRenderer = null;
AC.CIM.CIMRenderer layerRenderer = featureLayer.GetRenderer();
List<string> fields = new();
bool isSupported = false;
switch (layerRenderer)
{
case AC.CIM.CIMSimpleRenderer:
isSupported = true;
break;
case AC.CIM.CIMUniqueValueRenderer uniqueValueRenderer:
isSupported = true;
fields = uniqueValueRenderer.Fields.ToList();
break;
case AC.CIM.CIMClassBreaksRenderer classBreaksRenderer:
isSupported = true;
fields.Add(classBreaksRenderer.Field);
break;
default:
// TODO: log error here that a renderer is unsupported
break;
}
if (isSupported)
{
StoredRenderer = layerRenderer;
foreach (string field in fields)
{
if (layerFieldDictionary.TryGetValue(field, out string? fieldName))
{
StoredRendererFields.Add(fieldName);
}
}
}
}
/// <summary>
/// Stores a las layer renderer to be used by <see cref="ProcessLasLayerColor"/> in <see cref="StoredTinRenderer"/>
/// </summary>
/// <param name="lasLayer"></param>
/// <exception cref="AC.CalledOnWrongThreadException">Must be called on MCT.</exception>
public void StoreRenderer(ADM.LasDatasetLayer lasLayer)
{
// clear stored values
StoredTinRenderer = null;
// POC: not sure why we are only using the first renderer here
AC.CIM.CIMTinRenderer layerRenderer = lasLayer.GetRenderers()[0];
bool isSupported = false;
switch (layerRenderer)
{
case AC.CIM.CIMTinUniqueValueRenderer:
isSupported = true;
break;
default:
// TODO: log error here that a renderer is unsupported
break;
}
if (isSupported)
{
StoredTinRenderer = layerRenderer;
}
}
/// <summary>
/// Processes a las layer's point color by the stored <see cref="StoredRenderer"/>, and stores the point's id and color proxy to the <see cref="ColorProxyCache"/>.
/// POC: logic probably can be combined with ProcessFeatureLayerColor.
/// </summary>
/// <param name="point"></param>
public void ProcessLasLayerColor(ACD.Analyst3D.LasPoint point, string pointApplicationId)
{
// get the color from the renderer and point
AC.CIM.CIMColor? color;
switch (StoredTinRenderer)
{
case AC.CIM.CIMTinUniqueValueRenderer uniqueValueRenderer:
color = GetPointColorByUniqueValueRenderer(uniqueValueRenderer, point);
break;
default:
return;
}
// get or create the color proxy for the point
int argb = CIMColorToInt(color ?? point.RGBColor);
AddObjectIdToColorProxyCache(pointApplicationId, argb);
}
// Retrieves the las point color from a unique value renderer
// unique renderers have groups of conditions that may affect the color of a feature
// resulting in a different color than the default renderer symbol color
private AC.CIM.CIMColor? GetPointColorByUniqueValueRenderer(
AC.CIM.CIMTinUniqueValueRenderer renderer,
ACD.Analyst3D.LasPoint point
)
{
foreach (AC.CIM.CIMUniqueValueGroup group in renderer.Groups)
{
foreach (AC.CIM.CIMUniqueValueClass groupClass in group.Classes)
{
foreach (AC.CIM.CIMUniqueValue value in groupClass.Values)
{
// all field values have to match the row values
for (int i = 0; i < value.FieldValues.Length; i++)
{
string groupValue = value.FieldValues[i].Replace("<Null>", "");
object? pointValue = point.ClassCode;
if (ValuesAreEqual(groupValue, pointValue))
{
return groupClass.Symbol.Symbol.GetColor();
}
}
}
}
}
return null;
}
/// <summary>
/// Processes a feature layer's row color by the stored <see cref="StoredRenderer"/>, and stores the row's id and color proxy to the <see cref="ColorProxyCache"/>.
/// </summary>
/// <param name="row"></param>
/// <returns></returns>
/// <exception cref="ACD.Exceptions.GeodatabaseException"></exception>
public void ProcessFeatureLayerColor(ACD.Row row, string rowApplicationId)
{
// if stored color is not null, this means the renderer was a simple renderer that applies to the entire layer, and was already created.
// just add the row application id to the color proxy.
if (StoredColor is int existingColorProxyId)
{
AddObjectIdToColorProxyCache(rowApplicationId, existingColorProxyId);
return;
}
// get the color from the renderer and row
AC.CIM.CIMColor? color = null;
switch (StoredRenderer)
{
// simple renderers do not rely on fields, so the color can be retrieved from the renderer directly
case AC.CIM.CIMSimpleRenderer simpleRenderer:
color = simpleRenderer.Symbol.Symbol.GetColor();
break;
case AC.CIM.CIMUniqueValueRenderer uniqueValueRenderer:
color = GetRowColorByUniqueValueRenderer(uniqueValueRenderer, row);
break;
case AC.CIM.CIMClassBreaksRenderer classBreaksRenderer:
color = GetRowColorByClassBreaksRenderer(classBreaksRenderer, row);
break;
}
if (color is null)
{
// TODO: log error or throw exception that color could not be retrieved
return;
}
// get or create the color proxy for the row
int argb = CIMColorToInt(color);
AddObjectIdToColorProxyCache(rowApplicationId, argb);
// store color if from simple renderer
if (StoredRenderer is AC.CIM.CIMSimpleRenderer)
{
StoredColor = argb;
}
}
// Retrieves the row color from a class breaks renderer
// unique renderers have groups of conditions that may affect the color of a feature
// resulting in a different color than the default renderer symbol color
private AC.CIM.CIMColor? GetRowColorByClassBreaksRenderer(AC.CIM.CIMClassBreaksRenderer renderer, ACD.Row row)
{
AC.CIM.CIMColor? color = null;
// get the default symbol color
if (renderer.DefaultSymbol?.Symbol.GetColor() is AC.CIM.CIMColor defaultColor)
{
color = defaultColor;
}
// get the first stored field, since this renderer should only have 1 field
double storedFieldValue = Convert.ToDouble(row[StoredRendererFields.First()]);
List<AC.CIM.CIMClassBreak> reversedBreaks = new(renderer.Breaks);
reversedBreaks.Reverse();
foreach (var rBreak in reversedBreaks)
{
// keep looping until the last matching condition
if (storedFieldValue <= rBreak.UpperBound)
{
if (rBreak.Symbol.Symbol.GetColor() is AC.CIM.CIMColor breakColor)
{
color = breakColor;
}
else
{
// TODO: log error here, could not retrieve break color from symbol
}
}
}
return color;
}
// Retrieves the row color from a unique value renderer
// unique renderers have groups of conditions that may affect the color of a feature
// resulting in a different color than the default renderer symbol color
private AC.CIM.CIMColor? GetRowColorByUniqueValueRenderer(AC.CIM.CIMUniqueValueRenderer renderer, ACD.Row row)
{
AC.CIM.CIMColor? color = null;
// get the default symbol color
if (renderer.DefaultSymbol?.Symbol.GetColor() is AC.CIM.CIMColor defaultColor)
{
color = defaultColor;
}
// note: usually there is only 1 group
foreach (AC.CIM.CIMUniqueValueGroup group in renderer.Groups)
{
// loop through all values in groups to see if any have met conditions that result in a different color
foreach (AC.CIM.CIMUniqueValueClass groupClass in group.Classes)
{
bool groupConditionsMet = true;
foreach (AC.CIM.CIMUniqueValue value in groupClass.Values)
{
// all field values have to match the row values
for (int i = 0; i < StoredRendererFields.Count; i++)
{
string groupValue = value.FieldValues[i];
object? rowValue = row[StoredRendererFields[i]];
if (!ValuesAreEqual(groupValue, rowValue))
{
groupConditionsMet = false;
break;
}
}
}
// set the group color to class symbol color if conditions are met
if (groupConditionsMet)
{
if (groupClass.Symbol.Symbol.GetColor() is AC.CIM.CIMColor groupColor)
{
color = groupColor;
}
else
{
// TODO: log error here, could not retrieve group color from symbol
}
}
}
}
return color;
}
/// <summary>
/// Compares the label string of a UniqueValueRenderer (groupValue), and an object value (row, las point), to determine if they are equal
/// </summary>
/// <param name="objectValue"></param>
/// <param name="groupValue"></param>
private bool ValuesAreEqual(string groupValue, object? objectValue)
{
switch (objectValue)
{
case int:
case short:
case long:
case byte:
string objectValueString = Convert.ToString(objectValue) ?? "";
return groupValue.Equals(objectValueString);
case string:
return groupValue.Equals(objectValue);
// POC: these are tricky to compare with the label strings accurately, so will trim both values to 5 decimal places.
case double d:
return double.TryParse(groupValue, out double groupDouble) && groupDouble - d < 0.000001;
case float f:
return float.TryParse(groupValue, out float groupFloat) && groupFloat - f < 0.000001;
default:
return false;
}
}
private void AddObjectIdToColorProxyCache(string objectId, int argb)
{
if (ColorProxyCache.TryGetValue(argb, out ColorProxy? colorProxy))
{
colorProxy.objects.Add(objectId);
}
else
{
ColorProxy newColorProxy =
new()
{
name = argb.ToString(),
objects = new() { objectId },
value = argb,
applicationId = argb.ToString()
};
ColorProxyCache.Add(argb, newColorProxy);
}
}
private int ArgbToInt(int a, int r, int g, int b)
{
return (a << 24) | (r << 16) | (g << 8) | b;
}
// Gets the argb int from a CIMColor
// Defaults to assuming CIMColor.Values represent the red, green, and blue channels.
private int CIMColorToInt(AC.CIM.CIMColor color)
{
switch (color)
{
case AC.CIM.CIMHSVColor hsv:
(float hsvR, float hsvG, float hsvB) = RgbFromHsv(hsv.H, hsv.S, hsv.V);
return ArgbToInt(
(int)Math.Round(hsv.Alpha),
(int)Math.Round(hsvR * 255),
(int)Math.Round(hsvG * 255),
(int)Math.Round(hsvB * 255)
);
case AC.CIM.CIMCMYKColor cmyk:
float k = cmyk.K;
int cmykR = Convert.ToInt32(255 * (1 - cmyk.C) * (1 - k));
int cmykG = Convert.ToInt32(255 * (1 - cmyk.M) * (1 - k));
int cmykB = Convert.ToInt32(255 * (1 - cmyk.Y) * (1 - k));
return ArgbToInt((int)Math.Round(cmyk.Alpha), cmykR, cmykG, cmykB);
default:
return ArgbToInt(
(int)Math.Round(color.Alpha),
(int)Math.Round(color.Values[0]),
(int)Math.Round(color.Values[1]),
(int)Math.Round(color.Values[2])
);
}
}
private (float, float, float) RgbFromHsv(float hue, float saturation, float value)
{
// Translates HSV color to RGB color
// H: 0.0 - 360.0, S: 0.0 - 100.0, V: 0.0 - 100.0
// R, G, B: 0.0 - 1.0
float c = (value / 100) * (saturation / 100);
float x = c * (1 - Math.Abs(((hue / 60) % 2) - 1));
float m = (value / 100) - c;
float r = 0;
float g = 0;
float b = 0;
if (hue >= 0 && hue < 60)
{
r = c;
g = x;
b = 0;
}
else if (hue >= 60 && hue < 120)
{
r = x;
g = c;
b = 0;
}
else if (hue >= 120 && hue < 180)
{
r = 0;
g = c;
b = x;
}
else if (hue >= 180 && hue < 240)
{
r = 0;
g = x;
b = c;
}
else if (hue >= 240 && hue < 300)
{
r = x;
g = 0;
b = c;
}
else if (hue >= 300 && hue < 360)
{
r = c;
g = 0;
b = x;
}
r += m;
g += m;
b += m;
return (r, g, b);
}
}
@@ -1,97 +0,0 @@
using Speckle.Connectors.ArcGIS.HostApp.Extensions;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.ArcGIS.HostApp;
public class ArcGISLayerUnpacker
{
/// <summary>
/// Cache of all collections created by unpacked Layer MapMembers. Key is the Speckle applicationId (Layer URI).
/// </summary>
public Dictionary<string, Collection> CollectionCache { get; } = new();
/// <summary>
/// Mapmembers can be layers containing objects, or LayerContainers containing other layers.
/// Unpacks selected mapMembers and creates their corresponding collection on the root collection.
/// </summary>
/// <param name="mapMembers"></param>
/// <param name="parentCollection"></param>
/// <returns>List of layers containing objects.</returns>
/// <exception cref="AC.CalledOnWrongThreadException">Thrown when this method is *not* called on the MCT, because this method accesses mapmember fields</exception>
public List<ADM.MapMember> UnpackSelection(
IEnumerable<ADM.MapMember> mapMembers,
Collection parentCollection,
List<ADM.MapMember>? objects = null
)
{
if (objects is null)
{
objects = new();
}
foreach (ADM.MapMember mapMember in mapMembers)
{
switch (mapMember)
{
case ADM.ILayerContainer container:
Collection containerCollection = CreateAndCacheMapMemberCollection(mapMember, true);
parentCollection.elements.Add(containerCollection);
UnpackSelection(container.Layers, containerCollection, objects);
break;
default:
if (!(objects.Contains(mapMember)))
{
Collection collection = CreateAndCacheMapMemberCollection(mapMember);
parentCollection.elements.Add(collection);
objects.Add(mapMember);
}
break;
}
}
return objects;
}
private Collection CreateAndCacheMapMemberCollection(ADM.MapMember mapMember, bool isLayerContainer = false)
{
string mapMemberApplicationId = mapMember.GetSpeckleApplicationId();
Collection collection =
new()
{
name = mapMember.Name,
applicationId = mapMemberApplicationId,
["type"] = mapMember.GetType().Name
};
switch (mapMember)
{
case ADM.IDisplayTable displayTable: // get fields from layers that implement IDisplayTable, eg FeatureLayer or StandaloneTable
Dictionary<string, string>? fields = displayTable
.GetFieldDescriptions()
.ToDictionary(field => field.Name, field => field.Type.ToString());
collection["fields"] = fields;
if (mapMember is ADM.BasicFeatureLayer basicFeatureLayer)
{
collection["shapeType"] = basicFeatureLayer.ShapeType.ToString();
}
break;
case ADM.Layer layer:
collection["mapLayerType"] = layer.MapLayerType.ToString();
break;
case ADM.ILayerContainer:
default:
break;
}
if (!isLayerContainer) // do not cache layer containers, since these won't contain any objects
{
CollectionCache.Add(mapMemberApplicationId, collection);
}
return collection;
}
}
@@ -1,31 +0,0 @@
using ArcGIS.Core.Data.Raster;
namespace Speckle.Connectors.ArcGIS.HostApp.Extensions;
public static class SpeckleApplicationIdExtensions
{
/// <summary>
/// Retrieves the Speckle application id for map members
/// </summary>
public static string GetSpeckleApplicationId(this ADM.MapMember mapMember) => mapMember.URI;
/// <summary>
/// Constructs the Speckle application id for Features as a concatenation of the layer URI (applicationId)
/// and the row OID (index of row in layer).
/// </summary>
/// <exception cref="ACD.Exceptions.GeodatabaseException">Throws when this is *not* called on MCT. Use QueuedTask.Run.</exception>
public static string GetSpeckleApplicationId(this ACD.Row row, string layerApplicationId) =>
$"{layerApplicationId}_{row.GetObjectID()}";
/// <summary>
/// Constructs the Speckle application id for Raster as a concatenation of the layer URI (applicationId) and 0-index
/// </summary>
public static string GetSpeckleApplicationId(this Raster _, string layerApplicationId) => $"{layerApplicationId}_0";
/// <summary>
/// Constructs the Speckle application id for LasDatasets as a concatenation of the layer URI (applicationId)
/// and point OID.
/// </summary>
public static string GetSpeckleApplicationId(this ACD.Analyst3D.LasPoint point, string layerApplicationId) =>
$"{layerApplicationId}_{point.PointID}";
}
@@ -12,7 +12,7 @@ using Speckle.Connectors.Common.Operations;
using Speckle.Converters.ArcGIS3;
using Speckle.Converters.ArcGIS3.Utils;
using Speckle.Converters.Common;
using Speckle.Objects.Data;
using Speckle.Objects.GIS;
using Speckle.Objects.Other;
using Speckle.Sdk;
using Speckle.Sdk.Models;
@@ -20,6 +20,7 @@ using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.GraphTraversal;
using Speckle.Sdk.Models.Instances;
using Speckle.Sdk.Models.Proxies;
using RasterLayer = Speckle.Objects.GIS.RasterLayer;
namespace Speckle.Connectors.ArcGIS.Operations.Receive;
@@ -29,6 +30,7 @@ public class ArcGISHostObjectBuilder : IHostObjectBuilder
private readonly IFeatureClassUtils _featureClassUtils;
private readonly ILocalToGlobalUnpacker _localToGlobalUnpacker;
private readonly LocalToGlobalConverterUtils _localToGlobalConverterUtils;
private readonly ICrsUtils _crsUtils;
// POC: figure out the correct scope to only initialize on Receive
private readonly IConverterSettingsStore<ArcGISConversionSettings> _settingsStore;
@@ -41,6 +43,7 @@ public class ArcGISHostObjectBuilder : IHostObjectBuilder
IFeatureClassUtils featureClassUtils,
ILocalToGlobalUnpacker localToGlobalUnpacker,
LocalToGlobalConverterUtils localToGlobalConverterUtils,
ICrsUtils crsUtils,
GraphTraversal traverseFunction,
ArcGISColorManager colorManager
)
@@ -52,22 +55,10 @@ public class ArcGISHostObjectBuilder : IHostObjectBuilder
_localToGlobalConverterUtils = localToGlobalConverterUtils;
_traverseFunction = traverseFunction;
_colorManager = colorManager;
_crsUtils = crsUtils;
}
public Task<HostObjectBuilderResult> Build(
Base rootObject,
string projectName,
string modelName,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
{
return QueuedTask.Run(
() => BuildInternal(rootObject, projectName, modelName, onOperationProgressed, cancellationToken)
);
}
private HostObjectBuilderResult BuildInternal(
public async Task<HostObjectBuilderResult> Build(
Base rootObject,
string projectName,
string modelName,
@@ -87,18 +78,18 @@ public class ArcGISHostObjectBuilder : IHostObjectBuilder
.ToList();
if (materials != null)
{
_colorManager.ParseMaterials(materials, onOperationProgressed);
await _colorManager.ParseMaterials(materials, onOperationProgressed).ConfigureAwait(false);
}
// get colors
List<ColorProxy>? colors = (rootObject[ProxyKeys.COLOR] as List<object>)?.Cast<ColorProxy>().ToList();
if (colors != null)
{
_colorManager.ParseColors(colors, onOperationProgressed);
await _colorManager.ParseColors(colors, onOperationProgressed).ConfigureAwait(false);
}
int count = 0;
IReadOnlyCollection<LocalToGlobalMap> objectsToConvert = GetObjectsToConvert(rootObject);
List<LocalToGlobalMap> objectsToConvert = GetObjectsToConvert(rootObject);
Dictionary<TraversalContext, ObjectConversionTracker> conversionTracker = new();
// 1. convert everything
@@ -113,15 +104,13 @@ public class ArcGISHostObjectBuilder : IHostObjectBuilder
try
{
obj = _localToGlobalConverterUtils.TransformObjects(objectToConvert.AtomicObject, objectToConvert.Matrix);
object conversionResult = _converter.Convert(obj);
object? conversionResult =
obj is GisNonGeometricFeature
? null
: await QueuedTask.Run(() => _converter.Convert(obj)).ConfigureAwait(false);
string nestedLayerPath = $"{string.Join("\\", path)}";
if (obj is ArcgisObject gisObj)
{
nestedLayerPath += $"\\{gisObj.name}";
}
else
if (objectToConvert.TraversalContext.Parent?.Current is not VectorLayer)
{
nestedLayerPath += $"\\{obj.speckle_type.Split(".")[^1]}"; // add sub-layer by speckleType, for non-GIS objects
}
@@ -141,20 +130,29 @@ public class ArcGISHostObjectBuilder : IHostObjectBuilder
// 2.1. Group conversionTrackers (to write into datasets)
onOperationProgressed.Report(new("Grouping features into layers", null));
Dictionary<string, List<(TraversalContext, ObjectConversionTracker)>> convertedGroups =
_featureClassUtils.GroupConversionTrackers(
conversionTracker,
(s, progres) => onOperationProgressed.Report(new(s, progres))
);
Dictionary<string, List<(TraversalContext, ObjectConversionTracker)>> convertedGroups = await QueuedTask
.Run(async () =>
{
return await _featureClassUtils
.GroupConversionTrackers(conversionTracker, (s, progres) => onOperationProgressed.Report(new(s, progres)))
.ConfigureAwait(false);
})
.ConfigureAwait(false);
// 2.2. Write groups of objects to Datasets
onOperationProgressed.Report(new("Writing to Database", null));
_featureClassUtils.CreateDatasets(
conversionTracker,
convertedGroups,
(s, progres) => onOperationProgressed.Report(new(s, progres))
);
await QueuedTask
.Run(async () =>
{
await _featureClassUtils
.CreateDatasets(
conversionTracker,
convertedGroups,
(s, progres) => onOperationProgressed.Report(new(s, progres))
)
.ConfigureAwait(false);
})
.ConfigureAwait(false);
// 3. add layer and tables to the Map and Table Of Content
@@ -206,7 +204,8 @@ public class ArcGISHostObjectBuilder : IHostObjectBuilder
else
{
// no layer yet, create and add layer to Map
MapMember mapMember = AddDatasetsToMap(trackerItem, createdLayerGroups, projectName, modelName);
MapMember mapMember = await AddDatasetsToMap(trackerItem, createdLayerGroups, projectName, modelName)
.ConfigureAwait(false);
// add layer and layer URI to tracker
trackerItem.AddConvertedMapMember(mapMember);
@@ -234,20 +233,24 @@ public class ArcGISHostObjectBuilder : IHostObjectBuilder
if (bakedMember.Value.Item1 is FeatureLayer fLayer)
{
// Set the feature layer's renderer.
fLayer.SetRenderer(bakedMember.Value.Item2);
await QueuedTask.Run(() => fLayer.SetRenderer(bakedMember.Value.Item2)).ConfigureAwait(false);
}
}
bakedObjectIds.AddRange(createdLayerGroups.Values.Select(x => x.URI));
// TODO: validated a correct set regarding bakedobject ids
return new HostObjectBuilderResult(bakedObjectIds, results);
return new(bakedObjectIds, results);
}
private IReadOnlyCollection<LocalToGlobalMap> GetObjectsToConvert(Base rootObject)
private List<LocalToGlobalMap> GetObjectsToConvert(Base rootObject)
{
// keep GISlayers in the list, because they are still needed to extract CRS of the commit (code below)
List<TraversalContext> objectsToConvertTc = _traverseFunction.Traverse(rootObject).ToList();
// get CRS from any present VectorLayer
Base? vLayer = objectsToConvertTc.FirstOrDefault(x => x.Current is VectorLayer)?.Current;
using var crs = _crsUtils.FindSetCrsDataOnReceive(vLayer); // TODO help
// now filter the objects
objectsToConvertTc = objectsToConvertTc.Where(ctx => ctx.Current is not Collection).ToList();
@@ -301,72 +304,80 @@ public class ArcGISHostObjectBuilder : IHostObjectBuilder
}
}
private MapMember AddDatasetsToMap(
private async Task<MapMember> AddDatasetsToMap(
ObjectConversionTracker trackerItem,
Dictionary<string, GroupLayer> createdLayerGroups,
string projectName,
string modelName
)
{
// get layer details
string? datasetId = trackerItem.DatasetId; // should not be null here
Uri uri = new($"{_settingsStore.Current.SpeckleDatabasePath.AbsolutePath.Replace('/', '\\')}\\{datasetId}");
string nestedLayerName = trackerItem.NestedLayerName;
// add group for the current layer
string shortName = nestedLayerName.Split("\\")[^1];
string nestedLayerPath = string.Join("\\", nestedLayerName.Split("\\").SkipLast(1));
// if no general group layer found
if (createdLayerGroups.Count == 0)
{
Map map = _settingsStore.Current.Map;
GroupLayer mainGroupLayer = LayerFactory.Instance.CreateGroupLayer(map, 0, $"{projectName}: {modelName}");
mainGroupLayer.SetExpanded(true);
createdLayerGroups["Basic Speckle Group"] = mainGroupLayer; // key doesn't really matter here
}
var groupLayer = CreateNestedGroupLayer(nestedLayerPath, createdLayerGroups);
// Most of the Speckle-written datasets will be containing geometry and added as Layers
// although, some datasets might be just tables (e.g. native GIS Tables, in the future maybe Revit schedules etc.
// We can create a connection to the dataset in advance and determine its type, but this will be more
// expensive, than assuming by default that it's a layer with geometry (which in most cases it's expected to be)
try
{
var layer = LayerFactory.Instance.CreateLayer(uri, groupLayer, layerName: shortName);
if (layer == null)
return await QueuedTask
.Run(() =>
{
throw new SpeckleException($"Layer '{shortName}' was not created");
}
layer.SetExpanded(false);
// get layer details
string? datasetId = trackerItem.DatasetId; // should not be null here
Uri uri = new($"{_settingsStore.Current.SpeckleDatabasePath.AbsolutePath.Replace('/', '\\')}\\{datasetId}");
string nestedLayerName = trackerItem.NestedLayerName;
// if Scene
// https://community.esri.com/t5/arcgis-pro-sdk-questions/sdk-equivalent-to-changing-layer-s-elevation/td-p/1346139
if (_settingsStore.Current.Map.IsScene)
{
var groundSurfaceLayer = _settingsStore.Current.Map.GetGroundElevationSurfaceLayer();
var layerElevationSurface = new CIMLayerElevationSurface { ElevationSurfaceLayerURI = groundSurfaceLayer.URI, };
// add group for the current layer
string shortName = nestedLayerName.Split("\\")[^1];
string nestedLayerPath = string.Join("\\", nestedLayerName.Split("\\").SkipLast(1));
// for Feature Layers
if (layer.GetDefinition() is CIMFeatureLayer cimLyr)
// if no general group layer found
if (createdLayerGroups.Count == 0)
{
cimLyr.LayerElevation = layerElevationSurface;
layer.SetDefinition(cimLyr);
Map map = _settingsStore.Current.Map;
GroupLayer mainGroupLayer = LayerFactory.Instance.CreateGroupLayer(map, 0, $"{projectName}: {modelName}");
mainGroupLayer.SetExpanded(true);
createdLayerGroups["Basic Speckle Group"] = mainGroupLayer; // key doesn't really matter here
}
}
return layer;
}
catch (ArgumentException)
{
StandaloneTable table = StandaloneTableFactory.Instance.CreateStandaloneTable(
uri,
groupLayer,
tableName: shortName
);
return table;
}
var groupLayer = CreateNestedGroupLayer(nestedLayerPath, createdLayerGroups);
// Most of the Speckle-written datasets will be containing geometry and added as Layers
// although, some datasets might be just tables (e.g. native GIS Tables, in the future maybe Revit schedules etc.
// We can create a connection to the dataset in advance and determine its type, but this will be more
// expensive, than assuming by default that it's a layer with geometry (which in most cases it's expected to be)
try
{
var layer = LayerFactory.Instance.CreateLayer(uri, groupLayer, layerName: shortName);
if (layer == null)
{
throw new SpeckleException($"Layer '{shortName}' was not created");
}
layer.SetExpanded(false);
// if Scene
// https://community.esri.com/t5/arcgis-pro-sdk-questions/sdk-equivalent-to-changing-layer-s-elevation/td-p/1346139
if (_settingsStore.Current.Map.IsScene)
{
var groundSurfaceLayer = _settingsStore.Current.Map.GetGroundElevationSurfaceLayer();
var layerElevationSurface = new CIMLayerElevationSurface
{
ElevationSurfaceLayerURI = groundSurfaceLayer.URI,
};
// for Feature Layers
if (layer.GetDefinition() is CIMFeatureLayer cimLyr)
{
cimLyr.LayerElevation = layerElevationSurface;
layer.SetDefinition(cimLyr);
}
}
return (MapMember)layer;
}
catch (ArgumentException)
{
StandaloneTable table = StandaloneTableFactory.Instance.CreateStandaloneTable(
uri,
groupLayer,
tableName: shortName
);
return table;
}
})
.ConfigureAwait(false);
}
private GroupLayer CreateNestedGroupLayer(string nestedLayerPath, Dictionary<string, GroupLayer> createdLayerGroups)
@@ -408,4 +419,17 @@ public class ArcGISHostObjectBuilder : IHostObjectBuilder
var originalPath = reverseOrderPath.Reverse().ToArray();
return originalPath.Where(x => !string.IsNullOrEmpty(x)).ToArray();
}
[Pure]
private static bool HasGISParent(TraversalContext context)
{
List<Base> gisLayers = context.GetAscendants().Where(IsGISType).Where(obj => obj != context.Current).ToList();
return gisLayers.Count > 0;
}
[Pure]
private static bool IsGISType(Base obj)
{
return obj is RasterLayer or VectorLayer;
}
}
@@ -1,207 +1,189 @@
using ArcGIS.Core.Data.Raster;
using ArcGIS.Core.Geometry;
using System.Diagnostics;
using ArcGIS.Desktop.Framework.Threading.Tasks;
using ArcGIS.Desktop.Mapping;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.ArcGIS.HostApp;
using Speckle.Connectors.ArcGIS.HostApp.Extensions;
using Speckle.Connectors.ArcGIS.Utils;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.Common.Operations;
using Speckle.Converters.ArcGIS3;
using Speckle.Converters.Common;
using Speckle.Objects.GIS;
using Speckle.Sdk;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Proxies;
namespace Speckle.Connectors.ArcGis.Operations.Send;
/// <summary>
/// Stateless builder object to turn an ISendFilter into a <see cref="Base"/> object
/// </summary>
public class ArcGISRootObjectBuilder : IRootObjectBuilder<ADM.MapMember>
public class ArcGISRootObjectBuilder : IRootObjectBuilder<MapMember>
{
private readonly IRootToSpeckleConverter _rootToSpeckleConverter;
private readonly ArcGISLayerUnpacker _layerUnpacker;
private readonly ArcGISColorUnpacker _colorUnpacker;
private readonly ISendConversionCache _sendConversionCache;
private readonly ArcGISColorManager _colorManager;
private readonly IConverterSettingsStore<ArcGISConversionSettings> _converterSettings;
private readonly MapMembersUtils _mapMemberUtils;
private readonly ILogger<ArcGISRootObjectBuilder> _logger;
private readonly ISdkActivityFactory _activityFactory;
private readonly MapMembersUtils _mapMemberUtils;
public ArcGISRootObjectBuilder(
ArcGISLayerUnpacker layerUnpacker,
ArcGISColorUnpacker colorUnpacker,
ISendConversionCache sendConversionCache,
ArcGISColorManager colorManager,
IConverterSettingsStore<ArcGISConversionSettings> converterSettings,
IRootToSpeckleConverter rootToSpeckleConverter,
MapMembersUtils mapMemberUtils,
ILogger<ArcGISRootObjectBuilder> logger,
ISdkActivityFactory activityFactory,
MapMembersUtils mapMemberUtils
ISdkActivityFactory activityFactory
)
{
_layerUnpacker = layerUnpacker;
_colorUnpacker = colorUnpacker;
_sendConversionCache = sendConversionCache;
_colorManager = colorManager;
_converterSettings = converterSettings;
_rootToSpeckleConverter = rootToSpeckleConverter;
_mapMemberUtils = mapMemberUtils;
_logger = logger;
_activityFactory = activityFactory;
_mapMemberUtils = mapMemberUtils;
}
public Task<RootObjectBuilderResult> Build(
IReadOnlyList<ADM.MapMember> layers,
SendInfo __,
#pragma warning disable CA1506
public async Task<RootObjectBuilderResult> Build(
#pragma warning restore CA1506
IReadOnlyList<MapMember> objects,
SendInfo sendInfo,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
) => QueuedTask.Run(() => BuildInternal(layers, __, onOperationProgressed, cancellationToken));
private async Task<RootObjectBuilderResult> BuildInternal(
IReadOnlyList<ADM.MapMember> layers,
SendInfo __,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
CancellationToken ct = default
)
{
// TODO: add a warning if Geographic CRS is set
// "Data has been sent in the units 'degrees'. It is advisable to set the project CRS to Projected type (e.g. EPSG:32631) to be able to receive geometry correctly in CAD/BIM software"
int count = 0;
// 0 - Create Root collection and attach CRS properties
// CRS properties are useful for data based workflows coming out of gis applications
SpatialReference sr = _converterSettings.Current.ActiveCRSoffsetRotation.SpatialReference;
Dictionary<string, object?> spatialReference =
new()
{
["name"] = sr.Name,
["unit"] = sr.Unit.Name,
["wkid"] = sr.Wkid,
["wkt"] = sr.Wkt,
};
Collection rootObjectCollection = new() { name = MapView.Active.Map.Name }; //TODO: Collections
rootObjectCollection["units"] = _converterSettings.Current.SpeckleUnits;
Dictionary<string, object?> crs =
new()
{
["trueNorthRadians"] = _converterSettings.Current.ActiveCRSoffsetRotation.TrueNorthRadians,
["latOffset"] = _converterSettings.Current.ActiveCRSoffsetRotation.LatOffset,
["lonOffset"] = _converterSettings.Current.ActiveCRSoffsetRotation.LonOffset,
["spatialReference"] = spatialReference
};
List<SendConversionResult> results = new(objects.Count);
var cacheHitCount = 0;
List<(ILayerContainer, Collection)> nestedGroups = new();
Collection rootCollection =
new()
{
name = ADM.MapView.Active.Map.Name,
["units"] = _converterSettings.Current.SpeckleUnits,
["crs"] = crs
};
// reorder selected layers by Table of Content (TOC) order
List<(MapMember, int)> layersWithDisplayPriority = _mapMemberUtils.GetLayerDisplayPriority(
MapView.Active.Map,
objects
);
// 1 - Unpack the selected mapmembers
// In Arcgis, mapmembers are collections of other mapmember or objects.
// We need to unpack the selected mapmembers into all leaf-level mapmembers (containing just objects) and build the root collection structure during unpacking.
// Mapmember dynamically attached properties are also added at this step.
List<ADM.MapMember> unpackedLayers;
Dictionary<ADM.MapMember, long> layersWithFeatureCount;
long allFeaturesCount;
ADM.Map map = ADM.MapView.Active.Map;
IEnumerable<ADM.MapMember> layersOrdered = _mapMemberUtils.GetMapMembersInOrder(map, layers);
using (var _ = _activityFactory.Start("Unpacking selection"))
{
unpackedLayers = _layerUnpacker.UnpackSelection(layersOrdered, rootCollection);
// count number of features to convert. Raster layers are counter as 1 feature for now (not ideal)
layersWithFeatureCount = CountAllFeaturesInLayers(unpackedLayers);
allFeaturesCount = layersWithFeatureCount.Values.Sum();
}
List<SendConversionResult> results = new(unpackedLayers.Count);
onOperationProgressed.Report(new("Converting", null));
using (var convertingActivity = _activityFactory.Start("Converting objects"))
using (var __ = _activityFactory.Start("Converting objects"))
{
long count = 0;
foreach (var (layer, layerFeatureCount) in layersWithFeatureCount)
foreach ((MapMember mapMember, _) in layersWithDisplayPriority)
{
cancellationToken.ThrowIfCancellationRequested();
string layerApplicationId = layer.GetSpeckleApplicationId();
ct.ThrowIfCancellationRequested();
try
using (var convertingActivity = _activityFactory.Start("Converting object"))
{
// get the corresponding collection for this layer - we'll add all converted objects to the collection
if (_layerUnpacker.CollectionCache.TryGetValue(layerApplicationId, out Collection? layerCollection))
var collectionHost = rootObjectCollection;
string applicationId = mapMember.URI;
string sourceType = mapMember.GetType().Name;
Base converted;
try
{
var status = Status.SUCCESS;
var sdkStatus = SdkActivityStatusCode.Ok;
// TODO: check cache first to see if this layer was previously converted
/*
if (_sendConversionCache.TryGetValue(
sendInfo.ProjectId,
layerApplicationId,
out ObjectReference? value
))
int groupCount = nestedGroups.Count; // bake here, because count will change in the loop
// if the layer is not a part of the group, reset groups
for (int i = 0; i < groupCount; i++)
{
}
*/
switch (layer)
{
case ADM.FeatureLayer featureLayer:
List<Base> convertedFeatureLayerObjects = ConvertFeatureLayerObjects(
featureLayer,
count,
allFeaturesCount,
onOperationProgressed,
cancellationToken
);
layerCollection.elements.AddRange(convertedFeatureLayerObjects);
break;
case ADM.RasterLayer rasterLayer:
List<Base> convertedRasterLayerObjects = ConvertRasterLayerObjects(
rasterLayer,
count,
allFeaturesCount,
onOperationProgressed,
cancellationToken
);
layerCollection.elements.AddRange(convertedRasterLayerObjects);
break;
case ADM.LasDatasetLayer lasDatasetLayer:
List<Base> convertedLasDatasetObjects = ConvertLasDatasetLayerObjects(
lasDatasetLayer,
count,
allFeaturesCount,
onOperationProgressed,
cancellationToken
);
layerCollection.elements.AddRange(convertedLasDatasetObjects);
break;
default:
status = Status.ERROR;
sdkStatus = SdkActivityStatusCode.Error;
if (nestedGroups.Count > 0 && !nestedGroups[0].Item1.Layers.Select(x => x.URI).Contains(applicationId))
{
nestedGroups.RemoveAt(0);
}
else
{
// break at the first group, which contains current layer
break;
}
}
count += layerFeatureCount;
results.Add(new(status, layerApplicationId, layer.GetType().Name, layerCollection));
convertingActivity?.SetStatus(sdkStatus);
// don't use cache for group layers
if (
mapMember is not ILayerContainer
&& _sendConversionCache.TryGetValue(sendInfo.ProjectId, applicationId, out ObjectReference? value)
)
{
converted = value;
cacheHitCount++;
}
else
{
if (mapMember is ILayerContainer group)
{
// group layer will always come before it's contained layers
// keep active group last in the list
converted = new Collection();
nestedGroups.Insert(0, (group, (Collection)converted));
}
else
{
converted = await QueuedTask
.Run(() => (Collection)_rootToSpeckleConverter.Convert(mapMember))
.ConfigureAwait(false);
// get units & Active CRS (for writing geometry coords)
converted["units"] = _converterSettings.Current.SpeckleUnits;
var spatialRef = _converterSettings.Current.ActiveCRSoffsetRotation.SpatialReference;
converted["crs"] = new CRS
{
wkt = spatialRef.Wkt,
name = spatialRef.Name,
offset_y = Convert.ToSingle(_converterSettings.Current.ActiveCRSoffsetRotation.LatOffset),
offset_x = Convert.ToSingle(_converterSettings.Current.ActiveCRSoffsetRotation.LonOffset),
rotation = Convert.ToSingle(_converterSettings.Current.ActiveCRSoffsetRotation.TrueNorthRadians),
units_native = _converterSettings.Current.SpeckleUnits
};
}
// other common properties for layers and groups
converted["name"] = mapMember.Name;
converted.applicationId = applicationId;
}
if (
nestedGroups.Count == 0
|| nestedGroups.Count == 1 && nestedGroups[0].Item2.applicationId == applicationId
)
{
// add to host if no groups, or current root group
collectionHost.elements.Add(converted);
}
else
{
// if we are adding a layer inside the group
var parentCollection = nestedGroups.FirstOrDefault(x =>
x.Item1.Layers.Select(y => y.URI).Contains(applicationId)
);
parentCollection.Item2.elements.Add(converted);
}
results.Add(new(Status.SUCCESS, applicationId, sourceType, converted));
convertingActivity?.SetStatus(SdkActivityStatusCode.Ok);
}
else
catch (Exception ex) when (!ex.IsFatal())
{
throw new SpeckleException($"No converted Collection found for layer {layerApplicationId}.");
_logger.LogSendConversionError(ex, sourceType);
results.Add(new(Status.ERROR, applicationId, sourceType, null, ex));
convertingActivity?.SetStatus(SdkActivityStatusCode.Error);
convertingActivity?.RecordException(ex);
}
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogSendConversionError(ex, layer.GetType().Name);
results.Add(new(Status.ERROR, layerApplicationId, layer.GetType().Name, null, ex));
convertingActivity?.SetStatus(SdkActivityStatusCode.Error);
convertingActivity?.RecordException(ex);
}
await Task.Yield();
onOperationProgressed.Report(new("Converting", (double)++count / objects.Count));
}
}
@@ -210,153 +192,15 @@ public class ArcGISRootObjectBuilder : IRootObjectBuilder<ADM.MapMember>
throw new SpeckleException("Failed to convert all objects."); // fail fast instead creating empty commit! It will appear as model card error with red color.
}
// 3 - Add Color Proxies
rootCollection[ProxyKeys.COLOR] = _colorUnpacker.ColorProxyCache.Values.ToList();
// POC: Add Color Proxies
List<ColorProxy> colorProxies = _colorManager.UnpackColors(layersWithDisplayPriority);
rootObjectCollection[ProxyKeys.COLOR] = colorProxies;
return new RootObjectBuilderResult(rootCollection, results);
}
// POC: Log would be nice, or can be removed.
Debug.WriteLine(
$"Cache hit count {cacheHitCount} out of {objects.Count} ({(double)cacheHitCount / objects.Count})"
);
private Dictionary<ADM.MapMember, long> CountAllFeaturesInLayers(List<ADM.MapMember> unpackedLayers)
{
Dictionary<ADM.MapMember, long> layersFeatureCount = new();
foreach (ADM.MapMember layer in unpackedLayers)
{
switch (layer)
{
case ADM.FeatureLayer featureLayer:
layersFeatureCount.Add(featureLayer, featureLayer.GetFeatureClass().GetCount());
break;
case ADM.RasterLayer rasterLayer:
// count Raster layer as 1 feature: not optimal but this is the approach for now
layersFeatureCount.Add(rasterLayer, 1);
break;
case ADM.LasDatasetLayer lasDatasetLayer:
var dataset = lasDatasetLayer.GetLasDataset();
// simple dataset.GetPointCount() keeps returning null, so switched to EstimatePointCount
layersFeatureCount.Add(
lasDatasetLayer,
(long)dataset.EstimatePointCount(dataset.GetDefinition().GetExtent())
);
break;
}
}
return layersFeatureCount;
}
private List<Base> ConvertFeatureLayerObjects(
ADM.FeatureLayer featureLayer,
long count,
long allFeaturesCount,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
{
string layerApplicationId = featureLayer.GetSpeckleApplicationId();
List<Base> convertedObjects = new();
// store the layer renderer for color unpacking
_colorUnpacker.StoreRendererAndFields(featureLayer);
// search the rows of the layer, where each row is treated like an object
// RowCursor is IDisposable but is not being correctly picked up by IDE warnings.
// This means we need to be carefully adding using statements based on the API documentation coming from each method/class
using (ACD.RowCursor rowCursor = featureLayer.Search())
{
while (rowCursor.MoveNext())
{
// allow cancellation before every feature
cancellationToken.ThrowIfCancellationRequested();
// Same IDisposable issue appears to happen on Row class too. Docs say it should always be disposed of manually by the caller.
using (ACD.Row row = rowCursor.Current)
{
// get application id. test for subtypes before defaulting to base type.
Base converted = _rootToSpeckleConverter.Convert(row);
string applicationId = row.GetSpeckleApplicationId(layerApplicationId);
converted.applicationId = applicationId;
convertedObjects.Add(converted);
// process the object color
_colorUnpacker.ProcessFeatureLayerColor(row, applicationId);
}
// update report
onOperationProgressed.Report(new("Converting", (double)++count / allFeaturesCount));
}
}
return convertedObjects;
}
// POC: raster colors are stored as mesh vertex colors in RasterToSpeckleConverter. Should probably move to color unpacker.
private List<Base> ConvertRasterLayerObjects(
ADM.RasterLayer rasterLayer,
long count,
long allFeaturesCount,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
{
string layerApplicationId = rasterLayer.GetSpeckleApplicationId();
List<Base> convertedObjects = new();
Raster raster = rasterLayer.GetRaster();
// check cancellation token before conversion
cancellationToken.ThrowIfCancellationRequested();
Base converted = _rootToSpeckleConverter.Convert(raster);
string applicationId = raster.GetSpeckleApplicationId(layerApplicationId);
converted.applicationId = applicationId;
convertedObjects.Add(converted);
// update report
onOperationProgressed.Report(new("Converting", (double)++count / allFeaturesCount));
return convertedObjects;
}
private List<Base> ConvertLasDatasetLayerObjects(
ADM.LasDatasetLayer lasDatasetLayer,
long count,
long allFeaturesCount,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
{
string layerApplicationId = lasDatasetLayer.GetSpeckleApplicationId();
List<Base> convertedObjects = new();
try
{
// store the layer renderer for color unpacking
_colorUnpacker.StoreRenderer(lasDatasetLayer);
using (ACD.Analyst3D.LasPointCursor ptCursor = lasDatasetLayer.SearchPoints(new ACD.Analyst3D.LasPointFilter()))
{
while (ptCursor.MoveNext())
{
// allow cancellation before every point
cancellationToken.ThrowIfCancellationRequested();
using (ACD.Analyst3D.LasPoint pt = ptCursor.Current)
{
Base converted = _rootToSpeckleConverter.Convert(pt);
string applicationId = pt.GetSpeckleApplicationId(layerApplicationId);
converted.applicationId = applicationId;
convertedObjects.Add(converted);
// process the object color
_colorUnpacker.ProcessLasLayerColor(pt, applicationId);
}
// update report
onOperationProgressed.Report(new("Converting", (double)++count / allFeaturesCount));
}
}
}
catch (ACD.Exceptions.TinException ex)
{
throw new SpeckleException("3D analyst extension is not enabled for .las layer operations", ex);
}
return convertedObjects;
return new RootObjectBuilderResult(rootObjectCollection, results);
}
}
@@ -18,7 +18,7 @@ internal sealed class SpeckleDUI3ViewModel : DockPane
/// </summary>
protected override async Task InitializeAsync()
{
await base.InitializeAsync();
await base.InitializeAsync().ConfigureAwait(false);
}
/// <summary>
@@ -26,7 +26,7 @@ internal sealed class SpeckleDUI3ViewModel : DockPane
/// </summary>
protected override async Task UninitializeAsync()
{
await base.UninitializeAsync();
await base.UninitializeAsync().ConfigureAwait(false);
}
}
@@ -4,6 +4,7 @@ using Speckle.Connectors.ArcGIS.DependencyInjection;
using Speckle.Connectors.Common;
using Speckle.Connectors.DUI;
using Speckle.Converters.ArcGIS3;
using Speckle.Sdk.Host;
using Module = ArcGIS.Desktop.Framework.Contracts.Module;
namespace Speckle.Connectors.ArcGIS;
@@ -3,22 +3,20 @@ using ArcGIS.Desktop.Core.Events;
using ArcGIS.Desktop.Framework.Threading.Tasks;
using ArcGIS.Desktop.Mapping;
using ArcGIS.Desktop.Mapping.Events;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Utils;
using Speckle.Newtonsoft.Json;
using Speckle.Sdk.Common;
namespace Speckle.Connectors.ArcGIS.Utils;
public class ArcGISDocumentStore : DocumentModelStore
{
public ArcGISDocumentStore(
ILogger<DocumentModelStore> logger,
IJsonSerializer jsonSerializer,
JsonSerializerSettings serializerOption,
ITopLevelExceptionHandler topLevelExceptionHandler
)
: base(logger, jsonSerializer)
: base(serializerOption, true)
{
ActiveMapViewChangedEvent.Subscribe(a => topLevelExceptionHandler.CatchUnhandled(() => OnMapViewChanged(a)), true);
ProjectSavingEvent.Subscribe(
@@ -42,7 +40,7 @@ public class ArcGISDocumentStore : DocumentModelStore
if (!IsDocumentInit && MapView.Active != null)
{
IsDocumentInit = true;
LoadState();
ReadFromFile();
OnDocumentChanged();
}
}
@@ -54,14 +52,14 @@ public class ArcGISDocumentStore : DocumentModelStore
return;
}
SaveState();
WriteToFile();
}
private void OnProjectSaving()
{
if (MapView.Active is not null)
{
SaveState();
WriteToFile();
}
}
@@ -76,56 +74,58 @@ public class ArcGISDocumentStore : DocumentModelStore
}
IsDocumentInit = true;
LoadState();
ReadFromFile();
OnDocumentChanged();
}
protected override void HostAppSaveState(string modelCardState) =>
QueuedTask
.Run(() =>
public override void WriteToFile()
{
Map map = MapView.Active.Map;
QueuedTask.Run(() =>
{
// Read existing metadata - To prevent messing existing metadata. 🤞 Hope other add-in developers will do same :D
var existingMetadata = map.GetMetadata();
// Parse existing metadata
XDocument existingXmlDocument = !string.IsNullOrEmpty(existingMetadata)
? XDocument.Parse(existingMetadata)
: new XDocument(new XElement("metadata"));
string serializedModels = Serialize();
XElement xmlModelCards = new("SpeckleModelCards", serializedModels);
// Check if SpeckleModelCards element already exists at root and update it
var speckleModelCardsElement = existingXmlDocument.Root?.Element("SpeckleModelCards");
if (speckleModelCardsElement != null)
{
Map map = MapView.Active.Map;
// Read existing metadata - To prevent messing existing metadata. 🤞 Hope other add-in developers will do same :D
var existingMetadata = map.GetMetadata();
// Parse existing metadata
XDocument existingXmlDocument = !string.IsNullOrEmpty(existingMetadata)
? XDocument.Parse(existingMetadata)
: new XDocument(new XElement("metadata"));
XElement xmlModelCards = new("SpeckleModelCards", modelCardState);
// Check if SpeckleModelCards element already exists at root and update it
var speckleModelCardsElement = existingXmlDocument.Root?.Element("SpeckleModelCards");
if (speckleModelCardsElement != null)
{
speckleModelCardsElement.ReplaceWith(xmlModelCards);
}
else
{
existingXmlDocument.Root?.Add(xmlModelCards);
}
map.SetMetadata(existingXmlDocument.ToString());
})
.FireAndForget();
protected override void LoadState() =>
QueuedTask
.Run(() =>
speckleModelCardsElement.ReplaceWith(xmlModelCards);
}
else
{
Map map = MapView.Active.Map;
var metadata = map.GetMetadata();
var root = XDocument.Parse(metadata).Root;
var element = root?.Element("SpeckleModelCards");
if (element is null)
{
ClearAndSave();
return;
}
existingXmlDocument.Root?.Add(xmlModelCards);
}
string modelsString = element.Value;
LoadFromString(modelsString);
})
.FireAndForget();
map.SetMetadata(existingXmlDocument.ToString());
});
}
public override void ReadFromFile()
{
Map map = MapView.Active.Map;
QueuedTask.Run(() =>
{
var metadata = map.GetMetadata();
var root = XDocument.Parse(metadata).Root;
var element = root?.Element("SpeckleModelCards");
if (element is null)
{
Models = new();
return;
}
string modelsString = element.Value;
Models = Deserialize(modelsString).NotNull();
});
}
}
@@ -40,22 +40,24 @@ public class MapMembersUtils
return mapMembers;
}
/// <summary>
/// Sorts the selected mapmembers into the same order as they appear in the Table of Contents (TOC) bar in the file.
/// This is a required step before unpacking layers, because depending on the user selection order, some children layers may appear before their container layer if both the container and children layers are selected.
/// </summary>
public IEnumerable<MapMember> GetMapMembersInOrder(Map map, IReadOnlyList<MapMember> selectedMapMembers)
// Gets the layer display priority for selected layers
public List<(MapMember, int)> GetLayerDisplayPriority(Map map, IReadOnlyList<MapMember> selectedMapMembers)
{
// first get all map layers
List<MapMember> allMapMembers = GetAllMapMembers(map);
// recalculate selected layer priority from all map layers
List<(MapMember, int)> selectedLayers = new();
int newCount = 0;
foreach (MapMember mapMember in allMapMembers)
{
if (selectedMapMembers.Contains(mapMember))
{
yield return mapMember;
selectedLayers.Add((mapMember, newCount));
newCount++;
}
}
return selectedLayers;
}
}
@@ -166,6 +166,11 @@
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Speckle.DoubleNumerics": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "MzEQ1Im0zTja+tEsdRIk/WlPiKqb22NmTOJcR1ZKm/mz46pezyyID3/wRz6vJUELMpSLnG7LhsxBL+nxbr7V0w=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
@@ -221,28 +226,24 @@
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
"speckle.common.meshtriangulation": {
"type": "Project",
"dependencies": {
"LibTessDotNet": "[1.1.15, )",
"Speckle.DoubleNumerics": "[4.1.0, )"
}
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )",
"System.Threading.Tasks.Dataflow": "[6.0.0, )"
}
},
"speckle.connectors.dui.webview": {
@@ -259,7 +260,6 @@
"type": "Project",
"dependencies": {
"Esri.ArcGISPro.Extensions30": "[3.2.0.49743, )",
"Speckle.Common.MeshTriangulation": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )"
}
},
@@ -267,15 +267,9 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )"
}
},
"LibTessDotNet": {
"type": "CentralTransitive",
"requested": "[1.1.15, )",
"resolved": "1.1.15",
"contentHash": "KuA7N3Nv/lIeawJdQBQJR6oqWD9KETHLbWzBqapwFs+Tby+R5I4crkKujKMm5bXcSuFZ8LNtflFQVadsWCbBjg=="
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -303,26 +297,20 @@
"resolved": "1.0.1938.49",
"contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "r6GGA7Pq4CPGYOu6OmmLW/hhhREo/l44RUFhsoYmpHNGpOg1UjF45dwyfLgaU38wrNxQoURjLUQDkxGfhb8liA==",
"dependencies": {
"Speckle.Sdk": "3.3.5"
"Speckle.Sdk": "3.1.0-dev.181"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "VmRRphUJm+qfcjtdzxkTstAD0opaIP57tP6GJ2YnalyRj3wBTGBQuy7gwhGjWyAvsMnkugpKFEfAVuNm/zHQ2A==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -330,16 +318,22 @@
"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.DoubleNumerics": "4.0.1",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.3.5"
"Speckle.Sdk.Dependencies": "3.1.0-dev.181"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "W7Nb3hAPlLXGZmEFWdf9vyhXKGZk3M+lypEKB+IWQVwr3NL+bzPgfw2130kTuMr/9zGx8jUbSfx2ulTql9mZxw=="
},
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "+tyDCU3/B1lDdOOAJywHQoFwyXIUghIaP2BxG79uvhfTnO+D9qIgjVlL/JV2NTliYbMHpd6eKDmHp2VHpij7MA=="
}
},
"net6.0-windows7.0/win-x64": {
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<Target AfterTargets="Clean" Name="CleanAddinAutocad" Condition="'$(AutoCADVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true'">
<RemoveDir Directories="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Autocad$(AutoCADVersion);" />
</Target>
<Target AfterTargets="Build" Name="AfterBuildAutoCAD" Condition="'$(AutoCADVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true'">
<ItemGroup>
<AutoCADDLLs Include="$(TargetDir)\**\*.*" />
</ItemGroup>
<Message Text="AutoCAD Version $(AutoCADVersion)" Importance="high"/>
<Copy DestinationFolder="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Autocad$(AutoCADVersion)\%(RecursiveDir)" SourceFiles="@(AutoCADDLLs)" />
</Target>
<Target AfterTargets="Clean" Name="CleanAddinCivil3D" Condition="'$(Civil3DVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true'">
<RemoveDir Directories="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Civil3d$(Civil3DVersion);" />
</Target>
<Target AfterTargets="Build" Name="AfterBuildCivil3D" Condition="'$(Civil3DVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true'">
<ItemGroup>
<Civil3DDLLs Include="$(TargetDir)\**\*.*" />
</ItemGroup>
<Message Text="Civil3D Version $(Civil3DVersion)" Importance="high"/>
<Copy DestinationFolder="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Civil3d$(Civil3DVersion)\%(RecursiveDir)" SourceFiles="@(Civil3DDLLs)" />
</Target>
<PropertyGroup Condition="'$(AutoCADVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true'">
<StartAction>Program</StartAction>
<StartProgram>$(ProgramW6432)\Autodesk\AutoCAD $(AutoCADVersion)\acad.exe</StartProgram>
</PropertyGroup>
<PropertyGroup Condition="'$(Civil3DVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true'">
<StartAction>Program</StartAction>
<StartProgram>$(ProgramW6432)\Autodesk\AutoCAD $(Civil3DVersion)\acad.exe</StartProgram>
<StartArguments>/product C3D</StartArguments>
</PropertyGroup>
</Project>
@@ -6,10 +6,12 @@
<AutoCADVersion>2022</AutoCADVersion>
<DefineConstants>$(DefineConstants);AUTOCAD;AUTOCAD2022;AUTOCAD2022_OR_GREATER</DefineConstants>
<Configurations>Debug;Release;Local</Configurations>
<StartAction>Program</StartAction>
<StartProgram>$(ProgramW6432)\Autodesk\AutoCAD $(AutoCADVersion)\acad.exe</StartProgram>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2022.0.2" ExcludeAssets="runtime"/>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2022.0.2" />
</ItemGroup>
<ItemGroup>
@@ -164,6 +164,11 @@
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Speckle.DoubleNumerics": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "MzEQ1Im0zTja+tEsdRIk/WlPiKqb22NmTOJcR1ZKm/mz46pezyyID3/wRz6vJUELMpSLnG7LhsxBL+nxbr7V0w=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
@@ -259,16 +264,19 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )",
"System.Threading.Tasks.Dataflow": "[6.0.0, )"
}
},
"speckle.connectors.dui.webview": {
@@ -292,7 +300,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -328,26 +336,20 @@
"resolved": "1.0.1938.49",
"contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "r6GGA7Pq4CPGYOu6OmmLW/hhhREo/l44RUFhsoYmpHNGpOg1UjF45dwyfLgaU38wrNxQoURjLUQDkxGfhb8liA==",
"dependencies": {
"Speckle.Sdk": "3.3.5"
"Speckle.Sdk": "3.1.0-dev.181"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "VmRRphUJm+qfcjtdzxkTstAD0opaIP57tP6GJ2YnalyRj3wBTGBQuy7gwhGjWyAvsMnkugpKFEfAVuNm/zHQ2A==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -355,16 +357,22 @@
"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.DoubleNumerics": "4.0.1",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.3.5"
"Speckle.Sdk.Dependencies": "3.1.0-dev.181"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "W7Nb3hAPlLXGZmEFWdf9vyhXKGZk3M+lypEKB+IWQVwr3NL+bzPgfw2130kTuMr/9zGx8jUbSfx2ulTql9mZxw=="
},
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "+tyDCU3/B1lDdOOAJywHQoFwyXIUghIaP2BxG79uvhfTnO+D9qIgjVlL/JV2NTliYbMHpd6eKDmHp2VHpij7MA=="
}
}
}
@@ -6,10 +6,12 @@
<AutoCADVersion>2023</AutoCADVersion>
<DefineConstants>$(DefineConstants);AUTOCAD;AUTOCAD2023;AUTOCAD2022_OR_GREATER;AUTOCAD2023_OR_GREATER</DefineConstants>
<Configurations>Debug;Release;Local</Configurations>
<StartAction>Program</StartAction>
<StartProgram>$(ProgramW6432)\Autodesk\AutoCAD $(AutoCADVersion)\acad.exe</StartProgram>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2023.0.0" ExcludeAssets="runtime"/>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2023.0.0" />
</ItemGroup>
<ItemGroup>
@@ -164,6 +164,11 @@
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Speckle.DoubleNumerics": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "MzEQ1Im0zTja+tEsdRIk/WlPiKqb22NmTOJcR1ZKm/mz46pezyyID3/wRz6vJUELMpSLnG7LhsxBL+nxbr7V0w=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
@@ -259,16 +264,19 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )",
"System.Threading.Tasks.Dataflow": "[6.0.0, )"
}
},
"speckle.connectors.dui.webview": {
@@ -292,7 +300,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -328,26 +336,20 @@
"resolved": "1.0.1938.49",
"contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "r6GGA7Pq4CPGYOu6OmmLW/hhhREo/l44RUFhsoYmpHNGpOg1UjF45dwyfLgaU38wrNxQoURjLUQDkxGfhb8liA==",
"dependencies": {
"Speckle.Sdk": "3.3.5"
"Speckle.Sdk": "3.1.0-dev.181"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "VmRRphUJm+qfcjtdzxkTstAD0opaIP57tP6GJ2YnalyRj3wBTGBQuy7gwhGjWyAvsMnkugpKFEfAVuNm/zHQ2A==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -355,16 +357,22 @@
"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.DoubleNumerics": "4.0.1",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.3.5"
"Speckle.Sdk.Dependencies": "3.1.0-dev.181"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "W7Nb3hAPlLXGZmEFWdf9vyhXKGZk3M+lypEKB+IWQVwr3NL+bzPgfw2130kTuMr/9zGx8jUbSfx2ulTql9mZxw=="
},
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "+tyDCU3/B1lDdOOAJywHQoFwyXIUghIaP2BxG79uvhfTnO+D9qIgjVlL/JV2NTliYbMHpd6eKDmHp2VHpij7MA=="
}
}
}
@@ -1,15 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<AutoCADVersion>2024</AutoCADVersion>
<PlatformTarget>x64</PlatformTarget>
<UseWpf>true</UseWpf>
<AutoCADVersion>2024</AutoCADVersion>
<DefineConstants>$(DefineConstants);AUTOCAD;AUTOCAD2024;AUTOCAD2022_OR_GREATER;AUTOCAD2023_OR_GREATER;AUTOCAD2024_OR_GREATER</DefineConstants>
<Configurations>Debug;Release;Local</Configurations>
<StartAction>Program</StartAction>
<StartProgram>$(ProgramW6432)\Autodesk\AutoCAD $(AutoCADVersion)\acad.exe</StartProgram>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2024.0.0" ExcludeAssets="runtime"/>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2024.0.0" />
</ItemGroup>
<ItemGroup>
@@ -164,6 +164,11 @@
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Speckle.DoubleNumerics": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "MzEQ1Im0zTja+tEsdRIk/WlPiKqb22NmTOJcR1ZKm/mz46pezyyID3/wRz6vJUELMpSLnG7LhsxBL+nxbr7V0w=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
@@ -259,16 +264,19 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )",
"System.Threading.Tasks.Dataflow": "[6.0.0, )"
}
},
"speckle.connectors.dui.webview": {
@@ -293,7 +301,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -329,26 +337,20 @@
"resolved": "1.0.1938.49",
"contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "r6GGA7Pq4CPGYOu6OmmLW/hhhREo/l44RUFhsoYmpHNGpOg1UjF45dwyfLgaU38wrNxQoURjLUQDkxGfhb8liA==",
"dependencies": {
"Speckle.Sdk": "3.3.5"
"Speckle.Sdk": "3.1.0-dev.181"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "VmRRphUJm+qfcjtdzxkTstAD0opaIP57tP6GJ2YnalyRj3wBTGBQuy7gwhGjWyAvsMnkugpKFEfAVuNm/zHQ2A==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -356,16 +358,22 @@
"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.DoubleNumerics": "4.0.1",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.3.5"
"Speckle.Sdk.Dependencies": "3.1.0-dev.181"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "W7Nb3hAPlLXGZmEFWdf9vyhXKGZk3M+lypEKB+IWQVwr3NL+bzPgfw2130kTuMr/9zGx8jUbSfx2ulTql9mZxw=="
},
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "+tyDCU3/B1lDdOOAJywHQoFwyXIUghIaP2BxG79uvhfTnO+D9qIgjVlL/JV2NTliYbMHpd6eKDmHp2VHpij7MA=="
}
}
}
@@ -1,20 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<UseWpf>true</UseWpf>
<AutoCADVersion>2025</AutoCADVersion>
<DefineConstants>$(DefineConstants);AUTOCAD;AUTOCAD2025;AUTOCAD2022_OR_GREATER;AUTOCAD2023_OR_GREATER;AUTOCAD2024_OR_GREATER;AUTOCAD2025_OR_GREATER</DefineConstants>
<DefineConstants>$(DefineConstants);AUTOCAD2025;AUTOCAD</DefineConstants>
<Configurations>Debug;Release;Local</Configurations>
</PropertyGroup>
<PropertyGroup>
<!-- .NET Core uses this to move native dependencies into a root for runtime selection and usage for non-windows development https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#enablewindowstargeting -->
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <!--This is needed for managed dependencies-->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <!--This is needed for the rest-->
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <!--This is needed just to keep folder paths the same as the netframework versions of autocad/civil-->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <!-- .NET Core uses this to move native dependencies into a root for runtime selection and usage for non-windows development https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#enablewindowstargeting -->
<StartAction>Program</StartAction>
<StartProgram>$(ProgramW6432)\Autodesk\AutoCAD $(AutoCADVersion)\acad.exe</StartProgram>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2025.0.0" ExcludeAssets="runtime"/>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2025.0.0" />
<FrameworkReference Include="Microsoft.WindowsDesktop.App"/>
</ItemGroup>
<ItemGroup>
@@ -155,6 +155,11 @@
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Speckle.DoubleNumerics": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "MzEQ1Im0zTja+tEsdRIk/WlPiKqb22NmTOJcR1ZKm/mz46pezyyID3/wRz6vJUELMpSLnG7LhsxBL+nxbr7V0w=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
@@ -215,16 +220,19 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )",
"System.Threading.Tasks.Dataflow": "[6.0.0, )"
}
},
"speckle.connectors.dui.webview": {
@@ -249,7 +257,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -285,42 +293,42 @@
"resolved": "1.0.1938.49",
"contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "r6GGA7Pq4CPGYOu6OmmLW/hhhREo/l44RUFhsoYmpHNGpOg1UjF45dwyfLgaU38wrNxQoURjLUQDkxGfhb8liA==",
"dependencies": {
"Speckle.Sdk": "3.3.5"
"Speckle.Sdk": "3.1.0-dev.181"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "VmRRphUJm+qfcjtdzxkTstAD0opaIP57tP6GJ2YnalyRj3wBTGBQuy7gwhGjWyAvsMnkugpKFEfAVuNm/zHQ2A==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.CSharp": "4.7.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.DoubleNumerics": "4.0.1",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.3.5"
"Speckle.Sdk.Dependencies": "3.1.0-dev.181"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "W7Nb3hAPlLXGZmEFWdf9vyhXKGZk3M+lypEKB+IWQVwr3NL+bzPgfw2130kTuMr/9zGx8jUbSfx2ulTql9mZxw=="
},
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "+tyDCU3/B1lDdOOAJywHQoFwyXIUghIaP2BxG79uvhfTnO+D9qIgjVlL/JV2NTliYbMHpd6eKDmHp2VHpij7MA=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -1,27 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<AutoCADVersion>2026</AutoCADVersion>
<DefineConstants>$(DefineConstants);AUTOCAD;AUTOCAD2026;AUTOCAD2022_OR_GREATER;AUTOCAD2023_OR_GREATER;AUTOCAD2024_OR_GREATER;AUTOCAD2025_OR_GREATER;AUTOCAD2026_OR_GREATER</DefineConstants>
<Configurations>Debug;Release;Local</Configurations>
</PropertyGroup>
<PropertyGroup>
<!-- .NET Core uses this to move native dependencies into a root for runtime selection and usage for non-windows development https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#enablewindowstargeting -->
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <!--This is needed for managed dependencies-->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <!--This is needed for the rest-->
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <!--This is needed just to keep folder paths the same as the netframework versions of autocad/civil-->
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2026.0.0" ExcludeAssets="runtime"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Converters\Autocad\Speckle.Converters.Autocad2026\Speckle.Converters.Autocad2026.csproj" />
<ProjectReference Include="..\..\..\DUI3\Speckle.Connectors.DUI.WebView\Speckle.Connectors.DUI.WebView.csproj" />
<ProjectReference Include="..\..\..\Sdk\Speckle.Converters.Common\Speckle.Converters.Common.csproj" />
</ItemGroup>
<Import Project="..\Speckle.Connectors.AutocadShared\Speckle.Connectors.AutocadShared.projitems" Label="Shared" />
</Project>
@@ -1,7 +1,6 @@
using Autodesk.AutoCAD.DatabaseServices;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Autocad.HostApp.Extensions;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
@@ -20,8 +19,6 @@ public class AutocadBasicConnectorBinding : IBasicConnectorBinding
private readonly DocumentModelStore _store;
private readonly ISpeckleApplication _speckleApplication;
private readonly IThreadContext _threadContext;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly ILogger<AutocadBasicConnectorBinding> _logger;
public BasicConnectorBindingCommands Commands { get; }
@@ -31,9 +28,7 @@ public class AutocadBasicConnectorBinding : IBasicConnectorBinding
IBrowserBridge parent,
IAccountManager accountManager,
ISpeckleApplication speckleApplication,
ILogger<AutocadBasicConnectorBinding> logger,
IThreadContext threadContext,
ITopLevelExceptionHandler topLevelExceptionHandler
ILogger<AutocadBasicConnectorBinding> logger
)
{
_store = store;
@@ -41,14 +36,12 @@ public class AutocadBasicConnectorBinding : IBasicConnectorBinding
_accountManager = accountManager;
_speckleApplication = speckleApplication;
Commands = new BasicConnectorBindingCommands(parent);
_logger = logger;
_threadContext = threadContext;
_topLevelExceptionHandler = topLevelExceptionHandler;
_store.DocumentChanged += (_, _) =>
_topLevelExceptionHandler.FireAndForget(async () =>
parent.TopLevelExceptionHandler.FireAndForget(async () =>
{
await Commands.NotifyDocumentChanged();
await Commands.NotifyDocumentChanged().ConfigureAwait(false);
});
_logger = logger;
}
public string GetConnectorVersion() => _speckleApplication.SpeckleVersion;
@@ -73,14 +66,12 @@ public class AutocadBasicConnectorBinding : IBasicConnectorBinding
public DocumentModelStore GetDocumentState() => _store;
public void AddModel(ModelCard model) => _store.AddModel(model);
public void AddModel(ModelCard model) => _store.Models.Add(model);
public void UpdateModel(ModelCard model) => _store.UpdateModel(model);
public void RemoveModel(ModelCard model) => _store.RemoveModel(model);
public void RemoveModels(List<ModelCard> models) => _store.RemoveModels(models);
public async Task HighlightObjects(IReadOnlyList<string> objectIds)
{
// POC: Will be addressed to move it into AutocadContext!
@@ -88,7 +79,7 @@ public class AutocadBasicConnectorBinding : IBasicConnectorBinding
var dbObjects = doc.GetObjects(objectIds);
var acadObjectIds = dbObjects.Select(tuple => tuple.Root.Id).ToArray();
await HighlightObjectsOnView(acadObjectIds);
await HighlightObjectsOnView(acadObjectIds).ConfigureAwait(false);
}
public async Task HighlightModel(string modelCardId)
@@ -125,73 +116,78 @@ public class AutocadBasicConnectorBinding : IBasicConnectorBinding
if (objectIds.Length == 0)
{
await Commands.SetModelError(modelCardId, new OperationCanceledException("No objects found to highlight."));
await Commands
.SetModelError(modelCardId, new OperationCanceledException("No objects found to highlight."))
.ConfigureAwait(false);
return;
}
await HighlightObjectsOnView(objectIds, modelCardId);
await HighlightObjectsOnView(objectIds, modelCardId).ConfigureAwait(false);
}
private async Task HighlightObjectsOnView(ObjectId[] objectIds, string? modelCardId = null)
{
var doc = Application.DocumentManager.MdiActiveDocument;
await _threadContext.RunOnMainAsync(async () =>
{
try
await Parent
.RunOnMainThreadAsync(async () =>
{
doc.Editor.SetImpliedSelection([]); // Deselects
try
{
doc.Editor.SetImpliedSelection(objectIds);
}
catch (Exception e) when (!e.IsFatal())
{
// SWALLOW REASON:
// If the objects under the blocks, it won't be able to select them.
// If we try, API will throw the invalid input error, because we request something from API that Autocad doesn't
// handle it on its current canvas. Block elements only selectable when in its scope.
}
doc.Editor.UpdateScreen();
Extents3d selectedExtents = new();
var tr = doc.TransactionManager.StartTransaction();
foreach (ObjectId objectId in objectIds)
{
doc.Editor.SetImpliedSelection(Array.Empty<ObjectId>()); // Deselects
try
{
var entity = (Entity?)tr.GetObject(objectId, OpenMode.ForRead);
if (entity?.GeometricExtents != null)
{
selectedExtents.AddExtents(entity.GeometricExtents);
}
doc.Editor.SetImpliedSelection(objectIds);
}
catch (Exception e) when (!e.IsFatal())
{
// Note: we're swallowing exeptions here because of a weird case when receiving blocks, we would have
// acad api throw an error on accessing entity.GeometricExtents.
// may also throw Autodesk.AutoCAD.Runtime.Exception for invalid extents on objects like rays and xlines
// SWALLOW REASON:
// If the objects under the blocks, it won't be able to select them.
// If we try, API will throw the invalid input error, because we request something from API that Autocad doesn't
// handle it on its current canvas. Block elements only selectable when in its scope.
}
doc.Editor.UpdateScreen();
Extents3d selectedExtents = new();
var tr = doc.TransactionManager.StartTransaction();
foreach (ObjectId objectId in objectIds)
{
try
{
var entity = (Entity?)tr.GetObject(objectId, OpenMode.ForRead);
if (entity?.GeometricExtents != null)
{
selectedExtents.AddExtents(entity.GeometricExtents);
}
}
catch (Exception e) when (!e.IsFatal())
{
// Note: we're swallowing exeptions here because of a weird case when receiving blocks, we would have
// acad api throw an error on accessing entity.GeometricExtents.
}
}
doc.Editor.Zoom(selectedExtents);
tr.Commit();
Autodesk.AutoCAD.Internal.Utils.FlushGraphics();
}
catch (Exception ex) when (!ex.IsFatal())
{
if (modelCardId != null)
{
await Commands
.SetModelError(modelCardId, new OperationCanceledException("Failed to highlight objects."))
.ConfigureAwait(false);
}
else
{
// This will happen, in some cases, where we highlight individual objects. Should be caught by the top level handler and not
// crash the host app.
throw;
}
}
doc.Editor.Zoom(selectedExtents);
tr.Commit();
Autodesk.AutoCAD.Internal.Utils.FlushGraphics();
}
catch (Exception ex) when (!ex.IsFatal())
{
if (modelCardId != null)
{
await Commands.SetModelError(modelCardId, new OperationCanceledException("Failed to highlight objects."));
}
else
{
// This will happen, in some cases, where we highlight individual objects. Should be caught by the top level handler and not
// crash the host app.
throw;
}
}
});
})
.ConfigureAwait(false);
}
}
@@ -1,119 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Logging;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Sdk;
namespace Speckle.Connectors.Autocad.Bindings;
public abstract class AutocadReceiveBaseBinding : IReceiveBinding
{
public string Name => "receiveBinding";
public IBrowserBridge Parent { get; }
private readonly DocumentModelStore _store;
private readonly ICancellationManager _cancellationManager;
private readonly IServiceProvider _serviceProvider;
private readonly IOperationProgressManager _operationProgressManager;
private readonly ILogger<AutocadReceiveBinding> _logger;
private readonly ISpeckleApplication _speckleApplication;
private readonly IThreadContext _threadContext;
private ReceiveBindingUICommands Commands { get; }
protected AutocadReceiveBaseBinding(
DocumentModelStore store,
IBrowserBridge parent,
ICancellationManager cancellationManager,
IServiceProvider serviceProvider,
IOperationProgressManager operationProgressManager,
ILogger<AutocadReceiveBinding> logger,
ISpeckleApplication speckleApplication,
IThreadContext threadContext
)
{
_store = store;
_cancellationManager = cancellationManager;
_serviceProvider = serviceProvider;
_operationProgressManager = operationProgressManager;
_logger = logger;
_speckleApplication = speckleApplication;
_threadContext = threadContext;
Parent = parent;
Commands = new ReceiveBindingUICommands(parent);
}
protected abstract void InitializeSettings(IServiceProvider serviceProvider);
public void CancelReceive(string modelCardId) => _cancellationManager.CancelOperation(modelCardId);
public async Task Receive(string modelCardId) =>
await _threadContext.RunOnMainAsync(async () => await ReceiveInternal(modelCardId));
private async Task ReceiveInternal(string modelCardId)
{
using var scope = _serviceProvider.CreateScope();
InitializeSettings(scope.ServiceProvider);
try
{
// Get receiver card
if (_store.GetModelById(modelCardId) is not ReceiverModelCard modelCard)
{
// Handle as GLOBAL ERROR at BrowserBridge
throw new InvalidOperationException("No download model card was found.");
}
using var cancellationItem = _cancellationManager.GetCancellationItem(modelCardId);
// Disable document activation (document creation and document switch)
// Not disabling results in DUI model card being out of sync with the active document
// The DocumentActivated event isn't usable probably because it is pushed to back of main thread queue
Application.DocumentManager.DocumentActivationEnabled = false;
// Receive host objects
var operationResults = await scope
.ServiceProvider.GetRequiredService<ReceiveOperation>()
.Execute(
modelCard.GetReceiveInfo(_speckleApplication.Slug),
_operationProgressManager.CreateOperationProgressEventHandler(Parent, modelCardId, cancellationItem.Token),
cancellationItem.Token
);
await Commands.SetModelReceiveResult(
modelCardId,
operationResults.BakedObjectIds,
operationResults.ConversionResults
);
}
catch (OperationCanceledException)
{
// SWALLOW -> UI handles it immediately, so we do not need to handle anything for now!
// Idea for later -> when cancel called, create promise from UI to solve it later with this catch block.
// So have 3 state on UI -> Cancellation clicked -> Cancelling -> Cancelled
return;
}
catch (Exception ex) when (!ex.IsFatal()) // UX reasons - we will report operation exceptions as model card error. We may change this later when we have more exception documentation
{
_logger.LogModelCardHandledError(ex);
await Commands.SetModelError(modelCardId, ex);
}
finally
{
// reenable document activation
Application.DocumentManager.DocumentActivationEnabled = true;
// regenerate doc to flush graphics, sometimes some objects (ellipses, nurbs curves) do not appear fully visible after receive.
// Adding a regen (must be run on main thread) here, but it doesn't seem to work:
// it's run on main thread, tried sending the "regen" string to execute, also tried regen after every object bake, but still can't fix.
// the objects should appear visible if you manually call the "regen" command after the operation finishes, or click on a view on the view cube which also calls regen.
Application.DocumentManager.CurrentDocument.Editor.Regen();
}
}
}
@@ -1,49 +1,109 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Logging;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Converters.Autocad;
using Speckle.Converters.Common;
using Speckle.Sdk;
namespace Speckle.Connectors.Autocad.Bindings;
public sealed class AutocadReceiveBinding : AutocadReceiveBaseBinding
public sealed class AutocadReceiveBinding : IReceiveBinding
{
public string Name => "receiveBinding";
public IBrowserBridge Parent { get; }
private readonly DocumentModelStore _store;
private readonly CancellationManager _cancellationManager;
private readonly IServiceProvider _serviceProvider;
private readonly IOperationProgressManager _operationProgressManager;
private readonly ILogger<AutocadReceiveBinding> _logger;
private readonly IAutocadConversionSettingsFactory _autocadConversionSettingsFactory;
private readonly ISpeckleApplication _speckleApplication;
private ReceiveBindingUICommands Commands { get; }
public AutocadReceiveBinding(
DocumentModelStore store,
IBrowserBridge parent,
ICancellationManager cancellationManager,
CancellationManager cancellationManager,
IServiceProvider serviceProvider,
IOperationProgressManager operationProgressManager,
ILogger<AutocadReceiveBinding> logger,
IAutocadConversionSettingsFactory autocadConversionSettingsFactory,
ISpeckleApplication speckleApplication,
IThreadContext threadContext
ISpeckleApplication speckleApplication
)
: base(
store,
parent,
cancellationManager,
serviceProvider,
operationProgressManager,
logger,
speckleApplication,
threadContext
)
{
_store = store;
_cancellationManager = cancellationManager;
_serviceProvider = serviceProvider;
_operationProgressManager = operationProgressManager;
_logger = logger;
_autocadConversionSettingsFactory = autocadConversionSettingsFactory;
_speckleApplication = speckleApplication;
Parent = parent;
Commands = new ReceiveBindingUICommands(parent);
}
protected override void InitializeSettings(IServiceProvider serviceProvider)
public void CancelReceive(string modelCardId) => _cancellationManager.CancelOperation(modelCardId);
public async Task Receive(string modelCardId)
{
serviceProvider
.GetRequiredService<IConverterSettingsStore<AutocadConversionSettings>>()
using var scope = _serviceProvider.CreateScope();
scope
.ServiceProvider.GetRequiredService<IConverterSettingsStore<AutocadConversionSettings>>()
.Initialize(_autocadConversionSettingsFactory.Create(Application.DocumentManager.CurrentDocument));
try
{
// Get receiver card
if (_store.GetModelById(modelCardId) is not ReceiverModelCard modelCard)
{
// Handle as GLOBAL ERROR at BrowserBridge
throw new InvalidOperationException("No download model card was found.");
}
CancellationToken cancellationToken = _cancellationManager.InitCancellationTokenSource(modelCardId);
// Disable document activation (document creation and document switch)
// Not disabling results in DUI model card being out of sync with the active document
// The DocumentActivated event isn't usable probably because it is pushed to back of main thread queue
Application.DocumentManager.DocumentActivationEnabled = false;
// Receive host objects
var operationResults = await scope
.ServiceProvider.GetRequiredService<ReceiveOperation>()
.Execute(
modelCard.GetReceiveInfo(_speckleApplication.Slug),
_operationProgressManager.CreateOperationProgressEventHandler(Parent, modelCardId, cancellationToken),
cancellationToken
)
.ConfigureAwait(false);
await Commands
.SetModelReceiveResult(modelCardId, operationResults.BakedObjectIds, operationResults.ConversionResults)
.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// SWALLOW -> UI handles it immediately, so we do not need to handle anything for now!
// Idea for later -> when cancel called, create promise from UI to solve it later with this catch block.
// So have 3 state on UI -> Cancellation clicked -> Cancelling -> Cancelled
return;
}
catch (Exception ex) when (!ex.IsFatal()) // UX reasons - we will report operation exceptions as model card error. We may change this later when we have more exception documentation
{
_logger.LogModelCardHandledError(ex);
await Commands.SetModelError(modelCardId, ex).ConfigureAwait(false);
}
finally
{
// reenable document activation
Application.DocumentManager.DocumentActivationEnabled = true;
}
}
}
@@ -1,7 +1,6 @@
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Speckle.Connectors.Autocad.HostApp.Extensions;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
@@ -9,23 +8,17 @@ namespace Speckle.Connectors.Autocad.Bindings;
public class AutocadSelectionBinding : ISelectionBinding
{
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly IThreadContext _threadContext;
private const string SELECTION_EVENT = "setSelection";
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly HashSet<Document> _visitedDocuments = new();
public string Name => "selectionBinding";
public IBrowserBridge Parent { get; }
public AutocadSelectionBinding(
IBrowserBridge parent,
ITopLevelExceptionHandler topLevelExceptionHandler,
IThreadContext threadContext
)
public AutocadSelectionBinding(IBrowserBridge parent)
{
_topLevelExceptionHandler = topLevelExceptionHandler;
_threadContext = threadContext;
_topLevelExceptionHandler = parent.TopLevelExceptionHandler;
Parent = parent;
// POC: Use here Context for doc. In converters it's OK but we are still lacking to use context into bindings.
@@ -48,7 +41,9 @@ public class AutocadSelectionBinding : ISelectionBinding
if (!_visitedDocuments.Contains(document))
{
document.ImpliedSelectionChanged += (_, _) =>
_topLevelExceptionHandler.FireAndForget(async () => await _threadContext.RunOnMainAsync(OnSelectionChanged));
_topLevelExceptionHandler.FireAndForget(
async () => await Parent.RunOnMainThreadAsync(OnSelectionChanged).ConfigureAwait(false)
);
_visitedDocuments.Add(document);
}
@@ -62,7 +57,7 @@ public class AutocadSelectionBinding : ISelectionBinding
private async Task OnSelectionChanged()
{
_selectionInfo = GetSelectionInternal();
await Parent.Send(SELECTION_EVENT, _selectionInfo);
await Parent.Send(SELECTION_EVENT, _selectionInfo).ConfigureAwait(false);
}
public SelectionInfo GetSelection() => _selectionInfo;
@@ -89,17 +84,6 @@ public class AutocadSelectionBinding : ISelectionBinding
objectTypes.Add(dbObject.GetType().Name);
objs.Add(dbObject.GetSpeckleApplicationId());
// do the same also for each AttributeReference inside the BlockReference (attribute change is not affecting the block otherwise)
if (dbObject is BlockReference blockReference)
{
foreach (ObjectId id in blockReference.AttributeCollection)
{
var attr = (AttributeReference)tr.GetObject(id, OpenMode.ForRead);
objectTypes.Add(attr.GetType().Name);
objs.Add(attr.GetSpeckleApplicationId());
}
}
}
tr.Commit();
@@ -1,14 +1,13 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Collections.Concurrent;
using Autodesk.AutoCAD.DatabaseServices;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Autocad.HostApp.Extensions;
using Speckle.Connectors.Autocad.Operations.Send;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Exceptions;
@@ -22,7 +21,6 @@ using Speckle.Sdk.Common;
namespace Speckle.Connectors.Autocad.Bindings;
[SuppressMessage("ReSharper", "AsyncVoidMethod")]
public abstract class AutocadSendBaseBinding : ISendBinding
{
public string Name => "sendBinding";
@@ -31,16 +29,15 @@ public abstract class AutocadSendBaseBinding : ISendBinding
public IBrowserBridge Parent { get; }
private readonly DocumentModelStore _store;
private readonly IAutocadIdleManager _idleManager;
private readonly List<ISendFilter> _sendFilters;
private readonly ICancellationManager _cancellationManager;
private readonly CancellationManager _cancellationManager;
private readonly IServiceProvider _serviceProvider;
private readonly ISendConversionCache _sendConversionCache;
private readonly IOperationProgressManager _operationProgressManager;
private readonly ILogger<AutocadSendBinding> _logger;
private readonly ISpeckleApplication _speckleApplication;
private readonly IThreadContext _threadContext;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly IAppIdleManager _idleManager;
private readonly ISpeckleApplication _speckleApplication;
/// <summary>
/// Used internally to aggregate the changed objects' id. Note we're using a concurrent dictionary here as the expiry check method is not thread safe, and this was causing problems. See:
@@ -48,24 +45,23 @@ public abstract class AutocadSendBaseBinding : ISendBinding
/// As to why a concurrent dictionary, it's because it's the cheapest/easiest way to do so.
/// https://stackoverflow.com/questions/18922985/concurrent-hashsett-in-net-framework
/// </summary>
private ConcurrentBag<string> ChangedObjectIds { get; set; } = new();
private ConcurrentDictionary<string, byte> ChangedObjectIds { get; set; } = new();
protected AutocadSendBaseBinding(
DocumentModelStore store,
IAutocadIdleManager idleManager,
IBrowserBridge parent,
IEnumerable<ISendFilter> sendFilters,
ICancellationManager cancellationManager,
CancellationManager cancellationManager,
IServiceProvider serviceProvider,
ISendConversionCache sendConversionCache,
IOperationProgressManager operationProgressManager,
ILogger<AutocadSendBinding> logger,
ISpeckleApplication speckleApplication,
IThreadContext threadContext,
ITopLevelExceptionHandler topLevelExceptionHandler,
IAppIdleManager idleManager
ISpeckleApplication speckleApplication
)
{
_store = store;
_idleManager = idleManager;
_serviceProvider = serviceProvider;
_cancellationManager = cancellationManager;
_sendFilters = sendFilters.ToList();
@@ -73,9 +69,7 @@ public abstract class AutocadSendBaseBinding : ISendBinding
_operationProgressManager = operationProgressManager;
_logger = logger;
_speckleApplication = speckleApplication;
_threadContext = threadContext;
_topLevelExceptionHandler = topLevelExceptionHandler;
_idleManager = idleManager;
_topLevelExceptionHandler = parent.TopLevelExceptionHandler;
Parent = parent;
Commands = new SendBindingUICommands(parent);
@@ -109,25 +103,31 @@ public abstract class AutocadSendBaseBinding : ISendBinding
doc.Database.ObjectModified += (_, e) => OnObjectChanged(e.DBObject);
}
private void OnObjectChanged(DBObject dbObject) =>
private void OnObjectChanged(DBObject dbObject)
{
_topLevelExceptionHandler.CatchUnhandled(() => OnChangeChangedObjectIds(dbObject));
}
private void OnChangeChangedObjectIds(DBObject dBObject)
{
ChangedObjectIds.Add(dBObject.GetSpeckleApplicationId());
_idleManager.SubscribeToIdle(nameof(RunExpirationChecks), async () => await RunExpirationChecks());
ChangedObjectIds[dBObject.GetSpeckleApplicationId()] = 1;
_idleManager.SubscribeToIdle(
nameof(AutocadSendBinding),
async () => await RunExpirationChecks().ConfigureAwait(false)
);
}
private async Task RunExpirationChecks()
{
var senders = _store.GetSenders();
string[] objectIdsList = ChangedObjectIds.Keys.ToArray();
List<string> expiredSenderIds = new();
_sendConversionCache.EvictObjects(ChangedObjectIds);
_sendConversionCache.EvictObjects(objectIdsList);
foreach (SenderModelCard modelCard in senders)
{
var intersection = modelCard.SendFilter.NotNull().RefreshObjectIds().Intersect(ChangedObjectIds).ToList();
var intersection = modelCard.SendFilter.NotNull().RefreshObjectIds().Intersect(objectIdsList).ToList();
bool isExpired = intersection.Count != 0;
if (isExpired)
{
@@ -135,7 +135,7 @@ public abstract class AutocadSendBaseBinding : ISendBinding
}
}
await Commands.SetModelsExpired(expiredSenderIds);
await Commands.SetModelsExpired(expiredSenderIds).ConfigureAwait(false);
ChangedObjectIds = new();
}
@@ -144,7 +144,9 @@ public abstract class AutocadSendBaseBinding : ISendBinding
public List<ICardSetting> GetSendSettings() => [];
public async Task Send(string modelCardId) =>
await _threadContext.RunOnMainAsync(async () => await SendInternal(modelCardId));
await Parent
.RunOnMainThreadAsync(async () => await SendInternal(modelCardId).ConfigureAwait(false))
.ConfigureAwait(false);
protected abstract void InitializeSettings(IServiceProvider serviceProvider);
@@ -161,7 +163,7 @@ public abstract class AutocadSendBaseBinding : ISendBinding
using var scope = _serviceProvider.CreateScope();
InitializeSettings(scope.ServiceProvider);
using var cancellationItem = _cancellationManager.GetCancellationItem(modelCardId);
CancellationToken cancellationToken = _cancellationManager.InitCancellationTokenSource(modelCardId);
// Disable document activation (document creation and document switch)
// Not disabling results in DUI model card being out of sync with the active document
@@ -183,12 +185,15 @@ public abstract class AutocadSendBaseBinding : ISendBinding
.ServiceProvider.GetRequiredService<SendOperation<AutocadRootObject>>()
.Execute(
autocadObjects,
modelCard.GetSendInfo(_speckleApplication.ApplicationAndVersion),
_operationProgressManager.CreateOperationProgressEventHandler(Parent, modelCardId, cancellationItem.Token),
cancellationItem.Token
);
modelCard.GetSendInfo(_speckleApplication.Slug),
_operationProgressManager.CreateOperationProgressEventHandler(Parent, modelCardId, cancellationToken),
cancellationToken
)
.ConfigureAwait(false);
await Commands.SetModelSendResult(modelCardId, sendResult.RootObjId, sendResult.ConversionResults);
await Commands
.SetModelSendResult(modelCardId, sendResult.RootObjId, sendResult.ConversionResults)
.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -200,7 +205,7 @@ public abstract class AutocadSendBaseBinding : ISendBinding
catch (Exception ex) when (!ex.IsFatal()) // UX reasons - we will report operation exceptions as model card error. We may change this later when we have more exception documentation
{
_logger.LogModelCardHandledError(ex);
await Commands.SetModelError(modelCardId, ex);
await Commands.SetModelError(modelCardId, ex).ConfigureAwait(false);
}
finally
{
@@ -1,8 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
@@ -19,21 +19,20 @@ public sealed class AutocadSendBinding : AutocadSendBaseBinding
public AutocadSendBinding(
DocumentModelStore store,
IAutocadIdleManager idleManager,
IBrowserBridge parent,
IEnumerable<ISendFilter> sendFilters,
ICancellationManager cancellationManager,
CancellationManager cancellationManager,
IServiceProvider serviceProvider,
ISendConversionCache sendConversionCache,
IOperationProgressManager operationProgressManager,
ILogger<AutocadSendBinding> logger,
IAutocadConversionSettingsFactory autocadConversionSettingsFactory,
ISpeckleApplication speckleApplication,
IThreadContext threadContext,
ITopLevelExceptionHandler topLevelExceptionHandler,
IAppIdleManager appIdleManager
ISpeckleApplication speckleApplication
)
: base(
store,
idleManager,
parent,
sendFilters,
cancellationManager,
@@ -41,10 +40,7 @@ public sealed class AutocadSendBinding : AutocadSendBaseBinding
sendConversionCache,
operationProgressManager,
logger,
speckleApplication,
threadContext,
topLevelExceptionHandler,
appIdleManager
speckleApplication
)
{
_autocadConversionSettingsFactory = autocadConversionSettingsFactory;
@@ -10,10 +10,10 @@ using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.WebView;
using Speckle.Sdk.Models.GraphTraversal;
@@ -24,13 +24,14 @@ public static class SharedRegistration
{
public static void AddAutocadBase(this IServiceCollection serviceCollection)
{
serviceCollection.AddConnectors();
serviceCollection.AddDUI<DefaultThreadContext, AutocadDocumentStore>();
serviceCollection.AddConnectorUtils();
serviceCollection.AddDUI();
serviceCollection.AddDUIView();
// Register other connector specific types
serviceCollection.AddTransient<TransactionContext>();
serviceCollection.AddSingleton(new AutocadDocumentManager()); // TODO: Dependent to TransactionContext, can be moved to AutocadContext
serviceCollection.AddSingleton<DocumentModelStore, AutocadDocumentStore>();
serviceCollection.AddSingleton<AutocadContext>();
// Unpackers and builders
@@ -44,10 +45,10 @@ public static class SharedRegistration
serviceCollection.AddScoped<AutocadGroupBaker>();
serviceCollection.AddScoped<AutocadColorUnpacker>();
serviceCollection.AddScoped<IAutocadColorBaker, AutocadColorBaker>();
serviceCollection.AddScoped<AutocadColorBaker>();
serviceCollection.AddScoped<AutocadMaterialUnpacker>();
serviceCollection.AddScoped<IAutocadMaterialBaker, AutocadMaterialBaker>();
serviceCollection.AddScoped<AutocadMaterialBaker>();
serviceCollection.AddSingleton<IAppIdleManager, AutocadIdleManager>();
@@ -61,6 +62,8 @@ public static class SharedRegistration
serviceCollection.AddSingleton<IBinding>(sp => sp.GetRequiredService<IBasicConnectorBinding>());
serviceCollection.AddSingleton<IBasicConnectorBinding, AutocadBasicConnectorBinding>();
serviceCollection.AddSingleton<IBinding, ConfigBinding>();
serviceCollection.RegisterTopLevelExceptionHandler();
}
public static void LoadSend(this IServiceCollection serviceCollection)
@@ -1,7 +1,6 @@
using Autodesk.AutoCAD.Colors;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Operations;
using Speckle.InterfaceGenerator;
using Speckle.Sdk;
using Speckle.Sdk.Models.Proxies;
using AutocadColor = Autodesk.AutoCAD.Colors.Color;
@@ -11,9 +10,15 @@ namespace Speckle.Connectors.Autocad.HostApp;
/// <summary>
/// Expects to be a scoped dependency for a given operation and helps with layer creation and cleanup.
/// </summary>
[GenerateAutoInterface]
public class AutocadColorBaker(ILogger<AutocadColorBaker> logger) : IAutocadColorBaker
public class AutocadColorBaker
{
private readonly ILogger<AutocadColorBaker> _logger;
public AutocadColorBaker(ILogger<AutocadColorBaker> logger)
{
_logger = logger;
}
/// <summary>
/// For receive operations
/// </summary>
@@ -24,7 +29,10 @@ public class AutocadColorBaker(ILogger<AutocadColorBaker> logger) : IAutocadColo
/// </summary>
/// <param name="colorProxies"></param>
/// <param name="onOperationProgressed"></param>
public void ParseColors(IReadOnlyCollection<ColorProxy> colorProxies, IProgress<CardProgress> onOperationProgressed)
public async Task ParseColors(
IReadOnlyCollection<ColorProxy> colorProxies,
IProgress<CardProgress> onOperationProgressed
)
{
var count = 0;
foreach (ColorProxy colorProxy in colorProxies)
@@ -54,8 +62,10 @@ public class AutocadColorBaker(ILogger<AutocadColorBaker> logger) : IAutocadColo
}
catch (Exception ex) when (!ex.IsFatal())
{
logger.LogError(ex, "Failed parsing color proxy");
_logger.LogError(ex, "Failed parsing color proxy");
}
await Task.Yield();
}
}
@@ -1,26 +1,25 @@
using Microsoft.Extensions.Logging;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Utils;
using Speckle.Newtonsoft.Json;
using Speckle.Sdk.Common;
namespace Speckle.Connectors.Autocad.HostApp;
public class AutocadDocumentStore : DocumentModelStore
{
private const string NULL_DOCUMENT_NAME = "Null Doc";
private readonly string _nullDocumentName = "Null Doc";
private string _previousDocName;
private readonly AutocadDocumentManager _autocadDocumentManager;
public AutocadDocumentStore(
ILogger<DocumentModelStore> logger,
IJsonSerializer jsonSerializer,
JsonSerializerSettings jsonSerializerSettings,
AutocadDocumentManager autocadDocumentManager,
ITopLevelExceptionHandler topLevelExceptionHandler
)
: base(logger, jsonSerializer)
: base(jsonSerializerSettings, true)
{
_autocadDocumentManager = autocadDocumentManager;
_previousDocName = NULL_DOCUMENT_NAME;
_previousDocName = _nullDocumentName;
// POC: Will be addressed to move it into AutocadContext!
if (Application.DocumentManager.MdiActiveDocument != null)
@@ -42,38 +41,39 @@ public class AutocadDocumentStore : DocumentModelStore
private void OnDocChangeInternal(Document? doc)
{
var currentDocName = doc != null ? doc.Name : NULL_DOCUMENT_NAME;
var currentDocName = doc != null ? doc.Name : _nullDocumentName;
if (_previousDocName == currentDocName)
{
return;
}
_previousDocName = currentDocName;
LoadState();
ReadFromFile();
OnDocumentChanged();
}
protected override void LoadState()
public override void ReadFromFile()
{
Models = new();
// POC: Will be addressed to move it into AutocadContext!
Document? doc = Application.DocumentManager.MdiActiveDocument;
if (doc == null)
{
ClearAndSave();
return;
}
string? serializedModelCards = _autocadDocumentManager.ReadModelCards(doc);
if (serializedModelCards == null)
{
ClearAndSave();
return;
}
LoadFromString(serializedModelCards);
Models = Deserialize(serializedModelCards).NotNull();
}
protected override void HostAppSaveState(string modelCardState)
public override void WriteToFile()
{
// POC: Will be addressed to move it into AutocadContext!
Document doc = Application.DocumentManager.MdiActiveDocument;
@@ -83,6 +83,7 @@ public class AutocadDocumentStore : DocumentModelStore
return;
}
_autocadDocumentManager.WriteModelCards(doc, modelCardState);
string modelCardsString = Serialize();
_autocadDocumentManager.WriteModelCards(doc, modelCardsString);
}
}
@@ -2,7 +2,6 @@ using Autodesk.AutoCAD.DatabaseServices;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Conversion;
using Speckle.Sdk;
using Speckle.Sdk.Dependencies;
using Speckle.Sdk.Models.Proxies;
namespace Speckle.Connectors.Autocad.HostApp;
@@ -30,12 +29,12 @@ public class AutocadGroupBaker
/// <param name="applicationIdMap"></param>
/// <returns></returns>
// TODO: Oguzhan! Do not report here too! But this is TBD that we don't know the shape of the report yet.
public IReadOnlyCollection<ReceiveConversionResult> CreateGroups(
public List<ReceiveConversionResult> CreateGroups(
IEnumerable<GroupProxy> groupProxies,
Dictionary<string, IReadOnlyCollection<Entity>> applicationIdMap
Dictionary<string, List<Entity>> applicationIdMap
)
{
HashSet<ReceiveConversionResult> results = new();
List<ReceiveConversionResult> results = new();
using var groupCreationTransaction =
Application.DocumentManager.CurrentDocument.Database.TransactionManager.StartTransaction();
@@ -76,6 +75,6 @@ public class AutocadGroupBaker
groupCreationTransaction.Commit();
return results.Freeze();
return results;
}
}
@@ -12,7 +12,10 @@ public sealed class AutocadIdleManager(IIdleCallManager idleCallManager)
{
private readonly IIdleCallManager _idleCallManager = idleCallManager;
protected override void AddEvent() => Application.Idle += AutocadAppOnIdle;
protected override void AddEvent()
{
Application.Idle += AutocadAppOnIdle;
}
private void AutocadAppOnIdle(object? sender, EventArgs e) =>
_idleCallManager.AppOnIdle(() => Application.Idle -= AutocadAppOnIdle);
@@ -12,7 +12,6 @@ using Speckle.DoubleNumerics;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Dependencies;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
@@ -23,19 +22,19 @@ namespace Speckle.Connectors.Autocad.HostApp;
/// <summary>
/// Expects to be a scoped dependency receive operation.
/// </summary>
public class AutocadInstanceBaker : IInstanceBaker<IReadOnlyCollection<Entity>>
public class AutocadInstanceBaker : IInstanceBaker<List<Entity>>
{
private readonly AutocadLayerBaker _layerBaker;
private readonly IAutocadColorBaker _colorBaker;
private readonly IAutocadMaterialBaker _materialBaker;
private readonly AutocadColorBaker _colorBaker;
private readonly AutocadMaterialBaker _materialBaker;
private readonly AutocadContext _autocadContext;
private readonly ILogger<AutocadInstanceBaker> _logger;
private readonly IConverterSettingsStore<AutocadConversionSettings> _converterSettings;
public AutocadInstanceBaker(
AutocadLayerBaker layerBaker,
IAutocadColorBaker colorBaker,
IAutocadMaterialBaker materialBaker,
AutocadColorBaker colorBaker,
AutocadMaterialBaker materialBaker,
AutocadContext autocadContext,
ILogger<AutocadInstanceBaker> logger,
IConverterSettingsStore<AutocadConversionSettings> converterSettings
@@ -50,9 +49,9 @@ public class AutocadInstanceBaker : IInstanceBaker<IReadOnlyCollection<Entity>>
}
[SuppressMessage("Maintainability", "CA1506:Avoid excessive class coupling")]
public BakeResult BakeInstances(
ICollection<(Collection[] collectionPath, IInstanceComponent obj)> instanceComponents,
Dictionary<string, IReadOnlyCollection<Entity>> applicationIdMap,
public async Task<BakeResult> BakeInstances(
IReadOnlyCollection<(Collection[] collectionPath, IInstanceComponent obj)> instanceComponents,
Dictionary<string, List<Entity>> applicationIdMap,
string baseLayerName,
IProgress<CardProgress> onOperationProgressed
)
@@ -65,9 +64,9 @@ public class AutocadInstanceBaker : IInstanceBaker<IReadOnlyCollection<Entity>>
var definitionIdAndApplicationIdMap = new Dictionary<string, ObjectId>();
using var transaction = Application.DocumentManager.CurrentDocument.Database.TransactionManager.StartTransaction();
var conversionResults = new HashSet<ReceiveConversionResult>();
var createdObjectIds = new HashSet<string>();
var consumedObjectIds = new HashSet<string>();
var conversionResults = new List<ReceiveConversionResult>();
var createdObjectIds = new List<string>();
var consumedObjectIds = new List<string>();
var count = 0;
foreach (var (collectionPath, instanceOrDefinition) in sortedInstanceComponents)
@@ -80,9 +79,7 @@ public class AutocadInstanceBaker : IInstanceBaker<IReadOnlyCollection<Entity>>
{
// TODO: create definition (block table record)
var constituentEntities = definitionProxy
.objects.Select(id =>
applicationIdMap.TryGetValue(id, out IReadOnlyCollection<Entity>? value) ? value : null
)
.objects.Select(id => applicationIdMap.TryGetValue(id, out List<Entity>? value) ? value : null)
.Where(x => x is not null)
.SelectMany(ent => ent!)
.ToList();
@@ -112,8 +109,8 @@ public class AutocadInstanceBaker : IInstanceBaker<IReadOnlyCollection<Entity>>
definitionIdAndApplicationIdMap[definitionProxy.applicationId] = id;
transaction.AddNewlyCreatedDBObject(record, true);
var consumedEntitiesHandleValues = constituentEntities.Select(ent => ent.GetSpeckleApplicationId()).ToArray();
consumedObjectIds.UnionWith(consumedEntitiesHandleValues);
createdObjectIds.RemoveWhere(newId => consumedEntitiesHandleValues.Contains(newId));
consumedObjectIds.AddRange(consumedEntitiesHandleValues);
createdObjectIds.RemoveAll(newId => consumedEntitiesHandleValues.Contains(newId));
}
else if (
instanceOrDefinition is InstanceProxy instanceProxy
@@ -131,7 +128,7 @@ public class AutocadInstanceBaker : IInstanceBaker<IReadOnlyCollection<Entity>>
string layerName = _layerBaker.CreateLayerForReceive(collectionPath, baseLayerName);
// get color and material if any
string instanceId = instanceProxy.applicationId ?? instanceProxy.id.NotNull();
string instanceId = instanceProxy.applicationId ?? instanceProxy.id;
AutocadColor? objColor = _colorBaker.ObjectColorsIdMap.TryGetValue(instanceId, out AutocadColor? color)
? color
: null;
@@ -170,7 +167,8 @@ public class AutocadInstanceBaker : IInstanceBaker<IReadOnlyCollection<Entity>>
}
transaction.Commit();
return new(createdObjectIds.Freeze(), consumedObjectIds.Freeze(), conversionResults.Freeze());
await Task.Yield();
return new(createdObjectIds, consumedObjectIds, conversionResults);
}
/// <summary>
@@ -101,23 +101,6 @@ public class AutocadInstanceUnpacker : IInstanceUnpacker<AutocadRootObject>
instanceProxiesWithSameDefinition.Add(_instanceObjectsManager.GetInstanceProxy(instanceId));
// Add text attributes from Instances as separate atomic objects:
// AttributeReferences found on Instances are just a text, not a part of the Instance
// They are not actually references and are not linked to AttributeDefinition (as one would expect),
// and already have the correct position (no need for transforms).
// We don't want to create a new BlockDefinition for every changed text for now, because AutoCAD API doesn't provide one,
// e.g. AnonymousBlockTableRecord is provided for each dynamic blocks with geometry changes, but not for Attribute changes.
// Docs on AttributeReference usage (used totally independent of AttributeDefinition): https://help.autodesk.com/view/OARX/2025/ENU/?guid=GUID-BA69D85A-2AED-43C2-B5B7-73022B5F28F8
// Case of trying to match AttributeDefinition with AttributeReference via Tag value (which is not unique): https://forums.autodesk.com/t5/net-forum/get-the-value-of-an-attribute-in-c/td-p/9060940
foreach (ObjectId id in instance.AttributeCollection)
{
var reference = (AttributeReference)transaction.GetObject(id, OpenMode.ForRead);
string refAppId = reference.GetSpeckleApplicationId();
_instanceObjectsManager.AddAtomicObject(refAppId, new(reference, refAppId));
}
// rely on already converted Definition
if (
_instanceObjectsManager.TryGetInstanceDefinitionProxy(
definitionId.ToString(),
@@ -148,12 +131,12 @@ public class AutocadInstanceUnpacker : IInstanceUnpacker<AutocadRootObject>
{
Entity obj = (Entity)transaction.GetObject(id, OpenMode.ForRead);
// In the case of dynamic blocks, this prevents sending objects that are not visible in its current state.
// Also skipping AttributeDefinition because it only contains default text values. We convert AttributeReference above instead, as a separate object.
if (!obj.Visible || obj is AttributeDefinition)
// In the case of dynamic blocks, this prevents sending objects that are not visibile in its current state.
if (!obj.Visible)
{
continue;
}
string appId = obj.GetSpeckleApplicationId();
definitionProxy.objects.Add(appId);
@@ -2,7 +2,6 @@
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.LayerManager;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models.Collections;
using AutocadColor = Autodesk.AutoCAD.Colors.Color;
@@ -12,15 +11,15 @@ public class AutocadLayerBaker : TraversalContextUnpacker
{
private readonly string _layerFilterName = "Speckle";
private readonly AutocadContext _autocadContext;
private readonly IAutocadMaterialBaker _materialBaker;
private readonly IAutocadColorBaker _colorBaker;
private readonly AutocadMaterialBaker _materialBaker;
private readonly AutocadColorBaker _colorBaker;
private Document Doc => Application.DocumentManager.MdiActiveDocument;
private readonly HashSet<string> _uniqueLayerNames = new();
public AutocadLayerBaker(
AutocadContext autocadContext,
IAutocadMaterialBaker materialBaker,
IAutocadColorBaker colorBaker
AutocadMaterialBaker materialBaker,
AutocadColorBaker colorBaker
)
{
_autocadContext = autocadContext;
@@ -53,7 +52,7 @@ public class AutocadLayerBaker : TraversalContextUnpacker
// Goes up the tree to find any potential parent layer that has a material/color
for (int j = layerPath.Length - 1; j >= 0; j--)
{
string layerId = layerPath[j].applicationId ?? layerPath[j].id.NotNull();
string layerId = layerPath[j].applicationId ?? layerPath[j].id;
if (!foundColor)
{
@@ -4,10 +4,8 @@ using Autodesk.AutoCAD.GraphicsInterface;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Operations;
using Speckle.InterfaceGenerator;
using Speckle.Objects.Other;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models;
using Material = Autodesk.AutoCAD.DatabaseServices.Material;
using RenderMaterial = Speckle.Objects.Other.RenderMaterial;
@@ -17,8 +15,7 @@ namespace Speckle.Connectors.Autocad.HostApp;
/// <summary>
/// Expects to be a scoped dependency for a given operation and helps with layer creation and cleanup.
/// </summary>
[GenerateAutoInterface]
public class AutocadMaterialBaker : IAutocadMaterialBaker
public class AutocadMaterialBaker
{
private readonly ILogger<AutocadMaterialBaker> _logger;
private readonly AutocadContext _autocadContext;
@@ -45,7 +42,7 @@ public class AutocadMaterialBaker : IAutocadMaterialBaker
public bool TryGetMaterialId(Base originalObject, Base? parentObject, out ObjectId materialId)
{
materialId = ObjectId.Null;
var originalObjectId = originalObject.applicationId ?? originalObject.id.NotNull();
var originalObjectId = originalObject.applicationId ?? originalObject.id;
if (ObjectMaterialsIdMap.TryGetValue(originalObjectId, out ObjectId originalObjectMaterialId))
{
materialId = originalObjectMaterialId;
@@ -57,7 +54,7 @@ public class AutocadMaterialBaker : IAutocadMaterialBaker
return false;
}
var subObjectId = parentObject.applicationId ?? parentObject.id.NotNull();
var subObjectId = parentObject.applicationId ?? parentObject.id;
if (ObjectMaterialsIdMap.TryGetValue(subObjectId, out ObjectId subObjectMaterialId))
{
materialId = subObjectMaterialId;
@@ -94,8 +91,8 @@ public class AutocadMaterialBaker : IAutocadMaterialBaker
transaction.Commit();
}
public void ParseAndBakeRenderMaterials(
IReadOnlyCollection<RenderMaterialProxy> materialProxies,
public async Task ParseAndBakeRenderMaterials(
List<RenderMaterialProxy> materialProxies,
string baseLayerPrefix,
IProgress<CardProgress> onOperationProgressed
)
@@ -117,7 +114,7 @@ public class AutocadMaterialBaker : IAutocadMaterialBaker
// bake render material
RenderMaterial renderMaterial = materialProxy.value;
string renderMaterialId = renderMaterial.applicationId ?? renderMaterial.id.NotNull();
string renderMaterialId = renderMaterial.applicationId ?? renderMaterial.id;
ObjectId materialId = ObjectId.Null;
if (!ObjectMaterialsIdMap.TryGetValue(renderMaterialId, out materialId))
@@ -143,6 +140,7 @@ public class AutocadMaterialBaker : IAutocadMaterialBaker
}
transaction.Commit();
await Task.Yield();
}
private (ObjectId, ReceiveConversionResult) BakeMaterial(
@@ -158,7 +156,7 @@ public class AutocadMaterialBaker : IAutocadMaterialBaker
{
// POC: Currently we're relying on the render material name for identification if it's coming from speckle and from which model; could we do something else?
// POC: we should assume render materials all have application ids?
string renderMaterialId = renderMaterial.applicationId ?? renderMaterial.id.NotNull();
string renderMaterialId = renderMaterial.applicationId ?? renderMaterial.id;
string matName = _autocadContext.RemoveInvalidChars(
$"{renderMaterial.name}-({renderMaterialId})-{baseLayerPrefix}"
);
@@ -3,13 +3,10 @@ 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;
using Speckle.Sdk.Common;
using Speckle.Sdk.Dependencies;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
@@ -20,44 +17,84 @@ namespace Speckle.Connectors.Autocad.Operations.Receive;
/// <summary>
/// <para>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
) : IHostObjectBuilder
public class AutocadHostObjectBuilder : IHostObjectBuilder
{
public Task<HostObjectBuilderResult> Build(
private readonly AutocadLayerBaker _layerBaker;
private readonly IRootToHostConverter _converter;
private readonly ISyncToThread _syncToThread;
private readonly AutocadGroupBaker _groupBaker;
private readonly AutocadMaterialBaker _materialBaker;
private readonly AutocadColorBaker _colorBaker;
private readonly AutocadInstanceBaker _instanceBaker;
private readonly AutocadContext _autocadContext;
private readonly RootObjectUnpacker _rootObjectUnpacker;
public AutocadHostObjectBuilder(
IRootToHostConverter converter,
AutocadLayerBaker layerBaker,
AutocadGroupBaker groupBaker,
AutocadInstanceBaker instanceBaker,
AutocadMaterialBaker materialBaker,
AutocadColorBaker colorBaker,
ISyncToThread syncToThread,
AutocadContext autocadContext,
RootObjectUnpacker rootObjectUnpacker
)
{
_converter = converter;
_layerBaker = layerBaker;
_groupBaker = groupBaker;
_instanceBaker = instanceBaker;
_materialBaker = materialBaker;
_colorBaker = colorBaker;
_syncToThread = syncToThread;
_autocadContext = autocadContext;
_rootObjectUnpacker = rootObjectUnpacker;
}
public async Task<HostObjectBuilderResult> Build(
Base rootObject,
string projectName,
string modelName,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
CancellationToken _
)
{
// NOTE: This is the only place we apply ISyncToThread across connectors. We need to sync up with main thread here
// after GetObject and Deserialization. It is anti-pattern now. Happiness level 3/10 but works.
return await _syncToThread
.RunOnThread(
async () => await BuildImpl(rootObject, projectName, modelName, onOperationProgressed).ConfigureAwait(false)
)
.ConfigureAwait(false);
}
private async Task<HostObjectBuilderResult> BuildImpl(
Base rootObject,
string projectName,
string modelName,
IProgress<CardProgress> onOperationProgressed
)
{
// 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);
_layerBaker.CreateLayerFilter(projectName, modelName);
// 0 - Clean then Rock n Roll!
string baseLayerPrefix = autocadContext.RemoveInvalidChars($"SPK-{projectName}-{modelName}-");
string baseLayerPrefix = _autocadContext.RemoveInvalidChars($"SPK-{projectName}-{modelName}-");
PreReceiveDeepClean(baseLayerPrefix);
// 1 - Unpack objects and proxies from root commit object
var unpackedRoot = rootObjectUnpacker.Unpack(rootObject);
var unpackedRoot = _rootObjectUnpacker.Unpack(rootObject);
// 2 - Split atomic objects and instance components with their path
var (atomicObjects, instanceComponents) = rootObjectUnpacker.SplitAtomicObjectsAndInstances(
var (atomicObjects, instanceComponents) = _rootObjectUnpacker.SplitAtomicObjectsAndInstances(
unpackedRoot.ObjectsToConvert
);
var atomicObjectsWithPath = layerBaker.GetAtomicObjectsWithPath(atomicObjects);
var instanceComponentsWithPath = layerBaker.GetInstanceComponentsWithPath(instanceComponents);
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)
@@ -71,35 +108,33 @@ public class AutocadHostObjectBuilder(
// 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
);
await _materialBaker
.ParseAndBakeRenderMaterials(unpackedRoot.RenderMaterialProxies, baseLayerPrefix, onOperationProgressed)
.ConfigureAwait(true);
}
if (unpackedRoot.ColorProxies != null)
{
colorBaker.ParseColors(unpackedRoot.ColorProxies, onOperationProgressed);
await _colorBaker.ParseColors(unpackedRoot.ColorProxies, onOperationProgressed).ConfigureAwait(true);
}
// 4 - Convert atomic objects
HashSet<ReceiveConversionResult> results = new();
HashSet<string> bakedObjectIds = new();
Dictionary<string, IReadOnlyCollection<Entity>> applicationIdMap = new();
// 5 - Convert atomic objects
List<ReceiveConversionResult> results = new();
List<string> bakedObjectIds = new();
Dictionary<string, List<Entity>> applicationIdMap = new();
var count = 0;
foreach (var (layerPath, atomicObject) in atomicObjectsWithPath)
{
string objectId = atomicObject.applicationId ?? atomicObject.id.NotNull();
string objectId = atomicObject.applicationId ?? atomicObject.id;
onOperationProgressed.Report(new("Converting objects", (double)++count / atomicObjects.Count));
cancellationToken.ThrowIfCancellationRequested();
try
{
IReadOnlyCollection<Entity> convertedObjects = ConvertObject(atomicObject, layerPath, baseLayerPrefix);
List<Entity> convertedObjects = await ConvertObject(atomicObject, layerPath, baseLayerPrefix)
.ConfigureAwait(true);
applicationIdMap[objectId] = convertedObjects;
results.UnionWith(
results.AddRange(
convertedObjects.Select(e => new ReceiveConversionResult(
Status.SUCCESS,
atomicObject,
@@ -108,7 +143,7 @@ public class AutocadHostObjectBuilder(
))
);
bakedObjectIds.UnionWith(convertedObjects.Select(e => e.GetSpeckleApplicationId()));
bakedObjectIds.AddRange(convertedObjects.Select(e => e.GetSpeckleApplicationId()));
}
catch (Exception ex) when (!ex.IsFatal())
{
@@ -116,80 +151,72 @@ public class AutocadHostObjectBuilder(
}
}
// 5 - Convert instances
var (createdInstanceIds, consumedObjectIds, instanceConversionResults) = instanceBaker.BakeInstances(
instanceComponentsWithPath,
applicationIdMap,
baseLayerPrefix,
onOperationProgressed
);
// 6 - Convert instances
var (createdInstanceIds, consumedObjectIds, instanceConversionResults) = await _instanceBaker
.BakeInstances(instanceComponentsWithPath, applicationIdMap, baseLayerPrefix, onOperationProgressed)
.ConfigureAwait(true);
bakedObjectIds.RemoveWhere(id => consumedObjectIds.Contains(id));
bakedObjectIds.UnionWith(createdInstanceIds);
results.RemoveWhere(result => result.ResultId != null && consumedObjectIds.Contains(result.ResultId));
results.UnionWith(instanceConversionResults);
bakedObjectIds.RemoveAll(id => consumedObjectIds.Contains(id));
bakedObjectIds.AddRange(createdInstanceIds);
results.RemoveAll(result => result.ResultId != null && consumedObjectIds.Contains(result.ResultId));
results.AddRange(instanceConversionResults);
// 6 - Create groups
// 7 - Create groups
if (unpackedRoot.GroupProxies != null)
{
IReadOnlyCollection<ReceiveConversionResult> groupResults = groupBaker.CreateGroups(
List<ReceiveConversionResult> groupResults = _groupBaker.CreateGroups(
unpackedRoot.GroupProxies,
applicationIdMap
);
results.UnionWith(groupResults);
results.AddRange(groupResults);
}
return Task.FromResult(new HostObjectBuilderResult(bakedObjectIds, results));
return new HostObjectBuilderResult(bakedObjectIds, results);
}
private void PreReceiveDeepClean(string baseLayerPrefix)
{
layerBaker.DeleteAllLayersByPrefix(baseLayerPrefix);
instanceBaker.PurgeInstances(baseLayerPrefix);
materialBaker.PurgeMaterials(baseLayerPrefix);
_layerBaker.DeleteAllLayersByPrefix(baseLayerPrefix);
_instanceBaker.PurgeInstances(baseLayerPrefix);
_materialBaker.PurgeMaterials(baseLayerPrefix);
}
private IReadOnlyCollection<Entity> ConvertObject(Base obj, Collection[] layerPath, string baseLayerNamePrefix)
private async Task<List<Entity>> ConvertObject(Base obj, Collection[] layerPath, string baseLayerNamePrefix)
{
string layerName = layerBaker.CreateLayerForReceive(layerPath, baseLayerNamePrefix);
var convertedEntities = new HashSet<Entity>();
string layerName = _layerBaker.CreateLayerForReceive(layerPath, baseLayerNamePrefix);
var convertedEntities = new List<Entity>();
using var tr = Application.DocumentManager.CurrentDocument.Database.TransactionManager.StartTransaction();
// 1: convert
var converted = converter.Convert(obj);
var converted = _converter.Convert(obj);
// 2: handle result
switch (converted)
if (converted is Entity entity)
{
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;
var bakedEntity = BakeObject(entity, obj, layerName);
convertedEntities.Add(bakedEntity);
}
else if (converted is IEnumerable<(object, Base)> fallbackConversionResult)
{
var bakedFallbackEntities = BakeObjectsAsGroup(fallbackConversionResult, obj, layerName, baseLayerNamePrefix);
convertedEntities.AddRange(bakedFallbackEntities);
}
tr.Commit();
return convertedEntities.Freeze();
await Task.Delay(10).ConfigureAwait(true);
return convertedEntities;
}
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))
var objId = originalObject.applicationId ?? originalObject.id;
if (_colorBaker.ObjectColorsIdMap.TryGetValue(objId, out AutocadColor? color))
{
entity.Color = color;
}
if (materialBaker.TryGetMaterialId(originalObject, parentObject, out ObjectId matId))
if (_materialBaker.TryGetMaterialId(originalObject, parentObject, out ObjectId matId))
{
entity.MaterialId = matId;
}
@@ -199,7 +226,7 @@ public class AutocadHostObjectBuilder(
}
private List<Entity> BakeObjectsAsGroup(
List<(Entity, Base)> fallbackConversionResult,
IEnumerable<(object, Base)> fallbackConversionResult,
Base parentObject,
string layerName,
string baseLayerName
@@ -209,21 +236,22 @@ public class AutocadHostObjectBuilder(
var entities = new List<Entity>();
foreach (var (conversionResult, originalObject) in fallbackConversionResult)
{
BakeObject(conversionResult, originalObject, layerName, parentObject);
ids.Add(conversionResult.ObjectId);
entities.Add(conversionResult);
}
if (conversionResult is not Entity entity)
{
// TODO: throw?
continue;
}
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;
BakeObject(entity, originalObject, layerName, parentObject);
ids.Add(entity.ObjectId);
entities.Add(entity);
}
var tr = Application.DocumentManager.CurrentDocument.Database.TransactionManager.TopTransaction;
var groupDictionary = (DBDictionary)
tr.GetObject(Application.DocumentManager.CurrentDocument.Database.GroupDictionaryId, OpenMode.ForWrite);
var groupName = autocadContext.RemoveInvalidChars(
var groupName = _autocadContext.RemoveInvalidChars(
$@"{parentObject.speckle_type.Split('.').Last()} - {parentObject.applicationId ?? parentObject.id} ({baseLayerName})"
);
@@ -49,6 +49,13 @@ public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadR
_activityFactory = activityFactory;
}
public Task<RootObjectBuilderResult> Build(
IReadOnlyList<AutocadRootObject> objects,
SendInfo sendInfo,
IProgress<CardProgress> onOperationProgressed,
CancellationToken ct = default
) => Task.FromResult(BuildSync(objects, sendInfo, onOperationProgressed, ct));
[SuppressMessage(
"Maintainability",
"CA1506:Avoid excessive class coupling",
@@ -58,11 +65,11 @@ public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadR
proxy classes yet. So I'm supressing this one now!!!
"""
)]
public Task<RootObjectBuilderResult> Build(
private RootObjectBuilderResult BuildSync(
IReadOnlyList<AutocadRootObject> objects,
SendInfo sendInfo,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
CancellationToken ct = default
)
{
// 0 - Init the root
@@ -94,7 +101,7 @@ public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadR
int count = 0;
foreach (var (entity, applicationId) in atomicObjects)
{
cancellationToken.ThrowIfCancellationRequested();
ct.ThrowIfCancellationRequested();
using (var convertActivity = _activityFactory.Start("Converting object"))
{
// Create and add a collection for this entity if not done so already.
@@ -133,7 +140,7 @@ public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadR
// add any additional properties (most likely from verticals)
AddAdditionalProxiesToRoot(root);
return Task.FromResult(new RootObjectBuilderResult(root, results));
return new RootObjectBuilderResult(root, results);
}
}
@@ -1,10 +1,10 @@
using Speckle.Connectors.Common;
using Speckle.Sdk.Host;
namespace Speckle.Connectors.Autocad.Plugin;
public static class AppUtils
{
public static Speckle.Sdk.Application App =>
public static HostApplication App =>
#if CIVIL3D
HostApplications.Civil3D;
#elif AUTOCAD
@@ -14,9 +14,7 @@ public static class AppUtils
#endif
public static HostAppVersion Version =>
#if AUTOCAD2026 || CIVIL3D2026
HostAppVersion.v2026;
#elif AUTOCAD2025 || CIVIL3D2025
#if AUTOCAD2025 || CIVIL3D2025
HostAppVersion.v2025;
#elif AUTOCAD2024 || CIVIL3D2024
HostAppVersion.v2024;
@@ -31,7 +31,7 @@ public class AutocadCommand
return;
}
PaletteSet = new PaletteSet($"Speckle (Beta)", s_id)
PaletteSet = new PaletteSet($"Speckle (Beta) for {AppUtils.App.Name}", s_id)
{
Size = new Size(400, 500),
DockEnabled = (DockSides)((int)DockSides.Left + (int)DockSides.Right)
@@ -52,7 +52,7 @@ public class AutocadCommand
var panelWebView = Container.GetRequiredService<DUI3ControlWebView>();
PaletteSet.AddVisual("Speckle (Beta)", panelWebView);
PaletteSet.AddVisual($"Speckle (Beta) for {AppUtils.App.Name} WebView", panelWebView);
FocusPalette();
}
@@ -1,11 +1,9 @@
using System.IO;
using System.Reflection;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Autodesk.Windows;
using Speckle.Sdk;
#if !AUTOCAD2025_OR_GREATER && !CIVIL3D2025_OR_GREATER
using System.IO;
#endif
namespace Speckle.Connectors.Autocad.Plugin;
@@ -48,20 +46,20 @@ public class AutocadRibbon
private void Create()
{
RibbonTab tab = FindOrMakeTab("Speckle");
RibbonPanelSource source = new() { Title = "Speckle (Beta)" };
RibbonTab tab = FindOrMakeTab("Add-ins");
RibbonPanelSource source = new() { Title = "Speckle 2 (New Beta)" };
RibbonPanel panel = new() { Source = source };
tab.Panels.Add(panel);
RibbonToolTip speckleToolTip =
new()
{
Title = "Speckle (Beta)",
Content = $"Next Gen Speckle Connector (Beta) for {AppUtils.App.Name}",
Title = "Speckle 2 (New Beta)",
Content = "Speckle Connector for " + AppUtils.App.Name,
IsHelpEnabled = true // Without this "Press F1 for help" does not appear in the tooltip
};
_ = CreateSpeckleButton("Speckle (Beta)", source, null, speckleToolTip, "logo");
_ = CreateSpeckleButton("Connector " + AppUtils.App.Name + " (New)", source, null, speckleToolTip, "logo");
}
private void ComponentManager_ItemInitialized(object? sender, RibbonItemEventArgs e)
@@ -9,7 +9,6 @@
<Import_RootNamespace>Speckle.Connectors.AutocadShared</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Bindings\AutocadReceiveBaseBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\AutocadSelectionBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\AutocadReceiveBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\AutocadSendBinding.cs" />
@@ -22,7 +21,6 @@
<Compile Include="$(MSBuildThisFileDirectory)HostApp\AutocadColorUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\AutocadGroupBaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\AutocadGroupUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\AutocadIdleManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\AutocadInstanceBaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\AutocadInstanceUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\AutocadLayerBaker.cs" />
@@ -31,6 +29,7 @@
<Compile Include="$(MSBuildThisFileDirectory)HostApp\AutocadContext.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\AutocadDocumentManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\AutocadDocumentModelStore.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\AutocadIdleManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\AutocadMaterialUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Extensions\DatabaseExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Extensions\DocumentExtensions.cs" />
@@ -6,10 +6,12 @@
<Civil3DVersion>2022</Civil3DVersion>
<DefineConstants>$(DefineConstants);CIVIL3D;CIVIL3D2022;CIVIL3D2022_OR_GREATER</DefineConstants>
<Configurations>Debug;Release;Local</Configurations>
<StartAction>Program</StartAction>
<StartProgram>$(ProgramW6432)\Autodesk\AutoCAD $(Civil3DVersion)\acad.exe</StartProgram>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2022.0.2" ExcludeAssets="runtime"/>
<PackageReference Include="Speckle.Civil3D.API" VersionOverride="2022.0.2" ExcludeAssets="runtime"/>
<PackageReference Include="Speckle.AutoCAD.API" />
<PackageReference Include="Speckle.Civil3D.API" />
</ItemGroup>
<ItemGroup>
@@ -173,6 +173,11 @@
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Speckle.DoubleNumerics": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "MzEQ1Im0zTja+tEsdRIk/WlPiKqb22NmTOJcR1ZKm/mz46pezyyID3/wRz6vJUELMpSLnG7LhsxBL+nxbr7V0w=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
@@ -268,16 +273,19 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )",
"System.Threading.Tasks.Dataflow": "[6.0.0, )"
}
},
"speckle.connectors.dui.webview": {
@@ -302,7 +310,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -338,26 +346,20 @@
"resolved": "1.0.1938.49",
"contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "r6GGA7Pq4CPGYOu6OmmLW/hhhREo/l44RUFhsoYmpHNGpOg1UjF45dwyfLgaU38wrNxQoURjLUQDkxGfhb8liA==",
"dependencies": {
"Speckle.Sdk": "3.3.5"
"Speckle.Sdk": "3.1.0-dev.181"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "VmRRphUJm+qfcjtdzxkTstAD0opaIP57tP6GJ2YnalyRj3wBTGBQuy7gwhGjWyAvsMnkugpKFEfAVuNm/zHQ2A==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -365,16 +367,22 @@
"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.DoubleNumerics": "4.0.1",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.3.5"
"Speckle.Sdk.Dependencies": "3.1.0-dev.181"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "W7Nb3hAPlLXGZmEFWdf9vyhXKGZk3M+lypEKB+IWQVwr3NL+bzPgfw2130kTuMr/9zGx8jUbSfx2ulTql9mZxw=="
},
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "+tyDCU3/B1lDdOOAJywHQoFwyXIUghIaP2BxG79uvhfTnO+D9qIgjVlL/JV2NTliYbMHpd6eKDmHp2VHpij7MA=="
}
}
}
@@ -6,11 +6,13 @@
<Civil3DVersion>2023</Civil3DVersion>
<DefineConstants>$(DefineConstants);CIVIL3D;CIVIL3D2023;CIVIL3D2022_OR_GREATER;CIVIL3D2023_OR_GREATER</DefineConstants>
<Configurations>Debug;Release;Local</Configurations>
<StartAction>Program</StartAction>
<StartProgram>$(ProgramW6432)\Autodesk\AutoCAD $(Civil3DVersion)\acad.exe</StartProgram>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2023.0.0" ExcludeAssets="runtime"/>
<PackageReference Include="Speckle.Civil3D.API" VersionOverride="2023.0.0" ExcludeAssets="runtime"/>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2023.0.0" />
<PackageReference Include="Speckle.Civil3D.API" VersionOverride="2023.0.0" />
</ItemGroup>
<ItemGroup>
@@ -173,6 +173,11 @@
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Speckle.DoubleNumerics": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "MzEQ1Im0zTja+tEsdRIk/WlPiKqb22NmTOJcR1ZKm/mz46pezyyID3/wRz6vJUELMpSLnG7LhsxBL+nxbr7V0w=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
@@ -268,16 +273,19 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )",
"System.Threading.Tasks.Dataflow": "[6.0.0, )"
}
},
"speckle.connectors.dui.webview": {
@@ -302,7 +310,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -338,26 +346,20 @@
"resolved": "1.0.1938.49",
"contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "r6GGA7Pq4CPGYOu6OmmLW/hhhREo/l44RUFhsoYmpHNGpOg1UjF45dwyfLgaU38wrNxQoURjLUQDkxGfhb8liA==",
"dependencies": {
"Speckle.Sdk": "3.3.5"
"Speckle.Sdk": "3.1.0-dev.181"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "VmRRphUJm+qfcjtdzxkTstAD0opaIP57tP6GJ2YnalyRj3wBTGBQuy7gwhGjWyAvsMnkugpKFEfAVuNm/zHQ2A==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -365,16 +367,22 @@
"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.DoubleNumerics": "4.0.1",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.3.5"
"Speckle.Sdk.Dependencies": "3.1.0-dev.181"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "W7Nb3hAPlLXGZmEFWdf9vyhXKGZk3M+lypEKB+IWQVwr3NL+bzPgfw2130kTuMr/9zGx8jUbSfx2ulTql9mZxw=="
},
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "+tyDCU3/B1lDdOOAJywHQoFwyXIUghIaP2BxG79uvhfTnO+D9qIgjVlL/JV2NTliYbMHpd6eKDmHp2VHpij7MA=="
}
}
}
@@ -6,11 +6,13 @@
<Civil3DVersion>2024</Civil3DVersion>
<DefineConstants>$(DefineConstants);CIVIL3D;CIVIL3D2024;CIVIL3D2022_OR_GREATER;CIVIL3D2023_OR_GREATER;CIVIL3D2024_OR_GREATER</DefineConstants>
<Configurations>Debug;Release;Local</Configurations>
<StartAction>Program</StartAction>
<StartProgram>$(ProgramW6432)\Autodesk\AutoCAD $(Civil3DVersion)\acad.exe</StartProgram>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2024.0.0" ExcludeAssets="runtime"/>
<PackageReference Include="Speckle.Civil3D.API" VersionOverride="2024.0.0" ExcludeAssets="runtime"/>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2024.0.0" />
<PackageReference Include="Speckle.Civil3D.API" VersionOverride="2024.0.0" />
</ItemGroup>
<ItemGroup>
@@ -173,6 +173,11 @@
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Speckle.DoubleNumerics": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "MzEQ1Im0zTja+tEsdRIk/WlPiKqb22NmTOJcR1ZKm/mz46pezyyID3/wRz6vJUELMpSLnG7LhsxBL+nxbr7V0w=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
@@ -268,16 +273,19 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )",
"System.Threading.Tasks.Dataflow": "[6.0.0, )"
}
},
"speckle.connectors.dui.webview": {
@@ -302,7 +310,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -338,26 +346,20 @@
"resolved": "1.0.1938.49",
"contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "r6GGA7Pq4CPGYOu6OmmLW/hhhREo/l44RUFhsoYmpHNGpOg1UjF45dwyfLgaU38wrNxQoURjLUQDkxGfhb8liA==",
"dependencies": {
"Speckle.Sdk": "3.3.5"
"Speckle.Sdk": "3.1.0-dev.181"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "VmRRphUJm+qfcjtdzxkTstAD0opaIP57tP6GJ2YnalyRj3wBTGBQuy7gwhGjWyAvsMnkugpKFEfAVuNm/zHQ2A==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -365,16 +367,22 @@
"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.DoubleNumerics": "4.0.1",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.3.5"
"Speckle.Sdk.Dependencies": "3.1.0-dev.181"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "W7Nb3hAPlLXGZmEFWdf9vyhXKGZk3M+lypEKB+IWQVwr3NL+bzPgfw2130kTuMr/9zGx8jUbSfx2ulTql9mZxw=="
},
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "+tyDCU3/B1lDdOOAJywHQoFwyXIUghIaP2BxG79uvhfTnO+D9qIgjVlL/JV2NTliYbMHpd6eKDmHp2VHpij7MA=="
}
}
}
@@ -1,21 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<UseWpf>true</UseWpf>
<Civil3DVersion>2025</Civil3DVersion>
<DefineConstants>$(DefineConstants);CIVIL3D2025;CIVIL3D;CIVIL3D2022_OR_GREATER;CIVIL3D2023_OR_GREATER;CIVIL3D2024_OR_GREATER;CIVIL3D2025_OR_GREATER</DefineConstants>
<Configurations>Debug;Release;Local</Configurations>
</PropertyGroup>
<PropertyGroup>
<!-- .NET Core uses this to move native dependencies into a root for runtime selection and usage for non-windows development https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#enablewindowstargeting -->
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <!--This is needed for managed dependencies-->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <!--This is needed for the rest-->
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <!--This is needed just to keep folder paths the same as the netframework versions of autocad/civil-->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <!-- .NET Core uses this to move native dependencies into a root for runtime selection and usage for non-windows development https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#enablewindowstargeting -->
<StartAction>Program</StartAction>
<StartProgram>$(ProgramW6432)\Autodesk\AutoCAD $(Civil3DVersion)\acad.exe</StartProgram>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2025.0.0" ExcludeAssets="runtime"/>
<PackageReference Include="Speckle.Civil3d.API" VersionOverride="2025.0.0" ExcludeAssets="runtime"/>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2025.0.0" />
<PackageReference Include="Speckle.Civil3d.API" VersionOverride="2025.0.0" />
<FrameworkReference Include="Microsoft.WindowsDesktop.App" />
</ItemGroup>
<ItemGroup>
@@ -164,6 +164,11 @@
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Speckle.DoubleNumerics": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "MzEQ1Im0zTja+tEsdRIk/WlPiKqb22NmTOJcR1ZKm/mz46pezyyID3/wRz6vJUELMpSLnG7LhsxBL+nxbr7V0w=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
@@ -224,16 +229,19 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"Speckle.Sdk": "[3.1.0-dev.181, )",
"Speckle.Sdk.Dependencies": "[3.1.0-dev.181, )",
"System.Threading.Tasks.Dataflow": "[6.0.0, )"
}
},
"speckle.connectors.dui.webview": {
@@ -259,7 +267,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.3.5, )"
"Speckle.Objects": "[3.1.0-dev.181, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -295,42 +303,42 @@
"resolved": "1.0.1938.49",
"contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "r6GGA7Pq4CPGYOu6OmmLW/hhhREo/l44RUFhsoYmpHNGpOg1UjF45dwyfLgaU38wrNxQoURjLUQDkxGfhb8liA==",
"dependencies": {
"Speckle.Sdk": "3.3.5"
"Speckle.Sdk": "3.1.0-dev.181"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "VmRRphUJm+qfcjtdzxkTstAD0opaIP57tP6GJ2YnalyRj3wBTGBQuy7gwhGjWyAvsMnkugpKFEfAVuNm/zHQ2A==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.CSharp": "4.7.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.DoubleNumerics": "4.0.1",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.3.5"
"Speckle.Sdk.Dependencies": "3.1.0-dev.181"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
"requested": "[3.1.0-dev.181, )",
"resolved": "3.1.0-dev.181",
"contentHash": "W7Nb3hAPlLXGZmEFWdf9vyhXKGZk3M+lypEKB+IWQVwr3NL+bzPgfw2130kTuMr/9zGx8jUbSfx2ulTql9mZxw=="
},
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "+tyDCU3/B1lDdOOAJywHQoFwyXIUghIaP2BxG79uvhfTnO+D9qIgjVlL/JV2NTliYbMHpd6eKDmHp2VHpij7MA=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -1,30 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Civil3DVersion>2026</Civil3DVersion>
<DefineConstants>$(DefineConstants);CIVIL3D2026;CIVIL3D;CIVIL3D2022_OR_GREATER;CIVIL3D2023_OR_GREATER;CIVIL3D2024_OR_GREATER;CIVIL3D2025_OR_GREATER;CIVIL3D2026_OR_GREATER</DefineConstants>
<Configurations>Debug;Release;Local</Configurations>
</PropertyGroup>
<PropertyGroup>
<!-- .NET Core uses this to move native dependencies into a root for runtime selection and usage for non-windows development https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#enablewindowstargeting -->
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <!--This is needed for managed dependencies-->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <!--This is needed for the rest-->
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <!--This is needed just to keep folder paths the same as the netframework versions of autocad/civil-->
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2026.0.0" ExcludeAssets="runtime"/>
<PackageReference Include="Speckle.Civil3d.API" VersionOverride="2026.0.0" ExcludeAssets="runtime"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Converters\Civil3d\Speckle.Converters.Civil3d2026\Speckle.Converters.Civil3d2026.csproj" />
<ProjectReference Include="..\..\..\DUI3\Speckle.Connectors.DUI.WebView\Speckle.Connectors.DUI.WebView.csproj" />
<ProjectReference Include="..\..\..\Sdk\Speckle.Converters.Common\Speckle.Converters.Common.csproj" />
</ItemGroup>
<Import Project="..\Speckle.Connectors.Civil3dShared\Speckle.Connectors.Civil3dShared.projitems" Label="Shared" />
<Import Project="..\Speckle.Connectors.AutocadShared\Speckle.Connectors.AutocadShared.projitems" Label="Shared" />
</Project>
@@ -1,60 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Autocad.Bindings;
using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Converters.Autocad;
using Speckle.Converters.Civil3dShared;
using Speckle.Converters.Common;
using Speckle.Sdk;
namespace Speckle.Connectors.Civil3dShared.Bindings;
public sealed class Civil3dReceiveBinding : AutocadReceiveBaseBinding
{
private readonly ICivil3dConversionSettingsFactory _civil3dConversionSettingsFactory;
private readonly IAutocadConversionSettingsFactory _autocadConversionSettingsFactory;
public Civil3dReceiveBinding(
DocumentModelStore store,
IBrowserBridge parent,
ICancellationManager cancellationManager,
IServiceProvider serviceProvider,
IOperationProgressManager operationProgressManager,
ILogger<AutocadReceiveBinding> logger,
ICivil3dConversionSettingsFactory civil3dConversionSettingsFactory,
IAutocadConversionSettingsFactory autocadConversionSettingsFactory,
ISpeckleApplication speckleApplication,
IThreadContext threadContext
)
: base(
store,
parent,
cancellationManager,
serviceProvider,
operationProgressManager,
logger,
speckleApplication,
threadContext
)
{
_civil3dConversionSettingsFactory = civil3dConversionSettingsFactory;
_autocadConversionSettingsFactory = autocadConversionSettingsFactory;
}
// POC: we're registering the conversion settings for autocad here because we need the autocad conversion settings to be able to use the autocad typed converters.
// POC: We need a separate receive binding for civil3d due to using a different unit converter (needed for conversion settings construction)
protected override void InitializeSettings(IServiceProvider serviceProvider)
{
serviceProvider
.GetRequiredService<IConverterSettingsStore<Civil3dConversionSettings>>()
.Initialize(_civil3dConversionSettingsFactory.Create(Application.DocumentManager.CurrentDocument));
serviceProvider
.GetRequiredService<IConverterSettingsStore<AutocadConversionSettings>>()
.Initialize(_autocadConversionSettingsFactory.Create(Application.DocumentManager.CurrentDocument));
}
}
@@ -1,9 +1,9 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Autocad.Bindings;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
@@ -22,22 +22,21 @@ public sealed class Civil3dSendBinding : AutocadSendBaseBinding
public Civil3dSendBinding(
DocumentModelStore store,
IAutocadIdleManager idleManager,
IBrowserBridge parent,
IEnumerable<ISendFilter> sendFilters,
ICancellationManager cancellationManager,
CancellationManager cancellationManager,
IServiceProvider serviceProvider,
ISendConversionCache sendConversionCache,
IOperationProgressManager operationProgressManager,
ILogger<AutocadSendBinding> logger,
ICivil3dConversionSettingsFactory civil3dConversionSettingsFactory,
IAutocadConversionSettingsFactory autocadConversionSettingsFactory,
ISpeckleApplication speckleApplication,
IThreadContext threadContext,
ITopLevelExceptionHandler topLevelExceptionHandler,
IAppIdleManager appIdleManager
ISpeckleApplication speckleApplication
)
: base(
store,
idleManager,
parent,
sendFilters,
cancellationManager,
@@ -45,10 +44,7 @@ public sealed class Civil3dSendBinding : AutocadSendBaseBinding
sendConversionCache,
operationProgressManager,
logger,
speckleApplication,
threadContext,
topLevelExceptionHandler,
appIdleManager
speckleApplication
)
{
_civil3dConversionSettingsFactory = civil3dConversionSettingsFactory;
@@ -6,6 +6,7 @@ using Speckle.Connectors.Civil3dShared.Bindings;
using Speckle.Connectors.Civil3dShared.Operations.Send;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Converters.Civil3dShared.Helpers;
using Speckle.Converters.Civil3dShared.ToSpeckle;
using Speckle.Sdk;
@@ -16,20 +17,18 @@ public static class Civil3dConnectorModule
public static void AddCivil3d(this IServiceCollection serviceCollection)
{
serviceCollection.AddAutocadBase();
// add send
serviceCollection.LoadSend();
// register civil specific send classes
serviceCollection.AddScoped<IRootObjectBuilder<AutocadRootObject>, Civil3dRootObjectBuilder>();
serviceCollection.AddSingleton<IBinding, Civil3dSendBinding>();
// add receive
serviceCollection.LoadReceive();
serviceCollection.AddSingleton<IBinding, Civil3dReceiveBinding>();
// automatically detects the Class:IClass interface pattern to register all generated interfaces
serviceCollection.AddMatchingInterfacesAsTransient(Assembly.GetExecutingAssembly());
// additional classes
serviceCollection.AddScoped<PropertySetDefinitionHandler>();
// automatically detects the Class:IClass interface pattern to register all generated interfaces
serviceCollection.AddMatchingInterfacesAsTransient(Assembly.GetExecutingAssembly());
serviceCollection.AddScoped<CatchmentGroupHandler>();
serviceCollection.AddScoped<PipeNetworkHandler>();
}
}
@@ -4,6 +4,7 @@ using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Autocad.Operations.Send;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Operations;
using Speckle.Converters.Civil3dShared.Helpers;
using Speckle.Converters.Civil3dShared.ToSpeckle;
using Speckle.Converters.Common;
using Speckle.Sdk.Logging;
@@ -15,10 +16,14 @@ public sealed class Civil3dRootObjectBuilder : AutocadRootObjectBaseBuilder
{
private readonly AutocadLayerUnpacker _layerUnpacker;
private readonly PropertySetDefinitionHandler _propertySetDefinitionHandler;
private readonly CatchmentGroupHandler _catchmentGroupHandler;
private readonly PipeNetworkHandler _pipeNetworkHandler;
public Civil3dRootObjectBuilder(
AutocadLayerUnpacker layerUnpacker,
PropertySetDefinitionHandler propertySetDefinitionHandler,
CatchmentGroupHandler catchmentGroupHandler,
PipeNetworkHandler pipeNetworkHandler,
IRootToSpeckleConverter converter,
ISendConversionCache sendConversionCache,
AutocadInstanceUnpacker instanceObjectManager,
@@ -41,6 +46,8 @@ public sealed class Civil3dRootObjectBuilder : AutocadRootObjectBaseBuilder
{
_layerUnpacker = layerUnpacker;
_propertySetDefinitionHandler = propertySetDefinitionHandler;
_catchmentGroupHandler = catchmentGroupHandler;
_pipeNetworkHandler = pipeNetworkHandler;
}
public override (Collection, LayerTableRecord?) CreateObjectCollection(Entity entity, Transaction tr)
@@ -50,8 +57,11 @@ public sealed class Civil3dRootObjectBuilder : AutocadRootObjectBaseBuilder
return (layer, autocadLayer);
}
// POC: probably will need to add Network proxies as well
public override void AddAdditionalProxiesToRoot(Collection rootObject)
{
rootObject[ProxyKeys.PROPERTYSET_DEFINITIONS] = _propertySetDefinitionHandler.Definitions;
rootObject["catchmentGroupProxies"] = _catchmentGroupHandler.CatchmentGroupProxiesCache.Values.ToList();
rootObject["pipeNetworkProxies"] = _pipeNetworkHandler.PipeNetworkProxiesCache.Values.ToList();
}
}
@@ -9,13 +9,13 @@
<Import_RootNamespace>Speckle.Connectors.Civil3dShared</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Bindings\Civil3dReceiveBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DependencyInjection\Civil3dConnectorModule.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Civil3dRootObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\Civil3dSendBinding.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="$(MSBuildThisFileDirectory)DependencyInjection\" />
<Folder Include="$(MSBuildThisFileDirectory)Bindings\" />
<Folder Include="$(MSBuildThisFileDirectory)Operations\Send\" />
</ItemGroup>
</Project>
@@ -1,74 +0,0 @@
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Sdk;
namespace Speckle.Connectors.CSiShared.Bindings;
public class CsiSharedBasicConnectorBinding : IBasicConnectorBinding
{
private readonly ISpeckleApplication _speckleApplication;
private readonly DocumentModelStore _store;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly IThreadContext _threadContext;
public string Name => "baseBinding";
public IBrowserBridge Parent { get; }
public BasicConnectorBindingCommands Commands { get; }
public CsiSharedBasicConnectorBinding(
IBrowserBridge parent,
ISpeckleApplication speckleApplication,
DocumentModelStore store,
ITopLevelExceptionHandler topLevelExceptionHandler,
IThreadContext threadContext
)
{
_threadContext = threadContext;
Parent = parent;
_speckleApplication = speckleApplication;
_store = store;
_topLevelExceptionHandler = topLevelExceptionHandler;
Commands = new BasicConnectorBindingCommands(Parent);
_store.DocumentChanged += (_, _) =>
_topLevelExceptionHandler.FireAndForget(async () =>
{
// enforce main thread
await _threadContext.RunOnMainAsync(async () =>
{
await Commands.NotifyDocumentChanged();
});
});
}
public string GetConnectorVersion() => _speckleApplication.SpeckleVersion;
public string GetSourceApplicationName() => _speckleApplication.Slug;
public string GetSourceApplicationVersion() => _speckleApplication.HostApplicationVersion;
public DocumentInfo? GetDocumentInfo() => new("ETABS Model", "ETABS Model", "1");
public DocumentModelStore GetDocumentState() => _store;
/// <remarks>Operations must run on the main thread for ETABS and SAP 2000</remarks>
public void AddModel(ModelCard model) =>
_topLevelExceptionHandler.CatchUnhandled(() => _threadContext.RunOnThread(() => _store.AddModel(model), true));
/// <remarks>Operations must run on the main thread for ETABS and SAP 2000</remarks>
public void UpdateModel(ModelCard model) =>
_topLevelExceptionHandler.CatchUnhandled(() => _threadContext.RunOnThread(() => _store.UpdateModel(model), true));
/// <remarks>Operations must run on the main thread for ETABS and SAP 2000</remarks>
public void RemoveModel(ModelCard model) =>
_topLevelExceptionHandler.CatchUnhandled(() => _threadContext.RunOnThread(() => _store.RemoveModel(model), true));
public void RemoveModels(List<ModelCard> models) =>
_topLevelExceptionHandler.CatchUnhandled(() => _threadContext.RunOnThread(() => _store.RemoveModels(models), true));
public Task HighlightModel(string modelCardId) => Task.CompletedTask;
public Task HighlightObjects(IReadOnlyList<string> objectIds) => Task.CompletedTask;
}
@@ -1,108 +0,0 @@
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.CSiShared.Utils;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Converters.CSiShared.Utils;
using Timer = System.Timers.Timer;
namespace Speckle.Connectors.CSiShared.Bindings;
public class CsiSharedSelectionBinding : ISelectionBinding, IDisposable
{
private bool _disposed;
private readonly Timer _selectionTimer;
private readonly ICsiApplicationService _csiApplicationService;
private readonly IThreadContext _threadContext;
private HashSet<string> _lastSelection = new();
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
public IBrowserBridge Parent { get; }
public string Name => "selectionBinding";
public CsiSharedSelectionBinding(
IBrowserBridge parent,
ICsiApplicationService csiApplicationService,
ITopLevelExceptionHandler topLevelExceptionHandler,
IThreadContext threadContext
)
{
_threadContext = threadContext;
Parent = parent;
_csiApplicationService = csiApplicationService;
_topLevelExceptionHandler = topLevelExceptionHandler;
_selectionTimer = new Timer(1000);
_selectionTimer.Elapsed += (_, _) =>
_topLevelExceptionHandler.CatchUnhandled(() => _threadContext.RunOnMain(CheckSelectionChanged));
_selectionTimer.Start();
}
private void CheckSelectionChanged()
{
// timer callbacks are on a background thread, but CSI API calls must be on main thread
var currentSelection = GetSelection();
var currentIds = new HashSet<string>(currentSelection.SelectedObjectIds);
if (!_lastSelection.SetEquals(currentIds))
{
_lastSelection = currentIds;
// ensure UI updates also run on main thread
_threadContext.RunOnMain(
() =>
_topLevelExceptionHandler.CatchUnhandled(
() => Parent.Send(SelectionBindingEvents.SET_SELECTION, currentSelection)
)
);
}
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_selectionTimer?.Dispose();
}
_disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Gets the selection and creates an encoded ID (objectType and objectName).
/// </summary>
/// <remarks>
/// Refer to ObjectIdentifier.cs for more info.
/// </remarks>
public SelectionInfo GetSelection()
{
int numberItems = 0;
int[] objectType = [];
string[] objectName = [];
_csiApplicationService.SapModel.SelectObj.GetSelected(ref numberItems, ref objectType, ref objectName);
var encodedIds = new List<string>(numberItems);
var typeCounts = new Dictionary<string, int>();
for (int i = 0; i < numberItems; i++)
{
var typeKey = (ModelObjectType)objectType[i];
var typeName = typeKey.ToString();
encodedIds.Add(ObjectIdentifier.Encode(objectType[i], objectName[i]));
typeCounts[typeName] = (typeCounts.TryGetValue(typeName, out var count) ? count : 0) + 1; // NOTE: Cross-framework compatibility (net 48 and net8)
}
var summary =
encodedIds.Count == 0
? "No objects selected."
: $"{encodedIds.Count} objects ({string.Join(", ",
typeCounts.Select(kv => $"{kv.Value} {kv.Key}"))})";
return new SelectionInfo(encodedIds, summary);
}
}
@@ -1,132 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.CSiShared.Utils;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Exceptions;
using Speckle.Connectors.DUI.Logging;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.Settings;
using Speckle.Converters.Common;
using Speckle.Converters.CSiShared;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Logging;
namespace Speckle.Connectors.CSiShared.Bindings;
public sealed class CsiSharedSendBinding : ISendBinding
{
public string Name => "sendBinding";
public SendBindingUICommands Commands { get; }
public IBrowserBridge Parent { get; }
private readonly DocumentModelStore _store;
private readonly IServiceProvider _serviceProvider;
private readonly List<ISendFilter> _sendFilters;
private readonly ICancellationManager _cancellationManager;
private readonly IOperationProgressManager _operationProgressManager;
private readonly ILogger<CsiSharedSendBinding> _logger;
private readonly ICsiApplicationService _csiApplicationService;
private readonly ICsiConversionSettingsFactory _csiConversionSettingsFactory;
private readonly ISpeckleApplication _speckleApplication;
private readonly ISdkActivityFactory _activityFactory;
public CsiSharedSendBinding(
DocumentModelStore store,
IBrowserBridge parent,
IEnumerable<ISendFilter> sendFilters,
IServiceProvider serviceProvider,
ICancellationManager cancellationManager,
IOperationProgressManager operationProgressManager,
ILogger<CsiSharedSendBinding> logger,
ICsiConversionSettingsFactory csiConversionSettingsFactory,
ISpeckleApplication speckleApplication,
ISdkActivityFactory activityFactory,
ICsiApplicationService csiApplicationService
)
{
_store = store;
_serviceProvider = serviceProvider;
_sendFilters = sendFilters.ToList();
_cancellationManager = cancellationManager;
_operationProgressManager = operationProgressManager;
_logger = logger;
Parent = parent;
Commands = new SendBindingUICommands(parent);
_csiConversionSettingsFactory = csiConversionSettingsFactory;
_speckleApplication = speckleApplication;
_activityFactory = activityFactory;
_csiApplicationService = csiApplicationService;
}
public List<ISendFilter> GetSendFilters() => _sendFilters;
public List<ICardSetting> GetSendSettings() => [];
public async Task Send(string modelCardId)
{
using var activity = _activityFactory.Start();
try
{
if (_store.GetModelById(modelCardId) is not SenderModelCard modelCard)
{
throw new InvalidOperationException("No publish model card was found.");
}
using var scope = _serviceProvider.CreateScope();
scope
.ServiceProvider.GetRequiredService<IConverterSettingsStore<CsiConversionSettings>>()
.Initialize(_csiConversionSettingsFactory.Create(_csiApplicationService.SapModel));
using var cancellationItem = _cancellationManager.GetCancellationItem(modelCardId);
List<ICsiWrapper> wrappers = modelCard
.SendFilter.NotNull()
.RefreshObjectIds()
.Select(DecodeObjectIdentifier)
.ToList();
if (wrappers.Count == 0)
{
throw new SpeckleSendFilterException("No objects were found to convert. Please update your publish filter!");
}
var sendResult = await scope
.ServiceProvider.GetRequiredService<SendOperation<ICsiWrapper>>()
.Execute(
wrappers,
modelCard.GetSendInfo(_speckleApplication.ApplicationAndVersion),
_operationProgressManager.CreateOperationProgressEventHandler(Parent, modelCardId, cancellationItem.Token),
cancellationItem.Token
);
await Commands.SetModelSendResult(modelCardId, sendResult.RootObjId, sendResult.ConversionResults);
}
catch (OperationCanceledException)
{
return;
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogModelCardHandledError(ex);
await Commands.SetModelError(modelCardId, ex);
}
}
private ICsiWrapper DecodeObjectIdentifier(string encodedId)
{
var (type, name) = ObjectIdentifier.Decode(encodedId);
return CsiWrapperFactory.Create(type, name);
}
public void CancelSend(string modelCardId)
{
_cancellationManager.CancelOperation(modelCardId);
}
}
@@ -1,13 +0,0 @@
using Speckle.Connectors.DUI.Models.Card.SendFilter;
namespace Speckle.Connectors.CSiShared.Filters;
public class CsiSharedSelectionFilter : DirectSelectionSendFilter
{
public CsiSharedSelectionFilter()
{
IsDefault = true;
}
public override List<string> RefreshObjectIds() => SelectedObjectIds;
}
@@ -1 +0,0 @@
global using CSiAPIv1;
@@ -1,16 +0,0 @@
namespace Speckle.Connectors.CSiShared.HostApp;
/// <summary>
/// Create a centralized access point for ETABS and SAP APIs across the entire program.
/// </summary>
/// <remarks>
/// All API methods are based on the objectType and objectName, not the GUID.
/// CSi is already giving us the "sapModel" reference through the plugin interface. No need to attach to running instance.
/// Since objectType is a single int (1, 2 ... 7) we know first index will always be the objectType.
/// Prevent having to pass the "sapModel" around between classes and this ensures consistent access.
/// Name "sapModel" is misleading since it doesn't only apply to SAP2000, but this is the convention in the API, so we keep it.
/// </remarks>
public interface ICsiApplicationService
{
cSapModel SapModel { get; }
}
@@ -1,157 +0,0 @@
using System.IO;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Utils;
using Speckle.Sdk;
using Speckle.Sdk.Helpers;
using Speckle.Sdk.Logging;
using Timer = System.Timers.Timer;
namespace Speckle.Connectors.CSiShared.HostApp;
public class CsiDocumentModelStore : DocumentModelStore, IDisposable
{
private readonly ISpeckleApplication _speckleApplication;
private readonly ILogger<CsiDocumentModelStore> _logger;
private readonly ICsiApplicationService _csiApplicationService;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly IThreadContext _threadContext;
private readonly Timer _modelCheckTimer;
private string _lastModelFilename = string.Empty;
private bool _disposed;
private string HostAppUserDataPath { get; set; }
private string DocumentStateFile { get; set; }
private string ModelPathHash { get; set; }
public CsiDocumentModelStore(
ILogger<DocumentModelStore> baseLogger,
IJsonSerializer jsonSerializer,
ISpeckleApplication speckleApplication,
ILogger<CsiDocumentModelStore> logger,
ICsiApplicationService csiApplicationService,
ITopLevelExceptionHandler topLevelExceptionHandler,
IThreadContext threadContext
)
: base(baseLogger, jsonSerializer)
{
_threadContext = threadContext;
_speckleApplication = speckleApplication;
_logger = logger;
_csiApplicationService = csiApplicationService;
_topLevelExceptionHandler = topLevelExceptionHandler;
// initialize timer to check for model changes
_modelCheckTimer = new Timer(1000);
// timer runs on background thread but model checks must be on main thread
_modelCheckTimer.Elapsed += (_, _) =>
_topLevelExceptionHandler.CatchUnhandled(() => _threadContext.RunOnMain(CheckModelChanges));
_modelCheckTimer.Start();
}
private void CheckModelChanges()
{
string currentFilename = _csiApplicationService.SapModel.GetModelFilename();
if (string.IsNullOrEmpty(currentFilename) || currentFilename == _lastModelFilename)
{
return;
}
_lastModelFilename = currentFilename;
SetPaths();
LoadState();
OnDocumentChanged();
}
public override Task OnDocumentStoreInitialized()
{
var currentFilename = _csiApplicationService.SapModel.GetModelFilename();
if (!string.IsNullOrEmpty(currentFilename))
{
_lastModelFilename = currentFilename;
SetPaths();
LoadState();
}
return Task.CompletedTask;
}
private void SetPaths()
{
try
{
ModelPathHash = Crypt.Md5(_csiApplicationService.SapModel.GetModelFilename(), length: 32);
HostAppUserDataPath = Path.Combine(
SpecklePathProvider.UserSpeckleFolderPath,
"ConnectorsFileData",
_speckleApplication.Slug
);
DocumentStateFile = Path.Combine(HostAppUserDataPath, $"{ModelPathHash}.json");
_logger.LogDebug($"Paths set - Hash: {ModelPathHash}, File: {DocumentStateFile}");
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogError(ex, "Error in setting paths for CsiDocumentModelStore");
}
}
protected override void HostAppSaveState(string modelCardState)
{
try
{
if (!Directory.Exists(HostAppUserDataPath))
{
Directory.CreateDirectory(HostAppUserDataPath);
}
File.WriteAllText(DocumentStateFile, modelCardState);
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogError(ex, "Failed to save state");
}
}
protected override void LoadState()
{
try
{
if (!File.Exists(DocumentStateFile))
{
ClearAndSave();
return;
}
string serializedState = File.ReadAllText(DocumentStateFile);
LoadFromString(serializedState);
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogError(ex, "Failed to load state, initializing empty state");
ClearAndSave();
}
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
_modelCheckTimer.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
@@ -1,43 +0,0 @@
using Speckle.Converters.Common;
using Speckle.Converters.CSiShared;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.CSiShared.HostApp;
/// <summary>
/// We can use the CSiWrappers to create our collection structure.
/// </summary>
/// <remarks>
/// This class manages the collections. If the key (from the path) already exists, this collection is returned.
/// If it doesn't exist, a new collection is created and added to the rootObject.
/// </remarks>
public class CsiSendCollectionManager
{
protected IConverterSettingsStore<CsiConversionSettings> ConverterSettings { get; }
protected Dictionary<string, Collection> CollectionCache { get; } = new();
public CsiSendCollectionManager(IConverterSettingsStore<CsiConversionSettings> converterSettings)
{
ConverterSettings = converterSettings;
}
public virtual Collection AddObjectCollectionToRoot(Base convertedObject, Collection rootObject)
{
var path = GetCollectionPath(convertedObject);
if (CollectionCache.TryGetValue(path, out Collection? collection))
{
return collection;
}
Collection childCollection = CreateCollection(convertedObject);
rootObject.elements.Add(childCollection);
CollectionCache[path] = childCollection;
return childCollection;
}
protected virtual string GetCollectionPath(Base convertedObject) => convertedObject["type"]?.ToString() ?? "Unknown";
protected virtual Collection CreateCollection(Base convertedObject) => new(GetCollectionPath(convertedObject));
}
@@ -1,115 +0,0 @@
using Speckle.Converters.Common;
using Speckle.Converters.CSiShared;
using Speckle.Converters.CSiShared.Utils;
namespace Speckle.Connectors.CSiShared.HostApp.Helpers;
/// <summary>
/// Base frame section property extractor for CSi products.
/// </summary>
/// <remarks>
/// Handles common Csi API calls for frame section properties
/// Provides foundation for application-specific extractors.
/// </remarks>
public class CsiFrameSectionPropertyExtractor : IFrameSectionPropertyExtractor
{
private readonly IConverterSettingsStore<CsiConversionSettings> _settingsStore;
public CsiFrameSectionPropertyExtractor(IConverterSettingsStore<CsiConversionSettings> settingsStore)
{
_settingsStore = settingsStore;
}
public void ExtractProperties(string sectionName, Dictionary<string, object?> properties)
{
GetMaterialName(sectionName, properties);
GetSectionProperties(sectionName, properties);
GetPropertyModifiers(sectionName, properties);
}
private void GetMaterialName(string sectionName, Dictionary<string, object?> properties)
{
// get material name
string materialName = string.Empty;
_settingsStore.Current.SapModel.PropFrame.GetMaterial(sectionName, ref materialName);
// append to General Data of properties dictionary
Dictionary<string, object?> generalData = properties.EnsureNested(SectionPropertyCategory.GENERAL_DATA);
generalData["Material"] = materialName;
}
private void GetSectionProperties(string sectionName, Dictionary<string, object?> properties)
{
double crossSectionalArea = 0,
shearAreaInMajorAxisDirection = 0,
shearAreaInMinorAxisDirection = 0,
torsionalConstant = 0,
momentOfInertiaAboutMajorAxis = 0,
momentOfInertiaAboutMinorAxis = 0,
sectionModulusAboutMajorAxis = 0,
sectionModulusAboutMinorAxis = 0,
plasticModulusAboutMajorAxis = 0,
plasticModulusAboutMinorAxis = 0,
radiusOfGyrationAboutMajorAxis = 0,
radiusOfGyrationAboutMinorAxis = 0;
_settingsStore.Current.SapModel.PropFrame.GetSectProps(
sectionName,
ref crossSectionalArea,
ref shearAreaInMajorAxisDirection,
ref shearAreaInMinorAxisDirection,
ref torsionalConstant,
ref momentOfInertiaAboutMajorAxis,
ref momentOfInertiaAboutMinorAxis,
ref sectionModulusAboutMajorAxis,
ref sectionModulusAboutMinorAxis,
ref plasticModulusAboutMajorAxis,
ref plasticModulusAboutMinorAxis,
ref radiusOfGyrationAboutMajorAxis,
ref radiusOfGyrationAboutMinorAxis
);
string distanceUnit = _settingsStore.Current.SpeckleUnits;
string areaUnit = $"{distanceUnit}²"; // // TODO: Formalize this better
string modulusUnit = $"{distanceUnit}³"; // // TODO: Formalize this better
string inertiaUnit = $"{distanceUnit}\u2074"; // TODO: Formalize this better
Dictionary<string, object?> mechanicalProperties = properties.EnsureNested(
SectionPropertyCategory.SECTION_PROPERTIES
);
mechanicalProperties.AddWithUnits("Area", crossSectionalArea, areaUnit);
mechanicalProperties.AddWithUnits("As2", shearAreaInMajorAxisDirection, areaUnit);
mechanicalProperties.AddWithUnits("As3", shearAreaInMinorAxisDirection, areaUnit);
mechanicalProperties.AddWithUnits("J", torsionalConstant, inertiaUnit);
mechanicalProperties.AddWithUnits("I22", momentOfInertiaAboutMajorAxis, inertiaUnit);
mechanicalProperties.AddWithUnits("I33", momentOfInertiaAboutMinorAxis, inertiaUnit);
mechanicalProperties.AddWithUnits("S22", sectionModulusAboutMajorAxis, modulusUnit);
mechanicalProperties.AddWithUnits("S33", sectionModulusAboutMinorAxis, modulusUnit);
mechanicalProperties.AddWithUnits("Z22", plasticModulusAboutMajorAxis, modulusUnit);
mechanicalProperties.AddWithUnits("Z33", plasticModulusAboutMinorAxis, modulusUnit);
mechanicalProperties.AddWithUnits("R22", radiusOfGyrationAboutMajorAxis, distanceUnit);
mechanicalProperties.AddWithUnits("R33", radiusOfGyrationAboutMinorAxis, distanceUnit);
}
private void GetPropertyModifiers(string sectionName, Dictionary<string, object?> properties)
{
double[] stiffnessModifiersArray = [];
_settingsStore.Current.SapModel.PropFrame.GetModifiers(sectionName, ref stiffnessModifiersArray);
Dictionary<string, object?> modifiers =
new()
{
["Cross-section (Axial) Area"] = stiffnessModifiersArray[0],
["Shear Area in 2 Direction"] = stiffnessModifiersArray[1],
["Shear Area in 3 Direction"] = stiffnessModifiersArray[2],
["Torsional Constant"] = stiffnessModifiersArray[3],
["Moment of Inertia about 2 Axis"] = stiffnessModifiersArray[4],
["Moment of Inertia about 3 Axis"] = stiffnessModifiersArray[5],
["Mass"] = stiffnessModifiersArray[6],
["Weight"] = stiffnessModifiersArray[7],
};
Dictionary<string, object?> generalData = properties.EnsureNested(SectionPropertyCategory.GENERAL_DATA);
generalData["Modifiers"] = modifiers;
}
}
@@ -1,212 +0,0 @@
using Speckle.Converters.Common;
using Speckle.Converters.CSiShared;
using Speckle.Converters.CSiShared.Utils;
namespace Speckle.Connectors.CSiShared.HostApp.Helpers;
/// <summary>
/// Base material property extractor for CSi products.
/// </summary>
/// <remarks>
/// 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
{
/// <summary>
/// Property strings for all mechanical properties, used by numerous methods.
/// </summary>
private static class MechanicalPropertyNames
{
public const string MODULUS_OF_ELASTICITY = "Modulus of Elasticity, E";
public const string MODULUS_OF_ELASTICITY_ARRAY = "Modulus of Elasticity Array, E";
public const string POISSON_RATIO = "Poisson's Ratio, U";
public const string POISSON_RATIO_ARRAY = "Poisson's Ratio Array, U";
public const string THERMAL_COEFFICIENT = "Coefficient of Thermal Expansion, A";
public const string THERMAL_COEFFICIENT_ARRAY = "Coefficient of Thermal Expansion Array, A";
public const string SHEAR_MODULUS = "Shear Modulus, G";
public const string SHEAR_MODULUS_ARRAY = "Shear Modulus Array, G";
}
private readonly IConverterSettingsStore<CsiConversionSettings> _settingsStore;
public CsiMaterialPropertyExtractor(IConverterSettingsStore<CsiConversionSettings> settingsStore)
{
_settingsStore = settingsStore;
}
public void ExtractProperties(string materialName, Dictionary<string, object?> properties)
{
GetGeneralProperties(materialName, properties);
GetWeightAndMassProperties(materialName, properties); // TODO: Add units
GetMechanicalProperties(materialName, properties); // TODO: Add units
}
private void GetGeneralProperties(string materialName, Dictionary<string, object?> properties)
{
{
eMatType materialType = default;
int materialColor = 0;
string materialNotes = string.Empty;
string materialGuid = string.Empty;
_settingsStore.Current.SapModel.PropMaterial.GetMaterial(
materialName,
ref materialType,
ref materialColor,
ref materialNotes,
ref materialGuid
);
var generalData = properties.EnsureNested(SectionPropertyCategory.GENERAL_DATA);
generalData["Name"] = materialName;
generalData["Type"] = materialType.ToString();
generalData["Notes"] = materialNotes;
}
}
private void GetWeightAndMassProperties(string materialName, Dictionary<string, object?> properties)
{
double weightPerUnitVolume = double.NaN;
double massPerUnitVolume = double.NaN;
_settingsStore.Current.SapModel.PropMaterial.GetWeightAndMass(
materialName,
ref weightPerUnitVolume,
ref massPerUnitVolume
);
var weightAndMass = properties.EnsureNested("Weight and Mass");
weightAndMass["Weight per Unit Volume"] = weightPerUnitVolume;
weightAndMass["Mass per Unit Volume"] = massPerUnitVolume;
}
private void GetMechanicalProperties(string materialName, Dictionary<string, object?> properties)
{
int materialDirectionalSymmetryKey = 0;
eMatType materialType = default;
_settingsStore.Current.SapModel.PropMaterial.GetTypeOAPI(
materialName,
ref materialType,
ref materialDirectionalSymmetryKey
);
var materialDirectionalSymmetryValue = materialDirectionalSymmetryKey switch
{
0 => DirectionalSymmetryType.ISOTROPIC,
1 => DirectionalSymmetryType.ORTHOTROPIC,
2 => DirectionalSymmetryType.ANISOTROPIC,
3 => DirectionalSymmetryType.UNIAXIAL,
_ => throw new ArgumentException($"Unknown symmetry type: {materialDirectionalSymmetryKey}")
};
var mechanicalProperties = properties.EnsureNested("Mechanical Properties");
mechanicalProperties["Directional Symmetry Type"] = materialDirectionalSymmetryValue.ToString();
GetMechanicalPropertiesByType(materialName, materialDirectionalSymmetryValue, mechanicalProperties);
}
private void GetMechanicalPropertiesByType(
string materialName,
DirectionalSymmetryType directionalSymmetryType,
Dictionary<string, object?> mechanicalProperties
)
{
switch (directionalSymmetryType)
{
case DirectionalSymmetryType.ISOTROPIC:
ExtractIsotropicProperties(materialName, mechanicalProperties);
break;
case DirectionalSymmetryType.ORTHOTROPIC:
ExtractOrthotropicProperties(materialName, mechanicalProperties);
break;
case DirectionalSymmetryType.ANISOTROPIC:
ExtractAnisotropicProperties(materialName, mechanicalProperties);
break;
case DirectionalSymmetryType.UNIAXIAL:
ExtractUniaxialProperties(materialName, mechanicalProperties);
break;
default:
throw new ArgumentException($"Unknown directional symmetry type: {directionalSymmetryType}");
}
}
private void ExtractIsotropicProperties(string materialName, Dictionary<string, object?> mechanicalProperties)
{
double modulusOfElasticity = double.NaN;
double poissonRatio = double.NaN;
double thermalCoefficient = double.NaN;
double shearModulus = double.NaN;
_settingsStore.Current.SapModel.PropMaterial.GetMPIsotropic(
materialName,
ref modulusOfElasticity,
ref poissonRatio,
ref thermalCoefficient,
ref shearModulus
);
mechanicalProperties[MechanicalPropertyNames.MODULUS_OF_ELASTICITY] = modulusOfElasticity;
mechanicalProperties[MechanicalPropertyNames.POISSON_RATIO] = poissonRatio;
mechanicalProperties[MechanicalPropertyNames.THERMAL_COEFFICIENT] = thermalCoefficient;
mechanicalProperties[MechanicalPropertyNames.SHEAR_MODULUS] = shearModulus;
}
private void ExtractOrthotropicProperties(string materialName, Dictionary<string, object?> mechanicalProperties)
{
double[] modulusOfElasticityArray = [];
double[] poissonRatioArray = [];
double[] thermalCoefficientArray = [];
double[] shearModulusArray = [];
_settingsStore.Current.SapModel.PropMaterial.GetMPOrthotropic(
materialName,
ref modulusOfElasticityArray,
ref poissonRatioArray,
ref thermalCoefficientArray,
ref shearModulusArray
);
mechanicalProperties[MechanicalPropertyNames.MODULUS_OF_ELASTICITY_ARRAY] = modulusOfElasticityArray;
mechanicalProperties[MechanicalPropertyNames.POISSON_RATIO_ARRAY] = poissonRatioArray;
mechanicalProperties[MechanicalPropertyNames.THERMAL_COEFFICIENT_ARRAY] = thermalCoefficientArray;
mechanicalProperties[MechanicalPropertyNames.SHEAR_MODULUS_ARRAY] = shearModulusArray;
}
private void ExtractAnisotropicProperties(string materialName, Dictionary<string, object?> mechanicalProperties)
{
double[] modulusOfElasticityArray = [];
double[] poissonRatioArray = [];
double[] thermalCoefficientArray = [];
double[] shearModulusArray = [];
_settingsStore.Current.SapModel.PropMaterial.GetMPAnisotropic(
materialName,
ref modulusOfElasticityArray,
ref poissonRatioArray,
ref thermalCoefficientArray,
ref shearModulusArray
);
mechanicalProperties[MechanicalPropertyNames.MODULUS_OF_ELASTICITY_ARRAY] = modulusOfElasticityArray;
mechanicalProperties[MechanicalPropertyNames.POISSON_RATIO_ARRAY] = poissonRatioArray;
mechanicalProperties[MechanicalPropertyNames.THERMAL_COEFFICIENT_ARRAY] = thermalCoefficientArray;
mechanicalProperties[MechanicalPropertyNames.SHEAR_MODULUS_ARRAY] = shearModulusArray;
}
private void ExtractUniaxialProperties(string materialName, Dictionary<string, object?> mechanicalProperties)
{
double modulusOfElasticity = double.NaN;
double thermalCoefficient = double.NaN;
_settingsStore.Current.SapModel.PropMaterial.GetMPUniaxial(
materialName,
ref modulusOfElasticity,
ref thermalCoefficient
);
mechanicalProperties[MechanicalPropertyNames.MODULUS_OF_ELASTICITY] = modulusOfElasticity;
mechanicalProperties[MechanicalPropertyNames.THERMAL_COEFFICIENT] = thermalCoefficient;
}
}
@@ -1,68 +0,0 @@
using Speckle.Converters.Common;
using Speckle.Converters.CSiShared;
using Speckle.Converters.CSiShared.Utils;
namespace Speckle.Connectors.CSiShared.HostApp.Helpers;
/// <summary>
/// Base shell section property extractor for CSi products.
/// </summary>
/// <remarks>
/// Handles common Csi API calls for shell section properties.
/// Provides foundation for application-specific extractors.
/// </remarks>
public class CsiShellSectionPropertyExtractor : IShellSectionPropertyExtractor
{
private readonly IConverterSettingsStore<CsiConversionSettings> _settingsStore;
public CsiShellSectionPropertyExtractor(IConverterSettingsStore<CsiConversionSettings> settingsStore)
{
_settingsStore = settingsStore;
}
public void ExtractProperties(string sectionName, Dictionary<string, object?> properties)
{
GetPropertyType(sectionName, properties);
GetPropertyModifiers(sectionName, properties);
}
private void GetPropertyType(string sectionName, Dictionary<string, object?> properties)
{
int propertyTypeKey = 1;
_settingsStore.Current.SapModel.PropArea.GetTypeOAPI(sectionName, ref propertyTypeKey);
var propertyTypeValue = propertyTypeKey switch
{
1 => AreaPropertyType.SHELL,
2 => AreaPropertyType.PLANE,
3 => AreaPropertyType.ASOLID,
_ => throw new ArgumentException($"Unknown property type: {propertyTypeKey}"),
};
var generalData = properties.EnsureNested(SectionPropertyCategory.GENERAL_DATA);
generalData["Property Type"] = propertyTypeValue.ToString();
}
private void GetPropertyModifiers(string sectionName, Dictionary<string, object?> properties)
{
double[] stiffnessModifiersArray = [];
_settingsStore.Current.SapModel.PropArea.GetModifiers(sectionName, ref stiffnessModifiersArray);
Dictionary<string, object?> modifiers =
new()
{
["Membrane f11 Direction"] = stiffnessModifiersArray[0],
["Membrane f22 Direction"] = stiffnessModifiersArray[1],
["Membrane f12 Direction"] = stiffnessModifiersArray[2],
["Bending m11 Direction"] = stiffnessModifiersArray[3],
["Bending m22 Direction"] = stiffnessModifiersArray[3],
["Bending m12 Direction"] = stiffnessModifiersArray[4],
["Shear v13 Direction"] = stiffnessModifiersArray[5],
["Shear v23 Direction"] = stiffnessModifiersArray[6],
["Mass"] = stiffnessModifiersArray[7],
["Weight"] = stiffnessModifiersArray[8]
};
var generalData = properties.EnsureNested(SectionPropertyCategory.GENERAL_DATA);
generalData["Modifiers"] = modifiers;
}
}
@@ -1,18 +0,0 @@
namespace Speckle.Connectors.CSiShared.HostApp.Helpers;
/// <summary>
/// Contract for host application specific section 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 IApplicationSectionPropertyExtractor
{
void ExtractProperties(string sectionName, Dictionary<string, object?> properties);
}
// NOTE: Seemingly silly, but allows us to register the correct extractor for the correct type.
public interface IApplicationFrameSectionPropertyExtractor : IApplicationSectionPropertyExtractor { }
public interface IApplicationShellSectionPropertyExtractor : IApplicationSectionPropertyExtractor { }
@@ -1,14 +0,0 @@
namespace Speckle.Connectors.CSiShared.HostApp.Helpers;
/// <summary>
/// Core contract for section property extraction common across CSi products.
/// </summary>
public interface ISectionPropertyExtractor
{
void ExtractProperties(string sectionName, Dictionary<string, object?> properties);
}
// NOTE: Seemingly silly, but allows us to register the correct extractor for the correct type.
public interface IFrameSectionPropertyExtractor : ISectionPropertyExtractor { }
public interface IShellSectionPropertyExtractor : ISectionPropertyExtractor { }
@@ -1,10 +0,0 @@
using Speckle.Sdk.Models.Proxies;
namespace Speckle.Connectors.CSiShared.HostApp.Helpers;
// NOTE: Interface because Etabs and Sap2000 section unpacking and extraction is different.
// At ServiceRegistration, we inject the correct implementation of the ISectionUnpacker
public interface ISectionUnpacker
{
IEnumerable<GroupProxy> UnpackSections();
}
@@ -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;
}
}
}
@@ -1,163 +0,0 @@
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.CSiShared.HostApp.Helpers;
using Speckle.Converters.Common;
using Speckle.Converters.CSiShared;
using Speckle.Converters.CSiShared.Extensions;
using Speckle.Sdk;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.CSiShared.Builders;
/// <summary>
/// Manages the conversion of CSi model objects and establishes proxy-based relationships.
/// </summary>
/// <remarks>
/// Core responsibilities:
/// - Converts ICsiWrappers to Speckle objects through caching-aware conversion
/// - Creates proxy objects for materials and sections from model data
/// - Establishes relationships between objects and their dependencies
///
/// The builder follows a two-phase process:
/// 1. Conversion Phase: ICsiWrappers → Speckle objects with cached results handling
/// 2. Relationship Phase: Material/section proxy creation and relationship mapping
/// </remarks>
public class CsiRootObjectBuilder : IRootObjectBuilder<ICsiWrapper>
{
private readonly IRootToSpeckleConverter _rootToSpeckleConverter;
private readonly IConverterSettingsStore<CsiConversionSettings> _converterSettings;
private readonly CsiSendCollectionManager _sendCollectionManager;
private readonly MaterialUnpacker _materialUnpacker;
private readonly ISectionUnpacker _sectionUnpacker;
private readonly ILogger<CsiRootObjectBuilder> _logger;
private readonly ISdkActivityFactory _activityFactory;
private readonly ICsiApplicationService _csiApplicationService;
public CsiRootObjectBuilder(
IRootToSpeckleConverter rootToSpeckleConverter,
IConverterSettingsStore<CsiConversionSettings> converterSettings,
CsiSendCollectionManager sendCollectionManager,
MaterialUnpacker materialUnpacker,
ISectionUnpacker sectionUnpacker,
ILogger<CsiRootObjectBuilder> logger,
ISdkActivityFactory activityFactory,
ICsiApplicationService csiApplicationService
)
{
_converterSettings = converterSettings;
_sendCollectionManager = sendCollectionManager;
_materialUnpacker = materialUnpacker;
_sectionUnpacker = sectionUnpacker;
_rootToSpeckleConverter = rootToSpeckleConverter;
_logger = logger;
_activityFactory = activityFactory;
_csiApplicationService = csiApplicationService;
}
/// <summary>
/// Converts Csi objects into a Speckle-compatible object hierarchy with established relationships.
/// </summary>
/// <remarks>
/// Operation sequence:
/// 1. Creates root collection with model metadata
/// 2. Converts each object with caching and progress tracking
/// 3. Creates proxies for materials and sections
/// </remarks>
public async Task<RootObjectBuilderResult> Build(
IReadOnlyList<ICsiWrapper> csiObjects,
SendInfo sendInfo,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
{
using var activity = _activityFactory.Start("Build");
string modelFileName = _csiApplicationService.SapModel.GetModelFilename(false) ?? "Unnamed model";
Collection rootObjectCollection =
new() { name = modelFileName, ["units"] = _converterSettings.Current.SpeckleUnits };
List<SendConversionResult> results = new(csiObjects.Count);
int count = 0;
using (var _ = _activityFactory.Start("Convert all"))
{
foreach (ICsiWrapper csiObject in csiObjects)
{
cancellationToken.ThrowIfCancellationRequested();
using var _2 = _activityFactory.Start("Convert");
var result = ConvertCsiObject(csiObject, rootObjectCollection);
results.Add(result);
count++;
onOperationProgressed.Report(new("Converting", (double)count / csiObjects.Count));
await Task.Yield();
}
}
if (results.All(x => x.Status == Status.ERROR))
{
throw new SpeckleException("Failed to convert all objects.");
}
using (var _ = _activityFactory.Start("Process Proxies"))
{
// Create and add material proxies
rootObjectCollection[ProxyKeys.MATERIAL] = _materialUnpacker.UnpackMaterials().ToList();
// Create and all section proxies (frame and shell)
rootObjectCollection[ProxyKeys.SECTION] = _sectionUnpacker.UnpackSections().ToList();
}
return new RootObjectBuilderResult(rootObjectCollection, results);
}
/// <summary>
/// Converts a single Csi wrapper "object" to a data object with appropriate collection management.
/// </summary>
private SendConversionResult ConvertCsiObject(ICsiWrapper csiObject, Collection typeCollection)
{
string sourceType = csiObject.ObjectName;
string applicationId = csiObject switch
{
CsiJointWrapper jointWrapper => jointWrapper.GetSpeckleApplicationId(_csiApplicationService.SapModel),
CsiFrameWrapper frameWrapper => frameWrapper.GetSpeckleApplicationId(_csiApplicationService.SapModel),
CsiCableWrapper cableWrapper => cableWrapper.GetSpeckleApplicationId(_csiApplicationService.SapModel),
CsiTendonWrapper tendonWrapper => tendonWrapper.ObjectName, // No GetGUID method in the Csi API available
CsiShellWrapper shellWrapper => shellWrapper.GetSpeckleApplicationId(_csiApplicationService.SapModel),
CsiSolidWrapper solidWrapper => solidWrapper.GetSpeckleApplicationId(_csiApplicationService.SapModel),
CsiLinkWrapper linkWrapper => linkWrapper.GetSpeckleApplicationId(_csiApplicationService.SapModel),
_ => throw new ArgumentException($"Unsupported wrapper type: {csiObject.GetType()}", nameof(csiObject))
};
try
{
Base converted = _rootToSpeckleConverter.Convert(csiObject);
var collection = _sendCollectionManager.AddObjectCollectionToRoot(converted, typeCollection);
collection.elements.Add(converted);
return new(Status.SUCCESS, applicationId, sourceType, converted);
}
// Expected not implemented:
// TODO: SAP 2000: CsiCableWrapper, CsiSolidWrapper
// TODO: ETABS: CsiLinkWrapper, CsiTendonWrapper
// NOTE: CsiLinkWrapper - not important to data extraction workflow
// NOTE: CsiTendonWrapper - not typically modelled in ETABS, rather SAFE
catch (NotImplementedException ex)
{
_logger.LogError(ex, sourceType);
return new(Status.WARNING, applicationId, sourceType, null, ex);
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogError(ex, sourceType);
return new(Status.ERROR, applicationId, sourceType, null, ex);
}
}
}
@@ -1,57 +0,0 @@
namespace Speckle.Connectors.CSiShared;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
public abstract class CSiPluginBase : cPluginContract, IDisposable
{
private const string s_modality = "Non-Modal";
private SpeckleFormBase? _panel;
private bool _disposed;
public void Main(ref cSapModel sapModel, ref cPluginCallback pluginCallback)
{
_panel = CreateForm();
_panel.Initialize(ref sapModel, ref pluginCallback);
_panel.FormClosed += (_, _) => Dispose();
if (string.Equals(s_modality, "Non-Modal", StringComparison.OrdinalIgnoreCase))
{
_panel.Show();
}
else
{
_panel.ShowDialog();
}
}
protected abstract SpeckleFormBase CreateForm();
public virtual int Info(ref string text)
{
text = "Hey Speckler! This is our next-gen CSi Connector.";
return 0;
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_panel?.Dispose();
_panel = null;
}
_disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~CSiPluginBase()
{
Dispose(false);
}
}
@@ -1,99 +0,0 @@
using System.ComponentModel;
using System.Reflection;
using System.Windows.Forms.Integration;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common;
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.DUI;
using Speckle.Connectors.DUI.WebView;
using Speckle.Converters.CSiShared;
namespace Speckle.Connectors.CSiShared;
[DesignerCategory("")]
public abstract class SpeckleFormBase : Form, ICsiApplicationService
{
private ElementHost Host { get; set; }
private cPluginCallback _pluginCallback;
private bool _disposed;
#pragma warning disable CA2213
private ServiceProvider _container;
#pragma warning restore CA2213
protected SpeckleFormBase()
{
Text = "Speckle (Beta)";
Size = new System.Drawing.Size(400, 600);
}
public cSapModel SapModel { get; private set; }
protected virtual void ConfigureServices(IServiceCollection services)
{
services.Initialize(GetHostApplication(), GetVersion());
services.AddCsi();
services.AddCsiConverters();
}
protected abstract Speckle.Sdk.Application GetHostApplication();
protected abstract HostAppVersion GetVersion();
public void Initialize(ref cSapModel sapModel, ref cPluginCallback pluginCallback)
{
// store app-specific model and callback references (callback if at all possible?)
SapModel = sapModel;
_pluginCallback = pluginCallback;
string assemblyName =
Assembly.GetExecutingAssembly().GetName().Name
?? throw new InvalidOperationException("Could not determine executing assembly name");
string resourcePath = $"{assemblyName}.Resources.et_element_Speckle.bmp";
// load and set the speckle icon from embedded resources
using (var stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(resourcePath))
{
if (stream == null)
{
throw new InvalidOperationException($"Could not find resource: {resourcePath}");
}
using var bmp = new Bitmap(stream);
Icon = Icon.FromHandle(bmp.GetHicon());
}
// configure dependency injection services
var services = new ServiceCollection();
services.AddSingleton<ICsiApplicationService>(this);
ConfigureServices(services);
// build service container and initialize ui framework
_container = services.BuildServiceProvider();
_container.UseDUI();
// setup webview control and form properties
var webview = _container.GetRequiredService<DUI3ControlWebView>();
Host = new() { Child = webview, Dock = DockStyle.Fill };
Controls.Add(Host);
FormBorderStyle = FormBorderStyle.Sizable;
// this.TopLevel = true;
// TODO: Get IntrPtr for Csi window
FormClosing += Form1Closing;
}
private void Form1Closing(object? sender, FormClosingEventArgs e) => _pluginCallback.Finish(0);
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_container.Dispose();
Host.Dispose();
base.Dispose(disposing);
}
_disposed = true;
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

@@ -1,56 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.CSiShared.Bindings;
using Speckle.Connectors.CSiShared.Builders;
using Speckle.Connectors.CSiShared.Filters;
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.CSiShared.HostApp.Helpers;
using Speckle.Connectors.DUI;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.WebView;
using Speckle.Converters.CSiShared;
using Speckle.Converters.CSiShared.ToSpeckle.Helpers;
namespace Speckle.Connectors.CSiShared;
public static class ServiceRegistration
{
public static IServiceCollection AddCsi(this IServiceCollection services)
{
services.AddSingleton<IBrowserBridge, BrowserBridge>();
services.AddConnectors();
services.AddDUI<DefaultThreadContext, CsiDocumentModelStore>();
services.AddDUIView();
services.AddSingleton<IBinding, TestBinding>();
services.AddSingleton<IBinding, ConfigBinding>();
services.AddSingleton<IBinding, AccountBinding>();
services.AddSingleton<IBinding>(sp => sp.GetRequiredService<IBasicConnectorBinding>());
services.AddSingleton<IBasicConnectorBinding, CsiSharedBasicConnectorBinding>();
services.AddSingleton<IBinding, CsiSharedSelectionBinding>();
services.AddSingleton<IBinding, CsiSharedSendBinding>();
services.AddScoped<ISendFilter, CsiSharedSelectionFilter>();
services.AddScoped<CsiSendCollectionManager>();
services.AddScoped<IRootObjectBuilder<ICsiWrapper>, CsiRootObjectBuilder>();
services.AddScoped<SendOperation<ICsiWrapper>>();
services.AddScoped<CsiMaterialPropertyExtractor>();
services.AddScoped<MaterialUnpacker>();
services.AddScoped<IFrameSectionPropertyExtractor, CsiFrameSectionPropertyExtractor>();
services.AddScoped<IShellSectionPropertyExtractor, CsiShellSectionPropertyExtractor>();
// add converter caches
services.AddScoped<CsiToSpeckleCacheSingleton>();
return services;
}
}
@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>a8e949b8-aa55-4909-99f0-8b551791a1f8</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>Speckle.Connectors.CSiShared</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Resources\et_element_Speckle.bmp">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Bindings\CsiSharedBasicConnectorBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\CsiSharedSelectionBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\CsiSharedSendBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Filters\CsiSharedSelectionFilter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\MaterialUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\CsiSendCollectionManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\CsiFrameSectionPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\CsiMaterialPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\CsiShellSectionPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\IApplicationSectionPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\ISectionPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\ISectionUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\CsiRootObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\CsiPluginBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\SpeckleFormBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GlobalUsing.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\CsiApplicationService.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\CsiDocumentModelStore.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ServiceRegistration.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utils\ObjectIdentifiers.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="$(MSBuildThisFileDirectory)Resources\et_element_Speckle.bmp" />
</ItemGroup>
</Project>
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>a8e949b8-aa55-4909-99f0-8b551791a1f8</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<PropertyGroup />
<Import Project="Speckle.Connectors.CSiShared.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>

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