diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/BaseComponents/SpeckleScopedTaskCapableComponent.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/BaseComponents/SpeckleScopedTaskCapableComponent.cs new file mode 100644 index 000000000..5dd797194 --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/BaseComponents/SpeckleScopedTaskCapableComponent.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Speckle.Connectors.Grasshopper8.Components.BaseComponents; + +public abstract class SpeckleScopedTaskCapableComponent( + string name, + string nickname, + string description, + string category, + string subCategory +) : SpeckleTaskCapableComponent(name, nickname, description, category, subCategory) +{ + protected override Task PerformTask(TInput input, CancellationToken cancellationToken = default) + { + using var scope = PriorityLoader.Container.CreateScope(); + return PerformScopedTask(input, scope, cancellationToken); + } + + protected abstract Task PerformScopedTask( + TInput input, + IServiceScope scope, + CancellationToken cancellationToken = default + ); +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/BaseComponents/SpeckleTaskCapableComponent.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/BaseComponents/SpeckleTaskCapableComponent.cs new file mode 100644 index 000000000..9a55e0b5c --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/BaseComponents/SpeckleTaskCapableComponent.cs @@ -0,0 +1,60 @@ +using Grasshopper.Kernel; +using Speckle.Sdk; + +namespace Speckle.Connectors.Grasshopper8.Components.BaseComponents; + +public abstract class SpeckleTaskCapableComponent( + string name, + string nickname, + string description, + string category, + string subCategory +) : GH_TaskCapableComponent(name, nickname, description, category, subCategory) +{ + protected override void SolveInstance(IGH_DataAccess da) + { + //TODO: We're missing activity and logging here. Will enable it for all inherited classes. + + if (InPreSolve) + { + // Collect the data and create the task + try + { + var input = GetInput(da); + TaskList.Add(PerformTask(input, CancelToken)); + } + catch (SpeckleException e) + { + Console.WriteLine(e); + } + return; + } + + if (!GetSolveResults(da, out TOutput result)) + { + // INFO: This will run synchronously. Useful for Rhino.Compute runs, but can also be enabled by user. + try + { + TInput input = GetInput(da); + var syncResult = PerformTask(input).Result; + result = syncResult; + } + catch (SpeckleException e) + { + Console.WriteLine(e); + return; + } + } + + if (result is not null) + { + SetOutput(da, result); + } + } + + protected abstract TInput GetInput(IGH_DataAccess da); + + protected abstract void SetOutput(IGH_DataAccess da, TOutput result); + + protected abstract Task PerformTask(TInput input, CancellationToken cancellationToken = default); +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Collections/CreateCollectionComponent.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Collections/CreateCollectionComponent.cs new file mode 100644 index 000000000..f28900c40 --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Collections/CreateCollectionComponent.cs @@ -0,0 +1,165 @@ +using Grasshopper.Kernel; +using Speckle.Connectors.Grasshopper8.Components.BaseComponents; +using Speckle.Connectors.Grasshopper8.Parameters; +using Speckle.Sdk; +using Speckle.Sdk.Models; +using Speckle.Sdk.Models.Collections; + +namespace Speckle.Connectors.Grasshopper8.Components.Collections; + +public record CreateCollectionComponentInput( + Collection? Collection, + string? Name, + List? Elements, + List? Collections +); + +public record CreateCollectionComponentOutput(Collection Collection); + +public class CreateCollectionComponent + : SpeckleTaskCapableComponent +{ + public CreateCollectionComponent() + : base("Create Collection", "CrCol", "Creates a new collection", "Speckle", "Collections") { } + + public override Guid ComponentGuid => new("6A9EDFDE-8AC4-4E28-B455-45DF42E2172B"); + + protected override void RegisterInputParams(GH_InputParamManager pManager) + { + var colIndex = pManager.AddParameter( + new SpeckleCollectionParam(GH_ParamAccess.item), + "collection", + "Collection", + "Collection", + GH_ParamAccess.item + ); + var nameIndex = pManager.AddTextParameter("Name", "Name", "Name of the collection", GH_ParamAccess.item); + + var elementsIndex = pManager.AddParameter( + new SpeckleObjectParam(GH_ParamAccess.list), + "elements", + "Elements", + "Elements of the collection", + GH_ParamAccess.list + ); + var collectionsIndex = pManager.AddParameter( + new SpeckleCollectionParam(GH_ParamAccess.list), + "collections", + "Collections", + "Sub-collections of the collection", + GH_ParamAccess.list + ); + + pManager[colIndex].Optional = true; + pManager[nameIndex].Optional = true; + pManager[elementsIndex].Optional = true; + pManager[collectionsIndex].Optional = true; + } + + protected override void RegisterOutputParams(GH_OutputParamManager pManager) + { + var colIndex = pManager.AddParameter( + new SpeckleCollectionParam(GH_ParamAccess.item), + "collection", + "Collection", + "Collection", + GH_ParamAccess.item + ); + var nameIndex = pManager.AddTextParameter("Name", "Name", "Name of the collection", GH_ParamAccess.item); + + var elementsIndex = pManager.AddParameter( + new SpeckleObjectParam(GH_ParamAccess.list), + "elements", + "Elements", + "Elements of the collection", + GH_ParamAccess.list + ); + var collectionsIndex = pManager.AddParameter( + new SpeckleCollectionParam(GH_ParamAccess.list), + "collections", + "Collections", + "Sub-collections of the collection", + GH_ParamAccess.list + ); + + pManager[colIndex].Optional = true; + pManager[nameIndex].Optional = true; + pManager[elementsIndex].Optional = true; + pManager[collectionsIndex].Optional = true; + } + + protected override CreateCollectionComponentInput GetInput(IGH_DataAccess da) + { + Collection? collection = null; + string? name = ""; + List? elements = new List(); + List? collections = new List(); + + da.GetData(0, ref collection); + da.GetData(1, ref name); + da.GetDataList(2, elements); + da.GetDataList(3, collections); + + return new CreateCollectionComponentInput(collection, name, elements, collections); + } + + protected override void SetOutput(IGH_DataAccess da, CreateCollectionComponentOutput result) + { + da.SetData(0, result.Collection); + da.SetData(1, result.Collection.name); + da.SetDataList(2, result.Collection.elements.Where(e => e is not Collection)); + da.SetDataList(3, result.Collection.elements.Where(e => e is Collection)); + } + + protected override Task PerformTask( + CreateCollectionComponentInput input, + CancellationToken cancellationToken = default + ) + { + if (input.Collection is null) + { + // Create new collection + if (input.Name is null) + { + throw new SpeckleException("New collections must have a name"); + } + + var collection = new Collection(input.Name) { elements = input.Elements ?? new List() }; + var result = new CreateCollectionComponentOutput(collection); + + return Task.FromResult(result); + } + else + { + var collection = new Collection(input.Collection.name) { elements = input.Collection.elements }; + + // Create new collection + if (input.Name is not null && input.Name.Length != 0) + { + collection.name = input.Name; + } + var elements = new List(); + if (input.Elements is not null && input.Elements.Count != 0) + { + elements.AddRange(input.Elements); + } + else + { + elements.AddRange(collection.elements.Where(e => e is not Collection)); + } + + if (input.Collections is not null && input.Collections.Count != 0) + { + elements.AddRange(input.Collections); + } + else + { + elements.AddRange(collection.elements.Where(e => e is Collection)); + } + + var result = new CreateCollectionComponentOutput(collection); + + return Task.FromResult(result); + } + } +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Collections/UnpackRootObjectComponent.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Collections/UnpackRootObjectComponent.cs new file mode 100644 index 000000000..a7bece25b --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Collections/UnpackRootObjectComponent.cs @@ -0,0 +1,195 @@ +using Grasshopper.Kernel; +using Microsoft.Extensions.DependencyInjection; +using Speckle.Connectors.Common.Conversion; +using Speckle.Connectors.Common.Instances; +using Speckle.Connectors.Common.Operations.Receive; +using Speckle.Connectors.Grasshopper8.Components.BaseComponents; +using Speckle.Connectors.Grasshopper8.Parameters; +using Speckle.Sdk; +using Speckle.Sdk.Models; +using Speckle.Sdk.Models.Collections; +using Speckle.Sdk.Models.Instances; + +namespace Speckle.Connectors.Grasshopper8.Components.Collections; + +public record UnpackRootObjectComponentInput(Base RootObject) { } + +public record UnpackRootObjectComponentOutput( + List Elements, + List ElementPaths, + List Instances, + List InstancePaths +) { } + +public class UnpackRootObjectComponent + : SpeckleScopedTaskCapableComponent +{ + public UnpackRootObjectComponent() + : base("Unpack Root Object", "SURO", "Unpacks the root object from a receive operation", "Speckle", "Collections") + { } + + public override Guid ComponentGuid => new Guid("3C770686-20D5-434C-99E3-BDE735E8267F"); + + protected override void RegisterInputParams(GH_InputParamManager pManager) + { + pManager.AddParameter(new SpeckleObjectParam(GH_ParamAccess.item)); + } + + protected override void RegisterOutputParams(GH_OutputParamManager pManager) + { + pManager.AddTextParameter("Element Paths", "EP", "Path to the element in the collection tree", GH_ParamAccess.list); + pManager.AddParameter(new SpeckleObjectParam(), "Elements", "E", "Elements", GH_ParamAccess.list); + pManager.AddTextParameter( + "Instance Paths", + "IP", + "Path to the instance in the collection tree", + GH_ParamAccess.list + ); + pManager.AddParameter(new SpeckleObjectParam(), "Instances", "I", "Instances", GH_ParamAccess.list); + } + + protected override UnpackRootObjectComponentInput GetInput(IGH_DataAccess da) + { + Base? baseObject = null; + da.GetData(0, ref baseObject); + if (baseObject == null) + { + throw new SpeckleException("No base object provided"); + } + return new UnpackRootObjectComponentInput(baseObject); + } + + protected override void SetOutput(IGH_DataAccess da, UnpackRootObjectComponentOutput result) + { + da.SetDataList(0, result.ElementPaths); + da.SetDataList(1, result.Elements); + da.SetDataList(2, result.InstancePaths); + da.SetDataList(3, result.Instances); + } + + protected override async Task PerformScopedTask( + UnpackRootObjectComponentInput input, + IServiceScope scope, + CancellationToken cancellationToken = default + ) + { + var rootObjectUnpacker = scope.ServiceProvider.GetRequiredService(); + var contextUnpacker = scope.ServiceProvider.GetRequiredService(); + + var unpackedRoot = rootObjectUnpacker.Unpack(input.RootObject); + + // 2 - Split atomic objects and instance components with their path + var (atomicObjects, instanceComponents) = rootObjectUnpacker.SplitAtomicObjectsAndInstances( + unpackedRoot.ObjectsToConvert + ); + + var atomicObjectsWithPath = contextUnpacker.GetAtomicObjectsWithPath(atomicObjects); + var instanceComponentsWithPath = contextUnpacker.GetInstanceComponentsWithPath(instanceComponents); + + // 2.1 - these are not captured by traversal, so we need to re-add them here + if (unpackedRoot.DefinitionProxies != null && unpackedRoot.DefinitionProxies.Count > 0) + { + var transformed = unpackedRoot.DefinitionProxies.Select(proxy => + (Array.Empty(), proxy as IInstanceComponent) + ); + instanceComponentsWithPath.AddRange(transformed); + } + + var applicationIdMap = new Dictionary(); + atomicObjectsWithPath.ForEach(a => applicationIdMap.Add(a.current.applicationId ?? a.current.id, a.current)); + + var instanceResult = await ProcessInstances(instanceComponentsWithPath, applicationIdMap).ConfigureAwait(false); + + foreach (string objId in instanceResult.ConsumedObjectIds) + { + var indexAtomic = atomicObjectsWithPath.FindIndex(o => o.current.id == objId); + if (indexAtomic != -1) + { + atomicObjectsWithPath.RemoveAt(indexAtomic); + } + // HACK: Why is instancecomponent not ISpeckleObject? + var indexInstance = instanceComponentsWithPath.FindIndex(o => ((Base)o.instance).id == objId); + if (indexInstance != -1) + { + instanceComponentsWithPath.RemoveAt(indexInstance); + } + } + + var elements = new List(); + var instances = new List(); + var elementPaths = new List(); + var instancePaths = new List(); + + atomicObjectsWithPath.ForEach(atomicObj => + { + var names = atomicObj.path.Select(p => p.name); + elements.Add(atomicObj.current); + elementPaths.Add(string.Join("::", names)); + }); + + instanceComponentsWithPath.ForEach(instanceObj => + { + var names = instanceObj.path.Select(p => p.name); + instances.Add(instanceObj.instance); + instancePaths.Add(string.Join("::", names)); + }); + + var output = new UnpackRootObjectComponentOutput(elements, elementPaths, instances, instancePaths); + + return output; + } + + public Task ProcessInstances( + IReadOnlyCollection<(Collection[] collectionPath, IInstanceComponent obj)> instanceComponents, + Dictionary applicationIdMap + ) + { + var sortedInstanceComponents = instanceComponents + .OrderByDescending(x => x.obj.maxDepth) // Sort by max depth, so we start baking from the deepest element first + .ThenBy(x => x.obj is InstanceDefinitionProxy ? 0 : 1) // Ensure we bake the deepest definition first, then any instances that depend on it + .ToList(); + + var definitionObjectsMap = new Dictionary)>(); + + var consumedObjectIds = new List(); + foreach (var (layerCollection, instanceOrDefinition) in sortedInstanceComponents) + { + try + { + if (instanceOrDefinition is InstanceDefinitionProxy definitionProxy) + { + var currentSpeckleObjects = definitionProxy + .objects.Where(applicationIdMap.ContainsKey) + .Select(x => applicationIdMap[x]) + .ToList(); + + definitionObjectsMap.Add( + definitionProxy.applicationId ?? definitionProxy.id, + (definitionProxy, currentSpeckleObjects) + ); + + consumedObjectIds.AddRange(currentSpeckleObjects.Select(o => o.id)); + consumedObjectIds.Add(definitionProxy.id); + } + + if ( + instanceOrDefinition is InstanceProxy instanceProxy + && definitionObjectsMap.TryGetValue(instanceProxy.definitionId, out var definition) + ) + { + instanceProxy["__geometry"] = definition.Item2; + instanceProxy["__definition"] = definition.Item1; + applicationIdMap[instanceProxy.applicationId ?? instanceProxy.id] = instanceProxy; + } + } + catch (Exception ex) when (!ex.IsFatal()) + { + //_logger.LogError(ex, "Failed to create an instance from proxy"); + } + } + + //await Task.Yield(); + BakeResult processInstances = new(new List(), consumedObjectIds, new List()); + return Task.FromResult(processInstances); + } +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Operations/Conversion/ToNativeConversion.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Operations/Conversion/ToNativeConversion.cs new file mode 100644 index 000000000..2b5db3592 --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Operations/Conversion/ToNativeConversion.cs @@ -0,0 +1,156 @@ +using Grasshopper.Kernel; +using Microsoft.Extensions.DependencyInjection; +using Rhino; +using Rhino.Geometry; +using Speckle.Connectors.Grasshopper8.Components.BaseComponents; +using Speckle.Connectors.Grasshopper8.Parameters; +using Speckle.Converters.Common; +using Speckle.Converters.Rhino; +using Speckle.DoubleNumerics; +using Speckle.Sdk; +using Speckle.Sdk.Common; +using Speckle.Sdk.Common.Exceptions; +using Speckle.Sdk.Models; +using Speckle.Sdk.Models.Instances; + +namespace Speckle.Connectors.Grasshopper8.Components.Operations.Conversion; + +public static class RhinoUnitsExtension +{ + public static string ToSpeckleString(this UnitSystem unitSystem) + { + switch (unitSystem) + { + case UnitSystem.None: + return Units.Meters; + case UnitSystem.Millimeters: + return Units.Millimeters; + case UnitSystem.Centimeters: + return Units.Centimeters; + case UnitSystem.Meters: + return Units.Meters; + case UnitSystem.Kilometers: + return Units.Kilometers; + case UnitSystem.Inches: + return Units.Inches; + case UnitSystem.Feet: + return Units.Feet; + case UnitSystem.Yards: + return Units.Yards; + case UnitSystem.Miles: + return Units.Miles; + case UnitSystem.Unset: + return Units.Meters; + default: + throw new UnitNotSupportedException($"The Unit System \"{unitSystem}\" is unsupported."); + } + } +} + +public class ToNativeConversion() + : SpeckleScopedTaskCapableComponent>( + "ToNativeConversion", + "STN", + "Converts a speckle object to rhino", + "Speckle", + "Conversion" + ) +{ + public override Guid ComponentGuid => new Guid("38BAB10C-4D80-4E0C-8235-A87C3E66F55F"); + + protected override void RegisterInputParams(GH_InputParamManager pManager) + { + pManager.AddParameter(new SpeckleObjectParam(GH_ParamAccess.item)); + } + + protected override void RegisterOutputParams(GH_OutputParamManager pManager) + { + pManager.AddGeometryParameter("Geometry", "Geometry", "Geometry", GH_ParamAccess.list); + } + + protected override Base GetInput(IGH_DataAccess da) + { + Base? input = null; + if (!da.GetData(0, ref input) || input is null) + { + throw new SpeckleException("Input is not valid"); + } + + return input; + } + + protected override void SetOutput(IGH_DataAccess da, List result) + { + da.SetDataList(0, result); + } + + protected override Task> PerformScopedTask( + Base input, + IServiceScope scope, + CancellationToken cancellationToken = default + ) + { + var rhinoConversionSettingsFactory = scope.ServiceProvider.GetRequiredService(); + + scope + .ServiceProvider.GetRequiredService>() + .Initialize(rhinoConversionSettingsFactory.Create(RhinoDoc.ActiveDoc)); + + var rootConverter = scope.ServiceProvider.GetRequiredService(); + + if (input is InstanceProxy proxy) + { + var geometries = proxy["__geometry"] as List; + var converted = geometries.SelectMany(g => Convert(g, rootConverter)).ToList(); + var transform = MatrixToTransform(proxy.transform, proxy.units); + converted.ForEach(c => c.Transform(transform)); + return Task.FromResult(converted); + } + + return Task.FromResult(Convert(input, rootConverter)); + } + + private List Convert(Base input, IRootToHostConverter rootConverter) + { + var result = rootConverter.Convert(input); + + if (result is GeometryBase geometry) + { + return new List { geometry }; + } + else if (result is List geometryList) + { + return geometryList; + } + + throw new SpeckleException("Failed to convert input to rhino"); + } + + private Transform MatrixToTransform(Matrix4x4 matrix, string units) + { + var currentDoc = RhinoDoc.ActiveDoc; // POC: too much right now to interface around + var conversionFactor = Units.GetConversionFactor(units, currentDoc.ModelUnitSystem.ToSpeckleString()); + + var t = Transform.Identity; + t.M00 = matrix.M11; + t.M01 = matrix.M12; + t.M02 = matrix.M13; + t.M03 = matrix.M14 * conversionFactor; + + t.M10 = matrix.M21; + t.M11 = matrix.M22; + t.M12 = matrix.M23; + t.M13 = matrix.M24 * conversionFactor; + + t.M20 = matrix.M31; + t.M21 = matrix.M32; + t.M22 = matrix.M33; + t.M23 = matrix.M34 * conversionFactor; + + t.M30 = matrix.M41; + t.M31 = matrix.M42; + t.M32 = matrix.M43; + t.M33 = matrix.M44; + return t; + } +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Operations/Receive/ReceiveComponent.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Operations/Receive/ReceiveComponent.cs new file mode 100644 index 000000000..800cd121f --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Operations/Receive/ReceiveComponent.cs @@ -0,0 +1,97 @@ +using Grasshopper.Kernel; +using Microsoft.Extensions.DependencyInjection; +using Speckle.Connectors.Common.Operations; +using Speckle.Connectors.Grasshopper8.Components.BaseComponents; +using Speckle.Connectors.Grasshopper8.HostApp; +using Speckle.Connectors.Grasshopper8.Parameters; +using Speckle.Sdk; +using Speckle.Sdk.Api; +using Speckle.Sdk.Credentials; +using Speckle.Sdk.Models; + +namespace Speckle.Connectors.Grasshopper8.Components.Operations.Receive; + +public class ReceiveComponentOutput +{ + public Base RootObject { get; set; } +} + +public class ReceiveComponent : SpeckleScopedTaskCapableComponent +{ + public ReceiveComponent() + : base("Receive from Speckle", "RFS", "Receive objects from speckle", "Speckle", "Operations") { } + + public override Guid ComponentGuid => new("74954F59-B1B7-41FD-97DE-4C6B005F2801"); + protected override Bitmap Icon => BitmapBuilder.CreateSquareIconBitmap("R"); + + protected override void RegisterInputParams(GH_InputParamManager pManager) + { + pManager.AddParameter(new SpeckleUrlModelResourceParam(GH_ParamAccess.item)); + } + + protected override void RegisterOutputParams(GH_OutputParamManager pManager) + { + pManager.AddParameter( + new SpeckleObjectParam(GH_ParamAccess.item), + "Model", + "model", + "The model object for the received version", + GH_ParamAccess.item + ); + } + + protected override SpeckleUrlModelResource GetInput(IGH_DataAccess da) + { + SpeckleUrlModelResource? url = null; + da.GetData(0, ref url); + if (url is null) + { + throw new SpeckleException("Speckle url is null"); + } + + return url; + } + + protected override void SetOutput(IGH_DataAccess da, ReceiveComponentOutput result) + { + da.SetData(0, result.RootObject); + Message = "Done"; + } + + protected override async Task PerformScopedTask( + SpeckleUrlModelResource input, + IServiceScope scope, + CancellationToken cancellationToken = default + ) + { + // TODO: Resolving dependencies here may be overkill in most cases. Must re-evaluate. + var accountManager = scope.ServiceProvider.GetRequiredService(); + var clientFactory = scope.ServiceProvider.GetRequiredService(); + var receiveOperation = scope.ServiceProvider.GetRequiredService(); + + // Do the thing 👇🏼 + + // TODO: Get any account for this server, as we don't have a mechanism yet to pass accountIds through + var account = accountManager.GetAccountWithServerUrlFallback("", new Uri(input.Server)); + + if (account is null) + { + throw new SpeckleAccountManagerException($"No default account was found"); + } + + using var client = clientFactory.Create(account); + var receiveInfo = await input.GetReceiveInfo(client, cancellationToken).ConfigureAwait(false); + + var progress = new Progress(_ => + { + // TODO: Progress only makes sense in non-blocking async receive, which is not supported yet. + // Message = $"{progress.Status}: {progress.Progress}"; + }); + + var root = await receiveOperation + .ReceiveCommitObject(receiveInfo, progress, cancellationToken) + .ConfigureAwait(false); + + return new ReceiveComponentOutput { RootObject = root }; + } +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/SpeckleFirstComponent.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/SpeckleFirstComponent.cs similarity index 94% rename from Connectors/Rhino/Speckle.Connectors.Grasshopper8/SpeckleFirstComponent.cs rename to Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/SpeckleFirstComponent.cs index c1b8c919f..694dccdeb 100644 --- a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/SpeckleFirstComponent.cs +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/SpeckleFirstComponent.cs @@ -3,14 +3,14 @@ using Microsoft.Extensions.DependencyInjection; using Rhino; using Rhino.Geometry; using Speckle.Connectors.Common.Operations; -using Speckle.Connectors.Grasshopper8.Operations.Receive; +using Speckle.Connectors.Grasshopper8.HostApp; using Speckle.Converters.Common; using Speckle.Converters.Rhino; using Speckle.Sdk.Common; using Speckle.Sdk.Credentials; using Speckle.Sdk.Models; -namespace Speckle.Connectors.Grasshopper8; +namespace Speckle.Connectors.Grasshopper8.Components; public class SpeckleFirstComponent : GH_TaskCapableComponent> { @@ -24,7 +24,7 @@ public class SpeckleFirstComponent : GH_TaskCapableComponent> /// new tabs/panels will automatically be created. /// public SpeckleFirstComponent() - : base("Send to Speckle", "STP", "Sends objects to speckle", "Speckle", "Operations") + : base("First Speckle Component", "STP", "Sends objects to speckle", "Speckle", "Other") { _accountManager = PriorityLoader.Container.NotNull().GetRequiredService(); } @@ -45,8 +45,8 @@ public class SpeckleFirstComponent : GH_TaskCapableComponent> { // Collect the data and create the task string url = GetInput(da); - TaskList.Add(PerformReceiveOperation(url, CancelToken)); Message = "Receiving..."; + TaskList.Add(PerformReceiveOperation(url, CancelToken)); return; } @@ -125,9 +125,10 @@ public class SpeckleFirstComponent : GH_TaskCapableComponent> throw new NotSupportedException($"Unsupported conversion result type: {conversionResult}"); } + //results.Add(ghConversionResult.Source); if (ghConversionResult.Result is GeometryBase geometryBase) { - //var guid = BakeObject(geometryBase, obj, atts); + results.Add(geometryBase); } else if (ghConversionResult.Result is List geometryBases) // one to many raw encoding case { @@ -137,7 +138,6 @@ public class SpeckleFirstComponent : GH_TaskCapableComponent> { results.AddRange(fallbackConversionResult.Select(o => o.Item1)); } - results.Add(ghConversionResult.Result); } return results; diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/SpeckleResourceFromUrlComponent.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/SpeckleResourceFromUrlComponent.cs new file mode 100644 index 000000000..0cd9ba550 --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/SpeckleResourceFromUrlComponent.cs @@ -0,0 +1,48 @@ +using Grasshopper.Kernel; +using Speckle.Connectors.Grasshopper8.Components.BaseComponents; +using Speckle.Connectors.Grasshopper8.HostApp; +using Speckle.Connectors.Grasshopper8.Parameters; + +namespace Speckle.Connectors.Grasshopper8.Components; + +public class SpeckleResourceFromUrlComponent : SpeckleTaskCapableComponent +{ + public SpeckleResourceFromUrlComponent() + : base("Speckle Resource From Url", "spcklUrl", "Speckle resource from url", "Speckle", "Resources") { } + + public override Guid ComponentGuid => new("A55C74C6-D955-4822-84BB-2266A2B965EE"); + + protected override void RegisterInputParams(GH_InputParamManager pManager) + { + pManager.AddTextParameter("URL", "URL", "URL to send to resource", GH_ParamAccess.item); + } + + protected override void RegisterOutputParams(GH_OutputParamManager pManager) + { + pManager.AddParameter(new SpeckleUrlModelResourceParam(GH_ParamAccess.list)); + } + + protected override string GetInput(IGH_DataAccess da) + { + string url = string.Empty; + da.GetData(0, ref url); + return url; + } + + protected override void SetOutput(IGH_DataAccess da, SpeckleUrlModelResource[] result) + { + da.SetDataList(0, result); + } + + protected override Task PerformTask( + string input, + CancellationToken cancellationToken = default + ) + { + var resources = SpeckleResourceBuilder.FromUrlString(input); + + // TODO: Here's where we can validate the resources and throw or not? + + return Task.FromResult(resources); + } +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/BitmapBuilder.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/BitmapBuilder.cs new file mode 100644 index 000000000..f31114f3a --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/BitmapBuilder.cs @@ -0,0 +1,81 @@ +using System.Drawing.Drawing2D; + +namespace Speckle.Connectors.Grasshopper8.HostApp; + +public static class BitmapBuilder +{ + public static Bitmap CreateSquareIconBitmap(string text, int width = 24, int height = 24) + { + Bitmap bitmap = new(width, height); + using Graphics graphics = Graphics.FromImage(bitmap); + + // Enable high-quality rendering + graphics.SmoothingMode = SmoothingMode.AntiAlias; + + // Set background to transparent + graphics.Clear(Color.Transparent); + + // Rectangle with a 1px offset + Rectangle squareRect = new(1, 1, width - 2, height - 2); + + using (Brush blueBrush = new SolidBrush(Color.Blue)) + { + graphics.FillRectangle(blueBrush, squareRect); + } + + // Draw white letters in the center + using (Font font = new("Arial", 8, FontStyle.Bold, GraphicsUnit.Pixel)) + using (Brush whiteBrush = new SolidBrush(Color.White)) + { + StringFormat format = new() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }; + + graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; + graphics.DrawString(text, font, whiteBrush, new RectangleF(1, 1, width - 2, height - 2), format); + } + + return bitmap; + } + + public static Bitmap CreateHexagonalBitmap(string text, int width = 24, int height = 24) + { + Bitmap bitmap = new(width, height); + using Graphics graphics = Graphics.FromImage(bitmap); + + // Enable high-quality rendering + graphics.SmoothingMode = SmoothingMode.AntiAlias; + + // Set background to transparent + graphics.Clear(Color.Transparent); + + // Calculate hexagon points centered within the bitmap + float side = (width - 2) / 2.236f; // 2.236f approximates 4 / √3 for regular hex dimensions + float h = side * (float)Math.Sqrt(3) / 2; + float centerX = width / 2f; + float centerY = height / 2f; + + Point[] hexagonPoints = + [ + new((int)(centerX - side / 2), (int)(centerY - h)), + new((int)(centerX + side / 2), (int)(centerY - h)), + new((int)(centerX + side), (int)centerY), + new((int)(centerX + side / 2), (int)(centerY + h)), + new((int)(centerX - side / 2), (int)(centerY + h)), + new((int)(centerX - side), (int)centerY) + ]; + + using (Brush blueBrush = new SolidBrush(Color.Blue)) + { + graphics.FillPolygon(blueBrush, hexagonPoints); + } + + // Draw white letters in the center + using Font font = new("Monospace", 10, FontStyle.Bold, GraphicsUnit.Pixel); + using Brush whiteBrush = new SolidBrush(Color.White); + StringFormat format = new() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }; + + graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; + graphics.DrawString(text, font, whiteBrush, new RectangleF(0, 1, width, height), format); + + return bitmap; + } +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Operations/Receive/GrasshopperHostObjectBuilder.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/GrasshopperHostObjectBuilder.cs similarity index 97% rename from Connectors/Rhino/Speckle.Connectors.Grasshopper8/Operations/Receive/GrasshopperHostObjectBuilder.cs rename to Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/GrasshopperHostObjectBuilder.cs index 5b124a421..fb3bcb6cc 100644 --- a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Operations/Receive/GrasshopperHostObjectBuilder.cs +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/GrasshopperHostObjectBuilder.cs @@ -11,11 +11,12 @@ using Speckle.Sdk.Models; using Speckle.Sdk.Models.Collections; using Speckle.Sdk.Models.Instances; -namespace Speckle.Connectors.Grasshopper8.Operations.Receive; +namespace Speckle.Connectors.Grasshopper8.HostApp; public sealed class GrasshopperReceiveConversionResult : ReceiveConversionResult { public object? Result { get; set; } + public Base Source { get; set; } public GrasshopperReceiveConversionResult( Status status, @@ -28,6 +29,7 @@ public sealed class GrasshopperReceiveConversionResult : ReceiveConversionResult : base(status, source, resultId, resultType, exception) { Result = result; + Source = source; } } @@ -146,6 +148,8 @@ public class GrasshopperHostObjectBuilder : IHostObjectBuilder conversionResults.Add( new GrasshopperReceiveConversionResult(Status.SUCCESS, obj, result, null, result.GetType().ToString()) ); + + // applicationIdMap[obj.applicationId ?? obj.id] = conversionIds; convertActivity?.SetStatus(SdkActivityStatusCode.Ok); } catch (Exception ex) when (!ex.IsFatal()) diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/GrasshopperReceiveOperation.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/GrasshopperReceiveOperation.cs new file mode 100644 index 000000000..58de90475 --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/GrasshopperReceiveOperation.cs @@ -0,0 +1,100 @@ +using Speckle.Connectors.Common.Operations; +using Speckle.Connectors.Logging; +using Speckle.Sdk.Api; +using Speckle.Sdk.Credentials; +using Speckle.Sdk.Logging; +using Speckle.Sdk.Models; +using Speckle.Sdk.Transports; + +namespace Speckle.Connectors.Grasshopper8.HostApp; + +public class GrasshopperReceiveOperation +{ + private readonly AccountService _accountService; + private readonly IServerTransportFactory _serverTransportFactory; + private readonly IProgressDisplayManager _progressDisplayManager; + private readonly ISdkActivityFactory _activityFactory; + private readonly IOperations _operations; + private readonly IClientFactory _clientFactory; + + public GrasshopperReceiveOperation( + AccountService accountService, + IServerTransportFactory serverTransportFactory, + IProgressDisplayManager progressDisplayManager, + ISdkActivityFactory activityFactory, + IOperations operations, + IClientFactory clientFactory + ) + { + _accountService = accountService; + _serverTransportFactory = serverTransportFactory; + _progressDisplayManager = progressDisplayManager; + _activityFactory = activityFactory; + _operations = operations; + _clientFactory = clientFactory; + } + + public async Task ReceiveCommitObject( + ReceiveInfo receiveInfo, + IProgress onOperationProgressed, + CancellationToken cancellationToken + ) + { + using var execute = _activityFactory.Start("Receive Operation"); + execute?.SetTag("receiveInfo", receiveInfo); + // 2 - Check account exist + Account account = _accountService.GetAccountWithServerUrlFallback(receiveInfo.AccountId, receiveInfo.ServerUrl); + using Client apiClient = _clientFactory.Create(account); + using var userScope = ActivityScope.SetTag(Consts.USER_ID, account.GetHashedEmail()); + + var version = await apiClient + .Version.Get(receiveInfo.SelectedVersionId, receiveInfo.ProjectId, cancellationToken) + .ConfigureAwait(false); + + using var transport = _serverTransportFactory.Create(account, receiveInfo.ProjectId); + + double? previousPercentage = null; + _progressDisplayManager.Begin(); + Base commitObject = await _operations + .Receive2( + new Uri(account.serverInfo.url), + receiveInfo.ProjectId, + version.referencedObject, + account.token, + onProgressAction: new PassthroughProgress(args => + { + if (args.ProgressEvent == ProgressEvent.CacheCheck || args.ProgressEvent == ProgressEvent.DownloadBytes) + { + switch (args.ProgressEvent) + { + case ProgressEvent.CacheCheck: + previousPercentage = _progressDisplayManager.CalculatePercentage(args); + break; + } + } + if (!_progressDisplayManager.ShouldUpdate()) + { + return; + } + + switch (args.ProgressEvent) + { + case ProgressEvent.CacheCheck: + case ProgressEvent.DownloadBytes: + onOperationProgressed.Report(new("Checking and Downloading... ", previousPercentage)); + break; + case ProgressEvent.DeserializeObject: + onOperationProgressed.Report(new("Deserializing ...", _progressDisplayManager.CalculatePercentage(args))); + break; + } + }), + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + + await apiClient + .Version.Received(new(version.id, receiveInfo.ProjectId, receiveInfo.SourceApplication), cancellationToken) + .ConfigureAwait(false); + return commitObject; + } +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/SpeckleResource.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/SpeckleResource.cs new file mode 100644 index 000000000..6c2effad5 --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/SpeckleResource.cs @@ -0,0 +1,69 @@ +using Speckle.Connectors.Common.Operations; +using Speckle.Sdk.Api; +using Speckle.Sdk.Api.GraphQL.Models; +using Speckle.Sdk.Common; +using Version = Speckle.Sdk.Api.GraphQL.Models.Version; + +namespace Speckle.Connectors.Grasshopper8.HostApp; + +public abstract record SpeckleUrlModelResource(string Server, string ProjectId) +{ + public abstract Task GetReceiveInfo(Client client, CancellationToken cancellationToken = default); +} + +public record SpeckleUrlLatestModelVersionResource(string Server, string ProjectId, string ModelId) + : SpeckleUrlModelResource(Server, ProjectId) +{ + public override async Task GetReceiveInfo(Client client, CancellationToken cancellationToken = default) + { + Project project = await client.Project.Get(ProjectId, cancellationToken).ConfigureAwait(false); + ModelWithVersions model = await client + .Model.GetWithVersions(ModelId, ProjectId, 1, null, null, cancellationToken) + .ConfigureAwait(false); + Version version = model.versions.items[0]; + + var info = new ReceiveInfo( + client.Account.id, + new Uri(Server), + ProjectId, + project.name, + ModelId, + model.name, + version.id, + version.sourceApplication.NotNull() + ); + + return info; + } +} + +public record SpeckleUrlModelVersionResource(string Server, string ProjectId, string ModelId, string VersionId) + : SpeckleUrlModelResource(Server, ProjectId) +{ + public override async Task GetReceiveInfo(Client client, CancellationToken cancellationToken = default) + { + Project project = await client.Project.Get(ProjectId, cancellationToken).ConfigureAwait(false); + Model model = await client.Model.Get(ModelId, ProjectId, cancellationToken).ConfigureAwait(false); + Version version = await client.Version.Get(VersionId, ProjectId, cancellationToken).ConfigureAwait(false); + + var info = new ReceiveInfo( + client.Account.id, + new Uri(Server), + ProjectId, + project.name, + ModelId, + model.name, + VersionId, + version.sourceApplication.NotNull() + ); + + return info; + } +} + +public record SpeckleUrlModelObjectResource(string Server, string ProjectId, string ObjectId) + : SpeckleUrlModelResource(Server, ProjectId) +{ + public override Task GetReceiveInfo(Client client, CancellationToken cancellationToken = default) => + throw new NotImplementedException("Object Resources are not supported yet"); +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/SpeckleResourceBuilder.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/SpeckleResourceBuilder.cs new file mode 100644 index 000000000..31bc3a823 --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/SpeckleResourceBuilder.cs @@ -0,0 +1,82 @@ +using System.Text.RegularExpressions; +using Speckle.Sdk; + +namespace Speckle.Connectors.Grasshopper8.HostApp; + +public record SpeckleResourceBuilder +{ + /// + /// The ReGex pattern to determine if a URL's AbsolutePath is a Frontend2 URL or not. + /// + private static readonly Regex s_fe2UrlRegex = + new( + @"/projects/(?[\w\d]+)(?:/models/(?[\w\d]+(?:@[\w\d]+)?)(?:,(?[\w\d]+(?:@[\w\d]+)?))*)?" + ); + + public static SpeckleUrlModelResource[] FromUrlString(string speckleModel) + { + var uri = new Uri(speckleModel); + var serverUrl = uri.GetLeftPart(UriPartial.Authority); + var match = s_fe2UrlRegex.Match(speckleModel); + var result = ParseFe2RegexMatch(serverUrl, match); + return result; + } + + private static SpeckleUrlModelResource[] ParseFe2RegexMatch(string serverUrl, Match match) + { + var projectId = match.Groups["projectId"]; + var model = match.Groups["model"]; + var additionalModels = match.Groups["additionalModels"]; + + if (!projectId.Success) + { + throw new SpeckleException("The provided url is not a valid Speckle url"); + } + + if (!model.Success) + { + throw new SpeckleException("The provided url is not pointing to any model in the project."); + } + + if (model.Value == "all") + { + throw new NotSupportedException("Fetching all models is not supported."); + } + + if (model.Value.StartsWith("$")) + { + throw new NotSupportedException("Federation model urls are not supported"); + } + + var modelRes = GetUrlModelResource(serverUrl, projectId.Value, model.Value); + + var result = new List { modelRes }; + + if (additionalModels.Success) + { + foreach (Capture additionalModelsCapture in additionalModels.Captures) + { + var extraModel = GetUrlModelResource(serverUrl, projectId.Value, additionalModelsCapture.Value); + result.Add(extraModel); + } + } + + return result.ToArray(); + } + + private static SpeckleUrlModelResource GetUrlModelResource(string serverUrl, string projectId, string modelValue) + { + if (modelValue.Length == 32) + { + return new SpeckleUrlModelObjectResource(serverUrl, projectId, modelValue); // Model value is an ObjectID + } + + if (!modelValue.Contains('@')) + { + return new SpeckleUrlLatestModelVersionResource(serverUrl, projectId, modelValue); // Model has no version attached + } + + var res = modelValue.Split('@'); + return new SpeckleUrlModelVersionResource(serverUrl, projectId, res[0], res[1]); + } +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleCollectionGoo.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleCollectionGoo.cs new file mode 100644 index 000000000..3f0b488dc --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleCollectionGoo.cs @@ -0,0 +1,82 @@ +using Grasshopper.Kernel.Types; +using Speckle.Sdk.Models; +using Speckle.Sdk.Models.Collections; + +namespace Speckle.Connectors.Grasshopper8.Parameters; + +public class SpeckleCollectionGoo : GH_Goo +{ + // TODO: Massive hack for setup only!!! We need some sort of `ShallowCopy` or a transparent wrapper for Speckle Objects + // to prevent backwards propagation of changes of the same instance. + public override IGH_Goo Duplicate() => new SpeckleCollectionGoo { Value = m_value }; + + public override string ToString() => $"Speckle Collection [{m_value.name}]"; + + public override bool IsValid => true; + public override string TypeName => "SpeckleCollection"; + public override string TypeDescription => "A Speckle Collection"; + + public override bool CastFrom(object source) + { + Collection? obj = null; + switch (source) + { + case Collection speckleCollection: + obj = speckleCollection; + break; + case SpeckleObjectGoo speckleObjectGoo: + if (speckleObjectGoo.Value is Collection collection) + { + obj = collection; + } + break; + case SpeckleCollectionGoo speckleCollectionGoo: + obj = speckleCollectionGoo.Value; + break; + case GH_Goo speckleObjectGoo: + if (speckleObjectGoo.Value is Collection collection2) + { + obj = collection2; + } + break; + default: + obj = null; + break; + } + + if (obj is null) + { + return false; + } + + Value = obj; + return true; + } + + public override bool CastTo(ref TOut target) + { + var type = typeof(TOut); + var success = false; + if (type == typeof(SpeckleObjectGoo)) + { + target = (TOut)(object)new SpeckleObjectGoo { Value = Value }; + success = true; + } + else if (type == typeof(SpeckleCollectionGoo)) + { + target = (TOut)(object)new SpeckleObjectGoo { Value = Value }; + success = true; + } + else if (type == typeof(Collection)) + { + target = (TOut)(object)Value; + success = true; + } + else if (type == typeof(Base)) + { + target = (TOut)(object)Value; + success = true; + } + return success; + } +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleCollectionParam.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleCollectionParam.cs new file mode 100644 index 000000000..f94590f15 --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleCollectionParam.cs @@ -0,0 +1,23 @@ +using Grasshopper.Kernel; +using Speckle.Connectors.Grasshopper8.HostApp; + +namespace Speckle.Connectors.Grasshopper8.Parameters; + +public class SpeckleCollectionParam : GH_Param +{ + public SpeckleCollectionParam() + : this(GH_ParamAccess.item) { } + + public SpeckleCollectionParam(IGH_InstanceDescription tag) + : base(tag) { } + + public SpeckleCollectionParam(IGH_InstanceDescription tag, GH_ParamAccess access) + : base(tag, access) { } + + public SpeckleCollectionParam(GH_ParamAccess access) + : base("Speckle Collection", "SpcklCol", "XXX", "Speckle", "Params", access) { } + + public override Guid ComponentGuid => new("F397D941-6B4D-4143-B535-A11F7F776BA1"); + + protected override Bitmap Icon => BitmapBuilder.CreateHexagonalBitmap("C"); +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleObjectGoo.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleObjectGoo.cs new file mode 100644 index 000000000..e614dd0fa --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleObjectGoo.cs @@ -0,0 +1,60 @@ +using Grasshopper.Kernel.Types; +using Speckle.Sdk.Models; +using Speckle.Sdk.Models.Collections; + +namespace Speckle.Connectors.Grasshopper8.Parameters; + +public class SpeckleObjectGoo : GH_Goo +{ + // TODO: Massive hack for setup only!!! We need some sort of `ShallowCopy` or a transparent wrapper for Speckle Objects + // to prevent backwards propagation of changes of the same instance. + public override IGH_Goo Duplicate() => new SpeckleObjectGoo { Value = m_value }; + + public override string ToString() => $"Speckle Object [{m_value.GetType().Name}]"; + + public override bool IsValid => true; + public override string TypeName => "SpeckleObject"; + public override string TypeDescription => "A Speckle Object"; + + public override bool CastFrom(object source) + { + Base? obj = source switch + { + Base speckleObject => speckleObject, + SpeckleObjectGoo speckleObjectGoo => speckleObjectGoo.Value, + SpeckleCollectionGoo speckleCollectionGoo => speckleCollectionGoo.Value, + GH_Goo speckleObjectGoo => speckleObjectGoo.Value, + _ => null + }; + + if (obj is null) + { + return false; + } + + Value = obj; + return true; + } + + public override bool CastTo(ref TOut target) + { + var type = typeof(TOut); + var success = false; + if (type == typeof(SpeckleObjectGoo)) + { + target = (TOut)(object)new SpeckleObjectGoo { Value = Value }; + success = true; + } + else if (type == typeof(SpeckleCollectionGoo) && Value is Collection collection) + { + target = (TOut)(object)new SpeckleObjectGoo { Value = collection }; + success = true; + } + else if (type == typeof(Base)) + { + target = (TOut)(object)Value; + success = true; + } + return success; + } +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleObjectParam.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleObjectParam.cs new file mode 100644 index 000000000..2700ac2cc --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleObjectParam.cs @@ -0,0 +1,20 @@ +using Grasshopper.Kernel; + +namespace Speckle.Connectors.Grasshopper8.Parameters; + +public class SpeckleObjectParam : GH_Param +{ + public SpeckleObjectParam() + : this(GH_ParamAccess.item) { } + + public SpeckleObjectParam(IGH_InstanceDescription tag) + : base(tag) { } + + public SpeckleObjectParam(IGH_InstanceDescription tag, GH_ParamAccess access) + : base(tag, access) { } + + public SpeckleObjectParam(GH_ParamAccess access) + : base("Speckle Object", "SpklObj", "XXXXX", "Speckle", "Params", access) { } + + public override Guid ComponentGuid => new("F708F88C-FE00-44EF-8D30-02AB6CF5F728"); +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleUrlModelResourceGoo.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleUrlModelResourceGoo.cs new file mode 100644 index 000000000..c1e708745 --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleUrlModelResourceGoo.cs @@ -0,0 +1,40 @@ +using Grasshopper.Kernel.Types; +using Speckle.Connectors.Grasshopper8.HostApp; + +namespace Speckle.Connectors.Grasshopper8.Parameters; + +public class SpeckleUrlModelResourceGoo : GH_Goo +{ + public override IGH_Goo Duplicate() => new SpeckleUrlModelResourceGoo() { Value = Value }; + + public override string ToString() => Value.ToString(); + + public override bool IsValid => true; + public override string TypeName => "SpeckleUrlModelResource"; + public override string TypeDescription => "Points to a model/version/object in a Speckle server"; + + public override bool CastFrom(object source) + { + switch (source) + { + case SpeckleUrlModelResource resource: + Value = resource; + return true; + default: + return false; + } + } + + public override bool CastTo(ref TOut target) + { + var type = typeof(TOut); + var success = false; + if (type == typeof(SpeckleUrlModelResource)) + { + target = (TOut)(object)Value; + success = true; + } + + return success; + } +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleUrlModelResourceParam.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleUrlModelResourceParam.cs new file mode 100644 index 000000000..0ca7f386c --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleUrlModelResourceParam.cs @@ -0,0 +1,20 @@ +using Grasshopper.Kernel; + +namespace Speckle.Connectors.Grasshopper8.Parameters; + +public class SpeckleUrlModelResourceParam : GH_Param +{ + public SpeckleUrlModelResourceParam() + : this(GH_ParamAccess.item) { } + + public SpeckleUrlModelResourceParam(IGH_InstanceDescription tag) + : base(tag) { } + + public SpeckleUrlModelResourceParam(IGH_InstanceDescription tag, GH_ParamAccess access) + : base(tag, access) { } + + public SpeckleUrlModelResourceParam(GH_ParamAccess access) + : base("Speckle URL", "spcklUrl", "A Speckle resource", "Speckle", "Resources", access) { } + + public override Guid ComponentGuid => new Guid("E5421FC2-F10D-447F-BF23-5C934ABDB2D3"); +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/PriorityLoader.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/PriorityLoader.cs index 7c904d70c..29f5020ac 100644 --- a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/PriorityLoader.cs +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/PriorityLoader.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Speckle.Connectors.Common; using Speckle.Connectors.Common.Builders; using Speckle.Connectors.Common.Operations.Receive; -using Speckle.Connectors.Grasshopper8.Operations.Receive; +using Speckle.Connectors.Grasshopper8.HostApp; using Speckle.Converters.Rhino; using Speckle.Sdk; using Speckle.Sdk.Credentials; @@ -26,6 +26,7 @@ public class PriorityLoader : GH_AssemblyPriority services.AddRhinoConverters().AddConnectorUtils(); services.AddTransient(); + services.AddTransient(); services.AddSingleton(DefaultTraversal.CreateTraversalFunc()); services.AddTransient(); services.AddTransient(); diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Speckle.Connectors.Grasshopper8.csproj b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Speckle.Connectors.Grasshopper8.csproj index 4f2681875..65ca44857 100644 --- a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Speckle.Connectors.Grasshopper8.csproj +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Speckle.Connectors.Grasshopper8.csproj @@ -30,8 +30,4 @@ - - - - diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/SpeckleObjectParam.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/SpeckleObjectParam.cs deleted file mode 100644 index fa67999ed..000000000 --- a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/SpeckleObjectParam.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Grasshopper.Kernel; -using Grasshopper.Kernel.Types; -using Speckle.Sdk.Models; - -namespace Speckle.Connectors.Grasshopper8; - -public class SpeckleObjectParam : GH_Param -{ - public SpeckleObjectParam(IGH_InstanceDescription tag) - : base(tag) { } - - public SpeckleObjectParam(IGH_InstanceDescription tag, GH_ParamAccess access) - : base(tag, access) { } - - public SpeckleObjectParam(GH_ParamAccess access) - : base("Speckle Object", "SpklObj", "XXXXX", "Speckle", "Params", access) { } - - public override Guid ComponentGuid => new("F708F88C-FE00-44EF-8D30-02AB6CF5F728"); -} - -public class SpeckleObjectGoo : GH_Goo -{ - // TODO: Massive hack for setup only!!! We need some sort of `ShallowCopy` or a transparent wrapper for Speckle Objects - // to prevent backwards propagation of changes of the same instance. - public override IGH_Goo Duplicate() => new SpeckleObjectGoo { Value = m_value }; - - public override string ToString() => m_value.ToString(); - - public override bool IsValid => true; - public override string TypeName => "SpeckleObject"; - public override string TypeDescription => "A Speckle Object"; -} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/packages.lock.json b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/packages.lock.json index 73b6e6fcd..87e9c8a00 100644 --- a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/packages.lock.json +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/packages.lock.json @@ -273,9 +273,36 @@ "Speckle.Sdk.Dependencies": "[3.1.0-dev.191, )" } }, + "speckle.connectors.dui": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )", + "Speckle.Connectors.Common": "[1.0.0, )", + "Speckle.Sdk": "[3.1.0-dev.191, )", + "Speckle.Sdk.Dependencies": "[3.1.0-dev.191, )", + "System.Threading.Tasks.Dataflow": "[6.0.0, )" + } + }, + "speckle.connectors.dui.webview": { + "type": "Project", + "dependencies": { + "Microsoft.Web.WebView2": "[1.0.1938.49, )", + "Speckle.Connectors.DUI": "[1.0.0, )" + } + }, "speckle.connectors.logging": { "type": "Project" }, + "speckle.connectors.rhino7": { + "type": "Project", + "dependencies": { + "RhinoCommon": "[7.13.21348.13001, )", + "RhinoWindows": "[7.13.21348.13001, )", + "Speckle.Connectors.Common": "[1.0.0, )", + "Speckle.Connectors.DUI.WebView": "[1.0.0, )", + "Speckle.Converters.Rhino7": "[1.0.0, )" + } + }, "speckle.converters.common": { "type": "Project", "dependencies": { @@ -283,6 +310,13 @@ "Speckle.Objects": "[3.1.0-dev.191, )" } }, + "speckle.converters.rhino7": { + "type": "Project", + "dependencies": { + "RhinoCommon": "[7.13.21348.13001, )", + "Speckle.Converters.Common": "[1.0.0, )" + } + }, "speckle.converters.rhino8": { "type": "Project", "dependencies": { @@ -323,6 +357,21 @@ "resolved": "2.2.0", "contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A==" }, + "Microsoft.Web.WebView2": { + "type": "CentralTransitive", + "requested": "[1.0.1938.49, )", + "resolved": "1.0.1938.49", + "contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw==" + }, + "RhinoWindows": { + "type": "CentralTransitive", + "requested": "[8.9.24194.18121, )", + "resolved": "7.13.21348.13001", + "contentHash": "V94T8emmJmFfmbd5cu+uTNS0neZApx1Q5MXvsQGFtt/mEGEbdHE+dFOETNgbOOJXSdNboAnCR3uo0GosOFX+/g==", + "dependencies": { + "RhinoCommon": "[7.13.21348.13001]" + } + }, "Speckle.Objects": { "type": "CentralTransitive", "requested": "[3.1.0-dev.191, )", @@ -354,6 +403,12 @@ "requested": "[3.1.0-dev.191, )", "resolved": "3.1.0-dev.191", "contentHash": "EmEOyjsGsNi56Z/ZoBOn8WirTmIT2yqWvlUeUh0BSPX2TDMZXHTKOM/kHmP6HSd10KVFn2Zo/ItY7/K9iRtL1Q==" + }, + "System.Threading.Tasks.Dataflow": { + "type": "CentralTransitive", + "requested": "[6.0.0, )", + "resolved": "6.0.0", + "contentHash": "+tyDCU3/B1lDdOOAJywHQoFwyXIUghIaP2BxG79uvhfTnO+D9qIgjVlL/JV2NTliYbMHpd6eKDmHp2VHpij7MA==" } } } diff --git a/Directory.Build.props b/Directory.Build.props index 858928479..95118439f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -31,7 +31,7 @@ CA1848;CA2254;CA1727; - CA1815;CA1725; + CA1815;CA1725;CA1501; NETSDK1206; $(NoWarn)