feat: POC Send node

This commit is contained in:
Alan Rynne
2024-12-10 23:52:07 +01:00
parent f4a3b24b5a
commit 4b65aa4386
9 changed files with 391 additions and 32 deletions
@@ -27,11 +27,28 @@ public abstract class SpeckleTaskCapableComponent<TInput, TOutput>(
catch (SpeckleException e)
{
Console.WriteLine(e);
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, e.Message);
}
return;
}
if (!GetSolveResults(da, out TOutput result))
bool solveResults = false;
TOutput? result = default;
try
{
solveResults = GetSolveResults(da, out result);
}
catch (AggregateException e)
{
Console.WriteLine(e);
foreach (var inner in e.InnerExceptions)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, inner.Message);
}
}
if (!solveResults)
{
// INFO: This will run synchronously. Useful for Rhino.Compute runs, but can also be enabled by user.
try
@@ -40,9 +57,12 @@ public abstract class SpeckleTaskCapableComponent<TInput, TOutput>(
var syncResult = PerformTask(input).Result;
result = syncResult;
}
catch (SpeckleException e)
catch (AggregateException e)
{
Console.WriteLine(e);
foreach (var inner in e.InnerExceptions)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, inner.Message);
}
return;
}
}
@@ -56,6 +76,7 @@ public abstract class SpeckleTaskCapableComponent<TInput, TOutput>(
protected override Bitmap Icon => BitmapBuilder.CreateSquareIconBitmap(IconText);
protected string IconText => string.Join("", Name.Split(' ').Select(s => s.First()));
protected abstract TInput GetInput(IGH_DataAccess da);
protected abstract void SetOutput(IGH_DataAccess da, TOutput result);
@@ -0,0 +1,124 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;
using Microsoft.Extensions.DependencyInjection;
using Rhino;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Grasshopper8.Components.BaseComponents;
using Speckle.Connectors.Grasshopper8.HostApp;
using Speckle.Connectors.Grasshopper8.Parameters;
using Speckle.Converters.Common;
using Speckle.Converters.Rhino;
using Speckle.Sdk;
using Speckle.Sdk.Api;
using Speckle.Sdk.Common;
using Speckle.Sdk.Credentials;
namespace Speckle.Connectors.Grasshopper8.Components.Operations.Send;
public class SendComponentInput
{
public SpeckleUrlModelResource Resource { get; }
public Dictionary<string, GH_Structure<IGH_Goo>> Inputs { get; }
public SendComponentInput(SpeckleUrlModelResource resource, Dictionary<string, GH_Structure<IGH_Goo>> inputs)
{
Resource = resource;
Inputs = inputs;
}
}
public class SendComponentOutput(SpeckleUrlModelObjectResource resource)
{
public SpeckleUrlModelObjectResource Resource { get; } = resource;
}
public class SendComponent()
: SpeckleScopedTaskCapableComponent<SendComponentInput, SendComponentOutput>(
"Send",
"SS",
"Speckle Send",
"Speckle",
"Operations"
)
{
public override Guid ComponentGuid => new("0CF0D173-BDF0-4AC2-9157-02822B90E9FB");
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam());
pManager.AddGenericParameter("A", "A", "A", GH_ParamAccess.tree);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam());
}
protected override SendComponentInput GetInput(IGH_DataAccess da)
{
if (da.Iteration != 0)
{
throw new SpeckleException("No more than 1 resource allowed");
}
SpeckleUrlModelResource? resource = null;
if (!da.GetData(0, ref resource))
{
throw new SpeckleException("Failed to get resource");
}
var name = Params.Input[1].Name;
da.GetDataTree(1, out GH_Structure<IGH_Goo> tree);
var inputDict = new Dictionary<string, GH_Structure<IGH_Goo>> { { name, tree } };
return new SendComponentInput(resource.NotNull(), inputDict);
}
protected override void SetOutput(IGH_DataAccess da, SendComponentOutput result)
{
da.SetData(0, result.Resource);
}
protected override async Task<SendComponentOutput> PerformScopedTask(
SendComponentInput input,
IServiceScope scope,
CancellationToken cancellationToken = default
)
{
var rhinoConversionSettingsFactory = scope.ServiceProvider.GetRequiredService<IRhinoConversionSettingsFactory>();
scope
.ServiceProvider.GetRequiredService<IConverterSettingsStore<RhinoConversionSettings>>()
.Initialize(rhinoConversionSettingsFactory.Create(RhinoDoc.ActiveDoc));
var accountManager = scope.ServiceProvider.GetRequiredService<AccountService>();
var clientFactory = scope.ServiceProvider.GetRequiredService<IClientFactory>();
var sendOperation = scope.ServiceProvider.GetRequiredService<GrasshopperSendOperation>();
// 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.Resource.Server));
if (account is null)
{
throw new SpeckleAccountManagerException($"No default account was found");
}
var progress = new Progress<CardProgress>(_ =>
{
// TODO: Progress only makes sense in non-blocking async receive, which is not supported yet.
// Message = $"{progress.Status}: {progress.Progress}";
});
using var client = clientFactory.Create(account);
var receiveInfo = await input.Resource.GetSendInfo(client, cancellationToken).ConfigureAwait(false);
var result = await sendOperation
.Execute(input.Inputs, receiveInfo, progress, cancellationToken)
.ConfigureAwait(false);
return new SendComponentOutput(
new SpeckleUrlModelObjectResource(receiveInfo.ServerUrl.ToString(), receiveInfo.ProjectId, result.RootId)
);
}
}
@@ -0,0 +1,33 @@
using System.Reflection;
using Grasshopper.Kernel.Types;
using Speckle.Sdk;
namespace Speckle.Connectors.Grasshopper8.HostApp;
public static class GrasshopperGooExtensions
{
public static T UnwrapGoo<T>(this IGH_Goo goo)
{
if (goo is GH_Goo<T> specificGoo)
{
return specificGoo.Value;
}
var valuePropInfo = goo.GetType()
.GetField("m_value", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (valuePropInfo != null)
{
var tempValue = valuePropInfo.GetValue(goo);
if (tempValue is T value)
{
return value;
}
}
// TODO: Potentially unwrap new rhino objects
throw new SpeckleException(
$"Internal value of goo {goo.GetType().Name} was not the provided type {typeof(T).Name}"
);
}
}
@@ -0,0 +1,74 @@
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;
using Rhino.Geometry;
using Speckle.Connectors.Common.Operations;
using Speckle.Converters.Common;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.Grasshopper8.HostApp;
public class GrasshopperSendOperation
{
private readonly IRootObjectSender _baseObjectSender;
private readonly IRootToSpeckleConverter _converter;
public GrasshopperSendOperation(IRootObjectSender baseObjectSender, IRootToSpeckleConverter converter)
{
_baseObjectSender = baseObjectSender;
_converter = converter;
}
public async Task<GrashopperSendOperationResult> Execute(
IReadOnlyDictionary<string, GH_Structure<IGH_Goo>> objects,
SendInfo sendInfo,
IProgress<CardProgress> onOperationProgressed,
CancellationToken ct = default
)
{
var elements = new List<Base>();
foreach (var keypair in objects)
{
elements.Add(ConvertGhStructureToCollection(keypair.Key, keypair.Value));
}
var buildResult = new Collection("Grasshopper Model") { elements = elements, ["version"] = 3 };
var result = await _baseObjectSender.Send(buildResult, sendInfo, onOperationProgressed, ct).ConfigureAwait(false);
return new(result.RootId);
}
private Collection ConvertGhStructureToCollection(string name, GH_Structure<IGH_Goo> tree)
{
var result = tree.Paths.Select(path =>
{
var pathElements = tree.get_Branch(path) as IList<IGH_Goo>;
var convertedElements = new List<Base>();
pathElements.NotNull();
foreach (var pathElement in pathElements)
{
try
{
var goo = pathElement.UnwrapGoo<GeometryBase>();
var converted = _converter.Convert(goo);
convertedElements.Add(converted);
}
catch (Exception e) when (!e.IsFatal())
{
Console.WriteLine(e);
}
}
return new Collection(path.ToString()) { elements = convertedElements };
});
return new Collection(name) { elements = result.Cast<Base>().ToList() };
}
}
public record GrashopperSendOperationResult(string RootId);
@@ -9,6 +9,8 @@ namespace Speckle.Connectors.Grasshopper8.HostApp;
public abstract record SpeckleUrlModelResource(string Server, string ProjectId)
{
public abstract Task<ReceiveInfo> GetReceiveInfo(Client client, CancellationToken cancellationToken = default);
public abstract Task<SendInfo> GetSendInfo(Client client, CancellationToken cancellationToken = default);
}
public record SpeckleUrlLatestModelVersionResource(string Server, string ProjectId, string ModelId)
@@ -35,6 +37,21 @@ public record SpeckleUrlLatestModelVersionResource(string Server, string Project
return info;
}
public override async Task<SendInfo> GetSendInfo(Client client, CancellationToken cancellationToken = default)
{
// We don't care about the return info, we just want to be sure we have access and everything exists.
await client.Project.Get(ProjectId, cancellationToken).ConfigureAwait(false);
await client.Model.Get(ModelId, ProjectId, cancellationToken).ConfigureAwait(false);
return new SendInfo(
client.Account.id,
new Uri(Server),
ProjectId,
ModelId,
"Grasshopper8" // TODO: Grab from the right place!
);
}
}
public record SpeckleUrlModelVersionResource(string Server, string ProjectId, string ModelId, string VersionId)
@@ -59,6 +76,21 @@ public record SpeckleUrlModelVersionResource(string Server, string ProjectId, st
return info;
}
public override async Task<SendInfo> GetSendInfo(Client client, CancellationToken cancellationToken = default)
{
// We don't care about the return info, we just want to be sure we have access and everything exists.
await client.Project.Get(ProjectId, cancellationToken).ConfigureAwait(false);
await client.Model.Get(ModelId, ProjectId, cancellationToken).ConfigureAwait(false);
return new SendInfo(
client.Account.id,
new Uri(Server),
ProjectId,
ModelId,
"Grasshopper8" // TODO: Grab from the right place!
);
}
}
public record SpeckleUrlModelObjectResource(string Server, string ProjectId, string ObjectId)
@@ -66,4 +98,7 @@ public record SpeckleUrlModelObjectResource(string Server, string ProjectId, str
{
public override Task<ReceiveInfo> GetReceiveInfo(Client client, CancellationToken cancellationToken = default) =>
throw new NotImplementedException("Object Resources are not supported yet");
public override Task<SendInfo> GetSendInfo(Client client, CancellationToken cancellationToken = default) =>
throw new NotImplementedException("Object Resources are not supported yet");
}
@@ -1,14 +1,9 @@
using Grasshopper.Kernel;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.Grasshopper8.HostApp;
using Speckle.Converters.Rhino;
using Speckle.Sdk;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models.GraphTraversal;
namespace Speckle.Connectors.Grasshopper8;
@@ -23,15 +18,9 @@ public class PriorityLoader : GH_AssemblyPriority
{
var services = new ServiceCollection();
_disposableLogger = services.Initialize(HostApplications.Grasshopper, GetVersion());
services.AddRhinoConverters().AddConnectorUtils();
services.AddTransient<IHostObjectBuilder, GrasshopperHostObjectBuilder>();
services.AddTransient<GrasshopperReceiveOperation>();
services.AddSingleton(DefaultTraversal.CreateTraversalFunc());
services.AddScoped<RootObjectUnpacker>();
services.AddRhinoConverters().AddGrasshopper().AddConnectorUtils();
services.AddTransient<TraversalContextUnpacker>();
services.AddTransient<AccountManager>();
Container = services.BuildServiceProvider();
return GH_LoadingInstruction.Proceed;
}
@@ -0,0 +1,25 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.Grasshopper8.HostApp;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Models.GraphTraversal;
namespace Speckle.Connectors.Grasshopper8;
public static class ServiceRegistration
{
public static IServiceCollection AddGrasshopper(this IServiceCollection services)
{
services.AddTransient<IHostObjectBuilder, GrasshopperHostObjectBuilder>();
services.AddTransient<GrasshopperReceiveOperation>();
services.AddTransient<GrasshopperSendOperation>();
services.AddSingleton(DefaultTraversal.CreateTraversalFunc());
services.AddScoped<RootObjectUnpacker>();
services.AddTransient<TraversalContextUnpacker>();
services.AddTransient<AccountManager>();
return services;
}
}
@@ -11,27 +11,44 @@ public static class DisplayMeshExtractor
var renderMeshes = obj.GetMeshes(RG.MeshType.Render);
if (renderMeshes.Length == 0)
{
switch (obj)
{
case BrepObject brep:
renderMeshes = RG.Mesh.CreateFromBrep(brep.BrepGeometry, new(0.05, 0.05));
break;
case ExtrusionObject extrusion:
renderMeshes = RG.Mesh.CreateFromBrep(extrusion.ExtrusionGeometry.ToBrep(), new(0.05, 0.05));
break;
case SubDObject subDObject:
#pragma warning disable CA2000
var mesh = RG.Mesh.CreateFromSubD(subDObject.Geometry as RG.SubD, 0);
#pragma warning restore CA2000
renderMeshes = [mesh];
break;
default:
throw new ConversionException($"Unsupported object for display mesh generation {obj.GetType().FullName}");
}
renderMeshes = GetDisplayMeshes(obj.Geometry);
}
var joinedMesh = new RG.Mesh();
joinedMesh.Append(renderMeshes);
return joinedMesh;
}
public static RG.Mesh GetDisplayMesh(RG.GeometryBase obj)
{
// note: unsure this is nice, we get bigger meshes - we should to benchmark (conversion time vs size tradeoffs)
var renderMeshes = GetDisplayMeshes(obj);
var joinedMesh = new RG.Mesh();
joinedMesh.Append(renderMeshes);
return joinedMesh;
}
private static RG.Mesh[] GetDisplayMeshes(RG.GeometryBase obj)
{
RG.Mesh[] renderMeshes;
switch (obj)
{
case RG.Brep brep:
renderMeshes = RG.Mesh.CreateFromBrep(brep, new(0.05, 0.05));
break;
case RG.Extrusion extrusion:
renderMeshes = RG.Mesh.CreateFromBrep(extrusion.ToBrep(), new(0.05, 0.05));
break;
case RG.SubD subDObject:
#pragma warning disable CA2000
var mesh = RG.Mesh.CreateFromSubD(subDObject, 0);
#pragma warning restore CA2000
renderMeshes = [mesh];
break;
default:
throw new ConversionException($"Unsupported object for display mesh generation {obj.GetType().FullName}");
}
return renderMeshes;
}
}
@@ -0,0 +1,41 @@
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Converters.Rhino.ToSpeckle.Encoding;
using Speckle.Converters.Rhino.ToSpeckle.Meshing;
using Speckle.Sdk.Models;
namespace Speckle.Converters.Rhino.ToSpeckle.TopLevel;
[NameAndRankValue(nameof(RG.Brep), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)]
public class BrepToSpeckleTopLevelConverter : IToSpeckleTopLevelConverter
{
private readonly ITypedConverter<RG.Mesh, SOG.Mesh> _meshConverter;
private readonly IConverterSettingsStore<RhinoConversionSettings> _settingsStore;
public BrepToSpeckleTopLevelConverter(
ITypedConverter<RG.Mesh, SOG.Mesh> meshConverter,
IConverterSettingsStore<RhinoConversionSettings> settingsStore
)
{
_meshConverter = meshConverter;
_settingsStore = settingsStore;
}
public Base Convert(object target)
{
var brepObject = (RG.Brep)target;
var brepEncoding = RawEncodingCreator.Encode(brepObject, _settingsStore.Current.Document);
var mesh = DisplayMeshExtractor.GetDisplayMesh(brepObject);
var displayValue = new List<SOG.Mesh> { _meshConverter.Convert(mesh) };
var bx = new SOG.BrepX()
{
displayValue = displayValue,
encodedValue = brepEncoding,
units = _settingsStore.Current.SpeckleUnits
};
return bx;
}
}