Compare commits

..

1 Commits

Author SHA1 Message Date
Dimitrie Stefanescu 22c6303167 wip pipe implementation 2026-01-21 14:23:27 +00:00
220 changed files with 3390 additions and 5592 deletions
@@ -171,8 +171,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -182,13 +182,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +281,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -312,7 +312,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -356,11 +356,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
}
@@ -171,8 +171,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -182,13 +182,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +281,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -312,7 +312,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -356,11 +356,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
}
@@ -171,8 +171,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -182,13 +182,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +281,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -313,7 +313,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -357,11 +357,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
}
@@ -157,8 +157,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -166,13 +166,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -230,7 +230,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -262,7 +262,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -306,11 +306,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
},
@@ -157,8 +157,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -166,13 +166,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -230,7 +230,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -262,7 +262,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -306,11 +306,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
},
@@ -145,9 +145,7 @@ public abstract class AutocadSendBaseBinding : ISendBinding
Commands,
modelCardId,
(sp, card) => InitializeSettings(sp),
card => Application.DocumentManager.CurrentDocument.GetObjects(card.SendFilter.NotNull().RefreshObjectIds()),
Application.DocumentManager.CurrentDocument.Name,
null
card => Application.DocumentManager.CurrentDocument.GetObjects(card.SendFilter.NotNull().RefreshObjectIds())
);
}
finally
@@ -180,8 +180,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -191,13 +191,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -290,7 +290,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -322,7 +322,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -366,11 +366,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
}
@@ -180,8 +180,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -191,13 +191,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -290,7 +290,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -322,7 +322,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -366,11 +366,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
}
@@ -180,8 +180,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -191,13 +191,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -290,7 +290,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -322,7 +322,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -366,11 +366,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
}
@@ -166,8 +166,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -175,13 +175,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -239,7 +239,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -272,7 +272,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -316,11 +316,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
},
@@ -166,8 +166,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -175,13 +175,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -239,7 +239,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -272,7 +272,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -316,11 +316,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
},
@@ -1,5 +1,4 @@
using System.IO;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.CSiShared.Operations.Send.Settings;
@@ -56,7 +55,6 @@ public sealed class CsiSharedSendBinding : ISendBinding
public async Task Send(string modelCardId)
{
using var manager = _sendOperationManagerFactory.Create();
var (fileName, fileSizeBytes) = GetFileInfo();
await manager.Process(
Commands,
modelCardId,
@@ -71,24 +69,10 @@ public sealed class CsiSharedSendBinding : ISendBinding
)
);
},
card => card.SendFilter.NotNull().RefreshObjectIds().Select(DecodeObjectIdentifier).ToList(),
fileName,
fileSizeBytes
card => card.SendFilter.NotNull().RefreshObjectIds().Select(DecodeObjectIdentifier).ToList()
);
}
private (string? fileName, long? fileBytes) GetFileInfo()
{
string fullPath = _csiApplicationService.SapModel.GetModelFilename(true);
if (!File.Exists(fullPath))
{
return (null, null);
}
var fileInfo = new FileInfo(fullPath);
return (fileInfo.Name, fileInfo.Length);
}
private ICsiWrapper DecodeObjectIdentifier(string encodedId)
{
var (type, name) = ObjectIdentifier.Decode(encodedId);
@@ -171,8 +171,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -182,13 +182,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +281,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -305,7 +305,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.etabs21": {
@@ -355,11 +355,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
}
@@ -157,8 +157,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -166,13 +166,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -230,7 +230,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -254,7 +254,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.etabs22": {
@@ -304,11 +304,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
}
@@ -171,8 +171,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -182,13 +182,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +281,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -305,7 +305,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.navisworks2020": {
@@ -357,11 +357,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
},
@@ -171,8 +171,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -182,13 +182,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +281,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -305,7 +305,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.navisworks2021": {
@@ -357,11 +357,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
},
@@ -171,8 +171,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -182,13 +182,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +281,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -305,7 +305,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.navisworks2022": {
@@ -357,11 +357,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
},
@@ -171,8 +171,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -182,13 +182,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +281,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -305,7 +305,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.navisworks2023": {
@@ -357,11 +357,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
},
@@ -171,8 +171,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -182,13 +182,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +281,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -305,7 +305,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.navisworks2024": {
@@ -357,11 +357,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
},
@@ -177,8 +177,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -188,13 +188,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -287,7 +287,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -311,7 +311,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.navisworks2025": {
@@ -357,11 +357,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
},
@@ -186,8 +186,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -197,13 +197,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -288,7 +288,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -312,7 +312,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.navisworks2026": {
@@ -359,11 +359,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
},
@@ -6,22 +6,32 @@ using Speckle.Sdk;
namespace Speckle.Connector.Navisworks.Bindings;
public class NavisworksBasicConnectorBinding(
IBrowserBridge parent,
DocumentModelStore store,
ISpeckleApplication speckleApplication
) : IBasicConnectorBinding
public class NavisworksBasicConnectorBinding : IBasicConnectorBinding
{
public string Name => "baseBinding";
public IBrowserBridge Parent { get; } = parent;
public IBrowserBridge Parent { get; }
public BasicConnectorBindingCommands Commands { get; }
public BasicConnectorBindingCommands Commands { get; } = new(parent);
private readonly DocumentModelStore _store;
private readonly ISpeckleApplication _speckleApplication;
public string GetSourceApplicationName() => speckleApplication.Slug;
public NavisworksBasicConnectorBinding(
IBrowserBridge parent,
DocumentModelStore store,
ISpeckleApplication speckleApplication
)
{
Parent = parent;
_store = store;
_speckleApplication = speckleApplication;
Commands = new BasicConnectorBindingCommands(parent);
}
public string GetSourceApplicationVersion() => speckleApplication.HostApplicationVersion;
public string GetSourceApplicationName() => _speckleApplication.Slug;
public string GetConnectorVersion() => speckleApplication.SpeckleVersion;
public string GetSourceApplicationVersion() => _speckleApplication.HostApplicationVersion;
public string GetConnectorVersion() => _speckleApplication.SpeckleVersion;
public DocumentInfo? GetDocumentInfo() =>
NavisworksApp.ActiveDocument is null || NavisworksApp.ActiveDocument.Models.Count == 0
@@ -32,15 +42,15 @@ public class NavisworksBasicConnectorBinding(
NavisworksApp.ActiveDocument.GetHashCode().ToString()
);
public DocumentModelStore GetDocumentState() => store;
public DocumentModelStore GetDocumentState() => _store;
public void AddModel(ModelCard model) => store.AddModel(model);
public void AddModel(ModelCard model) => _store.AddModel(model);
public void UpdateModel(ModelCard model) => store.UpdateModel(model);
public void UpdateModel(ModelCard model) => _store.UpdateModel(model);
public void RemoveModel(ModelCard model) => store.RemoveModel(model);
public void RemoveModel(ModelCard model) => _store.RemoveModel(model);
public void RemoveModels(List<ModelCard> models) => store.RemoveModels(models);
public void RemoveModels(List<ModelCard> models) => _store.RemoveModels(models);
public Task HighlightModel(string modelCardId) => Task.CompletedTask;
@@ -1,6 +1,6 @@
using Speckle.Connector.Navisworks.Services;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Converter.Navisworks.Services;
namespace Speckle.Connector.Navisworks.Bindings;
@@ -1,5 +1,3 @@
using System.IO;
using Autodesk.Navisworks.Api;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connector.Navisworks.Operations.Send.Filters;
using Speckle.Connector.Navisworks.Operations.Send.Settings;
@@ -14,7 +12,6 @@ using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.Settings;
using Speckle.Converter.Navisworks.Services;
using Speckle.Converter.Navisworks.Settings;
using Speckle.Converters.Common;
using Speckle.Sdk.Common;
@@ -61,12 +58,12 @@ public class NavisworksSendBinding : ISendBinding
private static void SubscribeToNavisworksEvents() { }
// WARNING: Changes to filter behavior here must match everywhere filters are used, or saved sets won't update correctly
// Do not change the behavior/scope of this class on send binding unless make sure the behavior is same. Otherwise, we might not be able to update list of saved sets.
public List<ISendFilter> GetSendFilters() =>
[
new NavisworksSelectionFilter() { IsDefault = true },
new NavisworksSavedSetsFilter(new ConnectorElementSelectionService()),
new NavisworksSavedViewsFilter(new ConnectorElementSelectionService())
new NavisworksSavedSetsFilter(new ElementSelectionService()),
new NavisworksSavedViewsFilter(new ElementSelectionService())
];
public List<ICardSetting> GetSendSettings() =>
@@ -85,27 +82,7 @@ public class NavisworksSendBinding : ISendBinding
private async Task SendInternal(string modelCardId)
{
using var manager = _sendOperationManagerFactory.Create();
var (fileName, fileSizeBytes) = GetFileInfo();
await manager.Process(
Commands,
modelCardId,
InitializeConverterSettings,
GetNavisworksModelItems,
fileName,
fileSizeBytes
);
}
private (string? fileName, long? fileSizeBytes) GetFileInfo()
{
Document? activeDoc = NavisworksApp.ActiveDocument;
if (activeDoc is null || !File.Exists(activeDoc.FileName))
{
return (null, null);
}
FileInfo fileInfo = new(activeDoc.FileName);
return (fileInfo.Name, fileInfo.Length);
await manager.Process(Commands, modelCardId, InitializeConverterSettings, GetNavisworksModelItems);
}
private void InitializeConverterSettings(IServiceProvider serviceProvider, SenderModelCard modelCard) =>
@@ -128,7 +105,6 @@ public class NavisworksSendBinding : ISendBinding
)
{
var selectedPaths = modelCard.SendFilter.NotNull().RefreshObjectIds();
var convertHiddenElementsSetting =
modelCard.Settings!.FirstOrDefault(s => s.Id == "convertHiddenElements")?.Value as bool? ?? false;
var message = convertHiddenElementsSetting
@@ -139,78 +115,30 @@ public class NavisworksSendBinding : ISendBinding
{
throw new SpeckleSendFilterException(message);
}
onOperationProgressed.Report(new CardProgress("Getting selection...", null));
await Task.CompletedTask;
int estimatedCapacity = selectedPaths.Count * 10;
var modelItems = new List<NAV.ModelItem>(estimatedCapacity);
var modelItems = new List<NAV.ModelItem>();
double count = 0;
foreach (var path in selectedPaths)
{
onOperationProgressed.Report(new CardProgress("Getting selection...", count / selectedPaths.Count));
await Task.CompletedTask;
var modelItem = _selectionService.GetModelItemFromPath(path);
var hasChildren = modelItem.Children.Any();
if (hasChildren)
{
int nodesVisited = 0;
int hiddenBranchesPruned = 0;
const int REPORT_INTERVAL = 1000;
void TraverseWithProgress(NAV.ModelItem node)
{
nodesVisited++;
if (nodesVisited % REPORT_INTERVAL == 0)
{
onOperationProgressed.Report(
new CardProgress(
$"Expanding tree: {nodesVisited} visited, {modelItems.Count} with geometry, {hiddenBranchesPruned} hidden",
null
)
);
Task.Delay(1).Wait();
}
if (!_selectionService.IsVisible(node))
{
hiddenBranchesPruned++;
return;
}
if (node.HasGeometry)
{
modelItems.Add(node);
}
foreach (var child in node.Children)
{
TraverseWithProgress(child);
}
}
TraverseWithProgress(modelItem);
}
else
{
if (modelItem.HasGeometry && _selectionService.IsVisible(modelItem))
{
modelItems.Add(modelItem);
}
}
modelItems.AddRange(_selectionService.GetGeometryNodes(modelItem).Where(_selectionService.IsVisible));
count++;
}
return modelItems.Count == 0 ? throw new SpeckleSendFilterException(message) : modelItems;
}
public void CancelSend(string modelCardId) => _cancellationManager.CancelOperation(modelCardId);
/// <summary>
/// Cancels all outstanding send operations for the current document.
/// This method is called when the active document changes, to ensure
/// that any in-progress send operations are properly canceled before
/// the new document is loaded.
/// </summary>
public void CancelAllSendOperations()
{
foreach (var modelCardId in _store.GetSenders().Select(m => m.ModelCardId))
@@ -15,7 +15,7 @@ using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.WebView;
using Speckle.Converter.Navisworks.Constants.Registers;
using Speckle.Converter.Navisworks.Services;
using Speckle.Converter.Navisworks.Settings;
using Speckle.Converters.Common;
using Speckle.Sdk.Models.GraphTraversal;
@@ -53,6 +53,9 @@ public static class NavisworksConnectorServiceRegistration
serviceCollection.AddScoped<NavisworksMaterialUnpacker>();
serviceCollection.AddScoped<NavisworksColorUnpacker>();
// Register dual shared geometry stores for instancing pattern
serviceCollection.AddScoped<InstanceStoreManager>();
serviceCollection.AddSingleton<IAppIdleManager, NavisworksIdleManager>();
// Sending operations
@@ -61,9 +64,6 @@ public static class NavisworksConnectorServiceRegistration
serviceCollection.AddSingleton(DefaultTraversal.CreateTraversalFunc());
serviceCollection.AddSingleton<IOperationProgressManager, OperationProgressManager>();
// Registers and caches
serviceCollection.AddScoped<IInstanceFragmentRegistry, InstanceFragmentRegistry>();
// Register Intercom/interop
serviceCollection.AddSingleton<NavisworksDocumentModelStore>();
serviceCollection.AddSingleton<DocumentModelStore>(sp => sp.GetRequiredService<NavisworksDocumentModelStore>());
@@ -73,9 +73,6 @@ public static class NavisworksConnectorServiceRegistration
serviceCollection.AddScoped<ISendFilter, NavisworksSelectionFilter>();
serviceCollection.AddScoped<ISendFilter, NavisworksSavedSetsFilter>();
serviceCollection.AddScoped<ISendFilter, NavisworksSavedViewsFilter>();
serviceCollection.AddScoped<
Converter.Navisworks.Services.IElementSelectionService,
ConnectorElementSelectionService
>();
serviceCollection.AddScoped<IElementSelectionService, ElementSelectionService>();
}
}
@@ -1,12 +1,31 @@
namespace Speckle.Connector.Navisworks.Services;
using Speckle.InterfaceGenerator;
using static Speckle.Converter.Navisworks.Helpers.ElementSelectionHelper;
/// <summary>
/// Connector-specific element selection service that extends the converter's base implementation.
/// Inherits the cached visibility checking and path resolution from the converter layer.
/// </summary>
public class ConnectorElementSelectionService : Converter.Navisworks.Services.ElementSelectionService
namespace Speckle.Connector.Navisworks.Services;
[GenerateAutoInterface]
public class ElementSelectionService : IElementSelectionService
{
// This inherits all functionality from the converter's ElementSelectionService
// including cached IsVisible, GetModelItemPath, GetModelItemFromPath, and GetGeometryNodes
// Connector-specific extensions can be added here if needed in the future
private readonly Dictionary<Guid, bool> _visibleCache = new();
public string GetModelItemPath(NAV.ModelItem modelItem) => ResolveModelItemToIndexPath(modelItem);
public NAV.ModelItem GetModelItemFromPath(string path) => ResolveIndexPathToModelItem(path);
public bool IsVisible(NAV.ModelItem modelItem)
{
var key = modelItem.InstanceGuid;
if (_visibleCache.TryGetValue(key, out var isVisible))
{
return isVisible;
}
//same as ElementSelectionHelper.IsElementVisible
foreach (var item in modelItem.AncestorsAndSelf)
{
_visibleCache[item.InstanceGuid] = !item.IsHidden;
}
return _visibleCache[key];
}
public IEnumerable<NAV.ModelItem> GetGeometryNodes(NAV.ModelItem modelItem) => ResolveGeometryLeafNodes(modelItem);
}
@@ -1,6 +1,6 @@
using Microsoft.Extensions.Logging;
using Speckle.Connector.Navisworks.Services;
using Speckle.Converter.Navisworks.Helpers;
using Speckle.Converter.Navisworks.Services;
using Speckle.Converter.Navisworks.Settings;
using Speckle.Converters.Common;
using Speckle.Sdk;
@@ -130,20 +130,16 @@ public class NavisworksColorUnpacker(
var comSelection = ComBridge.ToInwOpSelection([modelItem]);
try
{
var paths = comSelection.Paths();
var pathsCollection = comSelection.Paths();
try
{
foreach (ComApi.InwOaPath path in paths)
foreach (ComApi.InwOaPath path in pathsCollection)
{
GC.KeepAlive(path);
var fragments = path.Fragments();
var fragmentsCollection = path.Fragments();
try
{
foreach (ComApi.InwOaFragment3 fragment in fragments)
foreach (ComApi.InwOaFragment3 fragment in fragmentsCollection.OfType<ComApi.InwOaFragment3>())
{
GC.KeepAlive(fragment);
fragment.GenerateSimplePrimitives(ComApi.nwEVertexProperty.eNORMAL, primitiveChecker);
if (primitiveChecker.HasTriangles)
@@ -154,9 +150,9 @@ public class NavisworksColorUnpacker(
}
finally
{
if (fragments != null)
if (fragmentsCollection != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(fragments);
System.Runtime.InteropServices.Marshal.ReleaseComObject(fragmentsCollection);
}
}
}
@@ -165,9 +161,9 @@ public class NavisworksColorUnpacker(
}
finally
{
if (paths != null)
if (pathsCollection != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(paths);
System.Runtime.InteropServices.Marshal.ReleaseComObject(pathsCollection);
}
}
}
@@ -120,6 +120,17 @@ public sealed class NavisworksDocumentEvents
}
}
private void UnsubscribeFromDocumentModelEvents(object _)
{
var activeDocument = NavisworksApp.ActiveDocument;
if (activeDocument != null)
{
UnsubscribeFromModelEvents(activeDocument);
}
_isSubscribed = false;
}
private void UnsubscribeFromModelEvents(NAV.Document document)
{
document.Models.CollectionChanged -= HandleDocumentModelCountChanged;
@@ -1,18 +1,22 @@
using Autodesk.Navisworks.Api.ComApi;
using Autodesk.Navisworks.Api.Interop.ComApi;
using Microsoft.Extensions.Logging;
using Speckle.Connector.Navisworks.Services;
using Speckle.Converter.Navisworks.Constants;
using Speckle.Converter.Navisworks.Helpers;
using Speckle.Converter.Navisworks.Services;
using Speckle.Converter.Navisworks.Settings;
using Speckle.Converter.Navisworks.ToSpeckle;
using Speckle.Converters.Common;
using Speckle.Objects.Other;
using Speckle.Sdk;
using static Speckle.Converter.Navisworks.Constants.MaterialConstants;
namespace Speckle.Connector.Navisworks.HostApp;
public class NavisworksMaterialUnpacker(
ILogger<NavisworksMaterialUnpacker> logger,
IConverterSettingsStore<NavisworksConversionSettings> converterSettings,
IElementSelectionService selectionService
IElementSelectionService selectionService,
GeometryToSpeckleConverter converter
)
{
private static T SelectByRepresentationMode<T>(
@@ -70,6 +74,66 @@ public class NavisworksMaterialUnpacker(
var navisworksObjectId = selectionService.GetModelItemPath(navisworksObject);
var finalId = mergedIds.TryGetValue(navisworksObjectId, out var mergedId) ? mergedId : navisworksObjectId;
string hashId = "";
try
{
var item = selectionService.GetModelItemFromPath(finalId);
var comSelection = ComApiBridge.ToInwOpSelection([item]);
try
{
var paths = comSelection.Paths();
try
{
if (paths.Count > 0)
{
var firstPath = paths.OfType<InwOaPath>().FirstOrDefault();
if (firstPath != null)
{
var fragments = firstPath.Fragments();
try
{
if (fragments.Count > 1)
{
var fragmentId = converter.GenerateFragmentId(paths);
hashId = $"{InstanceConstants.GEOMETRY_ID_PREFIX}{fragmentId}";
}
}
finally
{
if (fragments != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(fragments);
}
}
}
}
}
finally
{
if (paths != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(paths);
}
}
}
finally
{
if (comSelection != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(comSelection);
}
}
}
catch (Exception ex) when (!ex.IsFatal())
{ // If COM interop fails during hash generation, fall back to using finalId
logger.LogWarning(
ex,
"Failed to generate fragment hash ID for item {ItemId}, using finalId as fallback",
finalId
);
hashId = "";
}
var geometry = navisworksObject.Geometry;
var mode = converterSettings.Current.User.VisualRepresentationMode;
@@ -98,7 +162,7 @@ public class NavisworksMaterialUnpacker(
);
var materialName =
$"{DEFAULT_MATERIAL_NAME_PREFIX}{Math.Abs(ColorConverter.NavisworksColorToColor(renderColor).ToArgb())}";
$"{MaterialConstants.DEFAULT_MATERIAL_NAME_PREFIX}{Math.Abs(ColorConverter.NavisworksColorToColor(renderColor).ToArgb())}";
var itemCategory = navisworksObject.PropertyCategories.FindCategoryByDisplayName("Item");
if (itemCategory != null)
@@ -124,14 +188,14 @@ public class NavisworksMaterialUnpacker(
if (renderMaterialProxies.TryGetValue(renderMaterialId.ToString(), out RenderMaterialProxy? value))
{
value.objects.Add(finalId);
value.objects.Add(!string.IsNullOrEmpty(hashId) ? hashId : finalId);
}
else
{
renderMaterialProxies[renderMaterialId.ToString()] = new RenderMaterialProxy()
{
value = CreateRenderMaterial(materialName, renderTransparency, renderColor, renderMaterialId),
objects = [finalId]
objects = [!string.IsNullOrEmpty(hashId) ? hashId : finalId]
};
}
}
@@ -155,7 +219,9 @@ public class NavisworksMaterialUnpacker(
var speckleRenderMaterial = new RenderMaterial()
{
name = !string.IsNullOrEmpty(name) ? name : $"{DEFAULT_MATERIAL_NAME_PREFIX}{Math.Abs(color.ToArgb())}",
name = !string.IsNullOrEmpty(name)
? name
: $"{MaterialConstants.DEFAULT_MATERIAL_NAME_PREFIX}{Math.Abs(color.ToArgb())}",
opacity = 1 - transparency,
metalness = 0,
roughness = 1,
@@ -1,7 +1,7 @@
using Speckle.Connectors.DUI.Exceptions;
using Speckle.Connector.Navisworks.Services;
using Speckle.Connectors.DUI.Exceptions;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.Utils;
using Speckle.Converter.Navisworks.Services;
namespace Speckle.Connector.Navisworks.Operations.Send.Filters;
@@ -1,7 +1,7 @@
using Speckle.Connectors.DUI.Exceptions;
using Speckle.Connector.Navisworks.Services;
using Speckle.Connectors.DUI.Exceptions;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.Utils;
using Speckle.Converter.Navisworks.Services;
namespace Speckle.Connector.Navisworks.Operations.Send.Filters;
@@ -48,6 +48,8 @@ public class NavisworksSavedViewsFilter : DiscriminatedObject, ISendFilterSelect
return objectIds;
}
var savedViews = NavisworksApp.ActiveDocument.SavedViewpoints;
foreach (var savedViewItem in SelectedItems.Select(item => ResolveSavedView(item.Id)))
{
// Get the visible elements in the saved view.
@@ -80,12 +82,12 @@ public class NavisworksSavedViewsFilter : DiscriminatedObject, ISendFilterSelect
{
var objectIds = new List<string>();
// THIS IS COMMENTED OUT AS IT IS LEGACY DEFENSIVE BEHAVIOR - DISCUSSION REQUIRED
// THIS IS COMMENTED OUT AS IT IS LEGACY DEFENSIVE BEHAVIOUR - DISCUSSION REQUIRED
// if (!savedView.ContainsVisibilityOverrides)
// {
// // We check this again as the view settings may have changed in the saved card.
// // If the saved view does not contain visibility overrides, this is effectively everything in the model.
// // This will need to be the documented behavior.
// // This will need to be the documented behaviour.
// throw new SpeckleSendFilterException(
// "Saved view does not contain visibility overrides. This would effectively publish everything in the model."
// );
@@ -152,7 +154,7 @@ public class NavisworksSavedViewsFilter : DiscriminatedObject, ISendFilterSelect
switch (item)
{
// case NAV.SavedViewpoint { ContainsVisibilityOverrides: false }:
// Legacy defensive behavior: skip viewpoints without visibility overrides.
// Legacy defensive behaviour: skip viewpoints without visibility overrides.
// Essentially, send everything, or whatever the current view state for hidden elements is.
// break;
case NAV.SavedViewpointAnimationCut:
@@ -1,4 +1,4 @@
using static Speckle.Converter.Navisworks.Constants.PathConstants;
using Speckle.Converter.Navisworks.Constants;
namespace Speckle.Connector.Navisworks.Operations.Send.Filters;
@@ -15,6 +15,6 @@ public static class SavedItemHelpers
current = current.Parent;
}
return string.Join(SET_SEPARATOR, pathParts);
return string.Join(PathConstants.SET_SEPARATOR, pathParts);
}
}
@@ -1,5 +1,5 @@
using Speckle.Connector.Navisworks.Services;
using static Speckle.Converter.Navisworks.Constants.PathConstants;
using Speckle.Converter.Navisworks.Constants;
namespace Speckle.Connector.Navisworks.Operations.Send;
@@ -10,29 +10,29 @@ public static class GeometryNodeMerger
{
/// <summary>
/// Groups sibling geometry nodes based on material properties for merging.
/// This only merges nodes that share the same parent and have identical material properties.
/// Only merges nodes that share the same parent and have identical material properties.
/// </summary>
/// <param name="nodes">The collection of ModelItems to process</param>
/// <returns>Dictionary mapping parent paths (with material signature suffix) to their mergeable child nodes</returns>
public static Dictionary<string, List<NAV.ModelItem>> GroupSiblingGeometryNodes(IReadOnlyList<NAV.ModelItem> nodes)
{
var selectionService = new ConnectorElementSelectionService();
var selectionService = new ElementSelectionService();
// Group nameless geometry nodes by parent path and material signature
var mergeableGroups = nodes
.Where(node => node.HasGeometry && string.IsNullOrEmpty(node.DisplayName)) // Only anonymous geometry nodes
.GroupBy(node =>
{
// Get the parent path
// Get parent path
var path = selectionService.GetModelItemPath(node);
var lastSeparatorIndex = path.LastIndexOf(SEPARATOR);
var lastSeparatorIndex = path.LastIndexOf(PathConstants.SEPARATOR);
var parentPath = lastSeparatorIndex == -1 ? path : path[..lastSeparatorIndex];
// Generate material signature
string signature = GenerateSignature(node);
// Combine parent path with signature
return $"{parentPath}{MATERIAL_SEPARATOR}{signature}";
return $"{parentPath}{PathConstants.MATERIAL_SEPARATOR}{signature}";
})
.Where(group => group.Count() > 1) // Only include groups with multiple children
.ToDictionary(group => group.Key, group => group.ToList());
@@ -95,7 +95,7 @@ public static class GeometryNodeMerger
// Build a consistent string representation of all properties
var hashInput = new System.Text.StringBuilder();
// Sort keys to ensure a consistent order
// Sort keys to ensure consistent order
var sortedKeys = properties.Keys.OrderBy(k => k).ToList();
foreach (var key in sortedKeys)
@@ -139,7 +139,7 @@ public static class GeometryNodeMerger
/// </summary>
private static string GetMaterialName(NAV.ModelItem node)
{
// Check the Item category for material name
// Check Item category for material name
var itemCategory = node.PropertyCategories.FindCategoryByDisplayName("Item");
if (itemCategory != null)
{
@@ -1,8 +1,8 @@
using Speckle.Converter.Navisworks.Services;
using Speckle.Connector.Navisworks.Services;
using Speckle.Converter.Navisworks.Constants;
using Speckle.Converters.Common;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using static Speckle.Converter.Navisworks.Constants.PathConstants;
namespace Speckle.Connector.Navisworks.Operations.Send;
@@ -59,8 +59,8 @@ public class NavisworksHierarchyBuilder
allPaths.Sort(
(a, b) =>
{
var depthA = a.Count(c => c == SEPARATOR);
var depthB = b.Count(c => c == SEPARATOR);
var depthA = a.Count(c => c == PathConstants.SEPARATOR);
var depthB = b.Count(c => c == PathConstants.SEPARATOR);
return depthB.CompareTo(depthA); // <- Sort in ascending order of path length
}
);
@@ -126,7 +126,7 @@ public class NavisworksHierarchyBuilder
private static string GetParentPath(string path)
{
var idx = path.LastIndexOf(SEPARATOR);
var idx = path.LastIndexOf(PathConstants.SEPARATOR);
return idx == -1 ? string.Empty : path[..idx];
}
@@ -1,5 +1,6 @@
using Microsoft.Extensions.Logging;
using Speckle.Connector.Navisworks.HostApp;
using Speckle.Connector.Navisworks.Services;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Conversion;
@@ -13,10 +14,7 @@ using Speckle.Sdk;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
using static Speckle.Connector.Navisworks.Operations.Send.GeometryNodeMerger;
using static Speckle.Connectors.Common.Operations.ProxyKeys;
using static Speckle.Converter.Navisworks.Constants.InstanceConstants;
namespace Speckle.Connector.Navisworks.Operations.Send;
@@ -28,15 +26,14 @@ public class NavisworksRootObjectBuilder(
ISdkActivityFactory activityFactory,
NavisworksMaterialUnpacker materialUnpacker,
NavisworksColorUnpacker colorUnpacker,
Speckle.Converter.Navisworks.Constants.Registers.IInstanceFragmentRegistry instanceRegistry,
IElementSelectionService elementSelectionService,
IUiUnitsCache uiUnitsCache
IUiUnitsCache uiUnitsCache,
InstanceStoreManager instanceStoreManager
) : IRootObjectBuilder<NAV.ModelItem>
{
#pragma warning disable CA1823
#pragma warning restore CA1823
private bool SkipNodeMerging { get; set; }
private bool DisableGroupingForInstanceTesting { get; set; }
internal NavisworksConversionSettings GetCurrentSettings() => converterSettings.Current;
public async Task<RootObjectBuilderResult> Build(
IReadOnlyList<NAV.ModelItem> navisworksModelItems,
@@ -46,14 +43,14 @@ public class NavisworksRootObjectBuilder(
)
{
#if DEBUG
SkipNodeMerging = false;
DisableGroupingForInstanceTesting = false;
SkipNodeMerging = true;
#endif
using var activity = activityFactory.Start("Build");
ValidateInputs(navisworksModelItems, projectId, onOperationProgressed);
var rootCollection = InitializeRootCollection();
(Dictionary<string, Base?> convertedElements, List<SendConversionResult> conversionResults) =
await ConvertModelItemsAsync(navisworksModelItems, projectId, onOperationProgressed, cancellationToken);
@@ -61,17 +58,30 @@ public class NavisworksRootObjectBuilder(
var groupedNodes = SkipNodeMerging ? [] : GroupSiblingGeometryNodes(navisworksModelItems);
var finalElements = BuildFinalElements(convertedElements, groupedNodes);
List<Base> geometryDefinitions = instanceStoreManager.GetGeometryDefinitions();
await AddProxiesToCollection(rootCollection, navisworksModelItems, groupedNodes);
AddInstanceDefinitionsToCollection(rootCollection, ref finalElements);
int finalInstanceProxyCount = CountInstanceProxiesRecursive(finalElements);
logger.LogInformation(
"Final output contains {count} InstanceProxy objects in displayValues",
finalInstanceProxyCount
);
var geometryDefinitionsCollection = new Collection
{
name = "Geometry Definitions",
["units"] = converterSettings.Current.Derived.SpeckleUnits,
elements = geometryDefinitions
};
var mainElementsCollection = new Collection
{
name = rootCollection.name,
["units"] = converterSettings.Current.Derived.SpeckleUnits,
elements = finalElements
};
rootCollection.elements = [mainElementsCollection];
if (geometryDefinitions.Count > 0)
{
rootCollection.elements.Add(geometryDefinitionsCollection);
}
rootCollection.elements = finalElements;
return new RootObjectBuilderResult(rootCollection, conversionResults);
}
@@ -117,32 +127,16 @@ public class NavisworksRootObjectBuilder(
var convertedBases = new Dictionary<string, Base?>();
int processedCount = 0;
int totalCount = navisworksModelItems.Count;
int instanceProxyCount = 0;
foreach (var item in navisworksModelItems)
{
cancellationToken.ThrowIfCancellationRequested();
var converted = ConvertNavisworksItem(item, convertedBases, projectId);
results.Add(converted);
if (
converted.Status == Status.SUCCESS
&& convertedBases.TryGetValue(elementSelectionService.GetModelItemPath(item), out var convertedBase)
&& convertedBase?["displayValue"] is List<Base> displayValues
)
{
instanceProxyCount += displayValues.Count(dv => dv.GetType().Name == "InstanceProxy");
}
processedCount++;
onOperationProgressed.Report(new CardProgress("Converting", (double)processedCount / totalCount));
}
logger.LogInformation(
"Converted {total} items, found {instanceProxies} InstanceProxy objects",
totalCount,
instanceProxyCount
);
return Task.FromResult((convertedBases, results));
}
@@ -161,24 +155,10 @@ public class NavisworksRootObjectBuilder(
{
var finalElements = new List<Base>();
var processedPaths = new HashSet<string>();
if (!DisableGroupingForInstanceTesting)
{
AddGroupedElements(finalElements, convertedBases, groupedNodes, processedPaths);
logger.LogInformation(
"After grouping: {grouped} paths processed, {elements} elements in collection",
processedPaths.Count,
finalElements.Count
);
}
else
{
logger.LogInformation("Grouping disabled for instance testing");
}
AddGroupedElements(finalElements, convertedBases, groupedNodes, processedPaths);
if (converterSettings.Current.User.PreserveModelHierarchy)
{
logger.LogInformation("Building hierarchy (PreserveModelHierarchy=true)");
var hierarchyBuilder = new NavisworksHierarchyBuilder(
convertedBases,
rootToSpeckleConverter,
@@ -188,10 +168,7 @@ public class NavisworksRootObjectBuilder(
return hierarchyBuilder.BuildHierarchy();
}
logger.LogInformation("Adding remaining elements (flat mode)");
AddRemainingElements(finalElements, convertedBases, processedPaths);
logger.LogInformation("Final elements count: {count}", finalElements.Count);
return finalElements;
}
@@ -204,7 +181,7 @@ public class NavisworksRootObjectBuilder(
{
foreach (var group in groupedNodes)
{
var siblingBases = new List<Base>(group.Value.Count);
var siblingBases = new List<Base>();
foreach (var itemPath in group.Value.Select(elementSelectionService.GetModelItemPath))
{
processedPaths.Add(itemPath);
@@ -259,29 +236,10 @@ public class NavisworksRootObjectBuilder(
string cleanParentPath = ElementSelectionHelper.GetCleanPath(groupKey);
(string name, string path) = GetElementNameAndPath(cleanParentPath);
int estimatedCapacity = siblingBases.Sum(b => (b["displayValue"] as List<Base>)?.Count ?? 0);
var displayValues = new List<Base>(estimatedCapacity);
displayValues.AddRange(
siblingBases
.Where(sibling => sibling["displayValue"] is List<Base>)
.SelectMany(sibling => (List<Base>)sibling["displayValue"]!)
);
var instanceProxyCount = displayValues.Count(dv => dv.GetType().Name == "InstanceProxy");
if (instanceProxyCount > 0)
{
logger.LogDebug(
"Group {groupKey} merging {siblings} siblings with {proxies} InstanceProxy objects",
groupKey,
siblingBases.Count,
instanceProxyCount
);
}
return new NavisworksObject
{
name = name,
displayValue = displayValues,
displayValue = siblingBases.SelectMany(b => b["displayValue"] as List<Base> ?? []).ToList(),
properties = siblingBases.First()["properties"] as Dictionary<string, object?> ?? [],
units = converterSettings.Current.Derived.SpeckleUnits,
applicationId = groupKey,
@@ -322,100 +280,25 @@ public class NavisworksRootObjectBuilder(
var renderMaterials = materialUnpacker.UnpackRenderMaterial(navisworksModelItems, groupedNodes);
if (renderMaterials.Count > 0)
{
rootCollection[RENDER_MATERIAL] = renderMaterials;
rootCollection[ProxyKeys.RENDER_MATERIAL] = renderMaterials;
}
var colors = colorUnpacker.UnpackColor(navisworksModelItems, groupedNodes);
if (colors.Count > 0)
{
rootCollection[COLOR] = colors;
rootCollection[ProxyKeys.COLOR] = colors;
}
var instanceDefinitionProxies = instanceStoreManager.GetInstanceDefinitionProxies();
if (instanceDefinitionProxies.Count > 0)
{
rootCollection[ProxyKeys.INSTANCE_DEFINITION] = instanceDefinitionProxies.ToList();
}
return Task.CompletedTask;
}
private void AddInstanceDefinitionsToCollection(Collection rootCollection, ref List<Base> finalElements)
{
using var _ = activityFactory.Start("BuildInstanceDefinitions");
// Get all definition geometries from the registry
var allDefinitions = instanceRegistry.GetAllDefinitionGeometries();
if (allDefinitions.Count == 0)
{
logger.LogInformation("No instance definitions found - instancing may be disabled");
return;
}
logger.LogInformation("Building instance structure for {count} definition groups", allDefinitions.Count);
if (allDefinitions.Count > 100)
{
logger.LogWarning(
"Large number of definition groups ({count}) detected - this may indicate instance grouping is not working effectively",
allDefinitions.Count
);
}
var instanceDefinitionProxies = new List<InstanceDefinitionProxy>(allDefinitions.Count);
int estimatedGeometryCount = allDefinitions.Sum(kvp => kvp.Value.Count);
var allDefinitionGeometries = new List<Base>(estimatedGeometryCount);
foreach (var kvp in allDefinitions)
{
var groupKey = kvp.Key;
var geometries = kvp.Value;
var groupKeyHash = groupKey.ToHashString();
var defProxy = new InstanceDefinitionProxy
{
name = $"Shared Geometry {groupKeyHash}",
objects = geometries.Select(g => g.applicationId ?? "").Where(id => !string.IsNullOrEmpty(id)).ToList(),
applicationId = $"{DEFINITION_ID_PREFIX}{groupKeyHash}",
maxDepth = 0
};
instanceDefinitionProxies.Add(defProxy);
allDefinitionGeometries.AddRange(geometries);
}
rootCollection[INSTANCE_DEFINITION] = instanceDefinitionProxies;
var geometryDefinitionsCollection = new Collection
{
name = "Geometry Definitions",
elements = allDefinitionGeometries
};
var objectCollection = new Collection { name = "", elements = finalElements };
finalElements = [geometryDefinitionsCollection, objectCollection];
logger.LogInformation(
"Added {proxyCount} instance definition proxies and {geomCount} definition geometries",
instanceDefinitionProxies.Count,
allDefinitionGeometries.Count
);
}
private int CountInstanceProxiesRecursive(List<Base> elements)
{
int count = 0;
foreach (var element in elements)
{
if (element["displayValue"] is List<Base> displayValues)
{
count += displayValues.Count(dv => dv.GetType().Name == "InstanceProxy");
}
if (element is Collection { elements: not null } collection)
{
count += CountInstanceProxiesRecursive(collection.elements);
}
}
return count;
}
private SendConversionResult ConvertNavisworksItem(
NAV.ModelItem navisworksItem,
Dictionary<string, Base?> convertedBases,
@@ -1,4 +1,5 @@
using Speckle.Connectors.Common.Caching;
using System.Diagnostics.CodeAnalysis;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Converter.Navisworks.Settings;
using Speckle.InterfaceGenerator;
@@ -7,106 +8,158 @@ using Speckle.Sdk.Common;
namespace Speckle.Connector.Navisworks.Operations.Send.Settings;
[GenerateAutoInterface]
public class ToSpeckleSettingsManagerNavisworks(ISendConversionCache sendConversionCache)
: IToSpeckleSettingsManagerNavisworks
public class ToSpeckleSettingsManagerNavisworks : IToSpeckleSettingsManagerNavisworks
{
// cache invalidation process run with ModelCardId since the settings are model-specific
private readonly ISendConversionCache _sendConversionCache;
// cache invalidation process run with ModelCardId since the settings are model specific
private readonly Dictionary<string, RepresentationMode> _visualRepresentationCache = [];
private readonly Dictionary<string, OriginMode> _originModeCache = [];
private readonly Dictionary<string, bool> _convertHiddenElementsCache = [];
private readonly Dictionary<string, bool> _includeInternalPropertiesCache = [];
private readonly Dictionary<string, bool> _preserveModelHierarchyCache = [];
private readonly Dictionary<string, bool> _revitCategoryMappingCache = [];
private readonly Dictionary<string, bool?> _convertHiddenElementsCache = [];
private readonly Dictionary<string, bool?> _includeInternalPropertiesCache = [];
private readonly Dictionary<string, bool?> _preserveModelHierarchyCache = [];
private readonly Dictionary<string, bool?> _revitCategoryMappingCache = [];
/// <summary>
/// Generic helper to get a setting value with caching and cache invalidation.
/// </summary>
private T GetCachedSetting<T>(
SenderModelCard modelCard,
string settingId,
Dictionary<string, T> cache,
Func<object?, T> valueExtractor,
T defaultValue
)
public ToSpeckleSettingsManagerNavisworks(ISendConversionCache sendConversionCache)
{
_sendConversionCache = sendConversionCache;
}
public RepresentationMode GetVisualRepresentationMode(SenderModelCard modelCard)
{
if (modelCard == null)
{
throw new ArgumentNullException(nameof(modelCard));
}
var settingValue = modelCard.Settings?.FirstOrDefault(s => s.Id == settingId)?.Value;
var returnValue = settingValue != null ? valueExtractor(settingValue) : defaultValue;
var representationString = modelCard.Settings?.First(s => s.Id == "visualRepresentation").Value as string;
if (
cache.TryGetValue(modelCard.ModelCardId.NotNull(), out var previousValue)
&& !EqualityComparer<T>.Default.Equals(previousValue, returnValue)
representationString is not null
&& VisualRepresentationSetting.VisualRepresentationMap.TryGetValue(
representationString,
out RepresentationMode representation
)
)
{
if (_visualRepresentationCache.TryGetValue(modelCard.ModelCardId.NotNull(), out RepresentationMode previousType))
{
if (previousType != representation)
{
EvictCacheForModelCard(modelCard);
}
}
_visualRepresentationCache[modelCard.ModelCardId.NotNull()] = representation;
return representation;
}
throw new ArgumentException($"Invalid visual representation value: {representationString}");
}
public OriginMode GetOriginMode(SenderModelCard modelCard)
{
if (modelCard == null)
{
throw new ArgumentNullException(nameof(modelCard));
}
var originString = modelCard.Settings?.FirstOrDefault(s => s.Id == "originMode")?.Value as string;
if (!OriginModeSetting.OriginModeMap.TryGetValue(originString ?? string.Empty, out var origin))
{
return OriginMode.ModelOrigin; // Default to ModelOrigin if not specified or invalid
}
if (_originModeCache.TryGetValue(modelCard.ModelCardId.NotNull(), out var previousType) && previousType != origin)
{
EvictCacheForModelCard(modelCard);
}
cache[modelCard.ModelCardId.NotNull()] = returnValue;
_originModeCache[modelCard.ModelCardId.NotNull()] = origin;
return origin;
}
public bool GetMappingToRevitCategories(SenderModelCard modelCard)
{
if (modelCard == null)
{
throw new ArgumentNullException(nameof(modelCard));
}
var value = modelCard.Settings?.FirstOrDefault(s => s.Id == "mappingToRevitCategories")?.Value as bool?;
var returnValue = value != null && value.NotNull();
if (_revitCategoryMappingCache.TryGetValue(modelCard.ModelCardId.NotNull(), out var previousValue))
{
if (previousValue != returnValue)
{
EvictCacheForModelCard(modelCard);
}
}
_revitCategoryMappingCache[modelCard.ModelCardId] = returnValue;
return returnValue;
}
public RepresentationMode GetVisualRepresentationMode(SenderModelCard modelCard) =>
GetCachedSetting(
modelCard,
"visualRepresentation",
_visualRepresentationCache,
value =>
public bool GetConvertHiddenElements(SenderModelCard modelCard)
{
if (modelCard == null)
{
throw new ArgumentNullException(nameof(modelCard));
}
var value = modelCard.Settings?.FirstOrDefault(s => s.Id == "convertHiddenElements")?.Value as bool?;
var returnValue = value != null && value.NotNull();
if (_convertHiddenElementsCache.TryGetValue(modelCard.ModelCardId.NotNull(), out var previousValue))
{
if (previousValue != returnValue)
{
var representationString = value as string;
return
representationString is not null
&& VisualRepresentationSetting.VisualRepresentationMap.TryGetValue(
representationString,
out RepresentationMode representation
)
? representation
: throw new ArgumentException($"Invalid visual representation value: {representationString}");
},
RepresentationMode.Active // default value if setting not found
);
EvictCacheForModelCard(modelCard);
}
}
public OriginMode GetOriginMode(SenderModelCard modelCard) =>
GetCachedSetting(
modelCard,
"originMode",
_originModeCache,
value =>
_convertHiddenElementsCache[modelCard.ModelCardId] = returnValue;
return returnValue;
}
public bool GetIncludeInternalProperties([NotNull] SenderModelCard modelCard)
{
var value = modelCard.Settings?.FirstOrDefault(s => s.Id == "includeInternalProperties")?.Value as bool?;
var returnValue = value != null && value.NotNull();
if (_includeInternalPropertiesCache.TryGetValue(modelCard.ModelCardId.NotNull(), out var previousValue))
{
if (previousValue != returnValue)
{
var originString = value as string;
if (OriginModeSetting.OriginModeMap.TryGetValue(originString ?? string.Empty, out var origin))
{
return origin;
}
return OriginMode.ModelOrigin;
},
OriginMode.ModelOrigin
);
EvictCacheForModelCard(modelCard);
}
}
public bool GetMappingToRevitCategories(SenderModelCard modelCard) =>
GetCachedSetting(modelCard, "mappingToRevitCategories", _revitCategoryMappingCache, value => value is true, false);
_includeInternalPropertiesCache[modelCard.ModelCardId] = returnValue;
return returnValue;
}
public bool GetConvertHiddenElements(SenderModelCard modelCard) =>
GetCachedSetting(modelCard, "convertHiddenElements", _convertHiddenElementsCache, value => value is true, false);
public bool GetPreserveModelHierarchy([NotNull] SenderModelCard modelCard)
{
var value = modelCard.Settings?.FirstOrDefault(s => s.Id == "preserveModelHierarchy")?.Value as bool?;
public bool GetIncludeInternalProperties(SenderModelCard modelCard) =>
GetCachedSetting(
modelCard,
"includeInternalProperties",
_includeInternalPropertiesCache,
value => value is true,
false
);
var returnValue = value != null && value.NotNull();
if (_preserveModelHierarchyCache.TryGetValue(modelCard.ModelCardId.NotNull(), out var previousValue))
{
if (previousValue != returnValue)
{
EvictCacheForModelCard(modelCard);
}
}
public bool GetPreserveModelHierarchy(SenderModelCard modelCard) =>
GetCachedSetting(modelCard, "preserveModelHierarchy", _preserveModelHierarchyCache, value => value is true, false);
_preserveModelHierarchyCache[modelCard.ModelCardId] = returnValue;
return returnValue;
}
private void EvictCacheForModelCard(SenderModelCard modelCard)
{
var objectIds = modelCard.SendFilter != null ? modelCard.SendFilter.NotNull().SelectedObjectIds : [];
sendConversionCache.EvictObjects(objectIds);
_sendConversionCache.EvictObjects(objectIds);
}
}
@@ -14,7 +14,7 @@ public static class SpeckleV3Tool
public const string RIBBON_STRINGS = "NavisworksRibbon.name";
public const string PLUGIN_SUFFIX = ".Speckle";
public static Sdk.Application App =>
public static Speckle.Sdk.Application App =>
#if NAVIS
HostApplications.Navisworks;
#else
@@ -193,8 +193,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -204,13 +204,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -297,20 +297,13 @@
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"speckle.common.meshtriangulation": {
"type": "Project",
"dependencies": {
"LibTessDotNet": "[1.1.15, )",
"Speckle.DoubleNumerics": "[4.1.0, )"
}
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -333,23 +326,16 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.revit2022": {
"type": "Project",
"dependencies": {
"Speckle.Common.MeshTriangulation": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Revit.API": "[2022.0.2.1, )"
}
},
"LibTessDotNet": {
"type": "CentralTransitive",
"requested": "[1.1.15, )",
"resolved": "1.1.15",
"contentHash": "KuA7N3Nv/lIeawJdQBQJR6oqWD9KETHLbWzBqapwFs+Tby+R5I4crkKujKMm5bXcSuFZ8LNtflFQVadsWCbBjg=="
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -385,11 +371,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
},
"Speckle.Revit.API": {
@@ -193,8 +193,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -204,13 +204,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -297,20 +297,13 @@
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"speckle.common.meshtriangulation": {
"type": "Project",
"dependencies": {
"LibTessDotNet": "[1.1.15, )",
"Speckle.DoubleNumerics": "[4.1.0, )"
}
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -333,23 +326,16 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.revit2023": {
"type": "Project",
"dependencies": {
"Speckle.Common.MeshTriangulation": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Revit.API": "[2023.0.0, )"
}
},
"LibTessDotNet": {
"type": "CentralTransitive",
"requested": "[1.1.15, )",
"resolved": "1.1.15",
"contentHash": "KuA7N3Nv/lIeawJdQBQJR6oqWD9KETHLbWzBqapwFs+Tby+R5I4crkKujKMm5bXcSuFZ8LNtflFQVadsWCbBjg=="
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -385,11 +371,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
},
"Speckle.Revit.API": {
@@ -193,8 +193,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -204,13 +204,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -297,20 +297,13 @@
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"speckle.common.meshtriangulation": {
"type": "Project",
"dependencies": {
"LibTessDotNet": "[1.1.15, )",
"Speckle.DoubleNumerics": "[4.1.0, )"
}
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -333,23 +326,16 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.revit2024": {
"type": "Project",
"dependencies": {
"Speckle.Common.MeshTriangulation": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Revit.API": "[2024.0.0, )"
}
},
"LibTessDotNet": {
"type": "CentralTransitive",
"requested": "[1.1.15, )",
"resolved": "1.1.15",
"contentHash": "KuA7N3Nv/lIeawJdQBQJR6oqWD9KETHLbWzBqapwFs+Tby+R5I4crkKujKMm5bXcSuFZ8LNtflFQVadsWCbBjg=="
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -385,11 +371,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
},
"Speckle.Revit.API": {
@@ -173,8 +173,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -182,13 +182,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -240,20 +240,13 @@
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
"speckle.common.meshtriangulation": {
"type": "Project",
"dependencies": {
"LibTessDotNet": "[1.1.15, )",
"Speckle.DoubleNumerics": "[4.1.0, )"
}
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -276,23 +269,16 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.revit2025": {
"type": "Project",
"dependencies": {
"Speckle.Common.MeshTriangulation": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Revit.API": "[2025.0.0, )"
}
},
"LibTessDotNet": {
"type": "CentralTransitive",
"requested": "[1.1.15, )",
"resolved": "1.1.15",
"contentHash": "KuA7N3Nv/lIeawJdQBQJR6oqWD9KETHLbWzBqapwFs+Tby+R5I4crkKujKMm5bXcSuFZ8LNtflFQVadsWCbBjg=="
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -328,11 +314,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
},
"Speckle.Revit.API": {
@@ -3,15 +3,17 @@
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">
<!-- WebView2 is created lazily -->
<Border Name="BrowserContainer" Background="White">
<TextBlock Name="LoadingText"
Text="Loading Speckle..."
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="14"
Foreground="Gray" />
</Border>
<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>
@@ -2,8 +2,6 @@ using System.Windows.Controls;
using System.Windows.Threading;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;
using Speckle.Connectors.DUI;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.Revit.Plugin;
@@ -14,10 +12,6 @@ public sealed partial class RevitControlWebView : UserControl, IBrowserScriptExe
{
private readonly IServiceProvider _serviceProvider;
private readonly IRevitTask _revitTask;
#pragma warning disable CA2213
private WebView2? _browser;
#pragma warning restore CA2213
private bool _isInitializing;
public RevitControlWebView(IServiceProvider serviceProvider, IRevitTask revitTask)
{
@@ -25,73 +19,36 @@ public sealed partial class RevitControlWebView : UserControl, IBrowserScriptExe
_revitTask = revitTask;
InitializeComponent();
// Delay WebView2 creation until the panel is actually visible
// This avoids conflicts with other plugins (like pyRevit) during startup
IsVisibleChanged += OnIsVisibleChanged;
}
private void OnIsVisibleChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is true && _browser == null && !_isInitializing)
{
_isInitializing = true;
Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, CreateWebView2);
}
}
private void CreateWebView2()
{
_browser = new WebView2
{
CreationProperties = new CoreWebView2CreationProperties { UserDataFolder = "C:\\temp" },
HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
VerticalAlignment = System.Windows.VerticalAlignment.Stretch,
Source = Url.Netlify
};
_browser.CoreWebView2InitializationCompleted += (sender, args) =>
Browser.CoreWebView2InitializationCompleted += (sender, args) =>
_serviceProvider
.GetRequiredService<ITopLevelExceptionHandler>()
.CatchUnhandled(() => OnInitialized(sender, args));
BrowserContainer.Child = _browser;
}
public bool IsBrowserInitialized => _browser?.IsInitialized ?? false;
public bool IsBrowserInitialized => Browser.IsInitialized;
public object BrowserElement => _browser!;
public object BrowserElement => Browser;
/// <inheritdoc/>
public void ExecuteScript(string script, CancellationToken cancellationToken)
public void ExecuteScript(string script)
{
if (_browser == null || !_browser.IsInitialized)
if (!Browser.IsInitialized)
{
throw new InvalidOperationException("Failed to execute script, Webview2 is not initialized yet");
throw new InvalidOperationException("Failed to execute script, Webview2 is not initialized yet.");
}
if (!_browser.CheckAccess())
{
ExecuteScriptDispatched(script, cancellationToken);
return;
}
_browser.ExecuteScriptAsync(script);
_revitTask.Run(() => Browser.ExecuteScriptAsync(script));
}
/// <inheritdoc/>
public void ExecuteScriptDispatched(string script, CancellationToken cancellationToken)
public void SendProgress(string script)
{
if (_browser == null || !_browser.IsInitialized)
if (!Browser.IsInitialized)
{
throw new InvalidOperationException("Failed to execute script, Webview2 is not initialized yet");
throw new InvalidOperationException("Failed to execute script, Webview2 is not initialized yet.");
}
//Intentionally using the dispatcher even from the main thread
//As it allows the UI to pump messages, and stay responsive
_browser.Dispatcher.Invoke(
() => _browser.ExecuteScriptAsync(script),
DispatcherPriority.Background,
cancellationToken
//always invoke even on the main thread because it's better somehow
Browser.Dispatcher.Invoke(
//fire and forget
() => Browser.ExecuteScriptAsync(script),
DispatcherPriority.Background
);
}
@@ -117,18 +74,11 @@ public sealed partial class RevitControlWebView : UserControl, IBrowserScriptExe
private void SetupBinding(IBinding binding)
{
binding.Parent.AssociateWithBinding(binding);
_browser!.CoreWebView2.AddHostObjectToScript(binding.Name, binding.Parent);
Browser.CoreWebView2.AddHostObjectToScript(binding.Name, binding.Parent);
}
public void ShowDevTools() => _browser?.CoreWebView2?.OpenDevToolsWindow();
public void ShowDevTools() => Browser.CoreWebView2.OpenDevToolsWindow();
//https://github.com/MicrosoftEdge/WebView2Feedback/issues/2161
public void Dispose()
{
if (_browser != null)
{
_browser.Dispatcher.Invoke(() => _browser.Dispose(), DispatcherPriority.Send);
_browser = null;
}
}
public void Dispose() => Browser.Dispatcher.Invoke(() => Browser.Dispose(), DispatcherPriority.Send);
}
@@ -166,8 +166,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -175,13 +175,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -233,20 +233,13 @@
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
"speckle.common.meshtriangulation": {
"type": "Project",
"dependencies": {
"LibTessDotNet": "[1.1.15, )",
"Speckle.DoubleNumerics": "[4.1.0, )"
}
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -269,23 +262,16 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.revit2026": {
"type": "Project",
"dependencies": {
"Speckle.Common.MeshTriangulation": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Revit.API": "[2026.0.0, )"
}
},
"LibTessDotNet": {
"type": "CentralTransitive",
"requested": "[1.1.15, )",
"resolved": "1.1.15",
"contentHash": "KuA7N3Nv/lIeawJdQBQJR6oqWD9KETHLbWzBqapwFs+Tby+R5I4crkKujKMm5bXcSuFZ8LNtflFQVadsWCbBjg=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -312,11 +298,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
},
"Speckle.Revit.API": {
@@ -1,3 +1,4 @@
using System.Runtime.InteropServices;
using System.Windows.Controls;
using System.Windows.Threading;
using Autodesk.Revit.UI;
@@ -13,50 +14,36 @@ public partial class CefSharpPanel : Page, Autodesk.Revit.UI.IDockablePaneProvid
InitializeComponent();
}
/// <inheritdoc/>
public void ExecuteScript(string script, CancellationToken cancellationToken)
public void ExecuteScript(string script)
{
if (!Browser.CheckAccess())
try
{
ExecuteScriptDispatched(script, cancellationToken);
return;
}
//avoid exceptions by checking if IBrowser is there
if (!Browser.IsBrowserInitialized || Browser.GetBrowser() is null)
{
return;
}
Browser.ExecuteScriptAsync(script);
}
/// <inheritdoc/>
public void ExecuteScriptDispatched(string script, CancellationToken cancellationToken)
{
if (Browser == null || !Browser.IsInitialized)
{
throw new InvalidOperationException("Failed to execute script, ChromiumWebBrowser is not initialized yet");
}
//Intentionally using the dispatcher even from the main thread
//As it allows the UI to pump messages, and stay responsive
Browser.Dispatcher.Invoke(
() =>
{
//avoid exceptions by checking if IBrowser is there
if (!Browser.IsBrowserInitialized || Browser.GetBrowser() is null)
Browser.Dispatcher.Invoke(
() =>
{
return;
}
//avoid exceptions by checking if IBrowser is there
if (!Browser.IsBrowserInitialized || Browser.GetBrowser() is null)
{
return;
}
Browser.ExecuteScriptAsync(script);
},
DispatcherPriority.Background,
cancellationToken
);
Browser.ExecuteScriptAsync(script);
},
DispatcherPriority.Background
);
}
catch (SEHException)
{
//do nothing as we can't control external components
}
catch (OperationCanceledException)
{
//do nothing, happens when closing Revit while a script is being executed
}
}
public void SendProgress(string script) => ExecuteScript(script);
public bool IsBrowserInitialized => Browser.IsBrowserInitialized;
public object BrowserElement => Browser;
@@ -44,7 +44,6 @@ public sealed class RevitReceiveBinding(
toHostSettingsManager.GetReferencePointSetting(card),
false,
true,
false,
false
)
);
@@ -1,4 +1,3 @@
using System.IO;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.ExtensibleStorage;
using Microsoft.Extensions.DependencyInjection;
@@ -25,7 +24,7 @@ namespace Speckle.Connectors.Revit.Bindings;
internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
{
private readonly RevitIdleManager _revitIdleManager;
private readonly IAppIdleManager _idleManager;
private readonly RevitContext _revitContext;
private readonly DocumentModelStore _store;
private readonly ICancellationManager _cancellationManager;
@@ -39,9 +38,6 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
private readonly LinkedModelHandler _linkedModelHandler;
private readonly IThreadContext _threadContext;
private readonly ISendOperationManagerFactory _sendOperationManagerFactory;
private bool _isDocChangedSubscribed;
private EventHandler<Autodesk.Revit.DB.Events.DocumentChangedEventArgs>? _documentChangedHandler;
private readonly ConnectorConfig _config;
/// <summary>
/// Used internally to aggregate the changed objects' id. Note we're using a concurrent dictionary here as the expiry check method is not thread safe, and this was causing problems. See:
@@ -52,7 +48,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
private ConcurrentHashSet<ElementId> ChangedObjectIds { get; set; } = new();
public RevitSendBinding(
RevitIdleManager revitIdleManager,
IAppIdleManager idleManager,
RevitContext revitContext,
DocumentModelStore store,
ICancellationManager cancellationManager,
@@ -66,12 +62,11 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
LinkedModelHandler linkedModelHandler,
IThreadContext threadContext,
IRevitTask revitTask,
ISendOperationManagerFactory sendOperationManagerFactory,
IConfigStore configStore
ISendOperationManagerFactory sendOperationManagerFactory
)
: base("sendBinding", bridge)
{
_revitIdleManager = revitIdleManager;
_idleManager = idleManager;
_revitContext = revitContext;
_store = store;
_cancellationManager = cancellationManager;
@@ -84,7 +79,6 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
_linkedModelHandler = linkedModelHandler;
_threadContext = threadContext;
_sendOperationManagerFactory = sendOperationManagerFactory;
_config = configStore.GetConnectorConfig();
Commands = new SendBindingUICommands(bridge);
// TODO expiry events
@@ -92,58 +86,12 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
revitTask.Run(() =>
{
// revitContext.UIApplication.NotNull().Application.DocumentChanged += (_, e) =>
// _topLevelExceptionHandler.CatchUnhandled(() => DocChangeHandler(e));
_documentChangedHandler = (_, e) => _topLevelExceptionHandler.CatchUnhandled(() => DocChangeHandler(e));
_store.ModelCardsChanged += (_, e) => OnModelCardsChanged(e);
revitContext.UIApplication.NotNull().Application.DocumentChanged += (_, e) =>
_topLevelExceptionHandler.CatchUnhandled(() => DocChangeHandler(e));
_store.DocumentChanged += (_, _) => topLevelExceptionHandler.FireAndForget(async () => await OnDocumentChanged());
});
}
private void OnModelCardsChanged(ModelCardsChangedEventArgs e)
{
if (
!_config.DocumentChangeListeningDisabled
&& e.ModelCards.Count > 0
&& e.ModelCards.Any(m => m.TypeDiscriminator == nameof(SenderModelCard))
)
{
SubscribeDocChanged();
}
else
{
UnsubscribeDocChanged();
}
}
private void SubscribeDocChanged()
{
if (_documentChangedHandler == null || _isDocChangedSubscribed)
{
return;
}
_threadContext.RunOnMain(() =>
{
_revitContext.UIApplication.NotNull().Application.DocumentChanged += _documentChangedHandler;
});
_isDocChangedSubscribed = true;
}
private void UnsubscribeDocChanged()
{
if (_documentChangedHandler == null || !_isDocChangedSubscribed)
{
return;
}
_threadContext.RunOnMain(() =>
{
_revitContext.UIApplication.NotNull().Application.DocumentChanged -= _documentChangedHandler;
});
_isDocChangedSubscribed = false;
}
public List<ISendFilter> GetSendFilters() =>
[
new RevitSelectionFilter { IsDefault = true },
@@ -157,8 +105,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
new SendReferencePointSetting(),
new SendParameterNullOrEmptyStringsSetting(),
new LinkedModelsSetting(),
new SendRebarsAsVolumetricSetting(),
new SendAreasAsMeshSetting()
new SendRebarsAsVolumetricSetting()
];
public void CancelSend(string modelCardId) => _cancellationManager.CancelOperation(modelCardId);
@@ -173,7 +120,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
throw new SpeckleException("No document is active for sending.");
}
using var manager = _sendOperationManagerFactory.Create();
var (fileName, fileBytes) = GetFileInfo(document);
await manager.Process<DocumentToConvert>(
Commands,
modelCardId,
@@ -186,31 +133,14 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
_toSpeckleSettingsManager.GetReferencePointSetting(document, card),
_toSpeckleSettingsManager.GetSendParameterNullOrEmptyStringsSetting(document, card),
_toSpeckleSettingsManager.GetLinkedModelsSetting(document, card),
_toSpeckleSettingsManager.GetSendRebarsAsVolumetric(document, card),
_toSpeckleSettingsManager.GetSendAreasAsMesh(document, card)
_toSpeckleSettingsManager.GetSendRebarsAsVolumetric(document, card)
)
);
},
async x => await RefreshElementsIdsOnSender(document, x.NotNull()),
fileName: fileName,
fileSizeBytes: fileBytes
async x => await RefreshElementsIdsOnSender(document, x.NotNull())
);
}
private static (string? fileName, long? fileBytes) GetFileInfo(Document document)
{
string fullPath = document.PathName;
if (File.Exists(document.PathName))
{
var fileInfo = new FileInfo(document.PathName);
return (fileInfo.Name, fileInfo.Length);
}
else
{
return (fullPath.Split('/').LastOrDefault(), null);
}
}
private async Task<List<DocumentToConvert>> RefreshElementsIdsOnSender(Document document, SenderModelCard modelCard)
{
if (modelCard.SendFilter.NotNull() is IRevitSendFilter viewFilter)
@@ -346,7 +276,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
if (addedElementIds.Count > 0)
{
_revitIdleManager.SubscribeToIdle(nameof(PostSetObjectIds), PostSetObjectIds);
_idleManager.SubscribeToIdle(nameof(PostSetObjectIds), PostSetObjectIds);
}
if (HaveUnitsChanged(doc))
@@ -366,8 +296,8 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
_sendConversionCache.EvictObjects(unpackedObjectIds);
}
_revitIdleManager.SubscribeToIdle(nameof(CheckFilterExpiration), CheckFilterExpiration);
_revitIdleManager.SubscribeToIdle(nameof(RunExpirationChecks), RunExpirationChecks);
_idleManager.SubscribeToIdle(nameof(CheckFilterExpiration), CheckFilterExpiration);
_idleManager.SubscribeToIdle(nameof(RunExpirationChecks), RunExpirationChecks);
}
// Keeps track of doc and current units
@@ -1,6 +1,5 @@
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Settings;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk.Common;
@@ -18,32 +17,27 @@ internal sealed class SelectionBinding : RevitBaseBinding, ISelectionBinding, ID
public SelectionBinding(
RevitContext revitContext,
IBrowserBridge parent,
RevitIdleManager idleManager,
#if REVIT2022
IAppIdleManager idleManager,
ITopLevelExceptionHandler topLevelExceptionHandler,
#endif
IRevitTask revitTask,
IConfigStore configStore
IRevitTask revitTask
)
: base("selectionBinding", parent)
{
_revitContext = revitContext;
if (!configStore.GetConnectorConfig().SelectionChangeListeningDisabled)
{
#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.Start();
// 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.Start();
#else
revitTask.Run(
() =>
_revitContext.UIApplication.NotNull().SelectionChanged += (_, _) =>
idleManager.SubscribeToIdle(nameof(OnSelectionChanged), OnSelectionChanged)
);
revitTask.Run(
() =>
_revitContext.UIApplication.NotNull().SelectionChanged += (_, _) =>
idleManager.SubscribeToIdle(nameof(OnSelectionChanged), OnSelectionChanged)
);
#endif
}
}
private void OnSelectionChanged()
@@ -48,12 +48,11 @@ public static class ServiceRegistration
serviceCollection.AddSingleton<IBinding, SelectionBinding>();
serviceCollection.AddSingleton<IBinding, RevitSendBinding>();
serviceCollection.AddSingleton<IBinding, RevitReceiveBinding>();
serviceCollection.AddSingleton<RevitIdleManager>();
serviceCollection.AddSingleton<IBinding>(sp => sp.GetRequiredService<IBasicConnectorBinding>());
serviceCollection.AddSingleton<IBasicConnectorBinding, BasicConnectorBindingRevit>();
// serviceCollection.AddSingleton<IAppIdleManager, RevitIdleManager>();
serviceCollection.AddSingleton<IAppIdleManager, RevitIdleManager>();
// send operation and dependencies
serviceCollection.AddScoped<SendOperation<DocumentToConvert>>();
@@ -72,7 +71,6 @@ public static class ServiceRegistration
serviceCollection.AddScoped<ITransactionManager, TransactionManager>();
serviceCollection.AddScoped<RevitGroupBaker>();
serviceCollection.AddScoped<RevitMaterialBaker>();
serviceCollection.AddScoped<RevitViewBaker>();
serviceCollection.AddScoped<RevitViewManager>();
serviceCollection.AddSingleton<RevitUtils>();
serviceCollection.AddSingleton<IFailuresPreprocessor, HideWarningsFailuresPreprocessor>();
@@ -17,16 +17,13 @@ namespace Speckle.Connectors.Revit.HostApp;
internal sealed class RevitDocumentStore : DocumentModelStore
{
private readonly ILogger<RevitDocumentStore> _logger;
//private readonly IAppIdleManager _idleManager;
private readonly RevitIdleManager _idleManager;
private readonly IAppIdleManager _idleManager;
private readonly RevitContext _revitContext;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly ISqLiteJsonCacheManager _jsonCacheManager;
public RevitDocumentStore(
//IAppIdleManager idleManager,
RevitIdleManager idleManager,
IAppIdleManager idleManager,
RevitContext revitContext,
IJsonSerializer jsonSerializer,
ITopLevelExceptionHandler topLevelExceptionHandler,
@@ -37,7 +34,6 @@ internal sealed class RevitDocumentStore : DocumentModelStore
: base(logger, jsonSerializer)
{
_jsonCacheManager = jsonCacheManagerFactory.CreateForUser("ConnectorsFileData");
//_idleManager = idleManager;
_idleManager = idleManager;
_revitContext = revitContext;
_topLevelExceptionHandler = topLevelExceptionHandler;
@@ -5,6 +5,7 @@ using Speckle.Converters.Common;
using Speckle.Converters.RevitShared.Settings;
using Speckle.Objects.Other;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Extensions;
using Speckle.Sdk.Models.GraphTraversal;
@@ -31,24 +32,6 @@ public class RevitMaterialBaker
_converterSettings = converterSettings;
}
private ElementId? FindExistingMaterialByName(string? materialName)
{
if (string.IsNullOrWhiteSpace(materialName))
{
return null;
}
string sanitizedName = _revitUtils.RemoveInvalidChars(materialName!);
using var collector = new FilteredElementCollector(_converterSettings.Current.Document);
var existingMaterial = collector
.OfClass(typeof(Material))
.Cast<Material>()
.FirstOrDefault(m => string.Equals(m.Name, sanitizedName, StringComparison.OrdinalIgnoreCase));
return existingMaterial?.Id;
}
/// <summary>
/// Checks the every atomic object has render material or not, if not it tries to find it from its layer tree and mutates
/// its render material proxy objects list with the traversal current. It will also map displayable objects' display values to their
@@ -123,9 +106,11 @@ public class RevitMaterialBaker
/// Will bake render materials in the revit document.
/// </summary>
/// <param name="speckleRenderMaterialProxies"></param>
/// <param name="baseLayerName"></param>
/// <returns></returns>
public Dictionary<string, ElementId> BakeMaterials(
IReadOnlyCollection<RenderMaterialProxy> speckleRenderMaterialProxies
IReadOnlyCollection<RenderMaterialProxy> speckleRenderMaterialProxies,
string baseLayerName
)
{
Dictionary<string, ElementId> objectIdAndMaterialIndexMap = new();
@@ -135,42 +120,27 @@ public class RevitMaterialBaker
try
{
// first try to match existing material by name
ElementId? existingMaterialId = FindExistingMaterialByName(speckleRenderMaterial.name);
// all values assumed to be on the 0 - 1 scale need to pass through this validation and logging (if assumption wrong)
double roughness = ClampToUnitRange(speckleRenderMaterial.roughness, "roughness", speckleRenderMaterial.name);
double opacity = ClampToUnitRange(speckleRenderMaterial.opacity, "opacity", speckleRenderMaterial.name);
double metalness = ClampToUnitRange(speckleRenderMaterial.metalness, "metalness", speckleRenderMaterial.name);
ElementId materialIdToUse;
var diffuse = System.Drawing.Color.FromArgb(speckleRenderMaterial.diffuse);
double transparency = 1 - opacity;
double smoothness = 1 - roughness;
string materialId = speckleRenderMaterial.applicationId ?? speckleRenderMaterial.id.NotNull();
string matName = _revitUtils.RemoveInvalidChars($"{speckleRenderMaterial.name}-({materialId})-{baseLayerName}");
if (existingMaterialId != null)
{
// Use existing material
materialIdToUse = existingMaterialId;
}
else
{
// create new material
// all values assumed to be on the 0 - 1 scale need to pass through this validation and logging (if assumption wrong)
double roughness = ClampToUnitRange(speckleRenderMaterial.roughness, "roughness", speckleRenderMaterial.name);
double opacity = ClampToUnitRange(speckleRenderMaterial.opacity, "opacity", speckleRenderMaterial.name);
double metalness = ClampToUnitRange(speckleRenderMaterial.metalness, "metalness", speckleRenderMaterial.name);
var diffuse = System.Drawing.Color.FromArgb(speckleRenderMaterial.diffuse);
double transparency = 1 - opacity;
double smoothness = 1 - roughness;
string matName = _revitUtils.RemoveInvalidChars($"{speckleRenderMaterial.name}");
var newMaterialId = Material.Create(_converterSettings.Current.Document, matName);
var revitMaterial = (Material)_converterSettings.Current.Document.GetElement(newMaterialId);
revitMaterial.Color = new Color(diffuse.R, diffuse.G, diffuse.B);
revitMaterial.Transparency = (int)(transparency * 100);
revitMaterial.Shininess = (int)(metalness * 128);
revitMaterial.Smoothness = (int)(smoothness * 100);
materialIdToUse = revitMaterial.Id;
}
var newMaterialId = Material.Create(_converterSettings.Current.Document, matName);
var revitMaterial = (Material)_converterSettings.Current.Document.GetElement(newMaterialId);
revitMaterial.Color = new Color(diffuse.R, diffuse.G, diffuse.B);
revitMaterial.Transparency = (int)(transparency * 100);
revitMaterial.Shininess = (int)(metalness * 128);
revitMaterial.Smoothness = (int)(smoothness * 128);
foreach (var objectId in proxy.objects)
{
objectIdAndMaterialIndexMap[objectId] = materialIdToUse;
objectIdAndMaterialIndexMap[objectId] = revitMaterial.Id;
}
}
catch (Exception ex) when (!ex.IsFatal())
@@ -1,156 +0,0 @@
using Autodesk.Revit.DB;
using Microsoft.Extensions.Logging;
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Converters.RevitShared.Settings;
using Speckle.Objects.Other;
using Speckle.Sdk;
namespace Speckle.Connectors.Revit.HostApp;
/// <summary>
/// Utility class that creates View3D elements from Camera objects during receive.
/// </summary>
public class RevitViewBaker
{
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
private readonly ILogger<RevitViewBaker> _logger;
private readonly ITypedConverter<Speckle.Objects.Geometry.Point, XYZ> _pointConverter;
private readonly ITypedConverter<Speckle.Objects.Geometry.Vector, XYZ> _vectorConverter;
public RevitViewBaker(
IConverterSettingsStore<RevitConversionSettings> converterSettings,
ILogger<RevitViewBaker> logger,
ITypedConverter<Speckle.Objects.Geometry.Point, XYZ> pointConverter,
ITypedConverter<Speckle.Objects.Geometry.Vector, XYZ> vectorConverter
)
{
_converterSettings = converterSettings;
_logger = logger;
_pointConverter = pointConverter;
_vectorConverter = vectorConverter;
}
// Characters that are not allowed in Revit view names
private readonly char[] _invalidViewNameChars = ['{', '}', '[', ']', '|', ';', '<', '>', '?', '`', '~', '\\', ':'];
/// <summary>
/// Bakes Camera objects as View3D elements in Revit.
/// </summary>
public void BakeViews(IReadOnlyCollection<Camera> cameras)
{
if (cameras.Count == 0)
{
return;
}
foreach (var camera in cameras)
{
if (string.IsNullOrWhiteSpace(camera.name))
{
continue;
}
var restoredName = RestoreViewName(camera.name);
if (string.IsNullOrWhiteSpace(restoredName))
{
continue;
}
var existingView = FindViewByName(restoredName);
try
{
if (existingView != null)
{
UpdatePerspectiveView(existingView, camera);
}
else
{
CreatePerspectiveView(camera, restoredName);
}
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogError(ex, "Failed to create view '{ViewName}'", restoredName);
}
}
}
/// <summary>
/// Sanitizes the view name by removing invalid characters.
/// </summary>
private string RestoreViewName(string name)
{
var restored = name;
foreach (var c in _invalidViewNameChars)
{
restored = restored.Replace(c.ToString(), string.Empty);
}
return restored.Trim();
}
private View3D? FindViewByName(string name)
{
using var collector = new FilteredElementCollector(_converterSettings.Current.Document);
return collector
.OfClass(typeof(View3D))
.Cast<View3D>()
.FirstOrDefault(v => !v.IsTemplate && string.Equals(v.Name, name, StringComparison.Ordinal));
}
private void UpdatePerspectiveView(View3D view3D, Camera camera)
{
var eyePosition = _pointConverter.Convert(camera.position);
var forwardDirection = _vectorConverter.Convert(camera.forward).Normalize();
var upDirection = _vectorConverter.Convert(camera.up).Normalize();
var orientation = new ViewOrientation3D(eyePosition, upDirection, forwardDirection);
view3D.SetOrientation(orientation);
}
private void CreatePerspectiveView(Camera camera, string viewName)
{
var document = _converterSettings.Current.Document;
// Get ViewFamilyType for 3D views
using var collector = new FilteredElementCollector(document);
var viewFamilyType = collector
.OfClass(typeof(ViewFamilyType))
.Cast<ViewFamilyType>()
.FirstOrDefault(vft => vft.ViewFamily == ViewFamily.ThreeDimensional);
if (viewFamilyType == null)
{
_logger.LogError("Could not find a 3D ViewFamilyType to create view '{ViewName}'", viewName);
return;
}
// Create perspective view (View3D is a document element, not disposable) - low happiness level
#pragma warning disable CA2000
var view3D = View3D.CreatePerspective(document, viewFamilyType.Id);
#pragma warning restore CA2000
// Convert camera position, forward, and up vectors
var eyePosition = _pointConverter.Convert(camera.position);
var forwardDirection = _vectorConverter.Convert(camera.forward).Normalize();
var upDirection = _vectorConverter.Convert(camera.up).Normalize();
var orientation = new ViewOrientation3D(eyePosition, upDirection, forwardDirection);
view3D.SetOrientation(orientation);
view3D.Name = viewName;
// Set display style to Shaded (looks better than default wireframe)
view3D.DisplayStyle = DisplayStyle.Shading;
// Disable far clipping so depth is infinite
var farClipParam = view3D.get_Parameter(BuiltInParameter.VIEWER_BOUND_ACTIVE_FAR);
if (farClipParam != null && !farClipParam.IsReadOnly)
{
farClipParam.Set(0);
}
}
}
@@ -21,7 +21,6 @@ using Speckle.Sdk.Common;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Instances;
using Transform = Speckle.Objects.Other.Transform;
namespace Speckle.Connectors.Revit.Operations.Receive;
@@ -34,21 +33,17 @@ public sealed class RevitHostObjectBuilder(
ILocalToGlobalUnpacker localToGlobalUnpacker,
RevitGroupBaker groupManager,
RevitMaterialBaker materialBaker,
RevitViewBaker viewBaker,
RootObjectUnpacker rootObjectUnpacker,
ILogger<RevitHostObjectBuilder> logger,
IThreadContext threadContext,
RevitToHostCacheSingleton revitToHostCacheSingleton,
ITypedConverter<
(Base atomicObject, IReadOnlyCollection<Matrix4x4> matrix, DataObject? parentDataObject),
(Base atomicObject, IReadOnlyCollection<Matrix4x4> matrix),
DirectShape
> localToGlobalDirectShapeConverter,
IReceiveConversionHandler conversionHandler
) : IHostObjectBuilder, IDisposable
{
// Maps atomic object applicationId -> parent DataObject
private readonly Dictionary<string, DataObject> _atomicObjectToParentDataObject = new();
public Task<HostObjectBuilderResult> Build(
Base rootObject,
string projectName,
@@ -107,9 +102,6 @@ public sealed class RevitHostObjectBuilder(
unpackedRoot.ObjectsToConvert.ToList()
);
// Register DataObjects with InstanceProxy displayValues
RegisterDataObjectsWithInstanceProxies(unpackedRoot);
// NOTE: below is 💩... https://github.com/specklesystems/speckle-sharp-connectors/pull/813 broke sketchup to revit workflow
// ids were modified to fix receiving instances [CNX-1707](https://linear.app/speckle/issue/CNX-1707/revit-curves-and-meshes-in-blocks-come-as-duplicated)
// but we then broke sketchup to revit because applicationIds in proxies didn't match modified application ids which cam from #813 hack
@@ -184,15 +176,12 @@ public sealed class RevitHostObjectBuilder(
}
}
// Update DataObject lookup IDs
UpdateAtomicObjectLookupWithModifiedIds(originalToModifiedIds);
// 2 - Bake materials (now with the updated IDs)
if (unpackedRoot.RenderMaterialProxies != null)
{
transactionManager.StartTransaction(true, "Baking materials");
materialBaker.MapLayersRenderMaterials(unpackedRoot);
var map = materialBaker.BakeMaterials(unpackedRoot.RenderMaterialProxies);
var map = materialBaker.BakeMaterials(unpackedRoot.RenderMaterialProxies, baseGroupName);
foreach (var kvp in map)
{
revitToHostCacheSingleton.MaterialsByObjectId.Add(kvp.Key, kvp.Value);
@@ -200,14 +189,6 @@ public sealed class RevitHostObjectBuilder(
transactionManager.CommitTransaction();
}
// 2.1 - Bake views
if (unpackedRoot.Cameras is not null)
{
transactionManager.StartTransaction(true, "Baking views");
viewBaker.BakeViews(unpackedRoot.Cameras);
transactionManager.CommitTransaction();
}
// 3 - Bake objects
(
HostObjectBuilderResult builderResult,
@@ -253,87 +234,6 @@ public sealed class RevitHostObjectBuilder(
return conversionResults.builderResult;
}
/// <summary>
/// Registers DataObjects that have InstanceProxy displayValues and builds the lookup.
/// </summary>
private void RegisterDataObjectsWithInstanceProxies(RootObjectUnpackerResult unpackedRoot)
{
var definitionToDataObject = new Dictionary<string, DataObject>();
foreach (var tc in unpackedRoot.ObjectsToConvert)
{
if (tc.Current is DataObject dataObject)
{
var instanceProxies = dataObject.displayValue.OfType<InstanceProxy>().ToList();
if (instanceProxies.Count > 0)
{
foreach (var ip in instanceProxies)
{
definitionToDataObject[ip.definitionId] = dataObject;
}
}
}
}
// Build lookup: definition object applicationId -> parent DataObject
_atomicObjectToParentDataObject.Clear();
if (unpackedRoot.DefinitionProxies is not null)
{
foreach (var defProxy in unpackedRoot.DefinitionProxies)
{
if (
defProxy.applicationId is not null
&& definitionToDataObject.TryGetValue(defProxy.applicationId, out var parentDataObject)
)
{
foreach (var objectId in defProxy.objects)
{
_atomicObjectToParentDataObject[objectId] = parentDataObject;
}
}
else
{
logger.LogError(
"Could not find parent DataObject for DefinitionProxy {ApplicationId}",
defProxy.applicationId
);
}
}
}
}
/// <summary>
/// Updates the atomic object lookup with modified IDs
/// </summary>
private void UpdateAtomicObjectLookupWithModifiedIds(Dictionary<string, List<string>> originalToModifiedIds)
{
// Build updated entries first to avoid modifying collection during iteration
var entriesToAdd = new List<KeyValuePair<string, DataObject>>();
var keysToRemove = new List<string>();
foreach (var kvp in _atomicObjectToParentDataObject)
{
if (originalToModifiedIds.TryGetValue(kvp.Key, out var modifiedIds))
{
keysToRemove.Add(kvp.Key);
foreach (var modifiedId in modifiedIds)
{
entriesToAdd.Add(new(modifiedId, kvp.Value));
}
}
}
foreach (var key in keysToRemove)
{
_atomicObjectToParentDataObject.Remove(key);
}
foreach (var entry in entriesToAdd)
{
_atomicObjectToParentDataObject[entry.Key] = entry.Value;
}
}
private Autodesk.Revit.DB.Transform? CalculateNewTransform(
Autodesk.Revit.DB.Transform? receiveTransform,
Autodesk.Revit.DB.Transform? rootTransform
@@ -378,17 +278,9 @@ public sealed class RevitHostObjectBuilder(
onOperationProgressed.Report(new("Converting", (double)++count / localToGlobalMaps.Count));
if (result is DirectShapeDefinitionWrapper)
{
// Look up parent DataObject for this atomic object (handles InstanceProxy displayValue)
var atomicId = localToGlobalMap.AtomicObject.applicationId;
DataObject? parentDataObject = null;
if (atomicId is not null)
{
_atomicObjectToParentDataObject.TryGetValue(atomicId, out parentDataObject);
}
// direct shape creation happens here
DirectShape directShapes = localToGlobalDirectShapeConverter.Convert(
(localToGlobalMap.AtomicObject, localToGlobalMap.Matrix, parentDataObject)
(localToGlobalMap.AtomicObject, localToGlobalMap.Matrix)
);
bakedObjectIds.Add(directShapes.UniqueId);
@@ -459,7 +351,6 @@ public sealed class RevitHostObjectBuilder(
DirectShapeLibrary.GetDirectShapeLibrary(converterSettings.Current.Document).Reset(); // Note: this needs to be cleared, as it is being used in the converter
revitToHostCacheSingleton.MaterialsByObjectId.Clear(); // Massive hack!
_atomicObjectToParentDataObject.Clear();
groupManager.PurgeGroups(baseGroupName);
materialBaker.PurgeMaterials(baseGroupName);
}
@@ -81,11 +81,7 @@ public class RevitCategoriesFilter : DiscriminatedObject, ISendFilter, IRevitSen
foreach (Category category in _doc.Settings.Categories)
{
if (SupportedCategoriesUtils.IsSupportedCategory(category)
#if REVIT2023_OR_GREATER
&& category.BuiltInCategory != BuiltInCategory.INVALID
#endif
)
if (SupportedCategoriesUtils.IsSupportedCategory(category))
{
categories.Add(new CategoryData(category.Name, category.Id.ToString()));
}
@@ -1,4 +1,3 @@
using System.Diagnostics.CodeAnalysis;
using Autodesk.Revit.DB;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Builders;
@@ -8,7 +7,6 @@ using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Exceptions;
using Speckle.Connectors.DUI.Settings;
using Speckle.Connectors.Revit.HostApp;
using Speckle.Converters.Common;
using Speckle.Converters.RevitShared.Helpers;
@@ -31,8 +29,7 @@ public class RevitRootObjectBuilder(
SendCollectionManager sendCollectionManager,
ILogger<RevitRootObjectBuilder> logger,
RevitToSpeckleCacheSingleton revitToSpeckleCacheSingleton,
LinkedModelHandler linkedModelHandler,
IConfigStore configStore
LinkedModelHandler linkedModelHandler
) : IRootObjectBuilder<DocumentToConvert>
{
public Task<RootObjectBuilderResult> Build(
@@ -45,8 +42,11 @@ public class RevitRootObjectBuilder(
() => Task.FromResult(BuildSync(documentElementContexts, projectId, onOperationProgressed, ct))
);
[SuppressMessage("Maintainability", "CA1506:Avoid excessive class coupling")]
#pragma warning disable CA1506
#pragma warning disable CA1502
private RootObjectBuilderResult BuildSync(
#pragma warning restore CA1506
#pragma warning restore CA1502
IReadOnlyList<DocumentToConvert> documentElementContexts,
string projectId,
IProgress<CardProgress> onOperationProgressed,
@@ -60,6 +60,9 @@ public class RevitRootObjectBuilder(
throw new SpeckleException("Family Environment documents are not supported.");
}
// create a new send pipeline
using var sendPipeline = new Speckle.Sdk.Pipeline.Send();
// init the root
Collection rootObject =
new() { name = converterSettings.Current.Document.PathName.Split('\\').Last().Split('.').First() };
@@ -138,8 +141,6 @@ public class RevitRootObjectBuilder(
var cacheHitCount = 0;
var skippedObjectCount = 0;
var config = configStore.GetConnectorConfig();
foreach (var atomicObjectByDocumentAndTransform in atomicObjectsByDocumentAndTransform)
{
string? modelDisplayName = null;
@@ -190,14 +191,12 @@ public class RevitRootObjectBuilder(
// non-transformed elements can safely rely on cache
// TODO: Potential here to transform cached objects and NOT reconvert,
// TODO: we wont do !hasTransform here, and re-set application id before this
if (
!hasTransform
&& !config.DocumentChangeListeningDisabled //This is experimental
&& sendConversionCache.TryGetValue(projectId, applicationId, out ObjectReference? value)
)
bool wasCached = false;
if (!hasTransform && sendConversionCache.TryGetValue(projectId, applicationId, out ObjectReference? value))
{
// TODO: cahce hit
converted = value;
wasCached = true;
cacheHitCount++;
}
// not in cache means we convert
@@ -216,6 +215,12 @@ public class RevitRootObjectBuilder(
converted.applicationId = applicationId;
}
var reference = sendPipeline.Process(converted).Result; // .Wait(cancellationToken);//.ConfigureAwait(false);
if (!wasCached)
{
sendConversionCache.AppendSendResult(projectId, applicationId, reference);
}
var collection = sendCollectionManager.GetAndCreateObjectHostCollection(
revitElement,
rootObject,
@@ -223,7 +228,7 @@ public class RevitRootObjectBuilder(
modelDisplayName
);
collection.elements.Add(converted);
collection.elements.Add(reference);
results.Add(new(Status.SUCCESS, applicationId, sourceType, converted));
}
catch (Exception ex) when (!ex.IsFatal())
@@ -264,13 +269,20 @@ public class RevitRootObjectBuilder(
rootObject[ProxyKeys.INSTANCE_DEFINITION] = revitToSpeckleCacheSingleton.GetInstanceDefinitionProxiesForObjects(
idsAndSubElementIds
);
rootObject.elements.Add(
new Collection()
{
elements = revitToSpeckleCacheSingleton.GetBaseObjectsForObjects(idsAndSubElementIds),
name = "definitionGeometry"
}
);
// NOTE: i might be overdoing things in here, but tldr:
// - all instance objects (meshes) are processed individually
// - process their collection individually, and then attach it to the root collection
// we could, theoretically, just process the collection as a whole (but it can be big?)
// note/ask: do these need to go in the conversion cache? or not?
var instanceObjects = revitToSpeckleCacheSingleton.GetBaseObjectsForObjects(idsAndSubElementIds);
var instanceReferences = new Collection("revitInstancedObjects");
foreach (var instanceObject in instanceObjects)
{
var referenceInstanceObject = sendPipeline.Process(instanceObject).Result;
instanceReferences.elements.Add(referenceInstanceObject);
}
var instanceReferenceCollection = sendPipeline.Process(instanceReferences).Result;
rootObject.elements.Add(instanceReferenceCollection);
// STEP 6: Unpack all other objects to attach to root collection
List<Objects.Other.Camera> views = viewUnpacker.Unpack(converterSettings.Current.Document);
@@ -289,6 +301,10 @@ public class RevitRootObjectBuilder(
rootObject[RootKeys.REFERENCE_POINT_TRANSFORM] = transformMatrix;
}
return new RootObjectBuilderResult(rootObject, results);
// NOTE: could be
sendPipeline.Process(rootObject).Wait(cancellationToken);
sendPipeline.WaitForUpload().Wait(cancellationToken);
return new RootObjectBuilderResult(new Collection() { name = "ignore" }, results);
}
}
@@ -1,15 +0,0 @@
using Speckle.Connectors.DUI.Settings;
namespace Speckle.Connectors.Revit.Operations.Send.Settings;
public class SendAreasAsMeshSetting(bool value = SendAreasAsMeshSetting.DEFAULT_VALUE) : ICardSetting
{
public const string SETTING_ID = "sendAreasAsMesh";
public const bool DEFAULT_VALUE = false;
public string? Id { get; set; } = SETTING_ID;
public string? Title { get; set; } = "Send Areas As Mesh";
public string? Type { get; set; } = "boolean";
public object? Value { get; set; } = value;
public List<string>? Enum { get; set; }
}
@@ -22,7 +22,6 @@ public class ToSpeckleSettingsManager(
private readonly Dictionary<string, bool?> _sendNullParamsCache = [];
private readonly Dictionary<string, bool?> _sendLinkedModelsCache = [];
private readonly Dictionary<string, bool?> _sendRebarsAsVolumetricCache = [];
private readonly Dictionary<string, bool?> _sendAreasAsMeshCache = [];
public DetailLevelType GetDetailLevelSetting(Document document, SenderModelCard modelCard)
{
@@ -132,16 +131,6 @@ public class ToSpeckleSettingsManager(
"Send rebars as volumetric"
);
public bool GetSendAreasAsMesh(Document document, SenderModelCard modelCard) =>
GetBooleanSettingWithCache(
document,
SendAreasAsMeshSetting.SETTING_ID,
SendAreasAsMeshSetting.DEFAULT_VALUE,
modelCard,
_sendAreasAsMeshCache,
"Send areas as mesh"
);
/// <summary>
/// Helper method to handle boolean settings with caching and logging
/// </summary>
@@ -6,106 +6,38 @@ using Speckle.Sdk.Common;
namespace Speckle.Connectors.Revit.Plugin;
/// <remarks>
/// Please do NOT try and refactor this class.
/// Whether it's to try and generalize with the <see cref="IdleCallManager"/> class
/// or to unnecessary try and make this class thread safe.
/// This class is a simple singleton, targeted to a Revit's host app requirements
/// where everything happens on the main thread, and we can avoid overly complex threading/thread-safety.
///
/// Previous refactors with good intention have lead to poor debugging experiences, over-engineered threading,
/// and low confidence in the reliability.
/// </remarks>
/// should be registered as singleton
public class RevitIdleManager(RevitContext revitContext, ITopLevelExceptionHandler topLevelExceptionHandler)
public sealed class RevitIdleManager : AppIdleManager
{
private readonly UIApplication _uiApplication = revitContext.UIApplication.NotNull();
private readonly UIApplication _uiApplication;
private readonly IIdleCallManager _idleCallManager;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly Dictionary<string, Func<Task>> _calls = new();
private bool _hasSubscribed;
private event EventHandler<IdlingEventArgs>? OnIdle;
private bool _isExecutingIdle;
/// <summary>
/// Defers the invocation of an <paramref name="action"/> until next Revit idle tick (deduped by name).
/// The <paramref name="action"/> will be called only once.
/// </summary>
/// <param name="name">A key that prevents enqueuing duplicate events</param>
/// <param name="action">The action to be invoked</param>
/// <example>
/// Some events in host app are triggered many times, we might get 10x per object
/// Making this more like a deferred action, so we don't update the UI many times
/// </example>
/// <remarks>
/// This function must be called on the main thread
/// </remarks>
public void SubscribeToIdle(string name, Action action)
public RevitIdleManager(
RevitContext revitContext,
IIdleCallManager idleCallManager,
ITopLevelExceptionHandler topLevelExceptionHandler,
IRevitTask revitTask
)
: base(idleCallManager)
{
SubscribeToIdle(
name,
() =>
{
action.Invoke();
return Task.CompletedTask;
}
_topLevelExceptionHandler = topLevelExceptionHandler;
_uiApplication = revitContext.UIApplication.NotNull();
_idleCallManager = idleCallManager;
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
);
}
/// <inheritdoc cref="SubscribeToIdle(string, Action)"/>
public void SubscribeToIdle(string name, Func<Task> action)
protected override void AddEvent()
{
if (_isExecutingIdle)
_topLevelExceptionHandler.CatchUnhandled(() =>
{
//Either, you're trying to SubscribeToIdle from not the main thread (don't do this)
//OR, an OnIdle event handler is calling SubscribeToIdle (re-entry), check the call stack
throw new InvalidOperationException("SubscribeToIdle called while already executing idle events");
}
_calls[name] = action;
if (_hasSubscribed)
{
return;
}
_hasSubscribed = true;
_uiApplication.Idling += RevitAppOnIdle;
}
private void RevitAppOnIdle(object? sender, IdlingEventArgs e)
{
topLevelExceptionHandler.CatchUnhandled(() =>
{
if (_isExecutingIdle)
{
//Either, you're trying to SubscribeToIdle from not the main thread (don't do this)
//OR, an OnIdle event handler is calling SubscribeToIdle (re-entry), check the call stack... avoid this
throw new InvalidOperationException("SubscribeToIdle called while already executing idle events");
}
_isExecutingIdle = true;
try
{
try
{
foreach (KeyValuePair<string, Func<Task>> kvp in _calls)
{
topLevelExceptionHandler.FireAndForget(kvp.Value.Invoke);
}
}
finally
{
_calls.Clear();
}
}
finally
{
_uiApplication.Idling -= RevitAppOnIdle;
_isExecutingIdle = false;
// setting last will delay entering re-subscription
_hasSubscribed = false;
}
OnIdle += RevitAppOnIdle;
});
}
private void RevitAppOnIdle(object? sender, IdlingEventArgs e) =>
_idleCallManager.AppOnIdle(() => OnIdle -= RevitAppOnIdle);
}
@@ -24,7 +24,6 @@
<Compile Include="$(MSBuildThisFileDirectory)HostApp\LevelUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\LinkedModelHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitMaterialBaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitViewBaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\ViewUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\SupportedCategoriesUtils.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitViewManager.cs" />
@@ -49,16 +48,15 @@
<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\SendAreasAsMeshSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\SendRebarsAsVolumetricSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\ToSpeckleSettingsManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\SendReferencePointSetting.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\RevitIdleManager.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" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\RevitCefPlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\SpeckleRevitTaskException.cs" />
@@ -204,8 +204,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -215,13 +215,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -347,7 +347,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.logging": {
@@ -357,7 +357,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.rhino7": {
@@ -402,11 +402,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
}
@@ -204,8 +204,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -215,13 +215,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -347,7 +347,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.logging": {
@@ -357,7 +357,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.rhino8": {
@@ -401,11 +401,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
}
@@ -1,311 +0,0 @@
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.Collections;
namespace Speckle.Connectors.GrasshopperShared.Components.Collections;
/// <summary>
/// Creates collections by matching name tree structure to elements tree structure.
/// Each branch in the names tree corresponds to the same-path branch in the elements tree.
/// </summary>
[Guid("7E8F9A1B-2C3D-4E5F-6A7B-8C9D0E1F2A3B")]
public class CollectionsByName : GH_Component
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_collections_create; // TODO: Update to specific icon if available
public override GH_Exposure Exposure => GH_Exposure.primary;
public CollectionsByName()
: base(
"Collections by Name",
"CbN",
"Creates collections by matching name tree structure to objects tree structure. Each branch in the names tree corresponds to the same-path branch in the objects tree.",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.COLLECTIONS
) { }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddTextParameter(
"Names",
"N",
"Collection names (tree structure must match Objects tree structure)",
GH_ParamAccess.tree
);
pManager.AddGenericParameter(
"Objects",
"O",
"Objects to group into collections (tree structure must match Names tree structure)",
GH_ParamAccess.tree
);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager) =>
pManager.AddParameter(
new SpeckleCollectionParam(),
"Collection",
"C",
"Root collection containing named sub-collections",
GH_ParamAccess.item
);
protected override void SolveInstance(IGH_DataAccess da)
{
// access the tree data directly from parameters
var namesParam = Params.Input[0];
var elementsParam = Params.Input[1];
var namesTree = namesParam.VolatileData;
var elementsTree = elementsParam.VolatileData;
// validate that both inputs have data
if (namesTree.IsEmpty)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Names tree is empty");
return;
}
if (elementsTree.IsEmpty)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Objects tree is empty");
return;
}
// validate tree structures match exactly
if (!TreeStructuresMatch(namesTree, elementsTree))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Tree structures and topologies must match exactly");
return;
}
// create root collection
var rootCollection = CollectionHelpers.CreateRootCollection(InstanceGuid.ToString());
// process each path
foreach (var path in namesTree.Paths)
{
var nameBranch = namesTree.get_Branch(path);
var elementsBranch = elementsTree.get_Branch(path);
// validate name branch - throw if empty
if (nameBranch.Count == 0)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Name branch at path {path} is empty");
return;
}
// validate name branch - just warn if multiple, use first
if (nameBranch.Count > 1)
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
$"Name branch at path {path} has {nameBranch.Count} names - using first name only"
);
}
// get the collection name
string collectionName = GetCollectionName(nameBranch[0]);
if (string.IsNullOrWhiteSpace(collectionName))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Invalid collection name at path {path}");
return;
}
// skip empty element branches with warning
if (elementsBranch.Count == 0)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Skipping empty elements branch at path {path}");
continue;
}
// parse nested collection path (e.g. parent::child)
var collectionNames = collectionName.Split(
new[] { Constants.LAYER_PATH_DELIMITER },
StringSplitOptions.RemoveEmptyEntries
);
// create or get nested collection structure
var targetCollection = GetOrCreateNestedCollection(rootCollection, collectionNames, elementsBranch, path);
// add elements to deepest collection
AddElementsToCollection(targetCollection, elementsBranch, path);
}
// validate collection has content
if (rootCollection.Elements.Count == 0)
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
"Collection contains no valid geometry. All branches were empty or contained unsupported types."
);
return;
}
// validate for duplicate application IDs (following CreateCollection pattern)
if (CollectionHelpers.HasDuplicateApplicationIds(rootCollection))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "The same object(s) cannot appear in multiple collections");
return;
}
da.SetData(0, new SpeckleCollectionWrapperGoo(rootCollection));
}
/// <summary>
/// Validates that two tree structures have exactly matching paths
/// </summary>
private bool TreeStructuresMatch(
Grasshopper.Kernel.Data.IGH_Structure namesTree,
Grasshopper.Kernel.Data.IGH_Structure elementsTree
)
{
if (namesTree.PathCount != elementsTree.PathCount)
{
return false;
}
// check that all paths match exactly
var namePaths = namesTree.Paths.ToList();
var elementPaths = elementsTree.Paths.ToList();
for (int i = 0; i < namePaths.Count; i++)
{
if (namePaths[i].CompareTo(elementPaths[i]) != 0)
{
return false;
}
}
return true;
}
/// <summary>
/// Extracts collection name, handling GH_String and other text types
/// </summary>
private string GetCollectionName(object nameObj) =>
nameObj switch
{
GH_String ghString => ghString.Value,
IGH_Goo goo => goo.ToString(),
_ => nameObj.ToString()
};
/// <summary>
/// Gets or creates a nested collection structure based on the collection names.
/// </summary>
/// <remarks>
/// Handles paths like "parent::child::grandchild" by creating intermediate collections.
/// </remarks>
private SpeckleCollectionWrapper GetOrCreateNestedCollection(
SpeckleCollectionWrapper rootCollection,
string[] collectionNames,
System.Collections.IList elementsBranch,
Grasshopper.Kernel.Data.GH_Path path
)
{
SpeckleCollectionWrapper currentCollection = rootCollection;
var currentPath = new List<string>(rootCollection.Path);
foreach (var collectionName in collectionNames)
{
// build path for this level
currentPath.Add(collectionName);
// check if child collection already exists
var existingChild = currentCollection
.Elements.OfType<SpeckleCollectionWrapper>()
.FirstOrDefault(c => c.Name == collectionName);
if (existingChild != null)
{
// use existing collection
currentCollection = existingChild;
}
else
{
// create new child collection
var newChild = new SpeckleCollectionWrapper
{
Base = new Collection(),
Name = collectionName,
Path = currentPath.ToList(),
Color = null,
Material = null,
Topology = null, // only set topology on leaf collections
ApplicationId = Guid.NewGuid().ToString()
};
currentCollection.Elements.Add(newChild);
currentCollection = newChild;
}
}
// set topology on the final (leaf) collection
currentCollection.Topology = GetBranchTopology(path, elementsBranch.Count);
return currentCollection;
}
/// <summary>
/// Adds elements from a branch to the target collection
/// </summary>
private void AddElementsToCollection(
SpeckleCollectionWrapper targetCollection,
System.Collections.IList elementsBranch,
Grasshopper.Kernel.Data.GH_Path path
)
{
foreach (var item in elementsBranch)
{
if (item == null)
{
// preserve nulls for topology (CNX-2855 pattern)
targetCollection.Elements.Add(null);
continue;
}
// convert to SpeckleWrapper if possible - cast to IGH_Goo first
if (item is not IGH_Goo goo)
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
$"Unsupported object type in branch {path}: {item.GetType().Name}"
);
continue;
}
var wrapper = goo.ToSpeckleObjectWrapper();
if (wrapper == null)
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
$"Unsupported object type in branch {path}: {item.GetType().Name}"
);
continue;
}
if (wrapper is ISpeckleCollectionObject collectionObject)
{
targetCollection.Elements.Add(collectionObject);
}
else
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
$"Object type {wrapper.GetType().Name} is not a valid collection element"
);
}
}
}
/// <summary>
/// Creates topology string for a single branch (following GrasshopperHelpers.GetParamTopology pattern)
/// </summary>
private string GetBranchTopology(Grasshopper.Kernel.Data.GH_Path path, int count) =>
$"{path.ToString(false)}-{count}";
}
@@ -47,7 +47,7 @@ public class CreateCollection : VariableParameterComponentBase
protected override void SolveInstance(IGH_DataAccess dataAccess)
{
var rootCollection = CollectionHelpers.CreateRootCollection(InstanceGuid.ToString());
var rootCollection = CreateRootCollection();
bool hasAnyInput = false;
foreach (var inputParam in Params.Input)
@@ -73,14 +73,14 @@ public class CreateCollection : VariableParameterComponentBase
}
// validate for duplicate application IDs across the entire collection hierarchy
if (CollectionHelpers.HasDuplicateApplicationIds(rootCollection))
if (HasDuplicateApplicationIds(rootCollection))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "The same object(s) cannot appear in multiple collections");
return;
}
// validate collection isn't empty (CNX-2855)
if (rootCollection.Elements.Count == 0 || !rootCollection.Elements.Any(CollectionHelpers.HasAnyValidContent))
if (rootCollection.Elements.Count == 0 || !rootCollection.Elements.Any(HasAnyValidContent))
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
@@ -233,6 +233,56 @@ public class CreateCollection : VariableParameterComponentBase
}
}
/// <summary>
/// Validates that all application IDs are unique across the entire collection hierarchy.
/// Shows an error if duplicates are found, indicating objects appear in multiple collections.
/// </summary>
/// <returns>True if duplicates exist, false if all IDs are unique</returns>
private bool HasDuplicateApplicationIds(SpeckleCollectionWrapper rootCollection)
{
// args to CheckForDuplicateApplicationIds passed in since the method can recursively check
var seenIds = new HashSet<string>();
var duplicateIds = new HashSet<string>();
// iterate, create hash set and check all application IDs
ProcessAndCheckForDuplicateApplicationIds(rootCollection, seenIds, duplicateIds);
return duplicateIds.Count > 0;
}
/// <summary>
/// Recursively collects application IDs from all in the collection hierarchy.
/// </summary>
/// <remarks>
/// Only checks the wrapper's ApplicationId, not for example geometries within DataObjects.
/// </remarks>
private void ProcessAndCheckForDuplicateApplicationIds(
SpeckleCollectionWrapper collection,
HashSet<string> seenIds,
HashSet<string> duplicateIds
)
{
foreach (var element in collection.Elements)
{
switch (element)
{
case null:
break; // skip nulls (CNX-2855)
case SpeckleCollectionWrapper childCollection:
// recurse into child collections
ProcessAndCheckForDuplicateApplicationIds(childCollection, seenIds, duplicateIds);
break;
case SpeckleWrapper wrapper:
if (wrapper.ApplicationId != null && !seenIds.Add(wrapper.ApplicationId))
{
duplicateIds.Add(wrapper.ApplicationId);
}
break;
}
}
}
// IGH_VariableParameterComponent implementation
public override bool CanInsertParameter(GH_ParameterSide side, int index) => side == GH_ParameterSide.Input;
@@ -1,6 +1,5 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
@@ -8,7 +7,6 @@ using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Connectors.GrasshopperShared.Registration;
using Speckle.Sdk;
using Speckle.Sdk.Api;
using Speckle.Sdk.Models.Extensions;
namespace Speckle.Connectors.GrasshopperShared.Components.Dev;
@@ -63,23 +61,25 @@ public class TokenUrlComponent : GH_Component
try
{
// NOTE: once we split the logic in Sender and Receiver components, we need to set flag correctly
var resource = Task.Run(() => SolveInstanceWithUrAndToken(urlInput, tokenInput)).GetAwaiter().GetResult();
var (resource, hasPermission) = SolveInstanceWithUrAndToken(urlInput, tokenInput, true);
if (!hasPermission)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "You do not have enough permission for this project.");
}
da.SetData(0, resource);
}
catch (SpeckleException ex)
catch (SpeckleException e)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, ex.Message);
da.AbortComponentSolution();
}
catch (AggregateException ex)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, ex.ToFormattedString());
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, e.Message);
da.AbortComponentSolution();
}
}
private async Task<SpeckleUrlModelResource> SolveInstanceWithUrAndToken(string input, string? token)
public (SpeckleUrlModelResource resource, bool hasPermission) SolveInstanceWithUrAndToken(
string input,
string token,
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)
@@ -89,7 +89,7 @@ public class TokenUrlComponent : GH_Component
if (resources.Length != 1)
{
// POC: this shouldn't ever hit since exceptions are thrown in the FromUrlString method
throw new SpeckleException("FromUrlString parser returned an invalid resource");
throw new SpeckleException($"FromUrlString parser returned an invalid resource");
}
var resource = resources.First();
@@ -109,25 +109,24 @@ public class TokenUrlComponent : GH_Component
throw new SpeckleException("Token requires profile:read and profile:email scopes");
}
using var userScope = UserActivityScope.AddUserScope(account);
IClient client = scope.Get<IClientFactory>().Create(account);
var project = await client.Project.Get(resource.ProjectId);
if (project.workspaceId != null)
var project = client.Project.Get(resource.ProjectId).Result;
var projectPermissions = client.Project.GetPermissions(resource.ProjectId).Result;
if (project != null && project.workspaceId != null)
{
_ = await client.Workspace.Get(project.workspaceId);
var workspace = client.Workspace.Get(project.workspaceId).Result;
}
switch (resource)
{
case SpeckleUrlLatestModelVersionResource r:
_ = await client.Model.Get(r.ModelId, r.ProjectId);
case SpeckleUrlLatestModelVersionResource latestVersionResource:
var model = client.Model.Get(latestVersionResource.ModelId, latestVersionResource.ProjectId).Result;
break;
case SpeckleUrlModelVersionResource r:
_ = await client.Model.Get(r.ModelId, r.ProjectId);
case SpeckleUrlModelVersionResource versionResource:
var m = client.Model.Get(versionResource.ModelId, versionResource.ProjectId).Result;
// TODO: this wont be the case when we have separation between send and receive components
_ = await client.Version.Get(r.VersionId, r.ProjectId);
var v = client.Version.Get(versionResource.VersionId, versionResource.ProjectId).Result;
break;
case SpeckleUrlModelObjectResource:
throw new SpeckleException("Object URLs are not supported");
@@ -135,6 +134,6 @@ public class TokenUrlComponent : GH_Component
throw new SpeckleException("Unknown Speckle resource type");
}
return resource;
return (resource, isSender ? projectPermissions.canPublish.authorized : projectPermissions.canLoad.authorized);
}
}
@@ -1,6 +1,5 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using Grasshopper.Kernel.Types;
using Speckle.Connectors.GrasshopperShared.Components.BaseComponents;
using Speckle.Connectors.GrasshopperShared.HostApp;
@@ -13,7 +12,7 @@ namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
/// 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, IGH_VariableParameterComponent
public class FilterSpeckleObjects : GH_Component
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_objects_filter;
@@ -50,6 +49,17 @@ public class FilterSpeckleObjects : GH_Component, IGH_VariableParameterComponent
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)
@@ -93,39 +103,101 @@ public class FilterSpeckleObjects : GH_Component, IGH_VariableParameterComponent
dataAccess.GetData(2, ref property);
string material = "";
dataAccess.GetData(3, ref material);
// optional parameters - only read if they've been added via ⊕
string appId = "";
dataAccess.GetData(4, ref appId);
string speckleId = "";
int? appIdIndex = FindInputIndexByName("Application Id");
int? speckleIdIndex = FindInputIndexByName("Speckle Id");
if (appIdIndex.HasValue)
{
dataAccess.GetData(appIdIndex.Value, ref appId);
}
if (speckleIdIndex.HasValue)
{
dataAccess.GetData(speckleIdIndex.Value, ref speckleId);
}
bool filterByAppId = appIdIndex.HasValue;
bool filterBySpeckleId = speckleIdIndex.HasValue;
dataAccess.GetData(5, ref speckleId);
List<SpeckleWrapper> matchedObjects = new();
List<SpeckleWrapper> removedObjects = new();
foreach (SpeckleWrapper wrapper in objects.Cast<SpeckleWrapper>())
for (int i = 0; i < objects.Count; i++)
{
if (MatchesAllFilters(wrapper, name, property, material, appId, filterByAppId, speckleId, filterBySpeckleId))
SpeckleWrapper wrapper = objects[i]!;
// filter by name
if (!MatchesSearchPattern(name, wrapper.Name))
{
matchedObjects.Add(wrapper);
removedObjects.Add(wrapper);
continue;
}
// filter by property
bool foundProperty = false;
if (string.IsNullOrEmpty(property))
{
foundProperty = true;
}
else
{
removedObjects.Add(wrapper);
SpecklePropertyGroupGoo? properties = wrapper is SpeckleDataObjectWrapper dataObjPropWrapper
? dataObjPropWrapper.Properties
: wrapper is SpeckleGeometryWrapper geoPropWrapper
? geoPropWrapper.Properties
: null;
if (properties is not null)
{
// use flattened properties to search ALL nested property keys
// fix for [CNX-2512](https://linear.app/speckle/issue/CNX-2512/filter-objects-material-and-property-key-inputs-dont-work-as-expected)
Dictionary<string, SpecklePropertyGoo> flattenedProps = properties.Flatten();
foreach (string key in flattenedProps.Keys)
{
if (MatchesSearchPattern(property, key))
{
foundProperty = true;
break;
}
}
}
}
if (!foundProperty)
{
removedObjects.Add(wrapper);
continue;
}
// filter by material name
bool materialMatches = true;
if (!string.IsNullOrEmpty(material))
{
materialMatches = false;
if (wrapper is SpeckleGeometryWrapper geoWrapper)
{
materialMatches = MatchesSearchPattern(material, geoWrapper.Material?.Name ?? "");
}
else if (wrapper is SpeckleDataObjectWrapper dataObjWrapper)
{
// check if ANY geometry in the data object has a matching material (not sure about this...)
// fix for [CNX-2512](https://linear.app/speckle/issue/CNX-2512/filter-objects-material-and-property-key-inputs-dont-work-as-expected)
materialMatches = dataObjWrapper.Geometries.Any(geo =>
MatchesSearchPattern(material, geo.Material?.Name ?? "")
);
}
}
if (!materialMatches)
{
removedObjects.Add(wrapper);
continue;
}
// filter by application id
if (!MatchesSearchPattern(appId, wrapper.Base.applicationId ?? ""))
{
removedObjects.Add(wrapper);
continue;
}
// filter by speckle id
if (!MatchesSearchPattern(speckleId, wrapper.Base.id ?? ""))
{
removedObjects.Add(wrapper);
continue;
}
matchedObjects.Add(wrapper);
}
// Set output objects
@@ -142,190 +214,4 @@ public class FilterSpeckleObjects : GH_Component, IGH_VariableParameterComponent
return Operator.IsSymbolNameLike(target, searchPattern);
}
/// <summary>
/// Determines if a wrapper matches all active filter criteria.
/// </summary>
private bool MatchesAllFilters(
SpeckleWrapper wrapper,
string name,
string property,
string material,
string appId,
bool filterByAppId,
string speckleId,
bool filterBySpeckleId
)
{
// filter by name
if (!MatchesSearchPattern(name, wrapper.Name))
{
return false;
}
// filter by property
if (!MatchesPropertyFilter(wrapper, property))
{
return false;
}
// filter by material name
if (!MatchesMaterialFilter(wrapper, material))
{
return false;
}
// filter by application id (only if parameter was added)
if (filterByAppId && !MatchesSearchPattern(appId, wrapper.Base.applicationId ?? ""))
{
return false;
}
// filter by speckle id (only if parameter was added)
if (filterBySpeckleId && !MatchesSearchPattern(speckleId, wrapper.Base.id ?? ""))
{
return false;
}
return true;
}
private bool MatchesPropertyFilter(SpeckleWrapper wrapper, string property)
{
if (string.IsNullOrEmpty(property))
{
return true;
}
SpecklePropertyGroupGoo? properties = wrapper is SpeckleDataObjectWrapper dataObjPropWrapper
? dataObjPropWrapper.Properties
: wrapper is SpeckleGeometryWrapper geoPropWrapper
? geoPropWrapper.Properties
: null;
if (properties is null)
{
return false;
}
// use flattened properties to search ALL nested property keys
return properties.Flatten().Keys.Any(key => MatchesSearchPattern(property, key));
}
private bool MatchesMaterialFilter(SpeckleWrapper wrapper, string material)
{
if (string.IsNullOrEmpty(material))
{
return true;
}
if (wrapper is SpeckleGeometryWrapper geoWrapper)
{
return MatchesSearchPattern(material, geoWrapper.Material?.Name ?? "");
}
if (wrapper is SpeckleDataObjectWrapper dataObjWrapper)
{
// check if ANY geometry in the data object has a matching material
return dataObjWrapper.Geometries.Any(geo => MatchesSearchPattern(material, geo.Material?.Name ?? ""));
}
return false;
}
/// <summary>
/// Finds the index of an input parameter by its Name.
/// Returns null if the parameter doesn't exist.
/// </summary>
private int? FindInputIndexByName(string paramName)
{
for (int i = 0; i < Params.Input.Count; i++)
{
if (Params.Input[i].Name == paramName)
{
return i;
}
}
return null;
}
#region IGH_VariableParameterComponent
public bool CanInsertParameter(GH_ParameterSide side, int index)
{
if (side != GH_ParameterSide.Input)
{
return false;
}
// only allow inserting after the fixed parameters (index 4+)
if (index < 4)
{
return false;
}
// check how many optional params are already added (total inputs - 4 fixed)
int addedOptionalCount = Params.Input.Count - 4;
// we have 2 optional parameters available
return addedOptionalCount < 2;
}
public bool CanRemoveParameter(GH_ParameterSide side, int index) =>
// only allow removing optional input parameters (index 4+)
side == GH_ParameterSide.Input
&& index >= 4;
/// <remarks>
/// The ternary operator for NickName is needed due to a Grasshopper quirk where
/// dynamically created parameters don't respect the "Draw Full Names" setting automatically.
/// We check CanvasFullNames at creation time to set the appropriate NickName.
/// This does not handle the case where the user toggles "Draw Full Names" while the
/// component is already on the canvas. Handling that would require subscribing to
/// Grasshopper.CentralSettings.CanvasFullNamesChanged event, which is overkill for now.
/// </remarks>
public IGH_Param CreateParameter(GH_ParameterSide side, int index)
{
bool hasAppId = FindInputIndexByName("Application Id").HasValue;
bool hasSpeckleId = FindInputIndexByName("Speckle Id").HasValue;
if (!hasAppId)
{
return new Param_String
{
Name = "Application Id",
NickName = Grasshopper.CentralSettings.CanvasFullNames ? "Application Id" : "aID", // see remarks
Description = "Find objects with a matching applicationId",
Access = GH_ParamAccess.item,
Optional = true
};
}
if (!hasSpeckleId)
{
return new Param_String
{
Name = "Speckle Id",
NickName = Grasshopper.CentralSettings.CanvasFullNames ? "Speckle Id" : "sID", // see remarks
Description = "Find objects with a matching Speckle id",
Access = GH_ParamAccess.item,
Optional = true
};
}
return new Param_String();
}
public bool DestroyParameter(GH_ParameterSide side, int index) => side == GH_ParameterSide.Input && index >= 4;
public void VariableParameterMaintenance()
{
// ensure all optional parameters stay marked as optional
for (int i = 4; i < Params.Input.Count; i++)
{
Params.Input[i].Optional = true;
}
}
#endregion
}
@@ -9,7 +9,7 @@ namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
[Guid("8D2E3F4A-1B5C-4E7F-9A8B-3C6D9E2F1A4B")]
public class SpeckleBlockDefinitionPassthrough()
: SpecklePassthroughComponentBase(
: SpeckleSolveInstance(
"Speckle Block Definition",
"SBD",
"Create or modify a Speckle Block Definition",
@@ -21,9 +21,6 @@ public class SpeckleBlockDefinitionPassthrough()
protected override Bitmap Icon => Resources.speckle_objects_block_def;
public override GH_Exposure Exposure => GH_Exposure.tertiary;
protected override int FixedInputCount => 3;
protected override int FixedOutputCount => 3;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(
@@ -125,16 +122,12 @@ public class SpeckleBlockDefinitionPassthrough()
result.Value.Name = inputName;
}
// process application id (only if user provided one, otherwise preserve existing)
if (TryGetApplicationIdInput(da, out string? inputAppId))
{
result.Value.ApplicationId = inputAppId;
}
// no need to process application Id.
// New definitions should have a new appID generated in the new() constructor, and we want to preserve old appID otherwise for changetracking.
// set outputs
da.SetData(0, result);
da.SetDataList(1, result.Value.Objects.Select(o => o.CreateGoo()));
da.SetData(2, result.Value.Name);
SetApplicationIdOutput(da, result.Value.ApplicationId);
}
}
@@ -9,7 +9,7 @@ namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
[Guid("2F8A9B1C-3D4E-5F6A-7B8C-9D0E1F2A3B4C")]
public class SpeckleBlockInstancePassthrough()
: SpecklePassthroughComponentBase(
: SpeckleSolveInstance(
"Speckle Block Instance",
"SBI",
"Create or modify a Speckle Block Instance",
@@ -21,9 +21,6 @@ public class SpeckleBlockInstancePassthrough()
protected override Bitmap Icon => Resources.speckle_objects_block_inst;
public override GH_Exposure Exposure => GH_Exposure.tertiary;
protected override int FixedInputCount => 7;
protected override int FixedOutputCount => 7;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
int instanceIndex = pManager.AddParameter(
@@ -208,11 +205,8 @@ public class SpeckleBlockInstancePassthrough()
result.Value.Material = inputMaterial.Value;
}
// process application id (only if user provided one, otherwise preserve existing)
if (TryGetApplicationIdInput(da, out string? inputAppId))
{
result.Value.ApplicationId = inputAppId;
}
// no need to process application id.
// new appids are generated if this is a new object, otherwise the input object appID should be preserved for change tracking.
// Set outputs
da.SetData(0, result);
@@ -222,7 +216,6 @@ public class SpeckleBlockInstancePassthrough()
da.SetData(4, result.Value.Properties);
da.SetData(5, result.Value.Color);
da.SetData(6, result.Value.Material);
SetApplicationIdOutput(da, result.Value.ApplicationId);
}
private Transform? ExtractTransform(IGH_Goo input) =>
@@ -8,7 +8,7 @@ namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
[Guid("5CE8AA40-7706-4893-853D-4C77604548FA")]
public class SpeckleDataObjectPassthrough()
: SpecklePassthroughComponentBase(
: SpeckleSolveInstance(
"Speckle Data Object",
"SDO",
"Create or modify a Speckle Data Object",
@@ -20,9 +20,6 @@ public class SpeckleDataObjectPassthrough()
protected override Bitmap Icon => Resources.speckle_objects_dataobject;
public override GH_Exposure Exposure => GH_Exposure.secondary;
protected override int FixedInputCount => 4;
protected override int FixedOutputCount => 5;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
int objIndex = pManager.AddParameter(
@@ -161,17 +158,9 @@ public class SpeckleDataObjectPassthrough()
result.Properties = inputProperties;
}
// process application id (only if user provided one)
if (TryGetApplicationIdInput(da, out string? inputAppId))
{
result.ApplicationId = inputAppId;
}
else
{
// generate application ID for new data objects. Unlike SpeckleGeometry, DataObject wrappers aren't created
// through casting (which auto-generates IDs), so we must explicitly ensure an ID exists here
result.ApplicationId ??= Guid.NewGuid().ToString();
}
// generate application ID for new data objects. Unlike SpeckleGeometry, DataObject wrappers aren't created
// through casting (which auto-generates IDs), so we must explicitly ensure an ID exists here
result.ApplicationId ??= Guid.NewGuid().ToString();
// get the path
string? path =
@@ -183,6 +172,5 @@ public class SpeckleDataObjectPassthrough()
da.SetData(2, result.Name);
da.SetData(3, result.Properties);
da.SetData(4, path);
SetApplicationIdOutput(da, result.ApplicationId);
}
}
@@ -10,7 +10,7 @@ namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
[Guid("F9418610-ACAE-4417-B010-19EBEA6A121F")]
public class SpeckleGeometryPassthrough()
: SpecklePassthroughComponentBase(
: SpeckleSolveInstance(
"Speckle Geometry",
"SG",
"Create or modify a Speckle Geometry",
@@ -22,9 +22,6 @@ public class SpeckleGeometryPassthrough()
protected override Bitmap Icon => Resources.speckle_objects_geometry;
public override GH_Exposure Exposure => GH_Exposure.secondary;
protected override int FixedInputCount => 6;
protected override int FixedOutputCount => 7;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
int objIndex = pManager.AddGenericParameter(
@@ -223,11 +220,8 @@ public class SpeckleGeometryPassthrough()
result.Material = inputMaterial.Value;
}
// process application id (only if user provided one, otherwise preserve existing)
if (TryGetApplicationIdInput(da, out string? inputAppId))
{
result.ApplicationId = inputAppId;
}
// no need to process application Id.
// New definitions should have a new appID generated in the new() constructor, and we want to preserve old appID otherwise for changetracking.
// get the path
string? path =
@@ -241,7 +235,6 @@ public class SpeckleGeometryPassthrough()
da.SetData(4, result.Color);
da.SetData(5, result.Material);
da.SetData(6, path);
SetApplicationIdOutput(da, result.ApplicationId);
}
// keeps the geometry and wrapped base the same while assigning all other props from the inut wrapper
@@ -1,19 +1,8 @@
using Speckle.Connectors.Common.Operations;
using Speckle.Sdk.Credentials;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Receive;
/// <param name="Account"></param>
/// <param name="WorkspaceId"></param>
/// <param name="ProjectId"></param>
/// <param name="ProjectName"></param>
/// <param name="ModelId"></param>
/// <param name="ModelName"></param>
/// <param name="SelectedVersionId"></param>
/// <param name="SourceApplication">See <see cref="Version.sourceApplication"/></param>
/// <param name="ReceivingApplicationSlug">Slug of the application doing the receiving (i.e. the current host app)</param>
/// <param name="SelectedVersionUserId"></param>
public record GrasshopperReceiveInfo(
Account Account,
string? WorkspaceId,
@@ -23,6 +12,5 @@ public record GrasshopperReceiveInfo(
string ModelName,
string SelectedVersionId,
string SourceApplication,
string ReceivingApplicationSlug,
string? SelectedVersionUserId
) : ReceiveInfo(Account, ProjectId, ProjectName, ModelId, ModelName, SelectedVersionId, ReceivingApplicationSlug);
) : ReceiveInfo(Account, ProjectId, ProjectName, ModelId, ModelName, SelectedVersionId, SourceApplication);
@@ -1,7 +1,12 @@
using Speckle.Connectors.Common.Operations;
using Speckle.Sdk.Api;
using Speckle.Sdk.Credentials;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Send;
public record GrasshopperSendInfo(IClient Client, string? WorkspaceId, string ProjectId, string ModelId)
: SendInfo(Client, ProjectId, ModelId);
public record GrasshopperSendInfo(
Account Account,
string? WorkspaceId,
string ProjectId,
string ModelId,
string SourceApplication
) : SendInfo(Account, ProjectId, ModelId, SourceApplication);
@@ -87,7 +87,6 @@ public class SendAsyncComponent : GH_AsyncComponent<SendAsyncComponent>
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam());
pManager.AddTextParameter("Version ID", "V", "ID of the created version", GH_ParamAccess.item);
}
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
@@ -322,7 +321,6 @@ public class SendComponentWorker : WorkerInstance<SendAsyncComponent>
private Stopwatch? _stopwatch;
public SpeckleUrlModelResource? OutputParam { get; set; }
public string? OutputVersionId { get; set; }
private List<(GH_RuntimeMessageLevel, string)> RuntimeMessages { get; } = new();
public override WorkerInstance<SendAsyncComponent> Duplicate(string id, CancellationToken cancellationToken)
@@ -334,7 +332,6 @@ public class SendComponentWorker : WorkerInstance<SendAsyncComponent>
{
_stopwatch = new Stopwatch();
_stopwatch.Start();
OutputVersionId = null;
}
public override void SetData(IGH_DataAccess da)
@@ -345,7 +342,6 @@ public class SendComponentWorker : WorkerInstance<SendAsyncComponent>
{
Parent.JustPastedIn = false;
da.SetData(0, Parent.OutputParam);
da.SetData(1, OutputVersionId);
return;
}
@@ -361,7 +357,6 @@ public class SendComponentWorker : WorkerInstance<SendAsyncComponent>
}
da.SetData(0, OutputParam);
da.SetData(1, OutputVersionId);
Parent.CurrentComponentState = ComponentState.UpToDate;
Parent.OutputParam = OutputParam; // ref the outputs in the parent too, so we can serialise them on write/read
@@ -378,7 +373,7 @@ public class SendComponentWorker : WorkerInstance<SendAsyncComponent>
*/
Parent.AddRuntimeMessage(
GH_RuntimeMessageLevel.Remark,
"Successfully published to Speckle. Right-click on the component to view online."
$"Successfully published to Speckle. Right-click on the component to view online."
);
Parent.AddRuntimeMessage(
GH_RuntimeMessageLevel.Remark,
@@ -437,16 +432,22 @@ public class SendComponentWorker : WorkerInstance<SendAsyncComponent>
// Step 1 - SEND TO SERVER
var sendInfo = await urlModelResource.GetSendInfo(Parent.ApiClient, CancellationToken).ConfigureAwait(false);
var (fileName, fileBytes) = SendComponent.GetGrasshopperFileInfo();
var progress = new Progress<CardProgress>(p =>
{
reportProgress(Id, p.Progress ?? 0);
//sendComponent.Message = $"{p.Status}";
});
using var scope = PriorityLoader.CreateScopeForActiveDocument();
var sendOperation = scope.ServiceProvider.GetRequiredService<SendOperation<SpeckleCollectionWrapperGoo>>();
(SendOperationResult result, string versionId) = await sendOperation
.Send([rootCollectionWrapper], sendInfo, fileName, fileBytes, Parent.VersionMessage, progress, CancellationToken)
SendOperationResult? result = await sendOperation
.Execute(
new List<SpeckleCollectionWrapperGoo> { rootCollectionWrapper },
sendInfo,
Parent.VersionMessage,
progress,
CancellationToken
)
.ConfigureAwait(false);
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
@@ -467,10 +468,9 @@ public class SendComponentWorker : WorkerInstance<SendAsyncComponent>
sendInfo.WorkspaceId,
sendInfo.ProjectId,
sendInfo.ModelId,
versionId
result.VersionId
);
OutputParam = createdVersion;
OutputVersionId = versionId;
Parent.Url = $"{createdVersion.Account.Server}/projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}";
}
}
@@ -1,5 +1,4 @@
using System.Diagnostics;
using Grasshopper;
using Grasshopper.Kernel;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common.Analytics;
@@ -37,10 +36,9 @@ public class SendComponentInput
}
}
public class SendComponentOutput(SpeckleUrlModelResource? resource, string? versionId = null)
public class SendComponentOutput(SpeckleUrlModelResource? resource)
{
public SpeckleUrlModelResource? Resource { get; } = resource;
public string? VersionId { get; } = versionId;
}
public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, SendComponentOutput>
@@ -88,11 +86,8 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
pManager.AddBooleanParameter("Run", "r", "Run the publish operation", GH_ParamAccess.item);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
protected override void RegisterOutputParams(GH_OutputParamManager pManager) =>
pManager.AddParameter(new SpeckleUrlModelResourceParam());
pManager.AddTextParameter("Version ID", "V", "ID of the created version", GH_ParamAccess.item);
}
protected override SendComponentInput GetInput(IGH_DataAccess da)
{
@@ -139,7 +134,6 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
else
{
da.SetData(0, result.Resource);
da.SetData(1, result.VersionId);
Message = "Done";
}
}
@@ -214,8 +208,6 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
throw new SpeckleAccountManagerException("No default account was found");
}
var (fileName, fileBytes) = GetGrasshopperFileInfo();
var progress = new Progress<CardProgress>(_ =>
{
// TODO: Progress only makes sense in non-blocking async receive, which is not supported yet.
@@ -224,8 +216,14 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
using var client = clientFactory.Create(account);
var sendInfo = await input.Resource.GetSendInfo(client, cancellationToken).ConfigureAwait(false);
var (result, versionId) = await sendOperation
.Send([collectionToSend], sendInfo, fileName, fileBytes, VersionMessage, progress, cancellationToken)
await sendOperation
.Execute(
new List<SpeckleCollectionWrapperGoo> { collectionToSend },
sendInfo,
VersionMessage,
progress,
cancellationToken
)
.ConfigureAwait(false);
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
@@ -246,19 +244,6 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
sendInfo.ModelId
);
Url = $"{sendInfo.Account.serverInfo.url}/projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}";
return new SendComponentOutput(createdVersionResource, versionId);
}
public static (string? fileName, long? fileSizeBytes) GetGrasshopperFileInfo()
{
var doc = Instances.ActiveCanvas?.Document;
if (doc is null || !File.Exists(doc.FilePath))
{
return (null, null);
}
var fileInfo = new FileInfo(doc.FilePath);
return (fileInfo.Name, fileInfo.Length);
return new SendComponentOutput(createdVersionResource);
}
}
@@ -108,13 +108,11 @@ public class SpeckleSelectModelComponent : GH_Component
try
{
// NOTE: once we split the logic in Sender and Receiver components, we need to set flag correctly
var (resource, permissionCheck) = SpeckleOperationWizard.SolveInstanceWithUrlInput(urlInput, true, null);
if (!permissionCheck.authorized)
var (resource, hasPermission) = SpeckleOperationWizard.SolveInstanceWithUrlInput(urlInput, true, null);
if (!hasPermission)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, permissionCheck.message);
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "You do not have enough permission for this project.");
}
_storedUserId = SpeckleOperationWizard.SelectedAccount?.id;
_storedServer = resource.Account.Server;
da.SetData(0, resource);
@@ -164,14 +162,11 @@ public class SpeckleSelectModelComponent : GH_Component
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 == null)
{
_storedWorkspaceId = null;
}
else if (workspaces.items.Count == 0)
if (workspaces.items.Count == 0)
{
// Create a workspace flow
SpeckleOperationWizard.CreateNewWorkspaceUIState();
@@ -1,6 +1,5 @@
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Registration;
@@ -67,7 +66,7 @@ public class SpeckleOperationWizard
ModelMenuHandler.ModelSelected += OnModelSelected;
}
public (SpeckleUrlModelResource, PermissionCheckResult) SolveInstanceWithUrlInput(
public (SpeckleUrlModelResource resource, bool hasPermission) SolveInstanceWithUrlInput(
string input,
bool isSender,
string? token
@@ -115,7 +114,7 @@ public class SpeckleOperationWizard
$"No appropriate account found for the given '{urlDerivedAccount?.serverInfo.url}' server"
);
}
using var userScope = UserActivityScope.AddUserScope(SelectedAccount);
IClient client = _clientFactory.Create(SelectedAccount);
var project = client.Project.Get(resource.ProjectId).Result;
@@ -128,20 +127,18 @@ public class SpeckleOperationWizard
ProjectMenuHandler.RedrawMenuButton(project);
ModelPermissionChecks modelPermissions;
switch (resource)
{
case SpeckleUrlLatestModelVersionResource r:
var model = client.Model.Get(r.ModelId, r.ProjectId).Result;
modelPermissions = client.Model.GetPermissions(r.ProjectId, r.ModelId).Result;
case SpeckleUrlLatestModelVersionResource latestVersionResource:
var model = client.Model.Get(latestVersionResource.ModelId, latestVersionResource.ProjectId).Result;
ModelMenuHandler.RedrawMenuButton(model);
break;
case SpeckleUrlModelVersionResource r:
var m = client.Model.Get(r.ModelId, r.ProjectId).Result;
case SpeckleUrlModelVersionResource versionResource:
var m = client.Model.Get(versionResource.ModelId, versionResource.ProjectId).Result;
ModelMenuHandler.RedrawMenuButton(m);
modelPermissions = client.Model.GetPermissions(r.ProjectId, r.ModelId).Result;
// TODO: this wont be the case when we have separation between send and receive components
var v = client.Version.Get(r.VersionId, r.ProjectId).Result;
var v = client.Version.Get(versionResource.VersionId, versionResource.ProjectId).Result;
VersionMenuHandler?.RedrawMenuButton(v, false);
break;
case SpeckleUrlModelObjectResource:
@@ -150,7 +147,7 @@ public class SpeckleOperationWizard
throw new SpeckleException("Unknown Speckle resource type");
}
return (resource, isSender ? modelPermissions.canCreateVersion : projectPermissions.canLoad);
return (resource, isSender ? projectPermissions.canPublish.authorized : projectPermissions.canLoad.authorized);
}
public void SetAccount(Account? account, bool refreshComponent = true)
@@ -270,7 +267,7 @@ public class SpeckleOperationWizard
/// <summary>
/// Callback function to retrieve workspaces with the search text
/// </summary>
public async Task<ResourceCollection<Workspace>?> FetchWorkspaces(string searchText)
public async Task<ResourceCollection<Workspace>> FetchWorkspaces(string searchText)
{
if (SelectedAccount == null)
{
@@ -278,11 +275,6 @@ public class SpeckleOperationWizard
}
using IClient client = _clientFactory.Create(SelectedAccount);
if (!await client.Server.IsWorkspaceEnabled())
{
return null;
}
var workspaces = await client.ActiveUser.GetWorkspaces(10, null, new UserWorkspacesFilter(searchText));
WorkspaceMenuHandler.Workspaces = workspaces;
return workspaces;
@@ -291,9 +283,17 @@ public class SpeckleOperationWizard
/// <summary>
/// Callback function to retrieve workspaces with the search text sync
/// </summary>
public ResourceCollection<Workspace>? FetchWorkspacesSync(string searchText)
public ResourceCollection<Workspace> FetchWorkspacesSync(string searchText)
{
return Task.Run(async () => await FetchWorkspaces(searchText)).GetAwaiter().GetResult();
if (SelectedAccount == null)
{
return new ResourceCollection<Workspace>();
}
using IClient client = _clientFactory.Create(SelectedAccount);
var workspaces = client.ActiveUser.GetWorkspaces(10, null, new UserWorkspacesFilter(searchText)).Result;
WorkspaceMenuHandler.Workspaces = workspaces;
return workspaces;
}
/// <summary>
@@ -327,7 +327,27 @@ public class SpeckleOperationWizard
/// </summary>
public ResourceCollection<ProjectWithPermissions> FetchProjectsSync(string searchText)
{
return Task.Run(async () => await FetchProjects(searchText)).GetAwaiter().GetResult();
if (SelectedAccount == null)
{
return new ResourceCollection<ProjectWithPermissions>();
}
using 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>
@@ -353,7 +373,17 @@ public class SpeckleOperationWizard
/// </summary>
public ResourceCollection<Model> FetchModelsSync(string searchText)
{
return Task.Run(async () => await FetchModels(searchText)).GetAwaiter().GetResult();
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>
@@ -383,7 +413,19 @@ public class SpeckleOperationWizard
/// </summary>
public ResourceCollection<Version> FetchVersionsSync(int versionCount)
{
return Task.Run(async () => await FetchVersions(versionCount)).GetAwaiter().GetResult();
if (SelectedAccount == null || SelectedProject == null || SelectedModel == null)
{
return new ResourceCollection<Version>();
}
using 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()
@@ -10,9 +10,9 @@ public class WorkspaceSelectedEventArgs(LimitedWorkspace? model) : EventArgs
public class WorkspaceMenuHandler
{
private readonly Func<string, Task<ResourceCollection<Workspace>?>> _fetchWorkspaces;
private readonly Func<string, Task<ResourceCollection<Workspace>>> _fetchWorkspaces;
private ToolStripDropDown? _menu;
public bool IsPersonalProjects { get; private set; }
public bool IsPersonalProjects { get; set; }
private SearchToolStripMenuItem? _searchItem;
private readonly Func<Task> _createWorkspace;
private LimitedWorkspace? SelectedWorkspace { get; set; }
@@ -24,7 +24,7 @@ public class WorkspaceMenuHandler
public GhContextMenuButton WorkspaceContextMenuButton { get; }
public WorkspaceMenuHandler(
Func<string, Task<ResourceCollection<Workspace>?>> fetchWorkspaces,
Func<string, Task<ResourceCollection<Workspace>>> fetchWorkspaces,
Func<Task> createWorkspace
)
{
@@ -44,7 +44,6 @@ public class WorkspaceMenuHandler
_menu?.Close();
Workspaces = null;
SelectedWorkspace = null;
IsPersonalProjects = false;
RedrawMenuButton(null);
}
@@ -52,7 +51,7 @@ public class WorkspaceMenuHandler
{
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);
PopulateModelMenuItems(_menu!, _searchItem!);
}
private bool PopulateMenu(ToolStripDropDown menu)
@@ -61,7 +60,7 @@ public class WorkspaceMenuHandler
_menu = menu;
_searchItem = new SearchToolStripMenuItem(menu, Refetch);
if (Workspaces?.items.Count == 0)
if (Workspaces == null || Workspaces.items.Count == 0)
{
menu.Items.Clear();
_searchItem.AddMenuItem("Create a new workspace", (_, _) => _createWorkspace.Invoke());
@@ -73,7 +72,7 @@ public class WorkspaceMenuHandler
return true;
}
private void PopulateModelMenuItems(ToolStripDropDown menu, SearchToolStripMenuItem? searchItem)
private void PopulateModelMenuItems(ToolStripDropDown menu, SearchToolStripMenuItem searchItem)
{
for (int i = menu.Items.Count - 1; i > 1; i--)
{
@@ -82,12 +81,6 @@ public class WorkspaceMenuHandler
if (Workspaces == null)
{
searchItem?.AddMenuItem(
"Personal Projects",
(_, _) => OnWorkspaceSelected(null),
!IsPersonalProjects,
IsPersonalProjects
);
return;
}
@@ -101,6 +94,13 @@ public class WorkspaceMenuHandler
Base64ToImage(workspace.logo)
);
}
searchItem?.AddMenuItem(
"Personal Projects",
(_, _) => OnWorkspaceSelected(null),
!IsPersonalProjects,
IsPersonalProjects
);
}
private void OnWorkspaceSelected(LimitedWorkspace? workspace)
@@ -130,7 +130,7 @@ public class WorkspaceMenuHandler
WorkspaceContextMenuButton.SetIconOverride(null);
WorkspaceContextMenuButton.Name = "Personal Projects";
WorkspaceContextMenuButton.NickName = "Personal Projects";
WorkspaceContextMenuButton.Description = "Legacy"; //Servers that don't have workspaces enabled (e.g. public opensource server (v2.x))
WorkspaceContextMenuButton.Description = "Legacy";
}
else
{
@@ -1,231 +0,0 @@
using GH_IO.Serialization;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
namespace Speckle.Connectors.GrasshopperShared.Components;
/// <summary>
/// Base class for passthrough components with "hidden" Application ID parameter.
/// </summary>
/// <remarks>
/// Users can click ⊕ to add an optional Application ID input and output.
/// </remarks>
public abstract class SpecklePassthroughComponentBase : SpeckleSolveInstance, IGH_VariableParameterComponent
{
private const string APP_ID_NAME = "Application Id";
private const string APP_ID_NICKNAME = "aID";
private const string APP_ID_DESCRIPTION = "The application id of the Speckle objects";
protected abstract int FixedInputCount { get; }
protected abstract int FixedOutputCount { get; }
private bool HasApplicationIdParam => Params.Input.Count > FixedInputCount;
protected SpecklePassthroughComponentBase(
string name,
string nickname,
string description,
string category,
string subCategory
)
: base(name, nickname, description, category, subCategory) { }
/// <summary>
/// Reads the optional Application Id input. Returns true if user provided a valid value.
/// </summary>
protected bool TryGetApplicationIdInput(IGH_DataAccess da, out string? applicationId)
{
applicationId = null;
if (!HasApplicationIdParam)
{
return false;
}
string appId = string.Empty;
if (da.GetData(FixedInputCount, ref appId))
{
if (string.IsNullOrWhiteSpace(appId))
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
"Empty Application Id ignored - existing or auto-generated id will be used"
);
return false;
}
applicationId = appId;
return true;
}
return false;
}
/// <summary>
/// Sets the Application Id output (if the parameter exists).
/// </summary>
protected void SetApplicationIdOutput(IGH_DataAccess da, string? applicationId)
{
if (!HasApplicationIdParam)
{
return;
}
da.SetData(FixedOutputCount, applicationId);
}
public bool CanInsertParameter(GH_ParameterSide side, int index)
{
// only allow inserting if not yet added
if (HasApplicationIdParam)
{
return false;
}
// only allow at the end position
return side switch
{
GH_ParameterSide.Input => index == FixedInputCount,
GH_ParameterSide.Output => index == FixedOutputCount,
_ => false
};
}
public bool CanRemoveParameter(GH_ParameterSide side, int index)
{
if (!HasApplicationIdParam)
{
return false;
}
return side switch
{
GH_ParameterSide.Input => index == FixedInputCount,
GH_ParameterSide.Output => index == FixedOutputCount,
_ => false
};
}
/// <remarks>
/// The ternary for NickName handles a Grasshopper quirk where dynamically created parameters
/// don't respect the "Draw Full Names" setting automatically.
/// </remarks>
public IGH_Param CreateParameter(GH_ParameterSide side, int index)
{
// when adding on either side, add both input and output together
if (side == GH_ParameterSide.Input && Params.Output.Count == FixedOutputCount)
{
OnPingDocument()?.ScheduleSolution(5, _ => AddApplicationIdOutput());
}
else if (side == GH_ParameterSide.Output && Params.Input.Count == FixedInputCount)
{
OnPingDocument()?.ScheduleSolution(5, _ => AddApplicationIdInput());
}
return CreateApplicationIdParam();
}
public bool DestroyParameter(GH_ParameterSide side, int index)
{
// when removing from either side, remove both input and output together
if (side == GH_ParameterSide.Input && index == FixedInputCount && Params.Output.Count > FixedOutputCount)
{
OnPingDocument()?.ScheduleSolution(5, _ => RemoveApplicationIdOutput());
}
else if (side == GH_ParameterSide.Output && index == FixedOutputCount && Params.Input.Count > FixedInputCount)
{
OnPingDocument()?.ScheduleSolution(5, _ => RemoveApplicationIdInput());
}
return side switch
{
GH_ParameterSide.Input => index == FixedInputCount,
GH_ParameterSide.Output => index == FixedOutputCount,
_ => false
};
}
public void VariableParameterMaintenance()
{
// ensure the Application Id input stays optional
if (HasApplicationIdParam && Params.Input.Count > FixedInputCount)
{
Params.Input[FixedInputCount].Optional = true;
}
}
private static IGH_Param CreateApplicationIdParam() =>
new Param_String
{
Name = APP_ID_NAME,
NickName = Grasshopper.CentralSettings.CanvasFullNames ? APP_ID_NAME : APP_ID_NICKNAME,
Description = APP_ID_DESCRIPTION,
Access = GH_ParamAccess.item,
Optional = true
};
private void AddApplicationIdInput()
{
if (Params.Input.Count > FixedInputCount)
{
return;
}
Params.RegisterInputParam(CreateApplicationIdParam());
Params.OnParametersChanged();
VariableParameterMaintenance();
ExpireSolution(true);
}
private void AddApplicationIdOutput()
{
if (Params.Output.Count > FixedOutputCount)
{
return;
}
Params.RegisterOutputParam(CreateApplicationIdParam());
Params.OnParametersChanged();
ExpireSolution(true);
}
private void RemoveApplicationIdInput()
{
if (Params.Input.Count <= FixedInputCount)
{
return;
}
Params.UnregisterInputParameter(Params.Input[FixedInputCount]);
Params.OnParametersChanged();
ExpireSolution(true);
}
private void RemoveApplicationIdOutput()
{
if (Params.Output.Count <= FixedOutputCount)
{
return;
}
Params.UnregisterOutputParameter(Params.Output[FixedOutputCount]);
Params.OnParametersChanged();
ExpireSolution(true);
}
public override bool Write(GH_IWriter writer)
{
var result = base.Write(writer);
writer.SetBoolean("HasApplicationIdParam", HasApplicationIdParam);
return result;
}
public override bool Read(GH_IReader reader)
{
var result = base.Read(reader);
// parameters are restored by GH serialization, this flag is for reference
bool hasAppIdParam = false;
reader.TryGetBoolean("HasApplicationIdParam", ref hasAppIdParam);
return result;
}
}
@@ -1,83 +0,0 @@
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.GrasshopperShared.Components.Collections;
/// <summary>
/// Shared helper methods for collection components to avoid code duplication
/// </summary>
public static class CollectionHelpers
{
/// <summary>
/// Creates a root collection wrapper with default values
/// </summary>
public static SpeckleCollectionWrapper CreateRootCollection(string instanceGuid) =>
new SpeckleCollectionWrapper
{
Base = new Collection(),
Name = "Unnamed",
Path = new List<string> { "Unnamed" },
Color = null,
Material = null,
ApplicationId = instanceGuid
};
/// <summary>
/// Validates that all application IDs are unique across the entire collection hierarchy.
/// </summary>
/// <returns>True if duplicates exist, false if all IDs are unique</returns>
public static bool HasDuplicateApplicationIds(SpeckleCollectionWrapper rootCollection)
{
var seenIds = new HashSet<string>();
var duplicateIds = new HashSet<string>();
ProcessAndCheckForDuplicateApplicationIds(rootCollection, seenIds, duplicateIds);
return duplicateIds.Count > 0;
}
/// <summary>
/// Recursively collects application IDs from all wrappers in the collection hierarchy.
/// </summary>
/// <remarks>
/// Only checks the wrapper's ApplicationId, not for example geometries within DataObjects.
/// </remarks>
private static void ProcessAndCheckForDuplicateApplicationIds(
SpeckleCollectionWrapper collection,
HashSet<string> seenIds,
HashSet<string> duplicateIds
)
{
foreach (var element in collection.Elements)
{
switch (element)
{
case null:
break; // skip nulls (CNX-2855)
case SpeckleCollectionWrapper childCollection:
// recurse into child collections
ProcessAndCheckForDuplicateApplicationIds(childCollection, seenIds, duplicateIds);
break;
case SpeckleWrapper wrapper:
if (wrapper.ApplicationId != null && !seenIds.Add(wrapper.ApplicationId))
{
duplicateIds.Add(wrapper.ApplicationId);
}
break;
}
}
}
/// <summary>
/// Recursively checks if collection or any descendants contain valid geometry/data objects
/// </summary>
public static bool HasAnyValidContent(ISpeckleCollectionObject? element) =>
element switch
{
SpeckleGeometryWrapper => true,
SpeckleDataObjectWrapper => true,
SpeckleCollectionWrapper collection => collection.Elements.Any(HasAnyValidContent),
_ => false
};
}
@@ -1,5 +1,4 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.GrasshopperShared.Components.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.Components.Operations.Send;
@@ -51,7 +50,6 @@ public record SpeckleUrlLatestModelVersionResource(
model.name,
version.id,
version.sourceApplication.NotNull(),
HostApplications.Grasshopper.Slug,
version.authorUser?.id
);
@@ -67,7 +65,13 @@ public record SpeckleUrlLatestModelVersionResource(
await client.Project.Get(ProjectId, cancellationToken).ConfigureAwait(false);
await client.Model.Get(ModelId, ProjectId, cancellationToken).ConfigureAwait(false);
return new GrasshopperSendInfo(client, WorkspaceId, ProjectId, ModelId);
return new GrasshopperSendInfo(
client.Account,
WorkspaceId,
ProjectId,
ModelId,
"Grasshopper8" // TODO: Grab from the right place!
);
}
}
@@ -97,7 +101,6 @@ public record SpeckleUrlModelVersionResource(
model.name,
VersionId,
version.sourceApplication.NotNull(),
HostApplications.Grasshopper.Slug,
version.authorUser?.id
);
@@ -113,7 +116,13 @@ public record SpeckleUrlModelVersionResource(
await client.Project.Get(ProjectId, cancellationToken).ConfigureAwait(false);
await client.Model.Get(ModelId, ProjectId, cancellationToken).ConfigureAwait(false);
return new GrasshopperSendInfo(client, WorkspaceId, ProjectId, ModelId);
return new GrasshopperSendInfo(
client.Account,
WorkspaceId,
ProjectId,
ModelId,
"Grasshopper8" // TODO: Grab from the right place!
);
}
}
@@ -95,7 +95,7 @@ public class GrasshopperReceiveOperation
.ConfigureAwait(false);
await apiClient
.Version.Received(new(version.id, receiveInfo.ProjectId, receiveInfo.ReceivingApplicationSlug), cancellationToken)
.Version.Received(new(version.id, receiveInfo.ProjectId, receiveInfo.SourceApplication), cancellationToken)
.ConfigureAwait(false);
return commitObject;
}
@@ -225,14 +225,6 @@ internal sealed class LocalToGlobalMapHandler
var entry = _dataObjectInstanceRegistry.GetEntries()[dataObjectId];
var resolvedGeometries = ResolveInstanceProxiesToGeometries(entry.InstanceProxies);
var primitiveConverted = dataObject
.displayValue.Where(item => item is not InstanceProxy)
.SelectMany(item => SpeckleConversionContext.Current.ConvertToHost(item))
.ToList();
resolvedGeometries.AddRange(ConvertToGeometryWrappers(primitiveConverted));
var dataObjectWrapper = CreateDataObjectWrapper(dataObject, resolvedGeometries, path, objectCollection);
CollectionRebuilder.AppendSpeckleGrasshopperObject(dataObjectWrapper, path, _colorUnpacker, _materialUnpacker);
@@ -14,7 +14,6 @@
<Compile Include="$(MSBuildThisFileDirectory)Components\BaseComponents\ValueSet.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\BaseComponents\VariableParameterComponentBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Collections\CollectionPathsSelector.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Collections\CollectionsByName.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Collections\CreateCollection.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Collections\ExpandCollection.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Objects\ExpandSpeckleProperties.cs" />
@@ -42,9 +41,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Components\Operations\Wizard\VersionMenuHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Operations\Wizard\WorkspaceMenuHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Operations\Wizard\SpeckleOperationWizard.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\SpecklePassthroughComponentBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\SpeckleSolveInstance.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\CollectionHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Extras\StateTag.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\KeyWatcher.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\GrasshopperBlockUnpacker.cs" />
@@ -185,8 +185,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -196,13 +196,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -328,7 +328,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -361,7 +361,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.rhino7": {
@@ -421,11 +421,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
},
"System.Resources.Extensions": {
@@ -185,8 +185,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -196,13 +196,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -328,7 +328,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -361,7 +361,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.rhino8": {
@@ -420,11 +420,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
},
"System.Resources.Extensions": {
@@ -174,8 +174,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -183,13 +183,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -255,7 +255,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -279,7 +279,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.converters.rhino8": {
@@ -329,11 +329,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
},
@@ -1,5 +1,4 @@
using System.Collections.Concurrent;
using System.IO;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Rhino;
@@ -288,7 +287,6 @@ public sealed class RhinoSendBinding : ISendBinding
{
using var manager = _sendOperationManagerFactory.Create();
var (fileName, fileBytes) = GetFileInfo();
await manager.Process(
Commands,
modelCardId,
@@ -309,23 +307,10 @@ public sealed class RhinoSendBinding : ISendBinding
.Where(obj => obj != null)
.ToList()
);
},
fileName,
fileBytes
}
);
}
private static (string? fileName, long? fileBytes) GetFileInfo()
{
RhinoDoc doc = RhinoDoc.ActiveDoc.NotNull("No active rhino doc");
long? fileSize = null;
if (File.Exists(doc.Path))
{
fileSize = new FileInfo(doc.Path).Length;
}
return (doc.Name, fileSize);
}
public void CancelSend(string modelCardId) => _cancellationManager.CancelOperation(modelCardId);
/// <summary>
@@ -122,9 +122,7 @@ public class RhinoLayerBaker : TraversalContextUnpacker
continue;
}
var cleanNewLayerName = string.IsNullOrWhiteSpace(collection.name)
? "unnamed"
: RhinoUtils.CleanLayerName(collection.name);
var cleanNewLayerName = RhinoUtils.CleanLayerName(collection.name);
if (!ModelComponent.IsValidComponentName(cleanNewLayerName))
{
@@ -14,7 +14,6 @@ public static class RhinoUtils
public static string CleanLayerName(string str)
{
var sb = new StringBuilder(str.Length);
bool lastWasSpace = true;
foreach (char c in str)
{
@@ -31,29 +30,10 @@ public static class RhinoUtils
if (s_replaceWithHyphen.Contains(c))
{
sb.Append('-');
lastWasSpace = false;
continue;
}
// Collapse double spaces into one and skip leading spaces.
// e.g. " Items Name " -> "Items Name"
if (c == ' ')
{
if (!lastWasSpace)
{
sb.Append(c);
lastWasSpace = true;
}
continue;
}
sb.Append(c);
lastWasSpace = false;
}
if (sb.Length > 0 && sb[^1] == ' ')
{
sb.Length--;
}
return sb.ToString();
@@ -1,139 +0,0 @@
using Microsoft.Extensions.Logging;
using Rhino.DocObjects;
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Converters.Rhino;
using Speckle.Objects.Other;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using RG = Rhino.Geometry;
namespace Speckle.Connectors.Rhino.HostApp;
/// <summary>
/// Utility class that creates Named Views during receive.
/// </summary>
public class RhinoViewBaker
{
private readonly IConverterSettingsStore<RhinoConversionSettings> _converterSettings;
private readonly ILogger<RhinoViewBaker> _logger;
private readonly ITypedConverter<Speckle.Objects.Geometry.Point, RG.Point3d> _pointConverter;
private readonly ITypedConverter<Speckle.Objects.Geometry.Vector, RG.Vector3d> _vectorConverter;
public RhinoViewBaker(
IConverterSettingsStore<RhinoConversionSettings> converterSettings,
ILogger<RhinoViewBaker> logger,
ITypedConverter<Speckle.Objects.Geometry.Point, RG.Point3d> pointConverter,
ITypedConverter<Speckle.Objects.Geometry.Vector, RG.Vector3d> vectorConverter
)
{
_converterSettings = converterSettings;
_logger = logger;
_pointConverter = pointConverter;
_vectorConverter = vectorConverter;
}
/// <summary>
/// Bakes Camera objects as Named Views in Rhino.
/// </summary>
public void BakeViews(IReadOnlyCollection<Camera> cameras)
{
if (cameras.Count == 0)
{
return;
}
foreach (var camera in cameras)
{
if (string.IsNullOrWhiteSpace(camera.name))
{
continue;
}
var viewName = camera.name.Trim();
try
{
RemoveNamedViewIfExists(viewName);
CreateNamedView(camera, viewName);
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogError(ex, "Failed to create Named View '{ViewName}'", viewName);
}
}
}
private void RemoveNamedViewIfExists(string name)
{
var doc = _converterSettings.Current.Document;
for (int i = doc.NamedViews.Count - 1; i >= 0; i--)
{
if (string.Equals(doc.NamedViews[i].Name, name, StringComparison.OrdinalIgnoreCase))
{
doc.NamedViews.Delete(i);
break;
}
}
}
private void CreateNamedView(Camera camera, string viewName)
{
var doc = _converterSettings.Current.Document;
// Get the active viewport
var activeView = doc.Views.ActiveView;
if (activeView == null)
{
_logger.LogError("No active view available to create Named View '{ViewName}'", viewName);
return;
}
var cameraLocation = _pointConverter.Convert(camera.position);
var cameraDirection = _vectorConverter.Convert(camera.forward);
var cameraUp = _vectorConverter.Convert(camera.up);
// scaling needed (point converters don't scale)
var sourceUnits = camera.position.units;
if (!string.IsNullOrEmpty(sourceUnits))
{
var scaleFactor = Units.GetConversionFactor(sourceUnits, _converterSettings.Current.SpeckleUnits);
cameraLocation = new RG.Point3d(
cameraLocation.X * scaleFactor,
cameraLocation.Y * scaleFactor,
cameraLocation.Z * scaleFactor
);
}
// Direction vectors (they don't need scaling, just direction)
cameraDirection.Unitize();
cameraUp.Unitize();
var targetDistance = Units.GetConversionFactor(Units.Meters, _converterSettings.Current.SpeckleUnits);
var targetPoint = cameraLocation + cameraDirection * targetDistance;
var originalViewportInfo = new ViewportInfo(activeView.ActiveViewport);
// Copy the current viewport info to preserve aspect ratio settings
var viewportInfo = new ViewportInfo(activeView.ActiveViewport);
viewportInfo.SetCameraLocation(cameraLocation);
viewportInfo.SetCameraDirection(cameraDirection);
viewportInfo.SetCameraUp(cameraUp);
viewportInfo.TargetPoint = targetPoint;
// Ensure it's perspective (lensLength=50mm, symmetric=true, nearDistance=1)
if (!viewportInfo.IsPerspectiveProjection)
{
viewportInfo.ChangeToPerspectiveProjection(50, true, 1);
}
activeView.ActiveViewport.SetViewProjection(viewportInfo, true);
// Add the named view from the current viewport
doc.NamedViews.Add(viewName, activeView.ActiveViewportID);
// Restore original viewport state
activeView.ActiveViewport.SetViewProjection(originalViewportInfo, true);
activeView.Redraw();
}
}
@@ -33,7 +33,6 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
private readonly RhinoMaterialBaker _materialBaker;
private readonly RhinoColorBaker _colorBaker;
private readonly RhinoGroupBaker _groupBaker;
private readonly RhinoViewBaker _viewBaker;
private readonly RootObjectUnpacker _rootObjectUnpacker;
private readonly ISdkActivityFactory _activityFactory;
private readonly IThreadContext _threadContext;
@@ -50,7 +49,6 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
RhinoMaterialBaker materialBaker,
RhinoColorBaker colorBaker,
RhinoGroupBaker groupBaker,
RhinoViewBaker viewBaker,
ISdkActivityFactory activityFactory,
IThreadContext threadContext,
IReceiveConversionHandler conversionHandler,
@@ -66,7 +64,6 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
_colorBaker = colorBaker;
_layerBaker = layerBaker;
_groupBaker = groupBaker;
_viewBaker = viewBaker;
_activityFactory = activityFactory;
_threadContext = threadContext;
_conversionHandler = conversionHandler;
@@ -128,12 +125,6 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
_colorBaker.ParseColors(unpackedRoot.ColorProxies);
}
// 3.1 - Bake views (Named Views)
if (unpackedRoot.Cameras is not null)
{
_viewBaker.BakeViews(unpackedRoot.Cameras);
}
// 4 - Bake layers
// See [CNX-325: Rhino: Change receive operation order to increase performance](https://linear.app/speckle/issue/CNX-325/rhino-change-receive-operation-order-to-increase-performance)
onOperationProgressed.Report(new("Baking layers (redraw disabled)", null));
@@ -75,6 +75,8 @@ public class RhinoRootObjectBuilder : IRootObjectBuilder<RhinoObject>
)
{
using var activity = _activityFactory.Start("Build");
using var sendPipeline = new Speckle.Sdk.Pipeline.Send();
// 0 - Init the root
Collection rootObjectCollection = new() { name = _converterSettings.Current.Document.Name ?? "Unnamed document" };
rootObjectCollection["units"] = _converterSettings.Current.SpeckleUnits;
@@ -97,6 +99,7 @@ public class RhinoRootObjectBuilder : IRootObjectBuilder<RhinoObject>
// 3 - Convert atomic objects
List<SendConversionResult> results = new(atomicObjects.Count);
int count = 0;
using (var _ = _activityFactory.Start("Convert all"))
{
foreach (RhinoObject rhinoObject in atomicObjects)
@@ -108,9 +111,8 @@ public class RhinoRootObjectBuilder : IRootObjectBuilder<RhinoObject>
Layer layer = _converterSettings.Current.Document.Layers[rhinoObject.Attributes.LayerIndex];
Collection collectionHost = _layerUnpacker.GetHostObjectCollection(layer, rootObjectCollection);
var result = ConvertRhinoObject(rhinoObject, collectionHost, instanceProxies, projectId);
var result = await ConvertRhinoObject(rhinoObject, collectionHost, instanceProxies, projectId, sendPipeline);
results.Add(result);
++count;
onOperationProgressed.Report(new("Converting", (double)count / atomicObjects.Count));
await Task.Yield();
@@ -149,18 +151,23 @@ public class RhinoRootObjectBuilder : IRootObjectBuilder<RhinoObject>
}
}
return new RootObjectBuilderResult(rootObjectCollection, results);
await sendPipeline.Process(rootObjectCollection);
await sendPipeline.WaitForUpload();
return new RootObjectBuilderResult(new Collection() { name = "ignore" }, results);
}
private SendConversionResult ConvertRhinoObject(
private async Task<SendConversionResult> ConvertRhinoObject(
RhinoObject rhinoObject,
Collection collectionHost,
IReadOnlyDictionary<string, InstanceProxy> instanceProxies,
string projectId
string projectId,
Sdk.Pipeline.Send sendPipeline
)
{
string applicationId = rhinoObject.Id.ToString();
string sourceType = rhinoObject.ObjectType.ToString();
bool wasCached = false;
try
{
// get from cache or convert:
@@ -174,6 +181,7 @@ public class RhinoRootObjectBuilder : IRootObjectBuilder<RhinoObject>
else if (_sendConversionCache.TryGetValue(projectId, applicationId, out ObjectReference? value))
{
converted = value;
wasCached = true;
}
else
{
@@ -194,10 +202,17 @@ public class RhinoRootObjectBuilder : IRootObjectBuilder<RhinoObject>
converted["properties"] = properties;
}
// add to host
collectionHost.elements.Add(converted);
// process in pipeline
var reference = await sendPipeline.Process(converted).ConfigureAwait(false);
if (!wasCached)
{
_sendConversionCache.AppendSendResult(projectId, applicationId, reference);
}
return new(Status.SUCCESS, applicationId, sourceType, converted);
// add to host
collectionHost.elements.Add(reference);
return new(Status.SUCCESS, applicationId, sourceType, reference);
}
catch (Exception ex) when (!ex.IsFatal())
{
@@ -2,12 +2,11 @@ using Microsoft.Extensions.DependencyInjection;
using Rhino.PlugIns;
using Speckle.Connectors.Common;
using Speckle.Connectors.DUI;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.Rhino.DependencyInjection;
using Speckle.Converters.Rhino;
using Speckle.Sdk;
using Speckle.Sdk.Models.Extensions;
namespace Speckle.Connectors.Rhino.Plugin;
///<summary>
@@ -51,10 +50,6 @@ public class SpeckleConnectorsRhinoPlugin : PlugIn
// but the Rhino connector has `.rhp` as it is extension.
Container = services.BuildServiceProvider();
// FORCE INITIALIZATION RhinoDocumentStore to register event handlers for BeginOpenDocument and EndOpenDocument
// this is needed when the user opens a Rhino file by double clicking the file,
// instead of opening a file in an already running Rhino instance
Container.GetRequiredService<DocumentModelStore>();
Container.UseDUI();
return LoadReturnCode.Success;
@@ -93,7 +93,6 @@ public static class ServiceRegistration
serviceCollection.AddScoped<RhinoColorUnpacker>();
serviceCollection.AddScoped<RhinoViewUnpacker>();
serviceCollection.AddScoped<RhinoViewBaker>();
serviceCollection.AddScoped<PropertiesExtractor>();
serviceCollection.AddScoped<RevitMappingResolver>();
@@ -26,7 +26,6 @@
<Compile Include="$(MSBuildThisFileDirectory)HostApp\DataObjectInstanceGrouper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Properties\PropertiesExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RhinoViewUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RhinoViewBaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RhinoIdleManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RhinoLayerHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RhinoObjectHelper.cs" />
@@ -210,8 +210,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -221,13 +221,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -347,7 +347,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -380,7 +380,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"LibTessDotNet": {
@@ -430,11 +430,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
}
@@ -229,8 +229,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -240,13 +240,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -428,7 +428,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -461,7 +461,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"LibTessDotNet": {
@@ -511,11 +511,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
}
@@ -229,8 +229,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -240,13 +240,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -428,7 +428,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"speckle.connectors.dui": {
@@ -461,7 +461,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"LibTessDotNet": {
@@ -511,11 +511,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
}
@@ -1,5 +1,4 @@
using System.Collections.Concurrent;
using System.IO;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Caching;
@@ -98,7 +97,6 @@ public sealed class TeklaSendBinding : ISendBinding
public async Task Send(string modelCardId)
{
var (fileName, fileSizeBytes) = GetFileInfo();
using var manager = _sendOperationManagerFactory.Create();
await manager.Process(
Commands,
@@ -113,30 +111,12 @@ public sealed class TeklaSendBinding : ISendBinding
.RefreshObjectIds()
.Select(id => _model.SelectModelObject(new Identifier(new Guid(id))))
.Where(obj => obj != null)
.ToList(),
fileName,
fileSizeBytes
.ToList()
);
}
public void CancelSend(string modelCardId) => _cancellationManager.CancelOperation(modelCardId);
private (string? fileName, long? fileSizeBytes) GetFileInfo()
{
ModelInfo? info = _model.GetInfo();
if (info?.ModelPath is not null)
{
string path = Path.Combine(info.ModelPath, info.ModelName);
if (File.Exists(path))
{
FileInfo file = new(path);
return (file.Name, file.Length);
}
}
return (info?.ModelName, null);
}
private async Task RunExpirationChecks()
{
if (!_model.GetConnectionStatus())
@@ -171,8 +171,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -182,13 +182,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -279,7 +279,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"Microsoft.Extensions.Logging": {
@@ -308,11 +308,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
}
@@ -171,8 +171,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "qCKCPT4HeSCJ7S+wnnjF+N+9Sd6lj5+Ra9DfxDHHrFli9rtXdnQRU5UOObyfcbiWQidVXhc2n0kbo3LPCEcvNw==",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -182,13 +182,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.13.1"
"Speckle.Sdk.Dependencies": "3.11.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.13.1",
"contentHash": "McLXS+Hd/bW+AdJifxGUIQi+ftofGY5r6i/X00HmlnbOvHJKZAR6fzJ12E8otMhMd78He8tcyjBD3R2jOR9ctA=="
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -279,7 +279,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.13.1, )"
"Speckle.Objects": "[3.11.1, )"
}
},
"Microsoft.Extensions.Logging": {
@@ -308,11 +308,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.13.1, )",
"resolved": "3.13.1",
"contentHash": "VRG8SApTbAYA0YmgWTw0Eb+/AHeE0yOxDuKBTvFj3VipuSnwF29fV479BehnZdg3d8OBh4aP/YEx3vPAafybVw==",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"dependencies": {
"Speckle.Sdk": "3.13.1"
"Speckle.Sdk": "3.11.1"
}
}
}

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