Compare commits

...

53 Commits

Author SHA1 Message Date
Jedd Morgan 27a7d72de3 Merge pull request #1051 from specklesystems/dev
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
dev -> main
2025-08-26 17:20:06 +01:00
Jedd Morgan 678f113d05 Merge pull request #1050 from specklesystems/jrm/main-to-dev2
Update dev with main
2025-08-26 16:54:05 +01:00
Jedd Morgan 92da66bbbb Merge branch 'dev' into jrm/main-to-dev2 2025-08-26 16:53:25 +01:00
Oğuzhan Koral 79a5228899 Fix: invert boolean flag (#1049)
* Introduce global config

* invert boolean flag
2025-08-26 18:52:21 +03:00
Adam Hathcock 4d9411de42 fix(revit): Revit files persist model card data to a file like Tekla instead of into the file (#1045)
* Revit files persist model card data to a file like Tekla instead of into the file

* fmt

* fixes logger

* Update Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-26 16:31:48 +01:00
Oğuzhan Koral 3780747992 Introduce global config (#1041) 2025-08-26 09:54:50 +00:00
Mucahit Bilal GOKER 4514b1b831 Merge pull request #1039 from specklesystems/bilal/cnx-2354-add-area-scheme-property-to-areas-from-revit
feat(revit): Add Area Scheme
2025-08-24 18:29:02 +03:00
Björn Steinhagen 2bbbbf6204 Merge branch 'dev' into bilal/cnx-2354-add-area-scheme-property-to-areas-from-revit 2025-08-24 17:16:49 +02:00
Björn Steinhagen e1b5dea3f7 fix: csharpier 2025-08-24 17:15:29 +02:00
Mucahit Bilal GOKER 2d2c274030 Merge pull request #1040 from specklesystems/bilal/cnx-2121-duplicated-geometry-when-parts-visibility-is-on
fix(revit): Exclude host element from view filter when parts are enabled
2025-08-24 18:15:11 +03:00
Björn Steinhagen 81dd72a281 Merge branch 'dev' into bilal/cnx-2354-add-area-scheme-property-to-areas-from-revit 2025-08-24 17:12:17 +02:00
Björn Steinhagen b82349478c Merge branch 'dev' into bilal/cnx-2121-duplicated-geometry-when-parts-visibility-is-on 2025-08-24 17:09:08 +02:00
bimgeek 7d0690f7a0 bjorn pasha asked for these changes 2025-08-24 18:08:55 +03:00
bimgeek 62a0cb895d pasha bjorns comments 2025-08-24 17:56:21 +03:00
Claire Kuang f28ce73d33 Merge pull request #1035 from specklesystems/claire/cnx-2310-grasshopper-and-rhino-not-sending-correct-double-precision
fix(rhino/grasshopper): always use double precision meshes and current doc mesh settings
2025-08-22 16:56:26 +01:00
bimgeek 15425c5328 no need for 2 db queries 2025-08-22 15:12:33 +03:00
bimgeek 7c645e3c51 collector disposal 2025-08-22 15:05:35 +03:00
bimgeek 795d068175 exclude parts from view filter 2025-08-22 14:57:43 +03:00
Claire Kuang 90c2bd2873 Merge branch 'dev' into claire/cnx-2310-grasshopper-and-rhino-not-sending-correct-double-precision 2025-08-22 12:45:36 +01:00
Claire Kuang bd7a3c7c43 Merge pull request #1037 from specklesystems/claire/revit-snapping
feat(revit): adds snapping for mesh vertices and nurbs curves
2025-08-22 12:36:10 +01:00
Claire Kuang ea976309bc Merge branch 'dev' into claire/revit-snapping 2025-08-22 12:31:33 +01:00
Claire Kuang 1b5787274a Merge pull request #1010 from specklesystems/claire/cnx-2167-material-quantity-extraction-for-revit-railings
feat(revit): adds material quantities for railings
2025-08-22 12:31:17 +01:00
Björn 7e595deabc Merge branch 'dev' into claire/revit-snapping 2025-08-22 11:42:22 +02:00
Claire Kuang 66091b2b73 Merge branch 'dev' into claire/cnx-2167-material-quantity-extraction-for-revit-railings 2025-08-22 10:37:30 +01:00
Claire Kuang 4f8d8d4f07 Merge pull request #1036 from specklesystems/bjorn/cnx-2212-grasshopper-deconstruct-node-should-encapsulate-all-input
fix(grasshopper): handle multiple objects with different fields in deconstruct node
2025-08-22 09:57:20 +01:00
bimgeek 4fba12f966 add area scheme switch statement 2025-08-21 19:47:52 +03:00
Claire Kuang 348975c33d Merge branch 'dev' into claire/cnx-2310-grasshopper-and-rhino-not-sending-correct-double-precision 2025-08-21 10:33:54 +01:00
Björn cd6888868e fix: flickering, dynamic outputs and docstrings 2025-08-21 10:10:07 +02:00
Björn f2d4e64005 Merge remote-tracking branch 'origin/dev' into bjorn/cnx-2212-grasshopper-deconstruct-node-should-encapsulate-all-input 2025-08-21 08:54:14 +02:00
Björn Steinhagen a92b88f6d3 fix: replace list access with progressive field discovery in deconstruct component 2025-08-21 08:19:10 +02:00
dependabot[bot] abfdbdeffa chore(deps): bump actions/checkout from 4 to 5 (#1034)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-20 22:24:20 +01:00
Claire Kuang efe66e7e98 Merge branch 'dev' into claire/cnx-2167-material-quantity-extraction-for-revit-railings 2025-08-20 16:28:47 +01:00
Claire Kuang c3fa1bb0dc Update LocalToGlobalToDirectShapeConverter.cs 2025-08-20 15:32:12 +01:00
Claire Kuang e487981e5b adds snapping for mesh, curve, and points 2025-08-20 15:06:44 +01:00
Claire Kuang 9a6dda629b also fixes an issue with sending low res meshes.
uses current doc settings to convert display meshes for breps etc
2025-08-19 16:32:03 +01:00
Björn 46e7d6e432 chore: re-add comments 2025-08-19 17:04:12 +02:00
Björn b9f4845fa7 feat: handle multiple inputs 2025-08-19 16:49:20 +02:00
Björn 36863efc5a refactor: update SolveInstance to collect multiple input objects 2025-08-19 16:01:41 +02:00
Björn a0ce883a3f feat: DeconstructSpeckleParam input to accept multiple objects 2025-08-19 15:48:25 +02:00
Claire Kuang bc0fe17d08 Update MeshToSpeckleConverter.cs 2025-08-19 14:38:33 +01:00
Claire Kuang 2e52409db6 Update DisplayMeshExtractor.cs 2025-08-19 14:31:21 +01:00
Claire Kuang f434cde7b3 removes model far from origin logic from rhino 2025-08-19 14:23:12 +01:00
Claire Kuang 3e596cac29 Update MeshToSpeckleConverter.cs 2025-08-19 10:00:10 +01:00
Jedd Morgan 876d5c1bfe fix(rhino-importer): Do not save to objects sqlite cache (#1033)
* First pass

* ifc importer to not save objects to sqlite
2025-08-18 15:50:03 +01:00
Oğuzhan Koral 3424de9130 Merge pull request #1032 from specklesystems/dev
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Update dev to main
2025-08-18 11:33:50 +01:00
Jedd Morgan 279e900105 feat(file_import)!: .NET job processor (#992)
* First Pass

* commit transaction

* wip1

* rhino round2

* wip

* net8

* Got the importer importing!

* Refactor to separate containers

* New queuing queries

* generate solutions

* Generate solutions

* fixed tests

* Rhino headless imports

* minor fixes

* logging

* fix activity factory

* sketchup configs

* Add more logging

* Format

* Clean up the diff a bit

* relock

* delete bad launchsettings
2025-08-18 10:29:15 +00:00
Claire Kuang ac7398be49 fix(grasshopper): fix casting issues for model objects (#1031)
* adds missing path and properties to model object casting

* slight optimization to not retrieve material twice when color is inherited from material

* enables casting of non-geometrybase geometry like points
2025-08-18 11:24:41 +01:00
Jedd Morgan f1f17eea3d Merge pull request #1021 from specklesystems/jrm/dev-to-main
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Dev -> Main
2025-08-07 15:16:38 +01:00
Jedd Morgan 642607acad Merge branch 'main' into jrm/dev-to-main 2025-08-07 15:09:09 +01:00
Claire Kuang 7f3b23e71e Merge branch 'dev' into claire/cnx-2167-material-quantity-extraction-for-revit-railings 2025-08-07 09:00:26 +01:00
Claire Kuang 9b0a6c3202 Merge branch 'dev' into claire/cnx-2167-material-quantity-extraction-for-revit-railings 2025-07-31 17:08:45 +01:00
Claire Kuang de662e4a2b adds material quantities for pipes 2025-07-30 21:00:17 +01:00
Claire Kuang 2cb7211734 Merge pull request #1009 from specklesystems/dev
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
dev to main
2025-07-30 12:42:01 +01:00
72 changed files with 2618 additions and 1093 deletions
+2 -2
View File
@@ -7,7 +7,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -28,7 +28,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
+2 -2
View File
@@ -16,7 +16,7 @@ jobs:
file_version: ${{ steps.set-version.outputs.file_version }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -83,7 +83,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -3,48 +3,45 @@ using Autodesk.Revit.DB.ExtensibleStorage;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Events;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Utils;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.SQLite;
namespace Speckle.Connectors.Revit.HostApp;
// POC: should be interfaced out
internal sealed class RevitDocumentStore : DocumentModelStore
{
// POC: move to somewhere central?
private static readonly Guid s_revitDocumentStoreId = new("D35B3695-EDC9-4E15-B62A-D3FC2CB83FA3");
private readonly ILogger<RevitDocumentStore> _logger;
private readonly IAppIdleManager _idleManager;
private readonly RevitContext _revitContext;
private readonly DocumentModelStorageSchema _documentModelStorageSchema;
private readonly IdStorageSchema _idStorageSchema;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly IThreadContext _threadContext;
private readonly ISqLiteJsonCacheManager _jsonCacheManager;
public RevitDocumentStore(
ILogger<DocumentModelStore> logger,
IAppIdleManager idleManager,
RevitContext revitContext,
IJsonSerializer jsonSerializer,
DocumentModelStorageSchema documentModelStorageSchema,
IdStorageSchema idStorageSchema,
ITopLevelExceptionHandler topLevelExceptionHandler,
IThreadContext threadContext,
IRevitTask revitTask
IRevitTask revitTask,
ISqLiteJsonCacheManagerFactory jsonCacheManagerFactory,
ILogger<RevitDocumentStore> logger
)
: base(logger, jsonSerializer)
{
_jsonCacheManager = jsonCacheManagerFactory.CreateForUser("ConnectorsFileData");
_idleManager = idleManager;
_revitContext = revitContext;
_documentModelStorageSchema = documentModelStorageSchema;
_idStorageSchema = idStorageSchema;
_topLevelExceptionHandler = topLevelExceptionHandler;
_threadContext = threadContext;
_logger = logger;
UIApplication uiApplication = _revitContext.UIApplication.NotNull();
@@ -101,80 +98,36 @@ internal sealed class RevitDocumentStore : DocumentModelStore
return;
}
_threadContext
.RunOnMain(() =>
{
//if not the same active document then don't save the current cards to a bad document!
if (!EnsureActiveDocumentIsSame(document))
{
return;
}
using Transaction t = new(document, "Speckle Write State");
t.Start();
using DataStorage ds = GetSettingsDataStorage(document) ?? DataStorage.Create(document);
using Entity stateEntity = new(_documentModelStorageSchema.GetSchema());
string serializedModels = Serialize();
stateEntity.Set("contents", serializedModels);
using Entity idEntity = new(_idStorageSchema.GetSchema());
idEntity.Set("Id", s_revitDocumentStoreId);
ds.SetEntity(idEntity);
ds.SetEntity(stateEntity);
t.Commit();
})
.FireAndForget();
}
private bool EnsureActiveDocumentIsSame(Document document)
{
var localDoc = _revitContext.UIApplication?.ActiveUIDocument?.Document;
if (localDoc == null)
try
{
return false;
var key = document.ProjectInformation.UniqueId.NotNull();
_jsonCacheManager.UpdateObject(key, modelCardState);
}
catch (Exception ex) when (!ex.IsFatal())
{
var key = document.ProjectInformation.UniqueId.NotNull();
_logger.LogError(ex, "Failed to save model card state for document {DocumentId}", key);
}
return localDoc.Equals(document);
}
protected override void LoadState()
{
var stateEntity = GetSpeckleEntity(_revitContext.UIApplication?.ActiveUIDocument?.Document);
var document = _revitContext.UIApplication?.ActiveUIDocument?.Document;
// POC: this can happen? A: Not really, imho (dim) (Adam seyz yes it can if loading also triggers a save)
if (document == null)
{
return;
}
var stateEntity = GetSpeckleEntity(document);
if (stateEntity == null || !stateEntity.IsValid())
{
ClearAndSave();
return;
}
string modelsString = stateEntity.Get<string>("contents");
LoadFromString(modelsString);
}
private DataStorage? GetSettingsDataStorage(Document doc)
{
using FilteredElementCollector collector = new(doc);
FilteredElementCollector dataStorages = collector.OfClass(typeof(DataStorage));
foreach (Element element in dataStorages)
{
DataStorage dataStorage = (DataStorage)element;
Entity settingIdEntity = dataStorage.GetEntity(_idStorageSchema.GetSchema());
if (!settingIdEntity.IsValid())
{
continue;
}
Guid id = settingIdEntity.Get<Guid>("Id");
if (!id.Equals(s_revitDocumentStoreId))
{
continue;
}
return dataStorage;
}
return null;
var key = document.ProjectInformation.UniqueId.NotNull();
var state = _jsonCacheManager.GetObject(key);
LoadFromString(state);
}
private Entity? GetSpeckleEntity(Document? doc)
@@ -2,7 +2,9 @@ using Autodesk.Revit.DB;
using Speckle.Connectors.DUI.Exceptions;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.Utils;
using Speckle.Converters.RevitShared.Extensions;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk;
namespace Speckle.Connectors.RevitShared.Operations.Send.Filters;
@@ -75,8 +77,8 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
//this used to throw an exception, but we don't want to fail loudly if the view is not found
return [];
}
using var viewCollector = new FilteredElementCollector(_doc, view.Id);
var elementsInView = viewCollector.ToElements();
IEnumerable<Element> elementsInView = GetFilteredElementsForView(view);
// NOTE: FilteredElementCollector() includes sweeps and reveals from a wall family's definition and includes them as additional objects
// on this return. displayValue for Wall already includes these, therefore we end up with duplicate elements on wall sweeps
@@ -125,4 +127,52 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
_revitContext = revitContext;
_doc = _revitContext.UIApplication?.ActiveUIDocument.Document;
}
// NOTE: Element collector returns parts and source elements even when Parts Visibility is set as "Show Parts" only.
// Below function collects list of ids to exclude from final list.
private HashSet<ElementId> GetSourceElementIdsToExclude(IEnumerable<Element> elements)
{
var elementsToExclude = new HashSet<ElementId>();
foreach (var element in elements)
{
// check if element is a part
if (element.Category?.GetBuiltInCategory() == BuiltInCategory.OST_Parts && element is Part part)
{
try
{
// get source element ids from the part
var sourceIds = part.GetSourceElementIds();
if (sourceIds != null)
{
foreach (var sourceId in sourceIds)
{
elementsToExclude.Add(sourceId.HostElementId);
}
}
}
catch (Exception e) when (!e.IsFatal())
{
// silently continue processing other Parts if one fails
// this follows the pattern used elsewhere in the codebase
}
}
}
return elementsToExclude;
}
private IEnumerable<Element> GetFilteredElementsForView(View view)
{
using var viewCollector = new FilteredElementCollector(_doc, view.Id);
var allElements = viewCollector.ToElements();
// parts filtering when view is set to show Parts only (and overwrites allElements)
if (view.PartsVisibility == PartsVisibility.ShowPartsOnly)
{
var idsToExclude = GetSourceElementIdsToExclude(allElements);
return allElements.Where(e => !idsToExclude.Contains(e.Id));
}
return allElements;
}
}
@@ -343,7 +343,6 @@
"speckle.converters.rhino8": {
"type": "Project",
"dependencies": {
"RhinoCommon": "[8.9.24194.18121, )",
"Speckle.Converters.Common": "[1.0.0, )"
}
},
@@ -37,91 +37,173 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
protected override void SolveInstance(IGH_DataAccess da)
{
object data = new();
da.GetData(0, ref data);
List<OutputParamWrapper> outputParams = new();
switch (data)
// on first iteration, discover all fields from all objects to create stable output structure
if (da.Iteration == 0)
{
case SpeckleCollectionWrapperGoo collectionGoo when collectionGoo.Value != null:
// get children elements from the wrapper to override the elements prop while parsing
List<IGH_Goo> children = collectionGoo.Value.Elements.Select(o => ((SpeckleWrapper)o).CreateGoo()).ToList();
outputParams = ParseSpeckleWrapper(collectionGoo.Value, children);
break;
case SpeckleDataObjectWrapperGoo dataObjectGoo when dataObjectGoo.Value != null:
// get geometries from the wrapper to override the displayvalue prop while parsing
List<IGH_Goo> display = dataObjectGoo.Value.Geometries.Select(o => o.CreateGoo()).ToList();
outputParams = ParseSpeckleWrapper(dataObjectGoo.Value, null, display);
break;
case SpeckleGeometryWrapperGoo objectGoo when objectGoo.Value != null:
outputParams = ParseSpeckleWrapper(objectGoo.Value);
break;
case SpeckleBlockInstanceWrapperGoo blockInstanceGoo when blockInstanceGoo.Value != null:
outputParams = ParseSpeckleWrapper(blockInstanceGoo.Value);
break;
case SpeckleBlockDefinitionWrapperGoo blockDef:
outputParams = ParseSpeckleWrapper(blockDef.Value);
break;
case SpeckleMaterialWrapperGoo materialGoo when materialGoo.Value != null:
outputParams = ParseSpeckleWrapper(materialGoo.Value);
break;
HashSet<string> allFields = DiscoverAllFieldsFromInput();
case SpecklePropertyGroupGoo propGoo:
Name = $"properties ({propGoo.Value.Count})";
outputParams = new();
foreach (var key in propGoo.Value.Keys)
if (allFields.Count > 0)
{
var requiredOutputs = CreateOutputParamsFromFieldNames(allFields);
if (OutputMismatch(requiredOutputs))
{
ISpecklePropertyGoo value = propGoo.Value[key];
object? outputValue = value is SpecklePropertyGoo prop
? prop.Value
: value is SpecklePropertyGroupGoo propGroup
? propGroup
: value;
OutputParamWrapper output =
outputValue is IList
? CreateOutputParamByKeyValue(key, outputValue, GH_ParamAccess.list)
: CreateOutputParamByKeyValue(key, outputValue, GH_ParamAccess.item);
outputParams.Add(output);
OnPingDocument()?.ScheduleSolution(5, _ => CreateOutputs(requiredOutputs));
return;
}
break;
default:
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Type cannot be deconstructed: {data.GetType().Name}");
return;
}
}
// process current object normally
object data = new();
if (!da.GetData(0, ref data))
{
return;
}
var outputParams = DeconstructObject(data);
if (outputParams == null)
{
return;
}
// set component name based on the current object
NickName = Name;
if (da.Iteration == 0 && OutputMismatch(outputParams))
// set output data - fill missing fields with nulls for objects that don't have all fields
SetOutputData(da, outputParams);
}
/// <summary>
/// Discovers all unique field names from all input objects by looking at volatile data directly.
/// </summary>
private HashSet<string> DiscoverAllFieldsFromInput()
{
HashSet<string> allFields = new();
foreach (var item in Params.Input[0].VolatileData.AllData(true))
{
OnPingDocument()
.ScheduleSolution(
5,
_ =>
{
CreateOutputs(outputParams);
}
);
}
else
{
for (int i = 0; i < outputParams.Count; i++)
var objectOutputs = DeconstructObject(item);
if (objectOutputs != null)
{
var outParam = Params.Output[i];
var outParamWrapper = outputParams[i];
switch (outParam.Access)
foreach (var output in objectOutputs)
{
case GH_ParamAccess.item:
da.SetData(i, outParamWrapper.Value);
break;
case GH_ParamAccess.list:
da.SetDataList(i, outParamWrapper.Value as IList);
break;
allFields.Add(output.Param.Name);
}
}
}
return allFields;
}
/// <summary>
/// Creates output parameter wrappers from a set of field names, all with item access.
/// </summary>
private List<OutputParamWrapper> CreateOutputParamsFromFieldNames(HashSet<string> fieldNames) =>
fieldNames
.OrderBy(name => name)
.Select(fieldName => CreateOutputParamByKeyValue(fieldName, null, GH_ParamAccess.item))
.ToList();
/// <summary>
/// Deconstructs a single object into its constituent fields/properties.
/// </summary>
private List<OutputParamWrapper>? DeconstructObject(object data) =>
data switch
{
// get children elements from wrapper to override elements prop while parsing
SpeckleCollectionWrapperGoo collectionGoo when collectionGoo.Value != null
=> ParseSpeckleWrapper(
collectionGoo.Value,
collectionGoo.Value.Elements.Select(o => ((SpeckleWrapper)o).CreateGoo()).ToList()
),
// get geometries from wrapper to override displayValue prop while parsing
SpeckleDataObjectWrapperGoo dataObjectGoo when dataObjectGoo.Value != null
=> ParseSpeckleWrapper(
dataObjectGoo.Value,
null,
dataObjectGoo.Value.Geometries.Select(o => o.CreateGoo()).ToList()
),
SpeckleGeometryWrapperGoo objectGoo when objectGoo.Value != null => ParseSpeckleWrapper(objectGoo.Value),
SpeckleBlockInstanceWrapperGoo blockInstanceGoo when blockInstanceGoo.Value != null
=> ParseSpeckleWrapper(blockInstanceGoo.Value),
SpeckleBlockDefinitionWrapperGoo blockDef when blockDef.Value != null => ParseSpeckleWrapper(blockDef.Value),
SpeckleMaterialWrapperGoo materialGoo when materialGoo.Value != null => ParseSpeckleWrapper(materialGoo.Value),
SpecklePropertyGroupGoo propGoo when propGoo.Value != null => ParsePropertyGroup(propGoo),
_ => HandleUnsupportedType(data)
};
/// <summary>
/// Handles SpecklePropertyGroupGoo objects by extracting their key-value pairs.
/// </summary>
private List<OutputParamWrapper> ParsePropertyGroup(SpecklePropertyGroupGoo propGoo)
{
Name = $"properties ({propGoo.Value.Count})";
List<OutputParamWrapper> objectOutputs = new();
foreach (var key in propGoo.Value.Keys)
{
ISpecklePropertyGoo value = propGoo.Value[key];
object? outputValue = value switch
{
SpecklePropertyGoo prop => prop.Value,
SpecklePropertyGroupGoo propGroup => propGroup,
_ => value
};
objectOutputs.Add(CreateOutputParamByKeyValue(key, outputValue, GH_ParamAccess.item));
}
return objectOutputs;
}
/// <summary>
/// Handles unsupported object types by logging an error and returning null.
/// </summary>
private List<OutputParamWrapper>? HandleUnsupportedType(object data)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Type cannot be deconstructed: {data.GetType().Name}");
return null;
}
/// <summary>
/// Sets output data for the current iteration, filling missing fields with null values.
/// Uses a lookup dictionary for efficient field matching.
/// </summary>
private void SetOutputData(IGH_DataAccess da, List<OutputParamWrapper> currentOutputs)
{
if (Params.Output.Count == 0)
{
return;
}
// create a lookup for current outputs by field name
var outputLookup = currentOutputs.ToDictionary(o => o.Param.Name, o => o.Value);
// set data for each output parameter
for (int i = 0; i < Params.Output.Count; i++)
{
var outputParam = Params.Output[i];
// set the value if it exists, otherwise set null
object? value = outputLookup.TryGetValue(outputParam.Name, out var fieldValue) ? fieldValue : null;
switch (outputParam.Access)
{
case GH_ParamAccess.item:
da.SetData(i, value);
break;
case GH_ParamAccess.list:
da.SetDataList(i, value as IList ?? new List<object?>());
break;
}
}
}
private List<OutputParamWrapper> ParseSpeckleWrapper(
@@ -146,125 +228,146 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
return result;
}
// cycle through base props
// process each property of the Base object
foreach (var prop in @base.GetMembers(DynamicBaseMemberType.Instance | DynamicBaseMemberType.Dynamic))
{
// Convert and add to corresponding output structure
var value = prop.Value;
switch (value)
// skip internal dynamic property keys
if (prop.Key == nameof(Base.DynamicPropertyKeys))
{
case null:
result.Add(CreateOutputParamByKeyValue(prop.Key, null, GH_ParamAccess.item));
break;
continue;
}
case IList list:
List<object> nativeObjects = new();
// override list value if base is a collection and this is the elements prop, since this is empty if coming from a collectionwrapper
if (@base is Collection && prop.Key == "elements" && elements != null)
{
list = elements;
}
// override list value if base is a dataobject and this is the displayvalue prop, since this is empty if coming from a dataobject wrapper
if (@base is Speckle.Objects.Data.DataObject && prop.Key == "displayValue" && displayValue != null)
{
list = displayValue;
}
foreach (var x in list)
{
switch (x)
{
case SpeckleWrapper wrapper:
nativeObjects.Add(wrapper.CreateGoo());
break;
case Base xBase:
nativeObjects.AddRange(ConvertOrCreateWrapper(xBase));
break;
default:
nativeObjects.Add(x);
break;
}
}
result.Add(CreateOutputParamByKeyValue(prop.Key, nativeObjects, GH_ParamAccess.list));
break;
case Dictionary<string, object?> dict: // this should be treated a properties dict
SpecklePropertyGroupGoo propertyGoo = new();
propertyGoo.CastFrom(dict);
result.Add(CreateOutputParamByKeyValue(prop.Key, propertyGoo, GH_ParamAccess.item));
break;
case SpeckleWrapper wrapper:
result.Add(CreateOutputParamByKeyValue(prop.Key, wrapper.CreateGoo(), GH_ParamAccess.item));
break;
case Base baseValue:
result.Add(CreateOutputParamByKeyValue(prop.Key, ConvertOrCreateWrapper(baseValue), GH_ParamAccess.list));
break;
default:
// we don't want to output dynamic property keys
if (prop.Key == nameof(Base.DynamicPropertyKeys))
{
continue;
}
result.Add(CreateOutputParamByKeyValue(prop.Key, prop.Value, GH_ParamAccess.item));
break;
var outputParam = CreateOutputParamForProperty(prop, @base, elements, displayValue);
if (outputParam != null)
{
result.Add(outputParam);
}
}
return result;
}
/// <summary>
/// Creates an output parameter for a single property, handling different value types appropriately.
/// </summary>
private OutputParamWrapper CreateOutputParamForProperty(
KeyValuePair<string, object?> prop,
Base @base,
List<IGH_Goo>? elements,
List<IGH_Goo>? displayValue
) =>
prop.Value switch
{
null => CreateOutputParamByKeyValue(prop.Key, null, GH_ParamAccess.item),
IList list => CreateListOutputParam(prop.Key, list, @base, elements, displayValue),
Dictionary<string, object?> dict => CreateDictionaryOutputParam(prop.Key, dict),
SpeckleWrapper wrapper => CreateOutputParamByKeyValue(prop.Key, wrapper.CreateGoo(), GH_ParamAccess.item),
Base baseValue => CreateOutputParamByKeyValue(prop.Key, ConvertOrCreateWrapper(baseValue), GH_ParamAccess.list),
_ => CreateOutputParamByKeyValue(prop.Key, prop.Value, GH_ParamAccess.item)
};
/// <summary>
/// Creates an output parameter for list properties, with special handling for collection elements and display values.
/// </summary>
private OutputParamWrapper CreateListOutputParam(
string key,
IList list,
Base @base,
List<IGH_Goo>? elements,
List<IGH_Goo>? displayValue
)
{
// override list value for special cases
IList actualList = key switch
{
"elements" when @base is Collection && elements != null => elements,
"displayValue" when @base is Speckle.Objects.Data.DataObject && displayValue != null => displayValue,
_ => list
};
List<object> nativeObjects = new();
foreach (var item in actualList)
{
switch (item)
{
case SpeckleWrapper wrapper:
nativeObjects.Add(wrapper.CreateGoo());
break;
case Base baseItem:
nativeObjects.AddRange(ConvertOrCreateWrapper(baseItem));
break;
default:
nativeObjects.Add(item);
break;
}
}
return CreateOutputParamByKeyValue(key, nativeObjects, GH_ParamAccess.list);
}
/// <summary>
/// Creates an output parameter for dictionary properties, converting them to SpecklePropertyGroupGoo.
/// </summary>
private OutputParamWrapper CreateDictionaryOutputParam(string key, Dictionary<string, object?> dict)
{
SpecklePropertyGroupGoo propertyGoo = new();
propertyGoo.CastFrom(dict);
return CreateOutputParamByKeyValue(key, propertyGoo, GH_ParamAccess.item);
}
/// <summary>
/// Converts a Speckle Base object to host geometry or creates a wrapper if conversion fails.
/// Returns a list of SpeckleGeometryWrapperGoo objects.
/// </summary>
private List<SpeckleGeometryWrapperGoo> ConvertOrCreateWrapper(Base @base)
{
try
{
// convert the base and create a wrapper for each result
// attempt conversion to host geometry
List<(object, Base)> convertedBase = SpeckleConversionContext.Current.ConvertToHost(@base);
List<SpeckleGeometryWrapperGoo> convertedWrappers = new();
foreach ((object o, Base b) in convertedBase)
{
GeometryBase? g = o as GeometryBase;
SpeckleGeometryWrapper convertedWrapper =
new()
{
Base = b,
GeometryBase = g,
Name = b["name"] as string ?? "",
Color = null,
Material = null
};
convertedWrappers.Add(new(convertedWrapper));
}
return convertedWrappers;
return convertedBase.Select(CreateGeometryWrapper).ToList();
}
catch (ConversionException)
{
// some classes, like RawEncoding, have no direct conversion or fallback value.
// when this is the case, wrap it to allow users to further expand the object.
SpeckleGeometryWrapper convertedWrapper =
new()
{
Base = @base,
GeometryBase = null,
Name = @base[Constants.NAME_PROP] as string ?? "",
Color = null,
Material = null
};
return new() { new SpeckleGeometryWrapperGoo(convertedWrapper) };
// fallback: create wrapper without conversion for objects that can't be converted
return new List<SpeckleGeometryWrapperGoo> { CreateFallbackWrapper(@base) };
}
}
/// <summary>
/// Creates a SpeckleGeometryWrapperGoo from a converted geometry and base object pair.
/// </summary>
private SpeckleGeometryWrapperGoo CreateGeometryWrapper((object geometry, Base @base) converted)
{
SpeckleGeometryWrapper wrapper =
new()
{
Base = converted.@base,
GeometryBase = converted.geometry as GeometryBase,
Name = converted.@base["name"] as string ?? "",
Color = null,
Material = null
};
return new SpeckleGeometryWrapperGoo(wrapper);
}
/// <summary>
/// Creates a fallback wrapper for Base objects that cannot be converted to host geometry.
/// </summary>
private SpeckleGeometryWrapperGoo CreateFallbackWrapper(Base @base)
{
SpeckleGeometryWrapper wrapper =
new()
{
Base = @base,
GeometryBase = null,
Name = @base[Constants.NAME_PROP] as string ?? "",
Color = null,
Material = null
};
return new SpeckleGeometryWrapperGoo(wrapper);
}
private OutputParamWrapper CreateOutputParamByKeyValue(string key, object? value, GH_ParamAccess access)
{
Param_GenericObject param =
@@ -297,19 +400,17 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
return myParam;
}
public bool DestroyParameter(GH_ParameterSide side, int index)
{
return side == GH_ParameterSide.Output;
}
public bool DestroyParameter(GH_ParameterSide side, int index) => side == GH_ParameterSide.Output;
private void CreateOutputs(List<OutputParamWrapper> outputParams)
{
// TODO: better, nicer handling of creation/removal
// remove all existing output parameters
while (Params.Output.Count > 0)
{
Params.UnregisterOutputParameter(Params.Output[^1]);
}
// add new output parameters
foreach (var newParam in outputParams)
{
var param = new Param_GenericObject
@@ -322,11 +423,15 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
Params.RegisterOutputParam(param);
}
// notify Grasshopper of parameter changes
Params.OnParametersChanged();
VariableParameterMaintenance();
ExpireSolution(false);
}
/// <summary>
/// Determines if the current output parameter structure differs from the required structure.
/// </summary>
private bool OutputMismatch(List<OutputParamWrapper> outputParams)
{
if (Params.Output.Count != outputParams.Count)
@@ -334,10 +439,10 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
return true;
}
var count = 0;
foreach (var newParam in outputParams)
for (int i = 0; i < outputParams.Count; i++)
{
var oldParam = Params.Output[count];
var newParam = outputParams[i];
var oldParam = Params.Output[i];
if (
oldParam.NickName != newParam.Param.NickName
|| oldParam.Name != newParam.Param.Name
@@ -346,7 +451,6 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
{
return true;
}
count++;
}
return false;
@@ -22,21 +22,51 @@ public partial class SpecklePropertyGroupGoo : GH_Goo<Dictionary<string, ISpeckl
return CastFromModelObject(modelObject.UserText);
case ModelUserText userText:
Dictionary<string, ISpecklePropertyGoo> dictionary = new();
foreach (KeyValuePair<string, string> entry in userText)
{
SpecklePropertyGoo value = new() { Value = entry.Value };
dictionary.Add(entry.Key, value);
}
Value = dictionary;
return true;
var processedDictionary = ConvertToNested(userText.ToDictionary(o => o.Key, o => (object)o.Value));
return CastFrom(processedDictionary);
default:
return false;
}
}
// Property keys may already be concatenated with the `.` char, eg if baked from grasshopper.
public Dictionary<string, object> ConvertToNested(Dictionary<string, object> flatDict)
{
var nestedDict = new Dictionary<string, object>();
foreach (string keyPath in flatDict.Keys)
{
var keys = keyPath.Split('.');
var current = nestedDict;
for (int i = 0; i < keys.Length; i++)
{
var key = keys[i];
if (i == keys.Length - 1)
{
current[key] = flatDict[keyPath];
}
else
{
if (!current.TryGetValue(key, out var next))
{
var newDict = new Dictionary<string, object>();
current[key] = newDict;
current = newDict;
}
else
{
current = (Dictionary<string, object>)next;
}
}
}
}
return nestedDict;
}
private bool CastToModelObject<T>(ref T target)
{
var type = typeof(T);
@@ -35,19 +35,20 @@ public partial class SpeckleGeometryWrapperGoo : GH_Goo<SpeckleGeometryWrapper>,
private bool HandleModelObject(ModelObject modelObject)
{
modelObject.CastTo<GeometryBase>(out GeometryBase? geometryBase);
if (geometryBase is null)
modelObject.CastTo<IGH_GeometricGoo>(out IGH_GeometricGoo? geometryGoo);
if (geometryGoo is null)
{
throw new InvalidOperationException($"Could not retrieve geometry from model object.");
}
GeometryBase geometryBase = geometryGoo.ToGeometryBase();
Base converted = SpeckleConversionContext.Current.ConvertToSpeckle(geometryBase);
// get layer, props, color, and mat
SpeckleCollectionWrapper? collection = GetLayerCollectionFromModelObject(modelObject);
SpecklePropertyGroupGoo? props = GetPropsFromModelObjectAndAssignToBase(modelObject, converted);
Color? color = GetColorFromModelObject(modelObject);
SpeckleMaterialWrapper? material = GetMaterialFromModelObject(modelObject);
Color? color = GetColorFromModelObject(modelObject, material);
// get the definition if this is an instance
SpeckleBlockDefinitionWrapper? definition = GetBlockDefinition(geometryBase);
@@ -189,6 +190,7 @@ public partial class SpeckleGeometryWrapperGoo : GH_Goo<SpeckleGeometryWrapper>,
Transform = instance.Xform,
Definition = definition, // May be null in pure Grasshopper workflows
Parent = parent,
Path = parent?.Path ?? new(),
Name = name,
Color = color,
Material = mat,
@@ -200,6 +202,7 @@ public partial class SpeckleGeometryWrapperGoo : GH_Goo<SpeckleGeometryWrapper>,
GeometryBase = geometryBase,
Base = @base,
Parent = parent,
Path = parent?.Path ?? new(),
Name = name,
Color = color,
Material = mat,
@@ -268,7 +271,7 @@ public partial class SpeckleGeometryWrapperGoo : GH_Goo<SpeckleGeometryWrapper>,
return null;
}
private Color? GetColorFromModelObject(ModelObject modelObject)
private Color? GetColorFromModelObject(ModelObject modelObject, SpeckleMaterialWrapper? material)
{
// we need to retrieve the actual color by the color source (otherwise will return default color for anything other than by object)
int? argb = null;
@@ -281,8 +284,10 @@ public partial class SpeckleGeometryWrapperGoo : GH_Goo<SpeckleGeometryWrapper>,
argb = modelObject.Display.Color?.Color.ToArgb();
break;
case ObjectColorSource.ColorFromMaterial:
Rhino.Render.RenderMaterial? mat = GetRenderMaterial(modelObject);
argb = mat?.ToMaterial(Rhino.Render.RenderTexture.TextureGeneration.Skip)?.DiffuseColor.ToArgb();
if (material is not null)
{
argb = material.Material.diffuse;
}
break;
default:
break;
@@ -347,7 +347,6 @@
"speckle.converters.rhino8": {
"type": "Project",
"dependencies": {
"RhinoCommon": "[8.9.24194.18121, )",
"Speckle.Converters.Common": "[1.0.0, )"
}
},
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

@@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">
<!--This csproj is NOT designed to be run as a rhino plugin-->
<!--This csproj is NOT the rhino importer-->
<!--This csproj is the connector code used by the Speckle.Importers.Rhino project-->
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Configurations>Debug;Release;Local</Configurations>
<RhinoVersion>8</RhinoVersion>
<DefineConstants>$(DefineConstants);RHINO8;RHINO7_OR_GREATER;RHINO8_OR_GREATER</DefineConstants>
<TargetExt>.dll</TargetExt>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<UseWpf>true</UseWpf>
<UseWindowsForms>true</UseWindowsForms>
<GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources>
<!-- .NET Core uses this to move native dependencies into a root for runtime selection and usage for non-windows development https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#enablewindowstargeting -->
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <!--This is needed for managed dependencies-->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <!--This is needed for the rest-->
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RhinoCommon" IncludeAssets="compile; build" PrivateAssets="all" VersionOverride="8.21.25188.17001"/>
<PackageReference Include="RhinoWindows" IncludeAssets="compile; build" PrivateAssets="all" VersionOverride="8.21.25188.17001"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Converters\Rhino\Speckle.Converters.Rhino8\Speckle.Converters.Rhino8.csproj" />
<ProjectReference Include="..\..\..\Sdk\Speckle.Connectors.Common\Speckle.Connectors.Common.csproj" />
<ProjectReference Include="..\..\..\DUI3\Speckle.Connectors.DUI.WebView\Speckle.Connectors.DUI.WebView.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="EmbeddedResources\**\*" />
<EmbeddedResource Include="Resources\**\*.ico" />
</ItemGroup>
<Import Project="..\Speckle.Connectors.RhinoShared\Speckle.Connectors.RhinoShared.projitems" Label="Shared" />
<ItemGroup>
<None Include="..\Toolbars\Speckle.Connectors.Rhino.rui" Link="$(AssemblyName).rui" CopyToOutputDirectory="PreserveNewest"/>
</ItemGroup>
</Project>
@@ -235,9 +235,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1-rc.1, )",
"Speckle.Sdk": "[3.5.1-rc.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1-rc.1, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -261,7 +261,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1-rc.1, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.rhino8": {
@@ -311,18 +311,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1-rc.1, )",
"resolved": "3.5.1-rc.1",
"contentHash": "kkHlYdOdTuky7BvyU3fRayS5P/3E3EDQ6yBbwWqQqjGN3c/fozqI+20Xm87KJrkMb/9XpMfQxMfZX1n4UN80sA==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.5.1-rc.1"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.1-rc.1, )",
"resolved": "3.5.1-rc.1",
"contentHash": "ZyNQI1IyWSAQ4Xc72v0WOeXon9BwtpIiw6wCIT7GrRjymSe5jXW/qMpG3YpBMccGPljCDZJFDmd87fKnWNRt3w==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -330,14 +330,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1-rc.1"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1-rc.1, )",
"resolved": "3.5.1-rc.1",
"contentHash": "zDaocTPBFl2M5NkMsE9VqTLgbY7wy/MrE21JMBqOOcxHVtwDNOlSY9npBWDy8aAUEl6eJEKPoQr2B3Zd/RmJ9g=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -78,6 +78,24 @@ public class LocalToGlobalToDirectShapeConverter
.DirectShapeLibrary.GetDirectShapeLibrary(_converterSettings.Current.Document)
.FindDefinition(target.atomicObject.applicationId ?? target.atomicObject.id.NotNull());
result.SetShape(def);
// add snapping references for meshes and curves
foreach (var shape in def)
{
switch (shape)
{
case DB.Mesh m:
foreach (var v in m.Vertices)
{
result.AddReferencePoint(v);
}
break;
case DB.Curve c:
result.AddReferenceCurve(c);
break;
}
}
return result; // note fast exit here
}
@@ -22,15 +22,14 @@ public class ClassPropertiesExtractor
{
Dictionary<string, object?> elementPropertiesDict = ExtractElementProperties(element);
// add type specific props not included in parameters.
// so far, no extra props are needed
/*
// type specific properties
switch (element)
{
default:
// area scheme for area elements
case DB.Area area:
elementPropertiesDict.Add("areaScheme", area.AreaScheme?.Name);
break;
}
*/
return elementPropertiesDict;
}
@@ -44,9 +44,27 @@ public class MaterialQuantitiesToSpeckleLite : ITypedConverter<DB.Element, Dicti
public Dictionary<string, object> Convert(DB.Element target)
{
Dictionary<string, object> quantities = new();
if (target.Category?.HasMaterialQuantities ?? false) //category can be null
switch (target)
{
foreach (DB.ElementId? matId in target.GetMaterialIds(false))
case DBA.Railing railing:
// railings can have subelements including top rails, hand rails, and balusters.
// they also do *not* have any materials associated with their category.
List<DB.ElementId> railingElementIds = [railing.GetTypeId(), railing.TopRail, .. railing.GetHandRails()];
ProcessMaterialsByElementTypes(railingElementIds, quantities);
break;
default:
ProcessMaterialsByCategory(target, quantities);
break;
}
return quantities;
}
private void ProcessMaterialsByCategory(DB.Element element, Dictionary<string, object> quantities)
{
if (element.Category?.HasMaterialQuantities ?? false) //category can be null
{
foreach (DB.ElementId? matId in element.GetMaterialIds(false))
{
if (matId is null)
{
@@ -56,11 +74,18 @@ public class MaterialQuantitiesToSpeckleLite : ITypedConverter<DB.Element, Dicti
var materialQuantity = new Dictionary<string, object>();
var unitSettings = _converterSettings.Current.Document.GetUnits();
// add material props
if (TryAddMaterialPropertiesToQuantitiesDict(matId, materialQuantity, out string matName))
{
quantities[matName] = materialQuantity;
}
// add area and volume props
var areaUnitType = unitSettings.GetFormatOptions(DB.SpecTypeId.Area).GetUnitTypeId();
AddMaterialProperty(
materialQuantity,
"area",
_scalingService.Scale(target.GetMaterialArea(matId, false), areaUnitType),
_scalingService.Scale(element.GetMaterialArea(matId, false), areaUnitType),
areaUnitType
);
@@ -68,57 +93,142 @@ public class MaterialQuantitiesToSpeckleLite : ITypedConverter<DB.Element, Dicti
AddMaterialProperty(
materialQuantity,
"volume",
_scalingService.Scale(target.GetMaterialVolume(matId), volumeUnitType),
_scalingService.Scale(element.GetMaterialVolume(matId), volumeUnitType),
volumeUnitType
);
}
}
}
if (_converterSettings.Current.Document.GetElement(matId) is DB.Material material)
private void ProcessMaterialsByElementTypes(List<DB.ElementId> elementIds, Dictionary<string, object> quantities)
{
Dictionary<DB.ElementId, double> matLengths = new(); // stores mat id to total length found for mat
foreach (DB.ElementId elementId in elementIds)
{
if (
_converterSettings.Current.Document.GetElement(elementId) is DB.Element element
&& _converterSettings.Current.Document.GetElement(element.GetTypeId()) is DB.ElementType elementType
)
{
DB.ElementId elementMatId = DB.ElementId.InvalidElementId;
foreach (DB.Parameter param in elementType.Parameters)
{
materialQuantity["materialName"] = material.Name;
materialQuantity["materialCategory"] = material.MaterialCategory;
materialQuantity["materialClass"] = material.MaterialClass;
// get StructuralAssetId (or try to)
DB.ElementId structuralAssetId = material.StructuralAssetId;
if (structuralAssetId != DB.ElementId.InvalidElementId)
DB.Definition def = param.Definition;
if (param.StorageType == DB.StorageType.ElementId && def.GetDataType() == DB.SpecTypeId.Reference.Material)
{
StructuralAssetProperties structuralAssetProperties = _structuralAssetExtractor.TryGetProperties(
structuralAssetId
);
elementMatId = param.AsElementId();
break;
}
}
materialQuantity["structuralAsset"] = structuralAssetProperties.Name;
AddMaterialProperty(
materialQuantity,
"density",
structuralAssetProperties.Density,
structuralAssetProperties.DensityUnitId
);
// more reliable way of determining material type (wood/concrete/type) as it uses Revit enum
// materialClass, materialCategory etc. are user string inputs
materialQuantity["materialType"] = structuralAssetProperties.MaterialType;
// Only add compressive strength for concrete materials (used by F+E for Automate)
if (
structuralAssetProperties.MaterialType == "Concrete"
&& structuralAssetProperties.CompressiveStrength.HasValue
)
if (elementMatId != DB.ElementId.InvalidElementId)
{
// try get the length from the element
foreach (DB.Parameter eParam in element.Parameters)
{
DB.Definition eParamDef = eParam.Definition;
var forgeTypeId = eParamDef.GetDataType();
if (forgeTypeId == DB.SpecTypeId.Length)
{
AddMaterialProperty(
materialQuantity,
"compressiveStrength",
structuralAssetProperties.CompressiveStrength.Value,
structuralAssetProperties.CompressiveStrengthUnitId!
);
double length = eParam.AsDouble();
if (matLengths.TryGetValue(elementMatId, out double _))
{
matLengths[elementMatId] += length;
}
else
{
matLengths.Add(elementMatId, length);
}
}
}
quantities[material.Name] = materialQuantity;
}
}
}
return quantities;
foreach (var entry in matLengths)
{
var materialQuantity = new Dictionary<string, object>();
var unitSettings = _converterSettings.Current.Document.GetUnits();
// add material props
if (TryAddMaterialPropertiesToQuantitiesDict(entry.Key, materialQuantity, out string matName))
{
quantities[matName] = materialQuantity;
// add length prop
var lengthUnitType = unitSettings.GetFormatOptions(DB.SpecTypeId.Length).GetUnitTypeId();
AddMaterialProperty(
materialQuantity,
"length",
_scalingService.Scale(entry.Value, lengthUnitType),
lengthUnitType
);
}
}
}
/// <summary>
/// Adds the material properties (like name, category, and class) to the material quantity dictionary
/// </summary>
/// <param name="matId">the material id</param>
/// <param name="materialQuantity"></param>
/// <param name="matName"></param>
/// <returns>true if material is found, false if not</returns>
private bool TryAddMaterialPropertiesToQuantitiesDict(
DB.ElementId matId,
Dictionary<string, object> materialQuantity,
out string matName
)
{
matName = "";
if (_converterSettings.Current.Document.GetElement(matId) is DB.Material material)
{
materialQuantity["materialName"] = material.Name;
materialQuantity["materialCategory"] = material.MaterialCategory;
materialQuantity["materialClass"] = material.MaterialClass;
// get StructuralAssetId (or try to)
DB.ElementId structuralAssetId = material.StructuralAssetId;
if (structuralAssetId != DB.ElementId.InvalidElementId)
{
StructuralAssetProperties structuralAssetProperties = _structuralAssetExtractor.TryGetProperties(
structuralAssetId
);
materialQuantity["structuralAsset"] = structuralAssetProperties.Name;
AddMaterialProperty(
materialQuantity,
"density",
structuralAssetProperties.Density,
structuralAssetProperties.DensityUnitId
);
// more reliable way of determining material type (wood/concrete/type) as it uses Revit enum
// materialClass, materialCategory etc. are user string inputs
materialQuantity["materialType"] = structuralAssetProperties.MaterialType;
// Only add compressive strength for concrete materials (used by F+E for Automate)
if (
structuralAssetProperties.MaterialType == "Concrete"
&& structuralAssetProperties.CompressiveStrength.HasValue
)
{
AddMaterialProperty(
materialQuantity,
"compressiveStrength",
structuralAssetProperties.CompressiveStrength.Value,
structuralAssetProperties.CompressiveStrengthUnitId!
);
}
}
matName = material.Name;
return true;
}
return false;
}
/// <summary>
@@ -129,7 +239,7 @@ public class MaterialQuantitiesToSpeckleLite : ITypedConverter<DB.Element, Dicti
/// <param name="value">The numeric value of the property</param>
/// <param name="unitId">The Forge type ID representing the units of the property</param>
/// <remarks>
/// Saves code when used repeatedbly. Etabs implements an extension method to dicts (see utils folder). May be worth exploring.
/// Saves code when used repeatedly. Etabs implements an extension method to dicts (see utils folder). May be worth exploring.
/// </remarks>
private void AddMaterialProperty(
Dictionary<string, object> materialQuantity,
@@ -1,14 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<TargetFrameworks>net48;net8.0</TargetFrameworks>
<Configurations>Debug;Release;Local</Configurations>
<DefineConstants>$(DefineConstants);RHINO8;RHINO7_OR_GREATER;RHINO8_OR_GREATER</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RhinoCommon" VersionOverride="8.9.24194.18121" IncludeAssets="compile;build"/>
<ItemGroup Condition="'$(TargetFramework)' == 'net48'">
<PackageReference Include="RhinoCommon" IncludeAssets="compile; build" PrivateAssets="all" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net48'">
<PackageReference Include="RhinoCommon" IncludeAssets="compile; build" PrivateAssets="all" VersionOverride="8.21.25188.17001"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Sdk\Speckle.Converters.Common\Speckle.Converters.Common.csproj"/>
</ItemGroup>
@@ -317,6 +317,288 @@
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
},
"net8.0": {
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"PolySharp": {
"type": "Direct",
"requested": "[1.14.1, )",
"resolved": "1.14.1",
"contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ=="
},
"RhinoCommon": {
"type": "Direct",
"requested": "[8.21.25188.17001, )",
"resolved": "8.21.25188.17001",
"contentHash": "Wo6JXheyDBvilyIwDY6xZCQJC4170jzAlTSlMgh8yokUY+vYyCl4KJVXZofIOynNt/xx5wLqb2On5gZZekXR6w==",
"dependencies": {
"System.Drawing.Common": "7.0.0"
}
},
"Speckle.InterfaceGenerator": {
"type": "Direct",
"requested": "[0.9.6, )",
"resolved": "0.9.6",
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
},
"GraphQL.Client": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0",
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
"System.Reactive": "5.0.0"
}
},
"GraphQL.Client.Abstractions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==",
"dependencies": {
"GraphQL.Primitives": "6.0.0"
}
},
"GraphQL.Client.Abstractions.Websocket": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0"
}
},
"GraphQL.Primitives": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.Data.Sqlite": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "KGxbPeWsQMnmQy43DSBxAFtHz3l2JX8EWBSGUCvT3CuZ8KsuzbkqMIJMDOxWtG8eZSoCDI04aiVQjWuuV8HmSw==",
"dependencies": {
"Microsoft.Data.Sqlite.Core": "7.0.5",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
}
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "FTerRmQPqHrCrnoUzhBu+E+1DNGwyrAMLqHkAqOOOu5pGfyMOj8qQUBxI/gDtWtG11p49UxSfWmBzRNlwZqfUg==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==",
"dependencies": {
"Microsoft.Extensions.Primitives": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Binder": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==",
"dependencies": {
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Primitives": "2.2.0",
"System.ComponentModel.Annotations": "4.5.0"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==",
"dependencies": {
"System.Memory": "4.5.1",
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
}
},
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Microsoft.Win32.SystemEvents": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "2nXPrhdAyAzir0gLl8Yy8S5Mnm/uBSQQA7jEsILOS1MTyS7DbmV1NgViMtvV1sfCD1ebITpNwb1NIinKeJgUVQ=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
"SQLitePCLRaw.provider.e_sqlite3": "2.1.4"
}
},
"SQLitePCLRaw.core": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==",
"dependencies": {
"System.Memory": "4.5.3"
}
},
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
},
"SQLitePCLRaw.provider.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
},
"System.Drawing.Common": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==",
"dependencies": {
"Microsoft.Win32.SystemEvents": "7.0.0"
}
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.3",
"contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA=="
},
"System.Reactive": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
"speckle.converters.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
}
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==",
"dependencies": {
"Microsoft.Extensions.Configuration.Binder": "2.2.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging.Abstractions": "2.2.0",
"Microsoft.Extensions.Options": "2.2.0"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
}
}
}
@@ -1,24 +0,0 @@
namespace Speckle.Converters.Rhino.Extensions;
public static class GeometryBaseExtensions
{
/// <summary>
/// Getting translation vector from origin to the Geometry bbox Center (if geometry is far from origin and translation needed)
/// This is needed for some objects, because of Rhino using single precision numbers for Mesh vertices: https://wiki.mcneel.com/rhino/farfromorigin
/// </summary>
/// <returns>
/// Vector from origin to Geometry bbox center (if translation needed), otherwise zero-length vector.
/// </returns>
public static bool IsFarFromOrigin(this RG.GeometryBase geometry, out RG.Vector3d vectorToGeometry)
{
var geometryBbox = geometry.GetBoundingBox(false); // 'false' for 'accurate' parameter to accelerate bbox calculation
if (geometryBbox.Min.DistanceTo(RG.Point3d.Origin) > 1e5 || geometryBbox.Max.DistanceTo(RG.Point3d.Origin) > 1e5)
{
vectorToGeometry = new RG.Vector3d(geometryBbox.Center);
return true;
}
vectorToGeometry = new RG.Vector3d();
return false;
}
}
@@ -1,13 +1,8 @@
using Rhino;
using Rhino;
namespace Speckle.Converters.Rhino;
/// <summary>
/// Represents the settings used for Rhino and Grasshopper conversions.
/// </summary>
public record RhinoConversionSettings(
RhinoDoc Document,
string SpeckleUnits,
bool ModelFarFromOrigin,
bool AddVisualizationProperties
);
public record RhinoConversionSettings(RhinoDoc Document, string SpeckleUnits, bool AddVisualizationProperties);
@@ -1,4 +1,4 @@
using Rhino;
using Rhino;
using Speckle.Converters.Common;
using Speckle.InterfaceGenerator;
@@ -13,24 +13,5 @@ public class RhinoConversionSettingsFactory(
public RhinoConversionSettings Current => settingsStore.Current;
public RhinoConversionSettings Create(RhinoDoc document, bool addVisualizationProperties) =>
new(
document,
unitsConverter.ConvertOrThrow(RhinoDoc.ActiveDoc.ModelUnitSystem),
ModelFarFromOrigin(),
addVisualizationProperties
);
/// <summary>
/// Quick check whether any of the objects in the scene might be located too far from origin and cause precision issues during meshing.
/// It prevents 'normal' Rhino models (not too far from origin) from unnecessary Bbox calculations on every object on Send.
/// </summary>
private bool ModelFarFromOrigin()
{
RG.BoundingBox bbox = RhinoDoc.ActiveDoc.Objects.BoundingBox;
if (bbox.Min.DistanceTo(RG.Point3d.Origin) > 1e5 || bbox.Max.DistanceTo(RG.Point3d.Origin) > 1e5)
{
return true;
}
return false;
}
new(document, unitsConverter.ConvertOrThrow(RhinoDoc.ActiveDoc.ModelUnitSystem), addVisualizationProperties);
}
@@ -1,14 +1,13 @@
using Rhino;
using Rhino.DocObjects;
using Speckle.Converters.Common.Objects;
using Speckle.Converters.Rhino.Extensions;
using Speckle.DoubleNumerics;
using Speckle.Sdk.Common.Exceptions;
namespace Speckle.Converters.Rhino.ToSpeckle.Meshing;
public static class DisplayMeshExtractor
{
public static RG.Mesh GetDisplayMesh(RhinoObject obj)
public static RG.Mesh GetDisplayMesh(RhinoObject obj, RhinoDoc doc)
{
// note: unsure this is nice, we get bigger meshes - we should to benchmark (conversion time vs size tradeoffs)
var joinedMesh = new RG.Mesh();
@@ -23,15 +22,15 @@ public static class DisplayMeshExtractor
switch (obj)
{
case BrepObject brep:
joinedMesh.Append(GetGeometryDisplayMesh(brep.BrepGeometry));
joinedMesh.Append(GetGeometryDisplayMesh(brep.BrepGeometry, doc));
break;
case ExtrusionObject extrusion:
joinedMesh.Append(GetGeometryDisplayMesh(extrusion.ExtrusionGeometry.ToBrep()));
joinedMesh.Append(GetGeometryDisplayMesh(extrusion.ExtrusionGeometry.ToBrep(), doc));
break;
case SubDObject subDObject:
if (subDObject.Geometry is RG.SubD subdGeometry)
{
joinedMesh.Append(GetGeometryDisplayMesh(subdGeometry));
joinedMesh.Append(GetGeometryDisplayMesh(subdGeometry, doc));
}
else
{
@@ -49,18 +48,16 @@ public static class DisplayMeshExtractor
/// <summary>
/// Extracting Rhino Mesh from Rhino GeometryBase using specified MeshingParameters settings, e.g. minimumEdgeLength.
/// </summary>
public static RG.Mesh GetGeometryDisplayMesh(RG.GeometryBase geometry, bool highPrecision = false)
public static RG.Mesh GetGeometryDisplayMesh(RG.GeometryBase geometry, RhinoDoc doc)
{
double minEdgeLength = highPrecision ? GetAccurateMinEdgeLegth(geometry) : 0.05;
// declare "renderMeshes" as a separate var, because it needs to be checked for null after each Mesh.Create method
RG.Mesh[] renderMeshes;
var joinedMesh = new RG.Mesh();
RG.MeshingParameters meshParams = RG.MeshingParameters.DocumentCurrentSetting(doc);
switch (geometry)
{
case RG.Brep brep:
renderMeshes = RG.Mesh.CreateFromBrep(brep, new(0.05, minEdgeLength));
renderMeshes = RG.Mesh.CreateFromBrep(brep, meshParams);
break;
case RG.SubD subd:
#pragma warning disable CA2000
@@ -69,7 +66,7 @@ public static class DisplayMeshExtractor
renderMeshes = [subdMesh];
break;
case RG.Extrusion extrusion:
renderMeshes = RG.Mesh.CreateFromBrep(extrusion.ToBrep(), new(0.05, minEdgeLength));
renderMeshes = RG.Mesh.CreateFromBrep(extrusion.ToBrep(), meshParams);
break;
default:
throw new ConversionException($"Unsupported object for display mesh generation {geometry.GetType().FullName}");
@@ -93,61 +90,17 @@ public static class DisplayMeshExtractor
}
/// <summary>
/// Calculating optimal meshing parameter 'minimumEdgeLength' for the given geometry.
/// </summary>
private static double GetAccurateMinEdgeLegth(RG.GeometryBase geometry)
{
// adjust meshing parameters if Brep edges are too close to the document tolerance
double minEdgeLength = 0.05;
if (geometry is RG.Brep brep && brep.Edges.Any(x => x.GetLength() < minEdgeLength))
{
return 0;
}
return minEdgeLength;
}
/// <summary>
/// Extracting Rhino Mesh and converting to Speckle with the most suitable settings (e.g. moving to origin first, if needed)
/// This is needed because of Rhino using single precision numbers for Mesh vertices: https://wiki.mcneel.com/rhino/farfromorigin
/// Extracting Rhino Mesh and converting to Speckle with the most suitable settings
/// </summary>
/// <returns>List of converted Speckle meshes</returns>
public static List<SOG.Mesh> GetSpeckleMeshes(
RG.GeometryBase geometry,
bool modelFarFromOrigin,
string units,
ITypedConverter<RG.Mesh, SOG.Mesh> meshConverter
ITypedConverter<RG.Mesh, SOG.Mesh> meshConverter,
RhinoDoc doc
)
{
RG.GeometryBase geometryToMesh = geometry;
RG.Vector3d? vector = null;
// 1.1. If needed, move geometry to origin
if (modelFarFromOrigin && geometry.IsFarFromOrigin(out RG.Vector3d vectorToGeometry))
{
geometryToMesh = geometry.Duplicate();
geometryToMesh.Transform(RG.Transform.Translation(-vectorToGeometry));
vector = vectorToGeometry;
}
// 1.2. Extract Rhino Mesh
RG.Mesh movedDisplayMesh = GetGeometryDisplayMesh(geometryToMesh, true);
// 2. Convert extracted Mesh to Speckle. We don't move geometry back yet, because 'far from origin' geometry is causing Speckle conversion issues too
List<SOG.Mesh> displayValue = new() { meshConverter.Convert(movedDisplayMesh) };
// 3. Move Speckle geometry back from origin, if translation was applied
MoveSpeckleMeshes(displayValue, vector, units);
RG.Mesh displayMesh = GetGeometryDisplayMesh(geometry, doc);
List<SOG.Mesh> displayValue = new() { meshConverter.Convert(displayMesh) };
return displayValue;
}
public static void MoveSpeckleMeshes(List<SOG.Mesh> displayValue, RG.Vector3d? vectorToGeometry, string units)
{
if (vectorToGeometry is RG.Vector3d vector)
{
Matrix4x4 matrix = new(1, 0, 0, vector.X, 0, 1, 0, vector.Y, 0, 0, 1, vector.Z, 0, 0, 0, 1);
SO.Transform transform = new() { matrix = matrix, units = units };
displayValue.ForEach(x => x.Transform(transform));
}
}
}
@@ -30,9 +30,8 @@ public class BrepToSpeckleConverter : ITypedConverter<RG.Brep, SOG.BrepX>
List<SOG.Mesh> displayValue = DisplayMeshExtractor.GetSpeckleMeshes(
target,
_settingsStore.Current.ModelFarFromOrigin,
_settingsStore.Current.SpeckleUnits,
_meshConverter
_meshConverter,
_settingsStore.Current.Document
);
var bx = new SOG.BrepX()
@@ -30,9 +30,8 @@ public class ExtrusionToSpeckleConverter : ITypedConverter<RG.Extrusion, SOG.Ext
List<SOG.Mesh> displayValue = DisplayMeshExtractor.GetSpeckleMeshes(
target,
_settingsStore.Current.ModelFarFromOrigin,
_settingsStore.Current.SpeckleUnits,
_meshConverter
_meshConverter,
_settingsStore.Current.Document
);
var bx = new SOG.ExtrusionX()
@@ -41,9 +41,8 @@ public class HatchToSpeckleConverter : ITypedConverter<RG.Hatch, SOG.Region>
List<SOG.Mesh> displayValue = DisplayMeshExtractor.GetSpeckleMeshes(
brep,
_settingsStore.Current.ModelFarFromOrigin,
_settingsStore.Current.SpeckleUnits,
_meshConverter
_meshConverter,
_settingsStore.Current.Document
);
return new SOG.Region
@@ -1,7 +1,5 @@
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Converters.Rhino.Extensions;
using Speckle.Converters.Rhino.ToSpeckle.Meshing;
using Speckle.Sdk.Common.Exceptions;
namespace Speckle.Converters.Rhino.ToSpeckle.Raw;
@@ -34,39 +32,34 @@ public class MeshToSpeckleConverter : ITypedConverter<RG.Mesh, SOG.Mesh>
throw new ValidationException("Cannot convert a mesh with 0 vertices/faces");
}
// Extracting Rhino Mesh and converting to Speckle with the most suitable settings (e.g. moving to origin first, if needed)
// This is needed because of Rhino using single precision numbers for Mesh vertices: https://wiki.mcneel.com/rhino/farfromorigin
RG.Mesh meshToConvert = target;
RG.Vector3d? vector = null;
// 1. If needed, move geometry to origin
if (_settingsStore.Current.ModelFarFromOrigin && target.IsFarFromOrigin(out RG.Vector3d vectorToGeometry))
{
meshToConvert = (RG.Mesh)target.Duplicate();
meshToConvert.Transform(RG.Transform.Translation(-vectorToGeometry));
vector = vectorToGeometry;
}
// 2. Convert extracted Mesh to Speckle. We don't move geometry back yet, because 'far from origin' geometry is causing Speckle conversion issues too
SOG.Mesh convertedMesh = ConvertMesh(meshToConvert);
// 3. Move Speckle geometry back from origin, if translation was applied
DisplayMeshExtractor.MoveSpeckleMeshes([convertedMesh], vector, _settingsStore.Current.SpeckleUnits);
SOG.Mesh convertedMesh = ConvertMesh(target);
return convertedMesh;
}
private SOG.Mesh ConvertMesh(RG.Mesh target)
// Rhino common is casting mesh vertex coords from doubles to float: by default the api returns `Vertices` as float instead of double precision
// https://github.com/mcneel/rhino3dm/blob/71c63a8c1c87782a13a1b76c825e4b792b36fd09/src/dotnet/opennurbs/opennurbs_mesh.cs#L6990-L7000
// We need to use double precision or else meshes far from origin will come out distorted: do *not* access `Vertices` directly - use `ToPoint3dArray`
private double[] ConvertDoublePrecisionVertices(RG.Mesh target)
{
var vertexCoordinates = new double[target.Vertices.Count * 3];
RG.Point3d[] vertices = target.Vertices.ToPoint3dArray();
var x = 0;
for (int i = 0; i < target.Vertices.Count; i++)
for (int i = 0; i < vertices.Length; i++)
{
var v = target.Vertices[i];
var v = vertices[i];
vertexCoordinates[x++] = v.X;
vertexCoordinates[x++] = v.Y;
vertexCoordinates[x++] = v.Z;
}
return vertexCoordinates;
}
private SOG.Mesh ConvertMesh(RG.Mesh target)
{
var vertexCoordinates = ConvertDoublePrecisionVertices(target);
List<int> faces = new();
foreach (RG.MeshNgon polygon in target.GetNgonAndFacesEnumerable())
@@ -81,7 +74,7 @@ public class MeshToSpeckleConverter : ITypedConverter<RG.Mesh, SOG.Mesh>
}
var colors = new int[target.VertexColors.Count];
x = 0;
int x = 0;
foreach (var c in target.VertexColors)
{
colors[x++] = c.ToArgb();
@@ -30,9 +30,8 @@ public class SubDToSpeckleConverter : ITypedConverter<RG.SubD, SOG.SubDX>
List<SOG.Mesh> displayValue = DisplayMeshExtractor.GetSpeckleMeshes(
target,
_settingsStore.Current.ModelFarFromOrigin,
_settingsStore.Current.SpeckleUnits,
_meshConverter
_meshConverter,
_settingsStore.Current.Document
);
var bx = new SOG.SubDX()
@@ -115,6 +115,30 @@ public class ConfigBinding : IBinding
}
}
public GlobalConfig? GetGlobalConfig()
{
var rawConfig = _jsonCacheManager.GetObject("global");
if (rawConfig is null)
{
return null;
}
try
{
var config = _serializer.Deserialize<GlobalConfig>(rawConfig);
if (config is null)
{
throw new SerializationException("Failed to deserialize global config");
}
return config;
}
catch (SerializationException)
{
return null;
}
}
public AccountsConfig? GetAccountsConfig()
{
var rawConfig = _jsonCacheManager.GetObject("accounts");
@@ -182,6 +206,11 @@ public class ConnectorConfig
public bool DarkTheme { get; set; } = true;
}
public class GlobalConfig
{
public bool IsUpdateNotificationDisabled { get; set; }
}
public class AccountsConfig
{
public string? UserSelectedAccountId { get; set; }
+3
View File
@@ -6,6 +6,7 @@
<PackageVersion Include="altcover" Version="8.9.3" />
<PackageVersion Include="Bullseye" Version="5.0.0" />
<PackageVersion Include="CefSharp.Wpf" Version="92.0.260" />
<PackageVersion Include="Dapper" Version="2.1.66" />
<PackageVersion Include="Esri.ArcGISPro.Extensions30" Version="3.2.0.49743" />
<PackageVersion Include="FluentAssertions" Version="6.12.1" />
<PackageVersion Include="Glob" Version="1.1.9" />
@@ -17,6 +18,7 @@
<PackageVersion Include="Moq" Version="4.20.70" />
<PackageVersion Include="Microsoft.Build" Version="17.11.4" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="Npgsql" Version="9.0.3" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
@@ -26,6 +28,7 @@
<PackageVersion Include="NUnit3TestAdapter" version="4.6.0" />
<PackageVersion Include="Revit.Async" Version="2.1.1" />
<PackageVersion Include="RhinoCommon" Version="8.9.24194.18121" />
<PackageVersion Include="Rhino.Inside" Version="8.0.7-beta" />
<PackageVersion Include="Grasshopper" Version="8.9.24194.18121" />
<PackageVersion Include="RhinoWindows" Version="8.9.24194.18121" />
<PackageVersion Include="Semver" Version="3.0.0" />
@@ -0,0 +1,16 @@
namespace Speckle.Importers.JobProcessor.Domain;
/// <summary>
/// DB model for the fileimport job.
/// </summary>
internal sealed class FileimportJob
{
public required string Id { get; init; }
public required string JobType { get; init; }
public required FileimportPayload Payload { get; init; }
public required JobStatus Status { get; init; }
public required int Attempt { get; init; }
public required int MaxAttempt { get; init; }
public required DateTime CreatedAt { get; init; }
public required DateTime UpdatedAt { get; init; }
}
@@ -0,0 +1,19 @@
namespace Speckle.Importers.JobProcessor.Domain;
/// <summary>
/// Payload for the fileimport job
/// </summary>
internal sealed class FileimportPayload
{
public required string JobId { get; init; }
public required string Token { get; init; }
public required string BlobId { get; init; }
public required string JobType { get; init; }
public required string ModelId { get; init; }
public required string FileName { get; init; }
public required string FileType { get; init; }
public required string ProjectId { get; init; }
public required Uri ServerUrl { get; init; }
public required int PayloadVersion { get; init; }
public required int TimeOutSeconds { get; init; }
}
@@ -0,0 +1,12 @@
namespace Speckle.Importers.JobProcessor.Domain;
/// <summary>
/// Status enumeration for the job.
/// </summary>
internal enum JobStatus
{
QUEUED,
PROCESSING,
SUCCEEDED,
FAILED,
}
@@ -0,0 +1,23 @@
namespace Speckle.Importers.JobProcessor.JobHandlers;
public sealed class MaxAttemptsExceededException : Exception
{
public MaxAttemptsExceededException() { }
public MaxAttemptsExceededException(string? message)
: base(message) { }
public MaxAttemptsExceededException(string? message, Exception? innerException)
: base(message, innerException) { }
}
public sealed class JobTimeoutException : Exception
{
public JobTimeoutException() { }
public JobTimeoutException(string? message)
: base(message) { }
public JobTimeoutException(string? message, Exception? innerException)
: base(message, innerException) { }
}
@@ -0,0 +1,10 @@
using Speckle.Importers.JobProcessor.Domain;
using Speckle.Sdk.Api;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Importers.JobProcessor.JobHandlers;
internal interface IJobHandler
{
public Task<Version> ProcessJob(FileimportJob job, IClient client, CancellationToken cancellationToken);
}
@@ -0,0 +1,54 @@
using Microsoft.Extensions.Logging;
using Speckle.Importers.JobProcessor.Domain;
using Speckle.Importers.Rhino;
using Speckle.Sdk.Api;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Importers.JobProcessor.JobHandlers;
internal sealed class RhinoJobHandler(ILogger<RhinoJobHandler> logger) : IJobHandler
{
public async Task<Version> ProcessJob(FileimportJob job, IClient client, CancellationToken cancellationToken)
{
var directory = Directory.CreateTempSubdirectory("speckle-file-import");
try
{
string targetFilePath = $"{directory.FullName}/{job.Payload.JobId}.{job.Payload.FileType}";
await client.FileImport.DownloadFile(
job.Payload.ProjectId,
job.Payload.BlobId,
targetFilePath,
null,
cancellationToken
);
return await Importer.Import(
targetFilePath,
job.Payload.ProjectId,
job.Payload.ModelId,
client.Account,
cancellationToken
);
}
finally
{
try
{
await Cleanup(directory.FullName);
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
logger.LogError(ex, "Failed to cleanup file");
}
}
}
private static async Task Cleanup(string path)
{
//Some weird cases where *something* is keeping a lock on the file, this *may* fix things...
await Task.Delay(100);
GC.Collect();
GC.WaitForPendingFinalizers();
Directory.Delete(path, true);
}
}
@@ -0,0 +1,204 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.Logging;
using Speckle.Importers.JobProcessor.Domain;
using Speckle.Importers.JobProcessor.JobHandlers;
using Speckle.Importers.JobProcessor.JobQueue;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Logging;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Importers.JobProcessor;
internal sealed class JobProcessorInstance(
Repository repository,
ILogger<JobProcessorInstance> logger,
IJobHandler jobHandler,
IAccountFactory accountFactory,
IClientFactory clientFactory,
ISdkActivityFactory activityFactory
)
{
private static readonly TimeSpan s_idleTimeout = TimeSpan.FromSeconds(1);
public async Task StartProcessing(CancellationToken cancellationToken = default)
{
await using var connection = await repository.SetupConnection(cancellationToken).ConfigureAwait(false);
logger.LogInformation("Listening for jobs...");
while (true)
{
FileimportJob? job = await repository.GetNextJob(connection, cancellationToken);
if (job == null)
{
logger.LogDebug("No job found, sleeping for {timeout}", s_idleTimeout);
await Task.Delay(s_idleTimeout, cancellationToken);
continue;
}
logger.LogInformation("Starting {jobId}", job.Id);
using var activity = activityFactory.Start();
ActivityScope.SetTag("jobId", job.Id);
ActivityScope.SetTag("jobType", job.Payload.JobType);
ActivityScope.SetTag("job.attempt", job.Attempt.ToString());
ActivityScope.SetTag("serverUrl", job.Payload.ServerUrl.ToString());
ActivityScope.SetTag("projectId", job.Payload.ProjectId);
ActivityScope.SetTag("modelId", job.Payload.ModelId);
ActivityScope.SetTag("blobId", job.Payload.BlobId);
try
{
JobStatus jobStatus = await AttemptJob(job, cancellationToken);
await repository.SetJobStatus(connection, job.Id, jobStatus, cancellationToken);
activity?.SetStatus(SdkActivityStatusCode.Ok);
}
catch (Exception ex)
{
activity?.RecordException(ex);
activity?.SetStatus(SdkActivityStatusCode.Error);
throw;
}
}
}
private static async Task ReportSuccess(
FileimportJob job,
Version version,
IClient client,
CancellationToken cancellationToken
)
{
var input = new FileImportSuccessInput
{
projectId = job.Payload.ProjectId,
jobId = job.Payload.BlobId,
warnings = [],
result = new FileImportResult(0, 0, 0, "Rhino Importer", versionId: version.id)
};
await client.FileImport.FinishFileImportJob(input, cancellationToken);
}
private static async Task ReportFailed(
FileimportJob job,
IClient client,
Exception ex,
CancellationToken cancellationToken
)
{
var input = new FileImportErrorInput()
{
projectId = job.Payload.ProjectId,
jobId = job.Payload.BlobId,
warnings = [],
reason = string.IsNullOrEmpty(ex.Message) ? ex.GetType().ToString() : ex.Message,
result = new FileImportResult(0, 0, 0, "Rhino Importer", versionId: null)
};
await client.FileImport.FinishFileImportJob(input, cancellationToken);
}
private async Task<IClient> SetupClient(FileimportJob job, CancellationToken cancellationToken)
{
var account = await accountFactory.CreateAccount(
job.Payload.ServerUrl,
job.Payload.Token,
cancellationToken: cancellationToken
);
return clientFactory.Create(account);
}
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
private async Task<JobStatus> AttemptJob(FileimportJob job, CancellationToken cancellationToken)
{
using var activity = activityFactory.Start();
IClient? speckleClient = null;
try
{
speckleClient = await SetupClient(job, cancellationToken);
UserActivityScope.AddUserScope(speckleClient.Account);
if (job.Attempt > job.MaxAttempt)
{
//something went wrong, it should have been marked as failed
throw new MaxAttemptsExceededException("Unhandled error silently failed the job multiple times");
}
try
{
Version version = await ExecuteJobWithTimeout(job, speckleClient, cancellationToken);
await ReportSuccess(job, version, speckleClient, cancellationToken);
logger.LogInformation("Job {jobId} has succeeded creating {versionId}", job.Id, version.id);
activity?.SetStatus(SdkActivityStatusCode.Ok);
return JobStatus.SUCCEEDED;
}
catch (JobTimeoutException ex)
{
logger.LogInformation(ex, "Executing job timed out");
if (job.Attempt >= job.MaxAttempt)
{
throw new MaxAttemptsExceededException("The final attempt to process the job failed", ex);
}
activity?.RecordException(ex);
activity?.SetStatus(SdkActivityStatusCode.Error);
return JobStatus.QUEUED;
}
}
catch (Exception ex)
{
logger.LogError(ex, "Attempt {attempt} to process {jobId} failed", job.Attempt, job.Id);
if (speckleClient is not null)
{
await ReportFailed(job, speckleClient, ex, cancellationToken);
}
activity?.RecordException(ex);
activity?.SetStatus(SdkActivityStatusCode.Error);
return JobStatus.FAILED;
}
finally
{
speckleClient?.Dispose();
}
}
/// <summary>
///
/// </summary>
/// <param name="job"></param>
/// <param name="cancellationToken"></param>
/// <returns><see cref="Version"/> if attempt was successful, <see langword="null"/> if job timedout, but can be re-attempted without exceeding <see cref="FileimportJob.MaxAttempt"/></returns>
/// <exception cref="OperationCanceledException">Timeout was reached AND MaxAttempt was reached</exception>
private async Task<Version> ExecuteJobWithTimeout(
FileimportJob job,
IClient client,
CancellationToken cancellationToken
)
{
using CancellationTokenSource timeout = new();
timeout.CancelAfter(TimeSpan.FromSeconds(job.Payload.TimeOutSeconds));
using CancellationTokenSource linkedSource = CancellationTokenSource.CreateLinkedTokenSource(
timeout.Token,
cancellationToken
);
try
{
return await jobHandler.ProcessJob(job, client, linkedSource.Token);
}
catch (OperationCanceledException ex) when (timeout.IsCancellationRequested)
{
throw new JobTimeoutException(
$"Job was cancelled due to reaching the {job.Payload.TimeOutSeconds} second timeout",
ex
);
}
}
}
@@ -0,0 +1,25 @@
using System.Data;
using System.Text.Json;
using Dapper;
namespace Speckle.Importers.JobProcessor.JobQueue;
internal sealed class JsonHandler<T> : SqlMapper.TypeHandler<T>
{
private readonly JsonSerializerOptions _options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, };
public override void SetValue(IDbDataParameter parameter, T? value)
{
parameter.Value = JsonSerializer.Serialize(value);
}
public override T? Parse(object value)
{
if (value is string json)
{
return JsonSerializer.Deserialize<T>(json, _options);
}
throw new DataException($"Cannot convert {value.GetType()} to {typeof(T)}");
}
}
@@ -0,0 +1,114 @@
using System.Data;
using Dapper;
using Microsoft.Extensions.Logging;
using Npgsql;
using Speckle.Importers.JobProcessor.Domain;
namespace Speckle.Importers.JobProcessor.JobQueue;
internal sealed class Repository(ILogger<Repository> logger)
{
public async Task<NpgsqlConnection> SetupConnection(CancellationToken cancellationToken)
{
string fileImportQueuePostgresUrl =
Environment.GetEnvironmentVariable("FILEIMPORT_QUEUE_POSTGRES_URL")
?? throw new ArgumentException("Expected FILEIMPORT_QUEUE_POSTGRES_URL environment variable to be set");
string connectionString = ParseConnectionString(new(fileImportQueuePostgresUrl));
var connection = new NpgsqlConnection(connectionString);
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
return connection;
}
private static string ParseConnectionString(Uri connectionUrl)
{
if (connectionUrl.Scheme is not "postgres" and not "postgresql")
{
throw new ArgumentException("Invalid URI scheme. Expected 'postgres' or 'postgresql'.", nameof(connectionUrl));
}
var userInfo = connectionUrl.UserInfo.Split(':');
if (userInfo.Length != 2)
{
throw new ArgumentException("Invalid URI format: missing username or password.");
}
var builder = new NpgsqlConnectionStringBuilder
{
Host = connectionUrl.Host,
Port = connectionUrl.Port > 0 ? connectionUrl.Port : 5432, // Default to 5432 if not specified
Username = userInfo[0],
Password = userInfo[1],
Database = connectionUrl.AbsolutePath.TrimStart('/')
};
return builder.ConnectionString;
}
public async Task<FileimportJob?> GetNextJob(IDbConnection connection, CancellationToken cancellationToken)
{
//lang=postgresql
const string COMMAND_TEXT = """
WITH next_job AS (
UPDATE background_jobs
SET
"attempt" = "attempt" + 1,
"status" = @Status1,
"updatedAt" = NOW()
WHERE id = (
SELECT id FROM background_jobs
WHERE ( --queued job
(payload ->> 'fileType') = ANY(@FileTypes)
AND status = @Status2
)
OR ( --timed job left on processing state
(payload ->> 'fileType') = ANY(@FileTypes)
AND status = @Status1
AND "updatedAt" < NOW() - ("timeoutMs" * interval '1 millisecond')
)
ORDER BY "createdAt"
FOR UPDATE SKIP LOCKED
LIMIT 1
)
RETURNING *
)
SELECT * FROM next_job;
""";
var command = new CommandDefinition(
commandText: COMMAND_TEXT,
parameters: new
{
Status1 = nameof(JobStatus.PROCESSING).ToLowerInvariant(),
Status2 = nameof(JobStatus.QUEUED).ToLowerInvariant(),
FileTypes = SupportedFileTypes.FileTypes,
},
cancellationToken: cancellationToken
);
return await connection.QueryFirstOrDefaultAsync<FileimportJob?>(command);
}
public async Task SetJobStatus(
IDbConnection connection,
string jobId,
JobStatus jobStatus,
CancellationToken cancellationToken
)
{
logger.LogInformation("Updating job: {jobId}'s status to {jobStatus}", jobId, jobStatus);
//lang=postgresql
const string COMMAND_TEXT = """
UPDATE background_jobs
SET status = @status, "updatedAt" = NOW()
WHERE id = @jobId
""";
var command = new CommandDefinition(
commandText: COMMAND_TEXT,
parameters: new { status = jobStatus.ToString().ToLowerInvariant(), jobId, },
cancellationToken: cancellationToken
);
await connection.ExecuteAsync(command);
}
}
@@ -0,0 +1,78 @@
// See https://aka.ms/new-console-template for more information
using Dapper;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Rhino.Runtime.InProcess;
using RhinoInside;
using Speckle.Importers.JobProcessor.Domain;
using Speckle.Importers.JobProcessor.JobHandlers;
using Speckle.Importers.JobProcessor.JobQueue;
namespace Speckle.Importers.JobProcessor;
public static class Program
{
static Program()
{
Resolver.Initialize();
}
[STAThread]
public static async Task Main()
{
ILogger? logger = null;
try
{
// Dapper doesn't understand how to handle JSON deserialization, so we need to tell it what types can be deserialzied
SqlMapper.AddTypeHandler(new JsonHandler<FileimportPayload>());
// DI setup
var serviceCollection = new ServiceCollection();
serviceCollection.AddJobProcessor();
serviceCollection.AddTransient<IJobHandler, RhinoJobHandler>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var processor = serviceProvider.GetRequiredService<JobProcessorInstance>();
logger = serviceProvider.GetRequiredService<ILogger<object>>();
TaskScheduler.UnobservedTaskException += (sender, args) =>
logger.LogCritical(args.Exception, "Unobserved Task Exception");
using (new RhinoCore(["/netcore-8"], WindowStyle.NoWindow))
{
//What ever thread RhinoCore is created on it will grab as soon as it's available, and it will hog it forever.
//Right now, we're giving it the main STA thread (not 100% if it needs STA or if it could work on any thread)
await Task.Run(async () =>
{
logger.LogInformation("Job processor has started!");
try
{
await processor.StartProcessing();
}
catch (Exception ex)
{
logger.LogCritical(ex, "Unhandled exception in Main");
throw;
}
})
.ConfigureAwait(false);
}
}
catch (Exception ex)
{
if (logger is not null)
{
logger.LogCritical(ex, "Unhandled exception reached entry point");
}
else
{
Console.WriteLine($"Unhandled exception reached entry point: {ex}");
}
throw;
}
}
}
@@ -0,0 +1,11 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"Local Docker DB": {
"commandName": "Project",
"environmentVariables": {
"FILEIMPORT_QUEUE_POSTGRES_URL": "postgresql://speckle:speckle@127.0.0.1:5432/speckle"
}
}
}
}
@@ -0,0 +1,26 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common;
using Speckle.Importers.JobProcessor.JobQueue;
using Speckle.Sdk;
namespace Speckle.Importers.JobProcessor;
internal static class ServiceRegistration
{
private static readonly Application s_application = new(".NET File Import Job Processor", "jobprocessor");
public static IServiceCollection AddJobProcessor(this IServiceCollection serviceCollection)
{
serviceCollection.AddLoggingConfig();
serviceCollection.AddTransient<JobProcessorInstance>();
serviceCollection.AddTransient<Repository>();
return serviceCollection;
}
private static IServiceCollection AddLoggingConfig(this IServiceCollection serviceCollection)
{
serviceCollection.Initialize(s_application, HostAppVersion.v3);
return serviceCollection;
}
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<OutputType>Exe</OutputType>
<Configurations>Debug;Release;Local</Configurations>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Npgsql" />
<PackageReference Include="Dapper" />
<PackageReference Include="Serilog.Extensions.Logging" />
<PackageReference Include="Serilog.Formatting.Compact" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="RhinoCommon" IncludeAssets="compile; build" PrivateAssets="all" VersionOverride="8.21.25188.17001"/>
<PackageReference Include="RhinoWindows" IncludeAssets="compile; build" PrivateAssets="all" VersionOverride="8.21.25188.17001"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Speckle.Importers.Rhino\Speckle.Importers.Rhino.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,65 @@
namespace Speckle.Importers.JobProcessor;
public static class SupportedFileTypes
{
/// <summary>
/// This is the full list of file extensions that this job processor will look for jobs with that extension
/// This also happens to be the full list of file types that Rhino 8 supports.,
/// </summary>
public static readonly string[] FileTypes =
[
"3dm", // Rhino 3D Model (except ver 1.x save)
"3dmbak", // Rhino 3D Model Backup
"rws", // Rhino Worksession
"3mf", // 3MF
"3ds", // 3D Studio
"amf", // AMF
"ai", // Adobe Illustrator
"dwg", // AutoCAD Drawing
"dxf", // AutoCAD Drawing Exchange
"x", // DirectX
"e57", // E57
"dst", // Embroidery
"exp",
"dst", // Encapsulated PostScript
"exp",
"off", // Geomview OFF
"gf", // GHS Geometry
"gft",
"gltf", // GL Transmission Format
"glb",
"gts", // GTS (GNU Triangulated Surface)
"igs", // IGES
"iges",
"lwo", // Lightwave
"dgn", // Microstation
"fbx", // MotionBuilder
"scn", // NextEngine Scan
"obj", // OBJ (Wavefront)
"pdf", // PDF
"ply", // PLY
"asc", // Points
"csv",
"xyz",
"pts",
"cgo_ascii", // Points
"cgo_asci",
"txt", // Points
"raw", // Raw Triangles
"m", // Recon M
"svg", // Scalable Vector Graphics
"skp", // SketchUp
"slc", // Slice
"sldprt", // SOLIDWORKS
"sldasm",
"stp", // STEP
"step",
"stl", // STL (Stereolithography)
"vda", // VDA
"wrl", // VRML/Open Inventor
"vrml",
"iv",
"gdf", // WAMIT
"zpr", // Zcorp (3D Systems)
];
}
@@ -0,0 +1,382 @@
{
"version": 2,
"dependencies": {
"net8.0-windows7.0": {
"Dapper": {
"type": "Direct",
"requested": "[2.1.66, )",
"resolved": "2.1.66",
"contentHash": "/q77jUgDOS+bzkmk3Vy9SiWMaetTw+NOoPAV0xPBsGVAyljd5S6P+4RUW7R3ZUGGr9lDRyPKgAMj2UAOwvqZYw=="
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"Npgsql": {
"type": "Direct",
"requested": "[9.0.3, )",
"resolved": "9.0.3",
"contentHash": "tPvY61CxOAWxNsKLEBg+oR646X4Bc8UmyQ/tJszL/7mEmIXQnnBhVJZrZEEUv0Bstu0mEsHZD5At3EO8zQRAYw==",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "8.0.2"
}
},
"PolySharp": {
"type": "Direct",
"requested": "[1.14.1, )",
"resolved": "1.14.1",
"contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ=="
},
"RhinoCommon": {
"type": "Direct",
"requested": "[8.21.25188.17001, )",
"resolved": "8.21.25188.17001",
"contentHash": "Wo6JXheyDBvilyIwDY6xZCQJC4170jzAlTSlMgh8yokUY+vYyCl4KJVXZofIOynNt/xx5wLqb2On5gZZekXR6w==",
"dependencies": {
"System.Drawing.Common": "7.0.0"
}
},
"RhinoWindows": {
"type": "Direct",
"requested": "[8.21.25188.17001, )",
"resolved": "8.21.25188.17001",
"contentHash": "9zqCorcLRBeiW/j1RTwUS4E7bnZetAdA9WDdtd/AQccjOpxdtw76wdN+ciyQ6qslseWkwZ9qSBeh7QaM800Ntw==",
"dependencies": {
"RhinoCommon": "[8.21.25188.17001]"
}
},
"Serilog.Extensions.Logging": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "YEAMWu1UnWgf1c1KP85l1SgXGfiVo0Rz6x08pCiPOIBt2Qe18tcZLvdBUuV5o1QHvrs8FAry9wTIhgBRtjIlEg==",
"dependencies": {
"Microsoft.Extensions.Logging": "8.0.0",
"Serilog": "3.1.1"
}
},
"Serilog.Formatting.Compact": {
"type": "Direct",
"requested": "[3.0.0, )",
"resolved": "3.0.0",
"contentHash": "wQsv14w9cqlfB5FX2MZpNsTawckN4a8dryuNGbebB/3Nh1pXnROHZov3swtu3Nj5oNG7Ba+xdu7Et/ulAUPanQ==",
"dependencies": {
"Serilog": "4.0.0"
}
},
"Serilog.Sinks.Console": {
"type": "Direct",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "fQGWqVMClCP2yEyTXPIinSr5c+CBGUvBybPxjAGcf7ctDhadFhrQw03Mv8rJ07/wR5PDfFjewf2LimvXCDzpbA==",
"dependencies": {
"Serilog": "4.0.0"
}
},
"Speckle.InterfaceGenerator": {
"type": "Direct",
"requested": "[0.9.6, )",
"resolved": "0.9.6",
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
},
"GraphQL.Client": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0",
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
"System.Reactive": "5.0.0"
}
},
"GraphQL.Client.Abstractions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==",
"dependencies": {
"GraphQL.Primitives": "6.0.0"
}
},
"GraphQL.Client.Abstractions.Websocket": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0"
}
},
"GraphQL.Primitives": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.Data.Sqlite": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "KGxbPeWsQMnmQy43DSBxAFtHz3l2JX8EWBSGUCvT3CuZ8KsuzbkqMIJMDOxWtG8eZSoCDI04aiVQjWuuV8HmSw==",
"dependencies": {
"Microsoft.Data.Sqlite.Core": "7.0.5",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
}
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "FTerRmQPqHrCrnoUzhBu+E+1DNGwyrAMLqHkAqOOOu5pGfyMOj8qQUBxI/gDtWtG11p49UxSfWmBzRNlwZqfUg==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "8.0.2",
"contentHash": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Primitives": "8.0.0"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
},
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Microsoft.Win32.SystemEvents": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "2nXPrhdAyAzir0gLl8Yy8S5Mnm/uBSQQA7jEsILOS1MTyS7DbmV1NgViMtvV1sfCD1ebITpNwb1NIinKeJgUVQ=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
"SQLitePCLRaw.provider.e_sqlite3": "2.1.4"
}
},
"SQLitePCLRaw.core": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==",
"dependencies": {
"System.Memory": "4.5.3"
}
},
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
},
"SQLitePCLRaw.provider.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"System.Drawing.Common": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==",
"dependencies": {
"Microsoft.Win32.SystemEvents": "7.0.0"
}
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.3",
"contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA=="
},
"System.Reactive": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ=="
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.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.rhinoimporter": {
"type": "Project",
"dependencies": {
"Speckle.Connectors.Common": "[1.0.0, )",
"Speckle.Connectors.DUI.WebView": "[1.0.0, )",
"Speckle.Converters.Rhino8": "[1.0.0, )"
}
},
"speckle.converters.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.rhino8": {
"type": "Project",
"dependencies": {
"Speckle.Converters.Common": "[1.0.0, )"
}
},
"speckle.importers.rhino": {
"type": "Project",
"dependencies": {
"Rhino.Inside": "[8.0.7-beta, )",
"Speckle.Connectors.RhinoImporter": "[1.0.0, )",
"Speckle.Converters.Rhino8": "[1.0.0, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "8.0.0",
"contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
}
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "8.0.0",
"contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
"Microsoft.Extensions.Options": "8.0.0"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "8.0.2",
"contentHash": "nroMDjS7hNBPtkZqVBbSiQaQjWRDxITI8Y7XnDs97rqG3EbzVTNLZQf7bIeUJcaHOV8bca47s1Uxq94+2oGdxA==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2"
}
},
"Microsoft.Web.WebView2": {
"type": "CentralTransitive",
"requested": "[1.0.1938.49, )",
"resolved": "1.0.1938.49",
"contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw=="
},
"Rhino.Inside": {
"type": "CentralTransitive",
"requested": "[8.0.7-beta, )",
"resolved": "8.0.7-beta",
"contentHash": "PNZdWysS2zI9vaxJ8HQig3iZA3Xg1yG1hnq09VPStQpceoj94iJ6xk0Ubddaw5oswkYYUIJRmQ8xr+bLTROs/g==",
"dependencies": {
"Grasshopper": "8.0.23304.9001",
"RhinoCommon": "8.0.23304.9001"
}
},
"Serilog": {
"type": "CentralTransitive",
"requested": "[4.0.1, )",
"resolved": "4.0.0",
"contentHash": "2jDkUrSh5EofOp7Lx5Zgy0EB+7hXjjxE2ktTb1WVQmU00lDACR2TdROGKU0K1pDTBSJBN1PqgYpgOZF8mL7NJw=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
}
}
}
@@ -0,0 +1,31 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Importers.Rhino.Internal;
using Speckle.Sdk.Credentials;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Importers.Rhino;
/// <summary>
/// Entry point for the rhino import.
/// Is a wrapper around an internal DI container.
/// It's very important that the state of services doesn't bleed between job,
/// So every import creates a new container for its processing
/// I don't trust the current services to not hold on to caches or state that could influence the next run
/// </summary>
public static class Importer
{
public static async Task<Version> Import(
string filePath,
string projectId,
string modelId,
Account account,
CancellationToken cancellationToken
)
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddRhinoImporter();
using var serviceProvider = serviceCollection.BuildServiceProvider();
var instance = serviceProvider.GetRequiredService<ImporterInstance>();
return await instance.RunRhinoImport(filePath, projectId, modelId, account, cancellationToken);
}
}
@@ -0,0 +1,34 @@
using Speckle.Sdk.SQLite;
namespace Speckle.Importers.Rhino.Internal;
/// <summary>
/// Dummy implementation of <see cref="ISqLiteJsonCacheManager"/> to avoid
/// </summary>
public sealed class DummySqliteJsonCacheManager : ISqLiteJsonCacheManager
{
public void Dispose() { }
public IReadOnlyCollection<(string Id, string Json)> GetAllObjects() => [];
public void DeleteObject(string id) { }
public string? GetObject(string id) => null;
public void SaveObject(string id, string json) { }
public void UpdateObject(string id, string json) { }
public void SaveObjects(IEnumerable<(string id, string json)> items) { }
public bool HasObject(string objectId) => false;
}
public sealed class DummySqliteJsonCacheManagerFactory : ISqLiteJsonCacheManagerFactory
{
private static readonly ISqLiteJsonCacheManager s_instance = new DummySqliteJsonCacheManager();
public ISqLiteJsonCacheManager CreateForUser(string scope) => s_instance;
public ISqLiteJsonCacheManager CreateFromStream(string streamId) => s_instance;
}
@@ -0,0 +1,11 @@
using Rhino;
using Rhino.Collections;
namespace Speckle.Importers.Rhino.Internal.FileTypeConfig;
public sealed class DefaultConfig : IFileTypeConfig
{
public ArchivableDictionary? ImportOptions => null;
public void PreProcessDocument(RhinoDoc doc) { }
}
@@ -0,0 +1,24 @@
using Rhino;
using Rhino.Collections;
namespace Speckle.Importers.Rhino.Internal.FileTypeConfig;
/// <summary>
/// Represents configuration for a specific file type (e.g. <c>.skp</c>) to customise the import behaviour
/// </summary>
internal interface IFileTypeConfig
{
/// <summary>
/// Options to pass to the <see cref="RhinoDoc.Import(string, ArchivableDictionary?)"/> command
/// </summary>
public ArchivableDictionary? ImportOptions { get; }
/// <summary>
/// Run any operations on objects in the rhino document to clean up the export before converting to speckle
/// </summary>
/// <remarks>
/// Ran on the document after importing, but before any Speckle conversion
/// </remarks>
/// <param name="doc"></param>
public void PreProcessDocument(RhinoDoc doc);
}
@@ -0,0 +1,46 @@
using Rhino;
using Rhino.Collections;
using Rhino.DocObjects;
using Rhino.Geometry;
namespace Speckle.Importers.Rhino.Internal.FileTypeConfig;
public sealed class SketchupConfig : IFileTypeConfig
{
public ArchivableDictionary? ImportOptions => null;
/// <summary>
/// Clean up step to strip imported meshes of their NGon data, leaving only the triangle/quad data behind.
/// This works around a bug in the sketchup importer creating invalid ngons.
/// </summary>
/// <remarks>
/// Without this cleanup step, skp imports send incorrect meshes to speckle
/// I believe there is a bug in Rhino's skp file importing logic
/// The <see cref="MeshNgon.BoundaryVertexIndexList()"/> function documents that it will return ccw faces,
/// and this holds true for native modeled rhino meshes, but not meshes from sketchup imports.
/// Since Speckle's conversions rely on this function returning how it's documented, the resulting speckle geometry
/// would be invalid without this step
/// </remarks>
/// <param name="doc"></param>
public void PreProcessDocument(RhinoDoc doc)
{
// Process regular meshes in the document
foreach (var obj in doc.Objects.GetObjectList(ObjectType.Mesh))
{
if (obj.Geometry is not Mesh mesh)
{
continue;
}
if (mesh.Ngons.Count <= 0)
{
continue;
}
mesh.Ngons.Clear();
_ = doc.Objects.Replace(obj.Id, mesh);
}
//TODO: same for meshes inside blocks
}
}
@@ -0,0 +1,49 @@
using Rhino;
using Speckle.Importers.Rhino.Internal.FileTypeConfig;
using Speckle.Sdk;
using Speckle.Sdk.Credentials;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Importers.Rhino.Internal;
internal sealed class ImporterInstance(Sender sender)
{
public async Task<Version> RunRhinoImport(
string filePath,
string projectId,
string modelId,
Account account,
CancellationToken cancellationToken
)
{
using RhinoDoc open = RhinoDoc.CreateHeadless(null);
try
{
var config = GetConfig(Path.GetExtension(filePath));
RhinoDoc.ActiveDoc = open;
if (!open.Import(filePath, config.ImportOptions))
{
throw new SpeckleException("Rhino could not import this file");
}
config.PreProcessDocument(open);
var version = await sender.Send(projectId, modelId, account, cancellationToken);
return version;
}
finally
{
//Being a bit extra defensive to ensure we're cleaning up the old doc
RhinoDoc.ActiveDoc?.Dispose();
RhinoDoc.ActiveDoc = null;
}
}
private static IFileTypeConfig GetConfig(string extension) =>
extension.ToLowerInvariant() switch
{
".skp" => new SketchupConfig(),
_ => new DefaultConfig(),
};
}
@@ -1,8 +1,11 @@
using Speckle.Connectors.Common.Threading;
namespace Speckle.Importers.Rhino;
namespace Speckle.Importers.Rhino.Internal;
public class ImporterThreadContext : ThreadContext
/// <summary>
/// A custom implementation of the ThreadContext to behave correctly with windowless rhino
/// </summary>
internal sealed class ImporterThreadContext : ThreadContext
{
protected override Task<T> WorkerToMainAsync<T>(Func<Task<T>> action)
{
@@ -1,9 +1,9 @@
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Operations;
namespace Speckle.Importers.Rhino;
namespace Speckle.Importers.Rhino.Internal;
public class Progress(ILogger<Progress> logger) : IProgress<CardProgress>
internal sealed class Progress(ILogger<Progress> logger) : IProgress<CardProgress>
{
private readonly TimeSpan _debounce = TimeSpan.FromMilliseconds(1000);
private DateTime _lastTime = DateTime.UtcNow;
@@ -0,0 +1,64 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Rhino;
using Rhino.DocObjects;
using Speckle.Connectors.Common.Operations;
using Speckle.Converters.Common;
using Speckle.Converters.Rhino;
using Speckle.Sdk;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Logging;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Importers.Rhino.Internal;
internal sealed class Sender(
ISdkActivityFactory activityFactory,
IServiceProvider serviceProvider,
IRhinoConversionSettingsFactory rhinoConversionSettingsFactory,
Progress progress,
ILogger<Sender> logger
)
{
public async Task<Version> Send(
string projectId,
string modelId,
Account account,
CancellationToken cancellationToken
)
{
// NOTE: introduction of AddVisualizationProperties setting not accounted for, hence hardcoded as true (i.e. "as before")
using var activity = activityFactory.Start();
using var scope = serviceProvider.CreateScope();
scope
.ServiceProvider.GetRequiredService<IConverterSettingsStore<RhinoConversionSettings>>()
.Initialize(rhinoConversionSettingsFactory.Create(RhinoDoc.ActiveDoc, true));
List<RhinoObject> rhinoObjects = RhinoDoc
.ActiveDoc.Objects.GetObjectList(ObjectType.AnyObject)
.Where(obj => obj != null)
.ToList();
if (rhinoObjects.Count == 0)
{
throw new SpeckleException("There are no objects found in the file");
}
var operation = scope.ServiceProvider.GetRequiredService<SendOperation<RhinoObject>>();
var buildResults = await operation.Build(rhinoObjects, projectId, progress, cancellationToken);
var (results, version) = await operation.Send(
buildResults.RootObject,
projectId,
modelId,
"Rhino File Importer",
null,
account,
progress,
cancellationToken
);
logger.LogInformation($"Root: {results.RootId}");
return version;
}
}
@@ -0,0 +1,30 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.Rhino.DependencyInjection;
using Speckle.Converters.Rhino;
using Speckle.Sdk.SQLite;
namespace Speckle.Importers.Rhino.Internal;
internal static class ServiceRegistration
{
public static IServiceCollection AddRhinoImporter(this IServiceCollection services)
{
services.Initialize(HostApplications.RhinoImporter, HostAppVersion.v8);
services.AddRhino(false);
services.AddRhinoConverters();
services.AddTransient<Progress>();
services.AddTransient<Sender>();
services.AddTransient<ImporterInstance>();
// override default thread context
services.AddSingleton<IThreadContext>(new ImporterThreadContext());
// override sqlite cache, since we don't want to persist to disk any object data
services.AddTransient<ISqLiteJsonCacheManagerFactory, DummySqliteJsonCacheManagerFactory>();
return services;
}
}
@@ -1,124 +0,0 @@
using System.CommandLine;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Rhino;
using Rhino.Runtime.InProcess;
using Serilog;
using Serilog.Formatting.Compact;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.Rhino.DependencyInjection;
using Speckle.Converters.Rhino;
using Speckle.Newtonsoft.Json;
using Speckle.Sdk.Logging;
namespace Speckle.Importers.Rhino;
public static class Program
{
static Program()
{
Resolver.Initialize();
}
[STAThread]
public static async Task<int> Main(string[] args)
{
try
{
RootCommand rootCommand = new();
Argument<string> pathArg = new(name: "Source File Path", description: "Path to file to load and parse");
Argument<string> resultsPathArg =
new(name: "Results File Path", description: "Path to file to write results information (like version id)");
Argument<string> projectIdArg = new(name: "Project Id", description: "The project id to publish to");
Argument<string> modelIdArg = new(name: "Model Id", description: "The model id to publish to");
Argument<string> serverUrlArg = new(name: "Server Url", description: "The url of the server to publish to.");
Argument<string> tokenArg = new(name: "Token", description: "The speckle token to use for publish");
rootCommand.AddArgument(pathArg);
rootCommand.AddArgument(resultsPathArg);
rootCommand.AddArgument(projectIdArg);
rootCommand.AddArgument(modelIdArg);
rootCommand.AddArgument(serverUrlArg);
rootCommand.AddArgument(tokenArg);
rootCommand.SetHandler(Handle, pathArg, resultsPathArg, projectIdArg, modelIdArg, serverUrlArg, tokenArg);
await rootCommand.InvokeAsync(args).ConfigureAwait(false);
return 0;
}
#pragma warning disable CA1031
catch (Exception e)
#pragma warning restore CA1031
{
Console.WriteLine(e);
return -1;
}
}
#pragma warning disable CA1506
private static async Task Handle(
string filePath,
string resultsPath,
string projectId,
string modelId,
#pragma warning restore CA1506
string serverUrl,
string token
)
{
// Create file with account info here
var accountsDir = SpecklePathProvider.AccountsFolderPath;
if (!Directory.Exists(accountsDir))
{
Directory.CreateDirectory(accountsDir);
}
using (new RhinoCore([], WindowStyle.NoWindow))
{
//doc is often null so dispose the active doc too
using var doc = RhinoDoc.Open(filePath, out _);
using var __ = RhinoDoc.ActiveDoc;
var services = new ServiceCollection();
services.Initialize(HostApplications.RhinoImporter, HostAppVersion.v8);
services.AddRhino(false);
services.AddRhinoConverters();
// override default
services.AddSingleton<IThreadContext>(new ImporterThreadContext());
services.AddTransient<Progress>();
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console(new RenderedCompactJsonFormatter())
.CreateLogger();
services.AddLogging(loggingBuilder =>
{
loggingBuilder.ClearProviders();
loggingBuilder.AddSerilog(dispose: true);
});
// but the Rhino connector has `.rhp` as it is extension.
var container = services.BuildServiceProvider();
try
{
var sender = ActivatorUtilities.CreateInstance<Sender>(container);
var version = await sender.Send(projectId, modelId, new Uri(serverUrl), token);
var result =
version == null
? new RhinoImportResult() { Success = false, ErrorMessage = "Failed to create version!" }
: new RhinoImportResult() { Success = true, CommitId = version.id };
File.WriteAllText(resultsPath, JsonConvert.SerializeObject(result));
}
catch (Exception ex)
{
container.GetRequiredService<ILogger<Sender>>().LogError(ex, "Fatal error for import");
var results = new RhinoImportResult() { Success = false, ErrorMessage = ex.Message, };
File.WriteAllText(resultsPath, JsonConvert.SerializeObject(results));
throw;
}
}
}
}
@@ -1,124 +0,0 @@
using System.Reflection;
using Speckle.Sdk.Common;
namespace Speckle.Importers.Rhino;
//from https://github.com/mcneel/rhino.inside/blob/master/LICENSE
// https://github.com/mcneel/rhino.inside/commit/19b4d571ab3b02f5b29f1e0cf9112845e3323697
// https://github.com/mcneel/rhino.inside/blob/master/DotNet/RhinoInside/Resolver.cs
//
public static class Resolver
{
/// <summary>
/// Set up an assembly resolver to load RhinoCommon and other Rhino
/// assemblies from where Rhino is installed
/// </summary>
public static void Initialize()
{
if (System.IntPtr.Size != 8)
{
throw new InvalidOperationException("Only 64 bit applications can use RhinoInside");
}
AppDomain.CurrentDomain.AssemblyResolve += ResolveForRhinoAssemblies;
}
private static string? s_rhinoSystemDirectory;
/// <summary>
/// Directory used by assembly resolver to attempt load core Rhino assemblies. If not manually set,
/// this will be determined by inspecting the registry
/// </summary>
public static string? RhinoSystemDirectory
{
get
{
if (string.IsNullOrWhiteSpace(s_rhinoSystemDirectory))
{
s_rhinoSystemDirectory = FindRhinoSystemDirectory();
}
return s_rhinoSystemDirectory;
}
set => s_rhinoSystemDirectory = value;
}
/// <summary>
/// Whether or not to use the newest installation of Rhino on the system. By default the resolver will only use an
/// installation with a matching major version.
/// </summary>
public static bool UseLatest { get; set; }
private static Assembly? ResolveForRhinoAssemblies(object? sender, ResolveEventArgs args)
{
string? assemblyName = new AssemblyName(args.Name).Name;
if (RhinoSystemDirectory is null)
{
return null;
}
string path = System.IO.Path.Combine(RhinoSystemDirectory, assemblyName + ".dll");
if (assemblyName != null && assemblyName.StartsWith("Speckle.Connectors.Rhino"))
{
path = Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location).NotNull(),
assemblyName + ".rhp"
);
}
if (System.IO.File.Exists(path))
{
return Assembly.LoadFrom(path);
}
return null;
}
private static string? FindRhinoSystemDirectory()
{
var major = 8;
string baseName = @"SOFTWARE\McNeel\Rhinoceros";
using var baseKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(baseName);
if (baseKey == null)
{
return null;
}
string[] children = baseKey.GetSubKeyNames();
Array.Sort(children);
string versionName = "";
for (int i = children.Length - 1; i >= 0; i--)
{
// 20 Jan 2020 S. Baer (https://github.com/mcneel/rhino.inside/issues/248)
// A generic double.TryParse is failing when run under certain locales.
if (
double.TryParse(
children[i],
System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture,
out double d
)
)
{
versionName = children[i];
if (!UseLatest && (int)Math.Floor(d) != major)
{
continue;
}
using var installKey = baseKey.OpenSubKey($"{versionName}\\Install");
if (installKey is null)
{
return null;
}
string? corePath = (string?)installKey.GetValue("CoreDllPath");
if (System.IO.File.Exists(corePath))
{
return System.IO.Path.GetDirectoryName(corePath);
}
}
}
return null;
}
}
@@ -1,15 +0,0 @@
using Speckle.Newtonsoft.Json;
namespace Speckle.Importers.Rhino;
public class RhinoImportResult
{
[JsonProperty("success")]
public bool Success { get; set; }
[JsonProperty("commitId")]
public string CommitId { get; set; }
[JsonProperty("errorMessage")]
public string ErrorMessage { get; set; }
}
@@ -1,70 +1 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Rhino;
using Rhino.DocObjects;
using Speckle.Connectors.Common.Operations;
using Speckle.Converters.Common;
using Speckle.Converters.Rhino;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Logging;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Importers.Rhino;
public class Sender(
ISdkActivityFactory activityFactory,
IServiceProvider serviceProvider,
IRhinoConversionSettingsFactory rhinoConversionSettingsFactory,
IAccountFactory accountFactory,
Progress progress,
ILogger<Sender> logger
)
{
public async Task<Version?> Send(string projectId, string modelId, Uri serverUrl, string token)
{
// NOTE: introduction of AddVisualizationProperties setting not accounted for, hence hardcoded as true (i.e. "as before")
using var activity = activityFactory.Start();
using var scope = serviceProvider.CreateScope();
scope
.ServiceProvider.GetRequiredService<IConverterSettingsStore<RhinoConversionSettings>>()
.Initialize(rhinoConversionSettingsFactory.Create(RhinoDoc.ActiveDoc, true));
try
{
List<RhinoObject> rhinoObjects = RhinoDoc
.ActiveDoc.Objects.GetObjectList(ObjectType.AnyObject)
.Where(obj => obj != null)
.ToList();
if (rhinoObjects.Count == 0)
{
return null;
}
var account = await accountFactory.CreateAccount(serverUrl, token);
var operation = scope.ServiceProvider.GetRequiredService<SendOperation<RhinoObject>>();
var buildResults = await operation.Build(rhinoObjects, projectId, progress, CancellationToken.None);
var (results, version) = await operation.Send(
buildResults.RootObject,
projectId,
modelId,
token,
null,
account,
progress,
CancellationToken.None
);
logger.LogInformation($"Root: {results.RootId}");
return version;
}
#pragma warning disable CA1031
catch (Exception ex)
#pragma warning restore CA1031
{
logger.LogError(ex, "Error while sending");
}
return null;
}
}

@@ -1,23 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<Configurations>Debug;Release;Local</Configurations>
<OutputType>Exe</OutputType>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<Configurations>Debug;Release;Local</Configurations>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RhinoCommon" IncludeAssets="compile; build" PrivateAssets="all" />
<PackageReference Include="RhinoWindows" IncludeAssets="compile; build" PrivateAssets="all" />
<PackageReference Include="Serilog.Extensions.Logging" />
<PackageReference Include="Serilog.Formatting.Compact" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="System.CommandLine" />
<PackageReference Include="RhinoCommon" IncludeAssets="compile; build" PrivateAssets="all" VersionOverride="8.21.25188.17001"/>
<PackageReference Include="Grasshopper" IncludeAssets="compile; build" PrivateAssets="all" VersionOverride="8.21.25188.17001"/>
<PackageReference Include="RhinoWindows" IncludeAssets="compile; build" PrivateAssets="all" VersionOverride="8.21.25188.17001"/>
<PackageReference Include="Rhino.Inside" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Connectors\Rhino\Speckle.Connectors.Rhino8\Speckle.Connectors.Rhino8.csproj" />
<ProjectReference Include="..\..\..\Connectors\Rhino\Speckle.Connectors.RhinoImporter\Speckle.Connectors.RhinoImporter.csproj" />
<ProjectReference Include="..\..\..\Converters\Rhino\Speckle.Converters.Rhino8\Speckle.Converters.Rhino8.csproj" />
</ItemGroup>
</Project>
@@ -1,14 +1,23 @@
{
"version": 2,
"dependencies": {
".NETFramework,Version=v4.8": {
"net8.0-windows7.0": {
"Grasshopper": {
"type": "Direct",
"requested": "[8.21.25188.17001, )",
"resolved": "8.21.25188.17001",
"contentHash": "qpt9XQWuKLFfzn2cnYf5akfHRwYuffthK5AXsSY1UhjD0eG6bYSTtmAkNVH+Wdg130MZkGqJmPd012Om1kJPBA==",
"dependencies": {
"RhinoCommon": "[8.21.25188.17001]"
}
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net48": "1.0.3"
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
@@ -27,47 +36,32 @@
"resolved": "1.14.1",
"contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ=="
},
"Rhino.Inside": {
"type": "Direct",
"requested": "[8.0.7-beta, )",
"resolved": "8.0.7-beta",
"contentHash": "PNZdWysS2zI9vaxJ8HQig3iZA3Xg1yG1hnq09VPStQpceoj94iJ6xk0Ubddaw5oswkYYUIJRmQ8xr+bLTROs/g==",
"dependencies": {
"Grasshopper": "8.0.23304.9001",
"RhinoCommon": "8.0.23304.9001"
}
},
"RhinoCommon": {
"type": "Direct",
"requested": "[8.9.24194.18121, )",
"resolved": "8.9.24194.18121",
"contentHash": "XRMnm38sBFeMT5AAtRTJdSaql/YNtT02AGi8TEVP1VZ4fkm8VJ1q2nNioWN3tW/+H8Tdi4nV+DuhB/5uE41MCg=="
"requested": "[8.21.25188.17001, )",
"resolved": "8.21.25188.17001",
"contentHash": "Wo6JXheyDBvilyIwDY6xZCQJC4170jzAlTSlMgh8yokUY+vYyCl4KJVXZofIOynNt/xx5wLqb2On5gZZekXR6w==",
"dependencies": {
"System.Drawing.Common": "7.0.0"
}
},
"RhinoWindows": {
"type": "Direct",
"requested": "[8.9.24194.18121, )",
"resolved": "8.9.24194.18121",
"contentHash": "E+MMgC1ITYiPjcHTd+N2vLlg6pNDnRpNL1yGalfPp1eqHJiXkzsGpM1D8oz865D6ybnJAHOOeEVaKqUzFUSUag==",
"requested": "[8.21.25188.17001, )",
"resolved": "8.21.25188.17001",
"contentHash": "9zqCorcLRBeiW/j1RTwUS4E7bnZetAdA9WDdtd/AQccjOpxdtw76wdN+ciyQ6qslseWkwZ9qSBeh7QaM800Ntw==",
"dependencies": {
"RhinoCommon": "[8.9.24194.18121]"
}
},
"Serilog.Extensions.Logging": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "YEAMWu1UnWgf1c1KP85l1SgXGfiVo0Rz6x08pCiPOIBt2Qe18tcZLvdBUuV5o1QHvrs8FAry9wTIhgBRtjIlEg==",
"dependencies": {
"Microsoft.Extensions.Logging": "8.0.0",
"Serilog": "3.1.1"
}
},
"Serilog.Formatting.Compact": {
"type": "Direct",
"requested": "[3.0.0, )",
"resolved": "3.0.0",
"contentHash": "wQsv14w9cqlfB5FX2MZpNsTawckN4a8dryuNGbebB/3Nh1pXnROHZov3swtu3Nj5oNG7Ba+xdu7Et/ulAUPanQ==",
"dependencies": {
"Serilog": "4.0.0"
}
},
"Serilog.Sinks.Console": {
"type": "Direct",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "fQGWqVMClCP2yEyTXPIinSr5c+CBGUvBybPxjAGcf7ctDhadFhrQw03Mv8rJ07/wR5PDfFjewf2LimvXCDzpbA==",
"dependencies": {
"Serilog": "4.0.0"
"RhinoCommon": "[8.21.25188.17001]"
}
},
"Speckle.InterfaceGenerator": {
@@ -76,15 +70,6 @@
"resolved": "0.9.6",
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
},
"System.CommandLine": {
"type": "Direct",
"requested": "[2.0.0-beta4.22272.1, )",
"resolved": "2.0.0-beta4.22272.1",
"contentHash": "1uqED/q2H0kKoLJ4+hI2iPSBSEdTuhfCYADeJrAqERmiGQ2NNacYKRNEQ+gFbU4glgVyK8rxI+ZOe1onEtr/Pg==",
"dependencies": {
"System.Memory": "4.5.4"
}
},
"GraphQL.Client": {
"type": "Transitive",
"resolved": "6.0.0",
@@ -92,7 +77,6 @@
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0",
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
"System.Net.WebSockets.Client.Managed": "1.0.22",
"System.Reactive": "5.0.0"
}
},
@@ -117,29 +101,11 @@
"resolved": "6.0.0",
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"Microsoft.Bcl.HashCode": {
"type": "Transitive",
"resolved": "1.1.1",
"contentHash": "MalY0Y/uM/LjXtHfX/26l2VtN4LDNZ2OE3aumNOHDLsT4fNYy2hiHXI4CXCqKpNUNm7iJ2brrc4J89UdaL56FA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.CSharp": {
"type": "Transitive",
"resolved": "4.7.0",
"contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA=="
},
"Microsoft.Data.Sqlite": {
"type": "Transitive",
"resolved": "7.0.5",
@@ -157,44 +123,69 @@
"SQLitePCLRaw.core": "2.1.4"
}
},
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==",
"dependencies": {
"Microsoft.Extensions.Primitives": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Binder": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==",
"dependencies": {
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "8.0.0",
"System.Threading.Tasks.Extensions": "4.5.4"
}
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
"resolved": "2.2.0",
"contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Primitives": "8.0.0",
"System.ValueTuple": "4.5.0"
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Primitives": "2.2.0",
"System.ComponentModel.Annotations": "4.5.0"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==",
"resolved": "2.2.0",
"contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==",
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
"System.Memory": "4.5.1",
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
}
},
"Microsoft.NETFramework.ReferenceAssemblies.net48": {
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "zMk4D+9zyiEWByyQ7oPImPN/Jhpj166Ky0Nlla4eXlNL8hI/BtSJsgR8Inldd4NNpIAH3oh8yym0W2DrhXdSLQ=="
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Microsoft.Win32.SystemEvents": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "2nXPrhdAyAzir0gLl8Yy8S5Mnm/uBSQQA7jEsILOS1MTyS7DbmV1NgViMtvV1sfCD1ebITpNwb1NIinKeJgUVQ=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
@@ -206,7 +197,7 @@
"contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
"SQLitePCLRaw.provider.dynamic_cdecl": "2.1.4"
"SQLitePCLRaw.provider.e_sqlite3": "2.1.4"
}
},
"SQLitePCLRaw.core": {
@@ -222,113 +213,41 @@
"resolved": "2.1.4",
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
},
"SQLitePCLRaw.provider.dynamic_cdecl": {
"SQLitePCLRaw.provider.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "ZsaKKhgYF9B1fvcnOGKl3EycNAwd9CRWX7v0rEfuPWhQQ5Jjpvf2VEHahiLIGHio3hxi3EIKFJw9KvyowWOUAw==",
"contentHash": "CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"System.Buffers": {
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.5.1",
"contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
"resolved": "4.5.0",
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
},
"System.Collections.Immutable": {
"System.Drawing.Common": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "wfm2NgK22MmBe5qJjp52qzpkeDZKb4l9LbdubhZSehY1z4LS+lld6R+B+UQNb2AZRHu/QJlHxEUcRst5hIEejg==",
"resolved": "7.0.0",
"contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==",
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"System.Diagnostics.DiagnosticSource": {
"type": "Transitive",
"resolved": "8.0.1",
"contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==",
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"System.Formats.Nrbf": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "HuDjUInoaQvnX+3bRg2lX+KLD2raiDJA0v3c4hmukAHSQT8rpa6+2NKRoz+1W8Jmbu5ocvc+xPPKgHMVjeJpOg==",
"dependencies": {
"Microsoft.Bcl.HashCode": "1.1.1",
"System.Reflection.Metadata": "9.0.4",
"System.ValueTuple": "4.5.0"
"Microsoft.Win32.SystemEvents": "7.0.0"
}
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
"dependencies": {
"System.Buffers": "4.5.1",
"System.Numerics.Vectors": "4.5.0",
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"System.Net.WebSockets.Client.Managed": {
"type": "Transitive",
"resolved": "1.0.22",
"contentHash": "WqEOxPlXjuZrIjUtXNE9NxEfU/n5E35iV2PtoZdJSUC4tlrqwHnTee+wvMIM4OUaJWmwrymeqcgYrE0IkGAgLA==",
"dependencies": {
"System.Buffers": "4.4.0",
"System.Numerics.Vectors": "4.4.0"
}
},
"System.Numerics.Vectors": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ=="
"resolved": "4.5.3",
"contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA=="
},
"System.Reactive": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"System.Reflection.Metadata": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "qeJNsMmZPc/Lieg0Md+D4F6LoLcxV3b9QsUNmBRXc2ZVOkMbAcwuO9l2jbQFv3n+fLiHJilN8v6i5aJNivjrCQ==",
"dependencies": {
"System.Collections.Immutable": "9.0.4",
"System.Memory": "4.5.5"
}
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
},
"System.Threading.Channels": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "CMaFr7v+57RW7uZfZkPExsPB6ljwzhjACWW1gfU35Y56rk72B/Wu+sTqxVmGSk4SFUlPc3cjeKND0zktziyjBA==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"System.ValueTuple": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ=="
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
"speckle.connectors.common": {
"type": "Project",
@@ -354,23 +273,15 @@
"Speckle.Connectors.DUI": "[1.0.0, )"
}
},
"speckle.connectors.grasshopper8": {
"type": "Project",
"dependencies": {
"GrasshopperAsyncComponent": "[2.0.3, )",
"Speckle.Connectors.Common": "[1.0.0, )",
"Speckle.Converters.Rhino8": "[1.0.0, )",
"System.Resources.Extensions": "[9.0.4, )"
}
},
"speckle.connectors.logging": {
"type": "Project"
},
"speckle.connectors.rhino8": {
"speckle.connectors.rhinoimporter": {
"type": "Project",
"dependencies": {
"Speckle.Connectors.Common": "[1.0.0, )",
"Speckle.Connectors.DUI.WebView": "[1.0.0, )",
"Speckle.Connectors.Grasshopper8": "[1.0.0, )"
"Speckle.Converters.Rhino8": "[1.0.0, )"
}
},
"speckle.converters.common": {
@@ -383,54 +294,35 @@
"speckle.converters.rhino8": {
"type": "Project",
"dependencies": {
"RhinoCommon": "[8.9.24194.18121, )",
"Speckle.Converters.Common": "[1.0.0, )"
}
},
"GrasshopperAsyncComponent": {
"type": "CentralTransitive",
"requested": "[2.0.3, )",
"resolved": "2.0.3",
"contentHash": "AZvHP96WhYZWftVi7J3J65LiZmXO3hGS6W4AntMMb099gkrLqeiBKC2DOYD6YM9cOyQqly3S5knbUL2yr0jc4Q==",
"dependencies": {
"PolySharp": "1.14.1"
}
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "8.0.0",
"contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==",
"resolved": "2.2.0",
"contentHash": "MZtBIwfDFork5vfjpJdG5g8wuJFt7d/y3LOSVVtDK/76wlbtz6cjltfKHqLx2TKVqTj5/c41t77m1+h20zqtPA==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "8.0.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"System.Threading.Tasks.Extensions": "4.5.4"
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "8.0.0",
"contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==",
"resolved": "2.2.0",
"contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "8.0.0",
"Microsoft.Extensions.DependencyInjection": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
"Microsoft.Extensions.Options": "8.0.0",
"System.Diagnostics.DiagnosticSource": "8.0.0",
"System.ValueTuple": "4.5.0"
"Microsoft.Extensions.Configuration.Binder": "2.2.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging.Abstractions": "2.2.0",
"Microsoft.Extensions.Options": "2.2.0"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "8.0.0",
"contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"System.Buffers": "4.5.1",
"System.Memory": "4.5.5"
}
"resolved": "2.2.0",
"contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A=="
},
"Microsoft.Web.WebView2": {
"type": "CentralTransitive",
@@ -438,16 +330,6 @@
"resolved": "1.0.1938.49",
"contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw=="
},
"Serilog": {
"type": "CentralTransitive",
"requested": "[4.0.1, )",
"resolved": "4.0.0",
"contentHash": "2jDkUrSh5EofOp7Lx5Zgy0EB+7hXjjxE2ktTb1WVQmU00lDACR2TdROGKU0K1pDTBSJBN1PqgYpgOZF8mL7NJw==",
"dependencies": {
"System.Diagnostics.DiagnosticSource": "8.0.1",
"System.Threading.Channels": "8.0.0"
}
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
@@ -470,8 +352,6 @@
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
@@ -485,29 +365,6 @@
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
},
"System.Resources.Extensions": {
"type": "CentralTransitive",
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "X1oj5i1gy6O4TDU8Z48djqbDJJz4qkT+LgvD0Z2pbCsXieJNms9H5yBbvahXdEJad07Ntai5bST4J5XWoNmMsg==",
"dependencies": {
"System.Formats.Nrbf": "9.0.4",
"System.Memory": "4.5.5"
}
}
},
".NETFramework,Version=v4.8/win7-x64": {
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
},
"Microsoft.Web.WebView2": {
"type": "CentralTransitive",
"requested": "[1.0.1938.49, )",
"resolved": "1.0.1938.49",
"contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw=="
}
}
}
+21 -3
View File
@@ -230,6 +230,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Connectors.Grasshop
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Connectors.Rhino8", "Connectors\Rhino\Speckle.Connectors.Rhino8\Speckle.Connectors.Rhino8.csproj", "{5807B4F7-5CCB-E01D-A024-0E8BEFE0D7F0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Connectors.RhinoImporter", "Connectors\Rhino\Speckle.Connectors.RhinoImporter\Speckle.Connectors.RhinoImporter.csproj", "{90EFBF9F-FED4-BD4B-DD87-F3F184C1D073}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Converters.Rhino8", "Converters\Rhino\Speckle.Converters.Rhino8\Speckle.Converters.Rhino8.csproj", "{0D2A2F22-EAB0-802A-7ECE-AB012CE69B8D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{515D0695-DED8-6A19-72D5-7E987736EE52}"
@@ -281,6 +283,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Importers.Ifc", "Im
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Rhino", "Rhino", "{8F2BAB74-BA0A-005A-0FF9-9498950E3ADC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Importers.JobProcessor", "Importers\Rhino\Speckle.Importers.JobProcessor\Speckle.Importers.JobProcessor.csproj", "{391676A9-0E05-9346-FDB9-4F5296F00509}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Importers.Rhino", "Importers\Rhino\Speckle.Importers.Rhino\Speckle.Importers.Rhino.csproj", "{A9B240B9-4F3D-DB41-CB33-BD2019BBCCF2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sdk", "Sdk", "{FF13A09E-0E72-1904-124B-A4E979EF28D3}"
@@ -307,11 +311,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Performance", "Sdk\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Testing", "Sdk\Speckle.Testing\Speckle.Testing.csproj", "{02E79865-6261-CDE0-77EA-3DD2E01DACBD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "..\speckle-sharp-sdk\src\Speckle.Objects\Speckle.Objects", "..\speckle-sharp-sdk\src\Speckle.Objects\Speckle.Objects.csproj", "{FBD58ABD-9704-C232-7CF3-D96EC5CFCBE6}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Objects", "..\speckle-sharp-sdk\src\Speckle.Objects\Speckle.Objects.csproj", "{FBD58ABD-9704-C232-7CF3-D96EC5CFCBE6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "..\speckle-sharp-sdk\src\Speckle.Sdk\Speckle.Sdk", "..\speckle-sharp-sdk\src\Speckle.Sdk\Speckle.Sdk.csproj", "{0201A282-BEFA-8897-C535-03EBAC94870B}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Sdk", "..\speckle-sharp-sdk\src\Speckle.Sdk\Speckle.Sdk.csproj", "{0201A282-BEFA-8897-C535-03EBAC94870B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "..\speckle-sharp-sdk\src\Speckle.Sdk.Dependencies\Speckle.Sdk.Dependencies", "..\speckle-sharp-sdk\src\Speckle.Sdk.Dependencies\Speckle.Sdk.Dependencies.csproj", "{0677CA87-027F-D526-4463-3517F75B76EC}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Sdk.Dependencies", "..\speckle-sharp-sdk\src\Speckle.Sdk.Dependencies\Speckle.Sdk.Dependencies.csproj", "{0677CA87-027F-D526-4463-3517F75B76EC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -650,6 +654,12 @@ Global
{5807B4F7-5CCB-E01D-A024-0E8BEFE0D7F0}.Local|Any CPU.Build.0 = Local|Any CPU
{5807B4F7-5CCB-E01D-A024-0E8BEFE0D7F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5807B4F7-5CCB-E01D-A024-0E8BEFE0D7F0}.Release|Any CPU.Build.0 = Release|Any CPU
{90EFBF9F-FED4-BD4B-DD87-F3F184C1D073}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{90EFBF9F-FED4-BD4B-DD87-F3F184C1D073}.Debug|Any CPU.Build.0 = Debug|Any CPU
{90EFBF9F-FED4-BD4B-DD87-F3F184C1D073}.Local|Any CPU.ActiveCfg = Local|Any CPU
{90EFBF9F-FED4-BD4B-DD87-F3F184C1D073}.Local|Any CPU.Build.0 = Local|Any CPU
{90EFBF9F-FED4-BD4B-DD87-F3F184C1D073}.Release|Any CPU.ActiveCfg = Release|Any CPU
{90EFBF9F-FED4-BD4B-DD87-F3F184C1D073}.Release|Any CPU.Build.0 = Release|Any CPU
{0D2A2F22-EAB0-802A-7ECE-AB012CE69B8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D2A2F22-EAB0-802A-7ECE-AB012CE69B8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D2A2F22-EAB0-802A-7ECE-AB012CE69B8D}.Local|Any CPU.ActiveCfg = Local|Any CPU
@@ -710,6 +720,12 @@ Global
{9F11639F-3AB6-867F-7968-FFF8BFD8C410}.Local|Any CPU.Build.0 = Local|Any CPU
{9F11639F-3AB6-867F-7968-FFF8BFD8C410}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F11639F-3AB6-867F-7968-FFF8BFD8C410}.Release|Any CPU.Build.0 = Release|Any CPU
{391676A9-0E05-9346-FDB9-4F5296F00509}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{391676A9-0E05-9346-FDB9-4F5296F00509}.Debug|Any CPU.Build.0 = Debug|Any CPU
{391676A9-0E05-9346-FDB9-4F5296F00509}.Local|Any CPU.ActiveCfg = Local|Any CPU
{391676A9-0E05-9346-FDB9-4F5296F00509}.Local|Any CPU.Build.0 = Local|Any CPU
{391676A9-0E05-9346-FDB9-4F5296F00509}.Release|Any CPU.ActiveCfg = Release|Any CPU
{391676A9-0E05-9346-FDB9-4F5296F00509}.Release|Any CPU.Build.0 = Release|Any CPU
{A9B240B9-4F3D-DB41-CB33-BD2019BBCCF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A9B240B9-4F3D-DB41-CB33-BD2019BBCCF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9B240B9-4F3D-DB41-CB33-BD2019BBCCF2}.Local|Any CPU.ActiveCfg = Local|Any CPU
@@ -904,6 +920,7 @@ Global
{DB77E488-CD1B-681C-687F-AFA935E8E6A8} = {1FE9B3A2-9228-8E78-261C-2221B28F0E7B}
{2DF824DD-0A2F-0B15-F225-B054C92BC4EA} = {DB77E488-CD1B-681C-687F-AFA935E8E6A8}
{5807B4F7-5CCB-E01D-A024-0E8BEFE0D7F0} = {DB77E488-CD1B-681C-687F-AFA935E8E6A8}
{90EFBF9F-FED4-BD4B-DD87-F3F184C1D073} = {DB77E488-CD1B-681C-687F-AFA935E8E6A8}
{0D2A2F22-EAB0-802A-7ECE-AB012CE69B8D} = {DB77E488-CD1B-681C-687F-AFA935E8E6A8}
{515D0695-DED8-6A19-72D5-7E987736EE52} = {1FE9B3A2-9228-8E78-261C-2221B28F0E7B}
{66FDCF46-2365-F12E-9F40-92E86543B5A2} = {515D0695-DED8-6A19-72D5-7E987736EE52}
@@ -927,6 +944,7 @@ Global
{9DC47E2F-957F-CECC-82C0-1BAD872F8E13} = {88E31408-0177-4235-0BE8-6C9C8103E392}
{9F11639F-3AB6-867F-7968-FFF8BFD8C410} = {88E31408-0177-4235-0BE8-6C9C8103E392}
{8F2BAB74-BA0A-005A-0FF9-9498950E3ADC} = {0B7587C3-F447-44C0-7126-E1085C8A32F4}
{391676A9-0E05-9346-FDB9-4F5296F00509} = {8F2BAB74-BA0A-005A-0FF9-9498950E3ADC}
{A9B240B9-4F3D-DB41-CB33-BD2019BBCCF2} = {8F2BAB74-BA0A-005A-0FF9-9498950E3ADC}
{9AA81E7A-C9BC-C56B-BE8F-55BC1D85EEBF} = {FF13A09E-0E72-1904-124B-A4E979EF28D3}
{1BD0E5FD-D4B8-8018-08FE-E7DD4ABA741F} = {FF13A09E-0E72-1904-124B-A4E979EF28D3}
+7 -3
View File
@@ -151,6 +151,7 @@
<Folder Name="/Connectors/Rhino/8/">
<Project Path="Connectors\Rhino\Speckle.Connectors.Grasshopper8\Speckle.Connectors.Grasshopper8.csproj" />
<Project Path="Connectors\Rhino\Speckle.Connectors.Rhino8\Speckle.Connectors.Rhino8.csproj" />
<Project Path="Connectors\Rhino\Speckle.Connectors.RhinoImporter\Speckle.Connectors.RhinoImporter.csproj" Type="C#" />
<Project Path="Converters\Rhino\Speckle.Converters.Rhino8\Speckle.Converters.Rhino8.csproj" />
</Folder>
<Folder Name="/Connectors/Rhino/Shared/">
@@ -184,6 +185,9 @@
<Project Path="Importers\Ifc\Speckle.Importers.Ifc\Speckle.Importers.Ifc.csproj" />
</Folder>
<Folder Name="/Importers/Rhino/">
<Project Path="Importers\Rhino\Speckle.Importers.JobProcessor\Speckle.Importers.JobProcessor.csproj" Type="C#">
<Configuration Solution="Local|Any CPU" Project="Debug|Any CPU" />
</Project>
<Project Path="Importers\Rhino\Speckle.Importers.Rhino\Speckle.Importers.Rhino.csproj" />
</Folder>
<Folder Name="/Sdk/">
@@ -199,7 +203,7 @@
<Project Path="Sdk\Speckle.Performance\Speckle.Performance.csproj" />
<Project Path="Sdk\Speckle.Testing\Speckle.Testing.csproj" />
</Folder>
<Project Path="..\speckle-sharp-sdk\src\Speckle.Objects\Speckle.Objects.csproj" />
<Project Path="..\speckle-sharp-sdk\src\Speckle.Sdk.Dependencies\Speckle.Sdk.Dependencies.csproj" />
<Project Path="..\speckle-sharp-sdk\src\Speckle.Sdk\Speckle.Sdk.csproj" />
<Project Path="../speckle-sharp-sdk/src/Speckle.Objects/Speckle.Objects.csproj" />
<Project Path="../speckle-sharp-sdk/src/Speckle.Sdk.Dependencies/Speckle.Sdk.Dependencies.csproj" />
<Project Path="../speckle-sharp-sdk/src/Speckle.Sdk/Speckle.Sdk.csproj" />
</Solution>
+3
View File
@@ -67,6 +67,9 @@
<Project Path="Importers\Ifc\Speckle.Importers.Ifc\Speckle.Importers.Ifc.csproj" />
</Folder>
<Folder Name="/Importers/Rhino/">
<Project Path="Importers\Rhino\Speckle.Importers.JobProcessor\Speckle.Importers.JobProcessor.csproj" Type="C#">
<Configuration Solution="Local|Any CPU" Project="Debug|Any CPU" />
</Project>
<Project Path="Importers\Rhino\Speckle.Importers.Rhino\Speckle.Importers.Rhino.csproj" />
</Folder>
<Folder Name="/Sdk/">
+3
View File
@@ -67,6 +67,9 @@
<Project Path="Importers\Ifc\Speckle.Importers.Ifc\Speckle.Importers.Ifc.csproj" />
</Folder>
<Folder Name="/Importers/Rhino/">
<Project Path="Importers\Rhino\Speckle.Importers.JobProcessor\Speckle.Importers.JobProcessor.csproj" Type="C#">
<Configuration Solution="Local|Any CPU" Project="Debug|Any CPU" />
</Project>
<Project Path="Importers\Rhino\Speckle.Importers.Rhino\Speckle.Importers.Rhino.csproj" />
</Folder>
<Folder Name="/Sdk/">
+19
View File
@@ -310,6 +310,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Rhino", "Rhino", "{CD0C216A
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Importers.Rhino", "Importers\Rhino\Speckle.Importers.Rhino\Speckle.Importers.Rhino.csproj", "{92F065BE-97E2-4192-B561-88A6A8CFA53F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Importers.JobProcessor", "Importers\Rhino\Speckle.Importers.JobProcessor\Speckle.Importers.JobProcessor.csproj", "{1953F875-B271-484B-B589-3D4AC1288C2B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Connectors.RhinoImporter", "Connectors\Rhino\Speckle.Connectors.RhinoImporter\Speckle.Connectors.RhinoImporter.csproj", "{5422F2C8-1E00-4DAE-BB01-65A17BE8CD68}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -779,6 +783,18 @@ Global
{92F065BE-97E2-4192-B561-88A6A8CFA53F}.Local|Any CPU.Build.0 = Local|Any CPU
{92F065BE-97E2-4192-B561-88A6A8CFA53F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{92F065BE-97E2-4192-B561-88A6A8CFA53F}.Release|Any CPU.Build.0 = Release|Any CPU
{1953F875-B271-484B-B589-3D4AC1288C2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1953F875-B271-484B-B589-3D4AC1288C2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1953F875-B271-484B-B589-3D4AC1288C2B}.Local|Any CPU.ActiveCfg = Debug|Any CPU
{1953F875-B271-484B-B589-3D4AC1288C2B}.Local|Any CPU.Build.0 = Debug|Any CPU
{1953F875-B271-484B-B589-3D4AC1288C2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1953F875-B271-484B-B589-3D4AC1288C2B}.Release|Any CPU.Build.0 = Release|Any CPU
{5422F2C8-1E00-4DAE-BB01-65A17BE8CD68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5422F2C8-1E00-4DAE-BB01-65A17BE8CD68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5422F2C8-1E00-4DAE-BB01-65A17BE8CD68}.Local|Any CPU.ActiveCfg = Local|Any CPU
{5422F2C8-1E00-4DAE-BB01-65A17BE8CD68}.Local|Any CPU.Build.0 = Local|Any CPU
{5422F2C8-1E00-4DAE-BB01-65A17BE8CD68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5422F2C8-1E00-4DAE-BB01-65A17BE8CD68}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -918,6 +934,8 @@ Global
{60445031-CCEE-4421-83C0-BB10BEF7B7F2} = {FC224610-32D3-454E-9BC1-1219FE8ACD5F}
{CD0C216A-2A96-4423-956E-3DDFCAEA510C} = {336F0341-5C39-40F7-9377-122FED4E4549}
{92F065BE-97E2-4192-B561-88A6A8CFA53F} = {CD0C216A-2A96-4423-956E-3DDFCAEA510C}
{1953F875-B271-484B-B589-3D4AC1288C2B} = {CD0C216A-2A96-4423-956E-3DDFCAEA510C}
{5422F2C8-1E00-4DAE-BB01-65A17BE8CD68} = {5929C9C7-F971-449E-BC5B-4486016BD11F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EE253116-7070-4E9A-BCE8-2911C251B8C8}
@@ -950,6 +968,7 @@ Global
Connectors\Autocad\Speckle.Connectors.Civil3dShared\Speckle.Connectors.Civil3dShared.projitems*{4459f2b1-a340-488e-a856-eb2ae9c72ad4}*SharedItemsImports = 5
Converters\Revit\Speckle.Converters.RevitShared\Speckle.Converters.RevitShared.projitems*{4d40a101-07e6-4ff2-8934-83434932591d}*SharedItemsImports = 5
Converters\Tekla\Speckle.Converters.TeklaShared\Speckle.Converters.TeklaShared.projitems*{52666479-5401-47d6-b7ba-d554784439ea}*SharedItemsImports = 13
Connectors\Rhino\Speckle.Connectors.RhinoShared\Speckle.Connectors.RhinoShared.projitems*{5422f2c8-1e00-4dae-bb01-65a17be8cd68}*SharedItemsImports = 5
Converters\Autocad\Speckle.Converters.AutocadShared\Speckle.Converters.AutocadShared.projitems*{5505b953-d434-49ce-8ebd-efd7b3c378f7}*SharedItemsImports = 5
Converters\Navisworks\Speckle.Converters.NavisworksShared\Speckle.Converters.NavisworksShared.projitems*{56680ea7-3599-4d88-83a5-b43ba93ac046}*SharedItemsImports = 5
Converters\Rhino\Speckle.Converters.RhinoShared\Speckle.Converters.RhinoShared.projitems*{56a909ae-6e99-4d4d-a22e-38bdc5528b8e}*SharedItemsImports = 5
+4
View File
@@ -151,6 +151,7 @@
<Folder Name="/Connectors/Rhino/8/">
<Project Path="Connectors\Rhino\Speckle.Connectors.Grasshopper8\Speckle.Connectors.Grasshopper8.csproj" />
<Project Path="Connectors\Rhino\Speckle.Connectors.Rhino8\Speckle.Connectors.Rhino8.csproj" />
<Project Path="Connectors\Rhino\Speckle.Connectors.RhinoImporter\Speckle.Connectors.RhinoImporter.csproj" Type="Classic C#" />
<Project Path="Converters\Rhino\Speckle.Converters.Rhino8\Speckle.Converters.Rhino8.csproj" />
</Folder>
<Folder Name="/Connectors/Rhino/Shared/">
@@ -184,6 +185,9 @@
<Project Path="Importers\Ifc\Speckle.Importers.Ifc\Speckle.Importers.Ifc.csproj" />
</Folder>
<Folder Name="/Importers/Rhino/">
<Project Path="Importers\Rhino\Speckle.Importers.JobProcessor\Speckle.Importers.JobProcessor.csproj" Type="Classic C#">
<Configuration Solution="Local|Any CPU" Project="Debug|Any CPU" />
</Project>
<Project Path="Importers\Rhino\Speckle.Importers.Rhino\Speckle.Importers.Rhino.csproj" />
</Folder>
<Folder Name="/Sdk/">
+3
View File
@@ -43,6 +43,9 @@
<Project Path="Importers\Ifc\Speckle.Importers.Ifc\Speckle.Importers.Ifc.csproj" />
</Folder>
<Folder Name="/Importers/Rhino/">
<Project Path="Importers\Rhino\Speckle.Importers.JobProcessor\Speckle.Importers.JobProcessor.csproj" Type="C#">
<Configuration Solution="Local|Any CPU" Project="Debug|Any CPU" />
</Project>
<Project Path="Importers\Rhino\Speckle.Importers.Rhino\Speckle.Importers.Rhino.csproj" />
</Folder>
<Folder Name="/Sdk/">
+3
View File
@@ -63,6 +63,9 @@
<Project Path="Importers\Ifc\Speckle.Importers.Ifc\Speckle.Importers.Ifc.csproj" />
</Folder>
<Folder Name="/Importers/Rhino/">
<Project Path="Importers\Rhino\Speckle.Importers.JobProcessor\Speckle.Importers.JobProcessor.csproj" Type="C#">
<Configuration Solution="Local|Any CPU" Project="Debug|Any CPU" />
</Project>
<Project Path="Importers\Rhino\Speckle.Importers.Rhino\Speckle.Importers.Rhino.csproj" />
</Folder>
<Folder Name="/Sdk/">
+6 -3
View File
@@ -58,6 +58,9 @@
<Project Path="Importers\Ifc\Speckle.Importers.Ifc\Speckle.Importers.Ifc.csproj" />
</Folder>
<Folder Name="/Importers/Rhino/">
<Project Path="Importers\Rhino\Speckle.Importers.JobProcessor\Speckle.Importers.JobProcessor.csproj" Type="C#">
<Configuration Solution="Local|Any CPU" Project="Debug|Any CPU" />
</Project>
<Project Path="Importers\Rhino\Speckle.Importers.Rhino\Speckle.Importers.Rhino.csproj" />
</Folder>
<Folder Name="/Sdk/">
@@ -73,7 +76,7 @@
<Project Path="Sdk\Speckle.Performance\Speckle.Performance.csproj" />
<Project Path="Sdk\Speckle.Testing\Speckle.Testing.csproj" />
</Folder>
<Project Path="..\speckle-sharp-sdk\src\Speckle.Objects\Speckle.Objects.csproj" />
<Project Path="..\speckle-sharp-sdk\src\Speckle.Sdk.Dependencies\Speckle.Sdk.Dependencies.csproj" />
<Project Path="..\speckle-sharp-sdk\src\Speckle.Sdk\Speckle.Sdk.csproj" />
<Project Path="../speckle-sharp-sdk/src/Speckle.Objects/Speckle.Objects.csproj" />
<Project Path="../speckle-sharp-sdk/src/Speckle.Sdk.Dependencies/Speckle.Sdk.Dependencies.csproj" />
<Project Path="../speckle-sharp-sdk/src/Speckle.Sdk/Speckle.Sdk.csproj" />
</Solution>
+3
View File
@@ -58,6 +58,9 @@
<Project Path="Importers\Ifc\Speckle.Importers.Ifc\Speckle.Importers.Ifc.csproj" />
</Folder>
<Folder Name="/Importers/Rhino/">
<Project Path="Importers\Rhino\Speckle.Importers.JobProcessor\Speckle.Importers.JobProcessor.csproj" Type="C#">
<Configuration Solution="Local|Any CPU" Project="Debug|Any CPU" />
</Project>
<Project Path="Importers\Rhino\Speckle.Importers.Rhino\Speckle.Importers.Rhino.csproj" />
</Folder>
<Folder Name="/Sdk/">
+57
View File
@@ -0,0 +1,57 @@
<Solution>
<Configurations>
<BuildType Name="Debug" />
<BuildType Name="Local" />
<BuildType Name="Release" />
</Configurations>
<Folder Name="/Build/">
<Project Path="Build\Build.csproj" />
<File Path=".github\workflows\pr.yml" />
<File Path=".github\workflows\release.yml" />
</Folder>
<Folder Name="/Config/">
<File Path=".csharpierrc.yaml" />
<File Path=".editorconfig" />
<File Path="codecov.yml" />
<File Path="CodeMetricsConfig.txt" />
<File Path="Directory.Build.props" />
<File Path="Directory.Packages.props" />
<File Path=".config\dotnet-tools.json" />
<File Path="global.json" />
<File Path="README.md" />
</Folder>
<Folder Name="/Connectors/" />
<Folder Name="/Connectors/Rhino/">
<Project Path="Connectors\Rhino\Speckle.Connectors.RhinoImporter\Speckle.Connectors.RhinoImporter.csproj" Type="Classic C#" />
<Project Path="Connectors\Rhino\Speckle.Connectors.RhinoShared\Speckle.Connectors.RhinoShared.shproj" />
</Folder>
<Folder Name="/Connectors/Rhino/Converter/">
<Project Path="Converters\Rhino\Speckle.Converters.Rhino8\Speckle.Converters.Rhino8.csproj" Type="Classic C#" />
<Project Path="Converters\Rhino\Speckle.Converters.RhinoShared\Speckle.Converters.RhinoShared.shproj" />
</Folder>
<Folder Name="/Importers/" />
<Folder Name="/Importers/Ifc/">
<Project Path="Importers\Ifc\Speckle.Importers.Ifc.Tester2\Speckle.Importers.Ifc.Tester2.csproj" />
<Project Path="Importers\Ifc\Speckle.Importers.Ifc.Tester\Speckle.Importers.Ifc.Tester.csproj" />
<Project Path="Importers\Ifc\Speckle.Importers.Ifc\Speckle.Importers.Ifc.csproj" />
</Folder>
<Folder Name="/Importers/Rhino/">
<Project Path="Importers\Rhino\Speckle.Importers.JobProcessor\Speckle.Importers.JobProcessor.csproj" Type="C#">
<Configuration Solution="Local|Any CPU" Project="Debug|Any CPU" />
</Project>
<Project Path="Importers\Rhino\Speckle.Importers.Rhino\Speckle.Importers.Rhino.csproj" />
</Folder>
<Folder Name="/Sdk/">
<Project Path="DUI3\Speckle.Connectors.DUI.Tests\Speckle.Connectors.DUI.Tests.csproj" />
<Project Path="DUI3\Speckle.Connectors.DUI.WebView\Speckle.Connectors.DUI.WebView.csproj" />
<Project Path="DUI3\Speckle.Connectors.DUI\Speckle.Connectors.DUI.csproj" />
<Project Path="Sdk\Speckle.Common.MeshTriangulation\Speckle.Common.MeshTriangulation.csproj" />
<Project Path="Sdk\Speckle.Connectors.Common.Tests\Speckle.Connectors.Common.Tests.csproj" />
<Project Path="Sdk\Speckle.Connectors.Common\Speckle.Connectors.Common.csproj" />
<Project Path="Sdk\Speckle.Connectors.Logging\Speckle.Connectors.Logging.csproj" />
<Project Path="Sdk\Speckle.Converters.Common.Tests\Speckle.Converters.Common.Tests.csproj" />
<Project Path="Sdk\Speckle.Converters.Common\Speckle.Converters.Common.csproj" />
<Project Path="Sdk\Speckle.Performance\Speckle.Performance.csproj" />
<Project Path="Sdk\Speckle.Testing\Speckle.Testing.csproj" />
</Folder>
</Solution>
+4
View File
@@ -32,6 +32,7 @@
<Folder Name="/Connectors/Rhino/8/">
<Project Path="Connectors\Rhino\Speckle.Connectors.Grasshopper8\Speckle.Connectors.Grasshopper8.csproj" />
<Project Path="Connectors\Rhino\Speckle.Connectors.Rhino8\Speckle.Connectors.Rhino8.csproj" />
<Project Path="Connectors\Rhino\Speckle.Connectors.RhinoImporter\Speckle.Connectors.RhinoImporter.csproj" Type="C#" />
<Project Path="Converters\Rhino\Speckle.Converters.Rhino8\Speckle.Converters.Rhino8.csproj" />
</Folder>
<Folder Name="/Connectors/Rhino/Shared/">
@@ -46,6 +47,9 @@
<Project Path="Importers\Ifc\Speckle.Importers.Ifc\Speckle.Importers.Ifc.csproj" />
</Folder>
<Folder Name="/Importers/Rhino/">
<Project Path="Importers\Rhino\Speckle.Importers.JobProcessor\Speckle.Importers.JobProcessor.csproj" Type="C#">
<Configuration Solution="Local|Any CPU" Project="Debug|Any CPU" />
</Project>
<Project Path="Importers\Rhino\Speckle.Importers.Rhino\Speckle.Importers.Rhino.csproj" />
</Folder>
<Folder Name="/Sdk/">
+3
View File
@@ -47,6 +47,9 @@
<Project Path="Importers\Ifc\Speckle.Importers.Ifc\Speckle.Importers.Ifc.csproj" />
</Folder>
<Folder Name="/Importers/Rhino/">
<Project Path="Importers\Rhino\Speckle.Importers.JobProcessor\Speckle.Importers.JobProcessor.csproj" Type="C#">
<Configuration Solution="Local|Any CPU" Project="Debug|Any CPU" />
</Project>
<Project Path="Importers\Rhino\Speckle.Importers.Rhino\Speckle.Importers.Rhino.csproj" />
</Folder>
<Folder Name="/Sdk/">