From e7450a2e87bb639e2b6d0a94ce2dd0abd9df2c99 Mon Sep 17 00:00:00 2001 From: Alan Rynne Date: Mon, 3 Jun 2024 11:51:23 +0200 Subject: [PATCH 1/9] CNX-9586 Text input not working in Rhino 8 (#3461) * fix: Apply text input fix from #1362 to fix Rhino8 text panel * fix: Add missing braces --- .../ConnectorRhinoShared/UI/DuiPanel.xaml.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/DuiPanel.xaml.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/DuiPanel.xaml.cs index af5a81ee18..0962450834 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/DuiPanel.xaml.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/DuiPanel.xaml.cs @@ -24,7 +24,24 @@ public DuiPanel() var viewModel = new MainViewModel(SpeckleRhinoConnectorPlugin.Instance.Bindings); DataContext = viewModel; AvaloniaHost.Content = new MainUserControl(); + AvaloniaHost.MessageHook += AvaloniaHost_MessageHook; } catch (Exception ex) when (!ex.IsFatal()) { } } + + private const UInt32 DLGC_WANTARROWS = 0x0001; + private const UInt32 DLGC_HASSETSEL = 0x0008; + private const UInt32 DLGC_WANTCHARS = 0x0080; + private const UInt32 WM_GETDLGCODE = 0x0087; + + private IntPtr AvaloniaHost_MessageHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + if (msg != WM_GETDLGCODE) + { + return IntPtr.Zero; + } + + handled = true; + return new IntPtr(DLGC_WANTCHARS | DLGC_WANTARROWS | DLGC_HASSETSEL); + } } From dfb6d2e2154086d6fa47345ea14434434f74a1b9 Mon Sep 17 00:00:00 2001 From: Alan Rynne Date: Mon, 3 Jun 2024 11:59:45 +0200 Subject: [PATCH 2/9] CNX-9443 Rhino Compute units set as mm (#3460) * fix(rhino): Apply preprocessor directives to RHINO7_OR_GREATER not just RHINO7 * fix: Unify getVersionedName + fix initial exception on document open * chore: Flag GetVersionedAppName as obsolete * fix: Remove unnecessary using statement --- .../Extras/Utilities.cs | 15 +++----------- .../ConnectorGrasshopperShared/Loader.cs | 20 ++++++++++++------- .../SchemaBuilder/CreateSchemaObject.cs | 2 +- .../SchemaBuilder/CreateSchemaObjectBase.cs | 2 +- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/Extras/Utilities.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/Extras/Utilities.cs index 101ca74e88..022dcfb1c8 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/Extras/Utilities.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/Extras/Utilities.cs @@ -10,7 +10,6 @@ using Grasshopper.Kernel.Data; using Grasshopper.Kernel.Types; using Microsoft.CSharp.RuntimeBinder; -using Rhino; using Rhino.Display; using Rhino.Geometry; using Speckle.Core.Kits; @@ -27,17 +26,9 @@ public static class Utilities /// Gets the appropriate Grasshopper App name depending on the Version of Rhino currently running. /// /// If running in Rhino >7, Rhino7 will be used as fallback. - /// when Rhino 7 is running, otherwise. - public static string GetVersionedAppName() - { - var version = HostApplications.Grasshopper.GetVersion(HostAppVersion.v6); - if (RhinoApp.Version.Major >= 7) - { - version = HostApplications.Grasshopper.GetVersion(HostAppVersion.v7); - } - - return version; - } + /// when Rhino 7 is running, when Rhino 8 is running, otherwise. + [Obsolete("Use Loader.GetGrasshopperHostAppVersion instead")] + public static string GetVersionedAppName() => Loader.GetGrasshopperHostAppVersion(); public static ISpeckleConverter GetDefaultConverter() { diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/Loader.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/Loader.cs index 2084013717..7a2f00ab71 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/Loader.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/Loader.cs @@ -49,7 +49,8 @@ public override GH_LoadingInstruction PriorityLoad() Setup.Init(GetRhinoHostAppVersion(), HostApplications.Rhino.Slug, logConfig); Instances.CanvasCreated += OnCanvasCreated; -#if RHINO7 + +#if RHINO7_OR_GREATER if (Instances.RunningHeadless) { // If GH is running headless, we listen for document added/removed events. @@ -492,7 +493,7 @@ public static void KeepOpenOnDropdownCheck(ToolStripMenuItem ctl) public static void DisposeHeadlessDoc() { -#if RHINO7 +#if RHINO7_OR_GREATER _headlessDoc?.Dispose(); #endif _headlessDoc = null; @@ -500,7 +501,7 @@ public static void DisposeHeadlessDoc() public static void SetupHeadlessDoc() { -#if RHINO7 +#if RHINO7_OR_GREATER // var templatePath = Path.Combine(Helpers.UserApplicationDataPath, "Speckle", "Templates", // SpeckleGHSettings.HeadlessTemplateFilename); // Console.WriteLine($"Setting up doc. Looking for '{templatePath}'"); @@ -510,7 +511,8 @@ public static void SetupHeadlessDoc() _headlessDoc = RhinoDoc.CreateHeadless(null); Console.WriteLine( - $"Headless run with doc '{_headlessDoc.Name ?? "Untitled"}'\n with template: '{_headlessDoc.TemplateFileUsed ?? "No template"}'\n with units: {_headlessDoc.ModelUnitSystem}"); + $"Speckle - Backup headless doc is ready: '{_headlessDoc.Name ?? "Untitled"}'\n with template: '{_headlessDoc.TemplateFileUsed ?? "No template"}'\n with units: {_headlessDoc.ModelUnitSystem}"); + Console.WriteLine("Speckle - To modify the units in a headless run, you can override the 'RhinoDoc.ActiveDoc' in the '.gh' file using a c#/python script."); #endif } @@ -521,14 +523,18 @@ public static void SetupHeadlessDoc() /// public static RhinoDoc GetCurrentDocument() { -#if RHINO7 - if (Instances.RunningHeadless && RhinoDoc.ActiveDoc == null) +#if RHINO7_OR_GREATER + if (Instances.RunningHeadless && RhinoDoc.ActiveDoc == null && _headlessDoc != null) { + // Running headless, with no ActiveDoc override and _headlessDoc was correctly initialised. + // Only time the _headlessDoc is not set is upon document opening, where the components will + // check for this as their normal initialisation routine, but the document will be refreshed on every solution run. Console.WriteLine( - $"Fetching headless doc '{_headlessDoc.Name ?? "Untitled"}'\n with template: '{_headlessDoc.TemplateFileUsed ?? "No template"}'"); + $"Speckle - Fetching headless doc '{_headlessDoc?.Name ?? "Untitled"}'\n with template: '{_headlessDoc.TemplateFileUsed ?? "No template"}'"); Console.WriteLine(" Model units:" + _headlessDoc.ModelUnitSystem); return _headlessDoc; } + return RhinoDoc.ActiveDoc; #else return RhinoDoc.ActiveDoc; diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/SchemaBuilder/CreateSchemaObject.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/SchemaBuilder/CreateSchemaObject.cs index fc9ecae967..3749d94648 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/SchemaBuilder/CreateSchemaObject.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/SchemaBuilder/CreateSchemaObject.cs @@ -186,7 +186,7 @@ public override void AddedToDocument(GH_Document document) break; } (Params.Output[0] as SpeckleBaseParam).UseSchemaTag = UseSchemaTag; -#if RHINO7 +#if RHINO7_OR_GREATER if (!Instances.RunningHeadless) { (Params.Output[0] as SpeckleBaseParam).ExpirePreview(true); diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/SchemaBuilder/CreateSchemaObjectBase.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/SchemaBuilder/CreateSchemaObjectBase.cs index 83897b862c..3c15340967 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/SchemaBuilder/CreateSchemaObjectBase.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/SchemaBuilder/CreateSchemaObjectBase.cs @@ -243,7 +243,7 @@ public override void AddedToDocument(GH_Document document) } ((SpeckleBaseParam)Params.Output[0]).UseSchemaTag = UseSchemaTag; -#if RHINO7 +#if RHINO7_OR_GREATER if (!Instances.RunningHeadless) { (Params.Output[0] as SpeckleBaseParam).ExpirePreview(true); From a40569e335c0478409d5f343f039184a47e6ec38 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Mon, 3 Jun 2024 04:09:10 -0700 Subject: [PATCH 3/9] CNX-9752 add parts data to pipes and structures (#3457) add parts class and property to pipes and structures --- .../ConverterAutocadCivil.Civil.cs | 30 +++++++++++++------ Objects/Objects/Other/Civil/CivilDataField.cs | 20 +++++++++++++ Objects/Objects/Other/DataField.cs | 27 +++++++++++++++++ 3 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 Objects/Objects/Other/Civil/CivilDataField.cs create mode 100644 Objects/Objects/Other/DataField.cs diff --git a/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.Civil.cs b/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.Civil.cs index 20ede66c9c..a5d50b5efc 100644 --- a/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.Civil.cs +++ b/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.Civil.cs @@ -17,6 +17,7 @@ using Objects.BuiltElements.Civil; using Alignment = Objects.BuiltElements.Alignment; using Arc = Objects.Geometry.Arc; +using CivilDataField = Objects.Other.Civil.CivilDataField; using Polycurve = Objects.Geometry.Polycurve; using Featureline = Objects.BuiltElements.Featureline; using Line = Objects.Geometry.Line; @@ -1015,20 +1016,30 @@ public Structure StructureToSpeckle(CivilDB.Structure structure) }; // assign additional structure props + try { speckleStructure["grate"] = structure.Grate; } catch (Exception ex) when (!ex.IsFatal()) { } + try { speckleStructure["station"] = structure.Station; } catch (Exception ex) when (!ex.IsFatal()) { } + try { speckleStructure["network"] = structure.NetworkName; } catch (Exception ex) when (!ex.IsFatal()) { } AddNameAndDescriptionProperty(structure.Name, structure.Description, speckleStructure); + speckleStructure["partData"] = PartDataRecordToSpeckle(structure.PartData); - try - { - speckleStructure["grate"] = structure.Grate; - speckleStructure["station"] = structure.Station; - speckleStructure["network"] = structure.NetworkName; - } - catch (Exception e) when (!e.IsFatal()) + return speckleStructure; + } + + // part data + /// + /// Converts PartData into a list of DataField + /// + private List PartDataRecordToSpeckle(PartDataRecord partData) + { + List fields = new(); + + foreach (PartDataField partField in partData.GetAllDataFields()) { - // Couldn't set non-essential structure properties + CivilDataField field = new(partField.Name, partField.DataType.ToString(), partField.Units.ToString(), partField.Context.ToString(), partField.Value); + fields.Add(field); } - return speckleStructure; + return fields; } // pipes @@ -1059,6 +1070,7 @@ public Pipe PipeToSpeckle(CivilDB.Pipe pipe) // assign additional pipe props AddNameAndDescriptionProperty(pipe.Name, pipe.Description, specklePipe); + specklePipe["partData"] = PartDataRecordToSpeckle(pipe.PartData); try { specklePipe["shape"] = pipe.CrossSectionalShape.ToString(); } catch(Exception ex) when(!ex.IsFatal()) { } try { specklePipe["slope"] = pipe.Slope; } catch(Exception ex) when(!ex.IsFatal()) { } diff --git a/Objects/Objects/Other/Civil/CivilDataField.cs b/Objects/Objects/Other/Civil/CivilDataField.cs new file mode 100644 index 0000000000..48e1a2ad83 --- /dev/null +++ b/Objects/Objects/Other/Civil/CivilDataField.cs @@ -0,0 +1,20 @@ +namespace Objects.Other.Civil; + +public class CivilDataField : DataField +{ + public CivilDataField() { } + + public CivilDataField(string name, string type, string units, string context, object? value = null) + { + this.name = name; + this.type = type; + this.units = units; + this.context = context; + this.value = value; + } + + /// + /// The context type of the Civil3D part + /// + public string context { get; set; } +} diff --git a/Objects/Objects/Other/DataField.cs b/Objects/Objects/Other/DataField.cs new file mode 100644 index 0000000000..6770b3a8ef --- /dev/null +++ b/Objects/Objects/Other/DataField.cs @@ -0,0 +1,27 @@ +using Speckle.Core.Models; + +namespace Objects.Other; + +/// +/// Generic class for a data field +/// +public class DataField : Base +{ + public DataField() { } + + public DataField(string name, string type, string units, object? value = null) + { + this.name = name; + this.type = type; + this.units = units; + this.value = value; + } + + public string name { get; set; } + + public string type { get; set; } + + public object? value { get; set; } + + public string units { get; set; } +} From 3124cedbc9d0c6616fb57d42860b1eb8f43a4dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20Jedlicska?= <57442769+gjedlicska@users.noreply.github.com> Date: Tue, 4 Jun 2024 11:21:13 +0200 Subject: [PATCH 4/9] gergo/automateSdkExitCodeHotfix (#3465) * fix(automate): if function runs to completion we always need to exit with 0 * chore(automate): reformat --- Automate/Speckle.Automate.Sdk/Runner.cs | 26 ++++++++----------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/Automate/Speckle.Automate.Sdk/Runner.cs b/Automate/Speckle.Automate.Sdk/Runner.cs index 8a0d0b6496..62b9297c72 100644 --- a/Automate/Speckle.Automate.Sdk/Runner.cs +++ b/Automate/Speckle.Automate.Sdk/Runner.cs @@ -97,29 +97,17 @@ public static async Task Main(string[] args, Func public static async Task Main(string[] args, Func automateFunction) where TInput : struct { - int returnCode = 0; // This is the CLI return code, defaults to 0 (Success), change to 1 to flag a failed run. - Argument pathArg = new(name: "Input Path", description: "A file path to retrieve function inputs"); RootCommand rootCommand = new(); rootCommand.AddArgument(pathArg); rootCommand.SetHandler( - async (inputPath) => + async inputPath => { - FunctionRunData? data = FunctionRunDataParser.FromPath(inputPath); - - AutomationContext context = await RunFunction( - automateFunction, - data.AutomationRunData, - data.SpeckleToken, - data.FunctionInputs - ) - .ConfigureAwait(false); + FunctionRunData data = FunctionRunDataParser.FromPath(inputPath); - if (context.RunStatus != AutomationStatusMapping.Get(AutomationStatus.Succeeded)) - { - returnCode = 1; // Flag run as failed. - } + await RunFunction(automateFunction, data.AutomationRunData, data.SpeckleToken, data.FunctionInputs) + .ConfigureAwait(false); }, pathArg ); @@ -135,7 +123,7 @@ public static async Task Main(string[] args, Func Main(string[] args, Func Date: Wed, 5 Jun 2024 09:41:07 +0100 Subject: [PATCH 5/9] WEB-1031 Provide test utils from sdk (#3467) * provide test utils from sdk * lint errors --- .../Test/TestAutomateEnvironment.cs | 79 +++++++++++++++++++ .../Test/TestAutomateUtils.cs | 63 +++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 Automate/Speckle.Automate.Sdk/Test/TestAutomateEnvironment.cs create mode 100644 Automate/Speckle.Automate.Sdk/Test/TestAutomateUtils.cs diff --git a/Automate/Speckle.Automate.Sdk/Test/TestAutomateEnvironment.cs b/Automate/Speckle.Automate.Sdk/Test/TestAutomateEnvironment.cs new file mode 100644 index 0000000000..c5e3b8e080 --- /dev/null +++ b/Automate/Speckle.Automate.Sdk/Test/TestAutomateEnvironment.cs @@ -0,0 +1,79 @@ +using Speckle.Core.Logging; +using System.Text.Json; + +namespace Speckle.Automate.Sdk.Test; + +public class TestAppSettings +{ + public string? SpeckleToken { get; set; } + public string? SpeckleServerUrl { get; set; } + public string? SpeckleProjectId { get; set; } + public string? SpeckleAutomationId { get; set; } +} + +public static class TestAutomateEnvironment +{ + public static TestAppSettings? AppSettings { get; private set; } + + private static string GetEnvironmentVariable(string environmentVariableName) + { + var value = TryGetEnvironmentVariable(environmentVariableName); + + if (value is null) + { + throw new SpeckleException($"Cannot run tests without a {environmentVariableName} environment variable"); + } + + return value; + } + + private static string? TryGetEnvironmentVariable(string environmentVariableName) + { + return Environment.GetEnvironmentVariable(environmentVariableName); + } + + private static TestAppSettings? GetAppSettings() + { + if (AppSettings != null) + { + return AppSettings; + } + + var path = "./appsettings.json"; + var json = File.ReadAllText(path); + + var appSettings = JsonSerializer.Deserialize(json); + + AppSettings = appSettings; + + return AppSettings; + } + + public static string GetSpeckleToken() + { + return GetAppSettings()?.SpeckleToken ?? GetEnvironmentVariable("SPECKLE_TOKEN"); + } + + public static Uri GetSpeckleServerUrl() + { + var urlString = + GetAppSettings()?.SpeckleServerUrl ?? TryGetEnvironmentVariable("SPECKLE_SERVER_URL") ?? "http://127.0.0.1:3000"; + + return new Uri(urlString); + } + + public static string GetSpeckleProjectId() + { + return GetAppSettings()?.SpeckleProjectId ?? GetEnvironmentVariable("SPECKLE_PROJECT_ID"); + } + + public static string GetSpeckleAutomationId() + { + return GetAppSettings()?.SpeckleAutomationId ?? GetEnvironmentVariable("SPECKLE_AUTOMATION_ID"); + } + + public static void Clear() + { + AppSettings = null; + } +} diff --git a/Automate/Speckle.Automate.Sdk/Test/TestAutomateUtils.cs b/Automate/Speckle.Automate.Sdk/Test/TestAutomateUtils.cs new file mode 100644 index 0000000000..f28596bc88 --- /dev/null +++ b/Automate/Speckle.Automate.Sdk/Test/TestAutomateUtils.cs @@ -0,0 +1,63 @@ +using GraphQL; +using Speckle.Automate.Sdk.Schema; +using Speckle.Automate.Sdk.Schema.Triggers; +using Speckle.Core.Api; + +namespace Speckle.Automate.Sdk.Test; + +public static class TestAutomateUtils +{ + public static async Task CreateTestRun(Client speckleClient) + { + GraphQLRequest query = + new( + query: """ + mutation Mutation($projectId: ID!, $automationId: ID!) { + projectMutations { + automationMutations(projectId: $projectId) { + createTestAutomationRun(automationId: $automationId) { + automationRunId + functionRunId + triggers { + payload { + modelId + versionId + } + triggerType + } + } + } + } + } + """, + variables: new + { + automationId = TestAutomateEnvironment.GetSpeckleAutomationId(), + projectId = TestAutomateEnvironment.GetSpeckleProjectId() + } + ); + + dynamic res = await speckleClient.ExecuteGraphQLRequest(query).ConfigureAwait(false); + + var runData = res["projectMutations"]["automationMutations"]["createTestAutomationRun"]; + var triggerData = runData["triggers"][0]["payload"]; + + string modelId = triggerData["modelId"]; + string versionId = triggerData["versionId"]; + + var data = new AutomationRunData() + { + ProjectId = TestAutomateEnvironment.GetSpeckleProjectId(), + SpeckleServerUrl = TestAutomateEnvironment.GetSpeckleServerUrl().ToString(), + AutomationId = TestAutomateEnvironment.GetSpeckleAutomationId(), + AutomationRunId = runData["automationRunId"], + FunctionRunId = runData["functionRunId"], + Triggers = new List() + { + new VersionCreationTrigger(modelId: modelId, versionId: versionId) + } + }; + + return data; + } +} From f29aaebd064fc449c5bcb040429130ac57a08e46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsef=20L=2E=20Kiss?= <50739844+jozseflkiss@users.noreply.github.com> Date: Wed, 5 Jun 2024 10:54:07 +0200 Subject: [PATCH 6/9] feat(Archicad): Add default commit message (#3473) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add default commit message Co-authored-by: József L. Kiss <> --- ConnectorArchicad/ConnectorArchicad/ConnectorBinding.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ConnectorArchicad/ConnectorArchicad/ConnectorBinding.cs b/ConnectorArchicad/ConnectorArchicad/ConnectorBinding.cs index 8a34225426..650f5c2000 100644 --- a/ConnectorArchicad/ConnectorArchicad/ConnectorBinding.cs +++ b/ConnectorArchicad/ConnectorArchicad/ConnectorBinding.cs @@ -220,7 +220,8 @@ public override async Task SendStream(StreamState state, ProgressViewMod return await Speckle.Core.Api.Helpers.Send( IdentifyStream(state), commitObject, - state.CommitMessage, + state.CommitMessage + ?? $"Sent {progress.Report.ReportObjects.Count} objects from {HostApplications.Archicad.Name + " " + archicadVersion}.", HostApplications.Archicad.Name ); } From f4544216d8a7bb60d602902faa4cc3a0ef79b2a9 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Thu, 6 Jun 2024 09:09:25 +0100 Subject: [PATCH 7/9] Copy important changes to core from DUI3/alpha to main (#3478) Copy important changes to core from DUI3/alpha to dev --- Core/Core/Credentials/Account.cs | 9 +- Core/Core/Credentials/AccountManager.cs | 13 +- Core/Core/Models/Extensions/BaseExtensions.cs | 69 ++++++++- .../Models/GraphTraversal/DefaultTraversal.cs | 133 +++++++++++------- .../Models/GraphTraversal/GraphTraversal.cs | 7 +- .../Models/GraphTraversal/ITraversalRule.cs | 26 ++-- .../Core/Models/GraphTraversal/RuleBuilder.cs | 32 ++++- .../TraversalContextExtensions.cs | 47 +++++++ 8 files changed, 258 insertions(+), 78 deletions(-) create mode 100644 Core/Core/Models/GraphTraversal/TraversalContextExtensions.cs diff --git a/Core/Core/Credentials/Account.cs b/Core/Core/Credentials/Account.cs index 0daba40f44..04d33e7693 100644 --- a/Core/Core/Credentials/Account.cs +++ b/Core/Core/Credentials/Account.cs @@ -3,14 +3,17 @@ using System.Threading.Tasks; using Speckle.Core.Api; using Speckle.Core.Helpers; -using Speckle.Core.Logging; namespace Speckle.Core.Credentials; public class Account : IEquatable { - private string _id { get; set; } + private string _id; + /// + /// The account id is unique to user and server url. + /// + /// Account object invalid: missing required info public string id { get @@ -19,7 +22,7 @@ public string id { if (serverInfo == null || userInfo == null) { - throw new SpeckleException("Incomplete account info: cannot generate id."); + throw new InvalidOperationException("Incomplete account info: cannot generate id."); } _id = Crypt.Md5(userInfo.email + serverInfo.url, "X2"); diff --git a/Core/Core/Credentials/AccountManager.cs b/Core/Core/Credentials/AccountManager.cs index b304aaa573..0f9138825f 100644 --- a/Core/Core/Credentials/AccountManager.cs +++ b/Core/Core/Credentials/AccountManager.cs @@ -234,15 +234,22 @@ public static string GetDefaultServerUrl() return serverUrl; } + /// The Id of the account to fetch + /// + /// Account with was not found + public static Account GetAccount(string id) + { + return GetAccounts().FirstOrDefault(acc => acc.id == id) + ?? throw new SpeckleAccountManagerException($"Account {id} not found"); + } + /// /// Upgrades an account from the account.serverInfo.movedFrom account to the account.serverInfo.movedTo account /// /// Id of the account to upgrade public static async Task UpgradeAccount(string id) { - var account = - GetAccounts().FirstOrDefault(acc => acc.id == id) - ?? throw new SpeckleAccountManagerException($"Account {id} not found"); + Account account = GetAccount(id); if (account.serverInfo.migration.movedTo is not Uri upgradeUri) { diff --git a/Core/Core/Models/Extensions/BaseExtensions.cs b/Core/Core/Models/Extensions/BaseExtensions.cs index 1eb00ef442..d919d120c5 100644 --- a/Core/Core/Models/Extensions/BaseExtensions.cs +++ b/Core/Core/Models/Extensions/BaseExtensions.cs @@ -193,19 +193,19 @@ public static bool IsDisplayableObject(this Base speckleObject) return speckleObject.TryGetDisplayValue() != null; } - public static IEnumerable? TryGetDisplayValue(this Base obj) + public static IReadOnlyList? TryGetDisplayValue(this Base obj) where T : Base { var rawDisplayValue = obj["displayValue"] ?? obj["@displayValue"]; return rawDisplayValue switch { T b => new List { b }, - IEnumerable enumerable => enumerable.OfType(), + IReadOnlyList list => list, _ => null }; } - public static IEnumerable? TryGetDisplayValue(this Base obj) + public static IReadOnlyList? TryGetDisplayValue(this Base obj) { return TryGetDisplayValue(obj); } @@ -226,4 +226,67 @@ public static bool IsDisplayableObject(this Base speckleObject) { return TryGetParameters(obj); } + + /// + /// A variation of the OG Traversal extension from Alan, but with tracking the object path as well. + /// + /// Delegate condition to stop traverse. + /// List of base objects with their collection path. + public static IEnumerable<(string[], Base)> TraverseWithPath(this Base root, BaseRecursionBreaker recursionBreaker) + { + var stack = new Stack<(List, Base)>(); + stack.Push((new List(), root)); + + while (stack.Count > 0) + { + (List path, Base current) = stack.Pop(); + yield return (path.ToArray(), current); + + if (recursionBreaker(current)) + { + continue; + } + + foreach (string child in current.GetDynamicMemberNames()) + { + // NOTE: we can store collections rather than just path names. Where we have an actual collection, use that, where not, create a mock one based on the prop name + var localPathFragment = child; + if (current is Collection { name: { } } c) + { + localPathFragment = c.name; + } + + var newPath = new List(path) { localPathFragment }; + switch (current[child]) + { + case Base o: + stack.Push((newPath, o)); + break; + case IDictionary dictionary: + { + foreach (object obj in dictionary.Keys) + { + if (obj is Base b) + { + stack.Push((newPath, b)); + } + } + + break; + } + case IList collection: + { + foreach (object obj in collection) + { + if (obj is Base b) + { + stack.Push((newPath, b)); + } + } + break; + } + } + } + } + } } diff --git a/Core/Core/Models/GraphTraversal/DefaultTraversal.cs b/Core/Core/Models/GraphTraversal/DefaultTraversal.cs index 2b0749e41d..885b031df3 100644 --- a/Core/Core/Models/GraphTraversal/DefaultTraversal.cs +++ b/Core/Core/Models/GraphTraversal/DefaultTraversal.cs @@ -14,59 +14,15 @@ namespace Speckle.Core.Models.GraphTraversal; )] public static class DefaultTraversal { - /// - /// Default traversal rule that ideally should be used by all connectors - /// - /// - /// Treats convertable objects and objects with displayValues as "convertable" such that only elements and dynamic props will be traversed - /// - /// - /// - public static GraphTraversal CreateTraverseFunc(ISpeckleConverter converter) + public static GraphTraversal CreateTraversalFunc() { var convertableRule = TraversalRule .NewTraversalRule() - .When(converter.CanConvertToNative) + .When(b => b.GetType() != typeof(Base)) .When(HasDisplayValue) .ContinueTraversing(_ => ElementsPropAliases); - return new GraphTraversal(convertableRule, s_ignoreResultsRule, DefaultRule); - } - - /// - /// Traverses until finds a convertable object then HALTS deeper traversal - /// - /// - /// Current Revit connector does traversal, - /// so this traversal is a shallow traversal for directly convertable objects, - /// and a deep traversal for all other types - /// - /// - /// - public static GraphTraversal CreateRevitTraversalFunc(ISpeckleConverter converter) - { - var convertableRule = TraversalRule - .NewTraversalRule() - .When(converter.CanConvertToNative) - .When(HasDisplayValue) - .ContinueTraversing(None); - - return new GraphTraversal(convertableRule, s_ignoreResultsRule, DefaultRule); - } - - /// - /// Traverses until finds a convertable object (or fallback) then traverses members - /// - /// - /// - public static GraphTraversal CreateBIMTraverseFunc(ISpeckleConverter converter) - { - var bimElementRule = TraversalRule - .NewTraversalRule() - .When(converter.CanConvertToNative) - .ContinueTraversing(ElementsAliases); - - return new GraphTraversal(bimElementRule, s_ignoreResultsRule, DefaultRule); + return new GraphTraversal(convertableRule, s_ignoreResultsRule, DefaultRule.ShouldReturnToOutput(false)); } //These functions are just meant to make the syntax of defining rules less verbose, they are likely to change frequently/be restructured @@ -78,10 +34,8 @@ public static GraphTraversal CreateBIMTraverseFunc(ISpeckleConverter converter) .When(o => o.speckle_type.Contains("Objects.Structural.Results")) .ContinueTraversing(None); - public static readonly ITraversalRule DefaultRule = TraversalRule - .NewTraversalRule() - .When(_ => true) - .ContinueTraversing(Members()); + public static ITraversalBuilderReturn DefaultRule => + TraversalRule.NewTraversalRule().When(_ => true).ContinueTraversing(Members()); public static readonly IReadOnlyList ElementsPropAliases = new[] { "elements", "@elements" }; @@ -158,6 +112,8 @@ public static IEnumerable DisplayValueAndElementsAliases(Base _) #endregion + #region Legacy function varients + [Obsolete("Renamed to " + nameof(ElementsPropAliases))] [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Obsolete")] public static IReadOnlyList elementsPropAliases => ElementsPropAliases; @@ -178,4 +134,79 @@ public static IEnumerable DisplayValueAndElementsAliases(Base _) [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Obsolete")] [SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "Obsolete")] public static string[] displayValueAndElementsPropAliases => DisplayValueAndElementsPropAliases; + + /// + /// + /// + /// + [Obsolete($"Consider using {nameof(CreateTraversalFunc)}")] + public static GraphTraversal CreateTraverseFunc(ISpeckleConverter converter) + { + return CreateLegacyTraverseFunc(converter.CanConvertToNative); + } + + /// + /// Legacy traversal rule that was dependent on the converter + /// + /// + /// Treats convertable objects and objects with displayValues as "convertable" such that only elements and dynamic props will be traversed + /// New code should use instead. + /// + /// + /// + [Obsolete($"Consider using {nameof(CreateTraversalFunc)}")] + public static GraphTraversal CreateLegacyTraverseFunc(Func canConvertToNative) + { + var convertableRule = TraversalRule + .NewTraversalRule() + .When(b => canConvertToNative(b)) + .When(HasDisplayValue) + .ContinueTraversing(_ => ElementsPropAliases); + + return new GraphTraversal(convertableRule, s_ignoreResultsRule, DefaultRule); + } + + /// + /// Traverses until finds a convertable object then HALTS deeper traversal + /// + /// + /// The DUI2 Revit connector does traversal, + /// so this traversal is a shallow traversal for directly convertable objects, + /// and a deep traversal for all other types + /// New code should use instead. + /// + /// + /// + [Obsolete($"Consider using {nameof(CreateTraversalFunc)}")] + public static GraphTraversal CreateRevitTraversalFunc(ISpeckleConverter converter) + { + var convertableRule = TraversalRule + .NewTraversalRule() + .When(converter.CanConvertToNative) + .When(HasDisplayValue) + .ContinueTraversing(None); + + return new GraphTraversal(convertableRule, s_ignoreResultsRule, DefaultRule); + } + + /// + /// Traverses until finds a convertable object (or fallback) then traverses members + /// + /// + /// New code should use instead. + /// + /// + /// + [Obsolete($"Consider using {nameof(CreateTraversalFunc)}")] + public static GraphTraversal CreateBIMTraverseFunc(ISpeckleConverter converter) + { + var bimElementRule = TraversalRule + .NewTraversalRule() + .When(converter.CanConvertToNative) + .ContinueTraversing(ElementsAliases); + + return new GraphTraversal(bimElementRule, s_ignoreResultsRule, DefaultRule); + } + + #endregion } diff --git a/Core/Core/Models/GraphTraversal/GraphTraversal.cs b/Core/Core/Models/GraphTraversal/GraphTraversal.cs index f2fa1f5bb6..c397d292f6 100644 --- a/Core/Core/Models/GraphTraversal/GraphTraversal.cs +++ b/Core/Core/Models/GraphTraversal/GraphTraversal.cs @@ -42,11 +42,14 @@ public IEnumerable Traverse(Base root) T head = stack[headIndex]; stack.RemoveAt(headIndex); - yield return head; - Base current = head.Current; var activeRule = GetActiveRuleOrDefault(current); + if (activeRule.ShouldReturn) + { + yield return head; + } + foreach (string childProp in activeRule.MembersToTraverse(current)) { TraverseMemberToStack(stack, current[childProp], childProp, head); diff --git a/Core/Core/Models/GraphTraversal/ITraversalRule.cs b/Core/Core/Models/GraphTraversal/ITraversalRule.cs index 1695a2189b..2507271758 100644 --- a/Core/Core/Models/GraphTraversal/ITraversalRule.cs +++ b/Core/Core/Models/GraphTraversal/ITraversalRule.cs @@ -19,12 +19,24 @@ public interface ITraversalRule /// /// public bool DoesRuleHold(Base o); + + /// + /// When , + /// objects for which this rule applies, + /// will be filtered out from the traversal output + /// (but still traversed normally, as per the ) + /// + /// + /// This property was added to allow for easier filtering of the return of . + /// Without the option to set some rules as false, it was necessary to duplicate part of the rules in a + /// + public bool ShouldReturn { get; } } /// /// The "traverse none" rule that always holds true /// -public sealed class DefaultRule : ITraversalRule +internal sealed class DefaultRule : ITraversalRule { private static DefaultRule? s_instance; @@ -32,13 +44,9 @@ private DefaultRule() { } public static DefaultRule Instance => s_instance ??= new DefaultRule(); - public IEnumerable MembersToTraverse(Base b) - { - return Enumerable.Empty(); - } + public IEnumerable MembersToTraverse(Base b) => Enumerable.Empty(); + + public bool DoesRuleHold(Base o) => true; - public bool DoesRuleHold(Base o) - { - return true; - } + public bool ShouldReturn => true; } diff --git a/Core/Core/Models/GraphTraversal/RuleBuilder.cs b/Core/Core/Models/GraphTraversal/RuleBuilder.cs index c44dfc798f..2482464a62 100644 --- a/Core/Core/Models/GraphTraversal/RuleBuilder.cs +++ b/Core/Core/Models/GraphTraversal/RuleBuilder.cs @@ -8,22 +8,29 @@ namespace Speckle.Core.Models.GraphTraversal; /// Specifies what members to traverse if any provided are met. /// /// Follows the builder pattern to ensure that a rule is complete before usable, see usages -public sealed class TraversalRule : ITraversalRule, ITraversalBuilderTraverse +public sealed class TraversalRule : ITraversalBuilderReturn, ITraversalBuilderTraverse { private readonly List _conditions; - private SelectMembers _membersToTraverse; + private SelectMembers? _membersToTraverse; + public bool ShouldReturn { get; private set; } = true; private TraversalRule() { _conditions = new List(); } - public ITraversalRule ContinueTraversing(SelectMembers membersToTraverse) + public ITraversalBuilderReturn ContinueTraversing(SelectMembers membersToTraverse) { this._membersToTraverse = membersToTraverse; return this; } + public ITraversalRule ShouldReturnToOutput(bool shouldReturn = true) + { + ShouldReturn = shouldReturn; + return this; + } + public ITraversalBuilderTraverse When(WhenCondition condition) { _conditions.Add(condition); @@ -45,7 +52,7 @@ bool ITraversalRule.DoesRuleHold(Base o) IEnumerable ITraversalRule.MembersToTraverse(Base o) { - return _membersToTraverse(o).Distinct(); //TODO distinct is expensive, there may be a better way for us to avoid duplicates + return _membersToTraverse!(o).Distinct(); //TODO distinct is expensive, there may be a better way for us to avoid duplicates } /// a new Traversal Rule to be initialised using the Builder Pattern interfaces @@ -58,7 +65,7 @@ public static ITraversalBuilderWhen NewTraversalRule() public delegate bool WhenCondition(Base o); /// -/// Interface for traversal rule in a building (unusable) state +/// Builder Pattern Interface for a traversal rule in a partially built (unusable state) /// public interface ITraversalBuilderWhen { @@ -76,12 +83,23 @@ public interface ITraversalBuilderWhen public delegate IEnumerable SelectMembers(Base o); /// -/// Interface for traversal rule in a building (unusable) state +/// Builder Pattern Interface for a traversal rule in a partially built (unusable state) /// public interface ITraversalBuilderTraverse : ITraversalBuilderWhen { /// /// Function returning the members that should be traversed for objects where this rule holds /// Traversal rule in a usable state - ITraversalRule ContinueTraversing(SelectMembers membersToTraverse); + ITraversalBuilderReturn ContinueTraversing(SelectMembers membersToTraverse); +} + +/// +/// Builder Pattern Interface for a traversal rule in a usable state, with an (optional) final step to set the value of +/// +public interface ITraversalBuilderReturn : ITraversalRule +{ + /// + /// value to set + /// Traversal rule in a usable state + ITraversalRule ShouldReturnToOutput(bool shouldReturn = true); } diff --git a/Core/Core/Models/GraphTraversal/TraversalContextExtensions.cs b/Core/Core/Models/GraphTraversal/TraversalContextExtensions.cs new file mode 100644 index 0000000000..b987cb1b45 --- /dev/null +++ b/Core/Core/Models/GraphTraversal/TraversalContextExtensions.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; + +namespace Speckle.Core.Models.GraphTraversal; + +public static class TraversalContextExtensions +{ + /// + /// Walks up the tree, returning values, starting with , + /// walking up nodes + /// + /// + /// + public static IEnumerable GetPropertyPath(this TraversalContext context) + { + TraversalContext? head = context; + do + { + if (head?.PropName == null) + { + break; + } + yield return head.PropName; + + head = head.Parent; + } while (true); + } + + /// + /// Walks up the tree, returning all typed ascendant, starting the closest , + /// walking up nodes + /// + /// + /// + public static IEnumerable GetAscendantOfType(this TraversalContext context) + where T : Base + { + TraversalContext? head = context; + do + { + if (head.Current is T c) + { + yield return c; + } + head = head.Parent; + } while (head != null); + } +} From 504a606719c28d8ede688cf71224aa6620480e13 Mon Sep 17 00:00:00 2001 From: Alan Rynne Date: Thu, 6 Jun 2024 11:42:06 +0200 Subject: [PATCH 8/9] CNX-9295 Speckle Connector doesn't initialize on Mac (#3480) * fix: Rhino Mac initialisation + pluginType flag in csproj * fix: Revert unnecessary modifications --- .../ConnectorRhino/ConnectorRhinoShared/Entry/Plugin.cs | 2 +- ConnectorRhino/ConnectorRhino7/ConnectorRhino7.csproj | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/Entry/Plugin.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/Entry/Plugin.cs index 067a8e371e..5e3537ea51 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/Entry/Plugin.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/Entry/Plugin.cs @@ -184,7 +184,7 @@ protected override LoadReturnCode OnLoad(ref string errorMessage) var hostAppName = HostApplications.Rhino.Slug; var hostAppVersion = Utils.GetRhinoHostAppVersion(); - SpeckleLog.Initialize(HostApplications.Rhino.Slug, Utils.GetRhinoHostAppVersion()); + SpeckleLog.Initialize(HostApplications.Rhino.Slug, Utils.GetRhinoHostAppVersion(), logConfig); SpeckleLog.Logger.Information( "Loading Speckle Plugin for host app {hostAppName} version {hostAppVersion}", hostAppName, diff --git a/ConnectorRhino/ConnectorRhino7/ConnectorRhino7.csproj b/ConnectorRhino/ConnectorRhino7/ConnectorRhino7.csproj index 7746ae3948..39ec594c2e 100644 --- a/ConnectorRhino/ConnectorRhino7/ConnectorRhino7.csproj +++ b/ConnectorRhino/ConnectorRhino7/ConnectorRhino7.csproj @@ -25,6 +25,7 @@ true $(DefineConstants);RHINO7;RHINO6_OR_GREATER;RHINO7_OR_GREATER Library + rhp From c92da2d771b822c4b17f05231c62b5d89b1d0330 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Thu, 6 Jun 2024 06:01:24 -0700 Subject: [PATCH 9/9] CNX-9760 [Rhino]: add gis attributes as user strings (#3477) * adds gis attributes as user strings * adds custom conversion for GIS elements * adds generic gis attribute case for fallback conversion * Update ConnectorBindingsRhino.Receive.cs * add GisFeature class * adds gisfeature direct conversion and refactors attributes creation * Update ConverterRhinoGh.GIS.cs * updates gisfeature conversion * Update ConnectorBindingsRhino.Previews.cs * Update ConnectorBindingsRhino.Receive.cs --------- Co-authored-by: KatKatKateryna Co-authored-by: Alan Rynne --- .../UI/ConnectorBindingsRhino.Previews.cs | 4 +- .../UI/ConnectorBindingsRhino.Receive.cs | 138 +++++++++++----- .../ConverterRhinoGh.GIS.cs | 151 ++++++++++++++++++ .../ConverterRhinoGh.cs | 11 ++ .../ConverterRhinoGhShared.projitems | 1 + Objects/Objects/GIS/GisFeature.cs | 43 +++++ 6 files changed, 310 insertions(+), 38 deletions(-) create mode 100644 Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.GIS.cs create mode 100644 Objects/Objects/GIS/GisFeature.cs diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Previews.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Previews.cs index 00e1bb4df1..cd551584f8 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Previews.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Previews.cs @@ -100,7 +100,9 @@ private static bool IsPreviewIgnore(Base @object) || @object.speckle_type.Contains("View") || @object.speckle_type.Contains("Level") || @object.speckle_type.Contains("GridLine") - || @object.speckle_type.Contains("Collection"); + || @object.speckle_type.Contains("Collection") + || @object.speckle_type.Contains("PolygonElement") + || @object.speckle_type.Contains("GisFeature"); } public override async Task PreviewReceive(StreamState state, ProgressViewModel progress) diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs index e85475969d..944d35a57c 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs @@ -415,8 +415,8 @@ ApplicationObject NewAppObj() return null; } - // get parameters - var parameters = current["parameters"] as Base; + // get parameters and attributes from revit/gis + var parameters = current["parameters"] as Base ?? current["attributes"] as Base; //Handle convertable objects if (converter.CanConvertToNative(current)) @@ -569,29 +569,15 @@ private void BakeObject( continue; } - var attributes = new ObjectAttributes(); - // handle display style - Base display = obj["displayStyle"] as Base ?? obj["@displayStyle"] as Base; - Base render = obj["renderMaterial"] as Base ?? obj["@renderMaterial"] as Base; - if (display != null) - { - var convertedDisplay = converter.ConvertToNative(display) as ObjectAttributes; - if (convertedDisplay is not null) - { - attributes = convertedDisplay; - } - } - else if (render != null) - { - attributes.ColorSource = ObjectColorSource.ColorFromMaterial; - } - - // assign layer - attributes.LayerIndex = layer.Index; - - // handle user info, application id, revit parameters - SetUserInfo(obj, attributes, converter, parent); + // create attributes with layer, display and render, user info, application id, revit/gis parameters + ObjectAttributes attributes = CreateAttributesFromObject( + obj, + layer.Index, + converter, + out RenderMaterial renderMaterial, + parent + ); Guid id = Doc.Objects.Add(o, attributes); if (id == Guid.Empty) @@ -621,22 +607,26 @@ private void BakeObject( bakedCount++; // handle render material - if (render != null) + if (renderMaterial is not null) { - var convertedMaterial = converter.ConvertToNative(render) as RenderMaterial; - if (convertedMaterial is not null) - { - RhinoObject rhinoObject = Doc.Objects.FindId(id); - rhinoObject.RenderMaterial = convertedMaterial; - rhinoObject.CommitChanges(); - } + RhinoObject rhinoObject = Doc.Objects.FindId(id); + rhinoObject.RenderMaterial = renderMaterial; + rhinoObject.CommitChanges(); } break; case RhinoObject o: // this was prbly a block instance, baked during conversion - o.Attributes.LayerIndex = layer.Index; // assign layer - SetUserInfo(obj, o.Attributes, converter, parent); // handle user info, including application id + // create attributes with layer, display and render, user info, application id, revit/gis parameters + ObjectAttributes objectAttributes = CreateAttributesFromObject( + obj, + layer.Index, + converter, + out RenderMaterial objectRenderMaterial, + parent + ); + + o.Attributes = objectAttributes; o.CommitChanges(); if (parent != null) { @@ -650,6 +640,42 @@ private void BakeObject( bakedCount++; break; + case Group o: // this is a GIS object + // create attributes with layer, display and render, user info, application id, revit/gis parameters + ObjectAttributes groupAttributes = CreateAttributesFromObject( + obj, + layer.Index, + converter, + out RenderMaterial groupRenderMaterial, + parent + ); + + groupAttributes.AddToGroup(o.Index); + + foreach (RhinoObject groupObject in Doc.Objects.FindByGroup(o.Index)) + { + groupObject.Attributes = groupAttributes; + + // handle render material + if (groupRenderMaterial is not null) + { + groupObject.RenderMaterial = groupRenderMaterial; + } + + groupObject.CommitChanges(); + } + + if (parent != null) + { + parent.Update(o.Id.ToString()); + } + else + { + appObj.Update(o.Id.ToString()); + } + bakedCount++; + break; + case ViewInfo o: // this is a view, baked during conversion appObj.Update(o.Name); bakedCount++; @@ -675,13 +701,36 @@ private void BakeObject( } } - private void SetUserInfo( + private ObjectAttributes CreateAttributesFromObject( Base obj, - ObjectAttributes attributes, + int layerIndex, ISpeckleConverter converter, + out RenderMaterial renderMaterial, ApplicationObject parent = null ) { + ObjectAttributes attributes = new(); + renderMaterial = null; + + // handle display style + Base display = obj["displayStyle"] as Base ?? obj["@displayStyle"] as Base; + Base render = obj["renderMaterial"] as Base ?? obj["@renderMaterial"] as Base; + if (display != null) + { + var convertedDisplay = converter.ConvertToNative(display) as ObjectAttributes; + if (convertedDisplay is not null) + { + attributes = convertedDisplay; + } + } + else if (render != null) + { + attributes.ColorSource = ObjectColorSource.ColorFromMaterial; + } + + // assign layer + attributes.LayerIndex = layerIndex; + // set user strings if (obj[UserStrings] is Base userStrings) { @@ -708,12 +757,13 @@ private void SetUserInfo( attributes.Name = name; } - // set revit parameters as user strings + // set revit/gis parameters as user strings var paramId = parent != null ? parent.OriginalId : obj.id; if (StoredObjectParams.TryGetValue(paramId, out Base parameters)) { foreach (var member in parameters.GetMembers(DynamicBaseMemberType.Dynamic)) { + // parameters coming from revit, value Base is Objects.BuiltElements.Revit.Parameter if (member.Value is Base parameter) { var convertedParameter = converter.ConvertToNative(parameter) as Tuple; @@ -723,8 +773,22 @@ private void SetUserInfo( attributes.SetUserString(paramName, convertedParameter.Item2); } } + // attributes coming from GIS + else + { + string userStringValue = member.Value is object value ? value.ToString() : string.Empty; + attributes.SetUserString(member.Key, userStringValue); + } } } + + // render material + if (render is not null) + { + renderMaterial = converter.ConvertToNative(render) as RenderMaterial; + } + + return attributes; } // Clears the stored objects, params, and preview objects diff --git a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.GIS.cs b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.GIS.cs new file mode 100644 index 0000000000..cc766f2784 --- /dev/null +++ b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.GIS.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using Objects.GIS; +using Speckle.Core.Models; +using RH = Rhino.DocObjects; +using System.Linq; +using Rhino.Geometry; + +namespace Objects.Converter.RhinoGh; + +public partial class ConverterRhinoGh +{ + // polygon element + // NOTE: class no longer in use? from 2.19 + public ApplicationObject PolygonElementToNative(PolygonElement poly) + { + var appObj = new ApplicationObject(poly.id, poly.speckle_type) { applicationId = poly.applicationId }; + + // get the group name + var commitInfo = GetCommitInfo(); + string groupName = $"{commitInfo} - " + poly.id; + if (Doc.Groups.FindName(groupName) is RH.Group existingGroup) + { + Doc.Groups.Delete(existingGroup); + } + + List addedGeometry = new(); + foreach (object geo in poly.geometry) + { + if (geo is Base geoBase) + { + var display = geoBase["displayValue"] as List ?? geoBase["@displayValue"] as List; + if (display is null) + { + continue; + } + + foreach (object displayObject in display) + { + if (displayObject is Base baseObj) + { + if (ConvertToNative(baseObj) is GeometryBase convertedObject) + { + Guid id = Doc.Objects.Add(convertedObject); + if (id != Guid.Empty) + { + addedGeometry.Add(id); + } + } + } + } + } + } + + if (addedGeometry.Count == 0) + { + appObj.Update(status: ApplicationObject.State.Failed, logItem: "No objects were created for group"); + return appObj; + } + + int groupIndex = Doc.Groups.Add(groupName, addedGeometry); + if (groupIndex == -1) + { + appObj.Update(status: ApplicationObject.State.Failed, logItem: "Could not add group to doc"); + return appObj; + } + + RH.Group convertedGroup = Doc.Groups.FindIndex(groupIndex); + + // update appobj + appObj.Update(convertedItem: convertedGroup, createdIds: addedGeometry.Select(o => o.ToString()).ToList()); + + return appObj; + } + + // gis feature + public ApplicationObject GisFeatureToNative(GisFeature feature) + { + var appObj = new ApplicationObject(feature.id, feature.speckle_type) { applicationId = feature.applicationId }; + + // get the group name + var commitInfo = GetCommitInfo(); + string groupName = $"{commitInfo} - " + feature.id; + if (Doc.Groups.FindName(groupName) is RH.Group existingGroup) + { + Doc.Groups.Delete(existingGroup); + } + + // for gis features, we are assuming that the `displayValue prop` should be converted first + // if there are no objects in `displayValue`, then we will fall back to check for convertible objects in `geometries` + List convertedObjects = new(); + if (feature.displayValue is List displayValue && displayValue.Count > 0) + { + foreach (Base displayObj in displayValue) + { + if (ConvertToNative(displayObj) is GeometryBase convertedObject) + { + convertedObjects.Add(convertedObject); + } + } + } + else if (feature.geometry is List geometries && geometries.Count > 0) + { + foreach (Base displayObj in geometries) + { + if (ConvertToNative(displayObj) is GeometryBase convertedObject) + { + convertedObjects.Add(convertedObject); + } + } + } + else + { + appObj.Update( + status: ApplicationObject.State.Failed, + logItem: "No objects in displayValue or geometries was found" + ); + return appObj; + } + + List addedGeometry = new(); + foreach (GeometryBase convertedObject in convertedObjects) + { + Guid id = Doc.Objects.Add(convertedObject); + if (id != Guid.Empty) + { + addedGeometry.Add(id); + } + } + + if (addedGeometry.Count == 0) + { + appObj.Update(status: ApplicationObject.State.Failed, logItem: "No objects were created for group"); + return appObj; + } + + int groupIndex = Doc.Groups.Add(groupName, addedGeometry); + if (groupIndex == -1) + { + appObj.Update(status: ApplicationObject.State.Failed, logItem: "Could not add group to doc"); + return appObj; + } + + RH.Group convertedGroup = Doc.Groups.FindIndex(groupIndex); + + // update appobj + appObj.Update(convertedItem: convertedGroup, createdIds: addedGeometry.Select(o => o.ToString()).ToList()); + + return appObj; + } +} diff --git a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs index b467aad06a..7530e0e981 100644 --- a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs +++ b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs @@ -7,6 +7,7 @@ using Objects.BuiltElements.Revit; using Objects.BuiltElements.Revit.Curve; using Objects.Geometry; +using Objects.GIS; using Objects.Other; using Objects.Primitive; using Objects.Structural.Geometry; @@ -513,6 +514,14 @@ public object ConvertToNative(Base @object) rhinoObj = AlignmentToNative(o); break; + case PolygonElement o: + rhinoObj = PolygonElementToNative(o); + break; + + case GisFeature o: + rhinoObj = GisFeatureToNative(o); + break; + case Level o: rhinoObj = LevelToNative(o); break; @@ -735,6 +744,8 @@ public bool CanConvertToNative(Base @object) case Instance _: case GridLine _: case Alignment _: + case PolygonElement _: + case GisFeature _: case Level _: case Dimension _: case Collection c when !c.collectionType.ToLower().Contains("model"): diff --git a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGhShared.projitems b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGhShared.projitems index c0bb3fc5d2..67a382db59 100644 --- a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGhShared.projitems +++ b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGhShared.projitems @@ -9,6 +9,7 @@ ConverterRhinoGhShared + diff --git a/Objects/Objects/GIS/GisFeature.cs b/Objects/Objects/GIS/GisFeature.cs new file mode 100644 index 0000000000..1a5f4dc5ac --- /dev/null +++ b/Objects/Objects/GIS/GisFeature.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using Speckle.Core.Models; + +namespace Objects.GIS; + +public class GisFeature : Base +{ + public GisFeature() + { + attributes = new Base(); + } + + public GisFeature(Base attributes) + { + this.attributes = attributes; + } + + public GisFeature(List geometry, Base attributes) + { + this.geometry = geometry; + this.attributes = attributes; + } + + public GisFeature(Base attributes, List displayValue) + { + this.attributes = attributes; + this.displayValue = displayValue; + } + + public GisFeature(List geometry, Base attributes, List displayValue) + { + this.geometry = geometry; + this.attributes = attributes; + this.displayValue = displayValue; + } + + [DetachProperty] + public List? geometry { get; set; } + + [DetachProperty] + public List? displayValue { get; set; } + public Base attributes { get; set; } +}