diff --git a/.circleci/scripts/config-template.yml b/.circleci/scripts/config-template.yml index 1acc8a2280..d02df9246f 100644 --- a/.circleci/scripts/config-template.yml +++ b/.circleci/scripts/config-template.yml @@ -116,7 +116,6 @@ jobs: # Each project will have individual jobs for each specific task it has to test-core: machine: image: ubuntu-2204:2023.02.1 - docker_layer_caching: true resource_class: large steps: - cached-checkout @@ -131,6 +130,9 @@ jobs: # Each project will have individual jobs for each specific task it has to - run-tests: title: Core Integration Tests project: Core/IntegrationTests/TestsIntegration.csproj + - run-tests: + title: Automate Integration Tests + project: Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/Speckle.Automate.Sdk.Tests.Integration.csproj - store_test_results: path: TestResults diff --git a/All.sln b/All.sln index 0ac1813031..2ad59e31d7 100644 --- a/All.sln +++ b/All.sln @@ -167,6 +167,7 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConnectorCSIBridge", "ConnectorCSI\ConnectorCSIBridge\ConnectorCSIBridge.csproj", "{23BE6E54-96C1-4373-89F3-E18A1C9807FD}" ProjectSection(ProjectDependencies) = postProject {60BE029E-1F31-4473-8B68-A745A43AF179} = {60BE029E-1F31-4473-8B68-A745A43AF179} + {21223BA5-C6E8-405D-B581-106C4726EDC0} = {21223BA5-C6E8-405D-B581-106C4726EDC0} EndProjectSection EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "ConnectorCSIShared", "ConnectorCSI\ConnectorCSIShared\ConnectorCSIShared.shproj", "{61374CD0-E774-4DCD-BFAB-6356B0931283}" @@ -174,16 +175,19 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConnectorETABS", "ConnectorCSI\ConnectorETABS\ConnectorETABS.csproj", "{81299D15-5788-414D-A962-1A568C251323}" ProjectSection(ProjectDependencies) = postProject {60BE029E-1F31-4473-8B68-A745A43AF179} = {60BE029E-1F31-4473-8B68-A745A43AF179} + {D06F557C-452A-4BBE-9B79-A10DB03F3832} = {D06F557C-452A-4BBE-9B79-A10DB03F3832} EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConnectorSAFE", "ConnectorCSI\ConnectorSAFE\ConnectorSAFE.csproj", "{9D188843-8841-4A76-A844-EFBE8E32EE05}" ProjectSection(ProjectDependencies) = postProject {60BE029E-1F31-4473-8B68-A745A43AF179} = {60BE029E-1F31-4473-8B68-A745A43AF179} + {442116F3-0F4A-4136-894E-FF5F4295500B} = {442116F3-0F4A-4136-894E-FF5F4295500B} EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConnectorSAP2000", "ConnectorCSI\ConnectorSAP2000\ConnectorSAP2000.csproj", "{31E0C098-6813-4571-AB96-A245E0FC1C23}" ProjectSection(ProjectDependencies) = postProject {60BE029E-1F31-4473-8B68-A745A43AF179} = {60BE029E-1F31-4473-8B68-A745A43AF179} + {907AED7A-719B-4157-8CC9-D21CB26E9243} = {907AED7A-719B-4157-8CC9-D21CB26E9243} EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DriverCSharp", "ConnectorCSI\DriverCSharp\DriverCSharp.csproj", "{C091E499-597D-4077-B83F-08E069091090}" @@ -353,8 +357,14 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RevitSharedResources2024", "ConnectorRevit\RevitSharedResources2024\RevitSharedResources2024.csproj", "{C2BA8B6B-72BD-4DAB-865F-90C66083BDB2}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConnectorAutocad2024", "ConnectorAutocadCivil\ConnectorAutocad2024\ConnectorAutocad2024.csproj", "{658DE496-5177-4CD5-A949-FE59E47109B6}" + ProjectSection(ProjectDependencies) = postProject + {1F21E740-6B05-47BD-8D2A-C9ED5E91C577} = {1F21E740-6B05-47BD-8D2A-C9ED5E91C577} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConnectorCivil2024", "ConnectorAutocadCivil\ConnectorCivil2024\ConnectorCivil2024.csproj", "{3E30D170-3CB4-4728-97D5-887C5019DA9B}" + ProjectSection(ProjectDependencies) = postProject + {B4D6F6DC-0712-4F9F-A24F-6B76DAE84B6F} = {B4D6F6DC-0712-4F9F-A24F-6B76DAE84B6F} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConverterAutocad2024", "Objects\Converters\ConverterAutocadCivil\ConverterAutocad2024\ConverterAutocad2024.csproj", "{1F21E740-6B05-47BD-8D2A-C9ED5E91C577}" EndProject @@ -372,6 +382,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConverterAdvanceSteel2024", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConverterDynamoRevit2024", "Objects\Converters\ConverterDynamo\ConverterDynamoRevit2024\ConverterDynamoRevit2024.csproj", "{75144587-6F51-46C8-8E40-DA652FBC53F4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Automate.Sdk", "Automate\Speckle.Automate.Sdk\Speckle.Automate.Sdk.csproj", "{AF51DD10-C0D5-4209-AF55-8F6476EA8A99}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Automate", "Automate", "{F7399C6A-0EA4-4212-A49E-0342BED82F98}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C6FF0E4F-38A3-4464-98E9-AB71D74B06F4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Automate.Sdk.Tests.Integration", "Automate\Tests\Speckle.Automate.Sdk.Tests.Integration\Speckle.Automate.Sdk.Tests.Integration.csproj", "{A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ConnectorCore", "ConnectorCore", "{DA9DFC36-C53F-4B19-8911-BF7605230BA7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BatchUploader.OperationDriver", "ConnectorCore\BatchUploader.OperationDriver\BatchUploader.OperationDriver.csproj", "{7F0206A9-61D4-4D3A-9B43-789DABA7C143}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BatchUploader.Sdk", "ConnectorCore\BatchUploader.Sdk\BatchUploader.Sdk.csproj", "{2CC777EB-BD63-4FAB-BC3A-68A640D2E639}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug Mac|Any CPU = Debug Mac|Any CPU @@ -2057,6 +2081,70 @@ Global {75144587-6F51-46C8-8E40-DA652FBC53F4}.Release|Any CPU.Build.0 = Release|Any CPU {75144587-6F51-46C8-8E40-DA652FBC53F4}.Release|x64.ActiveCfg = Release|Any CPU {75144587-6F51-46C8-8E40-DA652FBC53F4}.Release|x64.Build.0 = Release|Any CPU + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99}.Debug Mac|Any CPU.ActiveCfg = Debug|Any CPU + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99}.Debug Mac|Any CPU.Build.0 = Debug|Any CPU + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99}.Debug Mac|x64.ActiveCfg = Debug|Any CPU + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99}.Debug Mac|x64.Build.0 = Debug|Any CPU + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99}.Debug|x64.ActiveCfg = Debug|Any CPU + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99}.Debug|x64.Build.0 = Debug|Any CPU + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99}.Release Mac|Any CPU.ActiveCfg = Debug|Any CPU + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99}.Release Mac|Any CPU.Build.0 = Debug|Any CPU + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99}.Release Mac|x64.ActiveCfg = Debug|Any CPU + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99}.Release Mac|x64.Build.0 = Debug|Any CPU + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99}.Release|Any CPU.Build.0 = Release|Any CPU + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99}.Release|x64.ActiveCfg = Release|Any CPU + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99}.Release|x64.Build.0 = Release|Any CPU + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}.Debug Mac|Any CPU.ActiveCfg = Debug|Any CPU + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}.Debug Mac|Any CPU.Build.0 = Debug|Any CPU + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}.Debug Mac|x64.ActiveCfg = Debug|Any CPU + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}.Debug Mac|x64.Build.0 = Debug|Any CPU + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}.Debug|x64.ActiveCfg = Debug|Any CPU + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}.Debug|x64.Build.0 = Debug|Any CPU + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}.Release Mac|Any CPU.ActiveCfg = Debug|Any CPU + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}.Release Mac|Any CPU.Build.0 = Debug|Any CPU + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}.Release Mac|x64.ActiveCfg = Debug|Any CPU + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}.Release Mac|x64.Build.0 = Debug|Any CPU + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}.Release|Any CPU.Build.0 = Release|Any CPU + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}.Release|x64.ActiveCfg = Release|Any CPU + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4}.Release|x64.Build.0 = Release|Any CPU + {7F0206A9-61D4-4D3A-9B43-789DABA7C143}.Debug Mac|Any CPU.ActiveCfg = Debug|Any CPU + {7F0206A9-61D4-4D3A-9B43-789DABA7C143}.Debug Mac|Any CPU.Build.0 = Debug|Any CPU + {7F0206A9-61D4-4D3A-9B43-789DABA7C143}.Debug Mac|x64.ActiveCfg = Debug|Any CPU + {7F0206A9-61D4-4D3A-9B43-789DABA7C143}.Debug Mac|x64.Build.0 = Debug|Any CPU + {7F0206A9-61D4-4D3A-9B43-789DABA7C143}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F0206A9-61D4-4D3A-9B43-789DABA7C143}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F0206A9-61D4-4D3A-9B43-789DABA7C143}.Debug|x64.ActiveCfg = Debug|Any CPU + {7F0206A9-61D4-4D3A-9B43-789DABA7C143}.Debug|x64.Build.0 = Debug|Any CPU + {7F0206A9-61D4-4D3A-9B43-789DABA7C143}.Release Mac|Any CPU.ActiveCfg = Debug|Any CPU + {7F0206A9-61D4-4D3A-9B43-789DABA7C143}.Release Mac|Any CPU.Build.0 = Debug|Any CPU + {7F0206A9-61D4-4D3A-9B43-789DABA7C143}.Release Mac|x64.ActiveCfg = Debug|Any CPU + {7F0206A9-61D4-4D3A-9B43-789DABA7C143}.Release Mac|x64.Build.0 = Debug|Any CPU + {7F0206A9-61D4-4D3A-9B43-789DABA7C143}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F0206A9-61D4-4D3A-9B43-789DABA7C143}.Release|Any CPU.Build.0 = Release|Any CPU + {7F0206A9-61D4-4D3A-9B43-789DABA7C143}.Release|x64.ActiveCfg = Release|Any CPU + {7F0206A9-61D4-4D3A-9B43-789DABA7C143}.Release|x64.Build.0 = Release|Any CPU + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639}.Debug Mac|Any CPU.ActiveCfg = Debug|Any CPU + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639}.Debug Mac|Any CPU.Build.0 = Debug|Any CPU + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639}.Debug Mac|x64.ActiveCfg = Debug|Any CPU + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639}.Debug Mac|x64.Build.0 = Debug|Any CPU + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639}.Debug|x64.ActiveCfg = Debug|Any CPU + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639}.Debug|x64.Build.0 = Debug|Any CPU + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639}.Release Mac|Any CPU.ActiveCfg = Debug|Any CPU + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639}.Release Mac|Any CPU.Build.0 = Debug|Any CPU + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639}.Release Mac|x64.ActiveCfg = Debug|Any CPU + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639}.Release Mac|x64.Build.0 = Debug|Any CPU + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639}.Release|Any CPU.Build.0 = Release|Any CPU + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639}.Release|x64.ActiveCfg = Release|Any CPU + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2214,6 +2302,11 @@ Global {3B9189B9-E485-448A-8793-9B9587A36791} = {7B7C4CB1-3D60-4A5B-9902-C812521A24B3} {737D5567-7B1F-410D-9B7B-BAE8065ED15B} = {BE521908-7944-46F3-98BF-B47D34509934} {75144587-6F51-46C8-8E40-DA652FBC53F4} = {F0DD5C38-083B-43EA-8654-96247028D8AC} + {AF51DD10-C0D5-4209-AF55-8F6476EA8A99} = {F7399C6A-0EA4-4212-A49E-0342BED82F98} + {C6FF0E4F-38A3-4464-98E9-AB71D74B06F4} = {F7399C6A-0EA4-4212-A49E-0342BED82F98} + {A0C9EBE0-A56A-4D07-B6EF-2EEAEC45D6C4} = {C6FF0E4F-38A3-4464-98E9-AB71D74B06F4} + {7F0206A9-61D4-4D3A-9B43-789DABA7C143} = {DA9DFC36-C53F-4B19-8911-BF7605230BA7} + {2CC777EB-BD63-4FAB-BC3A-68A640D2E639} = {DA9DFC36-C53F-4B19-8911-BF7605230BA7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1D43D91B-4F01-4A78-8250-CC6F9BD93A14} diff --git a/All.sln.DotSettings b/All.sln.DotSettings index 531edb4e6d..bd21bc95e3 100644 --- a/All.sln.DotSettings +++ b/All.sln.DotSettings @@ -67,9 +67,11 @@ Speckle:Cleanup + CSI GQL QL SQ UI ExternalToolData|CSharpier|csharpier||csharpier|$FILE$ - CamelCase \ No newline at end of file + CamelCase + True \ No newline at end of file diff --git a/Automate/Speckle.Automate.Sdk/AutomationContext.cs b/Automate/Speckle.Automate.Sdk/AutomationContext.cs new file mode 100644 index 0000000000..d3038244ab --- /dev/null +++ b/Automate/Speckle.Automate.Sdk/AutomationContext.cs @@ -0,0 +1,310 @@ +# nullable enable +using System.Diagnostics; +using GraphQL; +using Serilog.Debugging; +using Speckle.Automate.Sdk.Schema; +using Speckle.Core.Api; +using Speckle.Core.Credentials; +using Speckle.Core.Models; +using Speckle.Core.Transports; +using Speckle.Newtonsoft.Json; +using Speckle.Newtonsoft.Json.Serialization; + +namespace Speckle.Automate.Sdk; + +public class AutomationContext +{ + public AutomationRunData AutomationRunData { get; set; } + public string? ContextView => AutomationResult.ResultView; + public Client SpeckleClient { get; set; } + + private ServerTransport serverTransport; + private string speckleToken; + + // keep a memory transport at hand, to speed up things if needed + private MemoryTransport memoryTransport; + + // added for performance measuring + private Stopwatch initTime; + + internal AutomationResult AutomationResult { get; set; } + + public static async Task Initialize(AutomationRunData automationRunData, string speckleToken) + { + var account = new Account + { + token = speckleToken, + serverInfo = new ServerInfo { url = automationRunData.SpeckleServerUrl } + }; + await account.Validate().ConfigureAwait(false); + var client = new Client(account); + var serverTransport = new ServerTransport(account, automationRunData.ProjectId); + var initTime = new Stopwatch(); + initTime.Start(); + + return new AutomationContext + { + AutomationRunData = automationRunData, + SpeckleClient = client, + serverTransport = serverTransport, + speckleToken = speckleToken, + memoryTransport = new MemoryTransport(), + initTime = initTime, + AutomationResult = new AutomationResult(), + }; + } + + public static async Task Initialize(string automationRunData, string speckleToken) + { + var runData = JsonConvert.DeserializeObject( + automationRunData, + new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() } + ); + return await Initialize(runData, speckleToken).ConfigureAwait(false); + } + + public string RunStatus => AutomationResult.RunStatus; + + public string? StatusMessage => AutomationResult.StatusMessage; + public TimeSpan Elapsed => initTime.Elapsed; + + public async Task ReceiveVersion() + { + var commit = await SpeckleClient + .CommitGet(AutomationRunData.ProjectId, AutomationRunData.VersionId) + .ConfigureAwait(false); + var commitRootObject = await Operations + .Receive(commit.referencedObject, serverTransport, memoryTransport) + .ConfigureAwait(false); + if (commitRootObject == null) + throw new Exception("Commit root object was null"); + Console.WriteLine( + $"It took {Elapsed.TotalSeconds} seconds to receive the speckle version {AutomationRunData.VersionId}" + ); + return commitRootObject; + } + + public async Task CreateNewVersionInProject(Base rootObject, string branchName, string versionMessage = "") + { + if (branchName == AutomationRunData.BranchName) + throw new ArgumentException( + $"The target model: {branchName} cannot match the model that triggered this automation: {AutomationRunData.ModelId}/{AutomationRunData.BranchName}", + nameof(branchName) + ); + var rootObjectId = await Operations + .Send(rootObject, new List { serverTransport, memoryTransport }, useDefaultCache: false) + .ConfigureAwait(false); + + var branch = await SpeckleClient.BranchGet(AutomationRunData.ProjectId, branchName).ConfigureAwait(false); + if (branch is null) + { + // Create the branch with the specified name + await SpeckleClient + .BranchCreate(new BranchCreateInput() { streamId = AutomationRunData.ProjectId, name = branchName }) + .ConfigureAwait(false); + } + var versionId = await SpeckleClient + .CommitCreate( + new CommitCreateInput + { + streamId = AutomationRunData.ProjectId, + branchName = branchName, + objectId = rootObjectId, + message = versionMessage, + } + ) + .ConfigureAwait(false); + return versionId; + } + + public void SetContextView(List? resourceIds = null, bool includeSourceModelVersion = true) + { + var linkResources = new List(); + if (includeSourceModelVersion) + linkResources.Add($@"{AutomationRunData.ModelId}@{AutomationRunData.VersionId}"); + if (resourceIds is not null) + linkResources.AddRange(resourceIds); + if (linkResources.Count == 0) + throw new Exception("We do not have enough resource ids to compose a context view"); + + AutomationResult.ResultView = $"/projects/{AutomationRunData.ProjectId}/models/{string.Join(",", linkResources)}"; + } + + public async Task ReportRunStatus() + { + ObjectResults? objectResults = null; + if (RunStatus is "SUCCEEDED" or "FAILED") + { + objectResults = new ObjectResults + { + Values = new ObjectResultValues + { + BlobIds = AutomationResult.Blobs, + ObjectResults = AutomationResult.ObjectResults + } + }; + } + var request = new GraphQLRequest + { + Query = + @" + mutation ReportFunctionRunStatus( + $automationId: String!, + $automationRevisionId: String!, + $automationRunId: String!, + $versionId: String!, + $functionId: String!, + $functionName: String!, + $functionLogo: String, + $runStatus: AutomationRunStatus! + $elapsed: Float! + $resultVersionIds: [String!]! + $statusMessage: String + $objectResults: JSONObject + ){ + automationMutations { + functionRunStatusReport(input: { + automationId: $automationId + automationRevisionId: $automationRevisionId + automationRunId: $automationRunId + versionId: $versionId + functionRuns: [{ + functionId: $functionId, + functionName: $functionName, + functionLogo: $functionLogo, + status: $runStatus, + elapsed: $elapsed, + resultVersionIds: $resultVersionIds, + statusMessage: $statusMessage, + results: $objectResults, + }] + }) + } + } + ", + Variables = new + { + automationId = AutomationRunData.AutomationId, + automationRevisionId = AutomationRunData.AutomationRevisionId, + automationRunId = AutomationRunData.AutomationRunId, + versionId = AutomationRunData.VersionId, + functionId = AutomationRunData.FunctionId, + functionName = AutomationRunData.FunctionName, + functionLogo = AutomationRunData.FunctionLogo, + runStatus = RunStatus, + statusMessage = AutomationResult.StatusMessage, + elapsed = Elapsed.TotalSeconds, + resultVersionIds = AutomationResult.ResultVersions, + objectResults, + } + }; + await SpeckleClient.ExecuteGraphQLRequest>(request).ConfigureAwait(false); + } + + public async Task StoreFileResult(string filePath) + { + if (!File.Exists(filePath)) + throw new FileNotFoundException("The given file path doesn't exist", fileName: filePath); + using var formData = new MultipartFormDataContent(); + + var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); + using var streamContent = new StreamContent(fileStream); + formData.Add(streamContent, "files", Path.GetFileName(filePath)); + var request = await SpeckleClient.GQLClient.HttpClient + .PostAsync( + new Uri($"{AutomationRunData.SpeckleServerUrl}/api/stream/{AutomationRunData.ProjectId}/blob"), + formData + ) + .ConfigureAwait(false); + request.EnsureSuccessStatusCode(); + var responseString = await request.Content.ReadAsStringAsync().ConfigureAwait(false); + Console.WriteLine("RESPONSE - " + responseString); + var uploadResponse = JsonConvert.DeserializeObject(responseString); + if (uploadResponse.UploadResults.Count != 1) + throw new Exception("Expected one upload result."); + AutomationResult.Blobs.AddRange(uploadResponse.UploadResults.Select(r => r.BlobId)); + } + + private void _markRun(AutomationStatus status, string? statusMessage) + { + var duration = Elapsed.TotalSeconds; + AutomationResult.StatusMessage = statusMessage; + var statusValue = AutomationStatusMapping.Get(status); + AutomationResult.RunStatus = statusValue; + AutomationResult.Elapsed = duration; + + var msg = $"Automation run {statusValue} after {duration} seconds."; + if (statusMessage is not null) + msg += $"\n{statusMessage}"; + Console.WriteLine(msg); + } + + public void MarkRunFailed(string statusMessage) + { + _markRun(AutomationStatus.Failed, statusMessage); + } + + public void MarkRunSuccess(string? statusMessage) + { + _markRun(AutomationStatus.Succeeded, statusMessage); + } + + public void AttachErrorToObjects( + string category, + IEnumerable objectIds, + string? message = null, + Dictionary? metadata = null, + Dictionary? visualOverrides = null + ) + { + AttachResultToObjects(ObjectResultLevel.Error, category, objectIds, message, metadata, visualOverrides); + } + + public void AttachWarningToObjects( + string category, + IEnumerable objectIds, + string? message = null, + Dictionary? metadata = null, + Dictionary? visualOverrides = null + ) + { + AttachResultToObjects(ObjectResultLevel.Warning, category, objectIds, message, metadata, visualOverrides); + } + + public void AttachInfoToObjects( + string category, + IEnumerable objectIds, + string? message = null, + Dictionary? metadata = null, + Dictionary? visualOverrides = null + ) + { + AttachResultToObjects(ObjectResultLevel.Info, category, objectIds, message, metadata, visualOverrides); + } + + public void AttachResultToObjects( + ObjectResultLevel level, + string category, + IEnumerable objectIds, + string? message = null, + Dictionary? metadata = null, + Dictionary? visualOverrides = null + ) + { + var levelString = ObjectResultLevelMapping.Get(level); + var objectIdList = objectIds.ToList(); + Console.WriteLine($"Created new {levelString.ToUpper()} category: {category} caused by: {message}"); + + var resultCase = new ResultCase + { + Category = category, + Level = levelString, + ObjectIds = objectIdList, + Message = message, + Metadata = metadata, + VisualOverrides = visualOverrides + }; + + AutomationResult.ObjectResults.Add(resultCase); + } +} diff --git a/Automate/Speckle.Automate.Sdk/Runner.cs b/Automate/Speckle.Automate.Sdk/Runner.cs new file mode 100644 index 0000000000..45adb74fa0 --- /dev/null +++ b/Automate/Speckle.Automate.Sdk/Runner.cs @@ -0,0 +1,149 @@ +using System.CommandLine; +using System.Diagnostics.CodeAnalysis; +using Newtonsoft.Json.Schema.Generation; +using Newtonsoft.Json.Serialization; +using Speckle.Automate.Sdk.Schema; +using Speckle.Newtonsoft.Json; + +namespace Speckle.Automate.Sdk; + +/// +/// Provides mechanisms to execute any function that conforms to the AutomateFunction "interface" +/// +public static class AutomationRunner +{ + [SuppressMessage("Design", "CA1031:Do not catch general exception types")] + public static async Task RunFunction( + Func automateFunction, + AutomationRunData automationRunData, + string speckleToken, + TInput inputs + ) + where TInput : struct + { + var automationContext = await AutomationContext.Initialize(automationRunData, speckleToken).ConfigureAwait(false); + + try + { + await automateFunction(automationContext, inputs).ConfigureAwait(false); + if (automationContext.RunStatus is not ("FAILED" or "SUCCEEDED")) + automationContext.MarkRunSuccess( + "WARNING: Automate assumed a success status, but it was not marked as so by the function." + ); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + automationContext.MarkRunFailed("Function error. Check the automation run logs for details."); + } + finally + { + if (automationContext.ContextView is null) + automationContext.SetContextView(); + + await automationContext.ReportRunStatus().ConfigureAwait(false); + } + return automationContext; + } + + public static async Task RunFunction( + Func automateFunction, + AutomationRunData automationRunData, + string speckleToken + ) + { + return await RunFunction( + async (context, _) => await automateFunction(context).ConfigureAwait(false), + automationRunData, + speckleToken, + new Fake() + ) + .ConfigureAwait(false); + } + + private struct Fake { } + + /// + /// Main entrypoint to execute an Automate function with no input data + /// + /// The command line arguments passed into the function by automate + /// The automate function to execute + /// This should always be called in your own functions, as it contains the logic to trigger the function automatically. + public static async Task Main(string[] args, Func automateFunction) + { + return await Main( + args, + async (AutomationContext context, Fake _) => await automateFunction(context).ConfigureAwait(false) + ) + .ConfigureAwait(false); + } + + /// + /// Main entrypoint to execute an Automate function with input data of type + /// + /// The command line arguments passed into the function by automate + /// The automate function to execute + /// The provided input data + /// This should always be called in your own functions, as it contains the logic to trigger the function automatically. + public static async Task Main(string[] args, Func automateFunction) + where TInput : struct + { + var returnCode = 0; // This is the CLI return code, defaults to 0 (Success), change to 1 to flag a failed run. + + var speckleProjectDataArg = new Argument( + name: "Speckle project data", + description: "The values of the project / model / version that triggered this function" + ); + var functionInputsArg = new Argument( + name: "Function inputs", + description: "The values provided by the function user, matching the function input schema" + ); + var speckleTokenArg = new Argument( + name: "Speckle token", + description: "A token to talk to the Speckle server with" + ); + var rootCommand = new RootCommand(); + rootCommand.AddArgument(speckleProjectDataArg); + rootCommand.AddArgument(functionInputsArg); + rootCommand.AddArgument(speckleTokenArg); + rootCommand.SetHandler( + async (speckleProjectData, functionInputs, speckleToken) => + { + var automationRunData = JsonConvert.DeserializeObject(speckleProjectData); + var functionInputsParsed = JsonConvert.DeserializeObject(functionInputs); + + var context = await RunFunction(automateFunction, automationRunData, speckleToken, functionInputsParsed) + .ConfigureAwait(false); + + if (context.RunStatus != AutomationStatusMapping.Get(AutomationStatus.Succeeded)) + returnCode = 1; // Flag run as failed. + }, + speckleProjectDataArg, + functionInputsArg, + speckleTokenArg + ); + + var schemaFilePathArg = new Argument( + name: "Function inputs file path", + description: "A token to talk to the Speckle server with" + ); + + var generateSchemaCommand = new Command("generate-schema", "Generate JSON schema for the function inputs"); + generateSchemaCommand.AddArgument(schemaFilePathArg); + generateSchemaCommand.SetHandler( + async (schemaFilePath) => + { + var generator = new JSchemaGenerator { ContractResolver = new CamelCasePropertyNamesContractResolver() }; + var schema = generator.Generate(typeof(TInput)); + schema.ToString(global::Newtonsoft.Json.Schema.SchemaVersion.Draft2019_09); + File.WriteAllText(schemaFilePath, schema.ToString()); + }, + schemaFilePathArg + ); + rootCommand.Add(generateSchemaCommand); + + await rootCommand.InvokeAsync(args).ConfigureAwait(false); + + return returnCode; + } +} diff --git a/Automate/Speckle.Automate.Sdk/Schema/AutomationResult.cs b/Automate/Speckle.Automate.Sdk/Schema/AutomationResult.cs new file mode 100644 index 0000000000..9501b235d8 --- /dev/null +++ b/Automate/Speckle.Automate.Sdk/Schema/AutomationResult.cs @@ -0,0 +1,12 @@ +namespace Speckle.Automate.Sdk.Schema; + +public class AutomationResult +{ + public double Elapsed { get; set; } + public string? ResultView { get; set; } + public List ResultVersions { get; set; } = new(); + public List Blobs { get; set; } = new(); + public string RunStatus { get; set; } = AutomationStatusMapping.Get(AutomationStatus.Running); + public string? StatusMessage { get; set; } + public List ObjectResults { get; set; } = new(); +} diff --git a/Automate/Speckle.Automate.Sdk/Schema/AutomationRunData.cs b/Automate/Speckle.Automate.Sdk/Schema/AutomationRunData.cs new file mode 100644 index 0000000000..cc74fff330 --- /dev/null +++ b/Automate/Speckle.Automate.Sdk/Schema/AutomationRunData.cs @@ -0,0 +1,21 @@ +# nullable enable +namespace Speckle.Automate.Sdk.Schema; + +/// +///Values of the project, model and automation that triggere this function run. +/// +public struct AutomationRunData +{ + public string ProjectId { get; set; } + public string ModelId { get; set; } + public string BranchName { get; set; } + public string VersionId { get; set; } + public string SpeckleServerUrl { get; set; } + public string AutomationId { get; set; } + public string AutomationRevisionId { get; set; } + public string AutomationRunId { get; set; } + public string FunctionId { get; set; } + public string FunctionRelease { get; set; } + public string FunctionName { get; set; } + public string? FunctionLogo { get; set; } +} diff --git a/Automate/Speckle.Automate.Sdk/Schema/AutomationStatus.cs b/Automate/Speckle.Automate.Sdk/Schema/AutomationStatus.cs new file mode 100644 index 0000000000..b23ba0feaf --- /dev/null +++ b/Automate/Speckle.Automate.Sdk/Schema/AutomationStatus.cs @@ -0,0 +1,12 @@ +namespace Speckle.Automate.Sdk.Schema; + +/// +/// Set the status of the automation. +/// +public enum AutomationStatus +{ + Initializing, + Running, + Failed, + Succeeded +} diff --git a/Automate/Speckle.Automate.Sdk/Schema/AutomationStatusMapping.cs b/Automate/Speckle.Automate.Sdk/Schema/AutomationStatusMapping.cs new file mode 100644 index 0000000000..e4474b114a --- /dev/null +++ b/Automate/Speckle.Automate.Sdk/Schema/AutomationStatusMapping.cs @@ -0,0 +1,21 @@ +namespace Speckle.Automate.Sdk.Schema; + +public abstract class AutomationStatusMapping +{ + private const string Initializing = "INITIALIZING"; + private const string Running = "RUNNING"; + private const string Failed = "FAILED"; + private const string Succeeded = "SUCCEEDED"; + + public static string Get(AutomationStatus status) + { + return status switch + { + AutomationStatus.Running => Running, + AutomationStatus.Failed => Failed, + AutomationStatus.Succeeded => Succeeded, + AutomationStatus.Initializing => Initializing, + _ => throw new ArgumentOutOfRangeException($"Not valid value for enum {status}") + }; + } +} diff --git a/Automate/Speckle.Automate.Sdk/Schema/BlobUploadResponse.cs b/Automate/Speckle.Automate.Sdk/Schema/BlobUploadResponse.cs new file mode 100644 index 0000000000..f87ef17645 --- /dev/null +++ b/Automate/Speckle.Automate.Sdk/Schema/BlobUploadResponse.cs @@ -0,0 +1,6 @@ +namespace Speckle.Automate.Sdk.Schema; + +public struct BlobUploadResponse +{ + public List UploadResults { get; set; } +} diff --git a/Automate/Speckle.Automate.Sdk/Schema/ObjectResultLevel.cs b/Automate/Speckle.Automate.Sdk/Schema/ObjectResultLevel.cs new file mode 100644 index 0000000000..b868f67880 --- /dev/null +++ b/Automate/Speckle.Automate.Sdk/Schema/ObjectResultLevel.cs @@ -0,0 +1,8 @@ +namespace Speckle.Automate.Sdk.Schema; + +public enum ObjectResultLevel +{ + Info, + Warning, + Error +} diff --git a/Automate/Speckle.Automate.Sdk/Schema/ObjectResultLevelMapping.cs b/Automate/Speckle.Automate.Sdk/Schema/ObjectResultLevelMapping.cs new file mode 100644 index 0000000000..3668fe65ba --- /dev/null +++ b/Automate/Speckle.Automate.Sdk/Schema/ObjectResultLevelMapping.cs @@ -0,0 +1,19 @@ +namespace Speckle.Automate.Sdk.Schema; + +public abstract class ObjectResultLevelMapping +{ + private const string Info = "INFO"; + private const string Warning = "WARNING"; + private const string Error = "ERROR"; + + public static string Get(ObjectResultLevel level) + { + return level switch + { + ObjectResultLevel.Error => Error, + ObjectResultLevel.Warning => Warning, + ObjectResultLevel.Info => Info, + _ => throw new ArgumentOutOfRangeException($"Not valid value for enum {level}") + }; + } +} diff --git a/Automate/Speckle.Automate.Sdk/Schema/ObjectResultValues.cs b/Automate/Speckle.Automate.Sdk/Schema/ObjectResultValues.cs new file mode 100644 index 0000000000..1fdec07f64 --- /dev/null +++ b/Automate/Speckle.Automate.Sdk/Schema/ObjectResultValues.cs @@ -0,0 +1,7 @@ +namespace Speckle.Automate.Sdk.Schema; + +struct ObjectResultValues +{ + public List ObjectResults { get; set; } + public List BlobIds { get; set; } +} diff --git a/Automate/Speckle.Automate.Sdk/Schema/ObjectResults.cs b/Automate/Speckle.Automate.Sdk/Schema/ObjectResults.cs new file mode 100644 index 0000000000..947c072b0c --- /dev/null +++ b/Automate/Speckle.Automate.Sdk/Schema/ObjectResults.cs @@ -0,0 +1,7 @@ +namespace Speckle.Automate.Sdk.Schema; + +struct ObjectResults +{ + public string Version => "1.0.0"; + public ObjectResultValues Values { get; set; } +} diff --git a/Automate/Speckle.Automate.Sdk/Schema/ResultCase.cs b/Automate/Speckle.Automate.Sdk/Schema/ResultCase.cs new file mode 100644 index 0000000000..65f605e550 --- /dev/null +++ b/Automate/Speckle.Automate.Sdk/Schema/ResultCase.cs @@ -0,0 +1,11 @@ +namespace Speckle.Automate.Sdk.Schema; + +public struct ResultCase +{ + public string Category { get; set; } + public string Level { get; set; } + public List ObjectIds { get; set; } + public string? Message { get; set; } + public Dictionary? Metadata { get; set; } + public Dictionary? VisualOverrides { get; set; } +} diff --git a/Automate/Speckle.Automate.Sdk/Schema/UploadResult.cs b/Automate/Speckle.Automate.Sdk/Schema/UploadResult.cs new file mode 100644 index 0000000000..0fce6d4919 --- /dev/null +++ b/Automate/Speckle.Automate.Sdk/Schema/UploadResult.cs @@ -0,0 +1,8 @@ +namespace Speckle.Automate.Sdk.Schema; + +public struct UploadResult +{ + public string BlobId { get; set; } + public string FileName { get; set; } + public int UploadStatus { get; set; } +} diff --git a/Automate/Speckle.Automate.Sdk/Speckle.Automate.Sdk.csproj b/Automate/Speckle.Automate.Sdk/Speckle.Automate.Sdk.csproj new file mode 100644 index 0000000000..c3f4dc5858 --- /dev/null +++ b/Automate/Speckle.Automate.Sdk/Speckle.Automate.Sdk.csproj @@ -0,0 +1,31 @@ + + + + netstandard2.0 + enable + enable + true + Speckle.Automate.Sdk + Speckle.Automate.Sdk + Speckle Automate SDK + $(PackageTags) speckle automation + Speckle.Automate.Sdk + + + + + + + + + + + + + + + + + + + diff --git a/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/GetAutomationStatus.cs b/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/GetAutomationStatus.cs new file mode 100644 index 0000000000..43b8833103 --- /dev/null +++ b/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/GetAutomationStatus.cs @@ -0,0 +1,86 @@ +using GraphQL; +using Speckle.Core.Api; + +namespace Speckle.Automate.Sdk.Tests.Integration; + +public class FunctionRun +{ + public string StatusMessage { get; set; } +} + +public class AutomationRun +{ + public string Status { get; set; } + public IList FunctionRuns { get; set; } +} + +public class AutomationStatus +{ + public string Status { get; set; } + public IList AutomationRuns { get; set; } +} + +public class ModelAutomationStatus +{ + public AutomationStatus AutomationStatus { get; set; } +} + +public class ProjectAutomationStatus +{ + public ModelAutomationStatus Model { get; set; } +} + +public class AutomationStatusResponseModel +{ + public ProjectAutomationStatus Project { get; set; } +} + +public static class AutomationStatusOperations +{ + public static async Task Get(string projectId, string modelId, Client speckleClient) + { + GraphQLRequest query = + new( + """ + query AutomationRuns( + $projectId: String! + $modelId: String! + ) + { + project(id: $projectId) { + model(id: $modelId) { + automationStatus { + id + status + statusMessage + automationRuns { + id + automationId + versionId + createdAt + updatedAt + status + functionRuns { + id + functionId + elapsed + status + contextView + statusMessage + results + resultVersions { + id + } + } + } + } + } + } + } + """, + variables: new { projectId, modelId, } + ); + var response = await speckleClient.ExecuteGraphQLRequest(query); + return response.Project.Model.AutomationStatus; + } +} diff --git a/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/GlobalUsings.cs b/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/GlobalUsings.cs new file mode 100644 index 0000000000..324456763a --- /dev/null +++ b/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; diff --git a/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/Speckle.Automate.Sdk.Tests.Integration.csproj b/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/Speckle.Automate.Sdk.Tests.Integration.csproj new file mode 100644 index 0000000000..6cbda57ccc --- /dev/null +++ b/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/Speckle.Automate.Sdk.Tests.Integration.csproj @@ -0,0 +1,23 @@ + + + + net7.0 + enable + enable + + false + true + $(NoWarn);CA2007 + + + + + + + + + + + + + diff --git a/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/SpeckleAutomate.cs b/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/SpeckleAutomate.cs new file mode 100644 index 0000000000..4960258f35 --- /dev/null +++ b/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/SpeckleAutomate.cs @@ -0,0 +1,234 @@ +using Speckle.Automate.Sdk.Schema; +using Speckle.Core.Api; +using Speckle.Core.Credentials; +using Speckle.Core.Models; +using Speckle.Core.Transports; +using TestsIntegration; +using Utils = Speckle.Automate.Sdk.Tests.Integration.TestAutomateUtils; + +namespace Speckle.Automate.Sdk.Tests.Integration; + +[TestFixture] +public sealed class AutomationContextTest : IDisposable +{ + private async Task AutomationRunData(Base testObject) + { + string projectId = await client.StreamCreate(new() { name = "Automate function e2e test" }); + const string branchName = "main"; + + Branch model = await client.BranchGet(projectId, branchName, 1); + string modelId = model.id; + + string rootObjId = await Operations.Send( + testObject, + new List { new ServerTransport(client.Account, projectId) } + ); + + string versionId = await client.CommitCreate( + new() + { + streamId = projectId, + objectId = rootObjId, + branchName = model.name + } + ); + + var automationName = Utils.RandomString(10); + var automationId = Utils.RandomString(10); + var automationRevisionId = Utils.RandomString(10); + + await Utils.RegisterNewAutomation(projectId, modelId, client, automationId, automationName, automationRevisionId); + + var automationRunId = Utils.RandomString(10); + var functionId = Utils.RandomString(10); + var functionName = "Automation name " + Utils.RandomString(10); + var functionRelease = Utils.RandomString(10); + + return new AutomationRunData + { + ProjectId = projectId, + ModelId = modelId, + BranchName = branchName, + VersionId = versionId, + SpeckleServerUrl = client.ServerUrl, + AutomationId = automationId, + AutomationRevisionId = automationRevisionId, + AutomationRunId = automationRunId, + FunctionId = functionId, + FunctionName = functionName, + FunctionRelease = functionRelease, + }; + } + + private Client client; + private Account account; + + [OneTimeSetUp] + public async Task Setup() + { + account = await Fixtures.SeedUser().ConfigureAwait(false); + client = new Client(account); + } + + [Test] + public async Task TestFunctionRun() + { + var automationRunData = await AutomationRunData(Utils.TestObject()); + var automationContext = await AutomationRunner.RunFunction( + TestAutomateFunction.Run, + automationRunData, + account.token, + new TestFunctionInputs { ForbiddenSpeckleType = "Base" } + ); + + Assert.That(automationContext.RunStatus, Is.EqualTo("FAILED")); + + var status = await AutomationStatusOperations.Get( + automationRunData.ProjectId, + automationRunData.ModelId, + automationContext.SpeckleClient + ); + + Assert.That(status.Status, Is.EqualTo(automationContext.RunStatus)); + var statusMessage = status.AutomationRuns[0].FunctionRuns[0].StatusMessage; + + Assert.That(statusMessage, Is.EqualTo(automationContext.AutomationResult.StatusMessage)); + } + + [Test] + public async Task TestFileUploads() + { + var automationRunData = await AutomationRunData(Utils.TestObject()); + var automationContext = await AutomationContext.Initialize(automationRunData, account.token); + + string filePath = $"./{Utils.RandomString(10)}"; + await File.WriteAllTextAsync(filePath, "foobar"); + try + { + await automationContext.StoreFileResult(filePath); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + + File.Delete(filePath); + Assert.That(automationContext.AutomationResult.Blobs, Has.Count.EqualTo(1)); + } + + [Test] + public async Task TestCreateVersionInProject() + { + var automationRunData = await AutomationRunData(Utils.TestObject()); + var automationContext = await AutomationContext.Initialize(automationRunData, account.token); + + const string branchName = "test-branch"; + const string commitMsg = "automation test"; + + await automationContext.CreateNewVersionInProject(Utils.TestObject(), branchName, commitMsg); + + var branch = await automationContext.SpeckleClient + .BranchGet(automationRunData.ProjectId, branchName, 1) + .ConfigureAwait(false); + + Assert.NotNull(branch); + Assert.That(branch.name, Is.EqualTo(branchName)); + Assert.That(branch.commits.items[0].message, Is.EqualTo(commitMsg)); + } + + [Test] + public async Task TestCreateVersionInProject_ThrowsErrorForSameModel() + { + var automationRunData = await AutomationRunData(Utils.TestObject()); + var automationContext = await AutomationContext.Initialize(automationRunData, account.token); + + var branchName = automationRunData.BranchName; + const string commitMsg = "automation test"; + + Assert.ThrowsAsync(async () => + { + await automationContext.CreateNewVersionInProject(Utils.TestObject(), branchName, commitMsg); + }); + } + + [Test] + public async Task TestSetContextView() + { + var automationRunData = await AutomationRunData(Utils.TestObject()); + var automationContext = await AutomationContext.Initialize(automationRunData, account.token); + + automationContext.SetContextView(); + + Assert.That(automationContext.AutomationResult.ResultView, Is.Not.Null); + string originModelView = $"{automationRunData.ModelId}@{automationRunData.VersionId}"; + Assert.That(automationContext.AutomationResult.ResultView.EndsWith($"models/{originModelView}"), Is.True); + + await automationContext.ReportRunStatus(); + var dummyContext = "foo@bar"; + + automationContext.AutomationResult.ResultView = null; + automationContext.SetContextView(new List { dummyContext }, true); + + Assert.That(automationContext.AutomationResult.ResultView, Is.Not.Null); + Assert.That( + automationContext.AutomationResult.ResultView.EndsWith($"models/{originModelView},{dummyContext}"), + Is.True + ); + + await automationContext.ReportRunStatus(); + + automationContext.AutomationResult.ResultView = null; + automationContext.SetContextView(new List { dummyContext }, false); + + Assert.That(automationContext.AutomationResult.ResultView, Is.Not.Null); + Assert.That(automationContext.AutomationResult.ResultView.EndsWith($"models/{dummyContext}"), Is.True); + + await automationContext.ReportRunStatus(); + + automationContext.AutomationResult.ResultView = null; + + Assert.Throws(() => + { + automationContext.SetContextView(null, false); + }); + + await automationContext.ReportRunStatus(); + } + + [Test] + public async Task TestReportRunStatus_Succeeded() + { + var automationRunData = await AutomationRunData(Utils.TestObject()); + var automationContext = await AutomationContext.Initialize(automationRunData, account.token); + + Assert.That(automationContext.RunStatus, Is.EqualTo(AutomationStatusMapping.Get(Schema.AutomationStatus.Running))); + + automationContext.MarkRunSuccess("This is a success message"); + + Assert.That( + automationContext.RunStatus, + Is.EqualTo(AutomationStatusMapping.Get(Schema.AutomationStatus.Succeeded)) + ); + } + + [Test] + public async Task TestReportRunStatus_Failed() + { + var automationRunData = await AutomationRunData(Utils.TestObject()); + var automationContext = await AutomationContext.Initialize(automationRunData, account.token); + + Assert.That(automationContext.RunStatus, Is.EqualTo(AutomationStatusMapping.Get(Schema.AutomationStatus.Running))); + + var message = "This is a failure message"; + automationContext.MarkRunFailed(message); + + Assert.That(automationContext.RunStatus, Is.EqualTo(AutomationStatusMapping.Get(Schema.AutomationStatus.Failed))); + Assert.That(automationContext.StatusMessage, Is.EqualTo(message)); + } + + public void Dispose() + { + client.Dispose(); + } +} diff --git a/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/TestAutomateFunction.cs b/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/TestAutomateFunction.cs new file mode 100644 index 0000000000..e64be99771 --- /dev/null +++ b/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/TestAutomateFunction.cs @@ -0,0 +1,43 @@ +using System.ComponentModel.DataAnnotations; + +namespace Speckle.Automate.Sdk.Tests.Integration; + +public struct TestFunctionInputs +{ + [Required] + public string ForbiddenSpeckleType { get; set; } +} + +public static class TestAutomateFunction +{ + public static async Task Run(AutomationContext automateContext, TestFunctionInputs testFunctionInputs) + { + var versionRootObject = await automateContext.ReceiveVersion(); + + int count = 0; + if (versionRootObject.speckle_type == testFunctionInputs.ForbiddenSpeckleType) + { + if (versionRootObject.id is null) + throw new InvalidOperationException("Cannot operate on objects without their ids"); + + automateContext.AttachErrorToObjects( + "", + new[] { versionRootObject.id }, + $"This project should not contain the type: {testFunctionInputs.ForbiddenSpeckleType} " + ); + count += 1; + } + + if (count > 0) + { + automateContext.MarkRunFailed( + "Automation failed: " + + $"Found {count} object that have a forbidden speckle type: {testFunctionInputs.ForbiddenSpeckleType}" + ); + } + else + { + automateContext.MarkRunSuccess("No forbidden types found."); + } + } +} diff --git a/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/TestAutomateUtils.cs b/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/TestAutomateUtils.cs new file mode 100644 index 0000000000..80fb5326cc --- /dev/null +++ b/Automate/Tests/Speckle.Automate.Sdk.Tests.Integration/TestAutomateUtils.cs @@ -0,0 +1,69 @@ +using System.Diagnostics.CodeAnalysis; +using GraphQL; +using Speckle.Core.Api; +using Speckle.Core.Models; + +namespace Speckle.Automate.Sdk.Tests.Integration; + +public static class TestAutomateUtils +{ + [SuppressMessage("Security", "CA5394:Do not use insecure randomness")] + public static string RandomString(int length) + { + Random rand = new(); + const string pool = "abcdefghijklmnopqrstuvwxyz0123456789"; + var chars = Enumerable.Range(0, length).Select(_ => pool[rand.Next(0, pool.Length)]); + return new string(chars.ToArray()); + } + + public static Base TestObject() + { + Base rootObject = new() { ["foo"] = "bar" }; + return rootObject; + } + + public static async Task RegisterNewAutomation( + string projectId, + string modelId, + Client speckleClient, + string automationId, + string automationName, + string automationRevisionId + ) + { + GraphQLRequest query = + new( + query: """ + mutation CreateAutomation( + $projectId: String! + $modelId: String! + $automationName: String! + $automationId: String! + $automationRevisionId: String! + ) { + automationMutations { + create( + input: { + projectId: $projectId + modelId: $modelId + automationName: $automationName + automationId: $automationId + automationRevisionId: $automationRevisionId + } + ) + } + } + """, + variables: new + { + projectId, + modelId, + automationName, + automationId, + automationRevisionId, + } + ); + + await speckleClient.ExecuteGraphQLRequest(query); + } +} diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/AddOnMain.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/AddOnMain.cpp index b7f1f7c4ee..ae51e406e4 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/AddOnMain.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/AddOnMain.cpp @@ -15,6 +15,7 @@ #include "Commands/GetBeamData.hpp" #include "Commands/GetColumnData.hpp" #include "Commands/GetElementBaseData.hpp" +#include "Commands/GetGridElementData.hpp" #include "Commands/GetObjectData.hpp" #include "Commands/GetSlabData.hpp" #include "Commands/GetRoofData.hpp" @@ -28,6 +29,7 @@ #include "Commands/CreateWindow.hpp" #include "Commands/CreateBeam.hpp" #include "Commands/CreateColumn.hpp" +#include "Commands/CreateGridElement.hpp" #include "Commands/CreateObject.hpp" #include "Commands/CreateRoof.hpp" #include "Commands/CreateSkylight.hpp" @@ -197,6 +199,7 @@ static GSErrCode RegisterAddOnCommands () CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); + CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); @@ -208,6 +211,7 @@ static GSErrCode RegisterAddOnCommands () CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); + CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateBeam.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateBeam.cpp index bc7a00431e..49ab23ff50 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateBeam.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateBeam.cpp @@ -40,6 +40,10 @@ GSErrCode CreateBeam::GetElementFromObjectState (const GS::ObjectState& os, if (err != NoError) return err; + err = GetElementBaseFromObjectState (os, element, beamMask); + if (err != NoError) + return err; + // Positioning Objects::Point3D startPoint; if (os.Contains (Beam::begC)) { @@ -663,6 +667,7 @@ GSErrCode CreateBeam::GetElementFromObjectState (const GS::ObjectState& os, return NoError; } + GS::String CreateBeam::GetName () const { return CreateBeamCommandName; diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateColumn.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateColumn.cpp index 67a347ba2c..bc57f3b24d 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateColumn.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateColumn.cpp @@ -43,6 +43,10 @@ GSErrCode CreateColumn::GetElementFromObjectState (const GS::ObjectState& os, if (err != NoError) return err; + err = GetElementBaseFromObjectState (os, element, elementMask); + if (err != NoError) + return err; + // Positioning - geometry Objects::Point3D origoPos; if (os.Contains (Column::origoPos)) { diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateCommand.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateCommand.cpp index eead875a0f..e5010babe6 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateCommand.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateCommand.cpp @@ -40,11 +40,82 @@ void CreateCommand::GetStoryFromObjectState (const GS::ObjectState& os, const do { Objects::Level level; os.Get (ElementBase::Level, level); + + API_StoryInfo storyInfo; + BNZeroMemory (&storyInfo, sizeof (API_StoryInfo)); + ACAPI_Environment(APIEnv_GetStorySettingsID, &storyInfo); + + API_StoryCmdType command; + + for (short i = 0; i < (storyInfo.lastStory - storyInfo.firstStory + 1); ++i) { + const API_StoryType& actStory = (*storyInfo.data)[i]; + const API_StoryType& nextStory = (*storyInfo.data)[i + 1]; + + if (fabs(level.elevation - actStory.level) < EPS) { + break; + } else if (actStory.level + EPS < level.elevation && level.elevation < nextStory.level - EPS) { + BNZeroMemory (&command, sizeof (API_StoryCmdType)); + GS::ucscpy (command.uName, level.name.ToUStr ().Get ()); + command.action = APIStory_InsAbove; + command.height = level.elevation - actStory.level; + command.index = actStory.index; + ACAPI_Environment (APIEnv_ChangeStorySettingsID, &command); + + BNZeroMemory (&command, sizeof (API_StoryCmdType)); + command.action = APIStory_SetHeight; + command.height = (*storyInfo.data)[i + 1].level - level.elevation; + command.index = actStory.index + 1; + ACAPI_Environment (APIEnv_ChangeStorySettingsID, &command); + + break; + } else if (level.elevation < actStory.level - EPS) { + BNZeroMemory (&command, sizeof (API_StoryCmdType)); + GS::ucscpy (command.uName, level.name.ToUStr ().Get ()); + command.action = APIStory_InsBelow; + command.height = actStory.level - level.elevation; + command.index = actStory.index; + ACAPI_Environment (APIEnv_ChangeStorySettingsID, &command); + + break; + } else if (i == (storyInfo.lastStory - storyInfo.firstStory) && nextStory.level <= level.elevation) { + BNZeroMemory (&command, sizeof (API_StoryCmdType)); + GS::ucscpy (command.uName, level.name.ToUStr ().Get ()); + command.action = APIStory_InsAbove; + command.height = level.elevation - actStory.level; + command.index = storyInfo.lastStory; + ACAPI_Environment (APIEnv_ChangeStorySettingsID, &command); + } + } + Utility::SetStoryLevelAndFloor (elementLevel, level.floorIndex, relativeLevel); floorIndex = level.floorIndex; } +GSErrCode CreateCommand::GetElementBaseFromObjectState (const GS::ObjectState& os, API_Element& element, API_Element& elementMask) const +{ + GSErrCode err = NoError; + // layer + GS::UniString layer; + if (os.Contains (ElementBase::Layer)) { + os.Get (ElementBase::Layer, layer); + + API_Attribute attribute; + BNZeroMemory (&attribute, sizeof (API_Attribute)); + attribute.header.typeID = API_LayerID; + attribute.header.uniStringNamePtr = &layer; + err = ACAPI_Attribute_Get (&attribute); + + if (err == NoError) { + element.header.layer = attribute.header.index; + ACAPI_ELEMENT_MASK_SET (elementMask, API_Elem_Head, layer); + } + } + + return err; +} + + GS::ObjectState CreateCommand::Execute (const GS::ObjectState& parameters, GS::ProcessControl& /*processControl*/) const { GS::ObjectState result; @@ -155,7 +226,7 @@ GS::ObjectState CreateCommand::Execute (const GS::ObjectState& parameters, GS::P } -GS::ErrCode CreateCommand::ImportClassificationsAndProperties (const GS::ObjectState& os, API_Guid& elemGuid) const +GSErrCode CreateCommand::ImportClassificationsAndProperties (const GS::ObjectState& os, API_Guid& elemGuid) const { GSErrCode err = NoError; GS::Array classifications; diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateCommand.hpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateCommand.hpp index 33a02d600f..4264c729af 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateCommand.hpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateCommand.hpp @@ -39,10 +39,14 @@ class CreateCommand : public BaseCommand { short& floorIndex, double& relativeLevel) const; + GSErrCode GetElementBaseFromObjectState (const GS::ObjectState& os, + API_Element& element, + API_Element& elementMask) const; + public: virtual GS::ObjectState Execute (const GS::ObjectState& parameters, GS::ProcessControl& processControl) const override; - GS::ErrCode ImportClassificationsAndProperties (const GS::ObjectState& os, API_Guid& elemGuid) const; + GSErrCode ImportClassificationsAndProperties (const GS::ObjectState& os, API_Guid& elemGuid) const; }; } diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateDoor.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateDoor.cpp index bcb2de9d75..381589b14a 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateDoor.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateDoor.cpp @@ -42,6 +42,10 @@ GSErrCode CreateDoor::GetElementFromObjectState (const GS::ObjectState& os, Utility::SetElementType (element.header, API_DoorID); + err = GetElementBaseFromObjectState (os, element, elementMask); + if (err != NoError) + return err; + *marker = new API_SubElement (); BNZeroMemory (*marker, sizeof (API_SubElement)); err = Utility::GetBaseElementData (element, &memo, marker, log); diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateGridElement.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateGridElement.cpp new file mode 100644 index 0000000000..2b17b910a8 --- /dev/null +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateGridElement.cpp @@ -0,0 +1,159 @@ +#include "CreateGridElement.hpp" +#include "ResourceIds.hpp" +#include "ObjectState.hpp" +#include "APIHelper.hpp" +#include "Utility.hpp" +#include "Objects/Level.hpp" +#include "Objects/Point.hpp" +#include "FieldNames.hpp" +#include "GenArc2DData.h" +using namespace FieldNames; + +namespace AddOnCommands { + + +GS::String CreateGridElement::GetFieldName () const +{ + return FieldNames::GridElements; +} + + +GS::UniString CreateGridElement::GetUndoableCommandName () const +{ + return "CreateSpeckleGridElement"; +} + + +GSErrCode CreateGridElement::GetElementFromObjectState (const GS::ObjectState& os, + API_Element& element, + API_Element& elementMask, + API_ElementMemo& memo, + GS::UInt64& memoMask, + API_SubElement** /*marker*/, + AttributeManager& /*attributeManager*/, + LibpartImportManager& /*libpartImportManager*/, + GS::Array& log) const +{ + GSErrCode err = NoError; + + Utility::SetElementType (element.header, API_ElemType (API_ObjectID, APIVarId_GridElement)); + + err = Utility::GetBaseElementData (element, &memo, nullptr, log); + if (err != NoError) + return err; + + Objects::Point3D begin, end; + GS::UniString markerText; + bool isArc = false; + double length = .0; + double radius = .0; + double arcAngle = .0; + + if (os.Contains (GridElement::begin) && os.Contains (GridElement::end)) { + os.Get (GridElement::begin, begin); + os.Get (GridElement::end, end); + } + + if (os.Contains (GridElement::markerText)) { + os.Get (GridElement::markerText, markerText); + } + + if (os.Contains (GridElement::isArc)) { + os.Get (GridElement::isArc, isArc); + } + + if (os.Contains (GridElement::arcAngle)) { + os.Get (GridElement::arcAngle, arcAngle); + } + + if (!isArc) { + // position + element.object.pos = begin.ToAPI_Coord (); + ACAPI_ELEMENT_MASK_SET (elementMask, API_ObjectType, pos); + + // angle + Vector2D beginToEnd (end.x - begin.x, end.y - begin.y); + element.object.angle = beginToEnd.CalcAngleToReference (Vector2D (1.0, 0)); + ACAPI_ELEMENT_MASK_SET (elementMask, API_ObjectType, angle); + + // length + length = beginToEnd.GetLength (); + + } else { + // offset + GenArc arc = GenArc::CreateCircleArc (Point2D(begin.x,begin.y), Point2D(end.x, end.y), arcAngle); + Point2D origo = arc.GetOrigo (); + element.object.pos.x = origo.GetX (); + element.object.pos.y = origo.GetY (); + + Point2D beginPoint (begin.x, begin.y); + Point2D endPoint (end.x, end.y); + + Geometry::Transformation2D transformationGlobal = Geometry::Transformation2D::CreateTranslation (Vector2D(origo) * -1.0); + beginPoint = transformationGlobal.Apply (beginPoint); + endPoint = transformationGlobal.Apply (endPoint); + + // rotation + bool mirror = (arcAngle < 0.0); + + Vector2D beginVector (beginPoint); + element.object.angle = beginVector.CalcAngleToReference (Vector2D (mirror ? -1.0 : 1.0, 0)); + ACAPI_ELEMENT_MASK_SET (elementMask, API_ObjectType, angle); + + // mirror + if (mirror) { + arcAngle *= -1.0; + element.object.reflected = true; + ACAPI_ELEMENT_MASK_SET (elementMask, API_ObjectType, reflected); + } + + // radius + radius = beginVector.GetLength (); + } + + GSSize addParCount = BMGetHandleSize (reinterpret_cast(memo.params)) / sizeof (API_AddParType); + + for (Int32 i = 0; i < addParCount; ++i) { + API_AddParType& parameter = (*memo.params)[i]; + + if (CHCompareCStrings (parameter.name, "AC_MarkerText_1", CS_CaseSensitive) == 0) { + GS::ucscpy (parameter.value.uStr, markerText.ToUStr ().Get ()); + } else if (CHCompareCStrings (parameter.name, "AC_LineVisibility_i", CS_CaseSensitive) == 0) { + parameter.value.real = (int)1; + } else if (CHCompareCStrings (parameter.name, "AC_StaggerDist", CS_CaseSensitive) == 0) { + parameter.value.real = .0; + } else if (CHCompareCStrings (parameter.name, "AC_Type_i", CS_CaseSensitive) == 0) { + if (isArc) { + parameter.value.real = (int)2; + } else { + parameter.value.real = (int)1; + } + } else if (CHCompareCStrings (parameter.name, "AC_Length", CS_CaseSensitive) == 0) { + if (!isArc) { + parameter.value.real = length; + } + } else if (CHCompareCStrings (parameter.name, "AC_Angle", CS_CaseSensitive) == 0) { + if (isArc) { + parameter.value.real = arcAngle; + } + } else if (CHCompareCStrings (parameter.name, "AC_Radius", CS_CaseSensitive) == 0) { + if (isArc) { + parameter.value.real = radius; + } + } + } + + memoMask = APIMemoMask_AddPars; + + return NoError; +} + + +GS::String CreateGridElement::GetName () const +{ + return CreateGridElementCommandName; +} + + +} + // namespace AddOnCommands diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateGridElement.hpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateGridElement.hpp new file mode 100644 index 0000000000..1ab83dad9a --- /dev/null +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateGridElement.hpp @@ -0,0 +1,32 @@ +#ifndef CREATE_GRIDELEMENT_HPP +#define CREATE_GRIDELEMENT_HPP + +#include "CreateCommand.hpp" + + +namespace AddOnCommands { + + +class CreateGridElement : public CreateCommand { + GS::String GetFieldName () const override; + GS::UniString GetUndoableCommandName () const override; + + GSErrCode GetElementFromObjectState (const GS::ObjectState& os, + API_Element& element, + API_Element& elementMask, + API_ElementMemo& memo, + GS::UInt64& memoMask, + API_SubElement** marker, + AttributeManager& attributeManager, + LibpartImportManager& libpartImportManager, + GS::Array& log) const override; + +public: + virtual GS::String GetName () const override; +}; + + +} + + +#endif \ No newline at end of file diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateObject.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateObject.cpp index ebbd86b7b1..05d7a380d9 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateObject.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateObject.cpp @@ -44,6 +44,10 @@ GSErrCode CreateObject::GetElementFromObjectState (const GS::ObjectState& os, if (err != NoError) return err; + err = GetElementBaseFromObjectState (os, element, elementMask); + if (err != NoError) + return err; + // get the mesh GS::Array modelIds; os.Get (Model::ModelIds, modelIds); diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateOpeningBase.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateOpeningBase.cpp index 02fd29d487..813a11ce43 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateOpeningBase.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateOpeningBase.cpp @@ -54,7 +54,7 @@ bool CreateOpeningBase::CheckEnvironment (const GS::ObjectState& os, API_Element return false; // Set its parent - API_ElemTypeID elementType = Utility::GetElementType (elem.header); + API_ElemTypeID elementType = Utility::GetElementType (elem.header).typeID; if (elementType == API_DoorID) { element.door.owner = parentArchicadId; } else if (elementType == API_WindowID) { diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateRoof.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateRoof.cpp index d7c0f6046e..4eb3fbc1cc 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateRoof.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateRoof.cpp @@ -41,6 +41,10 @@ GSErrCode CreateRoof::GetElementFromObjectState (const GS::ObjectState& os, if (err != NoError) return err; + err = GetElementBaseFromObjectState (os, element, elementMask); + if (err != NoError) + return err; + // The structure of the roof if (os.Contains (Roof::RoofClassName)) { GS::UniString roofClassName; diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateShell.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateShell.cpp index cd18cc6db2..965f4175c1 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateShell.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateShell.cpp @@ -42,6 +42,10 @@ GSErrCode CreateShell::GetElementFromObjectState (const GS::ObjectState& os, if (err != NoError) return err; + err = GetElementBaseFromObjectState (os, element, elementMask); + if (err != NoError) + return err; + // The structure of the shell if (os.Contains (Shell::ShellClassName)) { GS::UniString shellClassName; diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateSkylight.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateSkylight.cpp index e67688519e..dab3d245cb 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateSkylight.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateSkylight.cpp @@ -49,6 +49,10 @@ GSErrCode CreateSkylight::GetElementFromObjectState (const GS::ObjectState& os, if (err != NoError) return err; + err = GetElementBaseFromObjectState (os, element, elementMask); + if (err != NoError) + return err; + if (!CheckEnvironment (os, element)) return Error; diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateSlab.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateSlab.cpp index 32f19f4b3a..99fde7a864 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateSlab.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateSlab.cpp @@ -43,6 +43,10 @@ GSErrCode CreateSlab::GetElementFromObjectState (const GS::ObjectState& os, if (err != NoError) return err; + err = GetElementBaseFromObjectState (os, element, mask); + if (err != NoError) + return err; + // Geometry and positioning memoMask = APIMemoMask_Polygon | APIMemoMask_SideMaterials | APIMemoMask_EdgeTrims; diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateWall.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateWall.cpp index 0d436d55f8..c7cf90d42f 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateWall.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateWall.cpp @@ -45,6 +45,10 @@ GSErrCode CreateWall::GetElementFromObjectState (const GS::ObjectState& os, if (err != NoError) return err; + err = GetElementBaseFromObjectState (os, element, elementMask); + if (err != NoError) + return err; + memoMask = APIMemoMask_Polygon; // Wall geometry diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateWindow.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateWindow.cpp index dd39575f2a..aac1284db6 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateWindow.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateWindow.cpp @@ -43,6 +43,10 @@ GSErrCode CreateWindow::GetElementFromObjectState (const GS::ObjectState& os, Utility::SetElementType (element.header, API_WindowID); + err = GetElementBaseFromObjectState (os, element, elementMask); + if (err != NoError) + return err; + *marker = new API_SubElement (); BNZeroMemory (*marker, sizeof (API_SubElement)); err = Utility::GetBaseElementData (element, &memo, marker, log); diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateZone.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateZone.cpp index edd5cfc022..ceaa070a20 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateZone.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateZone.cpp @@ -43,6 +43,10 @@ GSErrCode CreateZone::GetElementFromObjectState (const GS::ObjectState& os, if (err != NoError) return err; + err = GetElementBaseFromObjectState (os, element, mask); + if (err != NoError) + return err; + memoMask = APIMemoMask_Polygon; // The shape of the zone diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetDataCommand.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetDataCommand.cpp index 8ff0987b34..acea1e8554 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetDataCommand.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetDataCommand.cpp @@ -26,30 +26,32 @@ GS::ErrCode GetDataCommand::ExportClassificationsAndProperties (const API_Elemen if (err != NoError) return err; - const auto& classificationListAdder = os.AddList (FieldNames::ElementBase::Classifications); - for (const auto& systemItemPair : systemItemPairs) { - GS::ObjectState classificationOs; - API_ClassificationSystem system; - system.guid = systemItemPair.first; - err = ACAPI_Classification_GetClassificationSystem (system); - if (err != NoError) - break; - - classificationOs.Add (FieldNames::ElementBase::Classification::System, system.name); - - API_ClassificationItem item; - item.guid = systemItemPair.second; - err = ACAPI_Classification_GetClassificationItem (item); - if (err != NoError) - break; - - if (!item.id.IsEmpty()) - classificationOs.Add (FieldNames::ElementBase::Classification::Code, item.id); - - if (!item.name.IsEmpty()) - classificationOs.Add (FieldNames::ElementBase::Classification::Name, item.name); - - classificationListAdder (classificationOs); + if (systemItemPairs.GetSize () != 0) { + const auto& classificationListAdder = os.AddList (FieldNames::ElementBase::Classifications); + for (const auto& systemItemPair : systemItemPairs) { + GS::ObjectState classificationOs; + API_ClassificationSystem system; + system.guid = systemItemPair.first; + err = ACAPI_Classification_GetClassificationSystem (system); + if (err != NoError) + break; + + classificationOs.Add (FieldNames::ElementBase::Classification::System, system.name); + + API_ClassificationItem item; + item.guid = systemItemPair.second; + err = ACAPI_Classification_GetClassificationItem (item); + if (err != NoError) + break; + + if (!item.id.IsEmpty ()) + classificationOs.Add (FieldNames::ElementBase::Classification::Code, item.id); + + if (!item.name.IsEmpty ()) + classificationOs.Add (FieldNames::ElementBase::Classification::Name, item.name); + + classificationListAdder (classificationOs); + } } return err; @@ -68,6 +70,14 @@ GS::ErrCode GetDataCommand::SerializeElementType(const API_Element& elem, const os.Add(FieldNames::ElementBase::ApplicationId, APIGuidToString (elem.header.guid)); + API_Attribute attribute; + BNZeroMemory (&attribute, sizeof (API_Attribute)); + attribute.header.typeID = API_LayerID; + attribute.header.index = elem.header.layer; + if (NoError == ACAPI_Attribute_Get (&attribute)) { + os.Add(FieldNames::ElementBase::Layer, GS::UniString{attribute.header.name}); + } + err = ExportClassificationsAndProperties (elem, os); return err; @@ -98,7 +108,7 @@ GS::ObjectState GetDataCommand::Execute (const GS::ObjectState& parameters, // check for elem type if (API_ZombieElemID != GetElemTypeID ()) { - API_ElemTypeID elementType = Utility::GetElementType (element.header); + API_ElemTypeID elementType = Utility::GetElementType (element.header).typeID; if (elementType != GetElemTypeID ()) { continue; diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetElementTypes.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetElementTypes.cpp index a0f52f62a5..74ddda0f5f 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetElementTypes.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetElementTypes.cpp @@ -26,9 +26,13 @@ GS::ObjectState GetElementTypes::Execute (const GS::ObjectState& parameters, GS: const auto& listAdder = result.AddList (ElementBase::ElementTypes); for (const GS::UniString& id : ids) { API_Guid guid = APIGuidFromString (id.ToCStr ()); - API_ElemTypeID elementTypeId = Utility::GetElementType (guid); - GS::UniString elemType = elementNames.Get (elementTypeId); - GS::ObjectState listElem{ElementBase::ApplicationId, id, ElementBase::ElementType, elemType}; + API_ElemType elementType = Utility::GetElementType (guid); + + GS::UniString elementTypeName; + if (NoError != GetElementTypeName (elementType, elementTypeName)) + continue; + + GS::ObjectState listElem{ElementBase::ApplicationId, id, ElementBase::ElementType, elementTypeName}; listAdder (listElem); } @@ -36,4 +40,4 @@ GS::ObjectState GetElementTypes::Execute (const GS::ObjectState& parameters, GS: } -} \ No newline at end of file +} diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetGridElementData.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetGridElementData.cpp new file mode 100644 index 0000000000..544a8fea76 --- /dev/null +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetGridElementData.cpp @@ -0,0 +1,115 @@ +#include "GetGridElementData.hpp" +#include "ResourceIds.hpp" +#include "ObjectState.hpp" +#include "Utility.hpp" +#include "Objects/Level.hpp" +#include "Objects/Point.hpp" +#include "RealNumber.h" +#include "FieldNames.hpp" +#include "TypeNameTables.hpp" +using namespace FieldNames; + + +namespace AddOnCommands { + + +GS::String GetGridElementData::GetFieldName () const +{ + return GridElements; +} + + +API_ElemTypeID GetGridElementData::GetElemTypeID () const +{ + return API_ObjectID; +} + + +GS::ErrCode GetGridElementData::SerializeElementType (const API_Element& element, + const API_ElementMemo& memo, + GS::ObjectState& os) const +{ + GS::ErrCode err = NoError; + err = GetDataCommand::SerializeElementType (element, memo, os); + if (NoError != err) + return err; + + GS::UniString markerText; + double angle = element.object.angle; + double length = .0; + int type = 0; + double arcAngle = .0; + double radius = .0; + + { + GSSize addParCount = BMGetHandleSize (reinterpret_cast(memo.params)) / sizeof (API_AddParType); + + for (Int32 i = 0; i < addParCount; ++i) { + API_AddParType& parameter = (*memo.params)[i]; + if (CHCompareCStrings (parameter.name, "AC_Length", CS_CaseSensitive) == 0) { + length = parameter.value.real; + } else if (CHCompareCStrings (parameter.name, "AC_MarkerText_1", CS_CaseSensitive) == 0) { + markerText = parameter.value.uStr; + } else if (CHCompareCStrings (parameter.name, "AC_Type_i", CS_CaseSensitive) == 0) { + type = (int)parameter.value.real; + } else if (CHCompareCStrings (parameter.name, "AC_Angle", CS_CaseSensitive) == 0) { + arcAngle = parameter.value.real; + } else if (CHCompareCStrings (parameter.name, "AC_Radius", CS_CaseSensitive) == 0) { + radius = parameter.value.real; + } + } + } + + bool isArc = (type == 2); + + Vector2D beginVector, endVector; + if (!isArc) { + beginVector = Vector2D (.0, .0); + endVector = Vector2D (length, .0); + } else { + beginVector = Vector2D (radius, .0); + Geometry::Transformation2D rotationEnd = Geometry::Transformation2D::CreateOrigoRotation (arcAngle); + + endVector = rotationEnd.Apply (beginVector); + } + + { + // mirror + if (element.object.reflected) { + Geometry::Transformation2D rotationMirrorY = Geometry::Transformation2D::CreateMirrorY (); + beginVector = rotationMirrorY.Apply (beginVector); + endVector = rotationMirrorY.Apply (endVector); + + arcAngle *= -1; + } + + // rotation + Geometry::Transformation2D rotationGlobal = Geometry::Transformation2D::CreateOrigoRotation (angle); + beginVector = rotationGlobal.Apply (beginVector); + endVector = rotationGlobal.Apply (endVector); + + // offset + Geometry::Transformation2D transformationGlobal = Geometry::Transformation2D::CreateTranslation (Vector2D (element.object.pos.x, element.object.pos.y)); + Point2D beginPoint = transformationGlobal.Apply (Point2D (beginVector)); + Point2D endPoint = transformationGlobal.Apply (Point2D (endVector)); + + double z = Utility::GetStoryLevel (element.object.head.floorInd) + element.object.level; + os.Add (GridElement::begin, Objects::Point3D (beginPoint.x, beginPoint.y, z)); + os.Add (GridElement::end, Objects::Point3D (endPoint.x, endPoint.y, z)); + } + + os.Add (GridElement::markerText, markerText); + os.Add (GridElement::isArc, isArc); + os.Add (GridElement::arcAngle, arcAngle); + + return NoError; +} + + +GS::String GetGridElementData::GetName () const +{ + return GetGridElementCommandName; +} + + +} // namespace AddOnCommands diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetGridElementData.hpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetGridElementData.hpp new file mode 100644 index 0000000000..0edf0a6c4f --- /dev/null +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetGridElementData.hpp @@ -0,0 +1,24 @@ +#ifndef GET_GRID_DATA_HPP +#define GET_GRID_DATA_HPP + +#include "GetDataCommand.hpp" + +namespace AddOnCommands { + + +class GetGridElementData : public GetDataCommand { + GS::String GetFieldName () const override; + API_ElemTypeID GetElemTypeID () const override; + GS::ErrCode SerializeElementType (const API_Element& elem, + const API_ElementMemo& memo, + GS::ObjectState& os) const override; + +public: + virtual GS::String GetName () const override; +}; + + +} + + +#endif diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetModelForElements.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetModelForElements.cpp index 9b43120f1e..dd5c4587ef 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetModelForElements.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetModelForElements.cpp @@ -70,11 +70,19 @@ static void GetModelInfoForElement (const Modeler::Elem& elem, for (const auto& body : elem.TessellatedBodies ()) { UInt32 vetrexOffset = modelInfo.GetVertices ().GetSize (); + // vertices for (UInt32 vertexIdx = 0; vertexIdx < body.GetVertexCount (); ++vertexIdx) { const auto coord = body.GetVertexPoint (vertexIdx, transformation); modelInfo.AddVertex (ModelInfo::Vertex (coord.x, coord.y, coord.z)); } + // edges + for (ULong edgeIdx = 0; edgeIdx < body.GetEdgeCount (); ++edgeIdx) { + const EDGE& edge = body.GetConstEdge (edgeIdx); + modelInfo.AddEdge (ModelInfo::EdgeId (edge.vert1 + vetrexOffset, edge.vert2 + vetrexOffset), ModelInfo::EdgeData (ModelInfo::VisibleEdge, edge.pgon1, edge.pgon2)); + } + + // polygons CollectPolygonsFromBody (body, attributes, vetrexOffset, modelInfo); } } @@ -178,7 +186,7 @@ static GS::Array CheckForSubelements (const API_Guid& applicationId) return GS::Array (); } - switch (Utility::GetElementType (header)) { + switch (Utility::GetElementType (header).typeID) { case API_CurtainWallID: return GetCurtainWallSubElements (applicationId); case API_StairID: return GetStairSubElements (applicationId); case API_RailingID: return GetRailingSubElements (applicationId); diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetOpeningBaseData.hpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetOpeningBaseData.hpp index 10fbae9d21..166c416f9f 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetOpeningBaseData.hpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetOpeningBaseData.hpp @@ -50,7 +50,7 @@ GSErrCode GetOpeningBaseData (const T& element, GS::ObjectState& os) os.Add (OpeningBase::oSide, element.openingBase.oSide); os.Add (OpeningBase::refSide, element.openingBase.refSide); - API_ElemTypeID elementType = Utility::GetElementType (element.head); + API_ElemTypeID elementType = Utility::GetElementType (element.head).typeID; // Vertical Link type and story index if (elementType == API_WindowID || elementType == API_DoorID) { diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetSubElementInfo.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetSubElementInfo.cpp index 066e24b14f..87383d8b16 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetSubElementInfo.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetSubElementInfo.cpp @@ -38,12 +38,15 @@ GS::ObjectState GetSubElementInfo::Execute (const GS::ObjectState& parameters, G API_Guid currentGuid = elementGuids.Get (i); GS::UniString guid = APIGuidToString (currentGuid); - API_ElemTypeID elementTypeId = Utility::GetElementType (currentGuid); - GS::UniString elemType = elementNames.Get (elementTypeId); + API_ElemType elementType = Utility::GetElementType (currentGuid); + + GS::UniString elementTypeName; + if (NoError != GetElementTypeName (elementType, elementTypeName)) + continue; GS::ObjectState subelementModel; subelementModel.Add (ElementBase::ApplicationId, guid); - subelementModel.Add (ElementBase::ElementType, elemType); + subelementModel.Add (ElementBase::ElementType, elementTypeName); listAdder (subelementModel); } } diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/FieldNames.hpp b/ConnectorArchicad/AddOn/Sources/AddOn/FieldNames.hpp index 1cb4425c36..17033d4cef 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/FieldNames.hpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/FieldNames.hpp @@ -32,6 +32,7 @@ static const char* ElementTypes = "elementTypes"; static const char* Elements = "elements"; static const char* SubElements = "subElements"; static const char* Level = "level"; +static const char* Layer = "layer"; static const char* Shape = "shape"; static const char* Shape1 = "shape1"; static const char* Shape2 = "shape2"; @@ -50,6 +51,7 @@ static const char* Beams = "beams"; static const char* Columns = "columns"; static const char* DirectShapes = "directShapes"; static const char* Doors = "doors"; +static const char* GridElements = "gridElements"; static const char* Objects = "objects"; static const char* MeshModels = "meshModels"; static const char* Roofs = "roofs"; @@ -450,6 +452,17 @@ static const char* pos = "pos"; static const char* transform = "transform"; } +namespace GridElement +{ +// Main +static const char* begin = "begin"; +static const char* end = "end"; +static const char* angle = "angle"; +static const char* markerText = "markerText"; +static const char* isArc = "isArc"; +static const char* arcAngle = "arcAngle"; +} + namespace Slab { @@ -721,6 +734,14 @@ static const char* Vertices = "vertices"; static const char* VertexX = "x"; static const char* VertexY = "y"; static const char* VertexZ = "z"; +static const char* PointId1 = "v1"; +static const char* PointId2 = "v2"; +static const char* PolygonId1 = "p1"; +static const char* PolygonId2 = "p2"; +static const char* EdgeStatus = "s"; +static const char* HiddenEdgeValueName = "HiddenEdge"; +static const char* SmoothEdgeValueName = "SmoothEdge"; +static const char* VisibleEdgeValueName = "VisibleEdge"; static const char* Polygons = "polygons"; static const char* Materials = "materials"; static const char* PointIds = "pointIds"; diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/LibpartImportManager.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/LibpartImportManager.cpp index 1424bd7984..47e9f15acb 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/LibpartImportManager.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/LibpartImportManager.cpp @@ -229,7 +229,7 @@ GSErrCode LibpartImportManager::CreateLibraryPart (const ModelInfo& modelInfo, line = GS::String::SPrintf ("%s", GS::EOL); ACAPI_LibPart_WriteSection (line.GetLength(), line.ToCStr()); - GS::HashTable, GS::UShort> edges = modelInfo.GetEdges (); + const GS::HashTable& edges = modelInfo.GetEdges (); UInt32 edgeIndex = 1; UInt32 polygonIndex = 1; @@ -254,21 +254,22 @@ GSErrCode LibpartImportManager::CreateLibraryPart (const ModelInfo& modelInfo, Int32 end = i == pointIds.GetSize () - 1 ? 0 : i + 1; Int32 startPointId = pointIds[start], endPointId = pointIds[end]; - GS::Pair edge (startPointId, endPointId); - GS::Pair inverseEdge (endPointId, startPointId); + ModelInfo::EdgeId edge (startPointId, endPointId); bool smooth = false; - bool hidden = false; - GS::UShort edgeStatus; - if (edges.Get (edge, &edgeStatus) || edges.Get(inverseEdge, &edgeStatus)) { - switch (edgeStatus) { + bool hidden = true; + if (edges.ContainsKey(edge)) { + ModelInfo::EdgeData edgeData = edges.Get (edge); + + switch (edgeData.edgeStatus) { case ModelInfo::HiddenEdge: - hidden = true; break; case ModelInfo::SmoothEdge: + hidden = false; smooth = true; break; case ModelInfo::VisibleEdge: + hidden = false; break; default: break; diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Objects/ModelInfo.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Objects/ModelInfo.cpp index d4432ca44f..d735a9e593 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Objects/ModelInfo.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Objects/ModelInfo.cpp @@ -39,6 +39,7 @@ ModelInfo::Material::Material (const UMAT& aumat) : { } + ModelInfo::Material::Material (GS::UniString& name, short transparency, GS_RGBColor& ambientColor, GS_RGBColor& emissionColor) : name (name), transparency (transparency), @@ -70,6 +71,37 @@ GSErrCode ModelInfo::Material::Restore (const GS::ObjectState& os) } +ModelInfo::EdgeId::EdgeId (Int32 vertexId1, Int32 vertexId2) : + vertexId1 (vertexId1), vertexId2 (vertexId2) +{ +} + + +bool ModelInfo::EdgeId::operator== (ModelInfo::EdgeId otherEdgeId) const +{ + return (vertexId1 == otherEdgeId.vertexId1 && vertexId2 == otherEdgeId.vertexId2) || + (vertexId1 == otherEdgeId.vertexId2 && vertexId2 == otherEdgeId.vertexId1); +} + + +ULong ModelInfo::EdgeId::GenerateHashValue (void) const +{ + return GS::CalculateHashValue (GS::Max(vertexId1, vertexId2), GS::Min(vertexId1, vertexId2)); +} + + +ModelInfo::EdgeData::EdgeData () : + edgeStatus (HiddenEdge), polygonId1 (InvalidPolygonId), polygonId2 (InvalidPolygonId) +{ +} + + +ModelInfo::EdgeData::EdgeData (EdgeStatus edgeStatus, Int32 polygonId1 /* = InvalidPolygonId */, Int32 polygonId2 /* = InvalidPolygonId */) + : edgeStatus (edgeStatus), polygonId1 (polygonId1), polygonId2 (polygonId2) +{ +} + + ModelInfo::Polygon::Polygon (const GS::Array& pointIds, UInt32 material) : pointIds (pointIds), material (material) @@ -107,6 +139,24 @@ void ModelInfo::AddVertex (Vertex&& vertex) } +void ModelInfo::AddEdge (const EdgeId& edgeId, const EdgeData& edgeData) +{ + if (edges.ContainsKey (edgeId)) + edges[edgeId] = edgeData; + else + edges.Add (edgeId, edgeData); +} + + +void ModelInfo::AddEdge (EdgeId&& edgeId, EdgeData&& edgeData) +{ + if (edges.ContainsKey (edgeId)) + edges[edgeId] = edgeData; + else + edges.Add (edgeId, edgeData); +} + + void ModelInfo::AddPolygon (const Polygon& polygon) { polygons.Push (polygon); @@ -156,6 +206,39 @@ GSErrCode ModelInfo::GetMaterial (const UInt32 materialIndex, ModelInfo::Materia GSErrCode ModelInfo::Store (GS::ObjectState& os) const { os.Add (Model::Vertices, vertices); + + GS::Array edgeArray; + + for (auto edge : edges) + { + // skip hidden edges + if (edge.value->edgeStatus == HiddenEdge) + continue; + + GS::ObjectState osEdge; + + osEdge.Add (Model::PointId1, edge.key->vertexId1); + osEdge.Add (Model::PointId2, edge.key->vertexId2); + + if (edge.value->polygonId1 != EdgeData::InvalidPolygonId) + osEdge.Add (Model::PolygonId1, edge.value->polygonId1); + + if (edge.value->polygonId2 != EdgeData::InvalidPolygonId) + osEdge.Add (Model::PolygonId2, edge.value->polygonId2); + + GS::UniString edgeStatusName (Model::HiddenEdgeValueName); + if (edge.value->edgeStatus == SmoothEdge) + edgeStatusName = Model::SmoothEdgeValueName; + else if (edge.value->edgeStatus == VisibleEdge) + edgeStatusName = Model::VisibleEdgeValueName; + + osEdge.Add (Model::EdgeStatus, edgeStatusName); + + edgeArray.Push (osEdge); + } + + os.Add (Model::Edges, edgeArray); + os.Add (Model::Polygons, polygons); os.Add (Model::Materials, materials); @@ -167,9 +250,36 @@ GSErrCode ModelInfo::Restore (const GS::ObjectState& os) { os.Get (Model::Ids, ids); os.Get (Model::Vertices, vertices); + + GS::Array edgeArray; + os.Get (Model::Edges, edgeArray); + + for (GS::ObjectState osEdge : edgeArray) + { + if (!osEdge.Contains (Model::PointId1) || !osEdge.Contains (Model::PointId2) || !osEdge.Contains (Model::EdgeStatus)) + continue; + + Int32 pointId1 (0), pointId2 (0); + osEdge.Get (Model::PointId1, pointId1); + osEdge.Get (Model::PointId2, pointId2); + + EdgeId edgeId (pointId1, pointId2); + + EdgeStatus edgeStatus (HiddenEdge); + GS::UniString edgeStatusName; + osEdge.Get (Model::EdgeStatus, edgeStatusName); + if (edgeStatusName == Model::SmoothEdgeValueName) + edgeStatus = SmoothEdge; + else if (edgeStatusName == Model::VisibleEdgeValueName) + edgeStatus = VisibleEdge; + + EdgeData edgeData (edgeStatus); + + edges.Add(edgeId, edgeData); + } + os.Get (Model::Polygons, polygons); os.Get (Model::Materials, materials); - os.Get (Model::Edges, edges); return NoError; } diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Objects/ModelInfo.hpp b/ConnectorArchicad/AddOn/Sources/AddOn/Objects/ModelInfo.hpp index ad2a246f1d..720f3277a3 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Objects/ModelInfo.hpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Objects/ModelInfo.hpp @@ -33,26 +33,26 @@ class ModelInfo { double z = {}; }; - class Material { + class EdgeId { public: - Material () = default; - Material (const UMAT& aumat); - Material (GS::UniString& name, short transparency, GS_RGBColor& ambientColor, GS_RGBColor& emissionColor); + Int32 vertexId1, vertexId2; - inline const GS::UniString& GetName () const { return name; } - inline short GetTransparency () const { return transparency; } - inline GS_RGBColor GetAmbientColor () const { return ambientColor; } - inline GS_RGBColor GetEmissionColor () const { return emissionColor; } + EdgeId (Int32 vertexId1, Int32 vertexId2); - GSErrCode Store (GS::ObjectState& os) const; - GSErrCode Restore (const GS::ObjectState& os); + bool operator== (EdgeId otherEdgeId) const; + + ULong GenerateHashValue (void) const; + }; - private: - GS::UniString name; - short transparency = {}; // [0..100] - GS_RGBColor ambientColor = {}; - GS_RGBColor emissionColor = {}; + class EdgeData { + public: + static const Int32 InvalidPolygonId = -1; + + EdgeStatus edgeStatus; + Int32 polygonId1, polygonId2; + EdgeData (); + EdgeData (EdgeStatus edgeStatus, Int32 polygonId1 = InvalidPolygonId, Int32 polygonId2 = InvalidPolygonId); }; class Polygon { @@ -70,11 +70,36 @@ class ModelInfo { GS::Array pointIds; UInt32 material = {}; }; + + class Material { + public: + Material () = default; + Material (const UMAT& aumat); + Material (GS::UniString& name, short transparency, GS_RGBColor& ambientColor, GS_RGBColor& emissionColor); + + inline const GS::UniString& GetName () const { return name; } + inline short GetTransparency () const { return transparency; } + inline GS_RGBColor GetAmbientColor () const { return ambientColor; } + inline GS_RGBColor GetEmissionColor () const { return emissionColor; } + + GSErrCode Store (GS::ObjectState& os) const; + GSErrCode Restore (const GS::ObjectState& os); + + private: + GS::UniString name; + short transparency = {}; // [0..100] + GS_RGBColor ambientColor = {}; + GS_RGBColor emissionColor = {}; + + }; public: void AddVertex (const Vertex& vertex); void AddVertex (Vertex&& vertex); + void AddEdge (const EdgeId& edgeId, const EdgeData& edgeData); + void AddEdge (EdgeId&& edgeId, EdgeData&& edgeData); + void AddPolygon (const Polygon& polygon); void AddPolygon (Polygon&& polygon); @@ -85,9 +110,9 @@ class ModelInfo { GSErrCode GetMaterial (const UInt32 materialIndex, ModelInfo::Material& material) const; inline const GS::Array& GetVertices () const { return vertices; } + inline const GS::HashTable& GetEdges () const { return edges; } inline const GS::Array& GetPolygons () const { return polygons; } inline const GS::Array& GetMaterials () const { return materials; } - inline const GS::HashTable, GS::UShort>& GetEdges () const { return edges; } inline const GS::Array& GetIds () const { return ids; } GSErrCode Store (GS::ObjectState& os) const; @@ -96,9 +121,9 @@ class ModelInfo { private: GS::Array ids; GS::Array vertices; + GS::HashTable edges; GS::Array polygons; GS::Array materials; - GS::HashTable, GS::UShort> edges; }; diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/ResourceIds.hpp b/ConnectorArchicad/AddOn/Sources/AddOn/ResourceIds.hpp index e01654193d..c1354d0cde 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/ResourceIds.hpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/ResourceIds.hpp @@ -22,6 +22,7 @@ #define GetBeamDataCommandName "GetBeamData"; #define GetColumnDataCommandName "GetColumnData"; #define GetElementBaseDataCommandName "GetElementBaseData"; +#define GetGridElementCommandName "GetGridElementData"; #define GetObjectDataCommandName "GetObjectData"; #define GetSlabDataCommandName "GetSlabData"; #define GetRoomDataCommandName "GetRoomData"; @@ -36,7 +37,8 @@ #define CreateWindowCommandName "CreateWindow"; #define CreateBeamCommandName "CreateBeam"; #define CreateColumnCommandName "CreateColumn"; -#define CreateObjectCommandName "CreateObject"; +#define CreateGridElementCommandName "CreateGridElement"; +#define CreateObjectCommandName "CreateObject"; #define CreateSlabCommandName "CreateSlab"; #define CreateSkylightCommandName "CreateSkylight"; #define CreateRoofCommandName "CreateRoof"; diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/TypeNameTables.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/TypeNameTables.cpp index 3953d852cd..63652d462c 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/TypeNameTables.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/TypeNameTables.cpp @@ -1,30 +1,52 @@ #include "TypeNameTables.hpp" -const GS::HashTable elementNames +const GS::HashTable elementNames { - { API_ZombieElemID, "InvalidType"}, - { API_WallID, "Wall"}, - { API_ColumnID, "Column"}, - { API_BeamID, "Beam"}, - { API_WindowID, "Window"}, - { API_DoorID, "Door"}, - { API_ObjectID, "Object"}, - { API_LampID, "Lamp"}, - { API_SlabID, "Slab"}, - { API_RoofID, "Roof"}, - { API_MeshID, "Mesh"}, - { API_ZoneID, "Zone"}, - { API_CurtainWallID, "CurtainWall"}, - { API_ShellID, "Shell"}, - { API_SkylightID, "Skylight"}, - { API_MorphID, "Morph"}, - { API_StairID, "Stair"}, - { API_RailingID, "Railing"}, - { API_OpeningID, "Opening"} + { { API_ZombieElemID, APIVarId_Generic}, "InvalidType"}, + { { API_WallID, APIVarId_Generic}, "Wall"}, + { { API_ColumnID, APIVarId_Generic}, "Column"}, + { { API_BeamID, APIVarId_Generic}, "Beam"}, + { { API_WindowID, APIVarId_Generic}, "Window"}, + { { API_DoorID, APIVarId_Generic}, "Door"}, + { { API_ObjectID, APIVarId_Generic}, "Object"}, + { { API_LampID, APIVarId_Generic}, "Lamp"}, + { { API_SlabID, APIVarId_Generic}, "Slab"}, + { { API_RoofID, APIVarId_Generic}, "Roof"}, + { { API_MeshID, APIVarId_Generic}, "Mesh"}, + { { API_ZoneID, APIVarId_Generic}, "Zone"}, + { { API_CurtainWallID, APIVarId_Generic}, "CurtainWall"}, + { { API_ShellID, APIVarId_Generic}, "Shell"}, + { { API_SkylightID, APIVarId_Generic}, "Skylight"}, + { { API_MorphID, APIVarId_Generic}, "Morph"}, + { { API_StairID, APIVarId_Generic}, "Stair"}, + { { API_RailingID, APIVarId_Generic}, "Railing"}, + { { API_OpeningID, APIVarId_Generic}, "Opening"}, + { { API_ObjectID, APIVarId_GridElement}, "GridElement"} }; +GSErrCode GetElementTypeName (const API_ElemType& elementType, GS::UniString& elementTypeName) +{ + // check typeID and variationID first + if (elementNames.ContainsKey(elementType)) { + elementTypeName = elementNames.Get (elementType); + return NoError; + } + + // check only typeID + for (auto elementNameIt : elementNames) + { + if (elementNameIt.key->typeID == elementType.typeID) { + elementTypeName = *(elementNameIt.value); + return NoError; + } + } + + return Error; +} + + const GS::HashTable structureTypeNames { { API_BasicStructure, "Basic"}, diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/TypeNameTables.hpp b/ConnectorArchicad/AddOn/Sources/AddOn/TypeNameTables.hpp index 9796ca4b59..b478c0ca60 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/TypeNameTables.hpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/TypeNameTables.hpp @@ -4,7 +4,11 @@ #include "APIEnvir.h" #include "ACAPinc.h" -extern const GS::HashTable elementNames; +#include "Utility.hpp" + +extern const GS::HashTable elementNames; +GSErrCode GetElementTypeName (const API_ElemType& elementType, GS::UniString& elementTypeName); + extern const GS::HashTable structureTypeNames; extern const GS::HashTable wallTypeNames; diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Utility.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Utility.cpp index bac3f786b4..9c1b1e4b14 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Utility.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Utility.cpp @@ -11,17 +11,17 @@ using namespace FieldNames; namespace Utility { -API_ElemTypeID GetElementType (const API_Elem_Head& header) +API_ElemType GetElementType (const API_Elem_Head& header) { #ifdef ServerMainVers_2600 - return header.type.typeID; + return header.type; #else - return header.typeID; + return API_ElemType (header.typeID, header.variationID); #endif } -API_ElemTypeID GetElementType (const API_Guid& guid) +API_ElemType GetElementType (const API_Guid& guid) { API_Elem_Head elemHead = {}; elemHead.guid = guid; @@ -150,15 +150,22 @@ GS::ErrCode GetLocalizedElementTypeName (const API_Elem_Head& header, GS::UniStr void SetElementType (API_Elem_Head& header, const API_ElemTypeID& elementType) +{ + SetElementType (header, API_ElemType (elementType, APIVarId_Generic)); +} + + +void SetElementType (API_Elem_Head& header, const API_ElemType& elementType) { #ifdef ServerMainVers_2600 - header.type.typeID = elementType; + header.type = elementType; #else - header.typeID = elementType; + header.typeID = elementType.typeID; + header.variationID = elementType.variationID; #endif } - + bool ElementExists (const API_Guid& guid) { return (GetElementType (guid) != API_ZombieElemID); @@ -170,14 +177,14 @@ GSErrCode GetBaseElementData (API_Element& element, API_ElementMemo* memo, API_S GSErrCode err = NoError; API_Guid guid = element.header.guid; - API_ElemTypeID type = GetElementType (element.header); + API_ElemTypeID type = GetElementType (element.header).typeID; if (type == API_ZombieElemID) return Error; bool elemExists = ElementExists (guid); if (elemExists) { // type changed - if (type != GetElementType (guid)) + if (type != GetElementType (guid).typeID) return Error; err = ACAPI_Element_Get (&element); @@ -234,7 +241,7 @@ GSErrCode GetBaseElementData (API_Element& element, API_ElementMemo* memo, API_S bool IsElement3D (const API_Guid& guid) { - switch (GetElementType (guid)) { + switch (GetElementType (guid).typeID) { case API_WallID: case API_ColumnID: case API_BeamID: @@ -367,7 +374,7 @@ GS::Array GetElementSubelements (API_Element& element) { GS::Array result; - API_ElemTypeID elementType = GetElementType (element.header); + API_ElemTypeID elementType = GetElementType (element.header).typeID; if (elementType == API_WallID) { if (element.wall.hasDoor) { @@ -590,7 +597,7 @@ GSErrCode CreateAllSchemeData (const GS::ObjectState& os, if (memo->assemblySegmentSchemes != nullptr) { defaultSegmentScheme = memo->assemblySegmentSchemes[0]; - switch (Utility::GetElementType (element.header)) { + switch (Utility::GetElementType (element.header).typeID) { case API_BeamID: memo->assemblySegmentSchemes = (API_AssemblySegmentSchemeData*) BMAllocatePtr ((element.beam.nSchemes) * sizeof (API_AssemblySegmentSchemeData), ALLOCATE_CLEAR, 0); break; @@ -679,7 +686,7 @@ GSErrCode CreateAllCutData (const GS::ObjectState& os, GS::UInt32& numberOfCuts, if (memo->assemblySegmentCuts != nullptr) { defaultSegmentCut = memo->assemblySegmentCuts[0]; - switch (GetElementType (element.header)) { + switch (GetElementType (element.header).typeID) { case API_BeamID: memo->assemblySegmentCuts = (API_AssemblySegmentCutData*) BMAllocatePtr ((element.beam.nCuts) * sizeof (API_AssemblySegmentCutData), ALLOCATE_CLEAR, 0); break; diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Utility.hpp b/ConnectorArchicad/AddOn/Sources/AddOn/Utility.hpp index e37328bc04..14ca3a4023 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Utility.hpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Utility.hpp @@ -9,14 +9,67 @@ #define UNUSED(x) (void)(x) +#ifndef ServerMainVers_2600 +struct API_ElemType { + API_ElemTypeID typeID; // type of the element + API_ElemVariationID variationID; // type subcategory + + API_ElemType () = default; + + API_ElemType (API_ElemTypeID typeID) + : typeID (typeID), variationID (APIVarId_Generic) + {} + + API_ElemType (API_ElemTypeID typeID, API_ElemVariationID variationID) + : typeID (typeID), variationID (variationID) + {} + + API_ElemType& operator= (const API_ElemType&) = default; + + API_ElemType& operator= (API_ElemTypeID newTypeID) + { + typeID = newTypeID; + variationID = APIVarId_Generic; + return *this; + } + + bool operator== (const API_ElemType& other) const + { + return typeID == other.typeID && variationID == other.variationID; + } + + bool operator!= (const API_ElemType& other) const + { + return !operator== (other); + } + + bool operator== (API_ElemTypeID otherTypeID) const + { + return typeID == otherTypeID; + } + + bool operator!= (API_ElemTypeID otherTypeID) const + { + return !operator== (otherTypeID); + } + + ULong GenerateHashValue (void) const + { + return GS::CalculateHashValue (typeID, variationID); + } +}; +#endif + + namespace Utility { // Element Type -API_ElemTypeID GetElementType (const API_Elem_Head& header); -API_ElemTypeID GetElementType (const API_Guid& guid); +API_ElemType GetElementType (const API_Elem_Head& header); +API_ElemType GetElementType (const API_Guid& guid); GS::ErrCode GetNonLocalizedElementTypeName (const API_Elem_Head& header, GS::UniString& typeName); GS::ErrCode GetLocalizedElementTypeName (const API_Elem_Head& header, GS::UniString& typeName); void SetElementType (API_Elem_Head& header, const API_ElemTypeID& elementType); +void SetElementType (API_Elem_Head& header, const API_ElemType& elementType); bool ElementExists (const API_Guid& guid); diff --git a/ConnectorArchicad/ConnectorArchicad/Communication/AsyncCommandProcessor.cs b/ConnectorArchicad/ConnectorArchicad/Communication/AsyncCommandProcessor.cs index 659046ade3..2be6285f2e 100644 --- a/ConnectorArchicad/ConnectorArchicad/Communication/AsyncCommandProcessor.cs +++ b/ConnectorArchicad/ConnectorArchicad/Communication/AsyncCommandProcessor.cs @@ -14,12 +14,14 @@ internal class AsyncCommandProcessor #region --- Functions --- - public static Task? Execute(Commands.ICommand command) where TResult : class + public static Task? Execute(Commands.ICommand command) + where TResult : class { return Execute(command, CancellationToken.None); } - public static Task? Execute(Commands.ICommand command, CancellationToken token) where TResult : class + public static Task? Execute(Commands.ICommand command, CancellationToken token) + where TResult : class { try { diff --git a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_CreateBeam.cs b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_CreateBeam.cs index d49f228a29..153fafbbe0 100644 --- a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_CreateBeam.cs +++ b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_CreateBeam.cs @@ -1,49 +1,49 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Speckle.Core.Models; -using Speckle.Newtonsoft.Json; -using Objects.BuiltElements.Archicad; - -namespace Archicad.Communication.Commands -{ - sealed internal class CreateBeam : ICommand> - { - [JsonObject(MemberSerialization.OptIn)] - public sealed class Parameters - { - [JsonProperty("beams")] - private IEnumerable Datas { get; } - - public Parameters(IEnumerable datas) - { - Datas = datas; - } - } - - [JsonObject(MemberSerialization.OptIn)] - private sealed class Result - { - [JsonProperty("applicationObjects")] - public IEnumerable ApplicationObjects { get; private set; } - } - - private IEnumerable Datas { get; } - - public CreateBeam(IEnumerable datas) - { - foreach (var data in datas) - { - data.displayValue = null; - data.baseLine = null; - } - - Datas = datas; - } - - public async Task> Execute() - { - var result = await HttpCommandExecutor.Execute("CreateBeam", new Parameters(Datas)); - return result == null ? null : result.ApplicationObjects; - } - } -} +using System.Collections.Generic; +using System.Threading.Tasks; +using Speckle.Core.Models; +using Speckle.Newtonsoft.Json; +using Objects.BuiltElements.Archicad; + +namespace Archicad.Communication.Commands +{ + sealed internal class CreateBeam : ICommand> + { + [JsonObject(MemberSerialization.OptIn)] + public sealed class Parameters + { + [JsonProperty("beams")] + private IEnumerable Datas { get; } + + public Parameters(IEnumerable datas) + { + Datas = datas; + } + } + + [JsonObject(MemberSerialization.OptIn)] + private sealed class Result + { + [JsonProperty("applicationObjects")] + public IEnumerable ApplicationObjects { get; private set; } + } + + private IEnumerable Datas { get; } + + public CreateBeam(IEnumerable datas) + { + foreach (var data in datas) + { + data.displayValue = null; + data.baseLine = null; + } + + Datas = datas; + } + + public async Task> Execute() + { + var result = await HttpCommandExecutor.Execute("CreateBeam", new Parameters(Datas)); + return result == null ? null : result.ApplicationObjects; + } + } +} diff --git a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_CreateGridElement.cs b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_CreateGridElement.cs new file mode 100644 index 0000000000..3f560de448 --- /dev/null +++ b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_CreateGridElement.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Speckle.Core.Models; +using Speckle.Newtonsoft.Json; + +namespace Archicad.Communication.Commands +{ + sealed internal class CreateGridElement : ICommand> + { + [JsonObject(MemberSerialization.OptIn)] + public sealed class Parameters + { + [JsonProperty("gridElements")] + private IEnumerable Datas { get; } + + public Parameters(IEnumerable datas) + { + Datas = datas; + } + } + + [JsonObject(MemberSerialization.OptIn)] + private sealed class Result + { + [JsonProperty("applicationObjects")] + public IEnumerable ApplicationObjects { get; private set; } + } + + private IEnumerable Datas { get; } + + public CreateGridElement(IEnumerable datas) + { + Datas = datas; + } + + public async Task> Execute() + { + var result = await HttpCommandExecutor.Execute("CreateGridElement", new Parameters(Datas)); + return result == null ? null : result.ApplicationObjects; + } + } +} diff --git a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetBeamData.cs b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetBeamData.cs index 2ed4757293..f0d99e9c59 100644 --- a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetBeamData.cs +++ b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetBeamData.cs @@ -1,12 +1,10 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Speckle.Core.Kits; using Speckle.Newtonsoft.Json; -using Objects.BuiltElements.Archicad; namespace Archicad.Communication.Commands { - sealed internal class GetBeamData : ICommand> + sealed internal class GetBeamData : ICommand { [JsonObject(MemberSerialization.OptIn)] public sealed class Parameters @@ -20,13 +18,6 @@ public Parameters(IEnumerable applicationIds) } } - [JsonObject(MemberSerialization.OptIn)] - private sealed class Result - { - [JsonProperty("beams")] - public IEnumerable Datas { get; private set; } - } - private IEnumerable ApplicationIds { get; } public GetBeamData(IEnumerable applicationIds) @@ -34,16 +25,14 @@ public GetBeamData(IEnumerable applicationIds) ApplicationIds = applicationIds; } - public async Task> Execute() + public async Task Execute() { - Result result = await HttpCommandExecutor.Execute( + dynamic result = await HttpCommandExecutor.Execute( "GetBeamData", new Parameters(ApplicationIds) ); - foreach (var beam in result.Datas) - beam.units = Units.Meters; - return result.Datas; + return (Speckle.Newtonsoft.Json.Linq.JArray)result["beams"]; } } } diff --git a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetColumnData.cs b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetColumnData.cs index bde9e1c17a..545c6c174b 100644 --- a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetColumnData.cs +++ b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetColumnData.cs @@ -1,12 +1,10 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Speckle.Core.Kits; using Speckle.Newtonsoft.Json; -using Objects.BuiltElements.Archicad; namespace Archicad.Communication.Commands { - sealed internal class GetColumnData : ICommand> + sealed internal class GetColumnData : ICommand { [JsonObject(MemberSerialization.OptIn)] public sealed class Parameters @@ -20,13 +18,6 @@ public Parameters(IEnumerable applicationIds) } } - [JsonObject(MemberSerialization.OptIn)] - private sealed class Result - { - [JsonProperty("columns")] - public IEnumerable Datas { get; private set; } - } - private IEnumerable ApplicationIds { get; } public GetColumnData(IEnumerable applicationIds) @@ -34,16 +25,14 @@ public GetColumnData(IEnumerable applicationIds) ApplicationIds = applicationIds; } - public async Task> Execute() + public async Task Execute() { - Result result = await HttpCommandExecutor.Execute( + dynamic result = await HttpCommandExecutor.Execute( "GetColumnData", new Parameters(ApplicationIds) ); - foreach (var beam in result.Datas) - beam.units = Units.Meters; - return result.Datas; + return (Speckle.Newtonsoft.Json.Linq.JArray)result["columns"]; } } } diff --git a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetFloorData.cs b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetFloorData.cs index 95d880405a..3bc509c9d9 100644 --- a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetFloorData.cs +++ b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetFloorData.cs @@ -1,12 +1,10 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Speckle.Core.Kits; using Speckle.Newtonsoft.Json; -using Objects.BuiltElements.Archicad; namespace Archicad.Communication.Commands { - internal sealed class GetFloorData : ICommand> + internal sealed class GetFloorData : ICommand { [JsonObject(MemberSerialization.OptIn)] public sealed class Parameters @@ -20,13 +18,6 @@ public Parameters(IEnumerable applicationIds) } } - [JsonObject(MemberSerialization.OptIn)] - private sealed class Result - { - [JsonProperty("slabs")] - public IEnumerable Datas { get; private set; } - } - private IEnumerable ApplicationIds { get; } public GetFloorData(IEnumerable applicationIds) @@ -34,16 +25,14 @@ public GetFloorData(IEnumerable applicationIds) ApplicationIds = applicationIds; } - public async Task> Execute() + public async Task Execute() { - Result result = await HttpCommandExecutor.Execute( + dynamic result = await HttpCommandExecutor.Execute( "GetSlabData", new Parameters(ApplicationIds) ); - foreach (var floor in result.Datas) - floor.units = Units.Meters; - return result.Datas; + return (Speckle.Newtonsoft.Json.Linq.JArray)result["slabs"]; } } } diff --git a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetGridElementData.cs b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetGridElementData.cs new file mode 100644 index 0000000000..a8c9e4bd6d --- /dev/null +++ b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetGridElementData.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Speckle.Core.Kits; +using Speckle.Newtonsoft.Json; +using Objects.BuiltElements; + +namespace Archicad.Communication.Commands +{ + sealed internal class GetGridElementData : ICommand> + { + [JsonObject(MemberSerialization.OptIn)] + public sealed class Parameters + { + [JsonProperty("applicationIds")] + private IEnumerable ApplicationIds { get; } + + public Parameters(IEnumerable applicationIds) + { + ApplicationIds = applicationIds; + } + } + + [JsonObject(MemberSerialization.OptIn)] + private sealed class Result + { + [JsonProperty("gridElements")] + public IEnumerable Datas { get; private set; } + } + + private IEnumerable ApplicationIds { get; } + + public GetGridElementData(IEnumerable applicationIds) + { + ApplicationIds = applicationIds; + } + + public async Task> Execute() + { + Result result = await HttpCommandExecutor.Execute( + "GetGridElementData", + new Parameters(ApplicationIds) + ); + + return result.Datas; + } + } +} diff --git a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetRoofData.cs b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetRoofData.cs index 6b45799ead..5afb40d44f 100644 --- a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetRoofData.cs +++ b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetRoofData.cs @@ -1,12 +1,10 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Objects.BuiltElements.Archicad; -using Speckle.Core.Kits; using Speckle.Newtonsoft.Json; namespace Archicad.Communication.Commands { - internal sealed class GetRoofData : ICommand> + internal sealed class GetRoofData : ICommand { [JsonObject(MemberSerialization.OptIn)] public sealed class Parameters @@ -20,13 +18,6 @@ public Parameters(IEnumerable applicationIds) } } - [JsonObject(MemberSerialization.OptIn)] - private sealed class Result - { - [JsonProperty("Roofs")] - public IEnumerable Datas { get; private set; } - } - private IEnumerable ApplicationIds { get; } public GetRoofData(IEnumerable applicationIds) @@ -34,14 +25,14 @@ public GetRoofData(IEnumerable applicationIds) ApplicationIds = applicationIds; } - public async Task> Execute() + public async Task Execute() { - Result result = await HttpCommandExecutor.Execute("GetRoofData", new Parameters(ApplicationIds)); - foreach (var roof in result.Datas) - roof.units = Units.Meters; + dynamic result = await HttpCommandExecutor.Execute( + "GetRoofData", + new Parameters(ApplicationIds) + ); - return result.Datas; + return (Speckle.Newtonsoft.Json.Linq.JArray)result["roofs"]; } - } } diff --git a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetShellData.cs b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetShellData.cs index 9bf47081c6..c43c192fd8 100644 --- a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetShellData.cs +++ b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetShellData.cs @@ -1,12 +1,10 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Objects.BuiltElements.Archicad; -using Speckle.Core.Kits; using Speckle.Newtonsoft.Json; namespace Archicad.Communication.Commands { - internal sealed class GetShellData : ICommand> + internal sealed class GetShellData : ICommand { [JsonObject(MemberSerialization.OptIn)] public sealed class Parameters @@ -20,13 +18,6 @@ public Parameters(IEnumerable applicationIds) } } - [JsonObject(MemberSerialization.OptIn)] - private sealed class Result - { - [JsonProperty("Shells")] - public IEnumerable Datas { get; private set; } - } - private IEnumerable ApplicationIds { get; } public GetShellData(IEnumerable applicationIds) @@ -34,14 +25,14 @@ public GetShellData(IEnumerable applicationIds) ApplicationIds = applicationIds; } - public async Task> Execute() + public async Task Execute() { - Result result = await HttpCommandExecutor.Execute("GetShellData", new Parameters(ApplicationIds)); - foreach (var shell in result.Datas) - shell.units = Units.Meters; + dynamic result = await HttpCommandExecutor.Execute( + "GetShellData", + new Parameters(ApplicationIds) + ); - return result.Datas; + return (Speckle.Newtonsoft.Json.Linq.JArray)result["shells"]; } - } } diff --git a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetWallData.cs b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetWallData.cs index eeedf07687..a304fc21ae 100644 --- a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetWallData.cs +++ b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetWallData.cs @@ -1,12 +1,10 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Speckle.Core.Kits; using Speckle.Newtonsoft.Json; -using Objects.BuiltElements.Archicad; namespace Archicad.Communication.Commands { - sealed internal class GetWallData : ICommand> + sealed internal class GetWallData : ICommand { [JsonObject(MemberSerialization.OptIn)] public sealed class Parameters @@ -20,13 +18,6 @@ public Parameters(IEnumerable applicationIds) } } - [JsonObject(MemberSerialization.OptIn)] - private sealed class Result - { - [JsonProperty("walls")] - public IEnumerable Datas { get; private set; } - } - private IEnumerable ApplicationIds { get; } public GetWallData(IEnumerable applicationIds) @@ -34,16 +25,14 @@ public GetWallData(IEnumerable applicationIds) ApplicationIds = applicationIds; } - public async Task> Execute() + public async Task Execute() { - Result result = await HttpCommandExecutor.Execute( + dynamic result = await HttpCommandExecutor.Execute( "GetWallData", new Parameters(ApplicationIds) ); - foreach (var wall in result.Datas) - wall.units = Units.Meters; - return result.Datas; + return (Speckle.Newtonsoft.Json.Linq.JArray)result["walls"]; } } } diff --git a/ConnectorArchicad/ConnectorArchicad/Communication/HttpCommandExecutor.cs b/ConnectorArchicad/ConnectorArchicad/Communication/HttpCommandExecutor.cs index e2e9e3790b..b7aa0932d4 100644 --- a/ConnectorArchicad/ConnectorArchicad/Communication/HttpCommandExecutor.cs +++ b/ConnectorArchicad/ConnectorArchicad/Communication/HttpCommandExecutor.cs @@ -30,20 +30,46 @@ private static TResponse DeserializeResponse(string obj) return JsonConvert.DeserializeObject(obj, settings); } - public static async Task Execute(string commandName, TParameters parameters) where TParameters : class where TResult : class + public static async Task Execute(string commandName, TParameters parameters) + where TParameters : class + where TResult : class { var context = Archicad.Helpers.Timer.Context.Peek; - using (context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.HttpCommandExecute, commandName)) + using ( + context?.cumulativeTimer?.Begin( + ConnectorArchicad.Properties.OperationNameTemplates.HttpCommandExecute, + commandName + ) + ) { + bool log = false; + AddOnCommandRequest request = new AddOnCommandRequest(commandName, parameters); string requestMsg = SerializeRequest(request); - //Console.WriteLine(requestMsg); + + if (log) + { + Console.WriteLine(requestMsg); + } + string responseMsg; - using (context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.HttpCommandAPI, commandName)) + using ( + context?.cumulativeTimer?.Begin( + ConnectorArchicad.Properties.OperationNameTemplates.HttpCommandAPI, + commandName + ) + ) + { responseMsg = await ConnectionManager.Instance.Send(requestMsg); - //Console.WriteLine(responseMsg); + } + + if (log) + { + Console.WriteLine(responseMsg); + } + AddOnCommandResponse response = DeserializeResponse>(responseMsg); // TODO diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/ConverterArchicad.cs b/ConnectorArchicad/ConnectorArchicad/Converters/ConverterArchicad.cs index e2805c7de6..d9ae3fc506 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/ConverterArchicad.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/ConverterArchicad.cs @@ -66,6 +66,7 @@ public bool CanConvertToNativeImplemented(Base @object) Objects.BuiltElements.Column _ => true, Objects.BuiltElements.Floor _ => true, Objects.BuiltElements.Ceiling _ => true, + Objects.BuiltElements.GridLine => true, Objects.BuiltElements.Roof _ => true, Objects.BuiltElements.Room _ => true, Objects.BuiltElements.Wall _ => true, diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/BeamConverter.cs b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/BeamConverter.cs index a4e33dae3a..55fba21649 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/BeamConverter.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/BeamConverter.cs @@ -1,90 +1,105 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Archicad.Communication; -using Archicad.Model; -using Objects.Geometry; -using Speckle.Core.Models; -using Speckle.Core.Models.GraphTraversal; - -namespace Archicad.Converters -{ - public sealed class Beam : IConverter - { - public Type Type => typeof(Objects.BuiltElements.Beam); - - public async Task> ConvertToArchicad(IEnumerable elements, CancellationToken token) - { - var beams = new List(); - - var context = Archicad.Helpers.Timer.Context.Peek; - using (context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name)) - { - foreach (var tc in elements) - { - token.ThrowIfCancellationRequested(); - - switch (tc.current) - { - case Objects.BuiltElements.Archicad.ArchicadBeam archiBeam: - beams.Add(archiBeam); - break; - case Objects.BuiltElements.Beam beam: - - // upgrade (if not Archicad beam): Objects.BuiltElements.Beam --> Objects.BuiltElements.Archicad.ArchicadBeam - { - var baseLine = (Line)beam.baseLine; - var newBeam = new Objects.BuiltElements.Archicad.ArchicadBeam - { - id = beam.id, - applicationId = beam.applicationId, - begC = Utils.ScaleToNative(baseLine.start), - endC = Utils.ScaleToNative(baseLine.end) - }; - - beams.Add(newBeam); - } - - break; - } - } - } - - IEnumerable result; - result = await AsyncCommandProcessor.Execute(new Communication.Commands.CreateBeam(beams), token); - - return result is null ? new List() : result.ToList(); - } - - public async Task> ConvertToSpeckle(IEnumerable elements, - CancellationToken token) - { - var elementModels = elements as ElementModelData[] ?? elements.ToArray(); - IEnumerable data = - await AsyncCommandProcessor.Execute( - new Communication.Commands.GetBeamData(elementModels.Select(e => e.applicationId)), - token); - if (data is null) - { - return new List(); - } - - var beams = new List(); - foreach (Objects.BuiltElements.Archicad.ArchicadBeam beam in data) - { - // downgrade (always): Objects.BuiltElements.Archicad.ArchicadBeam --> Objects.BuiltElements.Beam - { - beam.displayValue = - Operations.ModelConverter.MeshesToSpeckle(elementModels.First(e => e.applicationId == beam.applicationId) - .model); - beam.baseLine = new Line(beam.begC, beam.endC); - beams.Add(beam); - } - } - - return beams; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Archicad.Communication; +using Archicad.Model; +using Objects.Geometry; +using Speckle.Core.Kits; +using Speckle.Core.Models; +using Speckle.Core.Models.GraphTraversal; + +namespace Archicad.Converters +{ + public sealed class Beam : IConverter + { + public Type Type => typeof(Objects.BuiltElements.Beam); + + public async Task> ConvertToArchicad( + IEnumerable elements, + CancellationToken token + ) + { + var beams = new List(); + + var context = Archicad.Helpers.Timer.Context.Peek; + using ( + context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name) + ) + { + foreach (var tc in elements) + { + token.ThrowIfCancellationRequested(); + + switch (tc.current) + { + case Objects.BuiltElements.Archicad.ArchicadBeam archiBeam: + beams.Add(archiBeam); + break; + case Objects.BuiltElements.Beam beam: + + // upgrade (if not Archicad beam): Objects.BuiltElements.Beam --> Objects.BuiltElements.Archicad.ArchicadBeam + { + var baseLine = (Line)beam.baseLine; + var newBeam = new Objects.BuiltElements.Archicad.ArchicadBeam + { + id = beam.id, + applicationId = beam.applicationId, + archicadLevel = Archicad.Converters.Utils.ConvertLevel(beam.level), + begC = Utils.ScaleToNative(baseLine.start), + endC = Utils.ScaleToNative(baseLine.end) + }; + + beams.Add(newBeam); + } + + break; + } + } + } + + IEnumerable result; + result = await AsyncCommandProcessor.Execute(new Communication.Commands.CreateBeam(beams), token); + + return result is null ? new List() : result.ToList(); + } + + public async Task> ConvertToSpeckle( + IEnumerable elements, + CancellationToken token + ) + { + var elementModels = elements as ElementModelData[] ?? elements.ToArray(); + + Speckle.Newtonsoft.Json.Linq.JArray jArray = await AsyncCommandProcessor.Execute( + new Communication.Commands.GetBeamData(elementModels.Select(e => e.applicationId)), + token + ); + + var beams = new List(); + if (jArray is null) + return beams; + + foreach (Speckle.Newtonsoft.Json.Linq.JToken jToken in jArray) + { + // convert between DTOs + Objects.BuiltElements.Archicad.ArchicadBeam beam = + Archicad.Converters.Utils.ConvertDTOs(jToken); + + // downgrade (always): Objects.BuiltElements.Archicad.ArchicadBeam --> Objects.BuiltElements.Beam + { + beam.units = Units.Meters; + beam.displayValue = Operations.ModelConverter.MeshesToSpeckle( + elementModels.First(e => e.applicationId == beam.applicationId).model + ); + beam.baseLine = new Line(beam.begC, beam.endC); + } + + beams.Add(beam); + } + + return beams; + } + } +} diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/ColumnConverter.cs b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/ColumnConverter.cs index a278f51ab9..98e16f8f46 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/ColumnConverter.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/ColumnConverter.cs @@ -1,82 +1,98 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Archicad.Communication; -using Archicad.Model; -using Objects.BuiltElements; -using Objects.Geometry; -using Speckle.Core.Models; -using Speckle.Core.Models.GraphTraversal; - -namespace Archicad.Converters -{ - public sealed class Column : IConverter - { - public Type Type => typeof(Objects.BuiltElements.Column); - - public async Task> ConvertToArchicad(IEnumerable elements, CancellationToken token) - { - var columns = new List(); - - var context = Archicad.Helpers.Timer.Context.Peek; - using (context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name)) - { - foreach (var tc in elements) - { - token.ThrowIfCancellationRequested(); - - switch (tc.current) - { - case Objects.BuiltElements.Archicad.ArchicadColumn archicadColumn: - columns.Add(archicadColumn); - break; - case Objects.BuiltElements.Column column: - var baseLine = (Line)column.baseLine; - Objects.BuiltElements.Archicad.ArchicadColumn newColumn = new Objects.BuiltElements.Archicad.ArchicadColumn - { - id = column.id, - applicationId = column.applicationId, - origoPos = Utils.ScaleToNative(baseLine.start), - height = Math.Abs(Utils.ScaleToNative(baseLine.end.z, baseLine.end.units) - Utils.ScaleToNative(baseLine.start.z, baseLine.start.units)) - }; - - columns.Add(newColumn); - break; - } - } - } - - IEnumerable result; - result = await AsyncCommandProcessor.Execute(new Communication.Commands.CreateColumn(columns), token); - return result is null ? new List() : result.ToList(); - } - - public async Task> ConvertToSpeckle(IEnumerable elements, - CancellationToken token) - { - var elementModels = elements as ElementModelData[] ?? elements.ToArray(); - IEnumerable data = - await AsyncCommandProcessor.Execute( - new Communication.Commands.GetColumnData(elementModels.Select(e => e.applicationId)), - token); - if (data is null) - { - return new List(); - } - - List columns = new List(); - foreach (Objects.BuiltElements.Archicad.ArchicadColumn column in data) - { - column.displayValue = - Operations.ModelConverter.MeshesToSpeckle(elementModels.First(e => e.applicationId == column.applicationId) - .model); - column.baseLine = new Line(column.origoPos, column.origoPos + new Point (0, 0, column.height)); - columns.Add(column); - } - - return columns; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Archicad.Communication; +using Archicad.Model; +using Objects.Geometry; +using Speckle.Core.Kits; +using Speckle.Core.Models; +using Speckle.Core.Models.GraphTraversal; + +namespace Archicad.Converters +{ + public sealed class Column : IConverter + { + public Type Type => typeof(Objects.BuiltElements.Column); + + public async Task> ConvertToArchicad( + IEnumerable elements, + CancellationToken token + ) + { + var columns = new List(); + + var context = Archicad.Helpers.Timer.Context.Peek; + using ( + context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name) + ) + { + foreach (var tc in elements) + { + token.ThrowIfCancellationRequested(); + + switch (tc.current) + { + case Objects.BuiltElements.Archicad.ArchicadColumn archicadColumn: + columns.Add(archicadColumn); + break; + case Objects.BuiltElements.Column column: + var baseLine = (Line)column.baseLine; + Objects.BuiltElements.Archicad.ArchicadColumn newColumn = + new Objects.BuiltElements.Archicad.ArchicadColumn + { + id = column.id, + applicationId = column.applicationId, + archicadLevel = Archicad.Converters.Utils.ConvertLevel(column.level), + origoPos = Utils.ScaleToNative(baseLine.start), + height = Math.Abs( + Utils.ScaleToNative(baseLine.end.z, baseLine.end.units) + - Utils.ScaleToNative(baseLine.start.z, baseLine.start.units) + ) + }; + + columns.Add(newColumn); + break; + } + } + } + + IEnumerable result; + result = await AsyncCommandProcessor.Execute(new Communication.Commands.CreateColumn(columns), token); + return result is null ? new List() : result.ToList(); + } + + public async Task> ConvertToSpeckle( + IEnumerable elements, + CancellationToken token + ) + { + var elementModels = elements as ElementModelData[] ?? elements.ToArray(); + Speckle.Newtonsoft.Json.Linq.JArray jArray = await AsyncCommandProcessor.Execute( + new Communication.Commands.GetColumnData(elementModels.Select(e => e.applicationId)), + token + ); + + var columns = new List(); + if (jArray is null) + return columns; + + foreach (Speckle.Newtonsoft.Json.Linq.JToken jToken in jArray) + { + // convert between DTOs + Objects.BuiltElements.Archicad.ArchicadColumn column = + Archicad.Converters.Utils.ConvertDTOs(jToken); + + column.units = Units.Meters; + column.displayValue = Operations.ModelConverter.MeshesToSpeckle( + elementModels.First(e => e.applicationId == column.applicationId).model + ); + column.baseLine = new Line(column.origoPos, column.origoPos + new Point(0, 0, column.height)); + columns.Add(column); + } + + return columns; + } + } +} diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/DirectShapeConverter.cs b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/DirectShapeConverter.cs index 0511974932..006bc15bfb 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/DirectShapeConverter.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/DirectShapeConverter.cs @@ -11,6 +11,7 @@ using Objects.Geometry; using Speckle.Core.Models; using Speckle.Core.Models.GraphTraversal; +using Speckle.Newtonsoft.Json.Linq; namespace Archicad.Converters { @@ -32,7 +33,9 @@ CancellationToken token var directShapes = new List(); var context = Archicad.Helpers.Timer.Context.Peek; - using (context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name)) + using ( + context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name) + ) { foreach (var tc in elements) { @@ -66,32 +69,31 @@ CancellationToken token } IEnumerable result; - result = await AsyncCommandProcessor.Execute( - new Communication.Commands.CreateDirectShape(directShapes), - token - ); + result = await AsyncCommandProcessor.Execute(new Communication.Commands.CreateDirectShape(directShapes), token); return result is null ? new List() : result.ToList(); } - public async Task> ConvertToSpeckle(IEnumerable elements, CancellationToken token) + public async Task> ConvertToSpeckle( + IEnumerable elements, + CancellationToken token + ) { var elementModels = elements as ElementModelData[] ?? elements.ToArray(); - IEnumerable data = - await AsyncCommandProcessor.Execute( - new Communication.Commands.GetElementBaseData(elementModels.Select(e => e.applicationId)), - token); - if (data is null) - { - return new List(); - } + IEnumerable data = await AsyncCommandProcessor.Execute( + new Communication.Commands.GetElementBaseData(elementModels.Select(e => e.applicationId)), + token + ); var directShapes = new List(); + if (data is null) + return directShapes; + foreach (Objects.BuiltElements.Archicad.DirectShape directShape in data) { { - directShape.displayValue = - Operations.ModelConverter.MeshesToSpeckle(elementModels.First(e => e.applicationId == directShape.applicationId) - .model); + directShape.displayValue = Operations.ModelConverter.MeshesToSpeckle( + elementModels.First(e => e.applicationId == directShape.applicationId).model + ); directShapes.Add(directShape); } } diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/DoorConverter.cs b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/DoorConverter.cs index 9a17c0cfd9..40ac47e076 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/DoorConverter.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/DoorConverter.cs @@ -17,12 +17,17 @@ public sealed class Door : IConverter { public Type Type => typeof(Objects.BuiltElements.Archicad.ArchicadDoor); - public async Task> ConvertToArchicad(IEnumerable elements, CancellationToken token) + public async Task> ConvertToArchicad( + IEnumerable elements, + CancellationToken token + ) { var doors = new List(); var context = Archicad.Helpers.Timer.Context.Peek; - using (context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name)) + using ( + context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name) + ) { foreach (var tc in elements) { @@ -34,14 +39,14 @@ public async Task> ConvertToArchicad(IEnumerable> ConvertToArchicad(IEnumerable() : result.ToList(); } - public async Task> ConvertToSpeckle(IEnumerable elements, - CancellationToken token) + public async Task> ConvertToSpeckle( + IEnumerable elements, + CancellationToken token + ) { var elementModels = elements as ElementModelData[] ?? elements.ToArray(); - IEnumerable datas = - await AsyncCommandProcessor.Execute(new Communication.Commands.GetDoorData(elementModels.Select(e => e.applicationId))); + IEnumerable data = await AsyncCommandProcessor.Execute( + new Communication.Commands.GetDoorData(elementModels.Select(e => e.applicationId)) + ); - if (datas is null) - { - return new List(); - } + var openings = new List(); + if (data is null) + return openings; - List openings = new List(); - foreach (Objects.BuiltElements.Archicad.ArchicadDoor subelement in datas) + foreach (Objects.BuiltElements.Archicad.ArchicadDoor subelement in data) { - subelement.displayValue = - Operations.ModelConverter.MeshesToSpeckle(elementModels.First(e => e.applicationId == subelement.applicationId) - .model); + subelement.displayValue = Operations.ModelConverter.MeshesToSpeckle( + elementModels.First(e => e.applicationId == subelement.applicationId).model + ); openings.Add(subelement); } diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/FloorConverter.cs b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/FloorConverter.cs index 8060d00f85..c669a46567 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/FloorConverter.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/FloorConverter.cs @@ -5,8 +5,8 @@ using System.Threading.Tasks; using Archicad.Communication; using Objects; -using Objects.BuiltElements; using Speckle.Core.Models; +using Speckle.Core.Kits; using Speckle.Core.Models.GraphTraversal; namespace Archicad.Converters @@ -15,12 +15,17 @@ public sealed class Floor : IConverter { public Type Type => typeof(Objects.BuiltElements.Floor); - public async Task> ConvertToArchicad(IEnumerable elements, CancellationToken token) + public async Task> ConvertToArchicad( + IEnumerable elements, + CancellationToken token + ) { var floors = new List(); var context = Archicad.Helpers.Timer.Context.Peek; - using (context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name)) + using ( + context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name) + ) { foreach (var tc in elements) { @@ -37,7 +42,8 @@ public async Task> ConvertToArchicad(IEnumerable> ConvertToArchicad(IEnumerable result; result = await AsyncCommandProcessor.Execute(new Communication.Commands.CreateFloor(floors), token); - return result is null ? new List() : result.ToList(); ; + return result is null ? new List() : result.ToList(); + ; } - public async Task> ConvertToSpeckle(IEnumerable elements, - CancellationToken token) + public async Task> ConvertToSpeckle( + IEnumerable elements, + CancellationToken token + ) { - var data = await AsyncCommandProcessor.Execute( - new Communication.Commands.GetFloorData(elements.Select(e => e.applicationId)), token); + Speckle.Newtonsoft.Json.Linq.JArray jArray = await AsyncCommandProcessor.Execute( + new Communication.Commands.GetFloorData(elements.Select(e => e.applicationId)), + token + ); var floors = new List(); - foreach (var slab in data) + if (jArray is null) + return floors; + + foreach (Speckle.Newtonsoft.Json.Linq.JToken jToken in jArray) { - slab.displayValue = Operations.ModelConverter.MeshesToSpeckle(elements - .First(e => e.applicationId == slab.applicationId) - .model); + // convert between DTOs + Objects.BuiltElements.Archicad.ArchicadFloor slab = + Archicad.Converters.Utils.ConvertDTOs(jToken); + + slab.units = Units.Meters; + slab.displayValue = Operations.ModelConverter.MeshesToSpeckle( + elements.First(e => e.applicationId == slab.applicationId).model + ); slab.outline = Utils.PolycurveToSpeckle(slab.shape.contourPolyline); if (slab.shape.holePolylines?.Count > 0) slab.voids = new List(slab.shape.holePolylines.Select(Utils.PolycurveToSpeckle)); diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/GridLineConverter.cs b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/GridLineConverter.cs new file mode 100644 index 0000000000..c195093cdb --- /dev/null +++ b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/GridLineConverter.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Archicad.Communication; +using Archicad.Model; +using Objects.Geometry; +using Speckle.Core.Kits; +using Speckle.Core.Models; +using Speckle.Core.Models.GraphTraversal; + +namespace Archicad.Converters +{ + public sealed class GridLineConverter : IConverter + { + public Type Type => typeof(Archicad.GridElement); + + public async Task> ConvertToArchicad( + IEnumerable elements, + CancellationToken token + ) + { + var archicadGridElements = new List(); + + var context = Archicad.Helpers.Timer.Context.Peek; + using ( + context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name) + ) + { + foreach (var tc in elements) + { + token.ThrowIfCancellationRequested(); + + switch (tc.current) + { + case Objects.BuiltElements.GridLine grid: + + Archicad.GridElement archicadGridElement = new Archicad.GridElement + { + id = grid.id, + applicationId = grid.applicationId, + markerText = grid.label + }; + + if (grid.baseLine is Line) + { + var baseLine = (Line)grid.baseLine; + archicadGridElement.begin = Utils.ScaleToNative(baseLine.start); + archicadGridElement.end = Utils.ScaleToNative(baseLine.end); + archicadGridElement.isArc = false; + } + else if (grid.baseLine is Arc) + { + var baseLine = (Arc)grid.baseLine; + archicadGridElement.begin = Utils.ScaleToNative(baseLine.startPoint); + archicadGridElement.end = Utils.ScaleToNative(baseLine.endPoint); + archicadGridElement.arcAngle = baseLine.angleRadians; + archicadGridElement.isArc = true; + } + + archicadGridElements.Add(archicadGridElement); + break; + } + } + } + + var result = await AsyncCommandProcessor.Execute( + new Communication.Commands.CreateGridElement(archicadGridElements), + token + ); + + return result is null ? new List() : result.ToList(); + } + + public async Task> ConvertToSpeckle( + IEnumerable elements, + CancellationToken token + ) + { + var elementModels = elements as ElementModelData[] ?? elements.ToArray(); + IEnumerable data = await AsyncCommandProcessor.Execute( + new Communication.Commands.GetGridElementData(elementModels.Select(e => e.applicationId)), + token + ); + + if (data is null) + { + return new List(); + } + + List gridlines = new List(); + foreach (Archicad.GridElement archicadGridElement in data) + { + Objects.BuiltElements.GridLine speckleGridLine = new Objects.BuiltElements.GridLine(); + + // convert from Archicad to Speckle data structure + // Speckle base properties + speckleGridLine.id = archicadGridElement.id; + speckleGridLine.applicationId = archicadGridElement.applicationId; + speckleGridLine.label = archicadGridElement.markerText; + speckleGridLine.units = Units.Meters; + + // Archicad properties + // elementType and classifications do not exist in Objects.BuiltElements.GridLine + //speckleGrid.elementType = archicadGridElement.elementType; + //speckleGrid.classifications = archicadGridElement.classifications; + + if (!archicadGridElement.isArc) + { + speckleGridLine.baseLine = new Line(archicadGridElement.begin, archicadGridElement.end); + } + else + { + speckleGridLine.baseLine = new Arc( + archicadGridElement.begin, + archicadGridElement.end, + archicadGridElement.arcAngle + ); + } + + speckleGridLine.displayValue = Operations.ModelConverter + .MeshesAndLinesToSpeckle(elementModels.First(e => e.applicationId == archicadGridElement.applicationId).model) + .Cast() + .ToList(); + + gridlines.Add(speckleGridLine); + } + + return gridlines; + } + } +} diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/RoofConverter.cs b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/RoofConverter.cs index 3219385081..09e357daad 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/RoofConverter.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/RoofConverter.cs @@ -7,6 +7,7 @@ using Objects; using Objects.BuiltElements; using Speckle.Core.Models; +using Speckle.Core.Kits; using Speckle.Core.Models.GraphTraversal; namespace Archicad.Converters @@ -15,13 +16,18 @@ public sealed class Roof : IConverter { public Type Type => typeof(Objects.BuiltElements.Roof); - public async Task> ConvertToArchicad(IEnumerable elements, CancellationToken token) + public async Task> ConvertToArchicad( + IEnumerable elements, + CancellationToken token + ) { var roofs = new List(); var shells = new List(); var context = Archicad.Helpers.Timer.Context.Peek; - using (context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name)) + using ( + context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name) + ) { foreach (var tc in elements) { @@ -36,21 +42,30 @@ public async Task> ConvertToArchicad(IEnumerable 0 ? await AsyncCommandProcessor.Execute(new Communication.Commands.CreateRoof(roofs), token) : null; - var resultShells = shells.Count > 0 ? await AsyncCommandProcessor.Execute(new Communication.Commands.CreateShell(shells), token) : null; + var resultRoofs = + roofs.Count > 0 + ? await AsyncCommandProcessor.Execute(new Communication.Commands.CreateRoof(roofs), token) + : null; + var resultShells = + shells.Count > 0 + ? await AsyncCommandProcessor.Execute(new Communication.Commands.CreateShell(shells), token) + : null; - var result = new List (); + var result = new List(); if (resultRoofs is not null) result.AddRange(resultRoofs.ToList()); @@ -60,51 +75,73 @@ public async Task> ConvertToArchicad(IEnumerable() : result; } - public async Task> ConvertToSpeckle(IEnumerable elements, - CancellationToken token) + public async Task> ConvertToSpeckle( + IEnumerable elements, + CancellationToken token + ) { - var data = await AsyncCommandProcessor.Execute( - new Communication.Commands.GetRoofData(elements.Select(e => e.applicationId)), token); + Speckle.Newtonsoft.Json.Linq.JArray jArray = await AsyncCommandProcessor.Execute( + new Communication.Commands.GetRoofData(elements.Select(e => e.applicationId)), + token + ); - var Roofs = new List(); - foreach (var roof in data) + var roofs = new List(); + if (jArray is not null) { - roof.displayValue = Operations.ModelConverter.MeshesToSpeckle(elements - .First(e => e.applicationId == roof.applicationId) - .model); - - if (roof.shape != null) + foreach (Speckle.Newtonsoft.Json.Linq.JToken jToken in jArray) { - roof.outline = Utils.PolycurveToSpeckle(roof.shape.contourPolyline); - if (roof.shape.holePolylines?.Count > 0) - roof.voids = new List(roof.shape.holePolylines.Select(Utils.PolycurveToSpeckle)); - } + // convert between DTOs + Objects.BuiltElements.Archicad.ArchicadRoof roof = + Archicad.Converters.Utils.ConvertDTOs(jToken); - Roofs.Add(roof); + roof.units = Units.Meters; + roof.displayValue = Operations.ModelConverter.MeshesToSpeckle( + elements.First(e => e.applicationId == roof.applicationId).model + ); + + if (roof.shape != null) + { + roof.outline = Utils.PolycurveToSpeckle(roof.shape.contourPolyline); + if (roof.shape.holePolylines?.Count > 0) + roof.voids = new List(roof.shape.holePolylines.Select(Utils.PolycurveToSpeckle)); + } + + roofs.Add(roof); + } } - var shelldData = await AsyncCommandProcessor.Execute( - new Communication.Commands.GetShellData(elements.Select(e => e.applicationId)), token); + jArray = await AsyncCommandProcessor.Execute( + new Communication.Commands.GetShellData(elements.Select(e => e.applicationId)), + token + ); - var Shells = new List(); - foreach (var shell in shelldData) + var shells = new List(); + if (jArray is not null) { - shell.displayValue = Operations.ModelConverter.MeshesToSpeckle(elements - .First(e => e.applicationId == shell.applicationId) - .model); - - if (shell.shape != null) + foreach (Speckle.Newtonsoft.Json.Linq.JToken jToken in jArray) { - shell.outline = Utils.PolycurveToSpeckle(shell.shape.contourPolyline); - if (shell.shape.holePolylines?.Count > 0) - shell.voids = new List(shell.shape.holePolylines.Select(Utils.PolycurveToSpeckle)); - } + // convert between DTOs + Objects.BuiltElements.Archicad.ArchicadShell shell = + Archicad.Converters.Utils.ConvertDTOs(jToken); - Shells.Add(shell); + shell.units = Units.Meters; + shell.displayValue = Operations.ModelConverter.MeshesToSpeckle( + elements.First(e => e.applicationId == shell.applicationId).model + ); + + if (shell.shape != null) + { + shell.outline = Utils.PolycurveToSpeckle(shell.shape.contourPolyline); + if (shell.shape.holePolylines?.Count > 0) + shell.voids = new List(shell.shape.holePolylines.Select(Utils.PolycurveToSpeckle)); + } + + shells.Add(shell); + } } - var result = Roofs; - result.AddRange(Shells); + var result = roofs; + result.AddRange(shells); return result; } } diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/RoomConverter.cs b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/RoomConverter.cs index d460808e05..81bf3fcbc2 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/RoomConverter.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/RoomConverter.cs @@ -73,16 +73,10 @@ CancellationToken token name = speckleRoom.name, number = speckleRoom.number, // Archicad properties - shape = Utils.PolycurvesToElementShape(speckleRoom.outline, speckleRoom.voids), + level = Archicad.Converters.Utils.ConvertLevel(speckleRoom.level), + shape = Utils.PolycurvesToElementShape(speckleRoom.outline, speckleRoom.voids) }; - Objects.BuiltElements.Archicad.ArchicadLevel level = new Objects.BuiltElements.Archicad.ArchicadLevel(); - level.applicationId = speckleRoom.level.applicationId; - level.elevation = speckleRoom.level.elevation; - level.name = speckleRoom.level.name; - - archicadRoom.level = level; - rooms.Add(archicadRoom); } break; @@ -105,12 +99,11 @@ CancellationToken token new Communication.Commands.GetRoomData(elementModels.Select(e => e.applicationId)), token ); + + var rooms = new List(); if (data is null) - { - return new List(); - } + return rooms; - List rooms = new List(); foreach (Archicad.Room archicadRoom in data) { Objects.BuiltElements.Archicad.ArchicadRoom speckleRoom = new Objects.BuiltElements.Archicad.ArchicadRoom(); @@ -127,7 +120,7 @@ CancellationToken token // Archicad properties speckleRoom.elementType = archicadRoom.elementType; speckleRoom.classifications = archicadRoom.classifications; - speckleRoom.level = archicadRoom.level; + speckleRoom.archicadLevel = archicadRoom.level; speckleRoom.height = archicadRoom.height ?? .0; speckleRoom.shape = archicadRoom.shape; diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/SkylightConverter.cs b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/SkylightConverter.cs index cac4b21e63..13ae5309cc 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/SkylightConverter.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/SkylightConverter.cs @@ -17,12 +17,17 @@ public sealed class Skylight : IConverter { public Type Type => typeof(Objects.BuiltElements.Archicad.ArchicadSkylight); - public async Task> ConvertToArchicad(IEnumerable elements, CancellationToken token) + public async Task> ConvertToArchicad( + IEnumerable elements, + CancellationToken token + ) { var skylights = new List(); var context = Archicad.Helpers.Timer.Context.Peek; - using (context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name)) + using ( + context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name) + ) { foreach (var tc in elements) { @@ -34,14 +39,14 @@ public async Task> ConvertToArchicad(IEnumerable> ConvertToArchicad(IEnumerable() : result.ToList(); } - public async Task> ConvertToSpeckle(IEnumerable elements, - CancellationToken token) + public async Task> ConvertToSpeckle( + IEnumerable elements, + CancellationToken token + ) { // Get subelements var elementModels = elements as ElementModelData[] ?? elements.ToArray(); - IEnumerable datas = - await AsyncCommandProcessor.Execute(new Communication.Commands.GetSkylightData(elementModels.Select(e => e.applicationId))); + IEnumerable data = await AsyncCommandProcessor.Execute( + new Communication.Commands.GetSkylightData(elementModels.Select(e => e.applicationId)) + ); - if (datas is null) - { - return new List(); - } + var openings = new List(); + if (data is null) + return openings; - List openings = new List(); - foreach (Objects.BuiltElements.Archicad.ArchicadSkylight subelement in datas) + foreach (Objects.BuiltElements.Archicad.ArchicadSkylight subelement in data) { - subelement.displayValue = - Operations.ModelConverter.MeshesToSpeckle(elementModels.First(e => e.applicationId == subelement.applicationId) - .model); + subelement.displayValue = Operations.ModelConverter.MeshesToSpeckle( + elementModels.First(e => e.applicationId == subelement.applicationId).model + ); openings.Add(subelement); } diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/Utils.cs b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/Utils.cs index edcc3db3f5..1aff4bdcd3 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/Utils.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/Utils.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using System.Linq; +using System.Reflection; using Archicad.Model; using Objects; +using Objects.BuiltElements; using Objects.BuiltElements.Archicad; using Objects.Geometry; using Speckle.Core.Kits; @@ -13,17 +15,32 @@ public static class Utils { public static Point VertexToPoint(MeshModel.Vertex vertex) { - return new Point { x = vertex.x, y = vertex.y , z = vertex.z }; + return new Point + { + x = vertex.x, + y = vertex.y, + z = vertex.z + }; } public static Vector VertexToVector(MeshModel.Vertex vertex) { - return new Vector { x = vertex.x, y = vertex.y, z = vertex.z }; + return new Vector + { + x = vertex.x, + y = vertex.y, + z = vertex.z + }; } public static System.Numerics.Vector3 VertexToVector3(MeshModel.Vertex vertex) { - return new System.Numerics.Vector3 { X = (float)vertex.x, Y = (float)vertex.y, Z = (float)vertex.z }; + return new System.Numerics.Vector3 + { + X = (float)vertex.x, + Y = (float)vertex.y, + Z = (float)vertex.z + }; } public static Point ScaleToNative(Point point, string? units = null) @@ -44,7 +61,12 @@ public static MeshModel.Vertex PointToNative(Point point, string? units = null) units ??= point.units; var scale = Units.GetConversionFactor(units, Units.Meters); - return new MeshModel.Vertex { x = point.x * scale, y = point.y * scale, z = point.z * scale }; + return new MeshModel.Vertex + { + x = point.x * scale, + y = point.y * scale, + z = point.z * scale + }; } public static Polycurve PolycurveToSpeckle(ElementShape.Polyline archiPolyline) @@ -56,9 +78,11 @@ public static Polycurve PolycurveToSpeckle(ElementShape.Polyline archiPolyline) }; foreach (var segment in archiPolyline.polylineSegments) { - poly.segments.Add(segment.arcAngle == 0 - ? new Line(segment.startPoint, segment.endPoint) - : new Arc(segment.startPoint, segment.endPoint, segment.arcAngle)); + poly.segments.Add( + segment.arcAngle == 0 + ? new Line(segment.startPoint, segment.endPoint) + : new Arc(segment.startPoint, segment.endPoint, segment.arcAngle) + ); } return poly; @@ -90,8 +114,11 @@ public static ElementShape.Polyline PolylineToNative(Polyline polyline) public static ElementShape.PolylineSegment ArcToNative(Arc arc) { - return new ElementShape.PolylineSegment(ScaleToNative(arc.startPoint), ScaleToNative(arc.endPoint), - arc.angleRadians); + return new ElementShape.PolylineSegment( + ScaleToNative(arc.startPoint), + ScaleToNative(arc.endPoint), + arc.angleRadians + ); } public static ElementShape.Polyline? CurveToNative(ICurve curve) @@ -122,5 +149,30 @@ public static ElementShape PolycurvesToElementShape(ICurve outline, List return shape; } + + public static T ConvertDTOs(dynamic jObject) + { + Objects.BuiltElements.Archicad.ArchicadLevel level = + jObject.level.ToObject(); + + jObject.Remove("level"); + T speckleObject = jObject.ToObject(); + + PropertyInfo prop = speckleObject.GetType().GetProperty("archicadLevel"); + prop.SetValue(speckleObject, level); + + return speckleObject; + } + + public static Objects.BuiltElements.Archicad.ArchicadLevel ConvertLevel(Objects.BuiltElements.Level level) + { + return new Objects.BuiltElements.Archicad.ArchicadLevel + { + id = level.id, + applicationId = level.applicationId, + elevation = level.elevation * Units.GetConversionFactor(level.units, Units.Meters), + name = level.name + }; + } } } diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/WallConverter.cs b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/WallConverter.cs index ce12a80e6d..ca10a79bfb 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/WallConverter.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/WallConverter.cs @@ -5,9 +5,11 @@ using System.Threading.Tasks; using Archicad.Communication; using Archicad.Model; +using Objects.BuiltElements; using Objects.BuiltElements.Archicad; using Objects.BuiltElements.Revit; using Objects.Geometry; +using Speckle.Core.Kits; using Speckle.Core.Models; using Speckle.Core.Models.GraphTraversal; @@ -17,12 +19,17 @@ public sealed class Wall : IConverter { public Type Type => typeof(Objects.BuiltElements.Wall); - public async Task> ConvertToArchicad(IEnumerable elements, CancellationToken token) + public async Task> ConvertToArchicad( + IEnumerable elements, + CancellationToken token + ) { var walls = new List(); var context = Archicad.Helpers.Timer.Context.Peek; - using (context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name)) + using ( + context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name) + ) { foreach (var tc in elements) { @@ -35,10 +42,12 @@ public async Task> ConvertToArchicad(IEnumerable> ConvertToArchicad(IEnumerable() : result.ToList(); } - public async Task> ConvertToSpeckle(IEnumerable elements, - CancellationToken token) + public async Task> ConvertToSpeckle( + IEnumerable elements, + CancellationToken token + ) { - List walls = new List(); var elementModels = elements as ElementModelData[] ?? elements.ToArray(); - IEnumerable data = - await AsyncCommandProcessor.Execute( - new Communication.Commands.GetWallData(elementModels.Select(e => e.applicationId)), - token); + Speckle.Newtonsoft.Json.Linq.JArray jArray = await AsyncCommandProcessor.Execute( + new Communication.Commands.GetWallData(elementModels.Select(e => e.applicationId)), + token + ); - if (data is null) + var walls = new List(); + if (jArray is null) return walls; - foreach (Objects.BuiltElements.Archicad.ArchicadWall wall in data) + foreach (Speckle.Newtonsoft.Json.Linq.JToken jToken in jArray) { - wall.displayValue = - Operations.ModelConverter.MeshesToSpeckle(elementModels.First(e => e.applicationId == wall.applicationId) - .model); + // convert between DTOs + Objects.BuiltElements.Archicad.ArchicadWall wall = + Archicad.Converters.Utils.ConvertDTOs(jToken); + + wall.units = Units.Meters; + wall.displayValue = Operations.ModelConverter.MeshesToSpeckle( + elementModels.First(e => e.applicationId == wall.applicationId).model + ); wall.baseLine = new Line(wall.startPoint, wall.endPoint); walls.Add(wall); } diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/WindowConverter.cs b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/WindowConverter.cs index 818840b6d5..accd107a00 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/WindowConverter.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/WindowConverter.cs @@ -17,12 +17,17 @@ public sealed class Window : IConverter { public Type Type => typeof(Objects.BuiltElements.Archicad.ArchicadWindow); - public async Task> ConvertToArchicad(IEnumerable elements, CancellationToken token) + public async Task> ConvertToArchicad( + IEnumerable elements, + CancellationToken token + ) { var windows = new List(); var context = Archicad.Helpers.Timer.Context.Peek; - using (context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name)) + using ( + context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name) + ) { foreach (var tc in elements) { @@ -34,42 +39,43 @@ public async Task> ConvertToArchicad(IEnumerable() : result.ToList(); } - public async Task> ConvertToSpeckle(IEnumerable elements, - CancellationToken token) + public async Task> ConvertToSpeckle( + IEnumerable elements, + CancellationToken token + ) { // Get subelements var elementModels = elements as ElementModelData[] ?? elements.ToArray(); - IEnumerable datas = - await AsyncCommandProcessor.Execute(new Communication.Commands.GetWindowData(elementModels.Select(e => e.applicationId))); - - if (datas is null) - { - return new List(); - } + IEnumerable data = await AsyncCommandProcessor.Execute( + new Communication.Commands.GetWindowData(elementModels.Select(e => e.applicationId)) + ); List openings = new List(); - foreach (Objects.BuiltElements.Archicad.ArchicadWindow subelement in datas) + if (data is null) + return openings; + + foreach (Objects.BuiltElements.Archicad.ArchicadWindow subelement in data) { - subelement.displayValue = - Operations.ModelConverter.MeshesToSpeckle(elementModels.First(e => e.applicationId == subelement.applicationId) - .model); + subelement.displayValue = Operations.ModelConverter.MeshesToSpeckle( + elementModels.First(e => e.applicationId == subelement.applicationId).model + ); openings.Add(subelement); } diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/ElementConverterManager.cs b/ConnectorArchicad/ConnectorArchicad/Converters/ElementConverterManager.cs index 555756feac..4fb1e4b54c 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/ElementConverterManager.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/ElementConverterManager.cs @@ -20,6 +20,7 @@ using Wall = Objects.BuiltElements.Wall; using Window = Objects.BuiltElements.Archicad.ArchicadWindow; using Skylight = Objects.BuiltElements.Archicad.ArchicadSkylight; +using GridLine = Objects.BuiltElements.GridLine; namespace Archicad { @@ -53,7 +54,7 @@ private ElementConverterManager() public async Task ConvertToSpeckle(ISelectionFilter filter, ProgressViewModel progress) { - var objectToCommit = new Base(); + var objectToCommit = new Collection("Archicad model", "model"); IEnumerable elementIds = filter.Selection; if (filter.Slug == "all") @@ -87,9 +88,13 @@ private ElementConverterManager() ElementTypeProvider.GetTypeByName(element), progress.CancellationToken ); // Deserialize all objects with hiven type + if (objects.Count() > 0) { - objectToCommit["@" + element] = objects; // Save 'em. Assigned objects are parents with subelements + var elementCollection = new Collection(element, "Element Type"); + elementCollection.applicationId = element; + elementCollection.elements = objects; + objectToCommit.elements.Add(elementCollection); // itermediate solution for the OneClick Send report for (int i = 0; i < objects.Count(); i++) @@ -145,8 +150,15 @@ public Converters.IConverter GetConverterForElement( bool forReceive ) { - if (forReceive && conversionOptions != null && !conversionOptions.ReceiveParametric) - return DefaultConverterForReceive; + if (forReceive) + { + // always convert to Archicad GridElement + if (elementType.IsAssignableFrom(typeof(GridLine))) + return Converters[typeof(Archicad.GridElement)]; + + if (conversionOptions != null && !conversionOptions.ReceiveParametric) + return DefaultConverterForReceive; + } if (Converters.ContainsKey(elementType)) return Converters[elementType]; @@ -168,6 +180,8 @@ bool forReceive return Converters[typeof(Roof)]; if (elementType.IsAssignableFrom(typeof(Objects.BuiltElements.Room))) return Converters[typeof(Archicad.Room)]; + if (elementType.IsAssignableFrom(typeof(Archicad.GridElement))) + return Converters[typeof(Archicad.GridElement)]; return forReceive ? DefaultConverterForReceive : DefaultConverterForSend; } diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/ElementTypeProvider.cs b/ConnectorArchicad/ConnectorArchicad/Converters/ElementTypeProvider.cs index 91597861b5..0cb725ff86 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/ElementTypeProvider.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/ElementTypeProvider.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.Collections.Generic; using Beam = Objects.BuiltElements.Archicad.ArchicadBeam; using Column = Objects.BuiltElements.Archicad.ArchicadColumn; using DirectShape = Objects.BuiltElements.Archicad.DirectShape; using Door = Objects.BuiltElements.Archicad.ArchicadDoor; using Floor = Objects.BuiltElements.Archicad.ArchicadFloor; +using GridElement = Archicad.GridElement; using Roof = Objects.BuiltElements.Archicad.ArchicadRoof; using Room = Archicad.Room; using Shell = Objects.BuiltElements.Archicad.ArchicadShell; @@ -28,7 +29,8 @@ public static class ElementTypeProvider { "Column", typeof(Column) }, { "Door", typeof(Door) }, { "Window", typeof(Window) }, - { "Skylight", typeof(Skylight) } + { "Skylight", typeof(Skylight) }, + { "GridElement", typeof(GridElement) } }; public static Type GetTypeByName(string name) diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/ModelConverter.cs b/ConnectorArchicad/ConnectorArchicad/Converters/ModelConverter.cs index de5aab42c5..95346128b2 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/ModelConverter.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/ModelConverter.cs @@ -24,16 +24,46 @@ public static List MeshesToSpeckle(MeshModel meshModel) foreach (var poly in meshModel.polygons) { var meshIndex = poly.material; - meshes[meshIndex].vertices.AddRange(poly.pointIds.SelectMany(id => FlattenPoint(meshModel.vertices[id])) - .ToList()); - meshes[meshIndex].faces - .AddRange(PolygonToSpeckle(poly, vertCount[meshIndex])); + meshes[meshIndex].vertices.AddRange( + poly.pointIds.SelectMany(id => FlattenPoint(meshModel.vertices[id])).ToList() + ); + meshes[meshIndex].faces.AddRange(PolygonToSpeckle(poly, vertCount[meshIndex])); vertCount[meshIndex] += poly.pointIds.Count; } return meshes; } + public static List MeshesAndLinesToSpeckle(MeshModel meshModel) + { + List meshes = MeshesToSpeckle(meshModel).Cast().ToList(); + + List lines = new List(); + foreach (var edge in meshModel.edges) + { + if (edge.Value.polygonId1 == EdgeData.InvalidPolygonId && edge.Value.polygonId2 == EdgeData.InvalidPolygonId) + { + var line = new Line( + new Point( + meshModel.vertices[edge.Key.vertexId1].x, + meshModel.vertices[edge.Key.vertexId1].y, + meshModel.vertices[edge.Key.vertexId1].z + ), + new Point( + meshModel.vertices[edge.Key.vertexId2].x, + meshModel.vertices[edge.Key.vertexId2].y, + meshModel.vertices[edge.Key.vertexId2].z + ) + ); + + lines.Add(line); + } + } + + meshes.AddRange(lines.Cast().ToList()); + return meshes; + } + public static MeshModel MeshToNative(IEnumerable meshes) { var context = Archicad.Helpers.Timer.Context.Peek; @@ -41,7 +71,7 @@ public static MeshModel MeshToNative(IEnumerable meshes) { var mergedVertexIndices = new Dictionary(); var originalToMergedVertexIndices = new List(); - var neigbourPolygonsByEdge = new Dictionary, List>(); + var neigbourPolygonsByEdge = new Dictionary>(); var polygonNormals = new Dictionary(); var vertexOffset = 0; @@ -82,9 +112,10 @@ public static MeshModel MeshToNative(IEnumerable meshes) var polygon = new Polygon(); var n = mesh.faces[i]; - if (n < 3) n += 3; + if (n < 3) + n += 3; - for (var vertexIdx = i+1; vertexIdx <= i+n; vertexIdx++) + for (var vertexIdx = i + 1; vertexIdx <= i + n; vertexIdx++) { var pointId = ToMergedVertexIndex(mesh.faces[vertexIdx]); if (polygon.pointIds.Count == 0 || pointId != polygon.pointIds[^1]) @@ -183,7 +214,8 @@ private static IEnumerable PolygonToSpeckle(MeshModel.Polygon polygon, int for (var i = 0; i < polygon.Count; i++) { var n = polygon[i]; - if (n < 3) n += 3; + if (n < 3) + n += 3; result.Add(new MeshModel.Polygon { pointIds = polygon.GetRange(i + 1, n) }); i += n; } @@ -213,7 +245,12 @@ private static Model.MeshModel.Material MaterialToNative(RenderMaterial renderMa Model.MeshModel.Material.Color ConvertColor(System.Drawing.Color color) { // In AC the Colors are encoded in ushort - return new Model.MeshModel.Material.Color { red = color.R * 256, green = color.G * 256, blue = color.B * 256 }; + return new Model.MeshModel.Material.Color + { + red = color.R * 256, + green = color.G * 256, + blue = color.B * 256 + }; } return new Model.MeshModel.Material @@ -225,11 +262,16 @@ Model.MeshModel.Material.Color ConvertColor(System.Drawing.Color color) }; } - private static void ProcessPolygonEdges(MeshModel meshModel, Dictionary, List> neigbourPolygonsByEdge, Dictionary polygonNormals, Polygon polygon) + private static void ProcessPolygonEdges( + MeshModel meshModel, + Dictionary> neigbourPolygonsByEdge, + Dictionary polygonNormals, + Polygon polygon + ) { for (var pointIdx = 0; pointIdx < polygon.pointIds.Count; pointIdx++) { - var edge = new Tuple(polygon.pointIds[pointIdx], polygon.pointIds[(pointIdx + 1) % polygon.pointIds.Count]); + var edge = new EdgeId(polygon.pointIds[pointIdx], polygon.pointIds[(pointIdx + 1) % polygon.pointIds.Count]); if (TryGetNeigbourPolygonListByEdge(neigbourPolygonsByEdge, ref edge, out List neigbourPolygonIdxs)) { if (!neigbourPolygonIdxs.Contains(meshModel.polygons.Count)) @@ -237,71 +279,89 @@ private static void ProcessPolygonEdges(MeshModel meshModel, Dictionary 2) - meshModel.edges[edge] = EdgeStatus.HiddenEdge; + meshModel.edges[edge] = new EdgeData(EdgeStatus.HiddenEdge); else if (IsHiddenEdge(edge, meshModel.polygons[neigbourPolygonIdxs[0]], polygon, polygonNormals, meshModel)) { - meshModel.edges[edge] = EdgeStatus.HiddenEdge; + meshModel.edges[edge] = new EdgeData(EdgeStatus.HiddenEdge); } } } else { neigbourPolygonsByEdge.Add(edge, new List { meshModel.polygons.Count }); - meshModel.edges.Add(edge, EdgeStatus.VisibleEdge); + meshModel.edges.Add(edge, new EdgeData(EdgeStatus.VisibleEdge)); } } } // try to find the list of neighbouring polygons of an edge // returns true if the edge or its inversion is present in neigbourPolygonsByEdge dictionary as key - private static bool TryGetNeigbourPolygonListByEdge(Dictionary, List> neigbourPolygonsByEdge, ref Tuple edge, out List neigbourPolygonIndices) + private static bool TryGetNeigbourPolygonListByEdge( + Dictionary> neigbourPolygonsByEdge, + ref EdgeId edge, + out List neigbourPolygonIndices + ) { if (neigbourPolygonsByEdge.TryGetValue(edge, out neigbourPolygonIndices)) return true; - edge = new Tuple(edge.Item2, edge.Item1); + edge = new EdgeId(edge.vertexId2, edge.vertexId1); return neigbourPolygonsByEdge.TryGetValue(edge, out neigbourPolygonIndices); } - private static System.Numerics.Vector3 GetOrientedNormal (Polygon polygon, Dictionary polygonNormals, MeshModel meshModel) + private static System.Numerics.Vector3 GetOrientedNormal( + Polygon polygon, + Dictionary polygonNormals, + MeshModel meshModel + ) { if (polygonNormals.TryGetValue(polygon, out System.Numerics.Vector3 normal)) return normal; - normal = new System.Numerics.Vector3 (); - System.Numerics.Vector3 vertex0, vertex1, vertex2; + normal = new System.Numerics.Vector3(); + System.Numerics.Vector3 vertex0, + vertex1, + vertex2; vertex0 = Utils.VertexToVector3(meshModel.vertices[polygon.pointIds[0]]); int count = polygon.pointIds.Count; for (int first = count - 1, second = 0; second < count; first = second++) - { + { vertex1 = Utils.VertexToVector3(meshModel.vertices[polygon.pointIds[first]]); vertex2 = Utils.VertexToVector3(meshModel.vertices[polygon.pointIds[second]]); - normal += System.Numerics.Vector3.Cross (vertex1 - vertex0, vertex2 - vertex0); + normal += System.Numerics.Vector3.Cross(vertex1 - vertex0, vertex2 - vertex0); } polygonNormals.Add(polygon, normal); return normal; } - private static int GetOrientation (Tuple edge, Polygon polygon) + private static int GetOrientation(EdgeId edge, Polygon polygon) { int count = polygon.pointIds.Count; for (int first = count - 1, second = 0; second < count; first = second++) { - if (polygon.pointIds[first] == edge.Item1 && polygon.pointIds[second] == edge.Item2) + if (polygon.pointIds[first] == edge.vertexId1 && polygon.pointIds[second] == edge.vertexId2) return 1; - if (polygon.pointIds[first] == edge.Item2 && polygon.pointIds[second] == edge.Item1) + if (polygon.pointIds[first] == edge.vertexId2 && polygon.pointIds[second] == edge.vertexId1) return -1; } return 0; } - private static bool IsHiddenEdge(Tuple edge, Polygon polygon1, Polygon polygon2, Dictionary polygonNormals, MeshModel meshModel) + private static bool IsHiddenEdge( + EdgeId edge, + Polygon polygon1, + Polygon polygon2, + Dictionary polygonNormals, + MeshModel meshModel + ) { - System.Numerics.Vector3 normal1 = GetOrientation(edge, polygon1) * GetOrientedNormal(polygon1, polygonNormals, meshModel); - System.Numerics.Vector3 normal2 = -1 * GetOrientation(edge, polygon2) * GetOrientedNormal(polygon2, polygonNormals, meshModel); + System.Numerics.Vector3 normal1 = + GetOrientation(edge, polygon1) * GetOrientedNormal(polygon1, polygonNormals, meshModel); + System.Numerics.Vector3 normal2 = + -1 * GetOrientation(edge, polygon2) * GetOrientedNormal(polygon2, polygonNormals, meshModel); normal1 = System.Numerics.Vector3.Normalize(normal1); normal2 = System.Numerics.Vector3.Normalize(normal2); diff --git a/ConnectorArchicad/ConnectorArchicad/Elements/GridElement.cs b/ConnectorArchicad/ConnectorArchicad/Elements/GridElement.cs new file mode 100644 index 0000000000..520c842f6a --- /dev/null +++ b/ConnectorArchicad/ConnectorArchicad/Elements/GridElement.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using Objects.Geometry; +using Objects.BuiltElements.Archicad; + +namespace Archicad +{ + public class GridElement + { + // Speckle-specific properties + // Base + public string? id { get; set; } + public string? applicationId { get; set; } + + // Archicad API properties + // Element base + public string? elementType { get; set; } + public List? classifications { get; set; } + + // Grid + public Point begin { get; set; } + public Point end { get; set; } + public string markerText { get; set; } + public bool isArc { get; set; } + public double arcAngle { get; set; } + + public GridElement() { } + + public GridElement(string id, string applicationId) + { + this.id = id; + this.applicationId = applicationId; + } + } +} diff --git a/ConnectorArchicad/ConnectorArchicad/Elements/Object.cs b/ConnectorArchicad/ConnectorArchicad/Elements/Object.cs index 713d451437..92d5e34fe9 100644 --- a/ConnectorArchicad/ConnectorArchicad/Elements/Object.cs +++ b/ConnectorArchicad/ConnectorArchicad/Elements/Object.cs @@ -10,7 +10,6 @@ using Speckle.Newtonsoft.Json; using Archicad.Model; - namespace Archicad { public class ArchicadObject @@ -24,6 +23,8 @@ public class ArchicadObject public ArchicadLevel level { get; set; } + public string? layer { get; set; } + public Point pos { get; set; } public Objects.Other.Transform transform { get; set; } @@ -34,7 +35,6 @@ public class ArchicadObject public ArchicadObject() { } [SchemaInfo("ArchicadObject", "Creates an Archicad object.", "Archicad", "Structure")] - public ArchicadObject(string id, string applicationId, Point basePoint, List modelIds) { this.id = id; diff --git a/ConnectorArchicad/ConnectorArchicad/Elements/Room.cs b/ConnectorArchicad/ConnectorArchicad/Elements/Room.cs index ad2ccbaebf..a7bf63e8f4 100644 --- a/ConnectorArchicad/ConnectorArchicad/Elements/Room.cs +++ b/ConnectorArchicad/ConnectorArchicad/Elements/Room.cs @@ -26,6 +26,7 @@ public class Room public string? elementType { get; set; } public List? classifications { get; set; } public ArchicadLevel? level { get; set; } + public string? layer { get; set; } // Room public double? height { get; set; } diff --git a/ConnectorArchicad/ConnectorArchicad/Model/MeshModel.cs b/ConnectorArchicad/ConnectorArchicad/Model/MeshModel.cs index 477f29b2d5..e5b687f60e 100644 --- a/ConnectorArchicad/ConnectorArchicad/Model/MeshModel.cs +++ b/ConnectorArchicad/ConnectorArchicad/Model/MeshModel.cs @@ -1,15 +1,15 @@ using System; using System.Collections.Generic; using System.Text; -using Microsoft.Extensions.Primitives; -using Speckle.Newtonsoft.Json; using Objects.Geometry; +using Speckle.Newtonsoft.Json; +using Speckle.Newtonsoft.Json.Linq; namespace Archicad.Model { public sealed class MeshModel { - public enum EdgeStatus + public enum EdgeStatus { HiddenEdge = 1, // invisible SmoothEdge = 2, // visible if countour bit @@ -18,9 +18,9 @@ public enum EdgeStatus #region --- Classes --- - public class MeshModelEdgeConverter : JsonConverter, EdgeStatus>> + public class MeshModelEdgeConverter : JsonConverter> { - public override void WriteJson(JsonWriter writer, Dictionary, EdgeStatus> value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, Dictionary value, JsonSerializer serializer) { StringBuilder jsonString = new StringBuilder(); jsonString.Append("["); @@ -28,30 +28,93 @@ public override void WriteJson(JsonWriter writer, Dictionary, Ed bool first = true; foreach (var entry in value) { + // skip hidden edges + if (entry.Value.edgeStatus == EdgeStatus.HiddenEdge) + continue; + if (!first) jsonString.Append(", "); else first = false; - jsonString.Append("{ \"first\": "); + jsonString.Append("{ \"v1\": "); + jsonString.Append(entry.Key.vertexId1.ToString()); + + jsonString.Append(", \"v2\": "); + jsonString.Append(entry.Key.vertexId2.ToString()); - jsonString.Append("{ \"first\": "); - jsonString.Append(entry.Key.Item1.ToString()); - jsonString.Append(", \"second\": "); - jsonString.Append(entry.Key.Item2.ToString()); - jsonString.Append(" }"); + if (entry.Value.polygonId1 != EdgeData.InvalidPolygonId) + { + jsonString.Append(", \"p1\": "); + jsonString.Append(entry.Value.polygonId1.ToString()); + } - jsonString.Append(", \"second\" :"); - jsonString.Append(((byte)entry.Value).ToString()); - jsonString.Append(" }"); + if (entry.Value.polygonId2 != EdgeData.InvalidPolygonId) + { + jsonString.Append(", \"p2\": "); + jsonString.Append(entry.Value.polygonId2.ToString()); + } + + jsonString.Append(", \"s\": \""); + jsonString.Append(entry.Value.edgeStatus.ToString()); + + jsonString.Append("\" }"); } - jsonString.Append ("]"); + + jsonString.Append("]"); writer.WriteRawValue(jsonString.ToString()); } - public override Dictionary, EdgeStatus> ReadJson(JsonReader reader, Type objectType, Dictionary, EdgeStatus> existingValue, bool hasExistingValue, JsonSerializer serializer) + public override Dictionary ReadJson( + JsonReader reader, + Type objectType, + Dictionary existingValue, + bool hasExistingValue, + JsonSerializer serializer + ) { - return new Dictionary, EdgeStatus>(); + Dictionary edges = new Dictionary(); + + JArray ja = JArray.Load(reader); + + foreach (JObject jo in ja) + { + JToken v1; + jo.TryGetValue("v1", out v1); + + JToken v2; + jo.TryGetValue("v2", out v2); + + JToken s; + jo.TryGetValue("s", out s); + + JToken p1; + jo.TryGetValue("p1", out p1); + + JToken p2; + jo.TryGetValue("p2", out p2); + + if (v1 == null || v2 == null || s == null) + continue; + + EdgeId edgeId = new EdgeId(((int)v1), ((int)v2)); + + MeshModel.EdgeStatus edgeStatus = MeshModel.EdgeStatus.HiddenEdge; + if (((string)s).Equals("SmoothEdge")) + edgeStatus = MeshModel.EdgeStatus.SmoothEdge; + else if (((string)s).Equals("VisibleEdge")) + edgeStatus = MeshModel.EdgeStatus.VisibleEdge; + + EdgeData edgeData = new EdgeData( + edgeStatus, + p1 != null ? ((int)p1) : MeshModel.EdgeData.InvalidPolygonId, + p2 != null ? ((int)p2) : MeshModel.EdgeData.InvalidPolygonId + ); + + edges.Add(edgeId, edgeData); + } + + return edges; } } @@ -74,6 +137,59 @@ public sealed class Vertex #endregion } + public sealed class EdgeId + { + #region --- Fields --- + + public int vertexId1 { get; set; } + + public int vertexId2 { get; set; } + + #endregion + + #region --- Methods --- + + public EdgeId(int vertexId1, int vertexId2) + { + this.vertexId1 = vertexId1; + this.vertexId2 = vertexId2; + } + + public bool Equals(EdgeId edge) => edge.vertexId1.Equals(vertexId1) && edge.vertexId2.Equals(vertexId2); + + public override bool Equals(object o) => Equals(o as EdgeId); + + public override int GetHashCode() => vertexId1.GetHashCode() ^ vertexId2.GetHashCode(); + + #endregion + } + + public sealed class EdgeData + { + public const int InvalidPolygonId = -1; + + #region --- Fields --- + + public EdgeStatus edgeStatus { get; set; } + + public int polygonId1 { get; set; } + + public int polygonId2 { get; set; } + + #endregion + + #region --- Methods --- + + public EdgeData(EdgeStatus edgeStatus, int polygonId1 = InvalidPolygonId, int polygonId2 = InvalidPolygonId) + { + this.edgeStatus = edgeStatus; + this.polygonId1 = polygonId1; + this.polygonId2 = polygonId2; + } + + #endregion + } + public sealed class Material { #region --- Classes --- @@ -85,7 +201,6 @@ public class Color public int green { get; set; } public int blue { get; set; } - } #endregion @@ -116,16 +231,16 @@ public sealed class Polygon public List ids { get; set; } = new List(); - public List polygons { get; set; } = new List(); - public List vertices { get; set; } = new List(); - public List materials { get; set; } = new List(); - [JsonConverter(typeof(MeshModelEdgeConverter))] - public Dictionary, EdgeStatus> edges { get; set; } = new Dictionary, EdgeStatus>(); + public Dictionary edges { get; set; } = new Dictionary(); + + public List polygons { get; set; } = new List(); + + public List materials { get; set; } = new List(); - public bool IsCoplanar (Polygon polygon) + public bool IsCoplanar(Polygon polygon) { Vector vector1 = Archicad.Converters.Utils.VertexToVector(vertices[polygon.pointIds[0]]); Vector vector2 = Archicad.Converters.Utils.VertexToVector(vertices[polygon.pointIds[1]]); @@ -141,7 +256,7 @@ public bool IsCoplanar (Polygon polygon) private bool IsCoplanar(Vector vector1, Vector vector2, Vector vector3, Vector vector4) { - var dotProduct = Vector.DotProduct(vector2 - vector1, Vector.CrossProduct(vector4 - vector1, vector3 - vector1)); + var dotProduct = Vector.DotProduct(vector2 - vector1, Vector.CrossProduct(vector4 - vector1, vector3 - vector1)); return Math.Abs(dotProduct) < Speckle.Core.Helpers.Constants.SmallEps; } diff --git a/ConnectorCSI/ConnectorCSIBridge/ConnectorCSIBridge.csproj b/ConnectorCSI/ConnectorCSIBridge/ConnectorCSIBridge.csproj index f427af43a9..e2d7c9fdb9 100644 --- a/ConnectorCSI/ConnectorCSIBridge/ConnectorCSIBridge.csproj +++ b/ConnectorCSI/ConnectorCSIBridge/ConnectorCSIBridge.csproj @@ -5,6 +5,6 @@ false - + \ No newline at end of file diff --git a/ConnectorCSI/ConnectorCSIShared/ConnectorCSIShared.projitems b/ConnectorCSI/ConnectorCSIShared/ConnectorCSIShared.projitems index 33a9ed6ec4..054ad3d0d5 100644 --- a/ConnectorCSI/ConnectorCSIShared/ConnectorCSIShared.projitems +++ b/ConnectorCSI/ConnectorCSIShared/ConnectorCSIShared.projitems @@ -19,5 +19,6 @@ + \ No newline at end of file diff --git a/ConnectorCSI/ConnectorCSIShared/StreamStateManager/StreamStateManager.cs b/ConnectorCSI/ConnectorCSIShared/StreamStateManager/StreamStateManager.cs index 084f242289..be01ee1006 100644 --- a/ConnectorCSI/ConnectorCSIShared/StreamStateManager/StreamStateManager.cs +++ b/ConnectorCSI/ConnectorCSIShared/StreamStateManager/StreamStateManager.cs @@ -12,6 +12,7 @@ namespace ConnectorCSI.Storage public static class StreamStateManager { private static string _speckleFilePath; + public static List ReadState(cSapModel model) { var strings = ReadSpeckleFile(model); @@ -37,9 +38,14 @@ public static List ReadState(cSapModel model) /// public static void WriteStreamStateList(cSapModel model, List streamStates) { - if (_speckleFilePath == null) + if (_speckleFilePath == null) GetOrCreateSpeckleFilePath(model); - FileStream fileStream = new FileStream(_speckleFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); + FileStream fileStream = new FileStream( + _speckleFilePath, + FileMode.Open, + FileAccess.ReadWrite, + FileShare.ReadWrite + ); using (var streamWriter = new StreamWriter(fileStream)) { @@ -50,8 +56,14 @@ public static void WriteStreamStateList(cSapModel model, List strea public static void ClearStreamStateList(cSapModel model) { - if (_speckleFilePath == null) GetOrCreateSpeckleFilePath(model); - FileStream fileStream = new FileStream(_speckleFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); + if (_speckleFilePath == null) + GetOrCreateSpeckleFilePath(model); + FileStream fileStream = new FileStream( + _speckleFilePath, + FileMode.Open, + FileAccess.ReadWrite, + FileShare.ReadWrite + ); try { fileStream.SetLength(0); @@ -106,7 +118,8 @@ private static string ReadSpeckleFile(cSapModel model) if (_speckleFilePath == null) GetOrCreateSpeckleFilePath(model); - if (_speckleFilePath == null) return ""; + if (_speckleFilePath == null) + return ""; FileStream fileStream = new FileStream(_speckleFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); try { @@ -115,7 +128,10 @@ private static string ReadSpeckleFile(cSapModel model) return streamReader.ReadToEnd(); } } - catch { return ""; } + catch + { + return ""; + } } /// @@ -136,9 +152,12 @@ public static void SaveBackupFile(cSapModel model) string speckleFolderPath = Path.Combine(CSIModelFolder, "speckle"); var backups = new List<(DateTime, string)>(); - foreach (var fileName in Directory.GetFiles(speckleFolderPath)) - { - if (fileName.Contains($"{CSIFileName}_speckleBackup") && fileName.Split('.').Last().ToLower() == fileExtension.ToLower()) + foreach (var fileName in Directory.GetFiles(speckleFolderPath)) + { + if ( + fileName.Contains($"{CSIFileName}_speckleBackup") + && fileName.Split('.').Last().ToLower() == fileExtension.ToLower() + ) { backups.Add((File.GetLastWriteTime(fileName), fileName)); } @@ -146,7 +165,9 @@ public static void SaveBackupFile(cSapModel model) if (backups.Count < 3) { - model.File.Save(Path.Combine(speckleFolderPath, $"{CSIFileName}_speckleBackup{backups.Count + 1}.{fileExtension}")); + model.File.Save( + Path.Combine(speckleFolderPath, $"{CSIFileName}_speckleBackup{backups.Count + 1}.{fileExtension}") + ); } else { @@ -159,4 +180,4 @@ public static void SaveBackupFile(cSapModel model) model.File.Save(CSIModelfilePath); } } -} \ No newline at end of file +} diff --git a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.ClientOperations.cs b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.ClientOperations.cs index 828660b364..a217c234b8 100644 --- a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.ClientOperations.cs +++ b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.ClientOperations.cs @@ -12,7 +12,6 @@ namespace Speckle.ConnectorCSI.UI { public partial class ConnectorBindingsCSI : ConnectorBindings - { #region Local stream I/O with local file public override List GetCustomStreamMenuItems() @@ -33,4 +32,4 @@ public override List GetStreamsInFile() #endregion } -} \ No newline at end of file +} diff --git a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Recieve.cs b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Recieve.cs index e8259387b5..d810f40ada 100644 --- a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Recieve.cs +++ b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Recieve.cs @@ -1,4 +1,4 @@ -using ConnectorCSI.Storage; +using ConnectorCSI.Storage; using DesktopUI2; using DesktopUI2.Models; using DesktopUI2.ViewModels; @@ -11,9 +11,15 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Resources; using System.Threading.Tasks; +using Serilog.Context; +using Speckle.Core.Logging; +using Speckle.Core.Models.GraphTraversal; +using Speckle.Core.Logging; +using Speckle.Core.Kits.ConverterInterfaces; namespace Speckle.ConnectorCSI.UI { @@ -22,6 +28,7 @@ public partial class ConnectorBindingsCSI : ConnectorBindings public List Preview { get; set; } = new List(); public Dictionary StoredObjects = new Dictionary(); public override bool CanPreviewReceive => false; + public override Task PreviewReceive(StreamState state, ProgressViewModel progress) { return null; @@ -33,7 +40,7 @@ public override async Task ReceiveStream(StreamState state, Progres var kit = KitManager.GetDefaultKit(); var appName = GetHostAppVersion(Model); - var converter = kit.LoadConverter(appName); + ISpeckleConverter converter = kit.LoadConverter(appName); // set converter settings as tuples (setting slug, setting selection) // for csi, these must go before the SetContextDocument method. @@ -46,44 +53,41 @@ public override async Task ReceiveStream(StreamState state, Progres converter.SetContextDocument(Model); Exceptions.Clear(); var previouslyReceivedObjects = state.ReceivedObjects; - + progress.CancellationToken.ThrowIfCancellationRequested(); - - Exceptions.Clear(); + Exceptions.Clear(); Commit commit = await ConnectorHelpers.GetCommitFromState(state, progress.CancellationToken); state.LastCommit = commit; Base commitObject = await ConnectorHelpers.ReceiveCommit(commit, state, progress); await ConnectorHelpers.TryCommitReceived(state, commit, GetHostAppVersion(Model), progress.CancellationToken); - + Preview.Clear(); StoredObjects.Clear(); - var conversionProgressDict = new ConcurrentDictionary(); - conversionProgressDict["Conversion"] = 1; //Execute.PostToUIThread(() => state.Progress.Maximum = state.SelectedObjectIds.Count()); - Action updateProgressAction = () => - { - conversionProgressDict["Conversion"]++; - progress.Update(conversionProgressDict); - }; - Preview = FlattenCommitObject(commitObject, converter); foreach (var previewObj in Preview) progress.Report.Log(previewObj); converter.ReceiveMode = state.ReceiveMode; - // needs to be set for editing to work + // needs to be set for editing to work converter.SetPreviousContextObjects(previouslyReceivedObjects); progress.CancellationToken.ThrowIfCancellationRequested(); StreamStateManager.SaveBackupFile(Model); + using var d0 = LogContext.PushProperty("converterName", converter.Name); + using var d1 = LogContext.PushProperty("converterAuthor", converter.Author); + using var d2 = LogContext.PushProperty("conversionDirection", nameof(ISpeckleConverter.ConvertToNative)); + using var d3 = LogContext.PushProperty("converterSettings", settings); + using var d4 = LogContext.PushProperty("converterReceiveMode", converter.ReceiveMode); + var newPlaceholderObjects = ConvertReceivedObjects(converter, progress); - + DeleteObjects(previouslyReceivedObjects, newPlaceholderObjects, progress); // The following block of code is a hack to properly refresh the view @@ -101,105 +105,125 @@ public override async Task ReceiveStream(StreamState state, Progres return state; } + [SuppressMessage("Design", "CA1031:Do not catch general exception types")] private List ConvertReceivedObjects(ISpeckleConverter converter, ProgressViewModel progress) { - var placeholders = new List(); - var conversionProgressDict = new ConcurrentDictionary(); - conversionProgressDict["Conversion"] = 1; + List conversionResults = new(); + ConcurrentDictionary conversionProgressDict = new() { ["Conversion"] = 1 }; foreach (var obj in Preview) { if (!StoredObjects.ContainsKey(obj.OriginalId)) continue; - var @base = StoredObjects[obj.OriginalId]; progress.CancellationToken.ThrowIfCancellationRequested(); + var @base = StoredObjects[obj.OriginalId]; + using var _0 = LogContext.PushProperty("fromType", @base.GetType()); + try { - var convRes = converter.ConvertToNative(@base); - - switch (convRes) - { - case ApplicationObject o: - placeholders.Add(o); - obj.Update(status: o.Status, createdIds: o.CreatedIds, converted: o.Converted, log: o.Log); - progress.Report.UpdateReportObject(obj); - break; - default: - break; - } + var conversionResult = (ApplicationObject)converter.ConvertToNative(@base); + + var finalStatus = + conversionResult.Status != ApplicationObject.State.Unknown + ? conversionResult.Status + : ApplicationObject.State.Created; + + obj.Update( + status: finalStatus, + createdIds: conversionResult.CreatedIds, + converted: conversionResult.Converted, + log: conversionResult.Log + ); } - catch (Exception e) + catch (Exception ex) { - obj.Update(status: ApplicationObject.State.Failed, logItem: e.Message); - progress.Report.UpdateReportObject(obj); + ConnectorHelpers.LogConversionException(ex); + + var failureStatus = ConnectorHelpers.GetAppObjectFailureState(ex); + obj.Update(status: failureStatus, logItem: ex.Message); } + conversionResults.Add(obj); + + progress.Report.UpdateReportObject(obj); + conversionProgressDict["Conversion"]++; progress.Update(conversionProgressDict); } - return placeholders; + if (converter is IFinalizable finalizable) + { + finalizable.FinalizeConversion(); + } + + return conversionResults; } /// - /// Recurses through the commit object and flattens it. + /// Traverses the object graph, returning objects to be converted. /// - /// - /// - /// - private List FlattenCommitObject(object obj, ISpeckleConverter converter) + /// The root object to traverse + /// The converter instance, used to define what objects are convertable + /// A flattened list of objects to be converted ToNative + private List FlattenCommitObject(Base obj, ISpeckleConverter converter) { - var objects = new List(); - - if (obj is Base @base) + void StoreObject(Base b) { - var appObj = new ApplicationObject(@base.id, ConnectorCSIUtils.SimplifySpeckleType(@base.speckle_type)) { applicationId = @base.applicationId, Status = ApplicationObject.State.Unknown }; + if (!StoredObjects.ContainsKey(b.id)) + StoredObjects.Add(b.id, b); + } - if (converter.CanConvertToNative(@base)) + ApplicationObject CreateApplicationObject(Base current) + { + ApplicationObject NewAppObj() { - if (StoredObjects.ContainsKey(@base.id)) - return objects; + var speckleType = current.speckle_type + .Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries) + .LastOrDefault(); - appObj.Convertible = true; - objects.Add(appObj); - StoredObjects.Add(@base.id, @base); - return objects; + return new ApplicationObject(current.id, speckleType) { applicationId = current.applicationId, }; } - else + + //Handle convertable objects + if (converter.CanConvertToNative(current)) { - foreach (var prop in @base.GetMembers().Keys) - objects.AddRange(FlattenCommitObject(@base[prop], converter)); - return objects; + var appObj = NewAppObj(); + appObj.Convertible = true; + StoreObject(current); + return appObj; } - } - if (obj is IList list && list != null) - { - foreach (var listObj in list) - objects.AddRange(FlattenCommitObject(listObj, converter)); - return objects; - } + //Handle objects convertable using displayValues + var fallbackMember = DefaultTraversal.displayValuePropAliases + .Where(o => current[o] != null) + .Select(o => current[o]) + .FirstOrDefault(); - if (obj is IDictionary dict) - { - foreach (DictionaryEntry kvp in dict) - objects.AddRange(FlattenCommitObject(kvp.Value, converter)); - return objects; - } - - else - { - if (obj != null && !obj.GetType().IsPrimitive && !(obj is string)) + if (fallbackMember != null) { - var appObj = new ApplicationObject(obj.GetHashCode().ToString(), obj.GetType().ToString()); - appObj.Update(status: ApplicationObject.State.Skipped, logItem: $"Receiving this object type is not supported in CSI"); - objects.Add(appObj); + var appObj = NewAppObj(); + var fallbackObjects = GraphTraversal.TraverseMember(fallbackMember).Select(CreateApplicationObject); + appObj.Fallback.AddRange(fallbackObjects); + + StoreObject(current); + return appObj; } + + return null; } - return objects; + var traverseFunction = DefaultTraversal.CreateTraverseFunc(converter); + + var objectsToConvert = traverseFunction + .Traverse(obj) + .Select(tc => CreateApplicationObject(tc.current)) + .Where(appObject => appObject != null) + .Reverse() //just for the sake of matching the previous behaviour as close as possible + .ToList(); + + return objectsToConvert; } private void RefreshDatabaseTable(string floorTableKey) @@ -213,7 +237,14 @@ private void RefreshDatabaseTable(string floorTableKey) int numInfoMsgs = 0; int numErrorMsgs = 0; string importLog = ""; - Model.DatabaseTables.GetTableForEditingArray(floorTableKey, "ThisParamIsNotActiveYet", ref tableVersion, ref fieldsKeysIncluded, ref numberRecords, ref tableData); + Model.DatabaseTables.GetTableForEditingArray( + floorTableKey, + "ThisParamIsNotActiveYet", + ref tableVersion, + ref fieldsKeysIncluded, + ref numberRecords, + ref tableData + ); double version = 0; string versionString = null; @@ -225,21 +256,44 @@ private void RefreshDatabaseTable(string floorTableKey) if (programVersion.CompareTo("20.0.0") < 0 && fieldsKeysIncluded[0] == "UniqueName") fieldsKeysIncluded[0] = "Unique Name"; - Model.DatabaseTables.SetTableForEditingArray(floorTableKey, ref tableVersion, ref fieldsKeysIncluded, numberRecords, ref tableData); - Model.DatabaseTables.ApplyEditedTables(false, ref numFatalErrors, ref numErrorMsgs, ref numWarnMsgs, ref numInfoMsgs, ref importLog); + Model.DatabaseTables.SetTableForEditingArray( + floorTableKey, + ref tableVersion, + ref fieldsKeysIncluded, + numberRecords, + ref tableData + ); + Model.DatabaseTables.ApplyEditedTables( + false, + ref numFatalErrors, + ref numErrorMsgs, + ref numWarnMsgs, + ref numInfoMsgs, + ref importLog + ); } // delete previously sent objects that are no longer in this stream - private void DeleteObjects(List previouslyReceiveObjects, List newPlaceholderObjects, ProgressViewModel progress) + private void DeleteObjects( + IReadOnlyCollection previouslyReceiveObjects, + IReadOnlyCollection newPlaceholderObjects, + ProgressViewModel progress + ) { foreach (var obj in previouslyReceiveObjects) { - if (obj.Converted.Count == 0 || newPlaceholderObjects.Any(x => x.applicationId == obj.applicationId)) + if (obj.Converted.Count == 0) + continue; + if (newPlaceholderObjects.Any(x => x.applicationId == obj.applicationId)) continue; - for (int i = 0; i < obj.Converted.Count; i++) + foreach (var o in obj.Converted) { - if (!(obj.Converted[i] is string s && s.Split(new[] { ConnectorCSIUtils.delimiter }, StringSplitOptions.None) is string[] typeAndName && typeAndName.Length == 2)) + if (o is not string s) + continue; + + string[] typeAndName = s.Split(new[] { ConnectorCSIUtils.Delimiter }, StringSplitOptions.None); + if (typeAndName.Length != 2) continue; switch (typeAndName[0]) diff --git a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Selection.cs b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Selection.cs index 961c5231b8..8f673691e6 100644 --- a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Selection.cs +++ b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Selection.cs @@ -13,7 +13,8 @@ public override List GetSelectedObjects() { var names = new List(); var typeNameTupleList = ConnectorCSIUtils.SelectedObjects(Model); - if (typeNameTupleList == null) return new List() { }; + if (typeNameTupleList == null) + return new List() { }; foreach (var item in typeNameTupleList) { (string typeName, string name) = item; @@ -29,43 +30,48 @@ public override List GetSelectedObjects() public override List GetSelectionFilters() { var filters = new List(); - filters.Add(new AllSelectionFilter - { - Slug = "all", - Name = "Everything", - Icon = "CubeScan", - Description = "Selects all document objects." - }); + filters.Add( + new AllSelectionFilter + { + Slug = "all", + Name = "Everything", + Icon = "CubeScan", + Description = "Selects all document objects." + } + ); filters.Add(new ManualSelectionFilter()); if (Model != null) { ConnectorCSIUtils.GetObjectIDsTypesAndNames(Model); - var objectTypes = ConnectorCSIUtils.ObjectIDsTypesAndNames - .Select(pair => pair.Value.Item1).Distinct().ToList(); - + var objectTypes = ConnectorCSIUtils.ObjectIDsTypesAndNames.Select(pair => pair.Value.Item1).Distinct().ToList(); + if (objectTypes.Any()) - filters.Add(new ListSelectionFilter - { - Slug = "type", - Name = "Categories", - Icon = "Category", - Values = objectTypes, - Description = "Adds all objects belonging to the selected types." - }); + filters.Add( + new ListSelectionFilter + { + Slug = "type", + Name = "Categories", + Icon = "Category", + Values = objectTypes, + Description = "Adds all objects belonging to the selected types." + } + ); string[] groupNames = new string[0]; int numNames = 0; Model.GroupDef.GetNameList(ref numNames, ref groupNames); if (groupNames.Any()) - filters.Add(new ListSelectionFilter - { - Slug = "group", - Name = "Group", - Icon = "SelectGroup", - Values = groupNames.ToList(), - Description = "Add all objects belonging to CSI Group." - }); + filters.Add( + new ListSelectionFilter + { + Slug = "group", + Name = "Group", + Icon = "SelectGroup", + Values = groupNames.ToList(), + Description = "Add all objects belonging to CSI Group." + } + ); } return filters; @@ -89,8 +95,7 @@ private List GetSelectionFilterObjects(ISelectionFilter filter) return GetSelectedObjects(); case "all": - selection.AddRange(ConnectorCSIUtils.ObjectIDsTypesAndNames - .Select(pair => pair.Key).ToList()); + selection.AddRange(ConnectorCSIUtils.ObjectIDsTypesAndNames.Select(pair => pair.Key).ToList()); return selection; case "type": @@ -98,10 +103,12 @@ private List GetSelectionFilterObjects(ISelectionFilter filter) foreach (var type in typeFilter.Selection) { - selection.AddRange(ConnectorCSIUtils.ObjectIDsTypesAndNames + selection.AddRange( + ConnectorCSIUtils.ObjectIDsTypesAndNames .Where(pair => pair.Value.Item1 == type) .Select(pair => pair.Key) - .ToList()); + .ToList() + ); } return selection; @@ -110,14 +117,14 @@ private List GetSelectionFilterObjects(ISelectionFilter filter) Model.SelectObj.ClearSelection(); var groupFilter = filter as ListSelectionFilter; foreach (var group in groupFilter.Selection) - { Model.SelectObj.Group(group); } + { + Model.SelectObj.Group(group); + } return GetSelectedObjects(); - } return selection; - } } -} \ No newline at end of file +} diff --git a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Send.cs b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Send.cs index 470693c354..77b7c9345f 100644 --- a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Send.cs +++ b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Send.cs @@ -9,8 +9,10 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; +using Serilog.Context; using SCT = Speckle.Core.Transports; namespace Speckle.ConnectorCSI.UI @@ -18,6 +20,7 @@ namespace Speckle.ConnectorCSI.UI public partial class ConnectorBindingsCSI : ConnectorBindings { public override bool CanPreviewSend => false; + public override void PreviewSend(StreamState state, ProgressViewModel progress) { // TODO! @@ -47,11 +50,13 @@ public override async Task SendStream(StreamState state, ProgressViewMod if (state.Filter != null) state.SelectedObjectIds = GetSelectionFilterObjects(state.Filter); - var totalObjectCount = state.SelectedObjectIds.Count(); + var totalObjectCount = state.SelectedObjectIds.Count; if (totalObjectCount == 0) { - throw new InvalidOperationException( "Zero objects selected; send stopped. Please select some objects, or check that your filter can actually select something."); + throw new InvalidOperationException( + "Zero objects selected; send stopped. Please select some objects, or check that your filter can actually select something." + ); } var conversionProgressDict = new ConcurrentDictionary(); @@ -59,6 +64,11 @@ public override async Task SendStream(StreamState state, ProgressViewMod conversionProgressDict["Conversion"] = 0; progress.Update(conversionProgressDict); + using var d0 = LogContext.PushProperty("converterName", converter.Name); + using var d1 = LogContext.PushProperty("converterAuthor", converter.Author); + using var d2 = LogContext.PushProperty("conversionDirection", nameof(ISpeckleConverter.ConvertToSpeckle)); + using var d3 = LogContext.PushProperty("converterSettings", settings); + BuildSendCommitObj(converter, state.SelectedObjectIds, ref progress, ref conversionProgressDict); var commitObj = GetCommitObj(converter, progress, conversionProgressDict); @@ -66,7 +76,13 @@ public override async Task SendStream(StreamState state, ProgressViewMod return await SendCommitObj(state, progress, commitObj, conversionProgressDict); } - public void BuildSendCommitObj(ISpeckleConverter converter, List selectedObjIds, ref ProgressViewModel progress, ref ConcurrentDictionary conversionProgressDict) + [SuppressMessage("Design", "CA1031:Do not catch general exception types")] + public void BuildSendCommitObj( + ISpeckleConverter converter, + List selectedObjIds, + ref ProgressViewModel progress, + ref ConcurrentDictionary conversionProgressDict + ) { foreach (var applicationId in selectedObjIds) { @@ -75,10 +91,10 @@ public void BuildSendCommitObj(ISpeckleConverter converter, List selecte Base converted = null; string containerName = string.Empty; - var selectedObjectType = ConnectorCSIUtils.ObjectIDsTypesAndNames - .Where(pair => pair.Key == applicationId) - .Select(pair => pair.Value.Item1).FirstOrDefault(); + .Where(pair => pair.Key == applicationId) + .Select(pair => pair.Value.Item1) + .FirstOrDefault(); var reportObj = new ApplicationObject(applicationId, selectedObjectType) { applicationId = applicationId }; @@ -89,28 +105,31 @@ public void BuildSendCommitObj(ISpeckleConverter converter, List selecte } var typeAndName = ConnectorCSIUtils.ObjectIDsTypesAndNames - .Where(pair => pair.Key == applicationId) - .Select(pair => pair.Value).FirstOrDefault(); + .Where(pair => pair.Key == applicationId) + .Select(pair => pair.Value) + .FirstOrDefault(); + + using var _0 = LogContext.PushProperty("fromType", typeAndName.typeName); try { converted = converter.ConvertToSpeckle(typeAndName); + if (converted == null) + throw new ConversionException("Conversion Returned Null"); + + reportObj.Update( + status: ApplicationObject.State.Created, + logItem: $"Sent as {ConnectorCSIUtils.SimplifySpeckleType(converted.speckle_type)}" + ); } catch (Exception ex) { - reportObj.Update(status: ApplicationObject.State.Failed, logItem: ex.Message); - progress.Report.Log(reportObj); - continue; - } + ConnectorHelpers.LogConversionException(ex); - if (converted == null) - { - reportObj.Update(status: ApplicationObject.State.Failed, logItem: $"Conversion returned null"); - progress.Report.Log(reportObj); - continue; + var failureStatus = ConnectorHelpers.GetAppObjectFailureState(ex); + reportObj.Update(status: failureStatus, logItem: ex.Message); } - reportObj.Update(status: ApplicationObject.State.Created, logItem: $"Sent as {ConnectorCSIUtils.SimplifySpeckleType(converted.speckle_type)}"); progress.Report.Log(reportObj); conversionProgressDict["Conversion"]++; @@ -118,7 +137,11 @@ public void BuildSendCommitObj(ISpeckleConverter converter, List selecte } } - public Base GetCommitObj(ISpeckleConverter converter, ProgressViewModel progress, ConcurrentDictionary conversionProgressDict) + public Base GetCommitObj( + ISpeckleConverter converter, + ProgressViewModel progress, + ConcurrentDictionary conversionProgressDict + ) { var commitObj = new Base(); var reportObj = new ApplicationObject("model", "ModelInfo"); @@ -163,7 +186,13 @@ public Base GetCommitObj(ISpeckleConverter converter, ProgressViewModel progress return commitObj; } - public async Task SendCommitObj(StreamState state, ProgressViewModel progress, Base commitObj, ConcurrentDictionary conversionProgressDict, string branchName = null) + public async Task SendCommitObj( + StreamState state, + ProgressViewModel progress, + Base commitObj, + ConcurrentDictionary conversionProgressDict, + string branchName = null + ) { var streamId = state.StreamId; var client = state.Client; @@ -172,25 +201,29 @@ public async Task SendCommitObj(StreamState state, ProgressViewModel pro progress.Max = conversionProgressDict["Conversion"]; var objectId = await Operations.Send( - @object: commitObj, - cancellationToken: progress.CancellationToken, - transports: transports, - onProgressAction: dict => - { - progress.Update(dict); - }, - onErrorAction: ConnectorHelpers.DefaultSendErrorHandler, - disposeTransports: true - ); - - + @object: commitObj, + cancellationToken: progress.CancellationToken, + transports: transports, + onProgressAction: dict => + { + progress.Update(dict); + }, + onErrorAction: ConnectorHelpers.DefaultSendErrorHandler, + disposeTransports: true + ); + if (branchName != null) { var branchesSplit = state.BranchName.Split('/'); branchesSplit[branchesSplit.Count() - 1] = branchName; branchName = string.Join("", branchesSplit); - var branchInput = new BranchCreateInput() { streamId = streamId, name = branchName, description = "This branch holds the comprehensive reports generated by Speckle"}; + var branchInput = new BranchCreateInput() + { + streamId = streamId, + name = branchName, + description = "This branch holds the comprehensive reports generated by Speckle" + }; var branch = await client.BranchGet(streamId, branchName); if (branch == null) await client.BranchCreate(branchInput); @@ -203,14 +236,19 @@ public async Task SendCommitObj(StreamState state, ProgressViewModel pro streamId = streamId, objectId = objectId, branchName = branchName, - message = state.CommitMessage != null ? state.CommitMessage : $"Pushed {conversionProgressDict["Conversion"]} elements from CSI.", + message = + state.CommitMessage != null + ? state.CommitMessage + : $"Pushed {conversionProgressDict["Conversion"]} elements from CSI.", sourceApplication = GetHostAppVersion(Model) }; - if (state.PreviousCommitId != null) { actualCommit.parents = new List() { state.PreviousCommitId }; } + if (state.PreviousCommitId != null) + { + actualCommit.parents = new List() { state.PreviousCommitId }; + } return await ConnectorHelpers.CreateCommit(client, actualCommit, progress.CancellationToken); - } } } diff --git a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Settings.cs b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Settings.cs index 1e7550e7e6..a5f9fdc4ed 100644 --- a/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Settings.cs +++ b/ConnectorCSI/ConnectorCSIShared/UI/ConnectorBindingsCSI.Settings.cs @@ -1,4 +1,5 @@ -using DesktopUI2; +using ConnectorCSIShared.Util; +using DesktopUI2; using DesktopUI2.Models.Settings; using System; using System.Collections.Generic; @@ -9,16 +10,59 @@ namespace Speckle.ConnectorCSI.UI public partial class ConnectorBindingsCSI : ConnectorBindings { // WARNING: These strings need to have the same value as the strings in ConverterCSIUtils - readonly string SendNodeResults = "sendNodeResults"; - readonly string Send1DResults = "send1DResults"; - readonly string Send2DResults = "send2DResults"; + public const string RESULTS_NODE_SLUG = "node-results"; + public const string RESULTS_1D_SLUG = "1d-results"; + public const string RESULTS_2D_SLUG = "2d-results"; + public const string RESULTS_LOAD_CASES_SLUG = "load-cases"; + + public const string BEAM_FORCES = "Beam Forces"; + public const string BRACE_FORCES = "Brace Forces"; + public const string COLUMN_FORCES = "Column Forces"; + public const string OTHER_FORCES = "Other Forces"; + public const string FORCES = "Forces"; + public const string STRESSES = "Stresses"; + public const string DISPLACEMENTS = "Displacements"; + public const string VELOCITIES = "Velocities"; + public const string ACCELERATIONS = "Accelerations"; public override List GetSettings() { return new List { - new CheckBoxSetting {Slug = SendNodeResults, Name = "Send Node Analysis Results", Icon ="Link", IsChecked= false, Description = "Include node analysis results with object data when sending to Speckle. This will make the sending process take more time."}, - new CheckBoxSetting {Slug = Send1DResults, Name = "Send 1D Analysis Results", Icon ="Link", IsChecked= false, Description = "Include 1D analysis results with object data when sending to Speckle. This will make the sending process take more time."}, - new CheckBoxSetting {Slug = Send2DResults, Name = "Send 2D Analysis Results", Icon ="Link", IsChecked= false, Description = "Include 2D analysis results with object data when sending to Speckle. This will make the sending process take MUCH more time."}, + new MultiSelectBoxSetting + { + Slug = RESULTS_LOAD_CASES_SLUG, + Name = "Load Case Results To Send", + Icon = "Link", + Description = "Only the analytical results that belong to the load combinations selected here \nwill be sent to Speckle", + Values = ResultUtils.GetNamesOfAllLoadCasesAndCombos(Model) + }, + + new MultiSelectBoxSetting + { + Slug = RESULTS_NODE_SLUG, + Name = "Node Results To Send", + Icon = "Link", + Description = "Determines which node results are sent to Speckle", + Values = new List() { DISPLACEMENTS, FORCES, VELOCITIES, ACCELERATIONS } + }, + + new MultiSelectBoxSetting + { + Slug = RESULTS_1D_SLUG, + Name = "1D Element Results To Send", + Icon = "Link", + Description = "Determines which 1D element results and sent to Speckle", + Values = new List() { BEAM_FORCES, BRACE_FORCES, COLUMN_FORCES, OTHER_FORCES } + }, + + new MultiSelectBoxSetting + { + Slug = RESULTS_2D_SLUG, + Name = "2D Element Results To Send", + Icon = "Link", + Description = "Determines which 2D element results are computed and sent to Speckle", + Values = new List() { FORCES, STRESSES } + }, }; } } diff --git a/ConnectorCSI/ConnectorCSIShared/Util/ConnectorCSIUtils.cs b/ConnectorCSI/ConnectorCSIShared/Util/ConnectorCSIUtils.cs index e622e9c2f5..2655d3c1a1 100644 --- a/ConnectorCSI/ConnectorCSIShared/Util/ConnectorCSIUtils.cs +++ b/ConnectorCSI/ConnectorCSIShared/Util/ConnectorCSIUtils.cs @@ -25,12 +25,12 @@ public static class ConnectorCSIUtils // public static string CSISlug = HostApplications.CSI.Slug; //#endif - public static Dictionary ObjectIDsTypesAndNames { get; set; } + public static Dictionary ObjectIDsTypesAndNames { get; set; } public static List ConversionErrors { get; set; } // warning: this delimter string needs to be the same as the delimter string in "converterCSIUtils" - public static string delimiter = "::"; + public const string Delimiter = "::"; public static void GetObjectIDsTypesAndNames(cSapModel model) { @@ -126,6 +126,7 @@ public static List GetAllNamesOfObjectType(cSapModel model, string objec return null; } } + #region Get List Names public static List GetAllPointNames(cSapModel model) { @@ -136,9 +137,12 @@ public static List GetAllPointNames(cSapModel model) model.PointObj.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } - + catch + { + return null; + } } + public static List GetAllFrameNames(cSapModel model) { int num = 0; @@ -148,7 +152,10 @@ public static List GetAllFrameNames(cSapModel model) model.FrameObj.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } public static List GetColumnNames(cSapModel model) @@ -239,8 +246,12 @@ public static List GetAllTendonNames(cSapModel model) model.TendonObj.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllAreaNames(cSapModel model) { int num = 0; @@ -250,7 +261,10 @@ public static List GetAllAreaNames(cSapModel model) model.AreaObj.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } public static List GetAllWallNames(cSapModel model) @@ -274,6 +288,7 @@ public static List GetAllWallNames(cSapModel model) return WallName; } + public static List GetAllFloorNames(cSapModel model) { var FloorNames = GetAllAreaNames(model); @@ -295,6 +310,7 @@ public static List GetAllFloorNames(cSapModel model) return FloorName; } + public static List GetAllLinkNames(cSapModel model) { int num = 0; @@ -304,8 +320,12 @@ public static List GetAllLinkNames(cSapModel model) model.LinkObj.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllPropMaterialNames(cSapModel model) { int num = 0; @@ -315,8 +335,12 @@ public static List GetAllPropMaterialNames(cSapModel model) model.PropMaterial.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllPropRebarNames(cSapModel model) { int num = 0; @@ -326,8 +350,12 @@ public static List GetAllPropRebarNames(cSapModel model) model.PropRebar.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllPropFrameNames(cSapModel model) { int num = 0; @@ -337,8 +365,12 @@ public static List GetAllPropFrameNames(cSapModel model) model.PropFrame.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllLoadCaseNames(cSapModel model) { int num = 0; @@ -348,8 +380,12 @@ public static List GetAllLoadCaseNames(cSapModel model) model.LoadCases.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllGroupNames(cSapModel model) { int num = 0; @@ -359,8 +395,12 @@ public static List GetAllGroupNames(cSapModel model) model.GroupDef.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllGridNames(cSapModel model) { int num = 0; @@ -370,8 +410,12 @@ public static List GetAllGridNames(cSapModel model) model.GridSys.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllComboNames(cSapModel model) { int num = 0; @@ -381,8 +425,12 @@ public static List GetAllComboNames(cSapModel model) model.RespCombo.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllConstraintNames(cSapModel model) { int num = 0; @@ -392,8 +440,12 @@ public static List GetAllConstraintNames(cSapModel model) model.ConstraintDef.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllLoadPatternNames(cSapModel model) { int num = 0; @@ -403,8 +455,12 @@ public static List GetAllLoadPatternNames(cSapModel model) model.LoadPatterns.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllSteelDesignNames(cSapModel model) { var name = ""; @@ -413,8 +469,12 @@ public static List GetAllSteelDesignNames(cSapModel model) model.DesignSteel.GetCode(ref name); return new List() { name }; } - catch { return null; } + catch + { + return null; + } } + public static List GetAllConcreteDesignNames(cSapModel model) { var name = ""; @@ -423,8 +483,12 @@ public static List GetAllConcreteDesignNames(cSapModel model) model.DesignConcrete.GetCode(ref name); return new List() { name }; } - catch { return null; } + catch + { + return null; + } } + public static List GetAllStoryNames(cSapModel model) { int num = 0; @@ -434,8 +498,12 @@ public static List GetAllStoryNames(cSapModel model) model.Story.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllDiaphragmNames(cSapModel model) { int num = 0; @@ -445,8 +513,12 @@ public static List GetAllDiaphragmNames(cSapModel model) model.Diaphragm.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllLineNames(cSapModel model) { int num = 0; @@ -456,8 +528,12 @@ public static List GetAllLineNames(cSapModel model) model.LineElm.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllPierLabelNames(cSapModel model) { int num = 0; @@ -467,8 +543,12 @@ public static List GetAllPierLabelNames(cSapModel model) model.PierLabel.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllPropAreaSpringNames(cSapModel model) { int num = 0; @@ -478,8 +558,12 @@ public static List GetAllPropAreaSpringNames(cSapModel model) model.PropAreaSpring.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllPropLineSpringNames(cSapModel model) { int num = 0; @@ -489,8 +573,12 @@ public static List GetAllPropLineSpringNames(cSapModel model) model.PropLineSpring.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllPropPointSpringNames(cSapModel model) { int num = 0; @@ -500,8 +588,12 @@ public static List GetAllPropPointSpringNames(cSapModel model) model.PropPointSpring.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllSpandrelLabelNames(cSapModel model) { int num = 0; @@ -512,8 +604,12 @@ public static List GetAllSpandrelLabelNames(cSapModel model) model.SpandrelLabel.GetNameList(ref num, ref names, ref isMultiStory); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllTowerNames(cSapModel model) { int num = 0; @@ -523,8 +619,12 @@ public static List GetAllTowerNames(cSapModel model) model.Tower.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllPropTendonNames(cSapModel model) { int num = 0; @@ -534,8 +634,12 @@ public static List GetAllPropTendonNames(cSapModel model) model.PropTendon.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } + public static List GetAllPropLinkNames(cSapModel model) { int num = 0; @@ -545,7 +649,10 @@ public static List GetAllPropLinkNames(cSapModel model) model.PropLink.GetNameList(ref num, ref names); return names.ToList(); } - catch { return null; } + catch + { + return null; + } } #endregion @@ -620,6 +727,7 @@ public enum CSIAPIUsableTypes Tendon, LoadPattern, Model, + //Diaphragm, BeamLoading, ColumnLoading, @@ -630,7 +738,6 @@ public enum CSIAPIUsableTypes WallLoading, NodeLoading, - //ColumnResults, //BeamResults, //BraceResults, @@ -649,5 +756,4 @@ public enum CSIViewSelectableTypes Area = 4 } } - -} \ No newline at end of file +} diff --git a/ConnectorCSI/ConnectorCSIShared/Util/ResultUtils.cs b/ConnectorCSI/ConnectorCSIShared/Util/ResultUtils.cs new file mode 100644 index 0000000000..df3310906b --- /dev/null +++ b/ConnectorCSI/ConnectorCSIShared/Util/ResultUtils.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; +using CSiAPIv1; + +namespace ConnectorCSIShared.Util +{ + internal static class ResultUtils + { + public static List GetNamesOfAllLoadCasesAndCombos(cSapModel sapModel) + { + List names = new(); + + int numberOfLoadCombinations = 0; + string[] loadCombinationNames = Array.Empty(); + sapModel.RespCombo.GetNameList(ref numberOfLoadCombinations, ref loadCombinationNames); + names.AddRange(loadCombinationNames); + + sapModel.LoadCases.GetNameList(ref numberOfLoadCombinations, ref loadCombinationNames); + names.AddRange(loadCombinationNames); + + return names; + } + } +} diff --git a/ConnectorCSI/ConnectorCSIShared/cPlugin.cs b/ConnectorCSI/ConnectorCSIShared/cPlugin.cs index 0f9c021889..a1131185bb 100644 --- a/ConnectorCSI/ConnectorCSIShared/cPlugin.cs +++ b/ConnectorCSI/ConnectorCSIShared/cPlugin.cs @@ -1,9 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using DesktopUI2; using DesktopUI2.ViewModels; using DesktopUI2.Views; using System.Timers; @@ -13,9 +9,9 @@ using Avalonia.ReactiveUI; using CSiAPIv1; using Speckle.ConnectorCSI.UI; - using System.Reflection; using System.IO; +using Speckle.Core.Logging; namespace SpeckleConnectorCSI { @@ -30,13 +26,14 @@ public class cPlugin public static ConnectorBindingsCSI Bindings { get; set; } - public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() - .UsePlatformDetect() - .With(new SkiaOptions { MaxGpuResourceSizeBytes = 8096000 }) - .With(new Win32PlatformOptions { AllowEglInitialization = true, EnableMultitouch = false }) - .LogToTrace() - .UseReactiveUI(); - + public static AppBuilder BuildAvaloniaApp() => + AppBuilder + .Configure() + .UsePlatformDetect() + .With(new SkiaOptions { MaxGpuResourceSizeBytes = 8096000 }) + .With(new Win32PlatformOptions { AllowEglInitialization = true, EnableMultitouch = false }) + .LogToTrace() + .UseReactiveUI(); public static void CreateOrFocusSpeckle() { @@ -67,6 +64,7 @@ private static void AppMain(Application app, string[] args) public static void OpenOrFocusSpeckle(cSapModel model) { Bindings = new ConnectorBindingsCSI(model); + Setup.Init(Bindings.GetHostAppNameVersion(), Bindings.GetHostAppName()); CreateOrFocusSpeckle(); } @@ -125,6 +123,4 @@ public void Main(ref cSapModel SapModel, ref cPluginCallback ISapPlugin) return; } } - - } diff --git a/ConnectorCSI/ConnectorSAP2000/ConnectorSAP2000.csproj b/ConnectorCSI/ConnectorSAP2000/ConnectorSAP2000.csproj index 8f61141175..d953a8de1f 100644 --- a/ConnectorCSI/ConnectorSAP2000/ConnectorSAP2000.csproj +++ b/ConnectorCSI/ConnectorSAP2000/ConnectorSAP2000.csproj @@ -5,6 +5,6 @@ false - + \ No newline at end of file diff --git a/ConnectorCSI/DriverCSharp/DriverCSharp.csproj b/ConnectorCSI/DriverCSharp/DriverCSharp.csproj index b2f9fefb85..723e97c68c 100644 --- a/ConnectorCSI/DriverCSharp/DriverCSharp.csproj +++ b/ConnectorCSI/DriverCSharp/DriverCSharp.csproj @@ -8,7 +8,6 @@ false - @@ -23,4 +22,5 @@ + \ No newline at end of file diff --git a/ConnectorCSI/DriverCSharp/PluginCallback.cs b/ConnectorCSI/DriverCSharp/PluginCallback.cs index 69f9ea23b4..671df0a684 100644 --- a/ConnectorCSI/DriverCSharp/PluginCallback.cs +++ b/ConnectorCSI/DriverCSharp/PluginCallback.cs @@ -9,18 +9,12 @@ class PluginCallback : cPluginCallback public int ErrorFlag { - get - { - return m_ErrorFlag; - } + get { return m_ErrorFlag; } } public bool Finished { - get - { - return m_IsFinished; - } + get { return m_IsFinished; } } public void Finish(int iVal) diff --git a/ConnectorCSI/DriverCSharp/Program.cs b/ConnectorCSI/DriverCSharp/Program.cs index ac9144b5c2..2ddae118e7 100644 --- a/ConnectorCSI/DriverCSharp/Program.cs +++ b/ConnectorCSI/DriverCSharp/Program.cs @@ -41,7 +41,7 @@ static int Main(string[] args) return ret; } - // attach to a running program instance + // attach to a running program instance try { // get the active SapObject @@ -72,20 +72,16 @@ static int Main(string[] args) progID = ProgID_SAP2000; mySapObject = myHelper.GetObject(progID); } - catch (Exception ex) - { - } + catch (Exception ex) { } if (mySapObject == null) { try - { + { progID = ProgID_ETABS; mySapObject = myHelper.GetObject(progID); } - catch (Exception ex) - { - } + catch (Exception ex) { } } if (mySapObject == null) { @@ -94,9 +90,7 @@ static int Main(string[] args) progID = ProgID_CSiBridge; mySapObject = myHelper.GetObject(progID); } - catch (Exception ex) - { - } + catch (Exception ex) { } } } } @@ -119,8 +113,10 @@ static int Main(string[] args) // DO NOT return from SpeckleConnectorETABS.cPlugin.Main() until all work is done. p.Main(ref mySapModel, ref cb); - if(cb.Finished == true) - { Environment.Exit(0); } + if (cb.Finished == true) + { + Environment.Exit(0); + } return cb.ErrorFlag; } diff --git a/ConnectorCore/BatchUploader.OperationDriver/BatchUploadOperationDriver.cs b/ConnectorCore/BatchUploader.OperationDriver/BatchUploadOperationDriver.cs new file mode 100644 index 0000000000..d99e133b64 --- /dev/null +++ b/ConnectorCore/BatchUploader.OperationDriver/BatchUploadOperationDriver.cs @@ -0,0 +1,129 @@ +using DesktopUI2; +using DesktopUI2.Models; +using DesktopUI2.Models.Filters; +using DesktopUI2.ViewModels; +using Speckle.BatchUploader.Sdk; +using Speckle.BatchUploader.Sdk.CommunicationModels; +using Speckle.BatchUploader.Sdk.Interfaces; +using Speckle.Core.Api; +using Speckle.Core.Credentials; + +namespace Speckle.BatchUploader.OperationDriver; + +/// +/// Connector implementation of a BatchUploader client +/// +/// +/// Only processes started by the batch uploader will start executing jobs +/// +public sealed class BatchUploadOperationDriver +{ + private readonly BatchUploaderClient _client; + private readonly IApplicationFunctionalityController _applicationController; + private readonly ConnectorBindings _connectorBindings; + private int _jobCounter; + + public BatchUploadOperationDriver( + BatchUploaderClient client, + IApplicationFunctionalityController applicationController, + ConnectorBindings connectorBindings + ) + { + _client = client; + _applicationController = applicationController; + _connectorBindings = connectorBindings; + } + + /// + /// Queries local BatchUploader service for any jobs for this application process + /// and starts executing them. Application will terminate once all jobs are complete. + /// + public async Task ProcessAllJobs() + { + while (await GetNextJobId().ConfigureAwait(false) is { } jobId) + { + // currently using this counter to determine whether this process was started by the batch uploader + // and needs to be closed by the batch uploader. Not the most robust system, I'd imagine. + _jobCounter++; + try + { + await ProcessJob(jobId).ConfigureAwait(false); + await _client.UpdateJobStatus(jobId, JobStatus.Completed).ConfigureAwait(false); + //TODO: Finish endpoint with result details. + Console.WriteLine("Finished"); + } + catch (Exception ex) + { + Console.WriteLine(ex); + //TODO: Finish endpoint with ex details + await _client.UpdateJobStatus(jobId, JobStatus.Failed).ConfigureAwait(false); + throw; + } + } + + if (_jobCounter > 0) + { + Environment.Exit(0); + } + } + + private async Task ProcessJob(Guid jobId) + { + await _client.UpdateJobStatus(jobId, JobStatus.Processing).ConfigureAwait(false); + var jobDescription = await _client.GetJobDescription(jobId).ConfigureAwait(false); + await _applicationController.OpenDocument(jobDescription.FilePath).ConfigureAwait(false); + + var state = new StreamState() + { + Client = new Client(AccountManager.GetDefaultAccount()), //TODO + BranchName = jobDescription.Branch, + StreamId = jobDescription.Stream, + Filter = new AllSelectionFilter + { + Slug = "all", + Name = "Everything", + Icon = "CubeScan", + Description = "Selects all document objects and project information." + }, + }; + ProgressViewModel progress = new(); + AddProgressListener(jobId, progress); + await _connectorBindings.SendStream(state, progress).ConfigureAwait(false); + } + + private void AddProgressListener(Guid jobId, ProgressViewModel viewModel) + { + viewModel.PropertyChanged += (_, p) => + { + if (p.PropertyName is not (nameof(ProgressViewModel.Value) or nameof(ProgressViewModel.Max))) + return; + + Task.Run(() => OnProgressUpdate(jobId, viewModel)); + }; + } + + private async Task OnProgressUpdate(Guid jobId, ProgressViewModel viewModel) + { + //Check for cancel + var status = await _client.GetJobStatus(jobId).ConfigureAwait(false); + if (status is JobStatus.Cancelled or JobStatus.Failed) + viewModel.CancellationTokenSource.Cancel(); + + //Update Progress + JobProgress newProgress = new((long)viewModel.Value, (long)viewModel.Max); + await _client.UpdateJobProgress(jobId, newProgress).ConfigureAwait(false); + } + + private async Task GetNextJobId() + { + try + { + return await _client.GetJob().ConfigureAwait(false); + } + catch (HttpRequestException ex) + { + Console.WriteLine(ex); + return null; + } + } +} diff --git a/ConnectorCore/BatchUploader.OperationDriver/BatchUploader.OperationDriver.csproj b/ConnectorCore/BatchUploader.OperationDriver/BatchUploader.OperationDriver.csproj new file mode 100644 index 0000000000..80c9a82419 --- /dev/null +++ b/ConnectorCore/BatchUploader.OperationDriver/BatchUploader.OperationDriver.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.0 + enable + enable + 10 + Speckle.BatchUploader.OperationDriver + + + + true + + + + + + + + diff --git a/ConnectorCore/BatchUploader.Sdk/BatchUploader.Sdk.csproj b/ConnectorCore/BatchUploader.Sdk/BatchUploader.Sdk.csproj new file mode 100644 index 0000000000..ed37ef1107 --- /dev/null +++ b/ConnectorCore/BatchUploader.Sdk/BatchUploader.Sdk.csproj @@ -0,0 +1,25 @@ + + + + netstandard2.0 + enable + enable + 10 + Speckle.BatchUploader.Sdk + true + Speckle.BatchUploader.Sdk + Speckle.BatchUploader.Sdk + SDK for connector batch uploader + $(PackageTags) revit uploader batch + true + + + + true + + + + + + + diff --git a/ConnectorCore/BatchUploader.Sdk/BatchUploaderClient.cs b/ConnectorCore/BatchUploader.Sdk/BatchUploaderClient.cs new file mode 100644 index 0000000000..8e60e53c96 --- /dev/null +++ b/ConnectorCore/BatchUploader.Sdk/BatchUploaderClient.cs @@ -0,0 +1,110 @@ +using System.Text; +using System.Text.Json; +using Speckle.BatchUploader.Sdk.CommunicationModels; + +[assembly: CLSCompliant(true)] + +namespace Speckle.BatchUploader.Sdk; + +public sealed class BatchUploaderClient : IDisposable +{ + private readonly Uri _serverUri; + private readonly HttpClient _httpClient; + + public BatchUploaderClient(Uri serverUri) + { + _serverUri = serverUri; + + using var p = System.Diagnostics.Process.GetCurrentProcess(); + _httpClient = new HttpClient(); + _httpClient.DefaultRequestHeaders.Add("pid", p.Id.ToString()); + } + + public async Task AddJob( + JobDescription description, + string versionedHostAppName, + CancellationToken cancellationToken = default + ) + { + using var requestMessage = new HttpRequestMessage( + HttpMethod.Post, + _serverUri + $"api/v1/Jobs?&versionedHostAppName={versionedHostAppName}" + ); + requestMessage.Content = SerializeBody(description); + using var response = await _httpClient.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + return await JsonSerializer + .DeserializeAsync(stream, cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + public async Task RegisterProcessor(string versionedHostAppName, CancellationToken cancellationToken = default) + { + using var requestMessage = new HttpRequestMessage( + HttpMethod.Post, + _serverUri + $"api/v1/Jobs/Processors?&versionedHostAppName={versionedHostAppName}" + ); + using var response = await _httpClient.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + } + + public async Task GetJob(CancellationToken cancellationToken = default) + { + using var requestMessage = new HttpRequestMessage(HttpMethod.Get, _serverUri + $"api/v1/Jobs"); + using var response = await _httpClient.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + return await JsonSerializer + .DeserializeAsync(stream, cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + public async Task GetJobDescription(Guid jobId, CancellationToken cancellationToken = default) + { + using var requestMessage = new HttpRequestMessage(HttpMethod.Get, _serverUri + $"api/v1/Jobs/{jobId}/description"); + using var response = await _httpClient.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + return await JsonSerializer + .DeserializeAsync(stream, cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + public async Task GetJobStatus(Guid jobId, CancellationToken cancellationToken = default) + { + using var requestMessage = new HttpRequestMessage(HttpMethod.Get, _serverUri + $"api/v1/Jobs/{jobId}/status"); + using var response = await _httpClient.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + return await JsonSerializer + .DeserializeAsync(stream, cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + public async Task UpdateJobStatus(Guid jobId, JobStatus newStatus, CancellationToken cancellationToken = default) + { + using var requestMessage = new HttpRequestMessage(HttpMethod.Put, _serverUri + $"api/v1/Jobs/{jobId}/status"); + requestMessage.Content = SerializeBody(newStatus); + using var response = await _httpClient.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + } + + public async Task UpdateJobProgress(Guid jobId, JobProgress newStatus, CancellationToken cancellationToken = default) + { + using var requestMessage = new HttpRequestMessage(HttpMethod.Put, _serverUri + $"api/v1/Jobs/{jobId}/progress"); + requestMessage.Content = SerializeBody(newStatus); + using var response = await _httpClient.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + } + + private static StringContent SerializeBody(T value) + { + return new StringContent(JsonSerializer.Serialize(value), Encoding.UTF8, "application/json"); + } + + public void Dispose() + { + _httpClient.Dispose(); + } +} diff --git a/ConnectorCore/BatchUploader.Sdk/CommunicationModels/Models.cs b/ConnectorCore/BatchUploader.Sdk/CommunicationModels/Models.cs new file mode 100644 index 0000000000..23f71e8931 --- /dev/null +++ b/ConnectorCore/BatchUploader.Sdk/CommunicationModels/Models.cs @@ -0,0 +1,59 @@ +using System.Text.Json.Serialization; + +namespace Speckle.BatchUploader.Sdk.CommunicationModels; + +public enum JobStatus +{ + None = default, + + /// Waiting for a processor + Queued, + + /// Job is assigned to a processor + Assigned, + + /// Processor has started processing Job + Processing, + + /// User has canceled Job + Cancelled, + + /// Processor failed to complete Job + Failed, + + /// Processor successfully completed job + Completed, +} + +public readonly struct JobProgress +{ + public long Progress { get; } + public long Max { get; } + + [JsonIgnore] + public double ProgressFraction => (double)Progress / Max; + + [JsonConstructor] + public JobProgress(long progress, long max) + { + Progress = progress; + Max = max; + } +} + +public readonly struct JobDescription +{ + public string FilePath { get; } + public string Stream { get; } + public string Branch { get; } + public string CommitMessage { get; } + + [JsonConstructor] + public JobDescription(string filePath, string stream, string branch, string commitMessage) + { + FilePath = filePath; + Stream = stream; + Branch = branch; + CommitMessage = commitMessage; + } +} diff --git a/ConnectorCore/BatchUploader.Sdk/Interfaces/IApplicationFunctionalityController.cs b/ConnectorCore/BatchUploader.Sdk/Interfaces/IApplicationFunctionalityController.cs new file mode 100644 index 0000000000..d430f397b6 --- /dev/null +++ b/ConnectorCore/BatchUploader.Sdk/Interfaces/IApplicationFunctionalityController.cs @@ -0,0 +1,6 @@ +namespace Speckle.BatchUploader.Sdk.Interfaces; + +public interface IApplicationFunctionalityController +{ + public Task OpenDocument(string path); +} diff --git a/ConnectorDynamo/ConnectorDynamoFunctions/Functions.cs b/ConnectorDynamo/ConnectorDynamoFunctions/Functions.cs index d735ccc9d3..e8070350b2 100644 --- a/ConnectorDynamo/ConnectorDynamoFunctions/Functions.cs +++ b/ConnectorDynamo/ConnectorDynamoFunctions/Functions.cs @@ -23,17 +23,21 @@ namespace Speckle.ConnectorDynamo.Functions [IsVisibleInDynamoLibrary(false)] public static class Functions { - - /// /// Sends data to a Speckle Server by creating a commit on the master branch of a Stream /// /// Data to send /// Transports to send the data to /// Log - public static List Send(Base data, List transports, CancellationToken cancellationToken, - Dictionary branchNames = null, string message = "", - Action> onProgressAction = null, Action onErrorAction = null) + public static List Send( + Base data, + List transports, + CancellationToken cancellationToken, + Dictionary branchNames = null, + string message = "", + Action> onProgressAction = null, + Action onErrorAction = null + ) { var commitWrappers = new List(); var responses = new List(); @@ -54,9 +58,9 @@ public static List Send(Base data, List transports, Cancella message = $"Sent {totalCount} object{plural} from Dynamo"; } - - var objectId = Operations.Send(data, cancellationToken, new List(transports), true, - onProgressAction, onErrorAction).Result; + var objectId = Operations + .Send(data, cancellationToken, new List(transports), true, onProgressAction, onErrorAction) + .Result; if (cancellationToken.IsCancellationRequested) return null; @@ -73,23 +77,30 @@ public static List Send(Base data, List transports, Cancella var client = new Client(serverTransport.Account); try { - var res = client.CommitCreate(cancellationToken, - new CommitCreateInput - { - streamId = serverTransport.StreamId, - branchName = branchName, - objectId = objectId, - message = message, - sourceApplication = Utils.GetAppName(), - parents = new List { serverTransport.StreamId } - }).Result; + var res = client + .CommitCreate( + new CommitCreateInput + { + streamId = serverTransport.StreamId, + branchName = branchName, + objectId = objectId, + message = message, + sourceApplication = Utils.GetAppName(), + parents = new List { serverTransport.StreamId } + }, + cancellationToken + ) + .Result; responses.Add(res); - var wrapper = - new StreamWrapper(serverTransport.StreamId, serverTransport.Account.userInfo.id, serverTransport.BaseUri) - { - CommitId = res - }; + var wrapper = new StreamWrapper( + serverTransport.StreamId, + serverTransport.Account.userInfo.id, + serverTransport.BaseUri + ) + { + CommitId = res + }; commitWrappers.Add(wrapper.ToString()); Analytics.TrackEvent(client.Account, Analytics.Events.Send); } @@ -98,7 +109,6 @@ public static List Send(Base data, List transports, Cancella Utils.HandleApiExeption(ex); return null; } - } return commitWrappers; @@ -119,9 +129,13 @@ public static object SendData(string output) /// Stream to receive from /// [MultiReturn(new[] { "data", "commit" })] - public static Dictionary Receive(StreamWrapper stream, CancellationToken cancellationToken, - Action> onProgressAction = null, Action onErrorAction = null, - Action onTotalChildrenCountKnown = null) + public static Dictionary Receive( + StreamWrapper stream, + CancellationToken cancellationToken, + Action> onProgressAction = null, + Action onErrorAction = null, + Action onTotalChildrenCountKnown = null + ) { var account = stream.GetAccount().Result; // @@ -135,7 +149,7 @@ public static Dictionary Receive(StreamWrapper stream, Cancellat try { - var branch = client.BranchGet(cancellationToken, stream.StreamId, stream.BranchName, 1).Result; + var branch = client.BranchGet(stream.StreamId, stream.BranchName, 1, cancellationToken).Result; if (!branch.commits.items.Any()) { throw new SpeckleException("No commits found."); @@ -152,7 +166,7 @@ public static Dictionary Receive(StreamWrapper stream, Cancellat { try { - commit = client.CommitGet(cancellationToken, stream.StreamId, stream.CommitId).Result; + commit = client.CommitGet(stream.StreamId, stream.CommitId!, cancellationToken).Result; } catch (Exception ex) { @@ -175,15 +189,17 @@ public static Dictionary Receive(StreamWrapper stream, Cancellat var transport = new ServerTransport(account, stream.StreamId); - var @base = Operations.Receive( - commit.referencedObject, - cancellationToken, - remoteTransport: transport, - onProgressAction: onProgressAction, - onErrorAction: ((s, exception) => throw exception), - onTotalChildrenCountKnown: onTotalChildrenCountKnown, - disposeTransports: true - ).Result; + var @base = Operations + .Receive( + commit.referencedObject, + cancellationToken, + remoteTransport: transport, + onProgressAction: onProgressAction, + onErrorAction: ((s, exception) => throw exception), + onTotalChildrenCountKnown: onTotalChildrenCountKnown, + disposeTransports: true + ) + .Result; if (@base == null) { @@ -191,13 +207,17 @@ public static Dictionary Receive(StreamWrapper stream, Cancellat } try { - client.CommitReceived(new CommitReceivedInput - { - streamId = stream.StreamId, - commitId = commit?.id, - message = commit?.message, - sourceApplication = HostApplications.Dynamo.GetVersion(HostAppVersion.vRevit) - }).Wait(); + client + .CommitReceived( + new CommitReceivedInput + { + streamId = stream.StreamId, + commitId = commit?.id, + message = commit?.message, + sourceApplication = HostApplications.Dynamo.GetVersion(HostAppVersion.vRevit) + } + ) + .Wait(); } catch { @@ -212,12 +232,16 @@ public static Dictionary Receive(StreamWrapper stream, Cancellat var data = converter.ConvertRecursivelyToNative(@base); - Analytics.TrackEvent(client.Account, Analytics.Events.Receive, new Dictionary() - { - { "sourceHostApp", HostApplications.GetHostAppFromString(commit.sourceApplication)?.Slug }, - { "sourceHostAppVersion", commit.sourceApplication }, - { "isMultiplayer", commit.authorId != client.Account.userInfo.id } - }); + Analytics.TrackEvent( + client.Account, + Analytics.Events.Receive, + new Dictionary() + { + { "sourceHostApp", HostApplications.GetHostAppFromString(commit.sourceApplication)?.Slug }, + { "sourceHostAppVersion", commit.sourceApplication }, + { "isMultiplayer", commit.authorId != client.Account.userInfo.id } + } + ); return new Dictionary { { "data", data }, { "commit", commit } }; } diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.ReceiveComponent.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.ReceiveComponent.cs index 9f03876eaf..25e24c2a4b 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.ReceiveComponent.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.ReceiveComponent.cs @@ -91,7 +91,7 @@ public override void DocumentContextChanged(GH_Document document, GH_DocumentCon // Get last commit from the branch var b = ApiClient - .BranchGet(BaseWorker.CancellationToken, StreamWrapper.StreamId, StreamWrapper.BranchName ?? "main", 1) + .BranchGet(StreamWrapper.StreamId, StreamWrapper.BranchName ?? "main", 1, BaseWorker.CancellationToken) .Result; // Compare commit id's. If they don't match, notify user or fetch data if in auto mode @@ -619,7 +619,7 @@ CancellationToken CancellationToken case StreamWrapperType.Commit: try { - myCommit = await client.CommitGet(CancellationToken, InputWrapper.StreamId, InputWrapper.CommitId); + myCommit = await client.CommitGet(InputWrapper.StreamId, InputWrapper.CommitId, CancellationToken); return myCommit; } catch (Exception e) diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.ReceiveComponentSync.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.ReceiveComponentSync.cs index 5db3a7d3de..75d81f888f 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.ReceiveComponentSync.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.ReceiveComponentSync.cs @@ -84,7 +84,7 @@ public override void DocumentContextChanged(GH_Document document, GH_DocumentCon // Get last commit from the branch var b = ApiClient - .BranchGet(source.Token, StreamWrapper.StreamId, StreamWrapper.BranchName ?? "main", 1) + .BranchGet(StreamWrapper.StreamId, StreamWrapper.BranchName ?? "main", 1, source.Token) .Result; // Compare commit id's. If they don't match, notify user or fetch data if in auto mode diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.SendComponent.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.SendComponent.cs index fd608e7513..82bd9d0f51 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.SendComponent.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.SendComponent.cs @@ -542,7 +542,7 @@ public override void DoWork(Action ReportProgress, Action Done) if (prevCommit != null) commitCreateInput.parents = new List { prevCommit.CommitId }; - var commitId = await client.CommitCreate(CancellationToken, commitCreateInput); + var commitId = await client.CommitCreate(commitCreateInput, CancellationToken); var wrapper = new StreamWrapper( $"{client.Account.serverInfo.url}/streams/{((ServerTransport)transport).StreamId}/commits/{commitId}?u={client.Account.userInfo.id}" diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.SendComponentSync.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.SendComponentSync.cs index 4183c3a72a..0a847c1fcf 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.SendComponentSync.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.SendComponentSync.cs @@ -425,7 +425,7 @@ public override void SolveInstanceWithLogContext(IGH_DataAccess DA) if (prevCommit != null) commitCreateInput.parents = new List { prevCommit.CommitId }; - var commitId = await client.CommitCreate(source.Token, commitCreateInput); + var commitId = await client.CommitCreate(commitCreateInput, source.Token); var wrapper = new StreamWrapper( $"{client.Account.serverInfo.url}/streams/{((ServerTransport)transport).StreamId}/commits/{commitId}?u={client.Account.userInfo.id}" diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.VariableInputSendComponent.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.VariableInputSendComponent.cs index ab2d0bc5a3..8dd8cc025e 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.VariableInputSendComponent.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Deprecated/Operations.VariableInputSendComponent.cs @@ -616,7 +616,7 @@ public override void DoWork(Action ReportProgress, Action Done) if (prevCommit != null) commitCreateInput.parents = new List { prevCommit.CommitId }; - var commitId = await client.CommitCreate(CancellationToken, commitCreateInput); + var commitId = await client.CommitCreate(commitCreateInput, CancellationToken); var wrapper = new StreamWrapper( $"{client.Account.serverInfo.url}/streams/{((ServerTransport)transport).StreamId}/commits/{commitId}?u={client.Account.userInfo.id}" diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Operations.SyncReceiveComponent.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Operations.SyncReceiveComponent.cs index 4c3ab5dd19..67fecfcb80 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Operations.SyncReceiveComponent.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Operations.SyncReceiveComponent.cs @@ -58,7 +58,7 @@ public override void DocumentContextChanged(GH_Document document, GH_DocumentCon // Get last commit from the branch var b = ApiClient - .BranchGet(CancelToken, StreamWrapper.StreamId, StreamWrapper.BranchName ?? "main", 1) + .BranchGet(StreamWrapper.StreamId, StreamWrapper.BranchName ?? "main", 1, CancelToken) .Result; // Compare commit id's. If they don't match, notify user or fetch data if in auto mode diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Operations.SyncSendComponent.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Operations.SyncSendComponent.cs index 46a49f005e..f69b181ee4 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Operations.SyncSendComponent.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Operations.SyncSendComponent.cs @@ -290,7 +290,7 @@ public override void SolveInstanceWithLogContext(IGH_DataAccess DA) if (prevCommit != null) commitCreateInput.parents = new List { prevCommit.CommitId }; - var commitId = await client.CommitCreate(CancelToken, commitCreateInput); + var commitId = await client.CommitCreate(commitCreateInput, CancelToken); var wrapper = new StreamWrapper( $"{client.Account.serverInfo.url}/streams/{((ServerTransport)transport).StreamId}/commits/{commitId}?u={client.Account.userInfo.id}" diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Operations.VariableInputReceiveComponent.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Operations.VariableInputReceiveComponent.cs index de83307c22..df532bda5a 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Operations.VariableInputReceiveComponent.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Operations.VariableInputReceiveComponent.cs @@ -128,7 +128,7 @@ public override void DocumentContextChanged(GH_Document document, GH_DocumentCon // Get last commit from the branch var b = ApiClient - .BranchGet(BaseWorker.CancellationToken, StreamWrapper.StreamId, StreamWrapper.BranchName ?? "main", 1) + .BranchGet(StreamWrapper.StreamId, StreamWrapper.BranchName ?? "main", 1, BaseWorker.CancellationToken) .Result; // Compare commit id's. If they don't match, notify user or fetch data if in auto mode @@ -740,7 +740,7 @@ CancellationToken CancellationToken case StreamWrapperType.Commit: try { - myCommit = await client.CommitGet(CancellationToken, InputWrapper.StreamId, InputWrapper.CommitId); + myCommit = await client.CommitGet(InputWrapper.StreamId, InputWrapper.CommitId, CancellationToken); if (myCommit == null) OnFail( GH_RuntimeMessageLevel.Warning, @@ -758,7 +758,7 @@ CancellationToken CancellationToken return myCommit; case StreamWrapperType.Stream: case StreamWrapperType.Undefined: - var mb = await client.BranchGet(CancellationToken, InputWrapper.StreamId, "main", 1); + var mb = await client.BranchGet(InputWrapper.StreamId, "main", 1, CancellationToken); if (mb.commits.totalCount == 0) // TODO: Warn that we're not pulling from the main branch OnFail( @@ -768,7 +768,7 @@ CancellationToken CancellationToken else return mb.commits.items[0]; - var cms = await client.StreamGetCommits(CancellationToken, InputWrapper.StreamId, 1); + var cms = await client.StreamGetCommits(InputWrapper.StreamId, 1, CancellationToken); if (cms.Count == 0) { OnFail(GH_RuntimeMessageLevel.Warning, "This stream has no commits."); @@ -777,7 +777,7 @@ CancellationToken CancellationToken return cms[0]; case StreamWrapperType.Branch: - var br = await client.BranchGet(CancellationToken, InputWrapper.StreamId, InputWrapper.BranchName, 1); + var br = await client.BranchGet(InputWrapper.StreamId, InputWrapper.BranchName, 1, CancellationToken); if (br == null) { OnFail( diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Operations.VariableInputSendComponent.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Operations.VariableInputSendComponent.cs index 3d9383fab3..95c09ce605 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Operations.VariableInputSendComponent.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/Ops/Operations.VariableInputSendComponent.cs @@ -652,7 +652,7 @@ public override void DoWork(Action ReportProgress, Action Done) if (prevCommit != null) commitCreateInput.parents = new List { prevCommit.CommitId }; - var commitId = await client.CommitCreate(CancellationToken, commitCreateInput); + var commitId = await client.CommitCreate(commitCreateInput, CancellationToken); var wrapper = new StreamWrapper( $"{client.Account.serverInfo.url}/streams/{((ServerTransport)transport).StreamId}/commits/{commitId}?u={client.Account.userInfo.id}" diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/Streams/StreamCreateComponentV2.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/Streams/StreamCreateComponentV2.cs index 2c4a3dfba4..53b442b223 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/Streams/StreamCreateComponentV2.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/Streams/StreamCreateComponentV2.cs @@ -108,7 +108,7 @@ public Task CreateStream(Account account) var client = new Client(account); - var streamId = client.StreamCreate(CancelToken, new StreamCreateInput { isPublic = false }).Result; + var streamId = client.StreamCreate(new StreamCreateInput { isPublic = false }, CancelToken).Result; var sw = new StreamWrapper(streamId, account.userInfo.id, account.serverInfo.url); sw.SetAccount(account); diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/Streams/StreamListComponentV2.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/Streams/StreamListComponentV2.cs index 4be4639e5c..fa7ab5dcea 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/Streams/StreamListComponentV2.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/Streams/StreamListComponentV2.cs @@ -90,7 +90,7 @@ private Task> ListStreams(Account account, int limit) { var client = new Client(account); var res = client - .StreamsGet(CancelToken, limit) + .StreamsGet(limit, CancelToken) .Result.Select(stream => new StreamWrapper(stream.id, account.userInfo.id, account.serverInfo.url)) .ToList(); return Task.FromResult(res); diff --git a/ConnectorRevit/ConnectorRevit.sln b/ConnectorRevit/ConnectorRevit.sln index 2edbe4b872..e7e5015d6b 100644 --- a/ConnectorRevit/ConnectorRevit.sln +++ b/ConnectorRevit/ConnectorRevit.sln @@ -96,6 +96,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RevitSharedResources2021", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RevitSharedResources2020", "RevitSharedResources2020\RevitSharedResources2020.csproj", "{1C053AF2-BC74-4538-B250-62C6B54380EF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BatchUploader.Sdk", "..\ConnectorCore\BatchUploader.Sdk\BatchUploader.Sdk.csproj", "{2018D167-8A0E-495B-B7F9-F952530C1B87}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BatchUploader.OperationDriver", "..\ConnectorCore\BatchUploader.OperationDriver\BatchUploader.OperationDriver.csproj", "{1D6D5E82-0CE2-4291-9871-A94EDFD546BE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -375,6 +379,22 @@ Global {1C053AF2-BC74-4538-B250-62C6B54380EF}.Release|Any CPU.Build.0 = Release|Any CPU {1C053AF2-BC74-4538-B250-62C6B54380EF}.Release|x64.ActiveCfg = Release|Any CPU {1C053AF2-BC74-4538-B250-62C6B54380EF}.Release|x64.Build.0 = Release|Any CPU + {2018D167-8A0E-495B-B7F9-F952530C1B87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2018D167-8A0E-495B-B7F9-F952530C1B87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2018D167-8A0E-495B-B7F9-F952530C1B87}.Debug|x64.ActiveCfg = Debug|Any CPU + {2018D167-8A0E-495B-B7F9-F952530C1B87}.Debug|x64.Build.0 = Debug|Any CPU + {2018D167-8A0E-495B-B7F9-F952530C1B87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2018D167-8A0E-495B-B7F9-F952530C1B87}.Release|Any CPU.Build.0 = Release|Any CPU + {2018D167-8A0E-495B-B7F9-F952530C1B87}.Release|x64.ActiveCfg = Release|Any CPU + {2018D167-8A0E-495B-B7F9-F952530C1B87}.Release|x64.Build.0 = Release|Any CPU + {1D6D5E82-0CE2-4291-9871-A94EDFD546BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D6D5E82-0CE2-4291-9871-A94EDFD546BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D6D5E82-0CE2-4291-9871-A94EDFD546BE}.Debug|x64.ActiveCfg = Debug|Any CPU + {1D6D5E82-0CE2-4291-9871-A94EDFD546BE}.Debug|x64.Build.0 = Debug|Any CPU + {1D6D5E82-0CE2-4291-9871-A94EDFD546BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D6D5E82-0CE2-4291-9871-A94EDFD546BE}.Release|Any CPU.Build.0 = Release|Any CPU + {1D6D5E82-0CE2-4291-9871-A94EDFD546BE}.Release|x64.ActiveCfg = Release|Any CPU + {1D6D5E82-0CE2-4291-9871-A94EDFD546BE}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -423,6 +443,8 @@ Global {57C2D297-25D4-472B-9034-A106A6E39C24} = {621DFAC1-C94B-4D7B-B8B0-7934E816CC98} {11D1995A-6FEF-4843-A1D7-A5F6B3592891} = {621DFAC1-C94B-4D7B-B8B0-7934E816CC98} {1C053AF2-BC74-4538-B250-62C6B54380EF} = {621DFAC1-C94B-4D7B-B8B0-7934E816CC98} + {2018D167-8A0E-495B-B7F9-F952530C1B87} = {39F07EF9-5867-4062-886D-2417C86305C2} + {1D6D5E82-0CE2-4291-9871-A94EDFD546BE} = {39F07EF9-5867-4062-886D-2417C86305C2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {650BE642-C7CC-4FF5-9EE5-8C57BF553143} diff --git a/ConnectorRevit/ConnectorRevit.slnf b/ConnectorRevit/ConnectorRevit.slnf index 18913dbce0..1f3820ecd5 100644 --- a/ConnectorRevit/ConnectorRevit.slnf +++ b/ConnectorRevit/ConnectorRevit.slnf @@ -2,6 +2,10 @@ "solution": { "path": "..\\All.sln", "projects": [ + "..\\batch-uploader\\ClientSDK\\BatchUploader.ClientSdk\\BatchUploader.ClientSdk.csproj", + "BatchUploadOperationDriver\\BatchUploadOperationDriver.csproj", + "ConnectorCore\\BatchUploader.OperationDriver\\BatchUploader.OperationDriver.csproj", + "ConnectorCore\\BatchUploader.Sdk\\BatchUploader.Sdk.csproj", "ConnectorRevit\\ConnectorRevit2020\\ConnectorRevit2020.csproj", "ConnectorRevit\\ConnectorRevit2021\\ConnectorRevit2021.csproj", "ConnectorRevit\\ConnectorRevit2022\\ConnectorRevit2022.csproj", diff --git a/ConnectorRevit/ConnectorRevit/ConnectorRevit.projitems b/ConnectorRevit/ConnectorRevit/ConnectorRevit.projitems index 601a93eddd..c774213abd 100644 --- a/ConnectorRevit/ConnectorRevit/ConnectorRevit.projitems +++ b/ConnectorRevit/ConnectorRevit/ConnectorRevit.projitems @@ -30,6 +30,7 @@ + diff --git a/ConnectorRevit/ConnectorRevit/Entry/App.cs b/ConnectorRevit/ConnectorRevit/Entry/App.cs index 20ceb40060..31354f2e05 100644 --- a/ConnectorRevit/ConnectorRevit/Entry/App.cs +++ b/ConnectorRevit/ConnectorRevit/Entry/App.cs @@ -6,7 +6,10 @@ using System.Windows.Media.Imaging; using Autodesk.Revit.ApplicationServices; using Autodesk.Revit.UI; +using ConnectorRevit; using RevitSharedResources.Models; +using Speckle.BatchUploader.Sdk; +using Speckle.BatchUploader.OperationDriver; using Speckle.ConnectorRevit.UI; using Speckle.Core.Kits; using Speckle.Core.Logging; @@ -138,6 +141,12 @@ Autodesk.Revit.DB.Events.ApplicationInitializedEventArgs e SpeckleRevitCommand.Bindings = bindings; SchedulerCommand.Bindings = bindings; + BatchUploaderClient client = new(new Uri("http://localhost:5001")); + RevitApplicationController revitAppController = new(AppInstance); + BatchUploadOperationDriver batchUploadOperationDriver = new(client, revitAppController, bindings); + + batchUploadOperationDriver.ProcessAllJobs(); + //This is also called in DUI, adding it here to know how often the connector is loaded and used Analytics.TrackEvent(Analytics.Events.Registered, null, false); diff --git a/ConnectorRevit/ConnectorRevit/RevitApplicationFunctionalityController.cs b/ConnectorRevit/ConnectorRevit/RevitApplicationFunctionalityController.cs new file mode 100644 index 0000000000..d235ca5742 --- /dev/null +++ b/ConnectorRevit/ConnectorRevit/RevitApplicationFunctionalityController.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using Autodesk.Revit.UI; +using RevitSharedResources.Models; +using Speckle.BatchUploader.Sdk.Interfaces; + +namespace ConnectorRevit +{ + internal class RevitApplicationController : IApplicationFunctionalityController + { + private readonly UIApplication application; + + public RevitApplicationController(UIApplication application) + { + this.application = application; + } + + public async Task OpenDocument(string path) + { + await APIContext + .Run(() => + { + application.OpenAndActivateDocument(path); + }) + .ConfigureAwait(false); + } + } +} diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Receive.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Receive.cs index 0654ae5ada..76d5d7b5e5 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Receive.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Receive.cs @@ -91,7 +91,10 @@ await ConnectorHelpers.TryCommitReceived( CurrentDoc.Document ); await elementTypeMapper - .Map(state.Settings.FirstOrDefault(x => x.Slug == "receive-mappings"), state.Settings.FirstOrDefault(x => x.Slug == DsFallbackSlug)) + .Map( + state.Settings.FirstOrDefault(x => x.Slug == "receive-mappings"), + state.Settings.FirstOrDefault(x => x.Slug == DsFallbackSlug) + ) .ConfigureAwait(false); } catch (Exception ex) @@ -247,10 +250,6 @@ void ConvertNestedElements(Base @base, ApplicationObject appObj, bool receiveDir } } - using var _d0 = LogContext.PushProperty("converterName", converter.Name); - using var _d1 = LogContext.PushProperty("converterAuthor", converter.Author); - using var _d2 = LogContext.PushProperty("conversionDirection", nameof(ISpeckleConverter.ConvertToNative)); - var convertedObjectsCache = new ConvertedObjectsCache(); converter.SetContextDocument(convertedObjectsCache); @@ -280,6 +279,11 @@ void ConvertNestedElements(Base @base, ApplicationObject appObj, bool receiveDir break; } + using var d0 = LogContext.PushProperty("converterName", converter.Name); + using var d1 = LogContext.PushProperty("converterAuthor", converter.Author); + using var d2 = LogContext.PushProperty("conversionDirection", nameof(ISpeckleConverter.ConvertToNative)); + using var d4 = LogContext.PushProperty("converterReceiveMode", converter.ReceiveMode); + // convert var index = -1; while (++index < Preview.Count) diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Send.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Send.cs index b2bee23f29..1c1871f5cf 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Send.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Send.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -32,6 +33,7 @@ public partial class ConnectorBindingsRevit /// the Server and the local DB, and creates a commit with the objects. /// /// StreamState passed by the UI + [SuppressMessage("Design", "CA1031:Do not catch general exception types")] public override async Task SendStream(StreamState state, ProgressViewModel progress) { using var ctx = RevitConverterState.Push(); @@ -94,9 +96,10 @@ public override async Task SendStream(StreamState state, ProgressViewMod await APIContext .Run(() => { - using var _d0 = LogContext.PushProperty("converterName", converter.Name); - using var _d1 = LogContext.PushProperty("converterAuthor", converter.Author); - using var _d2 = LogContext.PushProperty("conversionDirection", nameof(ISpeckleConverter.ConvertToSpeckle)); + using var d0 = LogContext.PushProperty("converterName", converter.Name); + using var d1 = LogContext.PushProperty("converterAuthor", converter.Author); + using var d2 = LogContext.PushProperty("conversionDirection", nameof(ISpeckleConverter.ConvertToSpeckle)); + using var d3 = LogContext.PushProperty("converterSettings", settings); foreach (var revitElement in selectedObjects) { @@ -114,7 +117,7 @@ out ApplicationObject reportObj progress.Report.Log(reportObj); //Add context to logger - using var _d3 = LogContext.PushProperty("elementType", revitElement.GetType()); + using var _d3 = LogContext.PushProperty("fromType", revitElement.GetType()); using var _d4 = LogContext.PushProperty("elementCategory", revitElement.Category?.Name); try @@ -140,14 +143,12 @@ out ApplicationObject reportObj commitObjectBuilder.IncludeObject(result, revitElement); convertedCount++; } - catch (ConversionSkippedException ex) - { - reportObj.Update(status: ApplicationObject.State.Skipped, logItem: ex.Message); - } catch (Exception ex) { - SpeckleLog.Logger.Error(ex, "Object failed during conversion"); - reportObj.Update(status: ApplicationObject.State.Failed, logItem: $"{ex.Message}"); + ConnectorHelpers.LogConversionException(ex); + + var failureStatus = ConnectorHelpers.GetAppObjectFailureState(ex); + reportObj.Update(status: failureStatus, logItem: ex.Message); } conversionProgressDict["Conversion"]++; diff --git a/ConnectorRevit/ConnectorRevit2020/ConnectorRevit2020.csproj b/ConnectorRevit/ConnectorRevit2020/ConnectorRevit2020.csproj index df22fe8fb7..f9cf817334 100644 --- a/ConnectorRevit/ConnectorRevit2020/ConnectorRevit2020.csproj +++ b/ConnectorRevit/ConnectorRevit2020/ConnectorRevit2020.csproj @@ -28,6 +28,7 @@ + diff --git a/ConnectorRevit/ConnectorRevit2021/ConnectorRevit2021.csproj b/ConnectorRevit/ConnectorRevit2021/ConnectorRevit2021.csproj index 3fa975cd1c..945c43d1b8 100644 --- a/ConnectorRevit/ConnectorRevit2021/ConnectorRevit2021.csproj +++ b/ConnectorRevit/ConnectorRevit2021/ConnectorRevit2021.csproj @@ -30,6 +30,7 @@ + diff --git a/ConnectorRevit/ConnectorRevit2022/ConnectorRevit2022.csproj b/ConnectorRevit/ConnectorRevit2022/ConnectorRevit2022.csproj index dcedeef4cb..35354b8542 100644 --- a/ConnectorRevit/ConnectorRevit2022/ConnectorRevit2022.csproj +++ b/ConnectorRevit/ConnectorRevit2022/ConnectorRevit2022.csproj @@ -26,6 +26,7 @@ + diff --git a/ConnectorRevit/ConnectorRevit2023/ConnectorRevit2023.csproj b/ConnectorRevit/ConnectorRevit2023/ConnectorRevit2023.csproj index a390f2b62a..09f36605b4 100644 --- a/ConnectorRevit/ConnectorRevit2023/ConnectorRevit2023.csproj +++ b/ConnectorRevit/ConnectorRevit2023/ConnectorRevit2023.csproj @@ -26,6 +26,7 @@ + diff --git a/ConnectorRevit/ConnectorRevit2024/ConnectorRevit2024.csproj b/ConnectorRevit/ConnectorRevit2024/ConnectorRevit2024.csproj index d22bcd1a77..706f5e5ce8 100644 --- a/ConnectorRevit/ConnectorRevit2024/ConnectorRevit2024.csproj +++ b/ConnectorRevit/ConnectorRevit2024/ConnectorRevit2024.csproj @@ -26,6 +26,7 @@ + diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Previews.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Previews.cs index fda4f2f307..c574d8ad1e 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Previews.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Previews.cs @@ -89,6 +89,7 @@ private static bool IsPreviewIgnore(Base @object) return @object.speckle_type.Contains("Instance") || @object.speckle_type.Contains("View") || @object.speckle_type.Contains("Level") + || @object.speckle_type.Contains("GridLine") || @object.speckle_type.Contains("Collection"); } diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs index bf808fad9a..977307967f 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs @@ -480,12 +480,16 @@ private void BakeObject( var attributes = new ObjectAttributes(); // handle display style - if (obj[@"displayStyle"] is Base display) + Base display = obj["displayStyle"] as Base ?? obj["@displayStyle"] as Base; + Base render = obj["renderMaterial"] as Base ?? obj["@renderMaterial"] as Base; + if (display != null) { if (converter.ConvertToNative(display) is ObjectAttributes displayAttribute) + { attributes = displayAttribute; + } } - else if (obj[@"renderMaterial"] is Base renderMaterial) + else if (render != null) { attributes.ColorSource = ObjectColorSource.ColorFromMaterial; } @@ -527,23 +531,28 @@ private void BakeObject( } if (parent != null) + { parent.Update(id.ToString()); + } else + { appObj.Update(id.ToString()); + } bakedCount++; // handle render material - if (obj[@"renderMaterial"] is Base render) + if (render != null) { - var convertedMaterial = converter.ConvertToNative(render); //Maybe wrap in try catch in case no conversion exists? - if (convertedMaterial is RenderMaterial rm) + var convertedMaterial = converter.ConvertToNative(render) as RenderMaterial; //Maybe wrap in try catch in case no conversion exists? + if (convertedMaterial != null) { - var rhinoObject = Doc.Objects.FindId(id); - rhinoObject.RenderMaterial = rm; + RhinoObject rhinoObject = Doc.Objects.FindId(id); + rhinoObject.RenderMaterial = convertedMaterial; rhinoObject.CommitChanges(); } } + break; case RhinoObject o: // this was prbly a block instance, baked during conversion @@ -596,7 +605,7 @@ private void SetUserInfo(Base obj, ObjectAttributes attributes, ApplicationObjec if (obj[UserDictionary] is Base userDictionary) ParseDictionaryToArchivable(attributes.UserDictionary, userDictionary); - var name = obj["name"] as string; + var name = obj["name"] as string ?? obj["label"] as string; // gridlines have a "label" prop instead of name? if (name != null) attributes.Name = name; } diff --git a/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructure.Send.cs b/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructure.Send.cs index 43526969a0..52ad61f17a 100644 --- a/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructure.Send.cs +++ b/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructure.Send.cs @@ -10,27 +10,28 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using Serilog.Context; using Tekla.Structures.Model; using SCT = Speckle.Core.Transports; - - namespace Speckle.ConnectorTeklaStructures.UI { public partial class ConnectorBindingsTeklaStructures : ConnectorBindings - { #region sending private List CurrentSettings { get; set; } public override bool CanPreviewSend => false; + public override void PreviewSend(StreamState state, ProgressViewModel progress) { return; } + [SuppressMessage("Design", "CA1031:Do not catch general exception types")] public override async System.Threading.Tasks.Task SendStream(StreamState state, ProgressViewModel progress) { var kit = KitManager.GetDefaultKit(); @@ -44,6 +45,11 @@ public override async System.Threading.Tasks.Task SendStream(StreamState settings.Add(setting.Slug, setting.Selection); converter.SetConverterSettings(settings); + using var d0 = LogContext.PushProperty("converterName", converter.Name); + using var d1 = LogContext.PushProperty("converterAuthor", converter.Author); + using var d2 = LogContext.PushProperty("conversionDirection", nameof(ISpeckleConverter.ConvertToSpeckle)); + using var d3 = LogContext.PushProperty("converterSettings", settings); + var commitObj = new Base(); int objCount = 0; @@ -60,7 +66,8 @@ public override async System.Threading.Tasks.Task SendStream(StreamState if (totalObjectCount == 0) { throw new InvalidOperationException( - "Zero objects selected; send stopped. Please select some objects, or check that your filter can actually select something."); + "Zero objects selected; send stopped. Please select some objects, or check that your filter can actually select something." + ); } var conversionProgressDict = new ConcurrentDictionary(); @@ -68,10 +75,6 @@ public override async System.Threading.Tasks.Task SendStream(StreamState conversionProgressDict["Conversion"] = 0; progress.Update(conversionProgressDict); - - - - foreach (ModelObject obj in selectedObjects) { if (progress.CancellationToken.IsCancellationRequested) @@ -82,7 +85,6 @@ public override async System.Threading.Tasks.Task SendStream(StreamState Base converted = null; string containerName = string.Empty; - //var selectedObjectType = ConnectorTeklaStructuresUtils.ObjectIDsTypesAndNames // .Where(pair => pair.Key == applicationId) // .Select(pair => pair.Value.Item1).FirstOrDefault(); @@ -97,30 +99,32 @@ public override async System.Threading.Tasks.Task SendStream(StreamState // .Where(pair => pair.Key == applicationId) // .Select(pair => pair.Value).FirstOrDefault(); + using var d4 = LogContext.PushProperty("fromType", obj.GetType()); + try { converted = converter.ConvertToSpeckle(obj); + if (converted == null) + throw new ConversionException("Conversion returned null"); } catch (Exception ex) { - //TODO: log - } - - if (converted == null) - { - var exception = new Exception($"Failed to convert object ${obj.Identifier.GUID} of type ${obj.GetType()}."); - progress.Report.LogConversionError(exception); - continue; + ConnectorHelpers.LogConversionException(ex); + progress.Report.LogConversionError( + new ConversionException( + $"Failed to convert object ${obj.Identifier.GUID} of type ${obj.GetType()} {ex.Message}", + ex + ) + ); } - if (converted != null) { if (commitObj["@Base"] == null) { commitObj["@Base"] = new List(); } - ((List)commitObj["@Base"]).Add(converted); + ((List)commitObj["@Base"]).Add(converted); } objCount++; @@ -143,14 +147,14 @@ public override async System.Threading.Tasks.Task SendStream(StreamState var transports = new List() { new SCT.ServerTransport(client.Account, streamId) }; progress.Max = totalObjectCount; var objectId = await Operations.Send( - @object: commitObj, - cancellationToken: progress.CancellationToken, - transports: transports, - onProgressAction: dict => progress.Update(dict), - onErrorAction: ConnectorHelpers.DefaultSendErrorHandler, - disposeTransports: true - ); - + @object: commitObj, + cancellationToken: progress.CancellationToken, + transports: transports, + onProgressAction: dict => progress.Update(dict), + onErrorAction: ConnectorHelpers.DefaultSendErrorHandler, + disposeTransports: true + ); + progress.CancellationToken.ThrowIfCancellationRequested(); var actualCommit = new CommitCreateInput @@ -158,11 +162,15 @@ public override async System.Threading.Tasks.Task SendStream(StreamState streamId = streamId, objectId = objectId, branchName = state.BranchName, - message = state.CommitMessage != null ? state.CommitMessage : $"Pushed {objCount} elements from TeklaStructures.", + message = + state.CommitMessage != null ? state.CommitMessage : $"Pushed {objCount} elements from TeklaStructures.", sourceApplication = ConnectorTeklaStructuresUtils.TeklaStructuresAppName }; - if (state.PreviousCommitId != null) { actualCommit.parents = new List() { state.PreviousCommitId }; } + if (state.PreviousCommitId != null) + { + actualCommit.parents = new List() { state.PreviousCommitId }; + } var commitId = await ConnectorHelpers.CreateCommit(client, actualCommit, progress.CancellationToken); return commitId; diff --git a/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructures.Receive.cs b/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructures.Receive.cs index fa20e4d68e..c363f3271d 100644 --- a/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructures.Receive.cs +++ b/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/UI/ConnectorBindingsTeklaStructures.Receive.cs @@ -1,28 +1,26 @@ using DesktopUI2; using DesktopUI2.Models; -using DesktopUI2.Models.Settings; using DesktopUI2.ViewModels; using Speckle.ConnectorTeklaStructures.Util; using Speckle.Core.Api; using Speckle.Core.Kits; using Speckle.Core.Models; -using Speckle.Core.Transports; using System; -using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; -using Serilog; +using Serilog.Context; using Speckle.Core.Models.GraphTraversal; namespace Speckle.ConnectorTeklaStructures.UI { public partial class ConnectorBindingsTeklaStructures : ConnectorBindings - { #region receiving public override bool CanPreviewReceive => false; + public override async Task PreviewReceive(StreamState state, ProgressViewModel progress) { return null; @@ -48,8 +46,13 @@ public override async Task ReceiveStream(StreamState state, Progres Commit myCommit = await ConnectorHelpers.GetCommitFromState(state, progress.CancellationToken); state.LastCommit = myCommit; Base commitObject = await ConnectorHelpers.ReceiveCommit(myCommit, state, progress); - await ConnectorHelpers.TryCommitReceived(state, myCommit, ConnectorTeklaStructuresUtils.TeklaStructuresAppName, progress.CancellationToken); - + await ConnectorHelpers.TryCommitReceived( + state, + myCommit, + ConnectorTeklaStructuresUtils.TeklaStructuresAppName, + progress.CancellationToken + ); + var conversionProgressDict = new ConcurrentDictionary(); conversionProgressDict["Conversion"] = 1; //Execute.PostToUIThread(() => state.Progress.Maximum = state.SelectedObjectIds.Count()); @@ -60,13 +63,18 @@ public override async Task ReceiveStream(StreamState state, Progres progress.Update(conversionProgressDict); }; + using var d0 = LogContext.PushProperty("converterName", converter.Name); + using var d1 = LogContext.PushProperty("converterAuthor", converter.Author); + using var d2 = LogContext.PushProperty("conversionDirection", nameof(ISpeckleConverter.ConvertToNative)); + using var d3 = LogContext.PushProperty("conversionSettings", settings); + using var d4 = LogContext.PushProperty("converterReceiveMode", converter.ReceiveMode); + foreach (var commitObj in FlattenCommitObject(commitObject, converter)) { - BakeObject(commitObj, state, converter); + BakeObject(commitObj, converter); updateProgressAction?.Invoke(); } - Model.CommitChanges(); progress.Report.Merge(converter.Report); return state; @@ -78,17 +86,21 @@ public override async Task ReceiveStream(StreamState state, Progres /// /// /// - private void BakeObject(Base obj, StreamState state, ISpeckleConverter converter) + [SuppressMessage("Design", "CA1031:Do not catch general exception types")] + private void BakeObject(Base obj, ISpeckleConverter converter) { + LogContext.PushProperty("fromType", obj.GetType()); try { converter.ConvertToNative(obj); } - catch (Exception e) + catch (Exception ex) { - var exception = new Exception($"Failed to convert object {obj.id} of type {obj.speckle_type}\n with error\n{e}"); + var exception = new ConversionException( + $"Failed to convert object {obj.id} of type {obj.speckle_type} with error\n{ex}" + ); converter.Report.LogOperationError(exception); - return; + ConnectorHelpers.LogConversionException(ex); } } @@ -98,11 +110,12 @@ private void BakeObject(Base obj, StreamState state, ISpeckleConverter converter /// The root object to traverse /// The converter instance, used to define what objects are convertable /// A flattened list of objects to be converted ToNative - private IEnumerable FlattenCommitObject(Base obj, ISpeckleConverter converter) + private static IEnumerable FlattenCommitObject(Base obj, ISpeckleConverter converter) { var traverseFunction = DefaultTraversal.CreateTraverseFunc(converter); - - return traverseFunction.Traverse(obj) + + return traverseFunction + .Traverse(obj) .Select(tc => tc.current) .Where(b => b != null) .Where(converter.CanConvertToNative) diff --git a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.ActivityOperations.cs b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.ActivityOperations.cs index 30e6e7ead3..17feb41ec4 100644 --- a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.ActivityOperations.cs +++ b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.ActivityOperations.cs @@ -13,45 +13,22 @@ public partial class Client /// /// Gets the activity of a stream /// - /// Id of the stream to get the activity from + /// Id of the stream to get the activity from /// Only show activity after this DateTime /// Only show activity before this DateTime /// Time to filter the activity with /// Time to filter the activity with /// Max number of activity items to get - /// - public Task> StreamGetActivity( - string streamId, - DateTime? after = null, - DateTime? before = null, - DateTime? cursor = null, - string actionType = "", - int limit = 10 - ) - { - return StreamGetActivity(CancellationToken.None, streamId, after, before, cursor, actionType, limit); - } - - /// - /// Gets the activity of a stream - /// /// - /// Id of the stream to get the activity from - /// Only show activity after this DateTime - /// Only show activity before this DateTime - /// Time to filter the activity with - /// Time to filter the activity with - /// Max number of commits to get /// - /// public async Task> StreamGetActivity( - CancellationToken cancellationToken, string id, DateTime? after = null, DateTime? before = null, DateTime? cursor = null, string actionType = "", - int limit = 25 + int limit = 25, + CancellationToken cancellationToken = default ) { var request = new GraphQLRequest diff --git a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.BranchOperations.cs b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.BranchOperations.cs index 7e4c8e0b13..c3cadabecc 100644 --- a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.BranchOperations.cs +++ b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.BranchOperations.cs @@ -1,6 +1,5 @@ #nullable enable -using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -22,11 +21,11 @@ public async Task> StreamGetBranchesWithLimitRetry(string streamId, List? branches = null; try { - branches = await StreamGetBranches(streamId, 500, commitsLimit).ConfigureAwait(true); + branches = await StreamGetBranches(streamId, ServerLimits.BRANCH_GET_LIMIT, commitsLimit).ConfigureAwait(true); } catch (SpeckleGraphQLException) { - branches = await StreamGetBranches(streamId, 100, commitsLimit).ConfigureAwait(true); + branches = await StreamGetBranches(streamId, ServerLimits.OLD_BRANCH_GET_LIMIT, commitsLimit).ConfigureAwait(true); } return branches; @@ -38,26 +37,13 @@ public async Task> StreamGetBranchesWithLimitRetry(string streamId, /// Id of the stream to get the branches from /// Max number of branches to retrieve /// Max number of commits to retrieve - /// - public Task> StreamGetBranches(string streamId, int branchesLimit = 10, int commitsLimit = 10) - { - return StreamGetBranches(CancellationToken.None, streamId, branchesLimit, commitsLimit); - } - - /// - /// Get branches from a given stream - /// /// - /// Id of the stream to get the branches from - /// Max number of branches to retrieve - /// Max number of commits to retrieve /// - /// public async Task> StreamGetBranches( - CancellationToken cancellationToken, string streamId, int branchesLimit = 10, - int commitsLimit = 10 + int commitsLimit = 10, + CancellationToken cancellationToken = default ) { var request = new GraphQLRequest @@ -99,18 +85,9 @@ public async Task> StreamGetBranches( /// Creates a branch on a stream. /// /// - /// The stream's id. - public Task BranchCreate(BranchCreateInput branchInput) - { - return BranchCreate(CancellationToken.None, branchInput); - } - - /// - /// Creates a branch on a stream. - /// - /// + /// /// The branch id. - public async Task BranchCreate(CancellationToken cancellationToken, BranchCreateInput branchInput) + public async Task BranchCreate(BranchCreateInput branchInput, CancellationToken cancellationToken = default) { var request = new GraphQLRequest { @@ -127,24 +104,13 @@ public async Task BranchCreate(CancellationToken cancellationToken, Bran /// /// Id of the stream to get the branch from /// Name of the branch to get - /// - public Task BranchGet(string streamId, string branchName, int commitsLimit = 10) - { - return BranchGet(CancellationToken.None, streamId, branchName, commitsLimit); - } - - /// - /// Gets a given branch from a stream. - /// /// - /// Id of the stream to get the branch from - /// Name of the branch to get - /// + /// The requested branch public async Task BranchGet( - CancellationToken cancellationToken, string streamId, string branchName, - int commitsLimit = 10 + int commitsLimit = 10, + CancellationToken cancellationToken = default ) { var request = new GraphQLRequest @@ -183,13 +149,39 @@ public async Task BranchGet( } /// - /// Updates a branch. + /// Gets a given model from a project. /// - /// - /// The stream's id. - public Task BranchUpdate(BranchUpdateInput branchInput) + /// + /// Id of the project to get the model from + /// Id of the model + /// + public async Task ModelGet(string projectId, string modelId, CancellationToken cancellationToken = default) { - return BranchUpdate(CancellationToken.None, branchInput); + var request = new GraphQLRequest + { + Query = + $@"query ProjectModel($projectId: String!, $modelId: String!) {{ + project(id: $projectId) {{ + model(id: $modelId){{ + id, + name, + description + }} + }} + }}", + Variables = new { projectId, modelId } + }; + + var res = await ExecuteGraphQLRequest>>>( + request, + cancellationToken + ) + .ConfigureAwait(false); + var branch = new Branch(); + branch.description = res["project"]["model"]["description"]; + branch.id = res["project"]["model"]["id"]; + branch.name = res["project"]["model"]["name"]; + return branch; } /// @@ -197,7 +189,7 @@ public Task BranchUpdate(BranchUpdateInput branchInput) /// /// /// The stream's id. - public async Task BranchUpdate(CancellationToken cancellationToken, BranchUpdateInput branchInput) + public async Task BranchUpdate(BranchUpdateInput branchInput, CancellationToken cancellationToken = default) { var request = new GraphQLRequest { @@ -213,18 +205,9 @@ public async Task BranchUpdate(CancellationToken cancellationToken, Branch /// Deletes a stream. /// /// + /// /// - public Task BranchDelete(BranchDeleteInput branchInput) - { - return BranchDelete(CancellationToken.None, branchInput); - } - - /// - /// Deletes a stream. - /// - /// - /// - public async Task BranchDelete(CancellationToken cancellationToken, BranchDeleteInput branchInput) + public async Task BranchDelete(BranchDeleteInput branchInput, CancellationToken cancellationToken = default) { var request = new GraphQLRequest { diff --git a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.CommentOperations.cs b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.CommentOperations.cs index ab0769847b..b43baa36b4 100644 --- a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.CommentOperations.cs +++ b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.CommentOperations.cs @@ -3,7 +3,6 @@ using System.Threading; using System.Threading.Tasks; using GraphQL; -using Speckle.Core.Logging; namespace Speckle.Core.Api; @@ -15,26 +14,13 @@ public partial class Client /// Id of the stream to get the comments from /// The number of comments to get /// Time to filter the comments with - /// - public Task StreamGetComments(string streamId, int limit = 25, string? cursor = null) - { - return StreamGetComments(CancellationToken.None, streamId, limit, cursor); - } - - /// - /// Gets the comments on a Stream - /// /// - /// Id of the stream to get the comments from - /// The number of comments to get - /// Time to filter the comments with /// - /// public async Task StreamGetComments( - CancellationToken cancellationToken, string streamId, int limit = 25, - string? cursor = null + string? cursor = null, + CancellationToken cancellationToken = default ) { var request = new GraphQLRequest @@ -88,25 +74,17 @@ public async Task StreamGetComments( } /// - /// Gets the screenshot of a Comment + /// Gets the screenshot of a Comment /// /// Id of the comment /// Id of the stream to get the comment from - /// - public Task StreamGetCommentScreenshot(string id, string streamId) - { - return StreamGetCommentScreenshot(CancellationToken.None, id, streamId); - } - - /// - /// Gets the screenshot of a Comment - /// /// - /// Id of the comment - /// Id of the stream to get the comment from /// - /// - public async Task StreamGetCommentScreenshot(CancellationToken cancellationToken, string id, string streamId) + public async Task StreamGetCommentScreenshot( + string id, + string streamId, + CancellationToken cancellationToken = default + ) { var request = new GraphQLRequest { diff --git a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.CommitOperations.cs b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.CommitOperations.cs index 04d49356c9..b32b6a795b 100644 --- a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.CommitOperations.cs +++ b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.CommitOperations.cs @@ -1,6 +1,5 @@ #nullable enable -using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -15,20 +14,9 @@ public partial class Client /// /// Id of the stream to get the commit from /// Id of the commit to get - /// - public Task CommitGet(string streamId, string commitId) - { - return CommitGet(CancellationToken.None, streamId, commitId); - } - - /// - /// Gets a given commit from a stream. - /// /// - /// Id of the stream to get the commit from - /// Id of the commit to get /// - public async Task CommitGet(CancellationToken cancellationToken, string streamId, string commitId) + public async Task CommitGet(string streamId, string commitId, CancellationToken cancellationToken = default) { var request = new GraphQLRequest { @@ -60,21 +48,13 @@ public async Task CommitGet(CancellationToken cancellationToken, string /// /// Id of the stream to get the commits from /// Max number of commits to get - /// - public Task> StreamGetCommits(string streamId, int limit = 10) - { - return StreamGetCommits(CancellationToken.None, streamId, limit); - } - - /// - /// Gets the latest commits from a stream - /// /// - /// Id of the stream to get the commits from - /// Max number of commits to get - /// - /// - public async Task> StreamGetCommits(CancellationToken cancellationToken, string streamId, int limit = 10) + /// The requested commits + public async Task> StreamGetCommits( + string streamId, + int limit = 10, + CancellationToken cancellationToken = default + ) { var request = new GraphQLRequest { @@ -110,14 +90,7 @@ public async Task> StreamGetCommits(CancellationToken cancellationT /// /// /// The commit id. - public Task CommitCreate(CommitCreateInput commitInput) - { - return CommitCreate(CancellationToken.None, commitInput); - } - - /// - /// - public async Task CommitCreate(CancellationToken cancellationToken, CommitCreateInput commitInput) + public async Task CommitCreate(CommitCreateInput commitInput, CancellationToken cancellationToken = default) { var request = new GraphQLRequest { @@ -133,18 +106,9 @@ public async Task CommitCreate(CancellationToken cancellationToken, Comm /// Updates a commit. /// /// + /// /// The stream's id. - public Task CommitUpdate(CommitUpdateInput commitInput) - { - return CommitUpdate(CancellationToken.None, commitInput); - } - - /// - /// Updates a commit. - /// - /// - /// The stream's id. - public async Task CommitUpdate(CancellationToken cancellationToken, CommitUpdateInput commitInput) + public async Task CommitUpdate(CommitUpdateInput commitInput, CancellationToken cancellationToken = default) { var request = new GraphQLRequest { @@ -160,18 +124,9 @@ public async Task CommitUpdate(CancellationToken cancellationToken, Commit /// Deletes a commit. /// /// + /// /// - public Task CommitDelete(CommitDeleteInput commitInput) - { - return CommitDelete(CancellationToken.None, commitInput); - } - - /// - /// Deletes a commit. - /// - /// - /// - public async Task CommitDelete(CancellationToken cancellationToken, CommitDeleteInput commitInput) + public async Task CommitDelete(CommitDeleteInput commitInput, CancellationToken cancellationToken = default) { var request = new GraphQLRequest { @@ -188,15 +143,12 @@ public async Task CommitDelete(CancellationToken cancellationToken, Commit /// /// Used for read receipts /// - /// - public Task CommitReceived(CommitReceivedInput commitReceivedInput) - { - return CommitReceived(CancellationToken.None, commitReceivedInput); - } - - /// /// - public async Task CommitReceived(CancellationToken cancellationToken, CommitReceivedInput commitReceivedInput) + /// + public async Task CommitReceived( + CommitReceivedInput commitReceivedInput, + CancellationToken cancellationToken = default + ) { var request = new GraphQLRequest { diff --git a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.ObjectOperations.cs b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.ObjectOperations.cs index 406cf194a8..9ea3c3ed56 100644 --- a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.ObjectOperations.cs +++ b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.ObjectOperations.cs @@ -9,24 +9,17 @@ namespace Speckle.Core.Api; public partial class Client { /// - /// Gets a given object from a stream. + /// Gets data about the requested Speckle object from a stream. /// /// Id of the stream to get the object from /// Id of the object to get - /// - public Task ObjectGet(string streamId, string objectId) - { - return ObjectGet(CancellationToken.None, streamId, objectId); - } - - /// - /// Gets a given object from a stream. - /// /// - /// Id of the stream to get the object from - /// Id of the object to get /// - public async Task ObjectGet(CancellationToken cancellationToken, string streamId, string objectId) + public async Task ObjectGet( + string streamId, + string objectId, + CancellationToken cancellationToken = default + ) { var request = new GraphQLRequest { @@ -53,20 +46,13 @@ public async Task ObjectGet(CancellationToken cancellationToken, /// /// /// - /// - public Task ObjectCountGet(string streamId, string objectId) - { - return ObjectCountGet(CancellationToken.None, streamId, objectId); - } - - /// - /// Gets a given object from a stream. - /// /// - /// - /// /// - public async Task ObjectCountGet(CancellationToken cancellationToken, string streamId, string objectId) + public async Task ObjectCountGet( + string streamId, + string objectId, + CancellationToken cancellationToken = default + ) { var request = new GraphQLRequest { diff --git a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.ObsoleteOperations.cs b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.ObsoleteOperations.cs new file mode 100644 index 0000000000..d2688296dd --- /dev/null +++ b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.ObsoleteOperations.cs @@ -0,0 +1,270 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using GraphQL; +using Speckle.Core.Logging; + +namespace Speckle.Core.Api; + +[SuppressMessage("Design", "CA1068:CancellationToken parameters must come last")] +public partial class Client +{ + #region Depreated Invites + + /// + /// Checks if Speckle Server version is at least v2.6.4 meaning stream invites are supported. + /// + /// + /// true if invites are supported + /// if Speckle Server version is less than v2.6.4 + [Obsolete("We're not supporting 2.6.4 version any more", true)] + public async Task _CheckStreamInvitesSupported(CancellationToken cancellationToken = default) + { + var version = ServerVersion ?? await GetServerVersion(cancellationToken).ConfigureAwait(false); + if (version < new System.Version("2.6.4")) + throw new SpeckleException("Stream invites are only supported as of Speckle Server v2.6.4."); + return true; + } + #endregion + + #region Stream Grant Permission + + /// + /// Grants permissions to a user on a given stream. + /// + /// + /// + [Obsolete("Please use the `StreamUpdatePermission` method", true)] + public Task StreamGrantPermission(StreamPermissionInput permissionInput) + { + return StreamGrantPermission(CancellationToken.None, permissionInput); + } + + /// + /// Grants permissions to a user on a given stream. + /// + /// + /// + /// + [Obsolete("Please use the `StreamUpdatePermission` method", true)] + public async Task StreamGrantPermission( + CancellationToken cancellationToken, + StreamPermissionInput permissionInput + ) + { + var request = new GraphQLRequest + { + Query = + @" + mutation streamGrantPermission($permissionParams: StreamGrantPermissionInput!) { + streamGrantPermission(permissionParams:$permissionParams) + }", + Variables = new { permissionParams = permissionInput } + }; + + var res = await ExecuteGraphQLRequest>(request, cancellationToken).ConfigureAwait(false); + return (bool)res["streamGrantPermission"]; + } + + #endregion + + #region Cancellation token as last param + + [Obsolete("Use overload with cancellation token parameter last")] + public Task> StreamGetActivity( + CancellationToken cancellationToken, + string id, + DateTime? after = null, + DateTime? before = null, + DateTime? cursor = null, + string actionType = "", + int limit = 25 + ) + { + return StreamGetActivity(id, after, before, cursor, actionType, limit, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task> StreamGetBranches( + CancellationToken cancellationToken, + string streamId, + int branchesLimit = 10, + int commitsLimit = 10 + ) + { + return StreamGetBranches(streamId, branchesLimit, commitsLimit, CancellationToken.None); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task BranchCreate(CancellationToken cancellationToken, BranchCreateInput branchInput) + { + return BranchCreate(branchInput, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task BranchGet( + CancellationToken cancellationToken, + string streamId, + string branchName, + int commitsLimit = 10 + ) + { + return BranchGet(streamId, branchName, commitsLimit, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task BranchUpdate(CancellationToken cancellationToken, BranchUpdateInput branchInput) + { + return BranchUpdate(branchInput, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task BranchDelete(CancellationToken cancellationToken, BranchDeleteInput branchInput) + { + return BranchDelete(branchInput, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task StreamGetComments( + CancellationToken cancellationToken, + string streamId, + int limit = 25, + string? cursor = null + ) + { + return StreamGetComments(streamId, limit, cursor, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task StreamGetCommentScreenshot(CancellationToken cancellationToken, string id, string streamId) + { + return StreamGetCommentScreenshot(id, streamId, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task CommitGet(CancellationToken cancellationToken, string streamId, string commitId) + { + return CommitGet(streamId, commitId, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task> StreamGetCommits(CancellationToken cancellationToken, string streamId, int limit = 10) + { + return StreamGetCommits(streamId, limit, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task CommitCreate(CancellationToken cancellationToken, CommitCreateInput commitInput) + { + return CommitCreate(commitInput, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task CommitUpdate(CancellationToken cancellationToken, CommitUpdateInput commitInput) + { + return CommitUpdate(commitInput, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task CommitDelete(CancellationToken cancellationToken, CommitDeleteInput commitInput) + { + return CommitDelete(commitInput, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task CommitReceived(CancellationToken cancellationToken, CommitReceivedInput commitReceivedInput) + { + return CommitReceived(commitReceivedInput, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task ObjectGet(CancellationToken cancellationToken, string streamId, string objectId) + { + return ObjectGet(streamId, objectId, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task ObjectCountGet(CancellationToken cancellationToken, string streamId, string objectId) + { + return ObjectCountGet(streamId, objectId, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task StreamGet(CancellationToken cancellationToken, string id, int branchesLimit = 10) + { + return StreamGet(id, branchesLimit, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task> StreamsGet(CancellationToken cancellationToken, int limit = 10) + { + return StreamsGet(limit, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task> FavoriteStreamsGet(CancellationToken cancellationToken, int limit = 10) + { + return FavoriteStreamsGet(limit, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task> StreamSearch(CancellationToken cancellationToken, string query, int limit = 10) + { + return StreamSearch(query, limit, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task StreamCreate(CancellationToken cancellationToken, StreamCreateInput streamInput) + { + return StreamCreate(streamInput, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task StreamUpdate(CancellationToken cancellationToken, StreamUpdateInput streamInput) + { + return StreamUpdate(streamInput, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task StreamDelete(CancellationToken cancellationToken, string id) + { + return StreamDelete(id, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task StreamRevokePermission( + CancellationToken cancellationToken, + StreamRevokePermissionInput permissionInput + ) + { + return StreamRevokePermission(permissionInput, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task StreamGetPendingCollaborators(CancellationToken cancellationToken, string id) + { + return StreamGetPendingCollaborators(id, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task StreamInviteCreate(CancellationToken cancellationToken, StreamInviteCreateInput inviteCreateInput) + { + return StreamInviteCreate(inviteCreateInput, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task OtherUserGet(CancellationToken cancellationToken, string id) + { + return OtherUserGet(id, cancellationToken); + } + + [Obsolete("Use overload with cancellation token parameter last")] + public Task> UserSearch(CancellationToken cancellationToken, string query, int limit = 10) + { + return UserSearch(query, limit, cancellationToken); + } + #endregion +} diff --git a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.ServerOperations.cs b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.ServerOperations.cs index 63b9291251..f45726b676 100644 --- a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.ServerOperations.cs +++ b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.ServerOperations.cs @@ -16,7 +16,6 @@ public partial class Client /// [Optional] defaults to an empty cancellation token /// object excluding any strings (eg "2.7.2-alpha.6995" becomes "2.7.2.6995") /// - /// public async Task GetServerVersion(CancellationToken cancellationToken = default) { var request = new GraphQLRequest diff --git a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.StreamOperations.cs b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.StreamOperations.cs index 2c2b40f6e6..937a0d9c07 100644 --- a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.StreamOperations.cs +++ b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.StreamOperations.cs @@ -9,11 +9,11 @@ namespace Speckle.Core.Api; public partial class Client { - /// - /// Cheks if a stream exists by id. + /// Checks if a stream exists by id. /// /// Id of the stream to get + /// /// public async Task IsStreamAccessible(string id, CancellationToken cancellationToken = default) { @@ -41,30 +41,17 @@ public async Task IsStreamAccessible(string id, CancellationToken cancella { return false; } - } - /// /// Gets a stream by id including basic branch info (id, name, description, and total commit count). - /// For detailed commit and branch info, use StreamGetCommits and StreamGetBranches respectively. - /// - /// Id of the stream to get - /// Max number of branches to retrieve - /// - public Task StreamGet(string id, int branchesLimit = 10) - { - return StreamGet(CancellationToken.None, id, branchesLimit); - } - - /// - /// Gets a stream by id including basic branch info (id, name, description, and total commit count). - /// For detailed commit and branch info, use StreamGetCommits and StreamGetBranches respectively. + /// For detailed commit and branch info, use and respectively. /// /// Id of the stream to get /// Max number of branches to retrieve + /// /// - public async Task StreamGet(CancellationToken cancellationToken, string id, int branchesLimit = 10) + public async Task StreamGet(string id, int branchesLimit = 10, CancellationToken cancellationToken = default) { var request = new GraphQLRequest { @@ -110,18 +97,9 @@ public async Task StreamGet(CancellationToken cancellationToken, string /// Gets all streams for the current user /// /// Max number of streams to return + /// /// - public Task> StreamsGet(int limit = 10) - { - return StreamsGet(CancellationToken.None, limit); - } - - /// - /// Gets all streams for the current user - /// - /// Max number of streams to return - /// - public async Task> StreamsGet(CancellationToken cancellationToken, int limit = 10) + public async Task> StreamsGet(int limit = 10, CancellationToken cancellationToken = default) { var request = new GraphQLRequest { @@ -172,17 +150,13 @@ public async Task> StreamsGet(CancellationToken cancellationToken, return res.activeUser.streams.items; } - public Task> FavoriteStreamsGet(int limit = 10) - { - return FavoriteStreamsGet(CancellationToken.None, limit); - } - /// /// Gets all favorite streams for the current user /// /// Max number of streams to return + /// /// - public async Task> FavoriteStreamsGet(CancellationToken cancellationToken, int limit = 10) + public async Task> FavoriteStreamsGet(int limit = 10, CancellationToken cancellationToken = default) { var request = new GraphQLRequest { @@ -234,19 +208,13 @@ public async Task> FavoriteStreamsGet(CancellationToken cancellatio /// /// String query to search for /// Max number of streams to return + /// /// - public Task> StreamSearch(string query, int limit = 10) - { - return StreamSearch(CancellationToken.None, query, limit); - } - - /// - /// Searches the user's streams by name, description, and ID - /// - /// String query to search for - /// Max number of streams to return - /// - public async Task> StreamSearch(CancellationToken cancellationToken, string query, int limit = 10) + public async Task> StreamSearch( + string query, + int limit = 10, + CancellationToken cancellationToken = default + ) { var request = new GraphQLRequest { @@ -276,7 +244,7 @@ public async Task> StreamSearch(CancellationToken cancellationToken Variables = new { query, limit } }; - var res = await GQLClient.SendMutationAsync(request, cancellationToken).ConfigureAwait(false); + var res = await GQLClient.SendMutationAsync(request, cancellationToken).ConfigureAwait(false); //WARN: Why do we do this? return (await ExecuteGraphQLRequest(request, cancellationToken).ConfigureAwait(false)).streams.items; } @@ -284,18 +252,9 @@ public async Task> StreamSearch(CancellationToken cancellationToken /// Creates a stream. /// /// + /// /// The stream's id. - public Task StreamCreate(StreamCreateInput streamInput) - { - return StreamCreate(CancellationToken.None, streamInput); - } - - /// - /// Creates a stream. - /// - /// - /// The stream's id. - public async Task StreamCreate(CancellationToken cancellationToken, StreamCreateInput streamInput) + public async Task StreamCreate(StreamCreateInput streamInput, CancellationToken cancellationToken = default) { var request = new GraphQLRequest { @@ -310,19 +269,9 @@ public async Task StreamCreate(CancellationToken cancellationToken, Stre /// Updates a stream. /// /// Note: the id field needs to be a valid stream id. - /// The stream's id. - public Task StreamUpdate(StreamUpdateInput streamInput) - { - return StreamUpdate(CancellationToken.None, streamInput); - } - - /// - /// Updates a stream. - /// /// - /// Note: the id field needs to be a valid stream id. /// The stream's id. - public async Task StreamUpdate(CancellationToken cancellationToken, StreamUpdateInput streamInput) + public async Task StreamUpdate(StreamUpdateInput streamInput, CancellationToken cancellationToken = default) { var request = new GraphQLRequest { @@ -339,19 +288,9 @@ public async Task StreamUpdate(CancellationToken cancellationToken, Stream /// Deletes a stream. /// /// Id of the stream to be deleted - /// - public Task StreamDelete(string id) - { - return StreamDelete(CancellationToken.None, id); - } - - /// - /// Deletes a stream. - /// /// - /// Id of the stream to be deleted /// - public async Task StreamDelete(CancellationToken cancellationToken, string id) + public async Task StreamDelete(string id, CancellationToken cancellationToken = default) { var request = new GraphQLRequest { @@ -362,62 +301,15 @@ public async Task StreamDelete(CancellationToken cancellationToken, string return (bool)res["streamDelete"]; } - /// - /// Grants permissions to a user on a given stream. - /// - /// - /// - [Obsolete("Please use the `StreamUpdatePermission` method", true)] - public Task StreamGrantPermission(StreamPermissionInput permissionInput) - { - return StreamGrantPermission(CancellationToken.None, permissionInput); - } - - /// - /// Grants permissions to a user on a given stream. - /// - /// - /// - /// - [Obsolete("Please use the `StreamUpdatePermission` method", true)] - public async Task StreamGrantPermission( - CancellationToken cancellationToken, - StreamPermissionInput permissionInput - ) - { - var request = new GraphQLRequest - { - Query = - @" - mutation streamGrantPermission($permissionParams: StreamGrantPermissionInput!) { - streamGrantPermission(permissionParams:$permissionParams) - }", - Variables = new { permissionParams = permissionInput } - }; - - var res = await ExecuteGraphQLRequest>(request, cancellationToken).ConfigureAwait(false); - return (bool)res["streamGrantPermission"]; - } - /// /// Revokes permissions of a user on a given stream. /// /// - /// - public Task StreamRevokePermission(StreamRevokePermissionInput permissionInput) - { - return StreamRevokePermission(CancellationToken.None, permissionInput); - } - - /// - /// Revokes permissions of a user on a given stream. - /// /// - /// /// public async Task StreamRevokePermission( - CancellationToken cancellationToken, - StreamRevokePermissionInput permissionInput + StreamRevokePermissionInput permissionInput, + CancellationToken cancellationToken = default ) { var request = new GraphQLRequest @@ -463,21 +355,13 @@ mutation streamUpdatePermission($permissionParams: StreamUpdatePermissionInput!) /// Gets the pending collaborators of a stream by id. /// Requires the user to be an owner of the stream. /// - /// Id of the stream to get - /// - public Task StreamGetPendingCollaborators(string id) - { - return StreamGetPendingCollaborators(CancellationToken.None, id); - } - - /// - /// Gets the pending collaborators of a stream by id. - /// Requires the user to be an owner of the stream. - /// - /// Id of the stream to get - /// Max number of branches to retrieve + /// + /// /// - public async Task StreamGetPendingCollaborators(CancellationToken cancellationToken, string id) + public async Task StreamGetPendingCollaborators( + string streamId, + CancellationToken cancellationToken = default + ) { var request = new GraphQLRequest { @@ -496,31 +380,21 @@ public async Task StreamGetPendingCollaborators(CancellationToken cancel } } }", - Variables = new { id } + Variables = new { id = streamId } }; - var res = await GQLClient.SendMutationAsync(request, cancellationToken).ConfigureAwait(false); + var res = await GQLClient.SendMutationAsync(request, cancellationToken).ConfigureAwait(false); //WARN: Why do we do this? return (await ExecuteGraphQLRequest(request, cancellationToken).ConfigureAwait(false)).stream; } /// /// Sends an email invite to join a stream and assigns them a collaborator role. /// - /// - /// - public Task StreamInviteCreate(StreamInviteCreateInput streamCreateInput) - { - return StreamInviteCreate(CancellationToken.None, streamCreateInput); - } - - /// - /// Sends an email invite to join a stream and assigns them a collaborator role. - /// - /// /// + /// /// public async Task StreamInviteCreate( - CancellationToken cancellationToken, - StreamInviteCreateInput inviteCreateInput + StreamInviteCreateInput inviteCreateInput, + CancellationToken cancellationToken = default ) { if ((inviteCreateInput.email == null) & (inviteCreateInput.userId == null)) @@ -566,21 +440,6 @@ mutation streamInviteCancel( $streamId: String!, $inviteId: String! ) { return (bool)res["streamInviteCancel"]; } - /// - /// Checks if Speckle Server version is at least v2.6.4 meaning stream invites are supported. - /// - /// - /// true if invites are supported - /// if Speckle Server version is less than v2.6.4 - [Obsolete("We're not supporting 2.6.4 version any more", true)] - public async Task _CheckStreamInvitesSupported(CancellationToken cancellationToken = default) - { - var version = ServerVersion ?? await GetServerVersion(cancellationToken).ConfigureAwait(false); - if (version < new System.Version("2.6.4")) - throw new SpeckleException("Stream invites are only supported as of Speckle Server v2.6.4."); - return true; - } - /// /// Accept or decline a stream invite. /// diff --git a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.UserOperations.cs b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.UserOperations.cs index c6e29d7a7f..939f9aee2c 100644 --- a/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.UserOperations.cs +++ b/Core/Core/Api/GraphQL/Client.GraphqlCleintOperations/Client.UserOperations.cs @@ -3,28 +3,17 @@ using System.Threading; using System.Threading.Tasks; using GraphQL; -using Speckle.Core.Logging; namespace Speckle.Core.Api; public partial class Client { - /// - /// Gets the currently active user profile. - /// - /// - public Task ActiveUserGet() - { - return ActiveUserGet(CancellationToken.None); - } - /// /// Gets the currently active user profile. /// /// /// - /// - public async Task ActiveUserGet(CancellationToken cancellationToken) + public async Task ActiveUserGet(CancellationToken cancellationToken = default) { var request = new GraphQLRequest { @@ -50,20 +39,9 @@ public async Task ActiveUserGet(CancellationToken cancellationToken) /// Get another user's profile by its user id. /// /// Id of the user you are looking for - /// - public Task OtherUserGet(string id) - { - return OtherUserGet(CancellationToken.None, id); - } - - /// - /// Get another user's profile by its user id. - /// /// - /// Id of the user you are looking for /// - /// - public async Task OtherUserGet(CancellationToken cancellationToken, string id) + public async Task OtherUserGet(string id, CancellationToken cancellationToken = default) { var request = new GraphQLRequest { @@ -90,18 +68,11 @@ public async Task ActiveUserGet(CancellationToken cancellationToken) /// String to search for. Must be at least 3 characters /// Max number of users to return /// - public Task> UserSearch(string query, int limit = 10) - { - return UserSearch(CancellationToken.None, query, limit); - } - - /// - /// Searches for a user on the server. - /// - /// String to search for. Must be at least 3 characters - /// Max number of users to return - /// - public async Task> UserSearch(CancellationToken cancellationToken, string query, int limit = 10) + public async Task> UserSearch( + string query, + int limit = 10, + CancellationToken cancellationToken = default + ) { var request = new GraphQLRequest { diff --git a/Core/Core/Api/GraphQL/Client.cs b/Core/Core/Api/GraphQL/Client.cs index 10c8640287..65f395458f 100644 --- a/Core/Core/Api/GraphQL/Client.cs +++ b/Core/Core/Api/GraphQL/Client.cs @@ -75,7 +75,7 @@ public Client(Account account) public string ApiToken => Account.token; - public System.Version ServerVersion { get; set; } + public System.Version? ServerVersion { get; set; } [JsonIgnore] public Account Account { get; set; } @@ -143,7 +143,7 @@ internal async Task ExecuteWithResiliencePolicies(Func> func) return await graphqlRetry.ExecuteAsync(func).ConfigureAwait(false); } - internal async Task ExecuteGraphQLRequest(GraphQLRequest request, CancellationToken cancellationToken = default) + public async Task ExecuteGraphQLRequest(GraphQLRequest request, CancellationToken cancellationToken = default) { using IDisposable context0 = LogContext.Push(_createEnrichers(request)); diff --git a/Core/Core/Api/GraphQL/Models.cs b/Core/Core/Api/GraphQL/Models.cs index 68886c9915..8f0097ad19 100644 --- a/Core/Core/Api/GraphQL/Models.cs +++ b/Core/Core/Api/GraphQL/Models.cs @@ -122,17 +122,17 @@ public class Stream public Branches branches { get; set; } /// - /// Set only in the case that you've requested this through . + /// Set only in the case that you've requested this through . /// public Branch branch { get; set; } /// - /// Set only in the case that you've requested this through . + /// Set only in the case that you've requested this through . /// public Commit commit { get; set; } /// - /// Set only in the case that you've requested this through + /// Set only in the case that you've requested this through /// public Commits commits { get; set; } @@ -383,6 +383,11 @@ public class ServerInfo public string version { get; set; } public string adminContact { get; set; } public string description { get; set; } + + //NOTE: this field is not returned from the GQL API + //it is manually populated by checking against the response headers + //TODO: deprecate after the transition from fe1 to fe2 + public bool frontend2 { get; set; } } public class StreamData diff --git a/Core/Core/Api/ServerLimits.cs b/Core/Core/Api/ServerLimits.cs new file mode 100644 index 0000000000..7f78def96a --- /dev/null +++ b/Core/Core/Api/ServerLimits.cs @@ -0,0 +1,14 @@ +namespace Speckle.Core.Api; + +/// +/// Defines the limits for specific API calls on the Speckle Server. +/// These are magic numbers! Should be aligned with server always. +/// +/// +/// ⚠️ Not all limits are reflected here! +/// +public static class ServerLimits +{ + public const int BRANCH_GET_LIMIT = 500; + public const int OLD_BRANCH_GET_LIMIT = 100; +} diff --git a/Core/Core/Core.csproj b/Core/Core/Core.csproj index 7ee14fe173..fc1dac829f 100644 --- a/Core/Core/Core.csproj +++ b/Core/Core/Core.csproj @@ -12,7 +12,6 @@ $(PackageTags) core true true - 8603, 8601, 8602, CS8625 diff --git a/Core/Core/Credentials/Account.cs b/Core/Core/Credentials/Account.cs index 54e1eed54b..a0bc237ba7 100644 --- a/Core/Core/Credentials/Account.cs +++ b/Core/Core/Credentials/Account.cs @@ -65,7 +65,7 @@ public string GetHashedEmail() public string GetHashedServer() { - string url = serverInfo?.url ?? "https://speckle.xyz/"; + string url = serverInfo?.url ?? AccountManager.DEFAULT_SERVER_URL; return Crypt.Hash(CleanURL(url)); } diff --git a/Core/Core/Credentials/AccountManager.cs b/Core/Core/Credentials/AccountManager.cs index 76c706360c..4398475b6a 100644 --- a/Core/Core/Credentials/AccountManager.cs +++ b/Core/Core/Credentials/AccountManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -27,6 +28,8 @@ namespace Speckle.Core.Credentials; /// public static class AccountManager { + public const string DEFAULT_SERVER_URL = "https://app.speckle.systems"; + private static readonly SQLiteTransport AccountStorage = new(scope: "Accounts"); private static bool _isAddingAccount; private static readonly SQLiteTransport AccountAddLockStorage = new(scope: "AccountAddFlow"); @@ -128,7 +131,7 @@ private static async Task GetUserServerInfo(string /// public static string GetDefaultServerUrl() { - var defaultServerUrl = "https://speckle.xyz"; + var serverUrl = DEFAULT_SERVER_URL; var customServerUrl = ""; // first mechanism, check for local file @@ -146,10 +149,10 @@ public static string GetDefaultServerUrl() Uri url = null; Uri.TryCreate(customServerUrl, UriKind.Absolute, out url); if (url != null) - defaultServerUrl = customServerUrl.TrimEnd('/'); + serverUrl = customServerUrl.TrimEnd('/'); } - return defaultServerUrl; + return serverUrl; } /// @@ -275,6 +278,7 @@ public static async Task UpdateAccounts() account.userInfo = userServerInfo.activeUser; account.serverInfo = userServerInfo.serverInfo; account.serverInfo.url = url; + account.serverInfo.frontend2 = await IsFrontend2Server(url).ConfigureAwait(false); } catch (Exception) { @@ -587,6 +591,29 @@ await response.Content.ReadAsStringAsync().ConfigureAwait(false) } } + private static async Task IsFrontend2Server(string server) + { + try + { + using var client = Http.GetHttpProxyClient(); + var response = await client.GetAsync(server).ConfigureAwait(false); + + if (response.Headers.TryGetValues("x-speckle-frontend-2", out IEnumerable values)) + { + if (values.Any() && bool.Parse(values.FirstOrDefault())) + { + return true; + } + } + + return false; + } + catch (Exception e) + { + return false; + } + } + private static string GenerateChallenge() { using (RandomNumberGenerator rng = new RNGCryptoServiceProvider()) diff --git a/Core/Core/Helpers/Crypt.cs b/Core/Core/Helpers/Crypt.cs index ad331307b1..0511911282 100644 --- a/Core/Core/Helpers/Crypt.cs +++ b/Core/Core/Helpers/Crypt.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; using System.Text; @@ -5,6 +6,7 @@ namespace Speckle.Core.Helpers; public static class Crypt { + [SuppressMessage("Security", "CA5351:Do Not Use Broken Cryptographic Algorithms")] public static string Hash(string input) { using (MD5 md5 = MD5.Create()) diff --git a/Core/Core/Helpers/Http.cs b/Core/Core/Helpers/Http.cs index c4636ed2de..36ee1b2093 100644 --- a/Core/Core/Helpers/Http.cs +++ b/Core/Core/Helpers/Http.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Http; using System.Net.NetworkInformation; @@ -181,7 +182,8 @@ public static HttpClient GetHttpProxyClient(SpeckleHttpClientHandler? handler = IWebProxy proxy = WebRequest.GetSystemWebProxy(); proxy.Credentials = CredentialCache.DefaultCredentials; - var client = new HttpClient(handler ?? new SpeckleHttpClientHandler()); + handler ??= new SpeckleHttpClientHandler(); + var client = new HttpClient(handler); client.Timeout = timeout ?? TimeSpan.FromSeconds(100); return client; } diff --git a/Core/Core/Kits/ConverterInterfaces/IFinalizable.cs b/Core/Core/Kits/ConverterInterfaces/IFinalizable.cs new file mode 100644 index 0000000000..1c6d27cdbb --- /dev/null +++ b/Core/Core/Kits/ConverterInterfaces/IFinalizable.cs @@ -0,0 +1,7 @@ +namespace Speckle.Core.Kits.ConverterInterfaces +{ + public interface IFinalizable + { + void FinalizeConversion(); + } +} diff --git a/Core/Core/Kits/Exceptions.cs b/Core/Core/Kits/Exceptions.cs index aa7f3096ed..3734926204 100644 --- a/Core/Core/Kits/Exceptions.cs +++ b/Core/Core/Kits/Exceptions.cs @@ -32,7 +32,7 @@ public KitException(string? message, Exception? innerException) : base(message, } /// -/// Exception thrown when conversion of an object fails +/// Exception thrown when conversion of an object was not successful /// /// /// Ideally this exception contains a meaningful message, and reference to the object that failed to be converted. @@ -56,8 +56,53 @@ public ConversionException() { } } /// -/// Exception thrown when an object was desirably skipped +/// Exception used when an object could not be converted, because we don't support a specific conversion. /// +/// +/// This Exception should be thrown as part of a pre-emptive check in conversions (not as part reactive error handling) +/// and usage (throwing) should not be dependent on external state: +/// i.e. given the same object and converter state, the outcome (exception throw or not) should be the same. +/// +/// +/// It can be used for: +///
    +///
  • objects who's we don't support (e.g. "Walls are not supported")
  • +///
  • objects with a property who's value we don't support (e.g. "Beams with shape type of Circular are not supported")
  • +///
  • complex object requirements (e.g. "We don't support walls with zero width and no displayValue")
  • +///
+/// It should NOT be used for: +///
    +///
  • Invalid Speckle Objects (e.g. "We don't support walls with null lines")
  • +///
  • Objects that we have already converted, and therefore now skip (e.g. "A Wall with the same name was already converted")
  • +///
  • Reactive error handling (e.g. "Failed to convert wall, I guess it wasn't supported")
  • +///
+///
+public class ConversionNotSupportedException : ConversionException +{ + public ConversionNotSupportedException(string? message, object? objectToConvert, Exception? innerException = null) + : base(message, objectToConvert, innerException) { } + + public ConversionNotSupportedException(string message, Exception innerException) : base(message, innerException) { } + + public ConversionNotSupportedException(string message) : base(message) { } + + public ConversionNotSupportedException() { } +} + +/// +/// Exception thrown when an object was desirably skipped
+///
+/// +/// Avoid throwing this exception Type!
+/// As it introduces some bad patterns for exception handling. +///
+/// Namely, it encodes how the exception WILL be handled, Not simply what HAS happened. +/// Exceptions shouldn't care how they are handled. +///
+/// We were also misusing this exception in Revit, to correct for ambiguity in the way certain objects should be traversed, +/// by selectively skipping objects that were already converted by other means. +///
+[Obsolete("Avoid using this type. Use " + nameof(ConversionNotSupportedException) + " instead, if appropriate")] public class ConversionSkippedException : ConversionException { public ConversionSkippedException(string? message, object? objectToConvert, Exception? innerException = null) diff --git a/Core/Core/Logging/Setup.cs b/Core/Core/Logging/Setup.cs index 01edc37053..61294e2056 100644 --- a/Core/Core/Logging/Setup.cs +++ b/Core/Core/Logging/Setup.cs @@ -40,7 +40,11 @@ static Setup() ///
internal static string VersionedHostApplication { get; private set; } = HostApplications.Other.Slug; - public static void Init(string versionedHostApplication, string hostApplication) + public static void Init( + string versionedHostApplication, + string hostApplication, + SpeckleLogConfiguration logConfiguration = null + ) { if (_initialized) { @@ -63,7 +67,7 @@ public static void Init(string versionedHostApplication, string hostApplication) //start mutex so that Manager can detect if this process is running mutex = new Mutex(false, "SpeckleConnector-" + hostApplication); - SpeckleLog.Initialize(hostApplication, versionedHostApplication); + SpeckleLog.Initialize(hostApplication, versionedHostApplication, logConfiguration); foreach (var account in AccountManager.GetAccounts()) Analytics.AddConnectorToProfile(account.GetHashedEmail(), hostApplication); diff --git a/Core/Core/Transports/Server.cs b/Core/Core/Transports/Server.cs index 916274a45f..b990c46e2c 100644 --- a/Core/Core/Transports/Server.cs +++ b/Core/Core/Transports/Server.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -80,7 +80,13 @@ public void Dispose() public string TransportName { get; set; } = "RemoteTransport"; public Dictionary TransportContext => - new() { { "name", TransportName }, { "type", GetType().Name }, { "streamId", StreamId }, { "serverUrl", BaseUri } }; + new() + { + { "name", TransportName }, + { "type", GetType().Name }, + { "streamId", StreamId }, + { "serverUrl", BaseUri } + }; public CancellationToken CancellationToken { get; set; } diff --git a/Core/IntegrationTests/Api.cs b/Core/IntegrationTests/Api.cs index f060cc11c0..72d17798f3 100644 --- a/Core/IntegrationTests/Api.cs +++ b/Core/IntegrationTests/Api.cs @@ -108,7 +108,6 @@ public async Task IsStreamAccessible() Assert.True(res); } - [Test, Order(13)] public async Task StreamSearch() { @@ -275,6 +274,54 @@ public async Task StreamGetBranches() Assert.That(res[0].name, Is.EqualTo("main")); } + [Test, Order(51)] + public async Task StreamGetBranches_Throws_WhenRequestingOverLimit() + { + Assert.ThrowsAsync>( + async () => await myClient.StreamGetBranches(streamId, ServerLimits.BRANCH_GET_LIMIT + 1).ConfigureAwait(false) + ); + var res = await myClient.StreamGetBranches(streamId, ServerLimits.BRANCH_GET_LIMIT).ConfigureAwait(false); + + Assert.That(res, Is.Not.Null); + } + + [Test, Order(52)] + public async Task StreamGetBranches_WithManyBranches() + { + var newStreamId = await myClient.StreamCreate(new StreamCreateInput { name = "Many branches stream" }); + + await CreateEmptyBranches(myClient, newStreamId, ServerLimits.BRANCH_GET_LIMIT); + + var res = await myClient.StreamGetBranches(newStreamId, ServerLimits.BRANCH_GET_LIMIT); + + Assert.That(res, Is.Not.Null); + Assert.That(res, Has.Count.EqualTo(ServerLimits.BRANCH_GET_LIMIT)); + } + + public async Task CreateEmptyBranches( + Client client, + string streamId, + int branchCount, + string branchPrefix = "Test branch" + ) + { + // now let's send HTTP requests to each of these URLs in parallel + var options = new ParallelOptions { MaxDegreeOfParallelism = 2 }; + + // now let's send HTTP requests to each of these URLs in parallel + await Parallel.ForEachAsync( + Enumerable.Range(0, branchCount), + options, + async (i, cancellationToken) => + { + await client.BranchCreate( + new BranchCreateInput { name = $"{branchPrefix} {i}", streamId = streamId }, + cancellationToken + ); + } + ); + } + #region commit [Test, Order(43)] diff --git a/Core/IntegrationTests/TestsIntegration.csproj b/Core/IntegrationTests/TestsIntegration.csproj index 2f1c6a6336..4d425df603 100644 --- a/Core/IntegrationTests/TestsIntegration.csproj +++ b/Core/IntegrationTests/TestsIntegration.csproj @@ -6,6 +6,7 @@ enable false + $(NoWarn);CA2007 diff --git a/Core/docker-compose.yml b/Core/docker-compose.yml index 5235371894..e22c793b59 100644 --- a/Core/docker-compose.yml +++ b/Core/docker-compose.yml @@ -53,7 +53,7 @@ services: # Speckle Server ####### speckle-frontend: - image: speckle/speckle-frontend:2 + image: speckle/speckle-frontend:latest restart: always ports: - "0.0.0.0:8080:8080" @@ -61,7 +61,7 @@ services: FILE_SIZE_LIMIT_MB: 100 speckle-server: - image: speckle/speckle-server:2 + image: speckle/speckle-server:latest restart: always healthcheck: test: @@ -98,6 +98,7 @@ services: S3_CREATE_BUCKET: "true" FILE_SIZE_LIMIT_MB: 100 + MAX_PROJECT_MODELS_PER_PAGE: 500 # TODO: Change this to a unique secret for this server SESSION_SECRET: "TODO:ReplaceWithLongString" @@ -112,19 +113,17 @@ services: ENABLE_MP: "false" preview-service: - image: speckle/speckle-preview-service:2 + image: speckle/speckle-preview-service:latest restart: always depends_on: speckle-server: condition: service_healthy - mem_limit: "1000m" - memswap_limit: "1000m" environment: DEBUG: "preview-service:*" PG_CONNECTION_STRING: "postgres://speckle:speckle@postgres/speckle" webhook-service: - image: speckle/speckle-webhook-service:2 + image: speckle/speckle-webhook-service:latest restart: always depends_on: speckle-server: @@ -135,7 +134,7 @@ services: WAIT_HOSTS: postgres:5432 fileimport-service: - image: speckle/speckle-fileimport-service:2 + image: speckle/speckle-fileimport-service:latest restart: always depends_on: speckle-server: diff --git a/DesktopUI2/DesktopUI2/ConnectorHelpers.cs b/DesktopUI2/DesktopUI2/ConnectorHelpers.cs index 6b83ab1390..2b811cb24d 100644 --- a/DesktopUI2/DesktopUI2/ConnectorHelpers.cs +++ b/DesktopUI2/DesktopUI2/ConnectorHelpers.cs @@ -8,6 +8,7 @@ using DesktopUI2.Views.Controls.StreamEditControls; using Serilog.Events; using Speckle.Core.Api; +using Speckle.Core.Kits; using Speckle.Core.Logging; using Speckle.Core.Models; using Speckle.Core.Transports; @@ -74,8 +75,6 @@ public static async Task ReceiveCommit(Commit commit, StreamState state, P return commitObject; } - - /// Progress cancellation token /// Current Stream card state (does not mutate) /// Requested Commit @@ -91,13 +90,13 @@ public static async Task GetCommitFromState(StreamState state, Cancellat if (state.CommitId == LatestCommitString) //if "latest", always make sure we get the latest commit { var res = await state.Client - .BranchGet(cancellationToken, state.StreamId, state.BranchName, 1) + .BranchGet(state.StreamId, state.BranchName, 1, cancellationToken) .ConfigureAwait(false); commit = res.commits.items.First(); } else { - var res = await state.Client.CommitGet(cancellationToken, state.StreamId, state.CommitId).ConfigureAwait(false); + var res = await state.Client.CommitGet(state.StreamId, state.CommitId, cancellationToken).ConfigureAwait(false); commit = res; } } @@ -118,7 +117,7 @@ public static async Task GetCommitFromState(StreamState state, Cancellat } /// - /// Try catch wrapper around with logging + /// Try catch wrapper around with logging /// public static async Task TryCommitReceived( Client client, @@ -128,7 +127,7 @@ public static async Task TryCommitReceived( { try { - await client.CommitReceived(cancellationToken, commitReceivedInput).ConfigureAwait(false); + await client.CommitReceived(commitReceivedInput, cancellationToken).ConfigureAwait(false); } catch (SpeckleException ex) { @@ -159,9 +158,9 @@ public static async Task TryCommitReceived( //TODO: should this just be how `CommitCreate` id implemented? /// - /// Wrapper around with Error handling. + /// Wrapper around with Error handling. /// - /// + /// /// /// All other exceptions public static async Task CreateCommit( @@ -172,7 +171,7 @@ public static async Task CreateCommit( { try { - var commitId = await client.CommitCreate(cancellationToken, commitInput).ConfigureAwait(false); + var commitId = await client.CommitCreate(commitInput, cancellationToken).ConfigureAwait(false); return commitId; } catch (OperationCanceledException) @@ -199,9 +198,37 @@ public static void DefaultSendErrorHandler(string error, Exception ex) //Treat all operation errors as fatal throw new SpeckleException($"Failed to send objects to server - {error}", ex); } - + + public const string ConversionFailedLogTemplate = "Converter failed to convert object"; + + public static void LogConversionException(Exception ex) + { + LogEventLevel logLevel = ex switch + { + ConversionNotSupportedException => LogEventLevel.Verbose, + ConversionSkippedException => LogEventLevel.Verbose, //Deprecated + ConversionException => LogEventLevel.Warning, + _ => LogEventLevel.Error + }; + + SpeckleLog.Logger.Write(logLevel, ex, ConversionFailedLogTemplate); + } + + public static ApplicationObject.State GetAppObjectFailureState(Exception ex) + { + if (ex is null) + throw new ArgumentNullException(nameof(ex)); + + return ex switch + { + ConversionNotSupportedException => ApplicationObject.State.Skipped, + ConversionSkippedException => ApplicationObject.State.Skipped, //Deprecated + _ => ApplicationObject.State.Failed, + }; + } + #region deprecated members - + [Obsolete("Use overload that has cancellation token last", true)] public static async Task TryCommitReceived( CancellationToken cancellationToken, @@ -211,13 +238,13 @@ CommitReceivedInput commitReceivedInput { await TryCommitReceived(client, commitReceivedInput, cancellationToken).ConfigureAwait(false); } - + [Obsolete("Use overload that has cancellation token last", true)] public static async Task GetCommitFromState(CancellationToken cancellationToken, StreamState state) { return await GetCommitFromState(state, cancellationToken).ConfigureAwait(false); } - + [Obsolete("Use overload that has cancellation token last", true)] public static async Task TryCommitReceived( CancellationToken cancellationToken, @@ -228,7 +255,7 @@ string sourceApplication { await TryCommitReceived(state, commit, sourceApplication, cancellationToken).ConfigureAwait(false); } - + [Obsolete("Use overload that has cancellation token last", true)] public static async Task CreateCommit( CancellationToken cancellationToken, diff --git a/DesktopUI2/DesktopUI2/ViewModels/CommentViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/CommentViewModel.cs index ff05cfc36c..d391c8a24c 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/CommentViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/CommentViewModel.cs @@ -122,8 +122,7 @@ public void OpenComment() var url = $"{_client.Account.serverInfo.url}/streams/{StreamId}/{r0.resourceType}s/{r0.resourceId}?cId={Comment.id}{overlay}"; - var config = ConfigManager.Load(); - if (config.UseFe2) + if (_client.Account.serverInfo.frontend2) url = $"{_client.Account.serverInfo.url}/projects/{StreamId}/"; Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); diff --git a/DesktopUI2/DesktopUI2/ViewModels/DialogViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/DialogViewModel.cs new file mode 100644 index 0000000000..308c04a518 --- /dev/null +++ b/DesktopUI2/DesktopUI2/ViewModels/DialogViewModel.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; +using DesktopUI2.Models; +using ReactiveUI; + +namespace DesktopUI2.ViewModels +{ + public class DialogViewModel : ReactiveObject + { + //UI Binding + public bool UseFe2 + { + get + { + var config = ConfigManager.Load(); + return config.UseFe2; + } + } + } +} diff --git a/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs index 2bf03bcf66..35058323db 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs @@ -181,10 +181,10 @@ private async Task GetStreams() { if (SelectedFilter == Filter.favorite) result = await account.Client - .FavoriteStreamsGet(StreamGetCancelTokenSource.Token, 25) + .FavoriteStreamsGet(25, StreamGetCancelTokenSource.Token) .ConfigureAwait(true); else - result = await account.Client.StreamsGet(StreamGetCancelTokenSource.Token, 25).ConfigureAwait(true); + result = await account.Client.StreamsGet(25, StreamGetCancelTokenSource.Token).ConfigureAwait(true); } //SEARCH else @@ -193,7 +193,7 @@ private async Task GetStreams() if (SelectedFilter == Filter.favorite) SelectedFilter = Filter.all; result = await account.Client - .StreamSearch(StreamGetCancelTokenSource.Token, SearchQuery, 25) + .StreamSearch(SearchQuery, 25, StreamGetCancelTokenSource.Token) .ConfigureAwait(true); } @@ -492,8 +492,6 @@ private void GenerateMenuItems() new MenuItemViewModel(ToggleDarkThemeCommand, "Toggle dark/light theme", MaterialIconKind.SunMoonStars) ); - menu.Items.Add(new MenuItemViewModel(ToggleFe2Command, "Toggle NEW Frontend support", MaterialIconKind.NewBox)); - #if DEBUG menu.Items.Add(new MenuItemViewModel(TestCommand, "Test stuff", MaterialIconKind.Bomb)); #endif @@ -579,7 +577,7 @@ public void ViewOnlineCommand(object parameter) var streamAcc = parameter as StreamAccountWrapper; var url = $"{streamAcc.Account.serverInfo.url.TrimEnd('/')}/streams/{streamAcc.Stream.id}"; - if (UseFe2) + if (streamAcc.Account.serverInfo.frontend2) url = $"{streamAcc.Account.serverInfo.url.TrimEnd('/')}/projects/{streamAcc.Stream.id}"; Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); @@ -940,6 +938,7 @@ public List FilteredStreams public bool HasSavedStreams => SavedStreams != null && SavedStreams.Any(); public bool HasStreams => FilteredStreams != null && FilteredStreams.Any(); + //UI Binding public bool UseFe2 { get diff --git a/DesktopUI2/DesktopUI2/ViewModels/StreamSelectorViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/StreamSelectorViewModel.cs index 39cbb6f6ca..fb5946b9f6 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/StreamSelectorViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/StreamSelectorViewModel.cs @@ -58,11 +58,11 @@ private async Task GetStreams() //NO SEARCH if (string.IsNullOrEmpty(SearchQuery)) - result = await account.Client.StreamsGet(StreamGetCancelTokenSource.Token, 25).ConfigureAwait(true); + result = await account.Client.StreamsGet(25, StreamGetCancelTokenSource.Token).ConfigureAwait(true); //SEARCH else result = await account.Client - .StreamSearch(StreamGetCancelTokenSource.Token, SearchQuery, 25) + .StreamSearch(SearchQuery, 25, StreamGetCancelTokenSource.Token) .ConfigureAwait(true); if (StreamGetCancelTokenSource.IsCancellationRequested) diff --git a/DesktopUI2/DesktopUI2/ViewModels/StreamViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/StreamViewModel.cs index d52a735b38..b300eda3c1 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/StreamViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/StreamViewModel.cs @@ -95,8 +95,7 @@ private string Url { get { - var config = ConfigManager.Load(); - if (config.UseFe2) + if (Client.Account.serverInfo.frontend2) { //sender if (!IsReceiver) @@ -252,11 +251,25 @@ internal async void GetBranchesAndRestoreState() Branches = await Client.StreamGetBranchesWithLimitRetry(Stream.id, 0).ConfigureAwait(true); - var index = Branches.FindIndex(x => x.name == StreamState.BranchName); + //TODO: Core's API calls and the StreamWrapper class need to be updated to properly support FE2 links + //this is a temporary workaround + var index = -1; + if (UseFe2) + { + index = Branches.FindIndex(x => x.id == StreamState.BranchName); + } + else + { + index = Branches.FindIndex(x => x.name == StreamState.BranchName); + } if (index != -1) + { SelectedBranch = BranchesViewModel[index]; + } else + { SelectedBranch = BranchesViewModel[0]; + } //restore selected filter if (StreamState.Filter != null) @@ -593,6 +606,12 @@ public string LastUsed } } + //UI Binding + public bool UseFe2 + { + get { return Client.Account.serverInfo.frontend2; } + } + public DateTime? LastUsedTime { get => StreamState.LastUsed; @@ -603,15 +622,6 @@ public DateTime? LastUsedTime } } - public bool UseFe2 - { - get - { - var config = ConfigManager.Load(); - return config.UseFe2; - } - } - private bool _isRemovingStream; public bool IsRemovingStream @@ -1306,8 +1316,7 @@ public async void SendCommand() OnClick = () => { var url = $"{StreamState.ServerUrl}/streams/{StreamState.StreamId}/commits/{commitId}"; - var config = ConfigManager.Load(); - if (config.UseFe2) + if (Client.Account.serverInfo.frontend2) url = $"{StreamState.ServerUrl}/projects/{StreamState.StreamId}/models/{SelectedBranch.Branch.id}"; OpenUrl(url); }, diff --git a/DesktopUI2/DesktopUI2/Views/Controls/StreamEditControls/Comments.xaml b/DesktopUI2/DesktopUI2/Views/Controls/StreamEditControls/Comments.xaml index fd1eb3f533..791dd95c21 100644 --- a/DesktopUI2/DesktopUI2/Views/Controls/StreamEditControls/Comments.xaml +++ b/DesktopUI2/DesktopUI2/Views/Controls/StreamEditControls/Comments.xaml @@ -18,6 +18,7 @@ + @@ -26,7 +27,7 @@ IsVisible="{Binding !Comments.Count}" Orientation="Vertical" Spacing="20"> - +