Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 27a7d72de3 | |||
| 678f113d05 | |||
| 92da66bbbb | |||
| 79a5228899 | |||
| 4d9411de42 | |||
| 3780747992 | |||
| 4514b1b831 | |||
| 2bbbbf6204 | |||
| e1b5dea3f7 | |||
| 2d2c274030 | |||
| 81dd72a281 | |||
| b82349478c | |||
| 7d0690f7a0 | |||
| 62a0cb895d | |||
| f28ce73d33 | |||
| 15425c5328 | |||
| 7c645e3c51 | |||
| 795d068175 | |||
| 90c2bd2873 | |||
| bd7a3c7c43 | |||
| ea976309bc | |||
| 1b5787274a | |||
| 7e595deabc | |||
| 66091b2b73 | |||
| 4f8d8d4f07 | |||
| 4fba12f966 | |||
| 348975c33d | |||
| cd6888868e | |||
| f2d4e64005 | |||
| a92b88f6d3 | |||
| abfdbdeffa | |||
| efe66e7e98 | |||
| c3fa1bb0dc | |||
| e487981e5b | |||
| 9a6dda629b | |||
| 46e7d6e432 | |||
| b9f4845fa7 | |||
| 36863efc5a | |||
| a0ce883a3f | |||
| bc0fe17d08 | |||
| 2e52409db6 | |||
| f434cde7b3 | |||
| 3e596cac29 | |||
| 876d5c1bfe | |||
| 7f3b23e71e | |||
| 9b0a6c3202 | |||
| de662e4a2b |
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
+52
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+282
-178
@@ -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;
|
||||
|
||||
+18
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+4
-5
@@ -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;
|
||||
}
|
||||
|
||||
+152
-42
@@ -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,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);
|
||||
}
|
||||
|
||||
+14
-61
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -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()
|
||||
|
||||
+2
-3
@@ -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()
|
||||
|
||||
+2
-3
@@ -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
|
||||
|
||||
+16
-23
@@ -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();
|
||||
|
||||
+2
-3
@@ -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; }
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using Speckle.Importers.JobProcessor.Domain;
|
||||
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 : IJobHandler
|
||||
internal sealed class RhinoJobHandler(ILogger<RhinoJobHandler> logger) : IJobHandler
|
||||
{
|
||||
public async Task<Version> ProcessJob(FileimportJob job, IClient client, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -31,7 +32,23 @@ internal sealed class RhinoJobHandler : IJobHandler
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(directory.FullName, true);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@ internal sealed class JobProcessorInstance(
|
||||
{
|
||||
await using var connection = await repository.SetupConnection(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
logger.LogInformation("Listening for jobs...");
|
||||
|
||||
while (true)
|
||||
{
|
||||
logger.LogInformation("Listening for jobs...");
|
||||
|
||||
FileimportJob? job = await repository.GetNextJob(connection, cancellationToken);
|
||||
if (job == null)
|
||||
{
|
||||
@@ -94,7 +94,7 @@ internal sealed class JobProcessorInstance(
|
||||
projectId = job.Payload.ProjectId,
|
||||
jobId = job.Payload.BlobId,
|
||||
warnings = [],
|
||||
reason = ex.ToString(),
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Rhino;
|
||||
using Rhino;
|
||||
using Speckle.Importers.Rhino.Internal.FileTypeConfig;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Credentials;
|
||||
@@ -34,10 +34,9 @@ internal sealed class ImporterInstance(Sender sender)
|
||||
}
|
||||
finally
|
||||
{
|
||||
//Being a bit extra defensive that we're cleaning up the old doc
|
||||
//Being a bit extra defensive to ensure we're cleaning up the old doc
|
||||
RhinoDoc.ActiveDoc?.Dispose();
|
||||
RhinoDoc.ActiveDoc = null;
|
||||
GC.Collect();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ 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;
|
||||
|
||||
@@ -21,6 +22,9 @@ internal static class ServiceRegistration
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -968,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
|
||||
|
||||
Reference in New Issue
Block a user