Merge pull request #838 from specklesystems/dev
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled

Update dev to main
This commit is contained in:
Oğuzhan Koral
2025-05-14 22:29:28 +03:00
committed by GitHub
260 changed files with 15881 additions and 1734 deletions
+3
View File
@@ -314,6 +314,9 @@ 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
+3 -2
View File
@@ -11,7 +11,7 @@ public static class Consts
"rhino",
[
new("Connectors/Rhino/Speckle.Connectors.Rhino7", "net48"),
new("Connectors/Rhino/Speckle.Connectors.Rhino8", "net48")
new("Connectors/Rhino/Speckle.Connectors.Rhino8", "net48"),
]
),
new(
@@ -20,7 +20,8 @@ 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.Revit2025", "net8.0-windows"),
new("Connectors/Revit/Speckle.Connectors.Revit2026", "net8.0-windows")
]
),
new(
@@ -3,6 +3,7 @@ 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;
@@ -12,16 +13,13 @@ namespace Speckle.Connectors.ArcGIS.Utils;
public class ArcGISDocumentStore : DocumentModelStore
{
private readonly IThreadContext _threadContext;
public ArcGISDocumentStore(
ILogger<DocumentModelStore> logger,
IJsonSerializer jsonSerializer,
ITopLevelExceptionHandler topLevelExceptionHandler,
IThreadContext threadContext
ITopLevelExceptionHandler topLevelExceptionHandler
)
: base(jsonSerializer)
: base(logger, jsonSerializer)
{
_threadContext = threadContext;
ActiveMapViewChangedEvent.Subscribe(a => topLevelExceptionHandler.CatchUnhandled(() => OnMapViewChanged(a)), true);
ProjectSavingEvent.Subscribe(
_ =>
@@ -233,9 +233,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -267,7 +267,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"LibTessDotNet": {
@@ -311,18 +311,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -332,14 +332,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
},
"net6.0-windows7.0/win-x64": {
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -292,7 +292,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -336,18 +336,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -357,14 +357,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
}
}
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -292,7 +292,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -336,18 +336,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -357,14 +357,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
}
}
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -293,7 +293,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +358,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
}
}
@@ -215,9 +215,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -249,7 +249,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -293,18 +293,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.CSharp": "4.7.0",
@@ -313,14 +313,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -215,9 +215,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -249,7 +249,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -293,18 +293,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.CSharp": "4.7.0",
@@ -313,14 +313,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -89,6 +89,17 @@ 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,3 +1,4 @@
using Microsoft.Extensions.Logging;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Utils;
@@ -9,17 +10,16 @@ public class AutocadDocumentStore : DocumentModelStore
private const string NULL_DOCUMENT_NAME = "Null Doc";
private string _previousDocName;
private readonly AutocadDocumentManager _autocadDocumentManager;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
public AutocadDocumentStore(
ILogger<DocumentModelStore> logger,
IJsonSerializer jsonSerializer,
AutocadDocumentManager autocadDocumentManager,
ITopLevelExceptionHandler topLevelExceptionHandler
)
: base(jsonSerializer)
: base(logger, jsonSerializer)
{
_autocadDocumentManager = autocadDocumentManager;
_topLevelExceptionHandler = topLevelExceptionHandler;
_previousDocName = NULL_DOCUMENT_NAME;
// POC: Will be addressed to move it into AutocadContext!
@@ -101,6 +101,23 @@ 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(),
@@ -131,12 +148,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 visibile in its current state.
if (!obj.Visible)
// 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)
{
continue;
}
string appId = obj.GetSpeckleApplicationId();
definitionProxy.objects.Add(appId);
@@ -268,9 +268,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -302,7 +302,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -346,18 +346,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -367,14 +367,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
}
}
@@ -268,9 +268,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -302,7 +302,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -346,18 +346,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -367,14 +367,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
}
}
@@ -268,9 +268,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -302,7 +302,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -346,18 +346,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -367,14 +367,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
}
}
@@ -224,9 +224,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -259,7 +259,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -303,18 +303,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.CSharp": "4.7.0",
@@ -323,14 +323,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -224,9 +224,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -259,7 +259,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -303,18 +303,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.CSharp": "4.7.0",
@@ -323,14 +323,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -26,6 +26,7 @@ public class CsiDocumentModelStore : DocumentModelStore, IDisposable
private string ModelPathHash { get; set; }
public CsiDocumentModelStore(
ILogger<DocumentModelStore> baseLogger,
IJsonSerializer jsonSerializer,
ISpeckleApplication speckleApplication,
ILogger<CsiDocumentModelStore> logger,
@@ -33,7 +34,7 @@ public class CsiDocumentModelStore : DocumentModelStore, IDisposable
ITopLevelExceptionHandler topLevelExceptionHandler,
IThreadContext threadContext
)
: base(jsonSerializer)
: base(baseLogger, jsonSerializer)
{
_threadContext = threadContext;
_speckleApplication = speckleApplication;
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"speckle.converters.etabs21": {
@@ -335,18 +335,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -356,14 +356,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
}
}
@@ -215,9 +215,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -241,7 +241,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"speckle.converters.etabs22": {
@@ -291,18 +291,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.CSharp": "4.7.0",
@@ -311,14 +311,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
}
}
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"speckle.converters.navisworks2020": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +358,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"speckle.converters.navisworks2021": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +358,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"speckle.converters.navisworks2022": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +358,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"speckle.converters.navisworks2023": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +358,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"speckle.converters.navisworks2024": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +358,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -265,9 +265,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -291,7 +291,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"speckle.converters.navisworks2025": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +358,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -266,9 +266,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -292,7 +292,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"speckle.converters.navisworks2026": {
@@ -339,18 +339,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -360,14 +360,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -1,4 +1,5 @@
using System.Data;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Utils;
@@ -19,10 +20,11 @@ public sealed class NavisworksDocumentModelStore : DocumentModelStore
private string _lastSavedState = string.Empty;
public NavisworksDocumentModelStore(
ILogger<DocumentModelStore> logger,
IJsonSerializer jsonSerializer,
ITopLevelExceptionHandler topLevelExceptionHandler
)
: base(jsonSerializer)
: base(logger, jsonSerializer)
{
_topLevelExceptionHandler = topLevelExceptionHandler;
LoadState();
@@ -287,9 +287,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -306,7 +306,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"speckle.converters.revit2022": {
@@ -351,11 +351,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Revit.API": {
@@ -366,9 +366,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -378,14 +378,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
}
}
@@ -287,9 +287,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -306,7 +306,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"speckle.converters.revit2023": {
@@ -351,11 +351,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Revit.API": {
@@ -366,9 +366,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -378,14 +378,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
}
}
@@ -287,9 +287,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -306,7 +306,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"speckle.converters.revit2024": {
@@ -351,11 +351,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Revit.API": {
@@ -366,9 +366,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -378,14 +378,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
}
}
@@ -237,9 +237,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.2.2, )",
"Speckle.Sdk": "[3.2.2, )",
"Speckle.Sdk.Dependencies": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
@@ -256,7 +256,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.2.2, )"
"Speckle.Objects": "[3.3.5, )"
}
},
"speckle.converters.revit2025": {
@@ -301,11 +301,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "W6HlBPGxh3Ol4fUUKaEsdmK0jGgkNvjmhYdput/PIVsskpSwFTOwHf1xfsfk7+OxGLXJFcCuYVgToSX8U317mw==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.2.2"
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Revit.API": {
@@ -316,9 +316,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "98OHYUKjycsEXrrY3BkwR5EfkoeFD7Yq3GLqpLnQKU3CBXMpjk1sONzX/W1QRhxyxs32zJYJEgqNWpRHgNF7Zg==",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.CSharp": "4.7.0",
@@ -327,14 +327,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.2.2"
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.2.2, )",
"resolved": "3.2.2",
"contentHash": "hLQgfPC/aVJsrSr1m7vKrw6+IhCXdHFKbwgm6yYftkhaaAykk3cmD1h75MBwZ8Cack5z8zA467CWxPb9KRD5KA=="
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -0,0 +1,19 @@
<UserControl x:Class="Speckle.Connectors.Revit2026.Plugin.RevitControlWebView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
xmlns:dui="clr-namespace:Speckle.Connectors.DUI;assembly=Speckle.Connectors.DUI"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<wv2:CoreWebView2CreationProperties x:Key="EvergreenWebView2CreationProperties" UserDataFolder="C:\temp" />
</UserControl.Resources>
<DockPanel>
<wv2:WebView2
CreationProperties="{StaticResource EvergreenWebView2CreationProperties}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Name="Browser" Grid.Row="0" Source="{x:Static dui:Url.Netlify}" />
</DockPanel>
</UserControl>
@@ -0,0 +1,84 @@
using System.Windows.Controls;
using System.Windows.Threading;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Web.WebView2.Core;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.Revit.Plugin;
namespace Speckle.Connectors.Revit2026.Plugin;
public sealed partial class RevitControlWebView : UserControl, IBrowserScriptExecutor, IDisposable
{
private readonly IServiceProvider _serviceProvider;
private readonly IRevitTask _revitTask;
public RevitControlWebView(IServiceProvider serviceProvider, IRevitTask revitTask)
{
_serviceProvider = serviceProvider;
_revitTask = revitTask;
InitializeComponent();
Browser.CoreWebView2InitializationCompleted += (sender, args) =>
_serviceProvider
.GetRequiredService<ITopLevelExceptionHandler>()
.CatchUnhandled(() => OnInitialized(sender, args));
}
public bool IsBrowserInitialized => Browser.IsInitialized;
public object BrowserElement => Browser;
public void ExecuteScript(string script)
{
if (!Browser.IsInitialized)
{
throw new InvalidOperationException("Failed to execute script, Webview2 is not initialized yet.");
}
_revitTask.Run(() => Browser.ExecuteScriptAsync(script));
}
public void SendProgress(string script)
{
if (!Browser.IsInitialized)
{
throw new InvalidOperationException("Failed to execute script, Webview2 is not initialized yet.");
}
//always invoke even on the main thread because it's better somehow
Browser.Dispatcher.Invoke(
//fire and forget
() => Browser.ExecuteScriptAsync(script),
DispatcherPriority.Background
);
}
private void OnInitialized(object? sender, CoreWebView2InitializationCompletedEventArgs e)
{
Console.WriteLine(CoreWebView2Environment.GetAvailableBrowserVersionString());
if (!e.IsSuccess)
{
throw new InvalidOperationException("Webview Failed to initialize", e.InitializationException);
}
// We use Lazy here to delay creating the binding until after the Browser is fully initialized.
// Otherwise the Browser cannot respond to any requests to ExecuteScriptAsyncMethod
foreach (var binding in _serviceProvider.GetRequiredService<IEnumerable<IBinding>>())
{
SetupBinding(binding);
}
}
/// <remark>
/// This must be called on the Main thread
/// </remark>
private void SetupBinding(IBinding binding)
{
binding.Parent.AssociateWithBinding(binding);
Browser.CoreWebView2.AddHostObjectToScript(binding.Name, binding.Parent);
}
public void ShowDevTools() => Browser.CoreWebView2.OpenDevToolsWindow();
//https://github.com/MicrosoftEdge/WebView2Feedback/issues/2161
public void Dispose() => Browser.Dispatcher.Invoke(() => Browser.Dispose(), DispatcherPriority.Send);
}
@@ -0,0 +1,22 @@
using System.Windows.Controls;
using Autodesk.Revit.UI;
namespace Speckle.Connectors.Revit2026.Plugin;
public sealed class RevitControlWebViewDockable : UserControl, Autodesk.Revit.UI.IDockablePaneProvider
{
public RevitControlWebViewDockable(RevitControlWebView dUI3ControlWebView)
{
Content = dUI3ControlWebView;
}
public void SetupDockablePane(DockablePaneProviderData data)
{
data.FrameworkElement = this;
data.InitialState = new Autodesk.Revit.UI.DockablePaneState
{
DockPosition = DockPosition.Tabbed,
TabBehind = DockablePanes.BuiltInDockablePanes.ProjectBrowser
};
}
}
@@ -0,0 +1,114 @@
using System.IO;
using System.Reflection;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Autodesk.Revit.UI;
using Speckle.Connectors.Common;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk;
namespace Speckle.Connectors.Revit2026.Plugin;
internal sealed class RevitWebViewPlugin(
UIControlledApplication uIControlledApplication,
RevitContext revitContext,
RevitControlWebViewDockable webViewPanel,
ISpeckleApplication speckleApplication
) : IRevitPlugin
{
public void Initialise()
{
// Create and register panels before app initialized. this is needed for double-click file open
CreateTabAndRibbonPanel(uIControlledApplication);
RegisterDockablePane();
uIControlledApplication.ControlledApplication.ApplicationInitialized += OnApplicationInitialized;
}
public void Shutdown()
{
// POC: should we be cleaning up the RibbonPanel etc...
// Should we be indicating to any active in-flight functions that we are being closed?
}
// POC: Could be injected but maybe not worthwhile
private void CreateTabAndRibbonPanel(UIControlledApplication application)
{
// POC: some top-level handling and feedback here
try
{
application.CreateRibbonTab(Connector.TabName);
}
catch (ArgumentException)
{
// exception occurs when the speckle tab has already been created.
// this happens when both the dui2 and the dui3 connectors are installed. Can be safely ignored.
}
RibbonPanel specklePanel = application.CreateRibbonPanel(Connector.TabName, Connector.TabTitle);
var dui3Button = (PushButton)
specklePanel.AddItem(
new PushButtonData(
"Speckle (Beta) for Revit",
Connector.TabTitle,
typeof(RevitExternalApplication).Assembly.Location,
typeof(SpeckleRevitCommand).FullName
)
);
string path = typeof(RevitWebViewPlugin).Assembly.Location;
dui3Button.Image = LoadPngImgSource(
$"Speckle.Connectors.Revit{speckleApplication.HostApplicationVersion}.Assets.logo16.png",
path
);
dui3Button.LargeImage = LoadPngImgSource(
$"Speckle.Connectors.Revit{speckleApplication.HostApplicationVersion}.Assets.logo32.png",
path
);
dui3Button.ToolTipImage = LoadPngImgSource(
$"Speckle.Connectors.Revit{speckleApplication.HostApplicationVersion}.Assets.logo32.png",
path
);
dui3Button.ToolTip = "Speckle (Beta) for Revit";
//dui3Button.AvailabilityClassName = typeof(CmdAvailabilityViews).FullName;
dui3Button.SetContextualHelp(new ContextualHelp(ContextualHelpType.Url, "https://speckle.systems"));
}
private void OnApplicationInitialized(object? sender, Autodesk.Revit.DB.Events.ApplicationInitializedEventArgs e)
{
var uiApplication = new UIApplication(sender as Autodesk.Revit.ApplicationServices.Application);
revitContext.UIApplication = uiApplication;
// POC: might be worth to interface this out, we shall see...
global::Revit.Async.RevitTask.Initialize(uiApplication);
}
private void RegisterDockablePane()
{
// Registering dockable pane should happen before UiApplication is initialized with RevitTask.
// Otherwise pane cannot be registered for double-click file open.
uIControlledApplication.RegisterDockablePane(
RevitExternalApplication.DockablePanelId,
Connector.TabTitle,
webViewPanel
);
}
private ImageSource? LoadPngImgSource(string sourceName, string path)
{
try
{
var assembly = Assembly.LoadFrom(Path.Combine(path));
var icon = assembly.GetManifestResourceStream(sourceName);
PngBitmapDecoder decoder = new(icon, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
ImageSource source = decoder.Frames[0];
return source;
}
catch (Exception ex) when (!ex.IsFatal())
{
// POC: logging
}
return null;
}
}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<RevitAddIns>
<AddIn Type="Application">
<Name>Speckle (Beta) for Revit</Name>
<Description>Speckle (Beta) for Revit</Description>
<Assembly>Speckle.Connectors.Revit2026\Speckle.Connectors.Revit2026.dll</Assembly>
<FullClassName>Speckle.Connectors.Revit.Plugin.RevitExternalApplication</FullClassName>
<ClientId>27ccff2c-011c-4374-bb79-b93990d0c86a</ClientId>
<VendorId>speckle</VendorId>
<VendorDescription>Speckle: Empowering your design and construction data. For any problems, visit our community forum https://speckle.community</VendorDescription>
</AddIn>
</RevitAddIns>
@@ -0,0 +1,9 @@
{
"profiles": {
"ConnectorRevit2026": {
"commandName": "Executable",
"executablePath": "C:\\Program Files\\Autodesk\\Revit 2026\\Revit.exe",
"runtime": "net8.0-windows"
}
}
}
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<UseWpf>true</UseWpf>
<RevitVersion>2026</RevitVersion>
<DefineConstants>$(DefineConstants);REVIT2026;REVIT2022_OR_GREATER;REVIT2023_OR_GREATER;REVIT2024_OR_GREATER;REVIT2025_OR_GREATER;REVIT2026_OR_GREATER</DefineConstants><CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<Configurations>Debug;Release;Local</Configurations>
</PropertyGroup>
<Import Project="..\Speckle.Connectors.RevitShared\Speckle.Connectors.RevitShared.projitems" Label="Shared" />
<ItemGroup>
<ProjectReference Include="..\..\..\Converters\Revit\Speckle.Converters.Revit2026\Speckle.Converters.Revit2026.csproj" />
<ProjectReference Include="..\..\..\DUI3\Speckle.Connectors.DUI\Speckle.Connectors.DUI.csproj" />
<ProjectReference Include="..\..\..\Sdk\Speckle.Connectors.Common\Speckle.Connectors.Common.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Revit.Async" />
<PackageReference Include="Microsoft.Web.WebView2" IncludeAssets="compile" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" VersionOverride="8.0.0" />
</ItemGroup>
<ItemGroup>
<Content Include="Plugin\Speckle.Connectors.Revit2026.addin">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
@@ -0,0 +1,338 @@
{
"version": 2,
"dependencies": {
"net8.0-windows7.0": {
"Microsoft.Extensions.DependencyInjection": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
}
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"Microsoft.Web.WebView2": {
"type": "Direct",
"requested": "[1.0.1938.49, )",
"resolved": "1.0.1938.49",
"contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw=="
},
"PolySharp": {
"type": "Direct",
"requested": "[1.14.1, )",
"resolved": "1.14.1",
"contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ=="
},
"Revit.Async": {
"type": "Direct",
"requested": "[2.1.1, )",
"resolved": "2.1.1",
"contentHash": "aK6R/fxrn3jpiKc8LYqfWZ+OfEKNnwgkiln1uyuvaPnTWBOvfiisnOfe7+Sgogr4iEuMmuMDsmBRMCycMlUpnw=="
},
"Speckle.InterfaceGenerator": {
"type": "Direct",
"requested": "[0.9.6, )",
"resolved": "0.9.6",
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
},
"GraphQL.Client": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0",
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
"System.Reactive": "5.0.0"
}
},
"GraphQL.Client.Abstractions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==",
"dependencies": {
"GraphQL.Primitives": "6.0.0"
}
},
"GraphQL.Client.Abstractions.Websocket": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0"
}
},
"GraphQL.Primitives": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.CSharp": {
"type": "Transitive",
"resolved": "4.7.0",
"contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA=="
},
"Microsoft.Data.Sqlite": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "KGxbPeWsQMnmQy43DSBxAFtHz3l2JX8EWBSGUCvT3CuZ8KsuzbkqMIJMDOxWtG8eZSoCDI04aiVQjWuuV8HmSw==",
"dependencies": {
"Microsoft.Data.Sqlite.Core": "7.0.5",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
}
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "FTerRmQPqHrCrnoUzhBu+E+1DNGwyrAMLqHkAqOOOu5pGfyMOj8qQUBxI/gDtWtG11p49UxSfWmBzRNlwZqfUg==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==",
"dependencies": {
"Microsoft.Extensions.Primitives": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Binder": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==",
"dependencies": {
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Primitives": "2.2.0",
"System.ComponentModel.Annotations": "4.5.0"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==",
"dependencies": {
"System.Memory": "4.5.1",
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
}
},
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
"SQLitePCLRaw.provider.e_sqlite3": "2.1.4"
}
},
"SQLitePCLRaw.core": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==",
"dependencies": {
"System.Memory": "4.5.3"
}
},
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
},
"SQLitePCLRaw.provider.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.3",
"contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA=="
},
"System.Reactive": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.3.5, )",
"Speckle.Sdk": "[3.3.5, )",
"Speckle.Sdk.Dependencies": "[3.3.5, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
}
},
"speckle.connectors.logging": {
"type": "Project"
},
"speckle.converters.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.3.5, )"
}
},
"speckle.converters.revit2026": {
"type": "Project",
"dependencies": {
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Revit.API": "[2026.0.0, )"
}
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==",
"dependencies": {
"Microsoft.Extensions.Configuration.Binder": "2.2.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging.Abstractions": "2.2.0",
"Microsoft.Extensions.Options": "2.2.0"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Revit.API": {
"type": "CentralTransitive",
"requested": "[2023.0.0, )",
"resolved": "2026.0.0",
"contentHash": "SiqqKbF1pXyZWXZhAl2JhjYhTt7RiYO5JaQrAjq+OlleAjT4zatwAp/DnTwQspFbP7UZr3b2Ed2kuWNN0ZFelw=="
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"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.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
},
"net8.0-windows7.0/win-x64": {
"Microsoft.Web.WebView2": {
"type": "Direct",
"requested": "[1.0.1938.49, )",
"resolved": "1.0.1938.49",
"contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw=="
},
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
}
}
}
}
@@ -18,6 +18,8 @@ public partial class CefSharpPanel : Page, Autodesk.Revit.UI.IDockablePaneProvid
Browser.Dispatcher.Invoke(() => Browser.ExecuteScriptAsync(script), DispatcherPriority.Background);
}
public void SendProgress(string script) => ExecuteScript(script);
public bool IsBrowserInitialized => Browser.IsBrowserInitialized;
public object BrowserElement => Browser;
@@ -1,8 +1,8 @@
using Autodesk.Revit.DB;
using Revit.Async;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Connectors.RevitShared;
using Speckle.Connectors.RevitShared.Operations.Send.Filters;
using Speckle.Converters.RevitShared.Helpers;
@@ -23,13 +23,15 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding
private readonly RevitContext _revitContext;
private readonly ISpeckleApplication _speckleApplication;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly IRevitTask _revitTask;
public BasicConnectorBindingRevit(
DocumentModelStore store,
IBrowserBridge parent,
RevitContext revitContext,
ISpeckleApplication speckleApplication,
ITopLevelExceptionHandler topLevelExceptionHandler
ITopLevelExceptionHandler topLevelExceptionHandler,
IRevitTask revitTask
)
{
Name = "baseBinding";
@@ -38,6 +40,7 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding
_revitContext = revitContext;
_speckleApplication = speckleApplication;
_topLevelExceptionHandler = topLevelExceptionHandler;
_revitTask = revitTask;
Commands = new BasicConnectorBindingCommands(parent);
_store.DocumentChanged += (_, _) =>
@@ -105,7 +108,7 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding
var view = revitViewsFilter.GetView();
if (view is not null)
{
await RevitTask
await _revitTask
.RunAsync(() =>
{
_revitContext.UIApplication.ActiveUIDocument.ActiveView = view;
@@ -170,7 +173,7 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding
_revitContext.UIApplication?.ActiveUIDocument
?? throw new SpeckleException("Unable to retrieve active UI document");
await RevitTask
await _revitTask
.RunAsync(() =>
{
activeUIDoc.Selection.SetElementIds(objectIds);
@@ -79,7 +79,8 @@ internal sealed class RevitReceiveBinding : IReceiveBinding
DetailLevelType.Coarse, // TODO figure out
null,
false,
true
true,
false
)
);
// Receive host objects
@@ -68,7 +68,8 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
ISpeckleApplication speckleApplication,
ITopLevelExceptionHandler topLevelExceptionHandler,
LinkedModelHandler linkedModelHandler,
IThreadContext threadContext
IThreadContext threadContext,
IRevitTask revitTask
)
: base("sendBinding", bridge)
{
@@ -92,9 +93,12 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
// TODO expiry events
// TODO filters need refresh events
revitContext.UIApplication.NotNull().Application.DocumentChanged += (_, e) =>
_topLevelExceptionHandler.CatchUnhandled(() => DocChangeHandler(e));
_store.DocumentChanged += (_, _) => topLevelExceptionHandler.FireAndForget(async () => await OnDocumentChanged());
revitTask.Run(() =>
{
revitContext.UIApplication.NotNull().Application.DocumentChanged += (_, e) =>
_topLevelExceptionHandler.CatchUnhandled(() => DocChangeHandler(e));
_store.DocumentChanged += (_, _) => topLevelExceptionHandler.FireAndForget(async () => await OnDocumentChanged());
});
}
public List<ISendFilter> GetSendFilters() =>
@@ -109,7 +113,8 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
new DetailLevelSetting(DetailLevelType.Medium),
new ReferencePointSetting(ReferencePointType.InternalOrigin),
new SendParameterNullOrEmptyStringsSetting(false),
new LinkedModelsSetting(true)
new LinkedModelsSetting(true),
new SendRebarsAsVolumetricSetting(false)
];
public void CancelSend(string modelCardId) => _cancellationManager.CancelOperation(modelCardId);
@@ -137,7 +142,8 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
_toSpeckleSettingsManager.GetDetailLevelSetting(modelCard),
_toSpeckleSettingsManager.GetReferencePointSetting(modelCard),
_toSpeckleSettingsManager.GetSendParameterNullOrEmptyStringsSetting(modelCard),
_toSpeckleSettingsManager.GetLinkedModelsSetting(modelCard)
_toSpeckleSettingsManager.GetLinkedModelsSetting(modelCard),
_toSpeckleSettingsManager.GetSendRebarsAsVolumetric(modelCard)
)
);
@@ -1,5 +1,6 @@
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk.Common;
@@ -12,30 +13,30 @@ internal sealed class SelectionBinding : RevitBaseBinding, ISelectionBinding, ID
private readonly System.Timers.Timer _selectionTimer;
#endif
private readonly RevitContext _revitContext;
private readonly IAppIdleManager _idleManager;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
public SelectionBinding(
RevitContext revitContext,
IBrowserBridge parent,
IAppIdleManager idleManager,
ITopLevelExceptionHandler topLevelExceptionHandler
ITopLevelExceptionHandler topLevelExceptionHandler,
IRevitTask revitTask
)
: base("selectionBinding", parent)
{
_revitContext = revitContext;
_idleManager = idleManager;
_topLevelExceptionHandler = topLevelExceptionHandler;
#if REVIT2022
// NOTE: getting the selection data should be a fast function all, even for '000s of elements - and having a timer hitting it every 1s is ok.
_selectionTimer = new System.Timers.Timer(1000);
_selectionTimer.Elapsed += (_, _) => _topLevelExceptionHandler.CatchUnhandled(OnSelectionChanged);
_selectionTimer.Elapsed += (_, _) => topLevelExceptionHandler.CatchUnhandled(OnSelectionChanged);
_selectionTimer.Start();
#else
_revitContext.UIApplication.NotNull().SelectionChanged += (_, _) =>
_idleManager.SubscribeToIdle(nameof(OnSelectionChanged), OnSelectionChanged);
revitTask.Run(
() =>
_revitContext.UIApplication.NotNull().SelectionChanged += (_, _) =>
idleManager.SubscribeToIdle(nameof(OnSelectionChanged), OnSelectionChanged)
);
#endif
}
@@ -1,5 +1,5 @@
using System.Reflection;
using Autodesk.Revit.DB;
using CefSharp;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Builders;
@@ -15,7 +15,13 @@ using Speckle.Connectors.Revit.Operations.Send;
using Speckle.Connectors.Revit.Operations.Send.Settings;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Converters.Common;
using Speckle.Sdk;
using Speckle.Sdk.Models.GraphTraversal;
#if REVIT2026_OR_GREATER
using Speckle.Connectors.Revit2026.Plugin;
#else
using CefSharp;
#endif
namespace Speckle.Connectors.Revit.DependencyInjection;
@@ -27,6 +33,7 @@ public static class ServiceRegistration
serviceCollection.AddConnectors();
serviceCollection.AddDUI<RevitThreadContext, RevitDocumentStore>();
RegisterUiDependencies(serviceCollection);
serviceCollection.AddMatchingInterfacesAsTransient(Assembly.GetExecutingAssembly());
// Storage Schema
serviceCollection.AddScoped<DocumentModelStorageSchema>();
@@ -80,7 +87,7 @@ public static class ServiceRegistration
serviceCollection.AddSingleton<CefSharpPanel>();
serviceCollection.AddSingleton<IBrowserScriptExecutor>(sp => sp.GetRequiredService<CefSharpPanel>());
serviceCollection.AddSingleton<IRevitPlugin, RevitCefPlugin>();
#else
#elif !REVIT2026_OR_GREATER
// different versions for different versions of CEF
serviceCollection.AddSingleton(BindingOptions.DefaultBinder);
@@ -90,6 +97,11 @@ public static class ServiceRegistration
serviceCollection.AddSingleton(panel);
serviceCollection.AddSingleton<IBrowserScriptExecutor>(c => c.GetRequiredService<CefSharpPanel>());
serviceCollection.AddSingleton<IRevitPlugin, RevitCefPlugin>();
#else
serviceCollection.AddSingleton<IRevitPlugin, RevitWebViewPlugin>();
serviceCollection.AddSingleton<IBrowserScriptExecutor>(c => c.GetRequiredService<RevitControlWebView>());
serviceCollection.AddSingleton<RevitControlWebView>();
serviceCollection.AddSingleton<RevitControlWebViewDockable>();
#endif
}
}
@@ -2,10 +2,12 @@ using Autodesk.Revit.DB;
using Autodesk.Revit.DB.ExtensibleStorage;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.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.Connectors.Revit.Plugin;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk.Common;
@@ -25,15 +27,17 @@ internal sealed class RevitDocumentStore : DocumentModelStore
private readonly IThreadContext _threadContext;
public RevitDocumentStore(
ILogger<DocumentModelStore> logger,
IAppIdleManager idleManager,
RevitContext revitContext,
IJsonSerializer jsonSerializer,
DocumentModelStorageSchema documentModelStorageSchema,
IdStorageSchema idStorageSchema,
ITopLevelExceptionHandler topLevelExceptionHandler,
IThreadContext threadContext
IThreadContext threadContext,
IRevitTask revitTask
)
: base(jsonSerializer)
: base(logger, jsonSerializer)
{
_idleManager = idleManager;
_revitContext = revitContext;
@@ -44,18 +48,21 @@ internal sealed class RevitDocumentStore : DocumentModelStore
UIApplication uiApplication = _revitContext.UIApplication.NotNull();
uiApplication.ViewActivated += (s, e) => _topLevelExceptionHandler.CatchUnhandled(() => OnViewActivated(s, e));
revitTask.Run(() =>
{
uiApplication.ViewActivated += (s, e) => _topLevelExceptionHandler.CatchUnhandled(() => OnViewActivated(s, e));
uiApplication.Application.DocumentOpening += (_, _) =>
_topLevelExceptionHandler.CatchUnhandled(() => IsDocumentInit = false);
uiApplication.Application.DocumentOpening += (_, _) =>
_topLevelExceptionHandler.CatchUnhandled(() => IsDocumentInit = false);
uiApplication.Application.DocumentOpened += (_, _) =>
_topLevelExceptionHandler.CatchUnhandled(() => IsDocumentInit = false);
uiApplication.Application.DocumentOpened += (_, _) =>
_topLevelExceptionHandler.CatchUnhandled(() => IsDocumentInit = false);
// There is no event that we can hook here for double-click file open...
// It is kind of harmless since we create this object as "SingleInstance".
LoadState();
OnDocumentChanged();
// There is no event that we can hook here for double-click file open...
// It is kind of harmless since we create this object as "SingleInstance".
LoadState();
OnDocumentChanged();
});
}
/// <summary>
@@ -87,9 +94,9 @@ internal sealed class RevitDocumentStore : DocumentModelStore
protected override void HostAppSaveState(string modelCardState)
{
var doc = _revitContext.UIApplication?.ActiveUIDocument?.Document;
var document = _revitContext.UIApplication?.ActiveUIDocument?.Document;
// POC: this can happen? A: Not really, imho (dim) (Adam seyz yes it can if loading also triggers a save)
if (doc == null)
if (document == null)
{
return;
}
@@ -97,9 +104,14 @@ internal sealed class RevitDocumentStore : DocumentModelStore
_threadContext
.RunOnMain(() =>
{
using Transaction t = new(doc, "Speckle Write State");
//if not the same active document then don't save the current cards to a bad document!
if (!EnsureActiveDocumentIsSame(document))
{
return;
}
using Transaction t = new(document, "Speckle Write State");
t.Start();
using DataStorage ds = GetSettingsDataStorage(doc) ?? DataStorage.Create(doc);
using DataStorage ds = GetSettingsDataStorage(document) ?? DataStorage.Create(document);
using Entity stateEntity = new(_documentModelStorageSchema.GetSchema());
string serializedModels = Serialize();
@@ -115,6 +127,17 @@ internal sealed class RevitDocumentStore : DocumentModelStore
.FireAndForget();
}
private bool EnsureActiveDocumentIsSame(Document document)
{
var localDoc = _revitContext.UIApplication?.ActiveUIDocument?.Document;
if (localDoc == null)
{
return false;
}
return localDoc.Equals(document);
}
protected override void LoadState()
{
var stateEntity = GetSpeckleEntity(_revitContext.UIApplication?.ActiveUIDocument?.Document);
@@ -14,6 +14,7 @@ using Speckle.Converters.RevitShared.Helpers;
using Speckle.Converters.RevitShared.Settings;
using Speckle.DoubleNumerics;
using Speckle.Objects;
using Speckle.Objects.Data;
using Speckle.Objects.Geometry;
using Speckle.Sdk;
using Speckle.Sdk.Common;
@@ -188,15 +189,21 @@ public sealed class RevitHostObjectBuilder(
//TODO TransformTo will be deprecated as it's dangerous and requires ID transposing which is wrong!
//ID needs to be copied to the new instance
var id = localToGlobalMap.AtomicObject.id;
var originalAppId = localToGlobalMap.AtomicObject.applicationId;
ITransformable? newTransformable = null;
foreach (var mat in localToGlobalMap.Matrix)
{
transformable.TransformTo(new Transform() { matrix = mat, units = units }, out newTransformable);
transformable = newTransformable; // we need to keep the reference to the new object, as we're going to use it in the cache
transformable = newTransformable;
}
localToGlobalMap.AtomicObject = (newTransformable as Base)!;
localToGlobalMap.AtomicObject.id = id; // restore the id, as it's used in the cache
localToGlobalMap.AtomicObject.id = id;
// Make applicationId unique by appending a short GUID
// This prevents DirectShapeLibrary from using the same definition for multiple instances
localToGlobalMap.AtomicObject.applicationId = $"{originalAppId ?? id}_{Guid.NewGuid().ToString("N")[..8]}"; // hack of all of the ages. related to CNX-1707
localToGlobalMap.Matrix = new HashSet<Matrix4x4>(); // flush out the list, as we've applied the transforms already
}
@@ -213,9 +220,13 @@ public sealed class RevitHostObjectBuilder(
bakedObjectIds.Add(directShapes.UniqueId);
groupManager.AddToTopLevelGroup(directShapes);
if (localToGlobalMap.AtomicObject is IRawEncodedObject and Base myBase)
// we need to establish where the "normal route" is, this targets specifically IRawEncodedObject and
// processes just IRawEncodedObject in maps to create post base paint targets for solids specifically
// this smells big time.
// TODO: created material is wrong nonetheless but visually it all looks correct in Revit. Investigate what is going on
if (localToGlobalMap.AtomicObject is Base myBase)
{
postBakePaintTargets.Add((directShapes, myBase.applicationId ?? myBase.id.NotNull()));
SetSolidPostBakePaintTargets(myBase, directShapes, postBakePaintTargets);
}
conversionResults.Add(
@@ -280,4 +291,24 @@ public sealed class RevitHostObjectBuilder(
}
public void Dispose() => transactionManager?.Dispose();
// NOTE: temp poc HACK!
// this hack only works if we are only assuming one material applied to the solids inside DataObject displayValue. as soon as we have multiple solids with multiple materials it will break again.
// TODO: clean this up / refactor
private void SetSolidPostBakePaintTargets(Base baseObj, DirectShape directShapes, List<(DirectShape, string)> targets)
{
switch (baseObj)
{
case IRawEncodedObject:
targets.Add((directShapes, baseObj.applicationId ?? baseObj.id.NotNull()));
break;
case DataObject dataObj:
foreach (var item in dataObj.displayValue)
{
SetSolidPostBakePaintTargets(item, directShapes, targets);
}
break;
}
}
}
@@ -25,7 +25,7 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
public RevitViewsFilter(RevitContext revitContext)
{
_revitContext = revitContext;
_doc = _revitContext.UIApplication?.ActiveUIDocument.Document;
_doc = _revitContext.UIApplication?.ActiveUIDocument?.Document;
GetViews();
}
@@ -60,7 +60,7 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
}
// Paşa Bilal wants it like this... (three dots = important meaning for ogu)
string[] result = SelectedView.Split(new string[] { " - " }, 2, StringSplitOptions.None);
string[] result = SelectedView.Split([" - "], 2, StringSplitOptions.None);
var viewFamilyString = result[0];
var viewString = result[1];
@@ -72,7 +72,8 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
if (view is null)
{
throw new SpeckleSendFilterException("View not found, please update your model send filter.");
//this used to throw an exception but we don't want to fail loudly if the view is not found
return [];
}
using var viewCollector = new FilteredElementCollector(_doc, view.Id);
var elementsInView = viewCollector.ToElements();
@@ -0,0 +1,12 @@
using Speckle.Connectors.DUI.Settings;
namespace Speckle.Connectors.Revit.Operations.Send.Settings;
public class SendRebarsAsVolumetricSetting(bool value) : ICardSetting
{
public string? Id { get; set; } = "sendRebarsAsVolumetric";
public string? Title { get; set; } = "Send Rebars As Volumetric";
public string? Type { get; set; } = "boolean";
public object? Value { get; set; } = value;
public List<string>? Enum { get; set; }
}
@@ -22,6 +22,7 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
private readonly Dictionary<string, Transform?> _referencePointCache = new();
private readonly Dictionary<string, bool?> _sendNullParamsCache = new();
private readonly Dictionary<string, bool?> _sendLinkedModelsCache = new();
private readonly Dictionary<string, bool?> _sendRebarsAsVolumetricCache = new();
public ToSpeckleSettingsManager(
RevitContext revitContext,
@@ -121,6 +122,21 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
return returnValue;
}
public bool GetSendRebarsAsVolumetric(SenderModelCard modelCard)
{
var value = modelCard.Settings?.First(s => s.Id == "sendRebarsAsVolumetric").Value as bool?;
var returnValue = value != null && value.NotNull();
if (_sendRebarsAsVolumetricCache.TryGetValue(modelCard.ModelCardId.NotNull(), out bool? previousValue))
{
if (previousValue != returnValue)
{
EvictCacheForModelCard(modelCard);
}
}
_sendRebarsAsVolumetricCache[modelCard.ModelCardId] = returnValue;
return returnValue;
}
private void EvictCacheForModelCard(SenderModelCard modelCard)
{
var objectIds = modelCard.SendFilter != null ? modelCard.SendFilter.NotNull().SelectedObjectIds : [];
@@ -1,3 +1,4 @@
#if !REVIT2026_OR_GREATER
using System.Diagnostics;
using System.IO;
using System.Reflection;
@@ -6,7 +7,6 @@ using System.Windows.Media.Imaging;
using Autodesk.Revit.UI;
using CefSharp;
using Microsoft.Extensions.DependencyInjection;
using Revit.Async;
using Speckle.Connectors.Common;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
@@ -104,7 +104,7 @@ internal sealed class RevitCefPlugin : IRevitPlugin
_revitContext.UIApplication = uiApplication;
// POC: might be worth to interface this out, we shall see...
RevitTask.Initialize(uiApplication);
global::Revit.Async.RevitTask.Initialize(uiApplication);
PostApplicationInit(); // for double-click file open
}
@@ -181,3 +181,4 @@ internal sealed class RevitCefPlugin : IRevitPlugin
return null;
}
}
#endif
@@ -1,7 +1,6 @@
using Autodesk.Revit.UI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Revit.Async;
using Speckle.Connectors.Common;
using Speckle.Connectors.DUI;
using Speckle.Connectors.Revit.DependencyInjection;
@@ -30,6 +29,8 @@ internal sealed class RevitExternalApplication : IExternalApplication
return HostAppVersion.v2024;
#elif REVIT2025
return HostAppVersion.v2025;
#elif REVIT2026
return HostAppVersion.v2026;
#else
throw new NotImplementedException();
#endif
@@ -50,7 +51,7 @@ internal sealed class RevitExternalApplication : IExternalApplication
_container = services.BuildServiceProvider();
_container.UseDUI();
RevitTask.Initialize(application);
global::Revit.Async.RevitTask.Initialize(application);
// resolve root object
_revitPlugin = _container.GetRequiredService<IRevitPlugin>();
_revitPlugin.Initialise();
@@ -58,7 +59,7 @@ internal sealed class RevitExternalApplication : IExternalApplication
catch (Exception e) when (!e.IsFatal())
{
_container
.GetRequiredService<ILoggerFactory>()
?.GetRequiredService<ILoggerFactory>()
.CreateLogger<RevitExternalApplication>()
.LogCritical(e, "Unhandled exception");
// POC: feedback?
@@ -17,14 +17,17 @@ public sealed class RevitIdleManager : AppIdleManager
public RevitIdleManager(
RevitContext revitContext,
IIdleCallManager idleCallManager,
ITopLevelExceptionHandler topLevelExceptionHandler
ITopLevelExceptionHandler topLevelExceptionHandler,
IRevitTask revitTask
)
: base(idleCallManager)
{
_topLevelExceptionHandler = topLevelExceptionHandler;
_uiApplication = revitContext.UIApplication.NotNull();
_idleCallManager = idleCallManager;
_uiApplication.Idling += (s, e) => OnIdle?.Invoke(s, e); // will be called on the main thread always and fixing the Revit exceptions on subscribing/unsubscribing Idle events
revitTask.Run(
() => _uiApplication.Idling += (s, e) => OnIdle?.Invoke(s, e) // will be called on the main thread always and fixing the Revit exceptions on subscribing/unsubscribing Idle events
);
}
protected override void AddEvent()
@@ -0,0 +1,21 @@
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bridge;
using Speckle.InterfaceGenerator;
namespace Speckle.Connectors.Revit.Plugin;
[GenerateAutoInterface]
public class RevitTask(ITopLevelExceptionHandler topLevelExceptionHandler) : IRevitTask
{
public void Run(Func<Task> handler) =>
global::Revit.Async.RevitTask.RunAsync(() => topLevelExceptionHandler.FireAndForget(handler)).FireAndForget();
public void Run(Action handler) =>
global::Revit.Async.RevitTask.RunAsync(() => topLevelExceptionHandler.CatchUnhandled(handler)).FireAndForget();
public Task RunAsync(Func<Task> handler) =>
global::Revit.Async.RevitTask.RunAsync(() => topLevelExceptionHandler.CatchUnhandledAsync(handler));
public Task RunAsync(Action handler) =>
global::Revit.Async.RevitTask.RunAsync(() => topLevelExceptionHandler.CatchUnhandled(handler));
}
@@ -1,4 +1,3 @@
using Revit.Async;
using Speckle.Connectors.Common.Threading;
using Speckle.Sdk;
@@ -20,11 +19,13 @@ public class RevitThreadContext : ThreadContext
protected override Task<T> RunMainAsync<T>(Func<Task<T>> action) => CatchExceptions(action);
protected override Task RunMain(Action action) => CatchExceptions(action);
private static async Task<T> CatchExceptions<T>(Func<T> action)
{
Exception? ex = null;
//force the usage of the application overload
var ret = await RevitTask.RunAsync(_ =>
var ret = await global::Revit.Async.RevitTask.RunAsync(_ =>
{
try
{
@@ -47,7 +48,7 @@ public class RevitThreadContext : ThreadContext
{
Exception? ex = null;
//force the usage of the application overload
var ret = await RevitTask.RunAsync(async _ =>
var ret = await global::Revit.Async.RevitTask.RunAsync(async _ =>
{
try
{
@@ -70,7 +71,7 @@ public class RevitThreadContext : ThreadContext
{
Exception? ex = null;
//force the usage of the application overload
await RevitTask.RunAsync(async _ =>
await global::Revit.Async.RevitTask.RunAsync(async _ =>
{
try
{
@@ -86,4 +87,25 @@ public class RevitThreadContext : ThreadContext
throw new SpeckleRevitTaskException(ex);
}
}
private static async Task CatchExceptions(Action action)
{
Exception? ex = null;
//force the usage of the application overload
await global::Revit.Async.RevitTask.RunAsync(() =>
{
try
{
action();
}
catch (Exception e) when (!e.IsFatal())
{
ex = e;
}
});
if (ex is not null)
{
throw new SpeckleRevitTaskException(ex);
}
}
}
@@ -45,11 +45,13 @@
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\RevitRootObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\LinkedModelsSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\SendParameterNullOrEmptyStringsSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\SendRebarsAsVolumetricSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\ToSpeckleSettingsManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\ReferencePointSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\DetailLevelSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\IRevitPlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\RevitCommand.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\RevitTask.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\RevitExternalApplication.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\RevitIdleManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\RevitThreadContext.cs" />
@@ -0,0 +1,20 @@
{
"profiles": {
"Rhino7Core": {
"commandName": "Executable",
"executablePath": "C:\\Program Files\\Rhino 7\\System\\Rhino.exe",
"commandLineArgs": "/netcore /runscript=\"_Grasshopper\"",
"environmentVariables": {
"RHINO_PACKAGE_DIRS": "$(ProjectDir)$(OutputPath)\\"
}
},
"Rhino7NetFx": {
"commandName": "Executable",
"executablePath": "C:\\Program Files\\Rhino 7\\System\\Rhino.exe",
"commandLineArgs": "/netfx /runscript=\"_Grasshopper\"",
"environmentVariables": {
"RHINO_PACKAGE_DIRS": "$(ProjectDir)$(OutputPath)\\"
}
}
}
}
@@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Select the framework(s) you wish to target.
Rhino 6: net45
Rhino 7: net48
Rhino 8 Windows: net48, net7.0, net7.0-windows, net7.0-windows10.0.22000.0, etc
Rhino 8 Mac: net7.0, net7.0-macos, net7.0-macos12.0, etc
-->
<TargetFramework>net48</TargetFramework>
<Configurations>Debug;Release;Local</Configurations>
<TargetExt>.gha</TargetExt>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<UseWindowsForms>true</UseWindowsForms>
<!--Root Namespace needs to be aligned across all grasshopper csproj to ensure the .resx file in the shared project has the correct resource namespace-->
<RootNamespace>Speckle.Connectors.GrasshopperShared</RootNamespace>
<DefineConstants>$(DefineConstants);GRASSHOPPER;RHINO7;RHINO7_OR_GREATER</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<Title>Speckle.Connectors.Grasshopper7</Title>
<Description>Description of Speckle.Connectors.Grasshopper7</Description>
</PropertyGroup>
<PropertyGroup>
<NeutralLanguage>en-US</NeutralLanguage>
<GenerateResourceUsePreserializedResources>True</GenerateResourceUsePreserializedResources>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GrasshopperAsyncComponent" />
<PackageReference Include="RhinoCommon" IncludeAssets="compile; build" PrivateAssets="all" VersionOverride="7.13.21348.13001" />
<PackageReference Include="Grasshopper" IncludeAssets="compile; build" PrivateAssets="all" VersionOverride="7.13.21348.13001" />
<PackageReference Include="System.Resources.Extensions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Converters\Rhino\Speckle.Converters.Rhino7\Speckle.Converters.Rhino7.csproj" />
<ProjectReference Include="..\..\..\Sdk\Speckle.Connectors.Common\Speckle.Connectors.Common.csproj" />
</ItemGroup>
<Import Project="..\Speckle.Connectors.GrasshopperShared\Speckle.Connectors.GrasshopperShared.projitems" Label="Shared" />
</Project>
@@ -0,0 +1,414 @@
{
"version": 2,
"dependencies": {
".NETFramework,Version=v4.8": {
"Grasshopper": {
"type": "Direct",
"requested": "[7.13.21348.13001, )",
"resolved": "7.13.21348.13001",
"contentHash": "XoxWouU2mzZAmj4kRP/ZqsKG+7e6Q+NBBitJSK/uxjuV8vNsGK2K19GW5ptDjjPuhd7GBI00KSB8Q1+JYFjELA==",
"dependencies": {
"RhinoCommon": "[7.13.21348.13001]"
}
},
"GrasshopperAsyncComponent": {
"type": "Direct",
"requested": "[1.2.3, )",
"resolved": "1.2.3",
"contentHash": "KdCmyZ7Su8T3wb5t5BEbSg2inz+aJfGFHpDysColTdeyYX9S6MRJK69UV4kYYHE+ro1FKPADOwoSE6eLKq/yDA=="
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net48": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"PolySharp": {
"type": "Direct",
"requested": "[1.14.1, )",
"resolved": "1.14.1",
"contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ=="
},
"RhinoCommon": {
"type": "Direct",
"requested": "[7.13.21348.13001, )",
"resolved": "7.13.21348.13001",
"contentHash": "JQdaNw61ddBqIe08E9O4N/grwrN1hjDHcYW7tWylwCZyFR7SepoCD4NS+6LN6+oSQhNbhLi9Bf+hQOFYFdRAEA=="
},
"Speckle.InterfaceGenerator": {
"type": "Direct",
"requested": "[0.9.6, )",
"resolved": "0.9.6",
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
},
"System.Resources.Extensions": {
"type": "Direct",
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "X1oj5i1gy6O4TDU8Z48djqbDJJz4qkT+LgvD0Z2pbCsXieJNms9H5yBbvahXdEJad07Ntai5bST4J5XWoNmMsg==",
"dependencies": {
"System.Formats.Nrbf": "9.0.4",
"System.Memory": "4.5.5"
}
},
"GraphQL.Client": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0",
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
"System.Net.WebSockets.Client.Managed": "1.0.22",
"System.Reactive": "5.0.0"
}
},
"GraphQL.Client.Abstractions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==",
"dependencies": {
"GraphQL.Primitives": "6.0.0"
}
},
"GraphQL.Client.Abstractions.Websocket": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0"
}
},
"GraphQL.Primitives": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"Microsoft.Bcl.HashCode": {
"type": "Transitive",
"resolved": "1.1.1",
"contentHash": "MalY0Y/uM/LjXtHfX/26l2VtN4LDNZ2OE3aumNOHDLsT4fNYy2hiHXI4CXCqKpNUNm7iJ2brrc4J89UdaL56FA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.CSharp": {
"type": "Transitive",
"resolved": "4.7.0",
"contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA=="
},
"Microsoft.Data.Sqlite": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "KGxbPeWsQMnmQy43DSBxAFtHz3l2JX8EWBSGUCvT3CuZ8KsuzbkqMIJMDOxWtG8eZSoCDI04aiVQjWuuV8HmSw==",
"dependencies": {
"Microsoft.Data.Sqlite.Core": "7.0.5",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
}
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "FTerRmQPqHrCrnoUzhBu+E+1DNGwyrAMLqHkAqOOOu5pGfyMOj8qQUBxI/gDtWtG11p49UxSfWmBzRNlwZqfUg==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==",
"dependencies": {
"Microsoft.Extensions.Primitives": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Binder": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==",
"dependencies": {
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Primitives": "2.2.0",
"System.ComponentModel.Annotations": "4.5.0"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==",
"dependencies": {
"System.Memory": "4.5.1",
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
}
},
"Microsoft.NETFramework.ReferenceAssemblies.net48": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "zMk4D+9zyiEWByyQ7oPImPN/Jhpj166Ky0Nlla4eXlNL8hI/BtSJsgR8Inldd4NNpIAH3oh8yym0W2DrhXdSLQ=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
"SQLitePCLRaw.provider.dynamic_cdecl": "2.1.4"
}
},
"SQLitePCLRaw.core": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==",
"dependencies": {
"System.Memory": "4.5.3"
}
},
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
},
"SQLitePCLRaw.provider.dynamic_cdecl": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "ZsaKKhgYF9B1fvcnOGKl3EycNAwd9CRWX7v0rEfuPWhQQ5Jjpvf2VEHahiLIGHio3hxi3EIKFJw9KvyowWOUAw==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"System.Buffers": {
"type": "Transitive",
"resolved": "4.5.1",
"contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
},
"System.Collections.Immutable": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "wfm2NgK22MmBe5qJjp52qzpkeDZKb4l9LbdubhZSehY1z4LS+lld6R+B+UQNb2AZRHu/QJlHxEUcRst5hIEejg==",
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
},
"System.Formats.Nrbf": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "HuDjUInoaQvnX+3bRg2lX+KLD2raiDJA0v3c4hmukAHSQT8rpa6+2NKRoz+1W8Jmbu5ocvc+xPPKgHMVjeJpOg==",
"dependencies": {
"Microsoft.Bcl.HashCode": "1.1.1",
"System.Reflection.Metadata": "9.0.4",
"System.ValueTuple": "4.5.0"
}
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
"dependencies": {
"System.Buffers": "4.5.1",
"System.Numerics.Vectors": "4.5.0",
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"System.Net.WebSockets.Client.Managed": {
"type": "Transitive",
"resolved": "1.0.22",
"contentHash": "WqEOxPlXjuZrIjUtXNE9NxEfU/n5E35iV2PtoZdJSUC4tlrqwHnTee+wvMIM4OUaJWmwrymeqcgYrE0IkGAgLA==",
"dependencies": {
"System.Buffers": "4.4.0",
"System.Numerics.Vectors": "4.4.0"
}
},
"System.Numerics.Vectors": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ=="
},
"System.Reactive": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"System.Reflection.Metadata": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "qeJNsMmZPc/Lieg0Md+D4F6LoLcxV3b9QsUNmBRXc2ZVOkMbAcwuO9l2jbQFv3n+fLiHJilN8v6i5aJNivjrCQ==",
"dependencies": {
"System.Collections.Immutable": "9.0.4",
"System.Memory": "4.5.5"
}
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"System.ValueTuple": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ=="
},
"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.connectors.logging": {
"type": "Project"
},
"speckle.converters.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.3.5, )"
}
},
"speckle.converters.rhino7": {
"type": "Project",
"dependencies": {
"RhinoCommon": "[7.13.21348.13001, )",
"Speckle.Converters.Common": "[1.0.0, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "MZtBIwfDFork5vfjpJdG5g8wuJFt7d/y3LOSVVtDK/76wlbtz6cjltfKHqLx2TKVqTj5/c41t77m1+h20zqtPA==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==",
"dependencies": {
"Microsoft.Extensions.Configuration.Binder": "2.2.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging.Abstractions": "2.2.0",
"Microsoft.Extensions.Options": "2.2.0"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.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.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
}
}
}
@@ -0,0 +1,20 @@
{
"profiles": {
"Rhino8Core": {
"commandName": "Executable",
"executablePath": "C:\\Program Files\\Rhino 8\\System\\Rhino.exe",
"commandLineArgs": "/netcore /runscript=\"_Grasshopper\"",
"environmentVariables": {
"RHINO_PACKAGE_DIRS": "$(ProjectDir)$(OutputPath)\\"
}
},
"Rhino8NetFx": {
"commandName": "Executable",
"executablePath": "C:\\Program Files\\Rhino 8\\System\\Rhino.exe",
"commandLineArgs": "/netfx /runscript=\"_Grasshopper\"",
"environmentVariables": {
"RHINO_PACKAGE_DIRS": "$(ProjectDir)$(OutputPath)\\"
}
}
}
}
@@ -0,0 +1,55 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Select the framework(s) you wish to target.
Rhino 6: net45
Rhino 7: net48
Rhino 8 Windows: net48, net7.0, net7.0-windows, net7.0-windows10.0.22000.0, etc
Rhino 8 Mac: net7.0, net7.0-macos, net7.0-macos12.0, etc
-->
<TargetFramework>net48</TargetFramework>
<Configurations>Debug;Release;Local</Configurations>
<TargetExt>.gha</TargetExt>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<UseWindowsForms>true</UseWindowsForms>
<!--Root Namespace needs to be aligned across all grasshopper csproj to ensure the .resx file in the shared project has the correct resource namespace-->
<RootNamespace>Speckle.Connectors.GrasshopperShared</RootNamespace>
<DefineConstants>$(DefineConstants);GRASSHOPPER;RHINO8;RHINO7_OR_GREATER;RHINO8_OR_GREATER</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<NeutralLanguage>en-US</NeutralLanguage>
<GenerateResourceUsePreserializedResources>True</GenerateResourceUsePreserializedResources>
</PropertyGroup>
<PropertyGroup>
<Title>Speckle.Connectors.Grasshopper8</Title>
<Description>Description of Speckle.Connectors.Grasshopper8</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GrasshopperAsyncComponent" />
<PackageReference Include="RhinoCommon" IncludeAssets="compile; build" PrivateAssets="all" />
<PackageReference Include="Grasshopper" IncludeAssets="compile; build" PrivateAssets="all" />
<PackageReference Include="System.Resources.Extensions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Converters\Rhino\Speckle.Converters.Rhino8\Speckle.Converters.Rhino8.csproj" />
<ProjectReference Include="..\..\..\Sdk\Speckle.Connectors.Common\Speckle.Connectors.Common.csproj" />
</ItemGroup>
<ItemGroup>
<!--
*.gh files are copied by the rhino.iss file into %appdata%\Grasshopper\Libraries
Since that folder is not specific to any Rhino version, only one Speckle.Connectors.Grasshopper
project need copy it to the output dir (tho this needs to be aligned in the iss file)
-->
<Content Include="..\Sample Files\*.gh">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="..\Speckle.Connectors.GrasshopperShared\Speckle.Connectors.GrasshopperShared.projitems" Label="Shared" />
</Project>
@@ -0,0 +1,414 @@
{
"version": 2,
"dependencies": {
".NETFramework,Version=v4.8": {
"Grasshopper": {
"type": "Direct",
"requested": "[8.9.24194.18121, )",
"resolved": "8.9.24194.18121",
"contentHash": "ZQ7vT1urn9jG2KLMdT/aVhCsijN349lj2Lrg7+Cd5A84KOW+RIErG6IqH+133hc9HT9D10+7oi/XnIlgYZRzqQ==",
"dependencies": {
"RhinoCommon": "[8.9.24194.18121]"
}
},
"GrasshopperAsyncComponent": {
"type": "Direct",
"requested": "[1.2.3, )",
"resolved": "1.2.3",
"contentHash": "KdCmyZ7Su8T3wb5t5BEbSg2inz+aJfGFHpDysColTdeyYX9S6MRJK69UV4kYYHE+ro1FKPADOwoSE6eLKq/yDA=="
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net48": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"PolySharp": {
"type": "Direct",
"requested": "[1.14.1, )",
"resolved": "1.14.1",
"contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ=="
},
"RhinoCommon": {
"type": "Direct",
"requested": "[8.9.24194.18121, )",
"resolved": "8.9.24194.18121",
"contentHash": "XRMnm38sBFeMT5AAtRTJdSaql/YNtT02AGi8TEVP1VZ4fkm8VJ1q2nNioWN3tW/+H8Tdi4nV+DuhB/5uE41MCg=="
},
"Speckle.InterfaceGenerator": {
"type": "Direct",
"requested": "[0.9.6, )",
"resolved": "0.9.6",
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
},
"System.Resources.Extensions": {
"type": "Direct",
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "X1oj5i1gy6O4TDU8Z48djqbDJJz4qkT+LgvD0Z2pbCsXieJNms9H5yBbvahXdEJad07Ntai5bST4J5XWoNmMsg==",
"dependencies": {
"System.Formats.Nrbf": "9.0.4",
"System.Memory": "4.5.5"
}
},
"GraphQL.Client": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0",
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
"System.Net.WebSockets.Client.Managed": "1.0.22",
"System.Reactive": "5.0.0"
}
},
"GraphQL.Client.Abstractions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==",
"dependencies": {
"GraphQL.Primitives": "6.0.0"
}
},
"GraphQL.Client.Abstractions.Websocket": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0"
}
},
"GraphQL.Primitives": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"Microsoft.Bcl.HashCode": {
"type": "Transitive",
"resolved": "1.1.1",
"contentHash": "MalY0Y/uM/LjXtHfX/26l2VtN4LDNZ2OE3aumNOHDLsT4fNYy2hiHXI4CXCqKpNUNm7iJ2brrc4J89UdaL56FA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.CSharp": {
"type": "Transitive",
"resolved": "4.7.0",
"contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA=="
},
"Microsoft.Data.Sqlite": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "KGxbPeWsQMnmQy43DSBxAFtHz3l2JX8EWBSGUCvT3CuZ8KsuzbkqMIJMDOxWtG8eZSoCDI04aiVQjWuuV8HmSw==",
"dependencies": {
"Microsoft.Data.Sqlite.Core": "7.0.5",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
}
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "FTerRmQPqHrCrnoUzhBu+E+1DNGwyrAMLqHkAqOOOu5pGfyMOj8qQUBxI/gDtWtG11p49UxSfWmBzRNlwZqfUg==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==",
"dependencies": {
"Microsoft.Extensions.Primitives": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Binder": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==",
"dependencies": {
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Primitives": "2.2.0",
"System.ComponentModel.Annotations": "4.5.0"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==",
"dependencies": {
"System.Memory": "4.5.1",
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
}
},
"Microsoft.NETFramework.ReferenceAssemblies.net48": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "zMk4D+9zyiEWByyQ7oPImPN/Jhpj166Ky0Nlla4eXlNL8hI/BtSJsgR8Inldd4NNpIAH3oh8yym0W2DrhXdSLQ=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
"SQLitePCLRaw.provider.dynamic_cdecl": "2.1.4"
}
},
"SQLitePCLRaw.core": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==",
"dependencies": {
"System.Memory": "4.5.3"
}
},
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
},
"SQLitePCLRaw.provider.dynamic_cdecl": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "ZsaKKhgYF9B1fvcnOGKl3EycNAwd9CRWX7v0rEfuPWhQQ5Jjpvf2VEHahiLIGHio3hxi3EIKFJw9KvyowWOUAw==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"System.Buffers": {
"type": "Transitive",
"resolved": "4.5.1",
"contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
},
"System.Collections.Immutable": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "wfm2NgK22MmBe5qJjp52qzpkeDZKb4l9LbdubhZSehY1z4LS+lld6R+B+UQNb2AZRHu/QJlHxEUcRst5hIEejg==",
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
},
"System.Formats.Nrbf": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "HuDjUInoaQvnX+3bRg2lX+KLD2raiDJA0v3c4hmukAHSQT8rpa6+2NKRoz+1W8Jmbu5ocvc+xPPKgHMVjeJpOg==",
"dependencies": {
"Microsoft.Bcl.HashCode": "1.1.1",
"System.Reflection.Metadata": "9.0.4",
"System.ValueTuple": "4.5.0"
}
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
"dependencies": {
"System.Buffers": "4.5.1",
"System.Numerics.Vectors": "4.5.0",
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"System.Net.WebSockets.Client.Managed": {
"type": "Transitive",
"resolved": "1.0.22",
"contentHash": "WqEOxPlXjuZrIjUtXNE9NxEfU/n5E35iV2PtoZdJSUC4tlrqwHnTee+wvMIM4OUaJWmwrymeqcgYrE0IkGAgLA==",
"dependencies": {
"System.Buffers": "4.4.0",
"System.Numerics.Vectors": "4.4.0"
}
},
"System.Numerics.Vectors": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ=="
},
"System.Reactive": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"System.Reflection.Metadata": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "qeJNsMmZPc/Lieg0Md+D4F6LoLcxV3b9QsUNmBRXc2ZVOkMbAcwuO9l2jbQFv3n+fLiHJilN8v6i5aJNivjrCQ==",
"dependencies": {
"System.Collections.Immutable": "9.0.4",
"System.Memory": "4.5.5"
}
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"System.ValueTuple": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ=="
},
"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.connectors.logging": {
"type": "Project"
},
"speckle.converters.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.3.5, )"
}
},
"speckle.converters.rhino8": {
"type": "Project",
"dependencies": {
"RhinoCommon": "[8.9.24194.18121, )",
"Speckle.Converters.Common": "[1.0.0, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "MZtBIwfDFork5vfjpJdG5g8wuJFt7d/y3LOSVVtDK/76wlbtz6cjltfKHqLx2TKVqTj5/c41t77m1+h20zqtPA==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==",
"dependencies": {
"Microsoft.Extensions.Configuration.Binder": "2.2.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging.Abstractions": "2.2.0",
"Microsoft.Extensions.Options": "2.2.0"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "VPbYI8TyPDlKlNUHPLPAL1HveN9649LKVxw8opgGypoqq0MC5I7WxQjDcuB8xKnQ1PCSO8suu4hEJgdyPcEvWg==",
"dependencies": {
"Speckle.Sdk": "3.3.5"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "7r8CmugwinniEF6v0N0bWuC+xpJaRfa/EnEjzj8NLpFG1b3uAjOxteGlQgR+evVacxTCEsuNkio7Mdv97odgpg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.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.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.3.5"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.3.5, )",
"resolved": "3.3.5",
"contentHash": "RukqLb0lVNgtmhKPeZJCncibnyutQ6Dr6+UQCa4PjWinIXpSm3A3ywK9ISkU+5StW1QoejiR7kc9a6qmiLys6w=="
}
}
}
}
@@ -0,0 +1,101 @@
namespace Speckle.Connectors.GrasshopperShared.Components.BaseComponents;
internal static class Operator
{
public enum CompareMethod
{
Nothing,
Equals,
StartsWith, // <
EndsWith, // >
Contains, // ?
Wildcard, // :
Regex, // ;
}
public static CompareMethod CompareMethodFromPattern(string pattern)
{
bool not = false;
return CompareMethodFromPattern(ref pattern, ref not);
}
public static CompareMethod CompareMethodFromPattern(ref string pattern, ref bool not)
{
if (pattern is null)
{
return CompareMethod.Nothing;
}
if (string.IsNullOrEmpty(pattern))
{
return CompareMethod.Equals;
}
switch (pattern[0])
{
case '~':
not = !not;
pattern = pattern[1..];
return CompareMethodFromPattern(ref pattern, ref not);
case '<':
pattern = pattern[1..];
return string.IsNullOrEmpty(pattern) ? CompareMethod.Equals : CompareMethod.StartsWith;
case '>':
pattern = pattern[1..];
return string.IsNullOrEmpty(pattern) ? CompareMethod.Equals : CompareMethod.EndsWith;
case '?':
pattern = pattern[1..];
return string.IsNullOrEmpty(pattern) ? CompareMethod.Equals : CompareMethod.Contains;
case ':':
pattern = pattern[1..];
return string.IsNullOrEmpty(pattern) ? CompareMethod.Equals : CompareMethod.Wildcard;
case ';':
pattern = pattern[1..];
return string.IsNullOrEmpty(pattern) ? CompareMethod.Equals : CompareMethod.Regex;
default:
return CompareMethod.Equals;
}
}
public static bool IsSymbolNameLike(this string source, string pattern)
{
if (pattern is null)
{
return true;
}
if (pattern == source)
{
return true;
}
bool not = false;
switch (CompareMethodFromPattern(ref pattern, ref not))
{
case CompareMethod.Nothing:
return not ^ false;
case CompareMethod.Equals:
return not ^ string.Equals(source, pattern, StringComparison.Ordinal);
case CompareMethod.StartsWith:
return not ^ source.StartsWith(pattern, StringComparison.Ordinal);
case CompareMethod.EndsWith:
return not ^ source.EndsWith(pattern, StringComparison.Ordinal);
case CompareMethod.Contains:
return not ^ (source.IndexOf(pattern, StringComparison.Ordinal) >= 0);
/*
case CompareMethod.Wildcard:
return not
^ Microsoft.VisualBasic.CompilerServices.LikeOperator.LikeString(
source,
pattern,
Microsoft.VisualBasic.CompareMethod.Text
);
*/
case CompareMethod.Regex:
var regex = new System.Text.RegularExpressions.Regex(pattern);
return not ^ regex.IsMatch(source);
}
return false;
}
}
@@ -0,0 +1,25 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.GrasshopperShared.Registration;
namespace Speckle.Connectors.GrasshopperShared.Components.BaseComponents;
public abstract class SpeckleScopedTaskCapableComponent<TInput, TOutput>(
string name,
string nickname,
string description,
string category,
string subCategory
) : SpeckleTaskCapableComponent<TInput, TOutput>(name, nickname, description, category, subCategory)
{
protected override Task<TOutput> PerformTask(TInput input, CancellationToken cancellationToken = default)
{
/*using*/var scope = PriorityLoader.Container.CreateScope(); // NOTE: this component does not work as intended in e.g the receive component; the scope gets disposed before the task completes.
return PerformScopedTask(input, scope, cancellationToken);
}
protected abstract Task<TOutput> PerformScopedTask(
TInput input,
IServiceScope scope,
CancellationToken cancellationToken = default
);
}
@@ -0,0 +1,60 @@
using Grasshopper.Kernel;
using Speckle.Sdk;
namespace Speckle.Connectors.GrasshopperShared.Components.BaseComponents;
public abstract class SpeckleTaskCapableComponent<TInput, TOutput>(
string name,
string nickname,
string description,
string category,
string subCategory
) : GH_TaskCapableComponent<TOutput>(name, nickname, description, category, subCategory)
{
protected override void SolveInstance(IGH_DataAccess da)
{
//TODO: We're missing activity and logging here. Will enable it for all inherited classes.
if (InPreSolve)
{
// Collect the data and create the task
try
{
var input = GetInput(da);
TaskList.Add(PerformTask(input, CancelToken));
}
catch (SpeckleException e)
{
Console.WriteLine(e);
}
return;
}
if (!GetSolveResults(da, out TOutput result))
{
// INFO: This will run synchronously. Useful for Rhino.Compute runs, but can also be enabled by user.
try
{
TInput input = GetInput(da);
var syncResult = PerformTask(input).Result;
result = syncResult;
}
catch (SpeckleException e)
{
Console.WriteLine(e);
return;
}
}
if (result is not null)
{
SetOutput(da, result);
}
}
protected abstract TInput GetInput(IGH_DataAccess da);
protected abstract void SetOutput(IGH_DataAccess da, TOutput result);
protected abstract Task<TOutput> PerformTask(TInput input, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,85 @@
using Grasshopper.Kernel.Types;
using Speckle.Connectors.GrasshopperShared.Components.BaseComponents;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Components.Collections;
public class CollectionPathsSelector : ValueSet<IGH_Goo>
{
public CollectionPathsSelector()
: base(
"Collection Selector",
"cSelect",
"Allows you to select a set of collection paths for querying",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.COLLECTIONS
) { }
public override Guid ComponentGuid => new Guid("65FC4D58-2209-41B6-9B22-BE51C8B28604");
protected override Bitmap Icon => Resources.speckle_inputs_collection;
protected override void LoadVolatileData()
{
var collections = VolatileData
.AllData(true)
.OfType<SpeckleCollectionWrapperGoo>()
.Select(goo => goo.Value)
.ToList();
if (collections.Count == 0)
{
return;
}
// NOTE: supporting multiple collections? maybe? not really?
var paths = new List<string>();
foreach (SpeckleCollectionWrapper wrapper in collections)
{
// note: we are skipping the input collection, to make the output paths more intuitive
foreach (var element in wrapper.Elements)
{
if (element is SpeckleCollectionWrapper childCollectionWrapper)
{
paths.AddRange(GetPaths(childCollectionWrapper));
}
else
{
// include the input collection only if there are objects directly inside
paths.Add("_objects");
}
}
}
m_data.Clear();
m_data.AppendRange(paths.Select(s => new GH_String(s)));
}
private List<string> GetPaths(SpeckleCollectionWrapper wrapper)
{
var currentPath = new List<string>();
var allPaths = new HashSet<string>();
void GetPathsInternal(SpeckleCollectionWrapper w)
{
currentPath.Add(w.Name);
var subCols = w.Elements.OfType<SpeckleCollectionWrapper>().ToList();
// NOTE: here we're basically outputting only paths that correspond to a collection
// that has values inside of it.
if (subCols.Count != w.Elements.Count)
{
allPaths.Add(string.Join(Constants.LAYER_PATH_DELIMITER, currentPath));
}
foreach (var subCol in subCols)
{
GetPathsInternal(subCol);
}
currentPath.RemoveAt(currentPath.Count - 1);
}
GetPathsInternal(wrapper);
return allPaths.ToList();
}
}
@@ -0,0 +1,204 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.HostApp.Extras;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.GrasshopperShared.Components.Collections;
#pragma warning disable CA1711
public class CreateCollection : GH_Component, IGH_VariableParameterComponent
#pragma warning restore CA1711
{
public override Guid ComponentGuid => new("BDCE743E-7BDB-479B-AA81-19854AB5A254");
protected override Bitmap Icon => Resources.speckle_collections_create;
private readonly DebounceDispatcher _debounceDispatcher = new();
public CreateCollection()
: base(
"Create Collection",
"cC",
"Creates a new Collection",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.COLLECTIONS
) { }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
var p = CreateParameter(GH_ParameterSide.Input, 0);
pManager.AddParameter(p);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddGenericParameter("Collection", "C", "Created parent collection", GH_ParamAccess.tree);
}
protected override void SolveInstance(IGH_DataAccess dataAccess)
{
string rootName = "Unnamed";
Collection rootCollection = new();
SpeckleCollectionWrapper rootSpeckleCollectionWrapper =
new()
{
Base = rootCollection,
Name = rootName,
Path = new() { rootName },
Color = null,
Material = null,
ApplicationId = InstanceGuid.ToString()
};
foreach (var inputParam in Params.Input)
{
var data = inputParam.VolatileData.AllData(true).ToList();
if (data.Count == 0)
{
continue;
}
var inputCollections = data.OfType<SpeckleCollectionWrapperGoo>()
.Empty()
.Select(o => (SpeckleCollectionWrapperGoo)o.Duplicate())
.ToList();
var inputNonCollections = data.Where(t => t is not SpeckleCollectionWrapperGoo).Empty().ToList();
if (inputCollections.Count != 0 && inputNonCollections.Count != 0)
{
// TODO: error out! we want to disallow setting objects and collections in the same parent collection
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
$"Parameter {inputParam.NickName} should not contain both objects and collections."
);
return;
}
List<string> childPath = new() { rootName };
childPath.Add(inputParam.NickName);
SpeckleCollectionWrapper childSpeckleCollectionWrapper =
new()
{
Base = new Collection(),
Name = inputParam.NickName,
Path = childPath,
Color = null,
Material = null,
Topology = GrasshopperHelpers.GetParamTopology(inputParam),
ApplicationId = inputParam.InstanceGuid.ToString(),
};
// handle collection inputs
// if on this port we're only receiving collections, we should become "pass-through" to not create
// needless nesting
if (inputCollections.Count == data.Count)
{
var nameTest = new HashSet<string>();
foreach (SpeckleCollectionWrapperGoo wrapperGoo in inputCollections)
{
// update the speckle collection path
wrapperGoo.Value.Path = childPath;
foreach (
string subCollectionName in wrapperGoo.Value.Elements.OfType<SpeckleCollectionWrapper>().Select(c => c.Name)
)
{
var hasNotSeenNameBefore = nameTest.Add(subCollectionName);
if (!hasNotSeenNameBefore)
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
$"Duplicate collection name found: {subCollectionName} in input parameter {inputParam.NickName}. Please ensure collection names are unique per nesting level.\n See https://speckle.docs/grashopper/collections"
);
return;
}
}
childSpeckleCollectionWrapper.Elements.AddRange(wrapperGoo.Value.Elements);
}
rootSpeckleCollectionWrapper.Elements.Add(childSpeckleCollectionWrapper);
continue;
}
// handle object inputs
foreach (var obj in inputNonCollections)
{
SpeckleObjectWrapperGoo wrapperGoo = new();
if (wrapperGoo.CastFrom(obj))
{
wrapperGoo.Value.Path = childPath;
wrapperGoo.Value.Parent = childSpeckleCollectionWrapper;
childSpeckleCollectionWrapper.Elements.Add(wrapperGoo.Value);
}
}
rootSpeckleCollectionWrapper.Elements.Add(childSpeckleCollectionWrapper);
}
dataAccess.SetData(0, new SpeckleCollectionWrapperGoo(rootSpeckleCollectionWrapper));
}
public bool CanInsertParameter(GH_ParameterSide side, int index)
{
return side == GH_ParameterSide.Input;
}
public bool CanRemoveParameter(GH_ParameterSide side, int index)
{
return side == GH_ParameterSide.Input;
}
public IGH_Param CreateParameter(GH_ParameterSide side, int index)
{
var myParam = new Param_GenericObject
{
Name = $"Sub-Collection {Params.Input.Count + 1}",
MutableNickName = true,
Optional = true,
Access = GH_ParamAccess.tree // always tree
};
myParam.NickName = myParam.Name;
myParam.Optional = true;
return myParam;
}
public bool DestroyParameter(GH_ParameterSide side, int index)
{
return side == GH_ParameterSide.Input;
}
public void VariableParameterMaintenance()
{
// TODO?
}
public override void AddedToDocument(GH_Document document)
{
base.AddedToDocument(document);
Params.ParameterChanged += (sender, args) =>
{
if (args.ParameterSide == GH_ParameterSide.Output)
{
return;
}
switch (args.OriginalArguments.Type)
{
case GH_ObjectEventType.NickName:
// This means the user is typing characters, debounce until it stops for 400ms before expiring the solution.
// Prevents UI from locking too soon while writing new names for inputs.
args.Parameter.Name = args.Parameter.NickName;
_debounceDispatcher.Debounce(500, e => ExpireSolution(true));
break;
case GH_ObjectEventType.NickNameAccepted:
args.Parameter.Name = args.Parameter.NickName;
ExpireSolution(true);
break;
}
};
}
}
@@ -0,0 +1,226 @@
using System.Collections;
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk.Common;
namespace Speckle.Connectors.GrasshopperShared.Components.Collections;
#pragma warning disable CA1711
[Guid("69BC8CFB-A72F-4A83-9263-F3399DDA2E5E")]
public class ExpandCollection : GH_Component, IGH_VariableParameterComponent
#pragma warning restore CA1711
{
public ExpandCollection()
: base(
"Expand Collection",
"eC",
"Expands a Collection into its children",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.COLLECTIONS
) { }
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_collections_expand;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(
new SpeckleCollectionParam(GH_ParamAccess.item),
"Data",
"D",
"The data you want to expand",
GH_ParamAccess.item
);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager) { }
protected override void SolveInstance(IGH_DataAccess da)
{
SpeckleCollectionWrapperGoo res = new();
da.GetData(0, ref res);
SpeckleCollectionWrapper wrapper = res.Value;
if (wrapper is null)
{
return;
}
Name = wrapper.Name;
NickName = wrapper.Name;
var objects = wrapper
.Elements.Where(el => el is not SpeckleCollectionWrapper)
.OfType<SpeckleObjectWrapper>()
.Select(o => new SpeckleObjectWrapperGoo(o))
.ToList();
var collections = wrapper
.Elements.Where(el => el is SpeckleCollectionWrapper)
.OfType<SpeckleCollectionWrapper>()
.ToList();
var outputParams = new List<OutputParamWrapper>();
if (objects.Count != 0)
{
var param = new Param_GenericObject()
{
Name = "_objects",
NickName = "_objs",
Description =
"Some collections may contain a mix of objects and other collections. Here we output the atomic objects from within this collection.",
Access = GH_ParamAccess.list
};
outputParams.Add(new OutputParamWrapper(param, objects, null));
}
foreach (SpeckleCollectionWrapper childWrapper in collections)
{
// skip empty
if (childWrapper.Elements.Count == 0)
{
continue;
}
var hasInnerCollections = childWrapper.Elements.Any(el => el is SpeckleCollectionWrapper);
var topology = childWrapper.Topology; // Note: this is a reminder for the future
var nickName = childWrapper.Name;
if (nickName.Length > 16)
{
nickName = childWrapper.Name[..6];
nickName += "..." + childWrapper.Name[^6..];
}
var param = new Param_GenericObject()
{
Name = childWrapper.Name,
NickName = nickName,
Access = hasInnerCollections
? GH_ParamAccess.item
: topology is null
? GH_ParamAccess.list
: GH_ParamAccess.tree // we will directly set objects out; note access can be list or tree based on whether it will be a path based collection
};
outputParams.Add(
new OutputParamWrapper(
param,
hasInnerCollections
? new SpeckleCollectionWrapperGoo(childWrapper)
: childWrapper.Elements.OfType<SpeckleObjectWrapper>().Select(o => new SpeckleObjectWrapperGoo(o)).ToList(),
topology
)
);
}
if (da.Iteration == 0 && OutputMismatch(outputParams))
{
OnPingDocument()
.ScheduleSolution(
5,
_ =>
{
CreateOutputs(outputParams);
}
);
}
else
{
for (int i = 0; i < outputParams.Count; i++)
{
var outParam = Params.Output[i];
var outParamWrapper = outputParams[i];
switch (outParam.Access)
{
case GH_ParamAccess.item:
da.SetData(i, outParamWrapper.Values);
break;
case GH_ParamAccess.list:
da.SetDataList(i, outParamWrapper.Values as IList);
break;
case GH_ParamAccess.tree:
//TODO: means we need to convert the collection to a tree
var topo = outParamWrapper.Topology.NotNull();
var values = outParamWrapper.Values as IList;
var tree = GrasshopperHelpers.CreateDataTreeFromTopologyAndItems(topo, values.NotNull());
da.SetDataTree(i, tree);
break;
}
}
}
}
private bool OutputMismatch(List<OutputParamWrapper> outputParams)
{
if (Params.Output.Count != outputParams.Count)
{
return true;
}
var count = 0;
foreach (var newParam in outputParams)
{
var oldParam = Params.Output[count];
if (
oldParam.NickName != newParam.Param.NickName
|| oldParam.Name != newParam.Param.Name
|| oldParam.Access != newParam.Param.Access
)
{
return true;
}
count++;
}
return false;
}
private void CreateOutputs(List<OutputParamWrapper> outputParams)
{
// TODO: better, nicer handling of creation/removal
while (Params.Output.Count > 0)
{
Params.UnregisterOutputParameter(Params.Output[^1]);
}
foreach (var newParam in outputParams)
{
var param = new Param_GenericObject
{
Name = newParam.Param.Name,
NickName = newParam.Param.NickName,
MutableNickName = false,
Access = newParam.Param.Access
};
Params.RegisterOutputParam(param);
}
Params.OnParametersChanged();
VariableParameterMaintenance();
ExpireSolution(false);
}
public void VariableParameterMaintenance() { }
public bool CanInsertParameter(GH_ParameterSide side, int index) => false;
public bool CanRemoveParameter(GH_ParameterSide side, int index) => false;
public IGH_Param CreateParameter(GH_ParameterSide side, int index)
{
var myParam = new Param_GenericObject
{
Name = GH_ComponentParamServer.InventUniqueNickname("ABCD", Params.Input),
MutableNickName = true,
Optional = true
};
myParam.NickName = myParam.Name;
return myParam;
}
public bool DestroyParameter(GH_ParameterSide side, int index) => side == GH_ParameterSide.Output;
}
public record OutputParamWrapper(Param_GenericObject Param, object Values, string? Topology);
@@ -0,0 +1,23 @@
namespace Speckle.Connectors.GrasshopperShared.Components;
// NOTE: The number of spaces determines the order in which they display in the ribbon (nice hack)
public static class ComponentCategories
{
public const string PRIMARY_RIBBON = "Speckle";
public const string OPERATIONS = " Ops";
public const string OBJECTS = " Objects";
public const string COLLECTIONS = " Collections";
public const string PARAMETERS = " Params";
public const string DEVELOPER = "Dev";
}
public enum ComponentState
{
Cancelled,
Expired,
NeedsInput,
Receiving,
Ready,
Sending,
UpToDate
}
@@ -0,0 +1,314 @@
using System.Collections;
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.GrasshopperShared.Components.Dev;
[Guid("C491D26C-84CB-4684-8BD2-AA78D0F2FE53")]
public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterComponent
{
public DeconstructSpeckleParam()
: base(
"Deconstruct",
"D",
"Deconstructs any Speckle param into its atomic parts",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.DEVELOPER
) { }
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_deconstruct;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddGenericParameter(
"Speckle Param",
"SP",
"Speckle param to deconstruct. Expects Collections, Objects, or Materials",
GH_ParamAccess.item
);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager) { }
protected override void SolveInstance(IGH_DataAccess da)
{
object data = new();
da.GetData(0, ref data);
List<OutputParamWrapper> outputParams = new();
switch (data)
{
case SpeckleObjectWrapperGoo obj:
Name = string.IsNullOrEmpty(obj.Value.Name) ? obj.Value.Base.speckle_type : obj.Value.Name;
outputParams = CreateOutputParamsFromBase(obj.Value.Base);
break;
case SpeckleCollectionWrapperGoo coll:
Name = string.IsNullOrEmpty(coll.Value.Collection.name)
? coll.Value.Collection.speckle_type
: coll.Value.Collection.name;
outputParams = CreateOutputParamsFromBase(coll.Value.Collection);
break;
case SpeckleMaterialWrapperGoo matGoo:
Name = string.IsNullOrEmpty(matGoo.Value.Name) ? matGoo.Value.Material.speckle_type : matGoo.Value.Name;
outputParams = CreateOutputParamsFromBase(matGoo.Value.Base);
break;
default:
return;
}
NickName = Name;
if (da.Iteration == 0 && OutputMismatch(outputParams))
{
OnPingDocument()
.ScheduleSolution(
5,
_ =>
{
CreateOutputs(outputParams);
}
);
}
else
{
for (int i = 0; i < outputParams.Count; i++)
{
var outParam = Params.Output[i];
var outParamWrapper = outputParams[i];
switch (outParam.Access)
{
case GH_ParamAccess.item:
da.SetData(i, outParamWrapper.Value);
break;
case GH_ParamAccess.list:
da.SetDataList(i, outParamWrapper.Value as IList);
break;
}
}
}
}
private List<OutputParamWrapper> CreateOutputParamsFromBase(Base @base)
{
List<OutputParamWrapper> result = new();
if (@base == null)
{
return result;
}
// cycle through base props
foreach (var prop in @base.GetMembers(DynamicBaseMemberType.Instance | DynamicBaseMemberType.Dynamic))
{
// Convert and add to corresponding output structure
var value = prop.Value;
switch (value)
{
case null:
result.Add(CreateOutputParamByKeyValue(prop.Key, null, GH_ParamAccess.item));
break;
case IList list:
List<object> nativeObjects = new();
foreach (var x in list)
{
switch (x)
{
case SpeckleCollectionWrapper collWrapper:
nativeObjects.Add(new SpeckleCollectionWrapperGoo(collWrapper));
break;
case SpeckleObjectWrapper objWrapper:
nativeObjects.Add(new SpeckleObjectWrapperGoo(objWrapper));
break;
case Base xBase:
nativeObjects.AddRange(ConvertOrCreateWrapper(xBase));
break;
default:
nativeObjects.Add(x);
break;
}
}
result.Add(CreateOutputParamByKeyValue(prop.Key, nativeObjects, GH_ParamAccess.list));
break;
case Dictionary<string, object?> dict: // this should be treated a properties dict
SpecklePropertyGroupGoo propertyGoo = new();
propertyGoo.CastFrom(dict);
result.Add(CreateOutputParamByKeyValue(prop.Key, propertyGoo, GH_ParamAccess.item));
break;
case SpeckleCollectionWrapper collWrapper:
result.Add(
CreateOutputParamByKeyValue(prop.Key, new SpeckleCollectionWrapperGoo(collWrapper), GH_ParamAccess.item)
);
break;
case SpeckleObjectWrapper objWrapper:
result.Add(
CreateOutputParamByKeyValue(prop.Key, new SpeckleObjectWrapperGoo(objWrapper), GH_ParamAccess.item)
);
break;
case Base baseValue:
result.Add(CreateOutputParamByKeyValue(prop.Key, ConvertOrCreateWrapper(baseValue), GH_ParamAccess.list));
break;
default:
// we don't want to output dynamic property keys
if (prop.Key == nameof(Base.DynamicPropertyKeys))
{
continue;
}
result.Add(CreateOutputParamByKeyValue(prop.Key, prop.Value, GH_ParamAccess.item));
break;
}
}
return result;
}
private List<SpeckleObjectWrapperGoo> ConvertOrCreateWrapper(Base @base)
{
try
{
// convert the base and create a wrapper for each result
List<(GeometryBase, Base)> convertedBase = SpeckleConversionContext.ConvertToHost(@base);
List<SpeckleObjectWrapperGoo> convertedWrappers = new();
foreach ((GeometryBase g, Base b) in convertedBase)
{
SpeckleObjectWrapper convertedWrapper =
new()
{
Base = b,
GeometryBase = g,
Name = b["name"] as string ?? "",
Color = null,
Material = null,
WrapperGuid = null
};
convertedWrappers.Add(new(convertedWrapper));
}
return convertedWrappers;
}
catch (ConversionException)
{
// some classes, like RawEncoding, have no direct conversion or fallback value.
// when this is the case, wrap it to allow users to further expand the object.
SpeckleObjectWrapper convertedWrapper =
new()
{
Base = @base,
GeometryBase = null,
Name = @base[Constants.NAME_PROP] as string ?? "",
Color = null,
Material = null,
WrapperGuid = null,
};
return new() { new SpeckleObjectWrapperGoo(convertedWrapper) };
}
}
private OutputParamWrapper CreateOutputParamByKeyValue(string key, object? value, GH_ParamAccess access)
{
Param_GenericObject param =
new()
{
Name = key,
NickName = key,
Description = "",
Access = access
};
return new OutputParamWrapper(param, value);
}
public bool CanInsertParameter(GH_ParameterSide side, int index) => false;
public bool CanRemoveParameter(GH_ParameterSide side, int index) => false;
public void VariableParameterMaintenance() { }
public IGH_Param CreateParameter(GH_ParameterSide side, int index)
{
var myParam = new Param_GenericObject
{
Name = GH_ComponentParamServer.InventUniqueNickname("ABCD", Params.Input),
MutableNickName = true,
Optional = true
};
myParam.NickName = myParam.Name;
return myParam;
}
public bool DestroyParameter(GH_ParameterSide side, int index)
{
return side == GH_ParameterSide.Output;
}
private void CreateOutputs(List<OutputParamWrapper> outputParams)
{
// TODO: better, nicer handling of creation/removal
while (Params.Output.Count > 0)
{
Params.UnregisterOutputParameter(Params.Output[^1]);
}
foreach (var newParam in outputParams)
{
var param = new Param_GenericObject
{
Name = newParam.Param.Name,
NickName = newParam.Param.NickName,
MutableNickName = false,
Access = newParam.Param.Access
};
Params.RegisterOutputParam(param);
}
Params.OnParametersChanged();
VariableParameterMaintenance();
ExpireSolution(false);
}
private bool OutputMismatch(List<OutputParamWrapper> outputParams)
{
if (Params.Output.Count != outputParams.Count)
{
return true;
}
var count = 0;
foreach (var newParam in outputParams)
{
var oldParam = Params.Output[count];
if (
oldParam.NickName != newParam.Param.NickName
|| oldParam.Name != newParam.Param.Name
|| oldParam.Access != newParam.Param.Access
)
{
return true;
}
count++;
}
return false;
}
}
public record OutputParamWrapper(Param_GenericObject Param, object? Value);
@@ -0,0 +1,19 @@
using Grasshopper.Kernel;
namespace Speckle.Connectors.GrasshopperShared.Components;
public class GhContextMenuButton(
string name,
string nickname,
string description,
Func<ToolStripDropDown, bool>? populateMenuAction = null
) : GH_DocumentObject(name, nickname, description, "Speckle", "UI")
{
public bool Enabled { get; set; } = true;
public override void CreateAttributes() => Attributes = new GhContextMenuButtonAttributes(this);
public override Guid ComponentGuid => new("B01FFD91-F4EC-4332-A9AA-F917AEDAA51D");
public override bool AppendMenuItems(ToolStripDropDown menu) => populateMenuAction?.Invoke(menu) ?? false;
}
@@ -0,0 +1,39 @@
using Grasshopper.GUI;
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel;
namespace Speckle.Connectors.GrasshopperShared.Components;
public class GhContextMenuButtonAttributes(GhContextMenuButton owner) : GH_Attributes<GhContextMenuButton>(owner)
{
protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
{
base.Render(canvas, graphics, channel);
if (channel != GH_CanvasChannel.Objects)
{
return; // No wires or other layers are being drawn in this component.
}
using var button1 = GH_Capsule.CreateTextCapsule(
Bounds,
Bounds,
Owner.Enabled ? GH_Palette.Black : GH_Palette.Grey,
Owner.Name,
2,
0
);
button1.Render(graphics, Parent.Selected, false, false);
}
public override GH_ObjectResponse RespondToMouseUp(GH_Canvas sender, GH_CanvasMouseEvent e)
{
if (!Owner.Enabled && e.Button == MouseButtons.Right)
{
// Prevents canvas from triggering the right-click behaviour, and showing the context menu.
return GH_ObjectResponse.Handled;
}
// Allowing event to bubble up to canvas will handle the event and show the context menu.
return base.RespondToMouseUp(sender, e);
}
}
@@ -0,0 +1,231 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
[Guid("F9418610-ACAE-4417-B010-19EBEA6A121F")]
public class CreateSpeckleObject : GH_Component
{
public CreateSpeckleObject()
: base(
"Speckle Object",
"SO",
"Create or modify a Speckle Object",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
) { }
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_objects_object;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddGenericParameter(
"Object",
"O",
"Input Object. Speckle objects, Model Objects, and geometry are accepted.",
GH_ParamAccess.item
);
Params.Input[0].Optional = true;
pManager.AddGenericParameter("Geometry", "G", "The geometry of the Speckle Object", GH_ParamAccess.item);
Params.Input[1].Optional = true;
pManager.AddTextParameter("Name", "N", "Name of the Speckle Object", GH_ParamAccess.item);
Params.Input[2].Optional = true;
pManager.AddGenericParameter(
"Properties",
"P",
"The properties of the Speckle Object. Speckle Properties and User Content are accepted.",
GH_ParamAccess.item
);
Params.Input[3].Optional = true;
pManager.AddColourParameter("Color", "c", "The color of the Speckle Object", GH_ParamAccess.item);
Params.Input[4].Optional = true;
pManager.AddGenericParameter(
"Material",
"m",
"The material of the Speckle Object. Display Materials, Model Materials, and Speckle Materials are accepted.",
GH_ParamAccess.item
);
Params.Input[5].Optional = true;
/* POC: disable for now as we are doing anything with this
pManager.AddTextParameter(
"Path",
"p",
"The Collection Path of the Speckle Object. Should be delimited with `:`",
GH_ParamAccess.item
);
Params.Input[6].Optional = true;
*/
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(new SpeckleObjectParam(), "Object", "O", "Speckle Object", GH_ParamAccess.item);
pManager.AddGenericParameter("Geometry", "G", "The geometry of the Speckle Object", GH_ParamAccess.item);
pManager.AddTextParameter("Name", "N", "Name of the Speckle Object", GH_ParamAccess.item);
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"The properties of the Speckle Object",
GH_ParamAccess.item
);
pManager.AddColourParameter("Color", "c", "The color of the Speckle Object", GH_ParamAccess.item);
pManager.AddParameter(
new SpeckleMaterialParam(),
"Material",
"M",
"The material of the Speckle Object.",
GH_ParamAccess.item
);
pManager.AddTextParameter(
"Path",
"p",
$"The Collection Path of the Speckle Object, delimited with `{Constants.LAYER_PATH_DELIMITER}`",
GH_ParamAccess.item
);
}
protected override void SolveInstance(IGH_DataAccess da)
{
IGH_Goo? inputObject = null;
da.GetData(0, ref inputObject);
IGH_GeometricGoo? inputGeometry = null;
da.GetData(1, ref inputGeometry);
string? inputName = null;
da.GetData(2, ref inputName);
IGH_Goo? inputProperties = null;
da.GetData(3, ref inputProperties);
Color? inputColor = null;
da.GetData(4, ref inputColor);
IGH_Goo? inputMaterial = null;
da.GetData(5, ref inputMaterial);
//string? inputPath = null;
//da.GetData(6, ref inputPath);
// keep track of mutation
// poc: we should not mark mutations on color or material, as this shouldn't affect the appId of the object, and will allow original display values to stay intact on send.
bool mutated = false;
// process the object
SpeckleObjectWrapperGoo result = new();
if (inputObject != null)
{
if (!result.CastFrom(inputObject))
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
$"Object input is not valid. Only Speckle Objects, Baked Model Objects, and Geometry are accepted."
);
return;
}
}
// process geometry
// at this point, we can ensure that the Base in the wrapper is a DataObject.
if (inputObject == null && inputGeometry == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Pass in an Object or Geometry.");
return;
}
if (inputGeometry != null)
{
result.Value.GeometryBase = inputGeometry.GeometricGooToGeometryBase();
Base converted = SpeckleConversionContext.ConvertToSpeckle(result.Value.GeometryBase);
converted[Constants.NAME_PROP] = result.Value.Name;
converted.applicationId = result.Value.ApplicationId;
result.Value.Base = converted;
mutated = true;
}
// process name
if (inputName != null)
{
result.Value.Name = inputName;
mutated = true;
}
// process properties
if (inputProperties != null)
{
SpecklePropertyGroupGoo propGoo = new();
if (!propGoo.CastFrom(inputProperties))
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
$"Properties input is not valid. Only Speckle Properties and User Content are accepted."
);
return;
}
result.Value.Properties = propGoo;
mutated = true;
}
// process color (no mutation)
if (inputColor != null)
{
result.Value.Color = inputColor;
}
// process material (no mutation)
if (inputMaterial != null)
{
SpeckleMaterialWrapperGoo matWrapperGoo = new();
if (!matWrapperGoo.CastFrom(inputMaterial))
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
$"Material input is not valid. Only Display Materials, Baked Model Materials, and Speckle Materials are accepted."
);
return;
}
result.Value.Material = matWrapperGoo.Value;
}
// process application Id. Use a new appId if mutated, or if this is a new object
result.Value.ApplicationId = mutated
? Guid.NewGuid().ToString()
: result.Value.ApplicationId ?? Guid.NewGuid().ToString();
// get the path
string path =
result.Value.Path.Count > 1
? string.Join(Constants.LAYER_PATH_DELIMITER, result.Value.Path)
: result.Value.Path.FirstOrDefault();
// set all the data
da.SetData(0, result.Value);
da.SetData(1, result.Value.GeometryBase);
da.SetData(2, result.Value.Name);
da.SetData(3, result.Value.Properties);
da.SetData(4, result.Value.Color);
da.SetData(5, result.Value.Material);
da.SetData(6, path);
}
}
@@ -0,0 +1,140 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using Speckle.Connectors.GrasshopperShared.HostApp.Extras;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
[Guid("A3FD5CBF-DFB0-44DF-9988-04466EB8E5E6")]
public class CreateSpeckleProperties : GH_Component, IGH_VariableParameterComponent
{
public CreateSpeckleProperties()
: base(
"Create Properties",
"CP",
"Creates a set of properties for Speckle objects",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
) { }
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_properties_create;
private readonly DebounceDispatcher _debounceDispatcher = new();
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
var p = CreateParameter(GH_ParameterSide.Input, 0);
pManager.AddParameter(p);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddGenericParameter("Properties", "P", "Properties for Speckle Objects", GH_ParamAccess.item);
}
protected override void SolveInstance(IGH_DataAccess da)
{
// Create a data tree to store output
Dictionary<string, object?> properties = new();
// Check for structure of all inputs to see matching branches
foreach (var inputParam in Params.Input)
{
string inputName = inputParam.NickName;
if (properties.ContainsKey(inputName))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Duplicate property name found: {inputName}.");
return;
}
properties.Add(inputName, new());
}
for (int i = 0; i < Params.Input.Count; i++)
{
object? value = null;
var success = da.GetData(i, ref value);
var actualValue = value?.GetType().GetProperty("Value").GetValue(value); // note: unsure if reflection here hurts our performance
if (!success || value == null || actualValue == null)
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
$"Parameter {Params.Input[i].NickName} should not contain anything other than strings, doubles, ints, and bools."
);
return;
}
properties[Params.Input[i].NickName] = actualValue;
}
var groupGoo = new SpecklePropertyGroupGoo(properties);
da.SetData(0, groupGoo);
}
public bool CanInsertParameter(GH_ParameterSide side, int index)
{
return side == GH_ParameterSide.Input;
}
public bool CanRemoveParameter(GH_ParameterSide side, int index)
{
return side == GH_ParameterSide.Input;
}
public IGH_Param CreateParameter(GH_ParameterSide side, int index)
{
var myParam = new Param_GenericObject
{
Name = $"Property {Params.Input.Count + 1}",
MutableNickName = true,
Optional = true,
Access = GH_ParamAccess.item
};
myParam.NickName = myParam.Name;
myParam.Optional = true;
return myParam;
}
public bool DestroyParameter(GH_ParameterSide side, int index)
{
return side == GH_ParameterSide.Input;
}
public void VariableParameterMaintenance()
{
// TODO?
}
public override void AddedToDocument(GH_Document document)
{
base.AddedToDocument(document);
Params.ParameterChanged += (sender, args) =>
{
if (args.ParameterSide == GH_ParameterSide.Output)
{
return;
}
switch (args.OriginalArguments.Type)
{
case GH_ObjectEventType.NickName:
// This means the user is typing characters, debounce until it stops for 400ms before expiring the solution.
// Prevents UI from locking too soon while writing new names for inputs.
args.Parameter.Name = args.Parameter.NickName;
_debounceDispatcher.Debounce(500, e => ExpireSolution(true));
break;
case GH_ObjectEventType.NickNameAccepted:
args.Parameter.Name = args.Parameter.NickName;
ExpireSolution(true);
break;
}
};
}
}
@@ -0,0 +1,182 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Speckle.Connectors.GrasshopperShared.Components.BaseComponents;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
/// <summary>
/// Given a list of objects, this component will filter the list for objects that match the queries.
/// </summary>
[Guid("26AEA046-4DD4-4F61-8251-E92A6D2AC880")]
public class FilterSpeckleObjects : GH_Component
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_objects_filter;
public FilterSpeckleObjects()
: base(
"Filter Objects",
"fO",
"Filters a list of Speckle Objects according to inputs. Filter methods: Equals (default), StartsWith(<), EndsWith(>) , Contains(?), Regex(;)",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
) { }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(new SpeckleObjectParam(), "Objects", "O", "Speckle Objects to filter", GH_ParamAccess.list);
pManager.AddTextParameter("Name", "N", "Find objects with a matching name", GH_ParamAccess.item);
Params.Input[1].Optional = true;
pManager.AddTextParameter(
"Property Key",
"P",
"Find objects with a property that has a matching key",
GH_ParamAccess.item
);
Params.Input[2].Optional = true;
pManager.AddTextParameter(
"Material Name",
"M",
"Find objects with a render material that has a matching name",
GH_ParamAccess.item
);
Params.Input[3].Optional = true;
pManager.AddTextParameter(
"Application Id",
"aID",
"Find objects with a matching applicationId",
GH_ParamAccess.item
);
Params.Input[4].Optional = true;
pManager.AddTextParameter("Speckle Id", "sID", "Find objects with a matching Speckle id", GH_ParamAccess.item);
Params.Input[5].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleObjectParam(),
"Objects",
"O",
"The objects that match the queries",
GH_ParamAccess.tree
);
pManager.AddParameter(
new SpeckleObjectParam(),
"Culled Objects",
"co",
"The objects that did not match the queries",
GH_ParamAccess.tree
);
}
protected override void SolveInstance(IGH_DataAccess dataAccess)
{
List<SpeckleObjectWrapperGoo?> inputObjects = new();
dataAccess.GetDataList(0, inputObjects);
if (inputObjects.Count == 0)
{
return;
}
string name = "";
dataAccess.GetData(1, ref name);
string property = "";
dataAccess.GetData(2, ref property);
string material = "";
dataAccess.GetData(3, ref material);
string appId = "";
dataAccess.GetData(4, ref appId);
string speckleId = "";
dataAccess.GetData(5, ref speckleId);
List<SpeckleObjectWrapper> matchedObjects = new();
List<SpeckleObjectWrapper> removedObjects = new();
for (int i = 0; i < inputObjects.Count; i++)
{
SpeckleObjectWrapperGoo? inputObject = inputObjects[i];
if (inputObject is null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"A null input object was detected.");
return;
}
// filter by name
if (!MatchesSearchPattern(name, inputObject.Value.Name))
{
removedObjects.Add(inputObject.Value);
continue;
}
// filter by property
bool foundProperty = false;
if (string.IsNullOrEmpty(property))
{
foundProperty = true;
}
else
{
foreach (string key in inputObject.Value.Properties.Value.Keys)
{
if (MatchesSearchPattern(property, key))
{
foundProperty = true;
break;
}
}
}
if (!foundProperty)
{
removedObjects.Add(inputObject.Value);
continue;
}
// filter by material name
if (!MatchesSearchPattern(material, inputObject.Value.Material?.Name ?? ""))
{
removedObjects.Add(inputObject.Value);
continue;
}
// filter by application id
if (!MatchesSearchPattern(appId, inputObject.Value.Base.applicationId ?? ""))
{
removedObjects.Add(inputObject.Value);
continue;
}
// filter by speckle id
if (!MatchesSearchPattern(speckleId, inputObject.Value.Base.id ?? ""))
{
removedObjects.Add(inputObject.Value);
continue;
}
matchedObjects.Add(inputObject.Value);
}
// Set output objects
dataAccess.SetDataList(0, matchedObjects);
dataAccess.SetDataList(1, removedObjects);
}
private bool MatchesSearchPattern(string searchPattern, string target)
{
if (string.IsNullOrEmpty(searchPattern))
{
return true;
}
return Operator.IsSymbolNameLike(target, searchPattern);
}
}
@@ -0,0 +1,141 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
/// <summary>
/// Given a collection, this component will output the objects in the subcollection corresponding to the input path
/// </summary>
[Guid("77CAEE94-F0B9-4611-897C-71F2A22BA311")]
public class GetCollectionObjects : GH_Component
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_objects_query;
public GetCollectionObjects()
: base(
"Query Objects",
"qO",
"Retrieves the objects inside a Speckle collection at the specified path",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
) { }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(
new SpeckleCollectionParam(),
"Collection",
"C",
"Collection to retrieve objects from",
GH_ParamAccess.item
);
pManager.AddTextParameter(
"Path",
"C",
"Get the Speckle objects in the subcollection indicated by this path",
GH_ParamAccess.item
);
Params.Input[1].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleObjectParam(),
"Objects",
"O",
"The objects in the input collection that match the queries",
GH_ParamAccess.tree
);
}
protected override void SolveInstance(IGH_DataAccess dataAccess)
{
SpeckleCollectionWrapperGoo collectionWrapperGoo = new();
dataAccess.GetData(0, ref collectionWrapperGoo);
if (collectionWrapperGoo.Value == null)
{
return;
}
string path = "";
dataAccess.GetData(1, ref path);
// filter by collection path
// Note: the collection paths selector will omit the target collection from the path of nested collections.
// the discard ("_objects") will be used to indicate objects found directly in the target collection.
List<SpeckleObjectWrapper> filteredObjects = new();
SpeckleCollectionWrapper? targetCollectionWrapper = null;
if (!string.IsNullOrEmpty(path))
{
targetCollectionWrapper =
path == "_objects" ? collectionWrapperGoo.Value : FindCollection(collectionWrapperGoo.Value, path);
filteredObjects = targetCollectionWrapper
.Elements.Where(e => e is SpeckleObjectWrapper)
.Select(e => (SpeckleObjectWrapper)e)
.ToList();
}
else
{
filteredObjects = GetAllObjectsFromCollection(collectionWrapperGoo.Value).ToList();
}
// Set output objects
if (targetCollectionWrapper?.Topology is string topology && !string.IsNullOrEmpty(topology))
{
var tree = GrasshopperHelpers.CreateDataTreeFromTopologyAndItems(topology, filteredObjects);
dataAccess.SetDataTree(0, tree);
}
else
{
dataAccess.SetDataList(0, filteredObjects);
}
}
private IEnumerable<SpeckleObjectWrapper> GetAllObjectsFromCollection(SpeckleCollectionWrapper collectionWrapper)
{
foreach (SpeckleWrapper element in collectionWrapper.Elements)
{
switch (element)
{
case SpeckleCollectionWrapper childCollectionWrapper:
foreach (var item in GetAllObjectsFromCollection(childCollectionWrapper))
{
yield return item;
}
break;
case SpeckleObjectWrapper objectWrapper:
yield return objectWrapper;
break;
}
}
}
private SpeckleCollectionWrapper FindCollection(SpeckleCollectionWrapper root, string unifiedPath)
{
// POC: SpeckleCollections now have a full list<string> path prop on them always. Is this easier to use?
List<string> paths = unifiedPath.Split([Constants.LAYER_PATH_DELIMITER], StringSplitOptions.None).ToList();
SpeckleCollectionWrapper currentCollectionWrapper = root;
while (paths.Count != 0)
{
currentCollectionWrapper = currentCollectionWrapper
.Elements.OfType<SpeckleCollectionWrapper>()
.First(wrapper => wrapper.Name == paths.First());
paths.RemoveAt(0);
if (paths.Count == 0)
{
return currentCollectionWrapper;
}
}
throw new SpeckleException("Did not find collection");
}
}
@@ -0,0 +1,162 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
[Guid("116F08A5-BAA7-45B3-B6C8-469E452C9AC7")]
public class GetObjectProperties : GH_Component, IGH_VariableParameterComponent
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_properties_query;
public GetObjectProperties()
: base(
"Query Properties",
"qP",
"Retrieves the values of the properties inside Speckle Objects at the specified keys",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
) { }
protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
{
pManager.AddParameter(
new SpeckleObjectParam(),
"Objects",
"O",
"Speckle Objects to retrieve properties",
GH_ParamAccess.item
);
pManager.AddTextParameter("Keys", "K", "Property keys to filter by", GH_ParamAccess.list);
}
protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager) { }
protected override void SolveInstance(IGH_DataAccess da)
{
List<string> paths = new();
da.GetDataList(1, paths);
if (paths.Count == 0)
{
return;
}
if (OutputMismatch(paths))
{
OnPingDocument()
.ScheduleSolution(
5,
_ =>
{
CreateOutputs(paths);
}
);
}
else
{
SpeckleObjectWrapperGoo objectWrapperGoo = new();
da.GetData(0, ref objectWrapperGoo);
for (int i = 0; i < paths.Count; i++)
{
var name = paths[i];
SpecklePropertyGoo objectProperty = FindProperty(objectWrapperGoo.Value.Properties, name);
da.SetData(i, objectProperty.Value);
}
}
}
private SpecklePropertyGoo FindProperty(SpecklePropertyGroupGoo root, string unifiedPath)
{
if (!root.Value.TryGetValue(unifiedPath, out SpecklePropertyGoo currentGoo))
{
return new() { Path = unifiedPath, Value = "" };
}
return currentGoo;
}
private bool OutputMismatch(List<string> outputParams)
{
if (Params.Output.Count != outputParams.Count)
{
return true;
}
var count = 0;
foreach (var newParam in outputParams)
{
var oldParam = Params.Output[count];
if (oldParam.NickName != newParam || oldParam.Name != newParam)
{
return true;
}
count++;
}
return false;
}
private void CreateOutputs(List<string> outputParams)
{
// Ensure we have the required count of output parameters
while (Params.Output.Count != outputParams.Count)
{
if (Params.Output.Count > outputParams.Count) // if too many, unregister
{
Params.UnregisterOutputParameter(Params.Output[^1]);
}
if (Params.Output.Count < outputParams.Count) // if too little, add some
{
var param = new Param_GenericObject
{
Name = "newParam",
NickName = "newParam",
MutableNickName = false,
Access = GH_ParamAccess.item
};
Params.RegisterOutputParam(param);
}
}
// now unify names and nicknames
int index = 0;
foreach (var newParam in outputParams)
{
Params.Output[index].NickName = newParam;
Params.Output[index].Name = newParam;
index++;
}
// now we can update the output params
Params.OnParametersChanged();
VariableParameterMaintenance();
ExpireSolution(false);
}
public bool CanInsertParameter(GH_ParameterSide side, int index) => false;
public bool CanRemoveParameter(GH_ParameterSide side, int index) => false;
public IGH_Param CreateParameter(GH_ParameterSide side, int index)
{
var myParam = new Param_GenericObject
{
Name = GH_ComponentParamServer.InventUniqueNickname("ABCD", Params.Input),
MutableNickName = true,
Optional = true
};
myParam.NickName = myParam.Name;
return myParam;
}
public bool DestroyParameter(GH_ParameterSide side, int index) => side == GH_ParameterSide.Output;
public void VariableParameterMaintenance() { }
}
@@ -0,0 +1,69 @@
using Grasshopper.Kernel.Types;
using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.GrasshopperShared.Components.BaseComponents;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
#if RHINO8_OR_GREATER
using Grasshopper.Rhinoceros.Model;
#endif
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
public class PropertyGroupPathsSelector : ValueSet<IGH_Goo>
{
public PropertyGroupPathsSelector()
: base(
"Property Selector",
"pSelect",
"Allows you to select a set of property keys for querying",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
) { }
public override Guid ComponentGuid => new Guid("8882BE3A-81F1-4416-B420-58D69E4CC8F1");
protected override Bitmap Icon => Resources.speckle_inputs_property;
protected override void LoadVolatileData()
{
var objectPropertyGroups = VolatileData
.AllData(true)
.OfType<SpeckleObjectWrapperGoo>()
.Select(goo => goo.Value.Properties.Value)
.ToList();
#if RHINO8_OR_GREATER
// support model objects direct piping also
if (objectPropertyGroups.Count != VolatileData.DataCount)
{
var modelObjects = VolatileData
.AllData(true)
.OfType<ModelObject>()
.Select(mo => new SpeckleObjectWrapperGoo(mo).Value.Properties.Value)
.ToList();
objectPropertyGroups.AddRange(modelObjects);
}
#endif
if (objectPropertyGroups.Count == 0)
{
return;
}
var paths = GetPropertyPaths(objectPropertyGroups);
m_data.Clear();
m_data.AppendRange(paths.Select(s => new GH_String(s)));
}
private static List<string> GetPropertyPaths(List<Dictionary<string, SpecklePropertyGoo>> objectPropertyGroups)
{
var result = new HashSet<string>();
foreach (var dict in objectPropertyGroups)
{
result.AddRange(
dict.Keys.Where(k => !(k.EndsWith(".name") || k.EndsWith(".units") || k.EndsWith(".internalDefinitionName")))
);
}
return result.ToList();
}
}
@@ -0,0 +1,16 @@
using Speckle.Connectors.Common.Operations;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Receive;
public record GrasshopperReceiveInfo(
string AccountId,
Uri ServerUrl,
string? WorkspaceId,
string ProjectId,
string ProjectName,
string ModelId,
string ModelName,
string SelectedVersionId,
string SourceApplication,
string? SelectedVersionUserId
) : ReceiveInfo(AccountId, ServerUrl, ProjectId, ProjectName, ModelId, ModelName, SelectedVersionId, SourceApplication);
@@ -0,0 +1,595 @@
using System.Runtime.InteropServices;
using Grasshopper.GUI;
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Attributes;
using GrasshopperAsyncComponent;
using Microsoft.Extensions.DependencyInjection;
using Rhino;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Analytics;
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Connectors.GrasshopperShared.Registration;
using Speckle.Sdk;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Extensions;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Receive;
[Guid("1587DF34-83E5-4AFE-B42E-F7C5C37ECD68")]
public class ReceiveAsyncComponent : GH_AsyncComponent
{
public ReceiveAsyncComponent()
: base("Load", "L", "Load a model from Speckle", ComponentCategories.PRIMARY_RIBBON, ComponentCategories.OPERATIONS)
{
BaseWorker = new ReceiveComponentWorker(this);
Attributes = new ReceiveAsyncComponentAttributes(this);
}
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_operations_load;
public string InputType { get; set; }
public bool AutoReceive { get; set; }
public string ReceivedVersionId { get; set; }
public ComponentState CurrentComponentState { get; set; } = ComponentState.NeedsInput;
public bool JustPastedIn { get; set; }
public string LastVersionDate { get; set; }
public string LastInfoMessage { get; set; }
public HostApp.SpeckleUrlModelResource? UrlModelResource { get; set; }
// DI props
public IClient ApiClient { get; private set; }
public MixPanelManager MixPanelManager { get; private set; }
public GrasshopperReceiveOperation ReceiveOperation { get; private set; }
public RootObjectUnpacker RootObjectUnpacker { get; private set; }
public static IServiceScope? Scope { get; private set; }
public AccountService AccountService { get; private set; }
public AccountManager AccountManager { get; private set; }
public IClientFactory ClientFactory { get; private set; }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam(GH_ParamAccess.item));
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleCollectionParam(GH_ParamAccess.item),
"Collection",
"collection",
"The model collection of the loaded version",
GH_ParamAccess.item
);
}
protected override void SolveInstance(IGH_DataAccess da)
{
da.DisableGapLogic();
// Dependency Injection
Scope = PriorityLoader.Container.CreateScope();
ReceiveOperation = Scope.ServiceProvider.GetRequiredService<GrasshopperReceiveOperation>();
MixPanelManager = Scope.ServiceProvider.GetRequiredService<MixPanelManager>();
RootObjectUnpacker = Scope.ServiceProvider.GetService<RootObjectUnpacker>();
AccountService = Scope.ServiceProvider.GetRequiredService<AccountService>();
AccountManager = Scope.ServiceProvider.GetRequiredService<AccountManager>();
ClientFactory = Scope.ServiceProvider.GetRequiredService<IClientFactory>();
// We need to call this always in here to be able to react and set events :/
ParseInput(da);
if (
(
AutoReceive
|| CurrentComponentState == ComponentState.Ready
|| CurrentComponentState == ComponentState.Receiving
) && !JustPastedIn
)
{
CurrentComponentState = ComponentState.Receiving;
// Delegate control to parent async component.
base.SolveInstance(da);
return;
}
if (JustPastedIn)
{
// This ensures that we actually do a run. The worker will check and determine if it needs to pull an existing object or not.
OnDisplayExpired(true);
base.SolveInstance(da);
}
else
{
CurrentComponentState = ComponentState.Expired;
Message = "Expired";
OnDisplayExpired(true);
}
}
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
base.AppendAdditionalMenuItems(menu);
Menu_AppendSeparator(menu);
if (InputType == "Model")
{
var autoReceiveMi = Menu_AppendItem(
menu,
"Load automatically",
(s, e) =>
{
AutoReceive = !AutoReceive;
RhinoApp.InvokeOnUiThread(
(Action)
delegate
{
OnDisplayExpired(true);
}
);
},
true,
AutoReceive
);
autoReceiveMi.ToolTipText =
"Toggle automatic loading. If set, any new version will be loaded instantly. This only is applicable when receiving from a model url.";
}
else
{
var autoReceiveMi = Menu_AppendItem(menu, "Automatic loading is disabled because you have specified a version.");
autoReceiveMi.ToolTipText = "To enable automatic loading, select a model without selecting a specific version.";
}
Menu_AppendSeparator(menu);
if (CurrentComponentState == ComponentState.Receiving)
{
Menu_AppendItem(
menu,
"Cancel Load",
(s, e) =>
{
CurrentComponentState = ComponentState.Expired;
RequestCancellation();
}
);
}
}
private void HandleNewCommit()
{
Message = "Expired";
CurrentComponentState = ComponentState.Expired;
AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, $"There is a newer version available for this {InputType}");
RhinoApp.InvokeOnUiThread(
(Action)
delegate
{
if (AutoReceive)
{
ExpireSolution(true);
}
else
{
OnDisplayExpired(true);
}
}
);
}
public override void RemovedFromDocument(GH_Document document)
{
RequestCancellation();
Scope?.Dispose();
base.RemovedFromDocument(document);
}
public override void DocumentContextChanged(GH_Document document, GH_DocumentContext context)
{
switch (context)
{
case GH_DocumentContext.Loaded:
{
// Will execute every time a document becomes active (from background or opening file.).
if (UrlModelResource != null)
{
Task.Run(async () =>
{
// Ensure fresh instance of client.
ResetApiClient(UrlModelResource);
// Get last commit from the branch
var b = UrlModelResource.GetReceiveInfo(ApiClient);
await b;
// Compare version ids. If they don't match, notify user or fetch data if in auto mode
if (b.Result.SelectedVersionId != ReceivedVersionId)
{
HandleNewCommit();
}
OnDisplayExpired(true);
});
}
break;
}
case GH_DocumentContext.Unloaded:
// Will execute every time a document becomes inactive (in background or closing file.)
// Correctly dispose of the client when changing documents to prevent subscription handlers being called in background.
CurrentComponentState = ComponentState.Expired;
RequestCancellation();
ApiClient?.Dispose();
break;
}
base.DocumentContextChanged(document, context);
}
private void ParseInput(IGH_DataAccess da)
{
HostApp.SpeckleUrlModelResource? dataInput = null;
da.GetData(0, ref dataInput);
if (dataInput is null)
{
UrlModelResource = null;
TriggerAutoSave();
return;
}
// set the type of url input
switch (dataInput)
{
case SpeckleUrlModelVersionResource:
InputType = "Version";
AutoReceive = false;
LastInfoMessage = "";
ResetApiClient(dataInput);
return;
case SpeckleUrlModelResource:
InputType = "Model";
// handled in do work
break;
default:
InputType = "Invalid";
break;
}
if (UrlModelResource != null && UrlModelResource.Equals(dataInput) && !JustPastedIn)
{
return;
}
UrlModelResource = dataInput;
ResetApiClient(UrlModelResource);
}
private void ApiClient_OnVersionCreated(object sender, ProjectVersionsUpdatedMessage e)
{
HandleNewCommit();
}
public void ResetApiClient(SpeckleUrlModelResource urlResource)
{
try
{
Account? account =
urlResource.AccountId != null
? AccountManager.GetAccount(urlResource.AccountId)
: AccountService.GetAccountWithServerUrlFallback("", new Uri(urlResource.Server)); // fallback the account that matches with URL if any
if (account is null)
{
throw new SpeckleAccountManagerException($"No default account was found");
}
ApiClient?.Dispose();
ApiClient = ClientFactory.Create(account);
ApiClient.Subscription.CreateProjectVersionsUpdatedSubscription(urlResource.ProjectId).Listeners +=
ApiClient_OnVersionCreated;
}
catch (Exception e) when (!e.IsFatal())
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, e.ToFormattedString());
}
}
}
public class ReceiveComponentWorker : WorkerInstance
{
public ReceiveComponentWorker(GH_Component p)
: base(p) { }
public Base Root { get; set; }
public SpeckleUrlModelResource? UrlModelResource { get; set; }
public SpeckleCollectionWrapperGoo Result { get; set; }
private List<(GH_RuntimeMessageLevel, string)> RuntimeMessages { get; } = new();
public override WorkerInstance Duplicate()
{
return new ReceiveComponentWorker(Parent);
}
public override void GetData(IGH_DataAccess da, GH_ComponentParamServer p)
{
UrlModelResource = ((ReceiveAsyncComponent)Parent).UrlModelResource;
}
public override void SetData(IGH_DataAccess da)
{
if (CancellationToken.IsCancellationRequested)
{
return;
}
foreach (var (level, message) in RuntimeMessages)
{
Parent.AddRuntimeMessage(level, message);
}
var parent = (ReceiveAsyncComponent)Parent;
parent.CurrentComponentState = ComponentState.UpToDate;
parent.JustPastedIn = false;
if (Result == null)
{
return;
}
da.SetData(0, Result);
}
#pragma warning disable CA1506
public override void DoWork(Action<string, double> reportProgress, Action done)
#pragma warning restore CA1506
{
var receiveComponent = (ReceiveAsyncComponent)Parent;
try
{
if (UrlModelResource is null)
{
throw new InvalidOperationException("Model Resource was null");
}
// Means it's a copy paste of an empty non-init component; set the record and exit fast.
if (receiveComponent.JustPastedIn && !receiveComponent.AutoReceive)
{
receiveComponent.JustPastedIn = false;
return;
}
receiveComponent.CurrentComponentState = ComponentState.Receiving;
RhinoApp.InvokeOnUiThread(
(Action)
delegate
{
receiveComponent.OnDisplayExpired(true);
}
);
var t = Task.Run(async () =>
{
// Step 1 - RECEIVE FROM SERVER
var receiveInfo = await UrlModelResource
.GetReceiveInfo(receiveComponent.ApiClient, CancellationToken)
.ConfigureAwait(false);
var progress = new Progress<CardProgress>(p =>
{
reportProgress(Id, p.Progress ?? 0);
//eceiveComponent.Message = $"{p.Status}";
});
if (CancellationToken.IsCancellationRequested)
{
return;
}
if (receiveInfo == null)
{
done();
return;
}
Root = await receiveComponent
.ReceiveOperation.ReceiveCommitObject(receiveInfo, progress, CancellationToken)
.ConfigureAwait(false);
if (CancellationToken.IsCancellationRequested)
{
return;
}
// Step 2 - CONVERT
//receiveComponent.Message = $"Unpacking...";
LocalToGlobalUnpacker localToGlobalUnpacker = new();
TraversalContextUnpacker traversalContextUnpacker = new();
var unpackedRoot = receiveComponent.RootObjectUnpacker.Unpack(Root);
// "flatten" block instances
var localToGlobalMaps = localToGlobalUnpacker.Unpack(
unpackedRoot.DefinitionProxies,
unpackedRoot.ObjectsToConvert.ToList()
);
// TODO: unpack colors and render materials
GrasshopperColorUnpacker colorUnpacker = new(unpackedRoot);
GrasshopperMaterialUnpacker materialUnpacker = new(unpackedRoot);
GrasshopperCollectionRebuilder collectionRebuilder =
new((Root as Collection) ?? new Collection() { name = "unnamed" });
LocalToGlobalMapHandler mapHandler =
new(traversalContextUnpacker, collectionRebuilder, colorUnpacker, materialUnpacker);
int count = 0;
int total = localToGlobalMaps.Count;
foreach (var map in localToGlobalMaps)
{
mapHandler.CreateGrasshopperObjectFromMap(map);
count++;
}
Result = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
var customProperties = new Dictionary<string, object>()
{
{ "isAsync", true },
{ "sourceHostApp", HostApplications.GetSlugFromHostAppNameAndVersion(receiveInfo.SourceApplication) },
{ "auto", receiveComponent.AutoReceive }
};
if (receiveInfo.WorkspaceId != null)
{
customProperties.Add("workspace_id", receiveInfo.WorkspaceId);
}
if (receiveInfo.SelectedVersionUserId != null)
{
customProperties.Add(
"isMultiplayer",
receiveInfo.SelectedVersionUserId != receiveComponent.ApiClient.Account.userInfo.id
);
}
await receiveComponent.MixPanelManager.TrackEvent(
MixPanelEvents.Receive,
receiveComponent.ApiClient.Account,
customProperties
);
// DONE
done();
});
t.Wait();
}
catch (Exception ex) when (!ex.IsFatal())
{
RuntimeMessages.Add((GH_RuntimeMessageLevel.Error, ex.ToFormattedString()));
done();
}
}
}
public class ReceiveAsyncComponentAttributes : GH_ComponentAttributes
{
private bool _selected;
public ReceiveAsyncComponentAttributes(GH_Component owner)
: base(owner) { }
private Rectangle ButtonBounds { get; set; }
public override bool Selected
{
get => _selected;
set => _selected = value;
}
protected override void Layout()
{
base.Layout();
var baseRec = GH_Convert.ToRectangle(Bounds);
baseRec.Height += 26;
var btnRec = baseRec;
btnRec.Y = btnRec.Bottom - 26;
btnRec.Height = 26;
btnRec.Inflate(-2, -2);
Bounds = baseRec;
ButtonBounds = btnRec;
}
protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
{
base.Render(canvas, graphics, channel);
var state = ((ReceiveAsyncComponent)Owner).CurrentComponentState;
if (channel == GH_CanvasChannel.Objects)
{
if (((ReceiveAsyncComponent)Owner).AutoReceive)
{
var autoReceiveButton = GH_Capsule.CreateTextCapsule(
ButtonBounds,
ButtonBounds,
GH_Palette.Blue,
"Auto Load",
2,
0
);
autoReceiveButton.Render(graphics, Selected, Owner.Locked, false);
autoReceiveButton.Dispose();
}
else
{
var palette =
state == ComponentState.Expired || state == ComponentState.UpToDate || state == ComponentState.Cancelled
? GH_Palette.Black
: GH_Palette.Transparent;
var text = state != ComponentState.Receiving ? "Load" : "Loading...";
var button = GH_Capsule.CreateTextCapsule(
ButtonBounds,
ButtonBounds,
palette,
text,
2,
state == ComponentState.Expired ? 10 : 0
);
button.Render(graphics, Selected, Owner.Locked, false);
button.Dispose();
}
}
}
public override GH_ObjectResponse RespondToMouseDown(GH_Canvas sender, GH_CanvasMouseEvent e)
{
if (e.Button != MouseButtons.Left)
{
return base.RespondToMouseDown(sender, e);
}
if (!((RectangleF)ButtonBounds).Contains(e.CanvasLocation))
{
return base.RespondToMouseDown(sender, e);
}
if (((ReceiveAsyncComponent)Owner).CurrentComponentState == ComponentState.Receiving)
{
return GH_ObjectResponse.Handled;
}
if (((ReceiveAsyncComponent)Owner).AutoReceive)
{
((ReceiveAsyncComponent)Owner).AutoReceive = false;
Owner.OnDisplayExpired(true);
return GH_ObjectResponse.Handled;
}
// TODO: check if owner has null account/client, and call the reset thing SYNC
((ReceiveAsyncComponent)Owner).CurrentComponentState = ComponentState.Ready;
Owner.ExpireSolution(true);
return GH_ObjectResponse.Handled;
}
}
@@ -0,0 +1,191 @@
using Grasshopper.Kernel;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Analytics;
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.Components.BaseComponents;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Connectors.GrasshopperShared.Registration;
using Speckle.Sdk;
using Speckle.Sdk.Api;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Receive;
public class ReceiveComponentInput
{
public SpeckleUrlModelResource Resource { get; }
public bool Run { get; }
public ReceiveComponentInput(SpeckleUrlModelResource resource, bool run)
{
Resource = resource;
Run = run;
}
}
public class ReceiveComponentOutput
{
public SpeckleCollectionWrapperGoo RootObject { get; set; }
}
public class ReceiveComponent : SpeckleScopedTaskCapableComponent<ReceiveComponentInput, ReceiveComponentOutput>
{
private readonly MixPanelManager _mixpanel;
public ReceiveComponent()
: base(
"(Sync) Load",
"sL",
"Load a model from Speckle, synchronously",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.DEVELOPER
)
{
_mixpanel = PriorityLoader.Container.GetRequiredService<MixPanelManager>();
}
public override Guid ComponentGuid => new("74954F59-B1B7-41FD-97DE-4C6B005F2801");
protected override Bitmap Icon => Resources.speckle_operations_syncload;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam(GH_ParamAccess.item));
pManager.AddBooleanParameter("Run", "r", "Run the load operation", GH_ParamAccess.item);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleCollectionParam(GH_ParamAccess.item),
"Collection",
"collection",
"The model collection of the loaded version",
GH_ParamAccess.item
);
}
protected override ReceiveComponentInput GetInput(IGH_DataAccess da)
{
SpeckleUrlModelResource? url = null;
da.GetData(0, ref url);
if (url is null)
{
throw new SpeckleException("Speckle model resource is null");
}
bool run = false;
da.GetData(1, ref run);
return new(url, run);
}
protected override void SetOutput(IGH_DataAccess da, ReceiveComponentOutput result)
{
if (result.RootObject is null)
{
Message = "Not Loaded";
}
else
{
da.SetData(0, result.RootObject);
Message = "Done";
}
}
protected override async Task<ReceiveComponentOutput> PerformScopedTask(
ReceiveComponentInput input,
IServiceScope scope,
CancellationToken cancellationToken = default
)
{
if (!input.Run)
{
return new();
}
var accountService = scope.ServiceProvider.GetRequiredService<AccountService>();
var accountManager = scope.ServiceProvider.GetRequiredService<AccountManager>();
var clientFactory = scope.ServiceProvider.GetRequiredService<IClientFactory>();
var receiveOperation = scope.ServiceProvider.GetRequiredService<GrasshopperReceiveOperation>();
// Do the thing 👇🏼
Account? account =
input.Resource.AccountId != null
? accountManager.GetAccount(input.Resource.AccountId)
: accountService.GetAccountWithServerUrlFallback("", new Uri(input.Resource.Server)); // fallback the account that matches with URL if any
if (account is null)
{
throw new SpeckleAccountManagerException($"No default account was found");
}
using var client = clientFactory.Create(account);
var receiveInfo = await input.Resource.GetReceiveInfo(client, cancellationToken).ConfigureAwait(false);
var progress = new Progress<CardProgress>(_ =>
{
// TODO: Progress only makes sense in non-blocking async receive, which is not supported yet.
// Message = $"{progress.Status}: {progress.Progress}";
});
var root = await receiveOperation
.ReceiveCommitObject(receiveInfo, progress, cancellationToken)
.ConfigureAwait(false);
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
var customProperties = new Dictionary<string, object>()
{
{ "isAsync", false },
{ "sourceHostApp", HostApplications.GetSlugFromHostAppNameAndVersion(receiveInfo.SourceApplication) }
};
if (receiveInfo.WorkspaceId != null)
{
customProperties.Add("workspace_id", receiveInfo.WorkspaceId);
}
if (receiveInfo.SelectedVersionUserId != null)
{
customProperties.Add("isMultiplayer", receiveInfo.SelectedVersionUserId != client.Account.userInfo.id);
}
await _mixpanel.TrackEvent(MixPanelEvents.Receive, account, customProperties);
// We need to rethink these lovely unpackers, there's a bit too many of 'em
var rootObjectUnpacker = scope.ServiceProvider.GetService<RootObjectUnpacker>();
var localToGlobalUnpacker = new LocalToGlobalUnpacker();
var traversalContextUnpacker = new TraversalContextUnpacker();
var unpackedRoot = rootObjectUnpacker.Unpack(root);
// "flatten" block instances
var localToGlobalMaps = localToGlobalUnpacker.Unpack(
unpackedRoot.DefinitionProxies,
unpackedRoot.ObjectsToConvert.ToList()
);
// unpack colors and render materials
GrasshopperColorUnpacker colorUnpacker = new(unpackedRoot);
GrasshopperMaterialUnpacker materialUnpacker = new(unpackedRoot);
GrasshopperCollectionRebuilder collectionRebuilder =
new((root as Collection) ?? new Collection() { name = "unnamed" });
LocalToGlobalMapHandler mapHandler =
new(traversalContextUnpacker, collectionRebuilder, colorUnpacker, materialUnpacker);
foreach (var map in localToGlobalMaps)
{
mapHandler.CreateGrasshopperObjectFromMap(map);
}
// var x = new SpeckleCollectionGoo { Value = collGen.RootCollection };
var goo = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
return new ReceiveComponentOutput { RootObject = goo };
}
}
@@ -0,0 +1,12 @@
using Speckle.Connectors.Common.Operations;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Send;
public record GrasshopperSendInfo(
string AccountId,
Uri ServerUrl,
string? WorkspaceId,
string ProjectId,
string ModelId,
string SourceApplication
) : SendInfo(AccountId, ServerUrl, ProjectId, ModelId, SourceApplication);
@@ -0,0 +1,548 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Timers;
using Grasshopper.GUI;
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Attributes;
using GrasshopperAsyncComponent;
using Microsoft.Extensions.DependencyInjection;
using Rhino;
using Speckle.Connectors.Common.Analytics;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Connectors.GrasshopperShared.Registration;
using Speckle.Sdk;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Models.Extensions;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Send;
[Guid("52481972-7867-404F-8D9F-E1481183F355")]
public class SendAsyncComponent : GH_AsyncComponent
{
private ResourceCollection<Project>? LastFetchedProjects { get; set; }
private ResourceCollection<Model>? LastFetchedModels { get; set; }
public GhContextMenuButton ProjectContextMenuButton { get; set; }
public GhContextMenuButton ModelContextMenuButton { get; set; }
private ToolStripDropDown? ProjectDropDown { get; set; }
private ToolStripDropDown? ModelDropDown { get; set; }
public SendAsyncComponent()
: base(
"Publish",
"P",
"Publish a collection to Speckle",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OPERATIONS
)
{
BaseWorker = new SendComponentWorker(this);
Attributes = new SendAsyncComponentAttributes(this);
}
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_operations_publish;
public ComponentState CurrentComponentState { get; set; } = ComponentState.NeedsInput;
public bool AutoSend { get; set; }
public bool JustPastedIn { get; set; }
public double OverallProgress { get; set; }
public string? Url { get; set; }
public IClient ApiClient { get; set; }
public MixPanelManager MixPanelManager { get; set; }
public HostApp.SpeckleUrlModelResource? UrlModelResource { get; set; }
public SpeckleCollectionWrapperGoo? RootCollectionWrapper { get; set; }
public SpeckleUrlModelResource? OutputParam { get; set; }
public SendOperation<SpeckleCollectionWrapperGoo> SendOperation { get; private set; }
public static IServiceScope? Scope { get; set; }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam());
pManager.AddParameter(
new SpeckleCollectionParam(GH_ParamAccess.item),
"Collection",
"collection",
"The collection model object to send",
GH_ParamAccess.item
);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam());
}
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
static void Open(string url)
{
var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true };
Process.Start(psi);
}
base.AppendAdditionalMenuItems(menu);
Menu_AppendSeparator(menu);
var autoSendMi = Menu_AppendItem(
menu,
"Publish automatically",
(s, e) =>
{
AutoSend = !AutoSend;
RhinoApp.InvokeOnUiThread(
(Action)
delegate
{
OnDisplayExpired(true);
}
);
},
true,
AutoSend
);
autoSendMi.ToolTipText =
"Toggle automatic data publishing. If set, any change in any of the input parameters of this component will start publishing.\n Please be aware that if a new publish starts before an old one is finished, the previous operation is cancelled.";
if (Url != null)
{
Menu_AppendSeparator(menu);
Menu_AppendItem(menu, $"View created version online ↗", (s, e) => Open(Url));
}
Menu_AppendSeparator(menu);
if (CurrentComponentState == ComponentState.Sending)
{
Menu_AppendItem(
menu,
"Cancel Publish",
(s, e) =>
{
CurrentComponentState = ComponentState.Expired;
RequestCancellation();
}
);
}
}
protected override void SolveInstance(IGH_DataAccess da)
{
// Dependency Injection
Scope = PriorityLoader.Container.CreateScope();
SendOperation = Scope.ServiceProvider.GetRequiredService<SendOperation<SpeckleCollectionWrapperGoo>>();
MixPanelManager = Scope.ServiceProvider.GetRequiredService<MixPanelManager>();
var accountService = Scope.ServiceProvider.GetRequiredService<AccountService>();
var accountManager = Scope.ServiceProvider.GetRequiredService<AccountManager>();
var clientFactory = Scope.ServiceProvider.GetRequiredService<IClientFactory>();
// We need to call this always in here to be able to react and set events :/
ParseInput(da, accountService, accountManager, clientFactory);
if (
(AutoSend || CurrentComponentState == ComponentState.Ready || CurrentComponentState == ComponentState.Sending)
&& !JustPastedIn
)
{
CurrentComponentState = ComponentState.Sending;
// Delegate control to parent async component.
base.SolveInstance(da);
return;
}
if (JustPastedIn)
{
// Set output data in a "first run" event. Note: we are not persisting the actual "sent" object as it can be very big.
base.SolveInstance(da);
return;
}
else
{
da.SetData(0, OutputParam);
CurrentComponentState = ComponentState.Expired;
Message = "Expired";
OnDisplayExpired(true);
}
}
public override void RemovedFromDocument(GH_Document document)
{
RequestCancellation();
Scope?.Dispose();
base.RemovedFromDocument(document);
}
public override void DisplayProgress(object sender, ElapsedEventArgs e)
{
if (Workers.Count == 0)
{
return;
}
Message = "";
var total = 0.0;
foreach (var kvp in ProgressReports)
{
Message += $"{kvp.Key}: {kvp.Value}\n";
total += kvp.Value;
}
OverallProgress = total / ProgressReports.Keys.Count;
RhinoApp.InvokeOnUiThread(
(Action)
delegate
{
OnDisplayExpired(true);
}
);
}
public override void DocumentContextChanged(GH_Document document, GH_DocumentContext context)
{
switch (context)
{
case GH_DocumentContext.Loaded:
OnDisplayExpired(true);
break;
case GH_DocumentContext.Unloaded:
// Will execute every time a document becomes inactive (in background or closing file.)
//Correctly dispose of the client when changing documents to prevent subscription handlers being called in background.
RequestCancellation();
break;
}
base.DocumentContextChanged(document, context);
}
private void ParseInput(
IGH_DataAccess da,
AccountService accountService,
AccountManager accountManager,
IClientFactory clientFactory
)
{
HostApp.SpeckleUrlModelResource? dataInput = null;
da.GetData(0, ref dataInput);
if (dataInput is null)
{
UrlModelResource = null;
TriggerAutoSave();
return;
}
UrlModelResource = dataInput;
try
{
Account? account =
dataInput.AccountId != null
? accountManager.GetAccount(dataInput.AccountId)
: accountService.GetAccountWithServerUrlFallback("", new Uri(dataInput.Server)); // fallback the account that matches with URL if any
if (account is null)
{
throw new SpeckleAccountManagerException($"No default account was found");
}
ApiClient?.Dispose();
ApiClient = clientFactory.Create(account);
}
catch (Exception e) when (!e.IsFatal())
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, e.ToFormattedString());
}
SpeckleCollectionWrapperGoo rootCollectionWrapper = new();
da.GetData(1, ref rootCollectionWrapper);
if (rootCollectionWrapper is null)
{
RootCollectionWrapper = null;
TriggerAutoSave();
return;
}
RootCollectionWrapper = rootCollectionWrapper;
}
}
public class SendComponentWorker : WorkerInstance
{
public SendComponentWorker(GH_Component p)
: base(p) { }
private Stopwatch _stopwatch;
public SpeckleUrlModelResource? OutputParam { get; set; }
private List<(GH_RuntimeMessageLevel, string)> RuntimeMessages { get; } = new();
public override WorkerInstance Duplicate()
{
return new SendComponentWorker(Parent);
}
public override void GetData(IGH_DataAccess da, GH_ComponentParamServer p)
{
_stopwatch = new Stopwatch();
_stopwatch.Start();
}
public override void SetData(IGH_DataAccess da)
{
_stopwatch.Stop();
if (((SendAsyncComponent)Parent).JustPastedIn)
{
((SendAsyncComponent)Parent).JustPastedIn = false;
da.SetData(0, ((SendAsyncComponent)Parent).OutputParam);
return;
}
if (CancellationToken.IsCancellationRequested)
{
((SendAsyncComponent)Parent).CurrentComponentState = ComponentState.Expired;
return;
}
foreach (var (level, message) in RuntimeMessages)
{
Parent.AddRuntimeMessage(level, message);
}
da.SetData(0, OutputParam);
((SendAsyncComponent)Parent).CurrentComponentState = ComponentState.UpToDate;
((SendAsyncComponent)Parent).OutputParam = OutputParam; // ref the outputs in the parent too, so we can serialise them on write/read
((SendAsyncComponent)Parent).OverallProgress = 0;
var hasWarnings = RuntimeMessages.Count > 0;
if (!hasWarnings)
{
/* POC: cannot use GetTotalChildrenCount() on the root collection, because this contains subcollection wrappers which are not recognized by our typeloader. Will throw exception as result.
Parent.AddRuntimeMessage(
GH_RuntimeMessageLevel.Remark,
$"Successfully published {((SendAsyncComponent)Parent).RootCollectionWrapper?.Value.Collection.GetTotalChildrenCount()} objects to Speckle."
);
*/
Parent.AddRuntimeMessage(
GH_RuntimeMessageLevel.Remark,
$"Successfully published to Speckle. Right-click to view online."
);
Parent.AddRuntimeMessage(
GH_RuntimeMessageLevel.Remark,
$"Publish duration: {_stopwatch.ElapsedMilliseconds / 1000f}s"
);
}
}
public override void DoWork(Action<string, double> reportProgress, Action done)
{
var sendComponent = (SendAsyncComponent)Parent;
if (sendComponent.JustPastedIn)
{
done();
return;
}
if (CancellationToken.IsCancellationRequested)
{
sendComponent.CurrentComponentState = ComponentState.Expired;
return;
}
try
{
SpeckleUrlModelResource? urlModelResource = sendComponent.UrlModelResource;
if (urlModelResource is null)
{
throw new InvalidOperationException("Url Resource was null");
}
SpeckleCollectionWrapperGoo? rootCollectionWrapper = sendComponent.RootCollectionWrapper;
if (rootCollectionWrapper is null)
{
throw new InvalidOperationException("Root Collection was null");
}
var t = Task.Run(async () =>
{
if (CancellationToken.IsCancellationRequested)
{
sendComponent.CurrentComponentState = ComponentState.Expired;
return;
}
// Step 1 - SEND TO SERVER
var sendInfo = await urlModelResource
.GetSendInfo(sendComponent.ApiClient, CancellationToken)
.ConfigureAwait(false);
var progress = new Progress<CardProgress>(p =>
{
reportProgress(Id, p.Progress ?? 0);
//sendComponent.Message = $"{p.Status}";
});
SendOperationResult? result = await sendComponent
.SendOperation.Execute(
new List<SpeckleCollectionWrapperGoo>() { rootCollectionWrapper },
sendInfo,
progress,
CancellationToken
)
.ConfigureAwait(false);
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
var customProperties = new Dictionary<string, object>()
{
{ "isAsync", true },
{ "auto", sendComponent.AutoSend }
};
if (sendInfo.WorkspaceId != null)
{
customProperties.Add("workspace_id", sendInfo.WorkspaceId);
}
await sendComponent.MixPanelManager.TrackEvent(
MixPanelEvents.Send,
sendComponent.ApiClient.Account,
customProperties
);
SpeckleUrlModelVersionResource? createdVersion =
new(
sendInfo.AccountId,
sendInfo.ServerUrl.ToString(),
sendInfo.WorkspaceId,
sendInfo.ProjectId,
sendInfo.ModelId,
result.VersionId
);
OutputParam = createdVersion;
sendComponent.Url =
$"{createdVersion.Server}projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}@{result.VersionId}";
// DONE
done();
});
t.Wait();
}
catch (Exception ex) when (!ex.IsFatal())
{
RuntimeMessages.Add((GH_RuntimeMessageLevel.Error, ex.ToFormattedString()));
done();
}
}
}
public class SendAsyncComponentAttributes : GH_ComponentAttributes
{
private bool _selected;
public SendAsyncComponentAttributes(GH_Component owner)
: base(owner) { }
private Rectangle ButtonBounds { get; set; }
public override bool Selected
{
get => _selected;
set => _selected = value;
}
protected override void Layout()
{
base.Layout();
var baseRec = GH_Convert.ToRectangle(Bounds);
baseRec.Height += 26;
var btnRec = baseRec;
btnRec.Y = btnRec.Bottom - 26;
btnRec.Height = 26;
btnRec.Inflate(-2, -2);
Bounds = baseRec;
ButtonBounds = btnRec;
}
protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
{
base.Render(canvas, graphics, channel);
var state = ((SendAsyncComponent)Owner).CurrentComponentState;
if (channel == GH_CanvasChannel.Objects)
{
if (((SendAsyncComponent)Owner).AutoSend)
{
var autoSendButton = GH_Capsule.CreateTextCapsule(
ButtonBounds,
ButtonBounds,
GH_Palette.Blue,
"Auto Publish",
2,
0
);
autoSendButton.Render(graphics, Selected, Owner.Locked, false);
autoSendButton.Dispose();
}
else
{
var palette =
state == ComponentState.Expired || state == ComponentState.UpToDate
? GH_Palette.Black
: GH_Palette.Transparent;
var text = state == ComponentState.Sending ? "Publishing..." : "Publish";
var button = GH_Capsule.CreateTextCapsule(
ButtonBounds,
ButtonBounds,
palette,
text,
2,
state == ComponentState.Expired ? 10 : 0
);
button.Render(graphics, Selected, Owner.Locked, false);
button.Dispose();
}
}
}
public override GH_ObjectResponse RespondToMouseDown(GH_Canvas sender, GH_CanvasMouseEvent e)
{
if (e.Button == MouseButtons.Left)
{
if (((RectangleF)ButtonBounds).Contains(e.CanvasLocation))
{
if (((SendAsyncComponent)Owner).AutoSend)
{
((SendAsyncComponent)Owner).AutoSend = false;
Owner.OnDisplayExpired(true);
return GH_ObjectResponse.Handled;
}
if (((SendAsyncComponent)Owner).CurrentComponentState == ComponentState.Sending)
{
return GH_ObjectResponse.Handled;
}
((SendAsyncComponent)Owner).CurrentComponentState = ComponentState.Ready;
Owner.ExpireSolution(true);
return GH_ObjectResponse.Handled;
}
}
return base.RespondToMouseDown(sender, e);
}
}
@@ -0,0 +1,190 @@
using System.Diagnostics;
using Grasshopper.Kernel;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common.Analytics;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.GrasshopperShared.Components.BaseComponents;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Connectors.GrasshopperShared.Registration;
using Speckle.Sdk;
using Speckle.Sdk.Api;
using Speckle.Sdk.Common;
using Speckle.Sdk.Credentials;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Send;
public class SendComponentInput
{
public SpeckleUrlModelResource Resource { get; }
public SpeckleCollectionWrapperGoo Input { get; }
public bool Run { get; }
public SendComponentInput(SpeckleUrlModelResource resource, SpeckleCollectionWrapperGoo input, bool run)
{
Resource = resource;
Input = input;
Run = run;
}
}
public class SendComponentOutput(SpeckleUrlModelResource? resource)
{
public SpeckleUrlModelResource? Resource { get; } = resource;
}
public class SendComponent : SpeckleScopedTaskCapableComponent<SendComponentInput, SendComponentOutput>
{
private readonly MixPanelManager _mixpanel;
public SendComponent()
: base(
"(Sync) Publish",
"sP",
"Publish a collection to Speckle, synchronously",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.DEVELOPER
)
{
_mixpanel = PriorityLoader.Container.GetRequiredService<MixPanelManager>();
}
public override Guid ComponentGuid => new("0CF0D173-BDF0-4AC2-9157-02822B90E9FB");
public string? Url { get; private set; }
protected override Bitmap Icon => Resources.speckle_operations_syncpublish;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam());
pManager.AddParameter(
new SpeckleCollectionParam(GH_ParamAccess.item),
"Collection",
"collection",
"The model collection to publish",
GH_ParamAccess.item
);
pManager.AddBooleanParameter("Run", "r", "Run the publish operation", GH_ParamAccess.item);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam());
}
protected override SendComponentInput GetInput(IGH_DataAccess da)
{
if (da.Iteration != 0)
{
throw new SpeckleException("No more than 1 resource allowed");
}
SpeckleUrlModelResource? resource = null;
if (!da.GetData(0, ref resource))
{
throw new SpeckleException("Failed to get resource");
}
SpeckleCollectionWrapperGoo rootCollectionWrapper = new();
da.GetData(1, ref rootCollectionWrapper);
bool run = false;
da.GetData(2, ref run);
return new SendComponentInput(resource.NotNull(), rootCollectionWrapper, run);
}
protected override void SetOutput(IGH_DataAccess da, SendComponentOutput result)
{
if (result.Resource is null)
{
Message = "Not Published";
}
else
{
da.SetData(0, result.Resource);
Message = "Done";
}
}
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
base.AppendAdditionalMenuItems(menu);
Menu_AppendSeparator(menu);
if (Url != null)
{
Menu_AppendSeparator(menu);
Menu_AppendItem(menu, $"View created version online ↗", (s, e) => Open(Url));
}
static void Open(string url)
{
var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true };
Process.Start(psi);
}
}
protected override async Task<SendComponentOutput> PerformScopedTask(
SendComponentInput input,
IServiceScope scope,
CancellationToken cancellationToken = default
)
{
if (!input.Run)
{
return new(null);
}
var accountService = scope.ServiceProvider.GetRequiredService<AccountService>();
var accountManager = scope.ServiceProvider.GetRequiredService<AccountManager>();
var clientFactory = scope.ServiceProvider.GetRequiredService<IClientFactory>();
var sendOperation = scope.ServiceProvider.GetRequiredService<SendOperation<SpeckleCollectionWrapperGoo>>();
Account? account =
input.Resource.AccountId != null
? accountManager.GetAccount(input.Resource.AccountId)
: accountService.GetAccountWithServerUrlFallback("", new Uri(input.Resource.Server)); // fallback the account that matches with URL if any
if (account is null)
{
throw new SpeckleAccountManagerException($"No default account was found");
}
var progress = new Progress<CardProgress>(_ =>
{
// TODO: Progress only makes sense in non-blocking async receive, which is not supported yet.
// Message = $"{progress.Status}: {progress.Progress}";
});
using var client = clientFactory.Create(account);
var sendInfo = await input.Resource.GetSendInfo(client, cancellationToken).ConfigureAwait(false);
var result = await sendOperation
.Execute(new List<SpeckleCollectionWrapperGoo>() { input.Input }, sendInfo, progress, cancellationToken)
.ConfigureAwait(false);
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
var customProperties = new Dictionary<string, object>() { { "isAsync", false } };
if (sendInfo.WorkspaceId != null)
{
customProperties.Add("workspace_id", sendInfo.WorkspaceId);
}
await _mixpanel.TrackEvent(MixPanelEvents.Send, account, customProperties);
SpeckleUrlLatestModelVersionResource createdVersionResource =
new(
sendInfo.AccountId,
sendInfo.ServerUrl.ToString(),
sendInfo.WorkspaceId,
sendInfo.ProjectId,
sendInfo.ModelId
);
Url = $"{createdVersionResource.Server}projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}"; // TODO: missing "@VersionId"
return new SendComponentOutput(createdVersionResource);
}
}
@@ -0,0 +1,357 @@
using GH_IO.Serialization;
using Grasshopper.Kernel;
using Speckle.Connectors.GrasshopperShared.Components.Operations.Wizard;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk;
using Speckle.Sdk.Credentials;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations;
public class SpeckleSelectModelComponent : GH_Component
{
private bool _justPastedIn;
private string? _storedUserId;
private string? _storedServer;
private string? _storedWorkspaceId;
private string? _storedProjectId;
private string? _storedModelId;
private string? _storedVersionId;
public override Guid ComponentGuid => new("9638B3B5-C469-4570-B69F-686D8DA5C48D");
public GhContextMenuButton WorkspaceContextMenuButton { get; }
public GhContextMenuButton ProjectContextMenuButton { get; }
public GhContextMenuButton ModelContextMenuButton { get; }
public GhContextMenuButton VersionContextMenuButton { get; }
private SpeckleOperationWizard SpeckleOperationWizard { get; }
protected override Bitmap Icon => Resources.speckle_inputs_model;
public SpeckleSelectModelComponent()
: base(
"Speckle Model URL",
"URL",
"User selectable model from Speckle",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OPERATIONS
)
{
Attributes = new SpeckleSelectModelComponentAttributes(this);
SpeckleOperationWizard = new SpeckleOperationWizard(RefreshComponent, UpdateComponentMessage, false);
WorkspaceContextMenuButton = SpeckleOperationWizard.WorkspaceMenuHandler.WorkspaceContextMenuButton;
ProjectContextMenuButton = SpeckleOperationWizard.ProjectMenuHandler.ProjectContextMenuButton;
ModelContextMenuButton = SpeckleOperationWizard.ModelMenuHandler.ModelContextMenuButton;
VersionContextMenuButton = SpeckleOperationWizard!.VersionMenuHandler!.VersionContextMenuButton; // TODO: fix this shit later when we split
}
private Task RefreshComponent()
{
ExpireSolution(true);
return Task.CompletedTask;
}
private Task UpdateComponentMessage(string message)
{
Message = message;
return Task.CompletedTask;
}
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
var urlIndex = pManager.AddTextParameter("Speckle Url", "Url", "Speckle URL", GH_ParamAccess.item);
pManager[urlIndex].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam());
}
private string? UrlInput { get; set; }
#pragma warning disable CA1502
protected override void SolveInstance(IGH_DataAccess da)
#pragma warning restore CA1502
{
try
{
// Deal with inputs
string? urlInput = null;
// SCENARIO 1: Component has input wire connected
if (da.GetData(0, ref urlInput))
{
UrlInput = urlInput;
//Lock button interactions before anything else, to ensure any input (even invalid ones) lock the state.
SpeckleOperationWizard.SetComponentButtonsState(false);
if (urlInput == null || string.IsNullOrEmpty(urlInput))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Input url was empty or null");
return;
}
try
{
// NOTE: once we split the logic in Sender and Receiver components, we need to set flag correctly
var (resource, hasPermission) = SpeckleOperationWizard.SolveInstanceWithUrlInput(urlInput, true);
if (!hasPermission)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "You do not have enough permission for this project.");
}
_storedUserId = SpeckleOperationWizard.SelectedAccount?.id;
_storedServer = resource.Server;
da.SetData(0, resource);
}
catch (SpeckleException e)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, e.Message);
}
return; // Fast exit!
}
// SCENARIO 2: Component is running with no wires connected to input.
// Unlock button interactions when no input data is provided (no wires connected)
SpeckleOperationWizard.SetComponentButtonsState(true);
// When user unplugs the URL input, we need to reset all first
if (UrlInput != null)
{
UrlInput = null;
SpeckleOperationWizard.ResetHandlers();
_storedUserId = SpeckleOperationWizard.SelectedAccount?.id;
}
if (_justPastedIn && _storedUserId != null && !string.IsNullOrEmpty(_storedUserId))
{
try
{
SpeckleOperationWizard.SetAccountFromId(_storedUserId);
}
catch (SpeckleAccountManagerException e)
{
// Swallow and move onto checking server.
Console.WriteLine(e);
}
if (_storedServer != null && SpeckleOperationWizard.SelectedAccount == null)
{
SpeckleOperationWizard.SetAccountFromIdAndUrl(_storedUserId, _storedServer);
}
}
// Validate backing data
if (SpeckleOperationWizard.SelectedAccount == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Please select an account in the right click menu");
WorkspaceContextMenuButton.Enabled = false;
ProjectContextMenuButton.Enabled = false;
ModelContextMenuButton.Enabled = false;
VersionContextMenuButton.Enabled = false;
return;
}
// 1- Workspaces
if (_justPastedIn && !string.IsNullOrEmpty(_storedWorkspaceId))
{
SpeckleOperationWizard.SetWorkspaceFromSavedIdSync(_storedWorkspaceId!);
}
// NOTE FOR LATER: Need to be handled in SDK... and will come later by Jeddward Morgan...
if (SpeckleOperationWizard.WorkspaceMenuHandler.Workspaces == null)
{
var workspaces = SpeckleOperationWizard.FetchWorkspacesSync("");
if (workspaces.items.Count == 0)
{
// Create a workspace flow
SpeckleOperationWizard.CreateNewWorkspaceUIState();
return;
}
}
// Unfortunately need to handle personal projects as workspace item for a while
if (SpeckleOperationWizard.WorkspaceMenuHandler.IsPersonalProjects)
{
_storedWorkspaceId = null;
}
else
{
SpeckleOperationWizard.SetDefaultWorkspaceSync();
if (SpeckleOperationWizard.SelectedWorkspace != null)
{
_storedWorkspaceId = SpeckleOperationWizard.SelectedWorkspace.id;
}
else
{
return;
}
}
// 2- Projects
ProjectContextMenuButton.Enabled = true;
// Get projects for menu
SpeckleOperationWizard.FetchProjectsSync("");
// Stored project id scenario
if (_justPastedIn && !string.IsNullOrEmpty(_storedProjectId))
{
SpeckleOperationWizard.SetProjectFromSavedIdSync(_storedProjectId!);
}
// No selected project scenario
if (SpeckleOperationWizard.SelectedProject == null)
{
ModelContextMenuButton.Enabled = false;
VersionContextMenuButton.Enabled = false;
return;
}
// 3- Models
ModelContextMenuButton.Enabled = true;
// Get models for menu
SpeckleOperationWizard.FetchModelsSync("");
if (_justPastedIn && !string.IsNullOrEmpty(_storedModelId))
{
SpeckleOperationWizard.SetModelFromSavedIdSync(_storedModelId!);
}
// No selected model scenario
if (SpeckleOperationWizard.SelectedModel == null)
{
VersionContextMenuButton.Enabled = false;
return;
}
// 4- Models
VersionContextMenuButton.Enabled = true;
SpeckleOperationWizard.FetchVersionsSync(10);
if (_justPastedIn && !string.IsNullOrEmpty(_storedVersionId))
{
SpeckleOperationWizard.SetVersionFromSavedIdSync(_storedVersionId!);
}
if (SpeckleOperationWizard.SelectedVersion == null)
{
// If no version selected, output `latest` resource
da.SetData(
0,
new SpeckleUrlLatestModelVersionResource(
SpeckleOperationWizard.SelectedAccount.id,
SpeckleOperationWizard.SelectedAccount.serverInfo.url,
SpeckleOperationWizard.SelectedWorkspace?.id,
SpeckleOperationWizard.SelectedProject.id,
SpeckleOperationWizard.SelectedModel.id
)
);
return;
}
// If all data points are selected, output specific version.
da.SetData(
0,
new SpeckleUrlModelVersionResource(
SpeckleOperationWizard.SelectedAccount.id,
SpeckleOperationWizard.SelectedAccount.serverInfo.url,
SpeckleOperationWizard.SelectedWorkspace?.id,
SpeckleOperationWizard.SelectedProject.id,
SpeckleOperationWizard.SelectedModel.id,
SpeckleOperationWizard.SelectedVersion.id
)
);
}
catch (AggregateException e) when (!e.IsFatal())
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
string.Join("\n", e.InnerExceptions.Select(innerE => innerE.Message))
);
}
catch (Exception e) when (!e.IsFatal())
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, e.Message);
}
}
protected override void AfterSolveInstance()
{
// If the component runs once till the end, then it's no longer "just pasted in".
_justPastedIn = false;
base.AfterSolveInstance();
}
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
base.AppendAdditionalMenuItems(menu);
var accountsMenu = Menu_AppendItem(menu, "Account");
if (SpeckleOperationWizard.Accounts == null)
{
// TODO: we have to think about auth flow!
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Please add an account");
return;
}
foreach (var account in SpeckleOperationWizard.Accounts)
{
Menu_AppendItem(
accountsMenu.DropDown,
account.ToString(),
(_, _) => SpeckleOperationWizard.SetAccount(account),
null,
SpeckleOperationWizard.SelectedAccount?.id != account.id,
SpeckleOperationWizard.SelectedAccount?.id == account.id
);
}
}
public override bool Write(GH_IWriter writer)
{
var baseRes = base.Write(writer);
writer.SetString("Server", SpeckleOperationWizard.SelectedAccount?.serverInfo.url);
writer.SetString("User", SpeckleOperationWizard.SelectedAccount?.id);
writer.SetString("Workspace", SpeckleOperationWizard.SelectedWorkspace?.id);
writer.SetString("Project", SpeckleOperationWizard.SelectedProject?.id);
writer.SetString("Model", SpeckleOperationWizard.SelectedModel?.id);
writer.SetString("Version", SpeckleOperationWizard.SelectedVersion?.id);
return baseRes;
}
public override bool Read(GH_IReader reader)
{
var readRes = base.Read(reader);
reader.TryGetString("Server", ref _storedServer);
reader.TryGetString("User", ref _storedUserId);
reader.TryGetString("Workspace", ref _storedWorkspaceId);
reader.TryGetString("Project", ref _storedProjectId);
reader.TryGetString("Model", ref _storedModelId);
reader.TryGetString("Version", ref _storedVersionId);
_justPastedIn = true;
return readRes;
}
public override void ExpirePreview(bool redraw)
{
WorkspaceContextMenuButton.ExpirePreview(redraw);
ProjectContextMenuButton.ExpirePreview(redraw);
ModelContextMenuButton.ExpirePreview(redraw);
VersionContextMenuButton.ExpirePreview(redraw);
base.ExpirePreview(redraw);
}
}
@@ -0,0 +1,102 @@
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Attributes;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations;
public class SpeckleSelectModelComponentAttributes : GH_ComponentAttributes
{
private readonly SpeckleSelectModelComponent _typedOwner;
public SpeckleSelectModelComponentAttributes(IGH_Component component)
: base(component)
{
_typedOwner = (SpeckleSelectModelComponent)component;
}
public override void AppendToAttributeTree(List<IGH_Attributes> attributes)
{
base.AppendToAttributeTree(attributes);
_typedOwner.WorkspaceContextMenuButton.Attributes?.AppendToAttributeTree(attributes);
_typedOwner.ProjectContextMenuButton.Attributes?.AppendToAttributeTree(attributes);
_typedOwner.ModelContextMenuButton.Attributes?.AppendToAttributeTree(attributes);
_typedOwner.VersionContextMenuButton.Attributes?.AppendToAttributeTree(attributes);
}
private void InitialiseAttributes()
{
_typedOwner.WorkspaceContextMenuButton.Attributes ??= new GhContextMenuButtonAttributes(
_typedOwner.WorkspaceContextMenuButton
)
{
Parent = this,
};
_typedOwner.ProjectContextMenuButton.Attributes ??= new GhContextMenuButtonAttributes(
_typedOwner.ProjectContextMenuButton
)
{
Parent = this,
};
_typedOwner.ModelContextMenuButton.Attributes ??= new GhContextMenuButtonAttributes(
_typedOwner.ModelContextMenuButton
)
{
Parent = this,
Pivot = Pivot
};
_typedOwner.VersionContextMenuButton.Attributes ??= new GhContextMenuButtonAttributes(
_typedOwner.VersionContextMenuButton
)
{
Parent = this,
Pivot = Pivot
};
}
protected override void Layout()
{
base.Layout();
var baseRec = GH_Convert.ToRectangle(Bounds);
baseRec.Height += 26 * 4;
var btnRec = baseRec;
btnRec.Y = baseRec.Bottom - 26 * 4;
btnRec.Height = 26;
btnRec.Inflate(-2, -2);
var btnRec2 = btnRec;
btnRec2.Y = btnRec.Bottom + 2;
var btnRec3 = btnRec;
btnRec3.Y = btnRec2.Bottom + 2;
var btnRec4 = btnRec;
btnRec4.Y = btnRec3.Bottom + 2;
Bounds = baseRec;
InitialiseAttributes();
// Both pivot and bounds require updating to proper render buttons on location
_typedOwner.WorkspaceContextMenuButton.Attributes.Pivot = btnRec.Location;
_typedOwner.WorkspaceContextMenuButton.Attributes.Bounds = btnRec;
_typedOwner.ProjectContextMenuButton.Attributes.Pivot = btnRec2.Location;
_typedOwner.ProjectContextMenuButton.Attributes.Bounds = btnRec2;
_typedOwner.ModelContextMenuButton.Attributes.Pivot = btnRec3.Location;
_typedOwner.ModelContextMenuButton.Attributes.Bounds = btnRec3;
_typedOwner.VersionContextMenuButton.Attributes.Pivot = btnRec4.Location;
_typedOwner.VersionContextMenuButton.Attributes.Bounds = btnRec4;
}
protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
{
base.Render(canvas, graphics, channel);
// Draw custom buttons and dropdowns
_typedOwner.WorkspaceContextMenuButton.Attributes.RenderToCanvas(canvas, channel);
_typedOwner.ProjectContextMenuButton.Attributes.RenderToCanvas(canvas, channel);
_typedOwner.ModelContextMenuButton.Attributes.RenderToCanvas(canvas, channel);
_typedOwner.VersionContextMenuButton.Attributes.RenderToCanvas(canvas, channel);
}
}
@@ -0,0 +1,131 @@
using Speckle.Sdk.Api.GraphQL.Models;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Wizard;
public class ModelSelectedEventArgs(Model? model) : EventArgs
{
public Model? SelectedModel { get; } = model;
}
/// <summary>
/// Helper class to manage model filtering and selection for the components.
/// </summary>
public class ModelMenuHandler
{
private readonly Func<string, Task<ResourceCollection<Model>>> _fetchModels;
private ToolStripDropDown? _menu;
private SearchToolStripMenuItem? _searchItem;
private Model? SelectedModel { get; set; }
public ResourceCollection<Model>? Models { get; set; }
public event EventHandler<ModelSelectedEventArgs>? ModelSelected;
public GhContextMenuButton ModelContextMenuButton { get; }
public ModelMenuHandler(Func<string, Task<ResourceCollection<Model>>> fetchModels)
{
_fetchModels = fetchModels;
ModelContextMenuButton = new GhContextMenuButton(
"Select Model",
"Select Model",
"Right-click to select a model",
PopulateMenu
);
}
public void Reset()
{
_menu?.Items.Clear();
_menu?.Close();
SelectedModel = null;
Models = null;
RedrawMenuButton(null);
}
public void RedrawMenuButton(Model? model)
{
var suffix = ModelContextMenuButton.Enabled
? "Right-click to select another model."
: "Selection is disabled due to component input.";
if (model != null)
{
ModelContextMenuButton.Name = model.name;
ModelContextMenuButton.NickName = model.id;
ModelContextMenuButton.Description = $"{model.description ?? "No description"}\n\n{suffix}";
}
else
{
ModelContextMenuButton.Name = "Select Model";
ModelContextMenuButton.NickName = "Model";
ModelContextMenuButton.Description = "Right-click to select model";
}
}
private async Task Refetch(string searchText)
{
Models = await _fetchModels.Invoke(searchText);
// NOTE: We shouldn't call PopulateMenu here bc it will reset the search item when search is happening, it borks the state.
PopulateModelMenuItems(_menu!, _searchItem!);
}
private bool PopulateMenu(ToolStripDropDown menu)
{
_menu = menu;
_searchItem = new SearchToolStripMenuItem(menu, Refetch);
if (Models == null)
{
_searchItem.AddMenuItem("No models were fetched");
return true;
}
if (Models.items.Count == 0)
{
_searchItem.AddMenuItem("Project has no models");
return true;
}
PopulateModelMenuItems(_menu, _searchItem);
return true;
}
private void PopulateModelMenuItems(ToolStripDropDown menu, SearchToolStripMenuItem searchItem)
{
var lastIndex = menu.Items.Count - 1;
if (lastIndex >= 0)
{
// clean the existing items because we re-populate when user search
for (int i = lastIndex; i > 1; i--)
{
menu.Items.RemoveAt(i);
}
}
if (Models == null)
{
return;
}
foreach (var model in Models.items)
{
var desc = string.IsNullOrEmpty(model.description) ? "No description" : model.description;
searchItem?.AddMenuItem(
$"{model.name} - {desc}",
(_, _) => OnModelSelected(model),
SelectedModel?.id != model.id,
SelectedModel?.id == model.id
);
}
}
private void OnModelSelected(Model? model)
{
_menu?.Close();
SelectedModel = model;
RedrawMenuButton(model);
ModelSelected?.Invoke(this, new ModelSelectedEventArgs(model));
}
}
@@ -0,0 +1,132 @@
using Speckle.Sdk.Api.GraphQL.Models;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Wizard;
public class ProjectSelectedEventArgs(ProjectWithPermissions? project) : EventArgs
{
public ProjectWithPermissions? SelectedProject { get; } = project;
}
/// <summary>
/// Helper class to manage project filtering and selection for the components.
/// </summary>
public class ProjectMenuHandler
{
private readonly Func<string, Task<ResourceCollection<ProjectWithPermissions>>> _fetchProjects;
private ToolStripDropDown? _menu;
private SearchToolStripMenuItem? _searchItem;
private ProjectWithPermissions? SelectedProject { get; set; }
public ResourceCollection<ProjectWithPermissions>? Projects { get; set; }
public event EventHandler<ProjectSelectedEventArgs>? ProjectSelected;
public GhContextMenuButton ProjectContextMenuButton { get; }
public ProjectMenuHandler(Func<string, Task<ResourceCollection<ProjectWithPermissions>>> fetchProjects)
{
_fetchProjects = fetchProjects;
ProjectContextMenuButton = new GhContextMenuButton(
"Select Project",
"Select Project",
"Right-click to select project",
PopulateMenu
);
}
public void Reset()
{
_menu?.Items.Clear();
_menu?.Close();
SelectedProject = null;
Projects = null;
RedrawMenuButton(null);
}
public void RedrawMenuButton(Project? project)
{
var suffix = ProjectContextMenuButton.Enabled
? "Right-click to select another project."
: "Selection is disabled due to component input.";
if (project != null)
{
ProjectContextMenuButton.Name = project.name;
ProjectContextMenuButton.NickName = project.id;
ProjectContextMenuButton.Description = $"{project.description ?? "No description"}\n\n{suffix}";
}
else
{
ProjectContextMenuButton.Name = "Select Project";
ProjectContextMenuButton.NickName = "Project";
ProjectContextMenuButton.Description = "Right-click to select project";
}
}
private async Task Refetch(string searchText)
{
Projects = await _fetchProjects.Invoke(searchText);
// NOTE: We shouldn't call PopulateMenu here bc it will reset the search item when search is happening, it borks the state.
PopulateMenuItems(_menu!, _searchItem!);
}
private bool PopulateMenu(ToolStripDropDown menu)
{
_menu = menu;
_searchItem = new SearchToolStripMenuItem(menu, Refetch);
if (Projects == null)
{
_searchItem.AddMenuItem("No projects were fetched");
return true;
}
PopulateMenuItems(_menu, _searchItem);
return true;
}
private void PopulateMenuItems(ToolStripDropDown menu, SearchToolStripMenuItem searchItem)
{
// Clear previous
for (int i = menu.Items.Count - 1; i > 1; i--)
{
menu.Items.RemoveAt(i);
}
if (Projects == null)
{
return;
}
if (Projects.items.Count == 0 && !string.IsNullOrEmpty(searchItem.SearchText))
{
var noProjectsFoundButton = searchItem.AddMenuItem("No projects found.");
noProjectsFoundButton.BackColor = Color.MistyRose;
return;
}
foreach (var project in Projects.items)
{
var desc = string.IsNullOrEmpty(project.description) ? "No description" : project.description;
var projectItem = searchItem.AddMenuItem(
$"{project.name} - {desc}",
(_, _) => OnProjectSelected(project),
SelectedProject?.id != project.id,
SelectedProject?.id == project.id
);
if (!project.permissions.canLoad.authorized)
{
projectItem.Enabled = false;
projectItem.ToolTipText = @"You do not have permission to do operation on this project.";
}
}
}
private void OnProjectSelected(ProjectWithPermissions project)
{
_menu?.Close();
SelectedProject = project;
RedrawMenuButton(project);
ProjectSelected?.Invoke(this, new ProjectSelectedEventArgs(project));
}
}
@@ -0,0 +1,143 @@
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Wizard;
public class SearchToolStripMenuItem
{
private const string SEARCH_PLACEHOLDER_TEXT = "\uD83D\uDD0D Search...";
private readonly TimeSpan _debounce = TimeSpan.FromMilliseconds(500);
private DateTime _lastTime = DateTime.UtcNow;
private ToolStripControlHost SearchHost { get; set; }
private ToolStripDropDown ParentDropDown { get; set; }
private string SearchItemId { get; set; }
public string? SearchText { get; set; }
private readonly Func<string, Task> _onSearchTextChanged;
public SearchToolStripMenuItem(ToolStripDropDown parent, Func<string, Task> onSearchTextChanged)
{
ParentDropDown = parent;
ParentDropDown.Opacity = 0.95;
ParentDropDown.TopLevel = true;
ParentDropDown.AllowDrop = false;
_onSearchTextChanged = onSearchTextChanged;
AddSearchBox();
AddMenuSeparator();
RegisterEvents();
}
public ToolStripMenuItem AddMenuItem(
string text,
EventHandler? click = null,
bool? visible = null,
bool? isChecked = null,
Image? image = null
)
{
var item = new ToolStripMenuItem(text)
{
Checked = isChecked ?? false,
Image = image,
ImageScaling = ToolStripItemImageScaling.SizeToFit
};
item.Click += click;
if (visible == false)
{
item.Enabled = false;
}
ParentDropDown.Items.Add(item);
return item;
}
public void AddMenuSeparator() => ParentDropDown.Items.Add(new ToolStripSeparator());
private bool _suppressTextChanged;
private void AddSearchBox()
{
var textBox = new TextBox
{
BorderStyle = BorderStyle.None,
Width = 600,
Font = new Font("Segoe UI", 9),
TextAlign = HorizontalAlignment.Left,
Text = SEARCH_PLACEHOLDER_TEXT,
BackColor = Color.White,
};
textBox.Click += (_, __) =>
{
if (textBox.Text == SEARCH_PLACEHOLDER_TEXT)
{
_suppressTextChanged = true;
textBox.Text = "";
_suppressTextChanged = false;
}
};
textBox.TextChanged += async (s, e) =>
{
if (_suppressTextChanged)
{
return;
}
SearchText = textBox.Text;
var now = DateTime.UtcNow;
if (now - _lastTime >= _debounce)
{
await _onSearchTextChanged.Invoke(textBox.Text);
}
_lastTime = now;
};
textBox.KeyDown += (s, e) =>
{
if (e.KeyCode == Keys.Escape)
{
textBox.Text = SEARCH_PLACEHOLDER_TEXT;
_onSearchTextChanged.Invoke("");
e.Handled = true;
}
};
SearchHost = new ToolStripControlHost(textBox)
{
Name = SearchItemId,
AutoSize = false,
Size = new Size(170, 24),
Margin = new Padding(4),
Padding = new Padding(2)
};
ParentDropDown.Items.Insert(0, SearchHost);
textBox.Focus();
}
private void RegisterEvents()
{
// Resets the search filter
// ParentDropDown.Opening += async (sender, args) =>
// {
// await _onSearchTextChanged.Invoke("");
// };
ParentDropDown.ItemClicked += (sender, args) =>
{
// we are not closing the dropdown only if user clicked the first item of the dropdown which is TextBox that we use for search
if (args.ClickedItem.Name == SearchItemId)
{
return;
}
ParentDropDown.Close();
};
ParentDropDown.Closed += (sender, args) =>
{
SearchText = null;
};
}
}
@@ -0,0 +1,489 @@
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Registration;
using Speckle.Sdk;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Credentials;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Wizard;
/// <summary>
/// Wizard to handle cascading selections in an order Workspace, Project, Model and Version for operations.
/// Wraps the UI components with it and exposes the state of the selection to consumer.
/// </summary>
public class SpeckleOperationWizard
{
private readonly IClientFactory _clientFactory;
public Account? SelectedAccount { get; private set; }
public List<Account>? Accounts { get; }
public Workspace? SelectedWorkspace { get; private set; }
public Project? SelectedProject { get; private set; }
public Model? SelectedModel { get; private set; }
public Version? SelectedVersion { get; private set; }
public WorkspaceMenuHandler WorkspaceMenuHandler { get; }
public ProjectMenuHandler ProjectMenuHandler { get; }
public ModelMenuHandler ModelMenuHandler { get; }
public VersionMenuHandler? VersionMenuHandler { get; }
private readonly Func<Task> _refreshComponent;
private readonly Func<string, Task> _updateComponentMessage;
private readonly AccountService _accountService;
private readonly AccountManager _accountManager;
/// <param name="refreshComponent"> Callback function to trigger when component need to refresh itself.</param>
/// <param name="isSender"> Whether it will be used in sender or receiver. Accordingly, the wizard will manage versions or not.</param>
public SpeckleOperationWizard(Func<Task> refreshComponent, Func<string, Task> updateComponentMessage, bool isSender)
{
_refreshComponent = refreshComponent;
_updateComponentMessage = updateComponentMessage;
_clientFactory = PriorityLoader.Container.GetRequiredService<IClientFactory>();
_accountManager = PriorityLoader.Container.GetRequiredService<AccountManager>();
_accountService = PriorityLoader.Container.GetRequiredService<AccountService>();
var userSelectedAccountId = _accountService.GetUserSelectedAccountId();
Accounts = _accountManager.GetAccounts().ToList();
SelectedAccount = userSelectedAccountId == null ? null : _accountManager.GetAccount(userSelectedAccountId);
WorkspaceMenuHandler = new WorkspaceMenuHandler(FetchWorkspaces, CreateNewWorkspace);
ProjectMenuHandler = new ProjectMenuHandler(FetchProjects);
ModelMenuHandler = new ModelMenuHandler(FetchModels);
if (!isSender)
{
VersionMenuHandler = new VersionMenuHandler(FetchVersions);
VersionMenuHandler.VersionSelected += OnVersionSelected;
}
WorkspaceMenuHandler.WorkspaceSelected += OnWorkspaceSelected;
ProjectMenuHandler.ProjectSelected += OnProjectSelected;
ModelMenuHandler.ModelSelected += OnModelSelected;
}
public (SpeckleUrlModelResource resource, bool hasPermission) SolveInstanceWithUrlInput(string input, bool isSender)
{
// When input is provided, lock interaction of buttons so only text is shown (no context menu)
// Should perform validation, fill in all internal data of the component (project, model, version, account)
// Should notify user if any of this goes wrong.
var resources = SpeckleResourceBuilder.FromUrlString(input);
if (resources.Length == 0)
{
throw new SpeckleException($"Input url string was empty");
}
if (resources.Length > 1)
{
throw new SpeckleException($"Input multi-model url is not supported");
}
var resource = resources.First();
var account = _accountService.GetAccountWithServerUrlFallback(string.Empty, new Uri(resource.Server));
SetAccount(account, false);
if (SelectedAccount == null)
{
throw new SpeckleException("No account found for server URL");
}
IClient client = _clientFactory.Create(SelectedAccount);
var project = client.Project.Get(resource.ProjectId).Result;
var projectPermissions = client.Project.GetPermissions(resource.ProjectId).Result;
if (project != null && project.workspaceId != null)
{
var workspace = client.Workspace.Get(project.workspaceId).Result;
WorkspaceMenuHandler.RedrawMenuButton(workspace);
}
ProjectMenuHandler.RedrawMenuButton(project);
switch (resource)
{
case SpeckleUrlLatestModelVersionResource latestVersionResource:
var model = client.Model.Get(latestVersionResource.ModelId, latestVersionResource.ProjectId).Result;
ModelMenuHandler.RedrawMenuButton(model);
break;
case SpeckleUrlModelVersionResource versionResource:
var m = client.Model.Get(versionResource.ModelId, versionResource.ProjectId).Result;
ModelMenuHandler.RedrawMenuButton(m);
// TODO: this wont be the case when we have separation between send and receive components
var v = client.Version.Get(versionResource.VersionId, versionResource.ProjectId).Result;
VersionMenuHandler?.RedrawMenuButton(v);
break;
case SpeckleUrlModelObjectResource:
throw new SpeckleException("Object URLs are not supported");
default:
throw new SpeckleException("Unknown Speckle resource type");
}
return (resource, isSender ? projectPermissions.canPublish.authorized : projectPermissions.canLoad.authorized);
}
public void SetAccount(Account? account, bool refreshComponent = true)
{
_updateComponentMessage.Invoke("");
SelectedAccount = account;
ResetWorkspaces();
ResetProjects();
ResetModels();
ResetVersions();
if (refreshComponent)
{
_refreshComponent.Invoke();
}
if (account != null)
{
_accountService.SetUserSelectedAccountId(account.id);
}
}
public void SetAccountFromId(string accountId)
{
var account = _accountManager.GetAccount(accountId);
SetAccount(account, false);
}
#pragma warning disable CA1054
public void SetAccountFromIdAndUrl(string accountId, string uri)
#pragma warning restore CA1054
{
var account = _accountService.GetAccountWithServerUrlFallback(accountId, new Uri(uri));
SetAccount(account, false);
}
public void SetComponentButtonsState(bool enabled)
{
WorkspaceMenuHandler.WorkspaceContextMenuButton.Enabled = enabled;
ProjectMenuHandler.ProjectContextMenuButton.Enabled = enabled;
ModelMenuHandler.ModelContextMenuButton.Enabled = enabled;
if (VersionMenuHandler?.VersionContextMenuButton != null)
{
VersionMenuHandler.VersionContextMenuButton.Enabled = enabled;
}
}
public void CreateNewWorkspaceUIState()
{
WorkspaceMenuHandler.WorkspaceContextMenuButton.Enabled = true;
WorkspaceMenuHandler.WorkspaceContextMenuButton.Name = "Create New Workspace";
ProjectMenuHandler.ProjectContextMenuButton.Enabled = false;
ModelMenuHandler.ModelContextMenuButton.Enabled = false;
if (VersionMenuHandler?.VersionContextMenuButton != null)
{
VersionMenuHandler.VersionContextMenuButton.Enabled = false;
}
}
public void ResetHandlers()
{
WorkspaceMenuHandler.Reset();
ProjectMenuHandler.Reset();
ModelMenuHandler.Reset();
VersionMenuHandler?.Reset();
}
public void SetWorkspaceFromSavedIdSync(string workspaceId)
{
if (SelectedAccount == null)
{
return;
}
IClient client = _clientFactory.Create(SelectedAccount);
var workspace = client.Workspace.Get(workspaceId).Result;
SelectedWorkspace = workspace;
WorkspaceMenuHandler.RedrawMenuButton(SelectedWorkspace);
}
public void SetProjectFromSavedIdSync(string projectId)
{
if (SelectedAccount == null)
{
return;
}
IClient client = _clientFactory.Create(SelectedAccount);
var project = client.Project.Get(projectId).Result;
SelectedProject = project;
ProjectMenuHandler.RedrawMenuButton(SelectedProject);
}
public void SetModelFromSavedIdSync(string modelId)
{
if (SelectedAccount == null || SelectedProject == null)
{
return;
}
IClient client = _clientFactory.Create(SelectedAccount);
var model = client.Model.Get(modelId, SelectedProject.id).Result;
SelectedModel = model;
ModelMenuHandler.RedrawMenuButton(SelectedModel);
}
public void SetVersionFromSavedIdSync(string versionId)
{
if (SelectedAccount == null || SelectedProject == null || SelectedModel == null)
{
return;
}
IClient client = _clientFactory.Create(SelectedAccount);
var version = client.Version.Get(versionId, SelectedProject.id).Result;
SelectedVersion = version;
VersionMenuHandler?.RedrawMenuButton(SelectedVersion);
}
/// <summary>
/// Callback function to retrieve workspaces with the search text
/// </summary>
public async Task<ResourceCollection<Workspace>> FetchWorkspaces(string searchText)
{
if (SelectedAccount == null)
{
return new ResourceCollection<Workspace>();
}
IClient client = _clientFactory.Create(SelectedAccount);
var workspaces = await client.ActiveUser.GetWorkspaces(10, null, new UserWorkspacesFilter(searchText));
WorkspaceMenuHandler.Workspaces = workspaces;
return workspaces;
}
/// <summary>
/// Callback function to retrieve workspaces with the search text sync
/// </summary>
public ResourceCollection<Workspace> FetchWorkspacesSync(string searchText)
{
if (SelectedAccount == null)
{
return new ResourceCollection<Workspace>();
}
IClient client = _clientFactory.Create(SelectedAccount);
var workspaces = client.ActiveUser.GetWorkspaces(10, null, new UserWorkspacesFilter(searchText)).Result;
WorkspaceMenuHandler.Workspaces = workspaces;
return workspaces;
}
/// <summary>
/// Callback function to retrieve projects with the search text
/// </summary>
public async Task<ResourceCollection<ProjectWithPermissions>> FetchProjects(string searchText)
{
if (SelectedAccount == null)
{
return new ResourceCollection<ProjectWithPermissions>();
}
IClient client = _clientFactory.Create(SelectedAccount);
var workspaceId = SelectedWorkspace?.id ?? null;
var projects = await client.ActiveUser.GetProjectsWithPermissions(
10,
null,
new UserProjectsFilter(
searchText,
workspaceId: workspaceId,
includeImplicitAccess: true,
personalOnly: WorkspaceMenuHandler.IsPersonalProjects
)
);
ProjectMenuHandler.Projects = projects;
return projects;
}
/// <summary>
/// Callback function to retrieve projects with the search text sync
/// </summary>
public ResourceCollection<ProjectWithPermissions> FetchProjectsSync(string searchText)
{
if (SelectedAccount == null)
{
return new ResourceCollection<ProjectWithPermissions>();
}
IClient client = _clientFactory.Create(SelectedAccount);
var workspaceId = SelectedWorkspace?.id ?? null;
var projects = client
.ActiveUser.GetProjectsWithPermissions(
10,
null,
new UserProjectsFilter(
searchText,
workspaceId: workspaceId,
includeImplicitAccess: true,
personalOnly: WorkspaceMenuHandler.IsPersonalProjects
)
)
.Result;
ProjectMenuHandler.Projects = projects;
return projects;
}
/// <summary>
/// Callback function to retrieve models with the search text
/// </summary>
public async Task<ResourceCollection<Model>> FetchModels(string searchText)
{
if (SelectedAccount == null || SelectedProject == null)
{
return new ResourceCollection<Model>();
}
IClient client = _clientFactory.Create(SelectedAccount);
var projectWithModels = await client
.Project.GetWithModels(SelectedProject.id, 10, modelsFilter: new ProjectModelsFilter(search: searchText))
.ConfigureAwait(true);
ModelMenuHandler.Models = projectWithModels.models;
return projectWithModels.models;
}
/// <summary>
/// Callback function to retrieve models with the search text sync
/// </summary>
public ResourceCollection<Model> FetchModelsSync(string searchText)
{
if (SelectedAccount == null || SelectedProject == null)
{
return new ResourceCollection<Model>();
}
IClient client = _clientFactory.Create(SelectedAccount);
var projectWithModels = client
.Project.GetWithModels(SelectedProject.id, 10, modelsFilter: new ProjectModelsFilter(search: searchText))
.Result;
ModelMenuHandler.Models = projectWithModels.models;
return projectWithModels.models;
}
/// <summary>
/// Callback function to retrieve amount of versions
/// </summary>
public async Task<ResourceCollection<Version>> FetchVersions(int versionCount)
{
if (SelectedAccount == null || SelectedProject == null || SelectedModel == null)
{
return new ResourceCollection<Version>();
}
IClient client = _clientFactory.Create(SelectedAccount);
var newVersionsResult = await client
.Model.GetWithVersions(SelectedModel.id, SelectedProject.id, versionCount)
.ConfigureAwait(true);
if (VersionMenuHandler != null)
{
VersionMenuHandler.Versions = newVersionsResult.versions;
}
return newVersionsResult.versions;
}
/// <summary>
/// Callback function to retrieve amount of versions
/// </summary>
public ResourceCollection<Version> FetchVersionsSync(int versionCount)
{
if (SelectedAccount == null || SelectedProject == null || SelectedModel == null)
{
return new ResourceCollection<Version>();
}
IClient client = _clientFactory.Create(SelectedAccount);
var newVersionsResult = client.Model.GetWithVersions(SelectedModel.id, SelectedProject.id, versionCount).Result;
if (VersionMenuHandler != null)
{
VersionMenuHandler.Versions = newVersionsResult.versions;
}
return newVersionsResult.versions;
}
public void SetDefaultWorkspaceSync()
{
if (SelectedAccount == null)
{
return;
}
IClient client = _clientFactory.Create(SelectedAccount);
var activeWorkspace = client.ActiveUser.GetActiveWorkspace().Result;
Workspace? selectedWorkspace =
SelectedWorkspace
?? activeWorkspace
?? (WorkspaceMenuHandler.Workspaces?.items.Count > 0 ? WorkspaceMenuHandler.Workspaces?.items[0] : null);
SelectedWorkspace = selectedWorkspace;
WorkspaceMenuHandler.RedrawMenuButton(SelectedWorkspace);
}
private void OnWorkspaceSelected(object sender, WorkspaceSelectedEventArgs e)
{
SelectedWorkspace = e.SelectedWorkspace;
ResetProjects();
_refreshComponent.Invoke();
}
private void OnProjectSelected(object sender, ProjectSelectedEventArgs e)
{
SelectedProject = e.SelectedProject;
ResetModels();
_refreshComponent.Invoke();
}
private void OnModelSelected(object sender, ModelSelectedEventArgs e)
{
SelectedModel = e.SelectedModel;
ResetVersions();
_refreshComponent.Invoke();
}
private void OnVersionSelected(object sender, VersionSelectedEventArgs e)
{
SelectedVersion = e.SelectedVersion;
_refreshComponent.Invoke();
}
private void ResetWorkspaces()
{
SelectedWorkspace = null;
WorkspaceMenuHandler.Reset();
ResetProjects();
}
private void ResetProjects()
{
SelectedProject = null;
ProjectMenuHandler.Reset();
ResetModels();
}
private void ResetModels()
{
SelectedModel = null;
ModelMenuHandler.Reset();
ResetVersions();
}
private void ResetVersions()
{
SelectedVersion = null;
VersionMenuHandler?.Reset();
}
private Task CreateNewWorkspace()
{
Process.Start(
new ProcessStartInfo
{
FileName = SelectedAccount?.serverInfo.url + "/workspaces/actions/create",
UseShellExecute = true
}
);
return Task.CompletedTask;
}
}
@@ -0,0 +1,177 @@
using Speckle.Sdk.Api.GraphQL.Models;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Wizard;
public class VersionSelectedEventArgs(Version? version) : EventArgs
{
public Version? SelectedVersion { get; } = version;
}
public class VersionMenuHandler
{
private int FetchedVersionCount { get; set; } = 10;
private readonly Func<int, Task<ResourceCollection<Version>>> _fetchVersions;
private ToolStripDropDown? _menu;
private Version? SelectedVersion { get; set; }
public ResourceCollection<Version>? Versions { get; set; }
public event EventHandler<VersionSelectedEventArgs>? VersionSelected;
public GhContextMenuButton VersionContextMenuButton { get; }
public VersionMenuHandler(Func<int, Task<ResourceCollection<Version>>> fetchVersions)
{
_fetchVersions = fetchVersions;
VersionContextMenuButton = new GhContextMenuButton(
"Select Version",
"Select Project",
"Right-click to select a version",
PopulateMenu
);
}
public void Reset()
{
_menu?.Items.Clear();
_menu?.Close();
SelectedVersion = null;
Versions = null;
FetchedVersionCount = 10;
RedrawMenuButton(null);
}
private async Task Refetch(int versionCount)
{
Versions = await _fetchVersions.Invoke(versionCount);
PopulateVersionMenuItems(_menu!);
}
private bool PopulateMenu(ToolStripDropDown menu)
{
_menu = menu;
if (Versions is null)
{
AddMenuItem("No versions were fetched");
return true;
}
if (Versions.items.Count == 0)
{
AddMenuItem("Model has no versions");
return true;
}
PopulateVersionMenuItems(menu);
return true;
}
private void PopulateVersionMenuItems(ToolStripDropDown menu)
{
menu.Items.Clear();
if (Versions == null)
{
return;
}
AddMenuItem("Latest Version", (_, _) => OnVersionSelected(null), true, SelectedVersion == null);
AddMenuSeparator();
foreach (var version in Versions.items)
{
var desc = string.IsNullOrEmpty(version.message) ? "No description" : version.message;
var versionItem = AddMenuItem(
$"{version.id} - {desc}",
(_, _) => OnVersionSelected(version),
true,
SelectedVersion?.id == version.id
);
if (version.referencedObject is null)
{
versionItem.Enabled = false;
versionItem.ToolTipText = @"Upgrade to load older versions";
}
}
if (Versions.items.Count >= FetchedVersionCount)
{
AddMenuSeparator();
var addMoreButton = new Button() { Text = @" Show more...", Size = new Size(200, 32) };
addMoreButton.Click += async (_, _) =>
{
FetchedVersionCount += 10;
await Refetch(FetchedVersionCount);
};
var addMoreButtonHost = new ToolStripControlHost(addMoreButton)
{
Name = "Show more...",
AutoSize = false,
Margin = new Padding(4),
Padding = new Padding(2)
};
menu.Items.Insert(menu.Items.Count, addMoreButtonHost);
}
}
public void RedrawMenuButton(Version? version)
{
var suffix = VersionContextMenuButton.Enabled
? "Right-click to select another version."
: "Selection is disabled due to component input.";
if (version != null)
{
VersionContextMenuButton.Name = version.id;
VersionContextMenuButton.NickName = version.id;
VersionContextMenuButton.Description = $"{version.message ?? "No message"}\n\n{suffix}";
}
// else if (_model != null)
// {
// VersionContextMenuButton.NickName = "Latest Version";
// VersionContextMenuButton.Name = "Latest Version";
// VersionContextMenuButton.Description = "Gets the latest version from the selected model";
// }
else
{
VersionContextMenuButton.Name = "Select Version";
VersionContextMenuButton.NickName = "Version";
VersionContextMenuButton.Description = "Right-click to select version";
}
}
private void OnVersionSelected(Version? version)
{
_menu?.Close();
SelectedVersion = version;
RedrawMenuButton(SelectedVersion);
VersionSelected?.Invoke(this, new VersionSelectedEventArgs(version));
}
private ToolStripMenuItem AddMenuItem(
string text,
EventHandler? click = null,
bool? visible = null,
bool? isChecked = null
)
{
var item = new ToolStripMenuItem(text) { Checked = isChecked ?? false };
item.Click += click;
if (visible == false)
{
item.Visible = false;
}
_menu?.Items.Add(item);
return item;
}
private void AddMenuSeparator() => _menu?.Items.Add(new ToolStripSeparator());
}
@@ -0,0 +1,186 @@
using System.Drawing.Drawing2D;
using Speckle.Sdk.Api.GraphQL.Models;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Wizard;
public class WorkspaceSelectedEventArgs(Workspace? model) : EventArgs
{
public Workspace? SelectedWorkspace { get; } = model;
}
public class WorkspaceMenuHandler
{
private readonly Func<string, Task<ResourceCollection<Workspace>>> _fetchWorkspaces;
private ToolStripDropDown? _menu;
public bool IsPersonalProjects { get; set; }
private SearchToolStripMenuItem? _searchItem;
private readonly Func<Task> _createWorkspace;
private Workspace? SelectedWorkspace { get; set; }
public ResourceCollection<Workspace>? Workspaces { get; set; }
public Bitmap? Logo { get; private set; }
public event EventHandler<WorkspaceSelectedEventArgs>? WorkspaceSelected;
public GhContextMenuButton WorkspaceContextMenuButton { get; }
public WorkspaceMenuHandler(
Func<string, Task<ResourceCollection<Workspace>>> fetchWorkspaces,
Func<Task> createWorkspace
)
{
_fetchWorkspaces = fetchWorkspaces;
_createWorkspace = createWorkspace;
WorkspaceContextMenuButton = new GhContextMenuButton(
"Select Workspace",
"Select Workspace",
"Right-click to select a workspace",
PopulateMenu
);
}
public void Reset()
{
_menu?.Items.Clear();
_menu?.Close();
Workspaces = null;
SelectedWorkspace = null;
RedrawMenuButton(null);
}
private async Task Refetch(string searchText)
{
Workspaces = await _fetchWorkspaces.Invoke(searchText);
// NOTE: We shouldn't call PopulateMenu here bc it will reset the search item when search is happening, it borks the state.
PopulateModelMenuItems(_menu!, _searchItem!);
}
private bool PopulateMenu(ToolStripDropDown menu)
{
_menu = menu;
_searchItem = new SearchToolStripMenuItem(menu, Refetch);
if (Workspaces == null || Workspaces.items.Count == 0)
{
menu.Items.Clear();
_searchItem.AddMenuItem("Create a new workspace", (_, _) => _createWorkspace.Invoke());
return true;
}
PopulateModelMenuItems(_menu, _searchItem);
return true;
}
private void PopulateModelMenuItems(ToolStripDropDown menu, SearchToolStripMenuItem searchItem)
{
for (int i = menu.Items.Count - 1; i > 1; i--)
{
menu.Items.RemoveAt(i);
}
if (Workspaces == null)
{
return;
}
foreach (var workspace in Workspaces.items)
{
searchItem?.AddMenuItem(
$"{workspace.name}",
(_, _) => OnWorkspaceSelected(workspace),
SelectedWorkspace?.id != workspace.id,
SelectedWorkspace?.id == workspace.id,
Base64ToImage(workspace.logo)
);
}
searchItem?.AddMenuItem(
"Personal Projects",
(_, _) => OnWorkspaceSelected(null),
!IsPersonalProjects,
IsPersonalProjects
);
}
private void OnWorkspaceSelected(Workspace? workspace)
{
IsPersonalProjects = workspace == null;
_menu?.Close();
SelectedWorkspace = workspace;
RedrawMenuButton(workspace);
WorkspaceSelected?.Invoke(this, new WorkspaceSelectedEventArgs(workspace));
}
public void RedrawMenuButton(Workspace? workspace)
{
var suffix = WorkspaceContextMenuButton.Enabled
? "Right-click to select another workspace."
: "Selection is disabled due to component input.";
if (workspace != null && !IsPersonalProjects)
{
Logo = Get24X24IconFromBase64(workspace.logo);
WorkspaceContextMenuButton.SetIconOverride(Logo);
WorkspaceContextMenuButton.Name = workspace.name;
WorkspaceContextMenuButton.NickName = workspace.id;
WorkspaceContextMenuButton.Description = $"{workspace.description ?? "No description"}\n\n{suffix}";
}
else if (IsPersonalProjects)
{
WorkspaceContextMenuButton.SetIconOverride(null);
WorkspaceContextMenuButton.Name = "Personal Projects";
WorkspaceContextMenuButton.NickName = "Personal Projects";
WorkspaceContextMenuButton.Description = "Legacy";
}
else
{
WorkspaceContextMenuButton.SetIconOverride(null);
WorkspaceContextMenuButton.Name = "Select Workspace";
WorkspaceContextMenuButton.NickName = "Workspace";
WorkspaceContextMenuButton.Description = "Right-click to select workspace";
}
}
private Image? Base64ToImage(string? base64)
{
if (base64 == null)
{
return null;
}
var base64Data = base64[(base64.IndexOf(',') + 1)..]; // remove data:image/...;base64, part
byte[] bytes = Convert.FromBase64String(base64Data);
using var ms = new MemoryStream(bytes);
return Image.FromStream(ms);
}
private Bitmap? Get24X24IconFromBase64(string? base64)
{
if (base64 == null)
{
return null;
}
// Strip metadata prefix
var base64Data = base64[(base64.IndexOf(',') + 1)..];
byte[] bytes = Convert.FromBase64String(base64Data);
using (var ms = new MemoryStream(bytes))
{
using (var originalImage = Image.FromStream(ms))
{
return ResizeImageToBitmap(originalImage, 24, 24);
}
}
}
private Bitmap ResizeImageToBitmap(Image image, int width, int height)
{
var bmp = new Bitmap(width, height);
using (var g = Graphics.FromImage(bmp))
{
g.CompositingQuality = CompositingQuality.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.DrawImage(image, 0, 0, width, height);
}
return bmp;
}
}
@@ -0,0 +1,113 @@
using System.Drawing.Drawing2D;
namespace Speckle.Connectors.GrasshopperShared.HostApp;
public static class BitmapBuilder
{
public static Bitmap CreateSquareIconBitmap(string text, int width = 24, int height = 24)
{
Bitmap bitmap = new(width, height);
using Graphics graphics = Graphics.FromImage(bitmap);
// Enable high-quality rendering
graphics.SmoothingMode = SmoothingMode.AntiAlias;
// Set background to transparent
graphics.Clear(Color.Transparent);
// Rectangle with a 1px offset
Rectangle squareRect = new(1, 1, width - 2, height - 2);
using (Brush blueBrush = new SolidBrush(Color.Blue))
{
graphics.FillRectangle(blueBrush, squareRect);
}
// Draw white letters in the center
using (Font font = new("Arial", 8, FontStyle.Bold, GraphicsUnit.Pixel))
using (Brush whiteBrush = new SolidBrush(Color.White))
{
StringFormat format = new() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
graphics.DrawString(text, font, whiteBrush, new RectangleF(1, 1, width - 2, height - 2), format);
}
return bitmap;
}
public static Bitmap CreateCircleIconBitmap(string text, int width = 24, int height = 24)
{
Bitmap bitmap = new(width, height);
using Graphics graphics = Graphics.FromImage(bitmap);
// Enable high-quality rendering
graphics.SmoothingMode = SmoothingMode.AntiAlias;
// Set background to transparent
graphics.Clear(Color.Transparent);
// Rectangle with a 1px offset
Rectangle squareRect = new(1, 1, width - 2, height - 2);
using (Brush blueBrush = new SolidBrush(Color.Blue))
{
graphics.FillEllipse(blueBrush, squareRect);
}
// Draw white letters in the center
using (Font font = new("Arial", 8, FontStyle.Bold, GraphicsUnit.Pixel))
using (Brush whiteBrush = new SolidBrush(Color.White))
{
StringFormat format = new() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
graphics.DrawString(text, font, whiteBrush, new RectangleF(1, 1, width - 2, height - 2), format);
}
return bitmap;
}
public static Bitmap CreateHexagonalBitmap(string text, int width = 24, int height = 24)
{
Bitmap bitmap = new(width, height);
using Graphics graphics = Graphics.FromImage(bitmap);
// Enable high-quality rendering
graphics.SmoothingMode = SmoothingMode.AntiAlias;
// Set background to transparent
graphics.Clear(Color.Transparent);
// Calculate hexagon points centered within the bitmap
float side = (width - 2) / 2.236f; // 2.236f approximates 4 / √3 for regular hex dimensions
float h = side * (float)Math.Sqrt(3) / 2;
float centerX = width / 2f;
float centerY = height / 2f;
Point[] hexagonPoints =
[
new((int)(centerX - side / 2), (int)(centerY - h)),
new((int)(centerX + side / 2), (int)(centerY - h)),
new((int)(centerX + side), (int)centerY),
new((int)(centerX + side / 2), (int)(centerY + h)),
new((int)(centerX - side / 2), (int)(centerY + h)),
new((int)(centerX - side), (int)centerY)
];
using (Brush blueBrush = new SolidBrush(Color.Blue))
{
graphics.FillPolygon(blueBrush, hexagonPoints);
}
// Draw white letters in the center
using Font font = new("Monospace", 10, FontStyle.Bold, GraphicsUnit.Pixel);
using Brush whiteBrush = new SolidBrush(Color.White);
StringFormat format = new() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
graphics.DrawString(text, font, whiteBrush, new RectangleF(0, 1, width, height), format);
return bitmap;
}
}
@@ -0,0 +1,136 @@
using System.Windows.Threading;
namespace Speckle.Connectors.GrasshopperShared.HostApp.Extras;
/// <summary>
/// Provides Debounce() and Throttle() methods.
/// Use these methods to ensure that events aren't handled too frequently.
///
/// Throttle() ensures that events are throttled by the interval specified.
/// Only the last event in the interval sequence of events fires.
///
/// Debounce() fires an event only after the specified interval has passed
/// in which no other pending event has fired. Only the last event in the
/// sequence is fired.
/// </summary>
public class DebounceDispatcher
{
private DispatcherTimer? _timer;
private DateTime TimerStarted { get; set; } = DateTime.UtcNow.AddYears(-1);
/// <summary>
/// Debounce an event by resetting the event timeout every time the event is
/// fired. The behavior is that the Action passed is fired only after events
/// stop firing for the given timeout period.
///
/// Use Debounce when you want events to fire only after events stop firing
/// after the given interval timeout period.
///
/// Wrap the logic you would normally use in your event code into
/// the Action you pass to this method to debounce the event.
/// Example: https://gist.github.com/RickStrahl/0519b678f3294e27891f4d4f0608519a
/// </summary>
/// <param name="interval">Timeout in Milliseconds</param>
/// <param name="action">Action<object> to fire when debounced event fires</object></param>
/// <param name="param">optional parameter</param>
/// <param name="priority">optional priorty for the dispatcher</param>
/// <param name="disp">optional dispatcher. If not passed or null CurrentDispatcher is used.</param>
public void Debounce(
int interval,
Action<object?> action,
object? param = null,
DispatcherPriority priority = DispatcherPriority.ApplicationIdle,
Dispatcher? disp = null
)
{
// kill pending timer and pending ticks
_timer?.Stop();
_timer = null;
if (disp == null)
{
disp = Dispatcher.CurrentDispatcher;
}
// timer is recreated for each event and effectively
// resets the timeout. Action only fires after timeout has fully
// elapsed without other events firing in between
_timer = new DispatcherTimer(
TimeSpan.FromMilliseconds(interval),
priority,
(s, e) =>
{
if (_timer == null)
{
return;
}
_timer?.Stop();
_timer = null;
action.Invoke(param);
},
disp
);
_timer.Start();
}
/// <summary>
/// This method throttles events by allowing only 1 event to fire for the given
/// timeout period. Only the last event fired is handled - all others are ignored.
/// Throttle will fire events every timeout ms even if additional events are pending.
///
/// Use Throttle where you need to ensure that events fire at given intervals.
/// </summary>
/// <param name="interval">Timeout in Milliseconds</param>
/// <param name="action">Action<object> to fire when debounced event fires</object></param>
/// <param name="param">optional parameter</param>
/// <param name="priority">optional priorty for the dispatcher</param>
/// <param name="disp">optional dispatcher. If not passed or null CurrentDispatcher is used.</param>
public void Throttle(
int interval,
Action<object?> action,
object? param = null,
DispatcherPriority priority = DispatcherPriority.ApplicationIdle,
Dispatcher? disp = null
)
{
// kill pending timer and pending ticks
_timer?.Stop();
_timer = null;
if (disp == null)
{
disp = Dispatcher.CurrentDispatcher;
}
var curTime = DateTime.UtcNow;
// if timeout is not up yet - adjust timeout to fire
// with potentially new Action parameters
if (curTime.Subtract(TimerStarted).TotalMilliseconds < interval)
{
interval -= (int)curTime.Subtract(TimerStarted).TotalMilliseconds;
}
_timer = new DispatcherTimer(
TimeSpan.FromMilliseconds(interval),
priority,
(s, e) =>
{
if (_timer == null)
{
return;
}
_timer?.Stop();
_timer = null;
action.Invoke(param);
},
disp
);
_timer.Start();
TimerStarted = curTime;
}
}
@@ -0,0 +1,10 @@
namespace Speckle.Connectors.GrasshopperShared.HostApp;
public static class Constants
{
public const string LAYER_PATH_DELIMITER = "::";
public const string PROPERTY_PATH_DELIMITER = ".";
public const string TOPOLOGY_PROP = "topology";
public const string NAME_PROP = "name";
public const string PROPERTIES_PROP = "properties";
}
@@ -0,0 +1,183 @@
using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.DoubleNumerics;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Common.Exceptions;
namespace Speckle.Connectors.GrasshopperShared.HostApp;
public static class GrasshopperHelpers
{
public static string ToSpeckleString(this UnitSystem unitSystem)
{
switch (unitSystem)
{
case UnitSystem.None:
return Units.Meters;
case UnitSystem.Millimeters:
return Units.Millimeters;
case UnitSystem.Centimeters:
return Units.Centimeters;
case UnitSystem.Meters:
return Units.Meters;
case UnitSystem.Kilometers:
return Units.Kilometers;
case UnitSystem.Inches:
return Units.Inches;
case UnitSystem.Feet:
return Units.Feet;
case UnitSystem.Yards:
return Units.Yards;
case UnitSystem.Miles:
return Units.Miles;
case UnitSystem.Unset:
return Units.Meters;
default:
throw new UnitNotSupportedException($"The Unit System \"{unitSystem}\" is unsupported.");
}
}
/// <summary>
/// Retrieves a unique Speckle application id from the rgb value.
/// </summary>
/// <remarks> Uses the rgb value since color names are not unique </remarks>
public static string GetSpeckleApplicationId(this Color color) => $"color_{color}";
/// <summary>
/// Creates a unique Speckle application id from the display material properties.
/// </summary>
/// <param name="mat"></param>
/// <returns></returns>
public static string GetSpeckleApplicationId(this Rhino.Display.DisplayMaterial mat) =>
$"material_{mat.Transparency}_{mat.Diffuse}_{mat.Emission}_{mat.Shine}_{mat.Specular}";
public static string GetSpeckleApplicationId(this SpeckleMaterialWrapper matWrapper) =>
$"material_{matWrapper.Material.opacity}_{matWrapper.Material.diffuse}_{matWrapper.Material.emissive}_{matWrapper.Material.metalness}_{matWrapper.Material.roughness}";
/// <summary>
/// Retrieves a unique Speckle application id from the path of the collection
/// </summary>
/// <param name="collectionWrapper"></param>
/// <returns></returns>
public static string GetSpeckleApplicationId(this SpeckleCollectionWrapper collectionWrapper) =>
$"{string.Join(Constants.LAYER_PATH_DELIMITER, collectionWrapper.Path)}";
public static Transform MatrixToTransform(Matrix4x4 matrix, string units)
{
var currentDoc = RhinoDoc.ActiveDoc; // POC: too much right now to interface around
var conversionFactor = Units.GetConversionFactor(units, currentDoc.ModelUnitSystem.ToSpeckleString());
var t = Transform.Identity;
t.M00 = matrix.M11;
t.M01 = matrix.M12;
t.M02 = matrix.M13;
t.M03 = matrix.M14 * conversionFactor;
t.M10 = matrix.M21;
t.M11 = matrix.M22;
t.M12 = matrix.M23;
t.M13 = matrix.M24 * conversionFactor;
t.M20 = matrix.M31;
t.M21 = matrix.M32;
t.M22 = matrix.M33;
t.M23 = matrix.M34 * conversionFactor;
t.M30 = matrix.M41;
t.M31 = matrix.M42;
t.M32 = matrix.M43;
t.M33 = matrix.M44;
return t;
}
/// <summary>
/// Attempts to cast the goo to a geometry base object.
/// </summary>
/// <param name="geoGeo"></param>
/// <returns></returns>
/// <exception cref="SpeckleException">If it fails to cast</exception>
public static GeometryBase GeometricGooToGeometryBase(this IGH_GeometricGoo geoGeo)
{
var value = geoGeo.GetType().GetProperty("Value")?.GetValue(geoGeo);
switch (value)
{
case GeometryBase gb:
return gb;
case Point3d pt:
return new Rhino.Geometry.Point(pt);
case Line ln:
return new LineCurve(ln);
case Rectangle3d rec:
return rec.ToNurbsCurve();
case Circle c:
return new ArcCurve(c);
case Arc ac:
return new ArcCurve(ac);
case Ellipse el:
return el.ToNurbsCurve();
case Sphere sp:
return sp.ToBrep();
}
throw new SpeckleException("Failed to cast IGH_GeometricGoo to geometry base");
}
/// <summary>
/// Creates a tree based of a string that encodes the grasshopper topology.
/// </summary>
/// <param name="topology"></param>
/// <param name="subset"></param>
/// <returns></returns>
public static DataTree<object> CreateDataTreeFromTopologyAndItems(string topology, System.Collections.IList subset)
{
var tree = new DataTree<object>();
var treeTopo = topology.Split(' ');
int subsetCount = 0;
foreach (var branch in treeTopo)
{
if (!string.IsNullOrEmpty(branch))
{
var branchTopo = branch.Split('-')[0].Split(';');
var branchIndexes = new List<int>();
foreach (var t in branchTopo)
{
branchIndexes.Add(Convert.ToInt32(t));
}
var elCount = Convert.ToInt32(branch.Split('-')[1]);
var myPath = new GH_Path(branchIndexes.ToArray());
for (int i = 0; i < elCount; i++)
{
tree.EnsurePath(myPath).Add(new Grasshopper.Kernel.Types.GH_ObjectWrapper(subset[subsetCount + i]));
}
subsetCount += elCount;
}
}
return tree;
}
/// <summary>
/// Encodes a tree topology into an exhaustive string which can be used to recreate it using
/// <see cref="CreateDataTreeFromTopologyAndItems"/>.
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
public static string GetParamTopology(IGH_Param param)
{
string topology = "";
foreach (GH_Path myPath in param.VolatileData.Paths)
{
topology += myPath.ToString(false) + "-" + param.VolatileData.get_Branch(myPath).Count + " ";
}
return topology;
}
}

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