Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 14046c6fad | |||
| 9d9a27d9cb | |||
| b2a885c193 | |||
| 60c1811fa6 | |||
| 5365809172 |
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -210,6 +210,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -210,6 +210,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -268,6 +268,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -268,6 +268,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -268,6 +268,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -219,6 +219,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -219,6 +219,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -210,6 +210,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -265,6 +265,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
-4
@@ -16,7 +16,6 @@ using Speckle.Connectors.DUI.Models;
|
||||
using Speckle.Connectors.DUI.Models.Card.SendFilter;
|
||||
using Speckle.Connectors.DUI.WebView;
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converter.Navisworks.Services;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Sdk.Models.GraphTraversal;
|
||||
|
||||
@@ -53,9 +52,6 @@ public static class NavisworksConnectorServiceRegistration
|
||||
serviceCollection.AddScoped<NavisworksMaterialUnpacker>();
|
||||
serviceCollection.AddScoped<NavisworksColorUnpacker>();
|
||||
|
||||
// Register dual shared geometry stores for instancing pattern
|
||||
serviceCollection.AddScoped<InstanceStoreManager>();
|
||||
|
||||
serviceCollection.AddSingleton<IAppIdleManager, NavisworksIdleManager>();
|
||||
|
||||
// Sending operations
|
||||
|
||||
+3
-20
@@ -1,10 +1,7 @@
|
||||
using Autodesk.Navisworks.Api.ComApi;
|
||||
using Autodesk.Navisworks.Api.Interop.ComApi;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Connector.Navisworks.Services;
|
||||
using Speckle.Converter.Navisworks.Helpers;
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converter.Navisworks.ToSpeckle;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Sdk;
|
||||
@@ -14,8 +11,7 @@ namespace Speckle.Connector.Navisworks.HostApp;
|
||||
public class NavisworksMaterialUnpacker(
|
||||
ILogger<NavisworksMaterialUnpacker> logger,
|
||||
IConverterSettingsStore<NavisworksConversionSettings> converterSettings,
|
||||
IElementSelectionService selectionService,
|
||||
GeometryToSpeckleConverter converter
|
||||
IElementSelectionService selectionService
|
||||
)
|
||||
{
|
||||
// Helper function to select a property based on the representation mode
|
||||
@@ -68,19 +64,6 @@ public class NavisworksMaterialUnpacker(
|
||||
|
||||
var navisworksObjectId = selectionService.GetModelItemPath(navisworksObject);
|
||||
var finalId = mergedIds.TryGetValue(navisworksObjectId, out var mergedId) ? mergedId : navisworksObjectId;
|
||||
|
||||
var item = selectionService.GetModelItemFromPath(finalId);
|
||||
string hashId = "";
|
||||
var comSelection = ComApiBridge.ToInwOpSelection([item]);
|
||||
var paths = comSelection.Paths();
|
||||
var path = paths.OfType<InwOaPath>().First();
|
||||
var fragments = path.Fragments();
|
||||
if (fragments.Count > 1)
|
||||
{
|
||||
var fragmentId = converter.GenerateFragmentId(paths);
|
||||
hashId = $"geom_{fragmentId}";
|
||||
}
|
||||
|
||||
var geometry = navisworksObject.Geometry;
|
||||
var mode = converterSettings.Current.User.VisualRepresentationMode;
|
||||
|
||||
@@ -137,7 +120,7 @@ public class NavisworksMaterialUnpacker(
|
||||
|
||||
if (renderMaterialProxies.TryGetValue(renderMaterialId.ToString(), out RenderMaterialProxy? value))
|
||||
{
|
||||
value.objects.Add(!string.IsNullOrEmpty(hashId) ? hashId : finalId);
|
||||
value.objects.Add(finalId);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -149,7 +132,7 @@ public class NavisworksMaterialUnpacker(
|
||||
renderColor,
|
||||
renderMaterialId
|
||||
),
|
||||
objects = [!string.IsNullOrEmpty(hashId) ? hashId : finalId]
|
||||
objects = [finalId]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+13
-49
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Connector.Navisworks.HostApp;
|
||||
using Speckle.Connector.Navisworks.Services;
|
||||
using Speckle.Connectors.Common.Builders;
|
||||
@@ -6,7 +6,6 @@ using Speckle.Connectors.Common.Caching;
|
||||
using Speckle.Connectors.Common.Conversion;
|
||||
using Speckle.Connectors.Common.Operations;
|
||||
using Speckle.Converter.Navisworks.Helpers;
|
||||
using Speckle.Converter.Navisworks.Services;
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Objects.Data;
|
||||
@@ -27,6 +26,7 @@ public class NavisworksRootObjectBuilder(
|
||||
NavisworksMaterialUnpacker materialUnpacker,
|
||||
NavisworksColorUnpacker colorUnpacker,
|
||||
IElementSelectionService elementSelectionService,
|
||||
IUiUnitsCache uiUnitsCache,
|
||||
InstanceStoreManager instanceStoreManager
|
||||
) : IRootObjectBuilder<NAV.ModelItem>
|
||||
{
|
||||
@@ -43,7 +43,7 @@ public class NavisworksRootObjectBuilder(
|
||||
{
|
||||
#if DEBUG
|
||||
// This is a temporary workaround to disable node merging for debugging purposes - false is default, true is for debugging
|
||||
SkipNodeMerging = true;
|
||||
SkipNodeMerging = false;
|
||||
#endif
|
||||
using var activity = activityFactory.Start("Build");
|
||||
|
||||
@@ -52,42 +52,22 @@ public class NavisworksRootObjectBuilder(
|
||||
// 2. Initialize root collection
|
||||
var rootCollection = InitializeRootCollection();
|
||||
|
||||
// InstanceStoreManager is scoped - starts fresh for each conversion session
|
||||
|
||||
// 3. Convert all model items and store results
|
||||
(Dictionary<string, Base?> convertedElements, List<SendConversionResult> conversionResults) =
|
||||
await ConvertModelItemsAsync(navisworksModelItems, projectId, onOperationProgressed, cancellationToken);
|
||||
var (convertedElements, conversionResults) = await ConvertModelItemsAsync(
|
||||
navisworksModelItems,
|
||||
projectId,
|
||||
onOperationProgressed,
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
ValidateConversionResults(conversionResults);
|
||||
|
||||
var groupedNodes = SkipNodeMerging ? [] : GroupSiblingGeometryNodes(navisworksModelItems);
|
||||
var finalElements = BuildFinalElements(convertedElements, groupedNodes);
|
||||
List<Base> geometryDefinitions = instanceStoreManager.GetGeometryDefinitions();
|
||||
|
||||
await AddProxiesToCollection(rootCollection, navisworksModelItems, groupedNodes);
|
||||
|
||||
// rootCollection.elements will contain two Collections: one for geometry definitions and one for the main elements
|
||||
|
||||
var geometryDefinitionsCollection = new Collection
|
||||
{
|
||||
name = "Geometry Definitions",
|
||||
["units"] = converterSettings.Current.Derived.SpeckleUnits,
|
||||
elements = geometryDefinitions
|
||||
};
|
||||
|
||||
var mainElementsCollection = new Collection
|
||||
{
|
||||
name = rootCollection.name,
|
||||
["units"] = converterSettings.Current.Derived.SpeckleUnits,
|
||||
elements = finalElements
|
||||
};
|
||||
|
||||
rootCollection.elements = [mainElementsCollection];
|
||||
if (geometryDefinitions.Count > 0)
|
||||
{
|
||||
rootCollection.elements.Add(geometryDefinitionsCollection);
|
||||
}
|
||||
|
||||
rootCollection.elements = finalElements;
|
||||
return new RootObjectBuilderResult(rootCollection, conversionResults);
|
||||
}
|
||||
|
||||
@@ -279,12 +259,14 @@ public class NavisworksRootObjectBuilder(
|
||||
|
||||
(string name, string path) = GetContext(convertedBase.applicationId);
|
||||
|
||||
var units = uiUnitsCache.Ensure();
|
||||
|
||||
return new NavisworksObject
|
||||
{
|
||||
name = name,
|
||||
displayValue = convertedBase["displayValue"] as List<Base> ?? [],
|
||||
properties = convertedBase["properties"] as Dictionary<string, object?> ?? [],
|
||||
units = converterSettings.Current.Derived.SpeckleUnits,
|
||||
units = units.ToString(),
|
||||
applicationId = convertedBase.applicationId,
|
||||
["path"] = path
|
||||
};
|
||||
@@ -310,24 +292,6 @@ public class NavisworksRootObjectBuilder(
|
||||
rootCollection[ProxyKeys.COLOR] = colors;
|
||||
}
|
||||
|
||||
// Add instance definition proxies from dual store
|
||||
var instanceDefinitionProxies = instanceStoreManager.GetInstanceDefinitionProxies();
|
||||
logger.LogDebug("Retrieved {Count} instance definition proxies from store", instanceDefinitionProxies.Count);
|
||||
|
||||
if (instanceDefinitionProxies.Count > 0)
|
||||
{
|
||||
rootCollection[ProxyKeys.INSTANCE_DEFINITION] = instanceDefinitionProxies.ToList();
|
||||
logger.LogDebug(
|
||||
"Added {Count} instance definition proxies to root collection under key '{Key}'",
|
||||
instanceDefinitionProxies.Count,
|
||||
ProxyKeys.INSTANCE_DEFINITION
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("No instance definition proxies to add to root collection");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
@@ -281,6 +281,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -281,6 +281,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -281,6 +281,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -226,6 +226,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -219,6 +219,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -94,7 +94,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
|
||||
|
||||
public List<ISendFilter> GetSendFilters() =>
|
||||
[
|
||||
new RevitSelectionFilter() { IsDefault = true },
|
||||
new RevitSelectionFilter { IsDefault = true },
|
||||
new RevitViewsFilter(_revitContext),
|
||||
new RevitCategoriesFilter(_revitContext)
|
||||
];
|
||||
|
||||
-1
@@ -74,7 +74,6 @@ public static class ServiceRegistration
|
||||
serviceCollection.AddSingleton<RevitUtils>();
|
||||
serviceCollection.AddSingleton<IFailuresPreprocessor, HideWarningsFailuresPreprocessor>();
|
||||
serviceCollection.AddSingleton(DefaultTraversal.CreateTraversalFunc());
|
||||
|
||||
serviceCollection.AddScoped<LocalToGlobalConverterUtils>();
|
||||
|
||||
// operation progress manager
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Autodesk.Revit.DB;
|
||||
using Autodesk.Revit.DB.Architecture;
|
||||
using Speckle.Converters.RevitShared.Extensions;
|
||||
|
||||
namespace Speckle.Connectors.Revit.HostApp;
|
||||
|
||||
@@ -23,11 +24,11 @@ public class ElementUnpacker
|
||||
// Step 1: unpack groups
|
||||
var atomicObjects = UnpackElements(selectionElements, doc);
|
||||
|
||||
// Step 2: pack curtain wall elements, once we know the full extent of our flattened item list.
|
||||
// The behaviour we're looking for:
|
||||
// If parent wall is part of selection, does not select individual elements out. Otherwise, selects individual elements (Panels, Mullions) as atomic objects.
|
||||
// NOTE: this also conditionally "packs" stacked wall elements if their parent is present. See detailed note inside the function.
|
||||
return PackCurtainWallElementsAndStackedWalls(atomicObjects, doc);
|
||||
// Step 2: Deduplicate parent-child elements in selection
|
||||
// Removes child elements (mullions, panels, top rails, stacked wall members) when
|
||||
// their parent element is also selected, since parents include children in their conversion.
|
||||
// Children are only converted independently when their parent is NOT in the selection.
|
||||
return RemoveKnownChildElementsWhenParentPresent(atomicObjects, doc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -108,7 +109,7 @@ public class ElementUnpacker
|
||||
// We use the nullable document (happiness level 5/10) for the sake of linked models - bc we use this function in 2 different places
|
||||
// 1- RootObjectBuilder with linked model document - otherwise we cannot unpack elements from correct document.
|
||||
// 2- Evicting the cache while introducing the settings
|
||||
private List<Element> PackCurtainWallElementsAndStackedWalls(List<Element> elements, Document doc)
|
||||
private List<Element> RemoveKnownChildElementsWhenParentPresent(List<Element> elements, Document doc)
|
||||
{
|
||||
//just used for contains so use ToHashSet
|
||||
var ids = elements.Select(el => el.Id).ToHashSet();
|
||||
@@ -131,64 +132,37 @@ public class ElementUnpacker
|
||||
// If you wonder why revit is driving people to insanity, this is one of those moments.
|
||||
// See [CNX-851: Stacked Wall Duplicate Geometry or Materials not applied](https://linear.app/speckle/issue/CNX-851/stacked-wall-duplicate-geometry-or-materials-not-applied)
|
||||
|| (element is Wall { IsStackedWallMember: true } wall && ids.Contains(wall.StackedWallOwnerId))
|
||||
// Railings: Remove TopRail when parent railing is selected
|
||||
// Prevents duplication since railing converter includes TopRail as a child element
|
||||
// TODO: Consider adding HandRail support (also inherits from ContinuousRail)
|
||||
|| (
|
||||
element is TopRail topRail
|
||||
&& doc.GetElement(topRail.HostRailingId) is Railing railing
|
||||
&& ids.Contains(railing.Id)
|
||||
)
|
||||
);
|
||||
return elements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a set of atomic elements, it will return a list of all their ids as well as their subelements. This currently handles <b>curtain walls</b> and <b>stacked walls</b>.
|
||||
/// This might not be an exhaustive list of valid objects with "subelements" in revit, and will need revisiting.
|
||||
/// Returns element IDs and their known child element IDs for cache tracking.
|
||||
/// Uses <see cref="ElementExtensions.GetKnownChildrenElements"/> to determine which children to include.
|
||||
/// </summary>
|
||||
/// <param name="elements"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="elements">Elements to process</param>
|
||||
/// <returns>Flattened list of parent and child element IDs</returns>
|
||||
public List<string> GetElementsAndSubelementIdsFromAtomicObjects(List<Element> elements)
|
||||
{
|
||||
var ids = new HashSet<string>();
|
||||
foreach (var element in elements)
|
||||
{
|
||||
switch (element)
|
||||
{
|
||||
case Wall wall:
|
||||
if (wall.CurtainGrid is { } grid)
|
||||
{
|
||||
foreach (var mullionId in grid.GetMullionIds())
|
||||
{
|
||||
ids.Add(mullionId.ToString());
|
||||
}
|
||||
foreach (var panelId in grid.GetPanelIds())
|
||||
{
|
||||
ids.Add(panelId.ToString());
|
||||
}
|
||||
}
|
||||
else if (wall.IsStackedWall)
|
||||
{
|
||||
foreach (var stackedWallId in wall.GetStackedWallMemberIds())
|
||||
{
|
||||
ids.Add(stackedWallId.ToString());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FootPrintRoof footPrintRoof:
|
||||
if (footPrintRoof.CurtainGrids is { } gs)
|
||||
{
|
||||
foreach (CurtainGrid roofGrid in gs)
|
||||
{
|
||||
foreach (var mullionId in roofGrid.GetMullionIds())
|
||||
{
|
||||
ids.Add(mullionId.ToString());
|
||||
}
|
||||
foreach (var panelId in roofGrid.GetPanelIds())
|
||||
{
|
||||
ids.Add(panelId.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// add the element's own ID
|
||||
ids.Add(element.Id.ToString());
|
||||
|
||||
// add all known children IDs using the extension method. trying to consolidate duplication here with converter
|
||||
foreach (var childId in element.GetKnownChildrenElements())
|
||||
{
|
||||
ids.Add(childId.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return ids.ToList();
|
||||
|
||||
@@ -325,6 +325,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -325,6 +325,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
+79
-63
@@ -447,73 +447,89 @@ public sealed class ReceiveComponentWorker : WorkerInstance<ReceiveAsyncComponen
|
||||
}
|
||||
|
||||
using var scope = PriorityLoader.CreateScopeForActiveDocument();
|
||||
Root = await scope
|
||||
.Get<GrasshopperReceiveOperation>()
|
||||
.ReceiveCommitObject(receiveInfo, progress, CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
CancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
SpecklePropertyGroupGoo? rootPropertiesGoo = null;
|
||||
if (Root is RootCollection rootCollection && rootCollection.properties.Count > 0)
|
||||
try
|
||||
{
|
||||
rootPropertiesGoo = new SpecklePropertyGroupGoo(rootCollection.properties);
|
||||
Root = await scope
|
||||
.Get<GrasshopperReceiveOperation>()
|
||||
.ReceiveCommitObject(receiveInfo, progress, CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
CancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
SpecklePropertyGroupGoo? rootPropertiesGoo = null;
|
||||
if (Root is RootCollection rootCollection && rootCollection.properties.Count > 0)
|
||||
{
|
||||
rootPropertiesGoo = new SpecklePropertyGroupGoo(rootCollection.properties);
|
||||
}
|
||||
|
||||
// Step 2 - CONVERT
|
||||
//receiveComponent.Message = $"Unpacking...";
|
||||
SpeckleConversionContext.SetupCurrent(scope);
|
||||
|
||||
var unpackedRoot = scope.Get<RootObjectUnpacker>().Unpack(Root);
|
||||
|
||||
// separate atomic objects from block instances
|
||||
var (atomicObjects, blockInstances) = scope
|
||||
.Get<RootObjectUnpacker>()
|
||||
.SplitAtomicObjectsAndInstances(unpackedRoot.ObjectsToConvert);
|
||||
|
||||
// initialize unpackers and collection builder (data holders - created with new)
|
||||
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
|
||||
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
|
||||
var collectionRebuilder = new GrasshopperCollectionRebuilder(
|
||||
(Root as Collection) ?? new Collection { name = "unnamed" }
|
||||
);
|
||||
|
||||
// get handler from DI and initialize with per-operation data
|
||||
var mapHandler = scope
|
||||
.Get<LocalToGlobalMapHandler>()
|
||||
.Initialize(
|
||||
scope.Get<TraversalContextUnpacker>(),
|
||||
colorUnpacker,
|
||||
materialUnpacker,
|
||||
collectionRebuilder,
|
||||
unpackedRoot.DefinitionProxies
|
||||
);
|
||||
|
||||
// handler deals with two-pass conversion: normal objects first, then DataObjects with InstanceProxies
|
||||
mapHandler.ConvertAtomicObjects(atomicObjects);
|
||||
|
||||
// process block instances using converted atomic objects
|
||||
// internally filters out InstanceProxies that belong to registered DataObjects
|
||||
// block processing needs converted objects, but object filtering needs block definitions.
|
||||
mapHandler.ConvertBlockInstances(blockInstances);
|
||||
|
||||
Result = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
|
||||
RootProperties = rootPropertiesGoo;
|
||||
|
||||
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
|
||||
var customProperties = new Dictionary<string, object>()
|
||||
{
|
||||
{ "isAsync", true },
|
||||
{ "sourceHostApp", HostApplications.GetSlugFromHostAppNameAndVersion(receiveInfo.SourceApplication) },
|
||||
{ "auto", Parent.AutoReceive }
|
||||
};
|
||||
if (receiveInfo.WorkspaceId != null)
|
||||
{
|
||||
customProperties.Add("workspace_id", receiveInfo.WorkspaceId);
|
||||
}
|
||||
|
||||
if (receiveInfo.SelectedVersionUserId != null)
|
||||
{
|
||||
customProperties.Add(
|
||||
"isMultiplayer",
|
||||
receiveInfo.SelectedVersionUserId != Parent.ApiClient.Account.userInfo.id
|
||||
);
|
||||
}
|
||||
|
||||
await scope
|
||||
.Get<IMixPanelManager>()
|
||||
.TrackEvent(MixPanelEvents.Receive, Parent.ApiClient.Account, customProperties);
|
||||
}
|
||||
|
||||
// Step 2 - CONVERT
|
||||
//receiveComponent.Message = $"Unpacking...";
|
||||
TraversalContextUnpacker traversalContextUnpacker = new();
|
||||
var unpackedRoot = scope.Get<RootObjectUnpacker>().Unpack(Root);
|
||||
|
||||
// separate atomic objects from block instances
|
||||
var (atomicObjects, blockInstances) = scope
|
||||
.Get<RootObjectUnpacker>()
|
||||
.SplitAtomicObjectsAndInstances(unpackedRoot.ObjectsToConvert);
|
||||
|
||||
// initialize unpackers and collection builder
|
||||
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
|
||||
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
|
||||
var collectionRebuilder = new GrasshopperCollectionRebuilder(
|
||||
(Root as Collection) ?? new Collection { name = "unnamed" }
|
||||
);
|
||||
|
||||
// convert atomic objects directly
|
||||
var mapHandler = new LocalToGlobalMapHandler(
|
||||
traversalContextUnpacker,
|
||||
collectionRebuilder,
|
||||
colorUnpacker,
|
||||
materialUnpacker
|
||||
);
|
||||
|
||||
foreach (var atomicContext in atomicObjects)
|
||||
finally
|
||||
{
|
||||
mapHandler.ConvertAtomicObject(atomicContext);
|
||||
SpeckleConversionContext.EndCurrent();
|
||||
}
|
||||
|
||||
// process block instances using converted atomic objects
|
||||
// block processing needs converted objects, but object filtering needs block definitions.
|
||||
mapHandler.ConvertBlockInstances(blockInstances, unpackedRoot.DefinitionProxies);
|
||||
|
||||
Result = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
|
||||
RootProperties = rootPropertiesGoo;
|
||||
|
||||
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
|
||||
var customProperties = new Dictionary<string, object>()
|
||||
{
|
||||
{ "isAsync", true },
|
||||
{ "sourceHostApp", HostApplications.GetSlugFromHostAppNameAndVersion(receiveInfo.SourceApplication) },
|
||||
{ "auto", Parent.AutoReceive }
|
||||
};
|
||||
if (receiveInfo.WorkspaceId != null)
|
||||
{
|
||||
customProperties.Add("workspace_id", receiveInfo.WorkspaceId);
|
||||
}
|
||||
|
||||
if (receiveInfo.SelectedVersionUserId != null)
|
||||
{
|
||||
customProperties.Add("isMultiplayer", receiveInfo.SelectedVersionUserId != Parent.ApiClient.Account.userInfo.id);
|
||||
}
|
||||
await scope.Get<IMixPanelManager>().TrackEvent(MixPanelEvents.Receive, Parent.ApiClient.Account, customProperties);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+91
-84
@@ -143,95 +143,102 @@ public class ReceiveComponent : SpeckleTaskCapableComponent<ReceiveComponentInpu
|
||||
}
|
||||
|
||||
using var scope = PriorityLoader.CreateScopeForActiveDocument();
|
||||
var clientFactory = scope.ServiceProvider.GetRequiredService<IClientFactory>();
|
||||
var receiveOperation = scope.ServiceProvider.GetRequiredService<GrasshopperReceiveOperation>();
|
||||
|
||||
// Do the thing 👇🏼
|
||||
|
||||
Account? account = input.Resource.Account.GetAccount(scope);
|
||||
if (account is null)
|
||||
try
|
||||
{
|
||||
throw new SpeckleAccountManagerException("No default account was found");
|
||||
var clientFactory = scope.ServiceProvider.GetRequiredService<IClientFactory>();
|
||||
var receiveOperation = scope.ServiceProvider.GetRequiredService<GrasshopperReceiveOperation>();
|
||||
|
||||
// Do the thing 👇🏼
|
||||
|
||||
Account? account = input.Resource.Account.GetAccount(scope);
|
||||
if (account is null)
|
||||
{
|
||||
throw new SpeckleAccountManagerException("No default account was found");
|
||||
}
|
||||
|
||||
using var client = clientFactory.Create(account);
|
||||
var receiveInfo = await input.Resource.GetReceiveInfo(client, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// store version id for tracking
|
||||
_lastVersionId = receiveInfo.SelectedVersionId;
|
||||
|
||||
var progress = new Progress<CardProgress>(_ =>
|
||||
{
|
||||
// TODO: Progress only makes sense in non-blocking async receive, which is not supported yet.
|
||||
// Message = $"{progress.Status}: {progress.Progress}";
|
||||
});
|
||||
|
||||
var root = await receiveOperation
|
||||
.ReceiveCommitObject(receiveInfo, progress, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// extract model-wide root properties (see cnx-2722)
|
||||
SpecklePropertyGroupGoo? rootPropertiesGoo = null;
|
||||
if (root is RootCollection rootCollection && rootCollection.properties.Count > 0)
|
||||
{
|
||||
rootPropertiesGoo = new SpecklePropertyGroupGoo(rootCollection.properties);
|
||||
}
|
||||
|
||||
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
|
||||
var customProperties = new Dictionary<string, object>
|
||||
{
|
||||
{ "isAsync", false },
|
||||
{ "sourceHostApp", HostApplications.GetSlugFromHostAppNameAndVersion(receiveInfo.SourceApplication) }
|
||||
};
|
||||
if (receiveInfo.WorkspaceId != null)
|
||||
{
|
||||
customProperties.Add("workspace_id", receiveInfo.WorkspaceId);
|
||||
}
|
||||
if (receiveInfo.SelectedVersionUserId != null)
|
||||
{
|
||||
customProperties.Add("isMultiplayer", receiveInfo.SelectedVersionUserId != client.Account.userInfo.id);
|
||||
}
|
||||
var mixpanel = PriorityLoader.Container.GetRequiredService<IMixPanelManager>();
|
||||
await mixpanel.TrackEvent(MixPanelEvents.Receive, account, customProperties);
|
||||
|
||||
// Setup conversion context BEFORE unpacking (which triggers DataObjectConverter)
|
||||
SpeckleConversionContext.SetupCurrent(scope);
|
||||
|
||||
var rootObjectUnpacker = scope.ServiceProvider.GetService<RootObjectUnpacker>();
|
||||
var unpackedRoot = rootObjectUnpacker.Unpack(root);
|
||||
|
||||
// split atomic objects from block components before conversion
|
||||
var (atomicObjects, blockInstances) = rootObjectUnpacker.SplitAtomicObjectsAndInstances(
|
||||
unpackedRoot.ObjectsToConvert
|
||||
);
|
||||
|
||||
// Initialize unpackers and collection builder (data holders - created with new)
|
||||
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
|
||||
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
|
||||
var collectionRebuilder = new GrasshopperCollectionRebuilder(
|
||||
(root as Collection) ?? new Collection { name = "unnamed" }
|
||||
);
|
||||
|
||||
// get handler from DI and initialize with per-operation data
|
||||
var mapHandler = scope
|
||||
.ServiceProvider.GetRequiredService<LocalToGlobalMapHandler>()
|
||||
.Initialize(
|
||||
scope.ServiceProvider.GetRequiredService<TraversalContextUnpacker>(),
|
||||
colorUnpacker,
|
||||
materialUnpacker,
|
||||
collectionRebuilder,
|
||||
unpackedRoot.DefinitionProxies
|
||||
);
|
||||
|
||||
// two-pass conversion: normal objects first, then DataObjects with InstanceProxies
|
||||
mapHandler.ConvertAtomicObjects(atomicObjects);
|
||||
|
||||
// process block instances (internally filters InstanceProxies belonging to registered DataObjects)
|
||||
mapHandler.ConvertBlockInstances(blockInstances);
|
||||
|
||||
var goo = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
|
||||
return new ReceiveComponentOutput { RootObject = goo, RootProperties = rootPropertiesGoo };
|
||||
}
|
||||
|
||||
using var client = clientFactory.Create(account);
|
||||
var receiveInfo = await input.Resource.GetReceiveInfo(client, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// store version id for tracking
|
||||
_lastVersionId = receiveInfo.SelectedVersionId;
|
||||
|
||||
var progress = new Progress<CardProgress>(_ =>
|
||||
finally
|
||||
{
|
||||
// TODO: Progress only makes sense in non-blocking async receive, which is not supported yet.
|
||||
// Message = $"{progress.Status}: {progress.Progress}";
|
||||
});
|
||||
|
||||
var root = await receiveOperation
|
||||
.ReceiveCommitObject(receiveInfo, progress, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// extract model-wide root properties (see cnx-2722)
|
||||
SpecklePropertyGroupGoo? rootPropertiesGoo = null;
|
||||
if (root is RootCollection rootCollection && rootCollection.properties.Count > 0)
|
||||
{
|
||||
rootPropertiesGoo = new SpecklePropertyGroupGoo(rootCollection.properties);
|
||||
SpeckleConversionContext.EndCurrent();
|
||||
}
|
||||
|
||||
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
|
||||
var customProperties = new Dictionary<string, object>
|
||||
{
|
||||
{ "isAsync", false },
|
||||
{ "sourceHostApp", HostApplications.GetSlugFromHostAppNameAndVersion(receiveInfo.SourceApplication) }
|
||||
};
|
||||
if (receiveInfo.WorkspaceId != null)
|
||||
{
|
||||
customProperties.Add("workspace_id", receiveInfo.WorkspaceId);
|
||||
}
|
||||
if (receiveInfo.SelectedVersionUserId != null)
|
||||
{
|
||||
customProperties.Add("isMultiplayer", receiveInfo.SelectedVersionUserId != client.Account.userInfo.id);
|
||||
}
|
||||
var mixpanel = PriorityLoader.Container.GetRequiredService<IMixPanelManager>();
|
||||
await mixpanel.TrackEvent(MixPanelEvents.Receive, account, customProperties);
|
||||
|
||||
// We need to rethink these lovely unpackers, there's a bit too many of 'em
|
||||
var rootObjectUnpacker = scope.ServiceProvider.GetService<RootObjectUnpacker>();
|
||||
var traversalContextUnpacker = new TraversalContextUnpacker();
|
||||
|
||||
var unpackedRoot = rootObjectUnpacker.Unpack(root);
|
||||
|
||||
// split atomic objects from block components before conversion
|
||||
var (atomicObjects, blockInstances) = rootObjectUnpacker.SplitAtomicObjectsAndInstances(
|
||||
unpackedRoot.ObjectsToConvert
|
||||
);
|
||||
|
||||
// Initialize unpackers and collection builder
|
||||
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
|
||||
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
|
||||
var collectionRebuilder = new GrasshopperCollectionRebuilder(
|
||||
(root as Collection) ?? new Collection { name = "unnamed" }
|
||||
);
|
||||
|
||||
// convert atomic objects directly
|
||||
var mapHandler = new LocalToGlobalMapHandler(
|
||||
traversalContextUnpacker,
|
||||
collectionRebuilder,
|
||||
colorUnpacker,
|
||||
materialUnpacker
|
||||
);
|
||||
|
||||
foreach (var atomicContext in atomicObjects)
|
||||
{
|
||||
mapHandler.ConvertAtomicObject(atomicContext);
|
||||
}
|
||||
|
||||
// process block instances using converted atomic objects
|
||||
// block processing needs converted objects, but object filtering needs block definitions.
|
||||
mapHandler.ConvertBlockInstances(blockInstances, unpackedRoot.DefinitionProxies);
|
||||
|
||||
// var x = new SpeckleCollectionGoo { Value = collGen.RootCollection };
|
||||
var goo = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
|
||||
return new ReceiveComponentOutput { RootObject = goo, RootProperties = rootPropertiesGoo };
|
||||
}
|
||||
|
||||
private void SetupSubscription(SpeckleUrlModelResource resource)
|
||||
|
||||
+3
-2
@@ -29,13 +29,13 @@ public class SpeckleConversionContext(IRootToSpeckleConverter speckleConverter,
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetupCurrent()
|
||||
public static void SetupCurrent(IServiceScope? scope = null)
|
||||
{
|
||||
if (s_currentContext != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
s_scope = PriorityLoader.CreateScopeForActiveDocument();
|
||||
s_scope = scope ?? PriorityLoader.CreateScopeForActiveDocument();
|
||||
s_currentContext = s_scope.Get<SpeckleConversionContext>();
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ public class SpeckleConversionContext(IRootToSpeckleConverter speckleConverter,
|
||||
{
|
||||
GeometryBase geometry => [(geometry, input)],
|
||||
List<GeometryBase> geometryList => geometryList.Select(o => ((object)o, input)).ToList(),
|
||||
List<(GeometryBase, Base)> pairList when pairList.Count == 0 => [],
|
||||
IEnumerable<(GeometryBase, Base)> fallbackConversionResult
|
||||
=> fallbackConversionResult.Select(o => ((object)o.Item1, o.Item2)).ToList(),
|
||||
object obj => [(obj, input)],
|
||||
|
||||
+328
-118
@@ -1,12 +1,18 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Rhino.Geometry;
|
||||
using Speckle.Connectors.Common.Operations.Receive;
|
||||
using Speckle.Connectors.GrasshopperShared.HostApp;
|
||||
using Speckle.Connectors.GrasshopperShared.Operations.Receive;
|
||||
using Speckle.Connectors.GrasshopperShared.Parameters;
|
||||
using Speckle.Converters.Common.ToHost;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Collections;
|
||||
using Speckle.Sdk.Models.GraphTraversal;
|
||||
using Speckle.Sdk.Models.Instances;
|
||||
using DataObject = Speckle.Objects.Data.DataObject;
|
||||
|
||||
namespace Speckle.Connectors.GrasshopperShared.Operations.Receive;
|
||||
|
||||
/// <summary>
|
||||
/// Handles conversion of atomic objects from TraversalContexts into Grasshopper wrapper objects.
|
||||
@@ -19,184 +25,388 @@ using Speckle.Sdk.Models.Instances;
|
||||
internal sealed class LocalToGlobalMapHandler
|
||||
{
|
||||
public Dictionary<string, SpeckleGeometryWrapper> ConvertedObjectsMap { get; } = new();
|
||||
public readonly GrasshopperCollectionRebuilder CollectionRebuilder;
|
||||
|
||||
private readonly TraversalContextUnpacker _traversalContextUnpacker;
|
||||
private readonly GrasshopperColorUnpacker _colorUnpacker;
|
||||
private readonly GrasshopperMaterialUnpacker _materialUnpacker;
|
||||
// injected via constructor (DI-managed)
|
||||
private readonly IDataObjectInstanceRegistry _dataObjectInstanceRegistry;
|
||||
private readonly ILogger<LocalToGlobalMapHandler> _logger;
|
||||
|
||||
// set via Initialize() method (per-operation data)
|
||||
private TraversalContextUnpacker _traversalContextUnpacker = null!;
|
||||
private GrasshopperColorUnpacker _colorUnpacker = null!;
|
||||
private GrasshopperMaterialUnpacker _materialUnpacker = null!;
|
||||
private IReadOnlyCollection<InstanceDefinitionProxy>? _definitionProxies;
|
||||
|
||||
// auto property (fixes IDE0032)
|
||||
public GrasshopperCollectionRebuilder CollectionRebuilder { get; private set; } = null!;
|
||||
|
||||
public LocalToGlobalMapHandler(
|
||||
IDataObjectInstanceRegistry dataObjectInstanceRegistry,
|
||||
ILogger<LocalToGlobalMapHandler> logger
|
||||
)
|
||||
{
|
||||
_dataObjectInstanceRegistry = dataObjectInstanceRegistry;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the handler with per-operation data.
|
||||
/// Must be called before using ConvertAtomicObjects or ConvertBlockInstances.
|
||||
/// </summary>
|
||||
public LocalToGlobalMapHandler Initialize(
|
||||
TraversalContextUnpacker traversalContextUnpacker,
|
||||
GrasshopperCollectionRebuilder collectionRebuilder,
|
||||
GrasshopperColorUnpacker colorUnpacker,
|
||||
GrasshopperMaterialUnpacker materialUnpacker
|
||||
GrasshopperMaterialUnpacker materialUnpacker,
|
||||
GrasshopperCollectionRebuilder collectionRebuilder,
|
||||
IReadOnlyCollection<InstanceDefinitionProxy>? definitionProxies
|
||||
)
|
||||
{
|
||||
_traversalContextUnpacker = traversalContextUnpacker;
|
||||
_colorUnpacker = colorUnpacker;
|
||||
_materialUnpacker = materialUnpacker;
|
||||
CollectionRebuilder = collectionRebuilder;
|
||||
_definitionProxies = definitionProxies;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts atomic object from TraversalContext to SpeckleObjectWrapper.
|
||||
/// Converts all atomic objects in two passes:
|
||||
/// Pass 1 - Convert normal objects and populate ConvertedObjectsMap
|
||||
/// Pass 2 - Resolve registered DataObjects with InstanceProxies using the populated map
|
||||
/// </summary>
|
||||
public void ConvertAtomicObject(TraversalContext atomicContext)
|
||||
public void ConvertAtomicObjects(IEnumerable<TraversalContext> atomicContexts)
|
||||
{
|
||||
// Cache to avoid re-iterating for registered check
|
||||
var atomicList = atomicContexts as IList<TraversalContext> ?? atomicContexts.ToList();
|
||||
|
||||
// Pass 1: Convert all non-registered DataObjects to populate ConvertedObjectsMap
|
||||
foreach (var atomicContext in atomicList)
|
||||
{
|
||||
ConvertObjectToCache(atomicContext);
|
||||
}
|
||||
|
||||
// Pass 2: Process registered DataObjects (definitions now available in ConvertedObjectsMap)
|
||||
foreach (var atomicContext in atomicList)
|
||||
{
|
||||
if (atomicContext.Current is DataObject dataObject)
|
||||
{
|
||||
var dataObjectId = dataObject.applicationId ?? dataObject.id;
|
||||
if (dataObjectId is not null && _dataObjectInstanceRegistry.IsRegistered(dataObjectId))
|
||||
{
|
||||
ResolveDataObjectInstanceProxies(atomicContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts and caches an atomic object for later lookup.
|
||||
/// Skips registered DataObjects (displayValue is InstanceProxy) - they are resolved in ResolveDataObjectInstanceProxies.
|
||||
/// </summary>
|
||||
private void ConvertObjectToCache(TraversalContext atomicContext)
|
||||
{
|
||||
var obj = atomicContext.Current;
|
||||
var objId = obj.applicationId ?? obj.id;
|
||||
|
||||
if (objId == null || ConvertedObjectsMap.ContainsKey(objId))
|
||||
if (objId is null || ConvertedObjectsMap.ContainsKey(objId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// skip registered DataObjects - they'll be processed in second pass
|
||||
if (obj is DataObject dataObject)
|
||||
{
|
||||
var id = dataObject.applicationId ?? dataObject.id.NotNull();
|
||||
if (_dataObjectInstanceRegistry.IsRegistered(id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
List<(object, Base)> converted = SpeckleConversionContext.Current.ConvertToHost(obj);
|
||||
|
||||
if (converted.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// get path and collection
|
||||
var path = _traversalContextUnpacker.GetCollectionPath(atomicContext).ToList();
|
||||
|
||||
// Always create collection - consumed objects will be cleaned up later
|
||||
var objectCollection = CollectionRebuilder.GetOrCreateSpeckleCollectionFromPath(
|
||||
path,
|
||||
_colorUnpacker,
|
||||
_materialUnpacker
|
||||
);
|
||||
|
||||
if (obj is Speckle.Objects.Data.DataObject dataObject)
|
||||
// nothing converted - nothing to do
|
||||
if (converted.Count == 0)
|
||||
{
|
||||
// get color and mat on dataobject first
|
||||
Color? dataObjColor = _colorUnpacker.Cache.TryGetValue(
|
||||
dataObject.applicationId ?? "",
|
||||
out var cachedDataObjColor
|
||||
)
|
||||
? cachedDataObjColor
|
||||
: null;
|
||||
|
||||
SpeckleMaterialWrapper? dataObjMat = _materialUnpacker.Cache.TryGetValue(
|
||||
dataObject.applicationId ?? "",
|
||||
out var cachedDataObjMaterial
|
||||
)
|
||||
? cachedDataObjMaterial
|
||||
: null;
|
||||
|
||||
// get geometries
|
||||
List<SpeckleGeometryWrapper> geometries = new();
|
||||
foreach ((object convertedObj, Base original) in converted)
|
||||
{
|
||||
if (convertedObj is GeometryBase geometryBase)
|
||||
{
|
||||
SpeckleGeometryWrapper wrapper =
|
||||
new()
|
||||
{
|
||||
Base = original,
|
||||
GeometryBase = geometryBase,
|
||||
Color = _colorUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjColor)
|
||||
? cachedObjColor
|
||||
: dataObjColor,
|
||||
Material = _materialUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjMaterial)
|
||||
? cachedObjMaterial
|
||||
: dataObjMat,
|
||||
};
|
||||
|
||||
geometries.Add(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
SpecklePropertyGroupGoo propertyGroup = new();
|
||||
propertyGroup.CastFrom(dataObject.properties);
|
||||
|
||||
// remove the displayvalue of the original dataobject since these are now processed and stored on the wrapper
|
||||
// to prevent storing of duplicate Base
|
||||
dataObject.displayValue.Clear();
|
||||
|
||||
var dataObjectWrapper = new SpeckleDataObjectWrapper()
|
||||
{
|
||||
Base = dataObject,
|
||||
Geometries = geometries,
|
||||
Path = path.Select(p => p.name).ToList(),
|
||||
Parent = objectCollection,
|
||||
Name = dataObject.name,
|
||||
Properties = propertyGroup,
|
||||
ApplicationId = dataObject.applicationId,
|
||||
};
|
||||
|
||||
// Add to collections (not to map since these won't be definition objects)
|
||||
CollectionRebuilder.AppendSpeckleGrasshopperObject(dataObjectWrapper, path, _colorUnpacker, _materialUnpacker);
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
// handle normal DataObject (has converted geometry)
|
||||
if (obj is DataObject normalDataObject)
|
||||
{
|
||||
SpecklePropertyGroupGoo propertyGroup = new();
|
||||
if (obj[Constants.PROPERTIES_PROP] is Dictionary<string, object?> props)
|
||||
{
|
||||
propertyGroup.CastFrom(props);
|
||||
}
|
||||
var geometries = ConvertToGeometryWrappers(converted);
|
||||
var dataObjectWrapper = CreateDataObjectWrapper(normalDataObject, geometries, path, objectCollection);
|
||||
|
||||
foreach ((object convertedObj, Base original) in converted)
|
||||
CollectionRebuilder.AppendSpeckleGrasshopperObject(dataObjectWrapper, path, _colorUnpacker, _materialUnpacker);
|
||||
return;
|
||||
}
|
||||
|
||||
// handle normal geometry (not DataObject)
|
||||
SpecklePropertyGroupGoo propertyGroup = new();
|
||||
if (obj[Constants.PROPERTIES_PROP] is Dictionary<string, object?> props)
|
||||
{
|
||||
propertyGroup.CastFrom(props);
|
||||
}
|
||||
|
||||
foreach ((object convertedObj, Base original) in converted)
|
||||
{
|
||||
if (convertedObj is GeometryBase geometryBase)
|
||||
{
|
||||
if (convertedObj is GeometryBase geometryBase)
|
||||
var wrapper = new SpeckleGeometryWrapper()
|
||||
{
|
||||
var wrapper = new SpeckleGeometryWrapper()
|
||||
{
|
||||
Base = original,
|
||||
Path = path.Select(p => p.name).ToList(),
|
||||
Parent = objectCollection,
|
||||
GeometryBase = geometryBase,
|
||||
Properties = propertyGroup,
|
||||
Name = obj[Constants.NAME_PROP] as string ?? "",
|
||||
Color = _colorUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjColor)
|
||||
? cachedObjColor
|
||||
: null,
|
||||
Material = _materialUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjMaterial)
|
||||
? cachedObjMaterial
|
||||
: null,
|
||||
ApplicationId = objId
|
||||
};
|
||||
Base = original,
|
||||
Path = path.Select(p => p.name).ToList(),
|
||||
Parent = objectCollection,
|
||||
GeometryBase = geometryBase,
|
||||
Properties = propertyGroup,
|
||||
Name = obj[Constants.NAME_PROP] as string ?? "",
|
||||
Color = _colorUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjColor)
|
||||
? cachedObjColor
|
||||
: null,
|
||||
Material = _materialUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjMaterial)
|
||||
? cachedObjMaterial
|
||||
: null,
|
||||
ApplicationId = objId
|
||||
};
|
||||
|
||||
// Always add to both map and collections
|
||||
ConvertedObjectsMap[objId] = wrapper;
|
||||
CollectionRebuilder.AppendSpeckleGrasshopperObject(wrapper, path, _colorUnpacker, _materialUnpacker);
|
||||
}
|
||||
ConvertedObjectsMap[objId] = wrapper;
|
||||
CollectionRebuilder.AppendSpeckleGrasshopperObject(wrapper, path, _colorUnpacker, _materialUnpacker);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal())
|
||||
{
|
||||
// TODO: throw?
|
||||
// don't throw - continue processing other objects
|
||||
_logger.LogError(ex, "Failed to convert object {objectId} of type {objectType}", objId, obj.speckle_type);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a registered DataObject by transforming its InstanceProxy definition objects.
|
||||
/// Requires definition objects to exist in ConvertedObjectsMap (populated by ConvertObjectToCache).
|
||||
/// </summary>
|
||||
private void ResolveDataObjectInstanceProxies(TraversalContext atomicContext)
|
||||
{
|
||||
var obj = atomicContext.Current;
|
||||
if (obj is not DataObject dataObject)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dataObjectId = dataObject.applicationId ?? dataObject.id.NotNull();
|
||||
if (!_dataObjectInstanceRegistry.IsRegistered(dataObjectId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var path = _traversalContextUnpacker.GetCollectionPath(atomicContext).ToList();
|
||||
var objectCollection = CollectionRebuilder.GetOrCreateSpeckleCollectionFromPath(
|
||||
path,
|
||||
_colorUnpacker,
|
||||
_materialUnpacker
|
||||
);
|
||||
|
||||
var entry = _dataObjectInstanceRegistry.GetEntries()[dataObjectId];
|
||||
var resolvedGeometries = ResolveInstanceProxiesToGeometries(entry.InstanceProxies);
|
||||
var dataObjectWrapper = CreateDataObjectWrapper(dataObject, resolvedGeometries, path, objectCollection);
|
||||
|
||||
CollectionRebuilder.AppendSpeckleGrasshopperObject(dataObjectWrapper, path, _colorUnpacker, _materialUnpacker);
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal())
|
||||
{
|
||||
// don't throw - continue processing other DataObjects
|
||||
_logger.LogError(ex, "Failed to resolve DataObject {dataObjectId} with InstanceProxies", dataObjectId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts block instances and definitions from traversal contexts into Grasshopper wrapper objects.
|
||||
/// Automatically filters out InstanceProxies belonging to registered DataObjects.
|
||||
/// Automatically handles cleanup of consumed objects from the collection hierarchy.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Deliberately handles both block conversion AND consumed object cleanup in a single operation.
|
||||
/// Too much, I know, BUT it ensures the cleanup always occurs immediately after block processing without
|
||||
/// requiring receive components to call a separate cleanup method in the correct order.
|
||||
/// </remarks>
|
||||
public void ConvertBlockInstances(
|
||||
IReadOnlyCollection<TraversalContext> blocks,
|
||||
IReadOnlyCollection<InstanceDefinitionProxy>? definitionProxies
|
||||
)
|
||||
public void ConvertBlockInstances(IReadOnlyCollection<TraversalContext> blockInstances)
|
||||
{
|
||||
// build set of registered InstanceProxy IDs for fast lookup
|
||||
var registeredProxyIds = new HashSet<string>();
|
||||
foreach (var entry in _dataObjectInstanceRegistry.GetEntries().Values)
|
||||
{
|
||||
foreach (var proxy in entry.InstanceProxies)
|
||||
{
|
||||
var proxyId = proxy.applicationId ?? proxy.id;
|
||||
if (proxyId is not null)
|
||||
{
|
||||
registeredProxyIds.Add(proxyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// filter out InstanceProxies that belong to registered DataObjects
|
||||
var filteredBlockInstances = blockInstances
|
||||
.Where(tc =>
|
||||
{
|
||||
if (tc.Current is InstanceProxy proxy)
|
||||
{
|
||||
var proxyId = proxy.applicationId ?? proxy.id;
|
||||
return proxyId is null || !registeredProxyIds.Contains(proxyId);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var blockUnpacker = new GrasshopperBlockUnpacker(_traversalContextUnpacker, _colorUnpacker, _materialUnpacker);
|
||||
|
||||
// Get consumed object IDs from unpacker
|
||||
// get consumed object IDs from unpacker
|
||||
var consumedObjectIds = blockUnpacker.UnpackBlocks(
|
||||
blocks,
|
||||
definitionProxies,
|
||||
filteredBlockInstances,
|
||||
_definitionProxies,
|
||||
ConvertedObjectsMap,
|
||||
CollectionRebuilder
|
||||
);
|
||||
|
||||
// Clean up consumed objects from collections
|
||||
// clean up consumed objects from collections
|
||||
CollectionRebuilder.RemoveConsumedObjects(consumedObjectIds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a DataObjectWrapper from a DataObject and its geometries.
|
||||
/// Handles color/material inheritance and property extraction.
|
||||
/// </summary>
|
||||
private SpeckleDataObjectWrapper CreateDataObjectWrapper(
|
||||
DataObject dataObject,
|
||||
List<SpeckleGeometryWrapper> geometries,
|
||||
List<Collection> path,
|
||||
SpeckleCollectionWrapper objectCollection
|
||||
)
|
||||
{
|
||||
// Get color and material on DataObject
|
||||
Color? dataObjColor = _colorUnpacker.Cache.TryGetValue(dataObject.applicationId ?? "", out var cachedDataObjColor)
|
||||
? cachedDataObjColor
|
||||
: null;
|
||||
|
||||
SpeckleMaterialWrapper? dataObjMat = _materialUnpacker.Cache.TryGetValue(
|
||||
dataObject.applicationId ?? "",
|
||||
out var cachedDataObjMaterial
|
||||
)
|
||||
? cachedDataObjMaterial
|
||||
: null;
|
||||
|
||||
// Apply DataObject color/material to geometries that don't have their own
|
||||
foreach (var geometry in geometries)
|
||||
{
|
||||
geometry.Color ??= dataObjColor;
|
||||
geometry.Material ??= dataObjMat;
|
||||
}
|
||||
|
||||
// Create property group
|
||||
SpecklePropertyGroupGoo propertyGroup = new();
|
||||
propertyGroup.CastFrom(dataObject.properties);
|
||||
|
||||
// Clear the displayValue to prevent storing duplicate Base
|
||||
dataObject.displayValue.Clear();
|
||||
|
||||
return new SpeckleDataObjectWrapper()
|
||||
{
|
||||
Base = dataObject,
|
||||
Geometries = geometries,
|
||||
Path = path.Select(p => p.name).ToList(),
|
||||
Parent = objectCollection,
|
||||
Name = dataObject.name,
|
||||
Properties = propertyGroup,
|
||||
ApplicationId = dataObject.applicationId,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves InstanceProxy displayValues to transformed geometries.
|
||||
/// Returns the list of resolved geometries that can be used as DataObject displayValue replacements.
|
||||
/// </summary>
|
||||
private List<SpeckleGeometryWrapper> ResolveInstanceProxiesToGeometries(List<InstanceProxy> instanceProxies)
|
||||
{
|
||||
var resolvedGeometries = new List<SpeckleGeometryWrapper>();
|
||||
|
||||
// build a lookup of definitionId -> definition objects for quick access
|
||||
var definitionObjectsMap = new Dictionary<string, List<string>>();
|
||||
if (_definitionProxies is not null)
|
||||
{
|
||||
foreach (var defProxy in _definitionProxies)
|
||||
{
|
||||
var defId = defProxy.applicationId ?? defProxy.id;
|
||||
if (defId is not null)
|
||||
{
|
||||
definitionObjectsMap[defId] = defProxy.objects;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var instanceProxy in instanceProxies)
|
||||
{
|
||||
// get the definition objects for this instance
|
||||
if (!definitionObjectsMap.TryGetValue(instanceProxy.definitionId, out var definitionObjectIds))
|
||||
{
|
||||
continue; // definition not found, skip this proxy
|
||||
}
|
||||
|
||||
// get transform from the instance proxy
|
||||
var transform = GrasshopperHelpers.MatrixToTransform(instanceProxy.transform, instanceProxy.units);
|
||||
|
||||
// apply transform to each definition object
|
||||
foreach (var objectId in definitionObjectIds)
|
||||
{
|
||||
if (ConvertedObjectsMap.TryGetValue(objectId, out var definitionObject))
|
||||
{
|
||||
// deep copy and transform the geometry
|
||||
var transformedWrapper = definitionObject.DeepCopy();
|
||||
transformedWrapper.GeometryBase.NotNull().Transform(transform);
|
||||
resolvedGeometries.Add(transformedWrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedGeometries;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the raw converted objects to SpeckleGeometryWrappers for DataObject display values.
|
||||
/// Does NOT apply DataObject-level colors/materials - that's handled by CreateDataObjectWrapper.
|
||||
/// </summary>
|
||||
private List<SpeckleGeometryWrapper> ConvertToGeometryWrappers(List<(object, Base)> converted)
|
||||
{
|
||||
var geometries = new List<SpeckleGeometryWrapper>();
|
||||
|
||||
foreach ((object convertedObj, Base original) in converted)
|
||||
{
|
||||
if (convertedObj is GeometryBase geometryBase)
|
||||
{
|
||||
SpeckleGeometryWrapper wrapper =
|
||||
new()
|
||||
{
|
||||
Base = original,
|
||||
GeometryBase = geometryBase,
|
||||
// try to get color/material from the individual geometry first
|
||||
Color = _colorUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjColor)
|
||||
? cachedObjColor
|
||||
: null,
|
||||
Material = _materialUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjMaterial)
|
||||
? cachedObjMaterial
|
||||
: null,
|
||||
};
|
||||
|
||||
geometries.Add(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
return geometries;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ using Speckle.Connectors.GrasshopperShared.Operations.Send;
|
||||
using Speckle.Connectors.GrasshopperShared.Parameters;
|
||||
using Speckle.Connectors.GrasshopperShared.Properties;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.Common.ToHost;
|
||||
using Speckle.Converters.Rhino;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Models.GraphTraversal;
|
||||
@@ -58,6 +59,8 @@ public class PriorityLoader : GH_AssemblyPriority
|
||||
services.AddTransient<GrasshopperReceiveOperation>();
|
||||
services.AddSingleton(DefaultTraversal.CreateTraversalFunc());
|
||||
services.AddTransient<TraversalContextUnpacker>();
|
||||
services.AddScoped<IDataObjectInstanceRegistry, DataObjectInstanceRegistry>();
|
||||
services.AddTransient<LocalToGlobalMapHandler>();
|
||||
|
||||
// send
|
||||
services.AddTransient<IRootObjectBuilder<SpeckleCollectionWrapperGoo>, GrasshopperRootObjectBuilder>();
|
||||
|
||||
@@ -306,6 +306,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -306,6 +306,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -235,6 +235,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Connectors.Rhino.Extensions;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.Common.ToHost;
|
||||
using Speckle.Converters.Rhino;
|
||||
using Speckle.Sdk;
|
||||
|
||||
namespace Speckle.Connectors.Rhino.HostApp;
|
||||
|
||||
/// <summary>
|
||||
/// Groups block instances created from DataObject with InstanceProxies as display values and applies DataObject metadata.
|
||||
/// </summary>
|
||||
public class DataObjectInstanceGrouper
|
||||
{
|
||||
private readonly IConverterSettingsStore<RhinoConversionSettings> _converterSettings;
|
||||
private readonly ILogger<DataObjectInstanceGrouper> _logger;
|
||||
private readonly IDataObjectInstanceRegistry _dataObjectInstanceRegistry;
|
||||
|
||||
public DataObjectInstanceGrouper(
|
||||
IConverterSettingsStore<RhinoConversionSettings> converterSettings,
|
||||
ILogger<DataObjectInstanceGrouper> logger,
|
||||
IDataObjectInstanceRegistry dataObjectInstanceRegistry
|
||||
)
|
||||
{
|
||||
_converterSettings = converterSettings;
|
||||
_logger = logger;
|
||||
_dataObjectInstanceRegistry = dataObjectInstanceRegistry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// After all instances have been created, we then run through the data object instance registry to see which instances
|
||||
/// belonged to a data object. The method then groups all instances to "re-assemble" the original data object and
|
||||
/// applies the properties of the data object on to the instances.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a deferred action and can only occur once the RhinoInstanceBaker has done its thing.
|
||||
/// </remarks>
|
||||
public void GroupAndApplyProperties()
|
||||
{
|
||||
var doc = _converterSettings.Current.Document;
|
||||
var entries = _dataObjectInstanceRegistry.GetEntries(); // see docstring
|
||||
|
||||
foreach (var kvp in entries)
|
||||
{
|
||||
var dataObjectId = kvp.Key;
|
||||
var entry = kvp.Value;
|
||||
try
|
||||
{
|
||||
var instanceIds = _dataObjectInstanceRegistry.GetInstanceIdsForDataObject(dataObjectId);
|
||||
if (instanceIds.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// create group, name the group and apply properties
|
||||
using var dataObjectAtts = entry.DataObject.GetAttributes();
|
||||
var groupName = dataObjectAtts.Name;
|
||||
var groupIndex = doc.Groups.Add(groupName, instanceIds.Select(id => new Guid(id)));
|
||||
|
||||
if (groupIndex >= 0)
|
||||
{
|
||||
// apply properties to each instance (doing this on an instance level because setting to group doesn't work)
|
||||
foreach (var instanceId in instanceIds)
|
||||
{
|
||||
var rhinoObj = doc.Objects.FindId(new Guid(instanceId));
|
||||
if (rhinoObj != null)
|
||||
{
|
||||
// set the name from DataObject
|
||||
rhinoObj.Attributes.Name = dataObjectAtts.Name;
|
||||
|
||||
// copy all user strings
|
||||
foreach (var key in dataObjectAtts.GetUserStrings().AllKeys)
|
||||
{
|
||||
rhinoObj.Attributes.SetUserString(key, dataObjectAtts.GetUserString(key));
|
||||
}
|
||||
rhinoObj.CommitChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal())
|
||||
{
|
||||
_logger.LogError(ex, "Failed to group DataObject instances {dataObjectId}", dataObjectId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using Speckle.Connectors.Common.Conversion;
|
||||
using Speckle.Connectors.Common.Instances;
|
||||
using Speckle.Connectors.Common.Operations;
|
||||
using Speckle.Connectors.Rhino.Extensions;
|
||||
using Speckle.Converters.Common.ToHost;
|
||||
using Speckle.DoubleNumerics;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Common;
|
||||
@@ -22,18 +23,21 @@ public class RhinoInstanceBaker : IInstanceBaker<IReadOnlyCollection<string>>
|
||||
private readonly RhinoLayerBaker _layerBaker;
|
||||
private readonly RhinoColorBaker _colorBaker;
|
||||
private readonly ILogger<RhinoInstanceBaker> _logger;
|
||||
private readonly IDataObjectInstanceRegistry _dataObjectInstanceRegistry;
|
||||
|
||||
public RhinoInstanceBaker(
|
||||
RhinoLayerBaker layerBaker,
|
||||
RhinoMaterialBaker rhinoMaterialBaker,
|
||||
RhinoColorBaker colorBaker,
|
||||
ILogger<RhinoInstanceBaker> logger
|
||||
ILogger<RhinoInstanceBaker> logger,
|
||||
IDataObjectInstanceRegistry dataObjectInstanceRegistry
|
||||
)
|
||||
{
|
||||
_layerBaker = layerBaker;
|
||||
_materialBaker = rhinoMaterialBaker;
|
||||
_colorBaker = colorBaker;
|
||||
_logger = logger;
|
||||
_dataObjectInstanceRegistry = dataObjectInstanceRegistry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -155,6 +159,9 @@ public class RhinoInstanceBaker : IInstanceBaker<IReadOnlyCollection<string>>
|
||||
applicationIdMap[instanceProxyId] = new List<string>() { id.ToString() };
|
||||
createdObjectIds.Add(id.ToString());
|
||||
conversionResults.Add(new(Status.SUCCESS, instanceProxy, id.ToString(), "Instance (Block)"));
|
||||
|
||||
// link this baked instance back to its DataObject if it came from one (the method handles the check)
|
||||
_dataObjectInstanceRegistry.LinkInstanceToDataObject(instanceProxyId, id.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal())
|
||||
|
||||
+24
-5
@@ -10,6 +10,7 @@ using Speckle.Connectors.Common.Threading;
|
||||
using Speckle.Connectors.Rhino.Extensions;
|
||||
using Speckle.Connectors.Rhino.HostApp;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.Common.ToHost;
|
||||
using Speckle.Converters.Rhino;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Common.Exceptions;
|
||||
@@ -36,6 +37,8 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
|
||||
private readonly ISdkActivityFactory _activityFactory;
|
||||
private readonly IThreadContext _threadContext;
|
||||
private readonly IReceiveConversionHandler _conversionHandler;
|
||||
private readonly IDataObjectInstanceRegistry _dataObjectInstanceRegistry;
|
||||
private readonly DataObjectInstanceGrouper _dataObjectInstanceGrouper;
|
||||
|
||||
public RhinoHostObjectBuilder(
|
||||
IRootToHostConverter converter,
|
||||
@@ -48,7 +51,9 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
|
||||
RhinoGroupBaker groupBaker,
|
||||
ISdkActivityFactory activityFactory,
|
||||
IThreadContext threadContext,
|
||||
IReceiveConversionHandler conversionHandler
|
||||
IReceiveConversionHandler conversionHandler,
|
||||
IDataObjectInstanceRegistry dataObjectInstanceRegistry,
|
||||
DataObjectInstanceGrouper dataObjectInstanceGrouper
|
||||
)
|
||||
{
|
||||
_converter = converter;
|
||||
@@ -62,6 +67,8 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
|
||||
_activityFactory = activityFactory;
|
||||
_threadContext = threadContext;
|
||||
_conversionHandler = conversionHandler;
|
||||
_dataObjectInstanceRegistry = dataObjectInstanceRegistry;
|
||||
_dataObjectInstanceGrouper = dataObjectInstanceGrouper;
|
||||
}
|
||||
|
||||
#pragma warning disable CA1506
|
||||
@@ -188,8 +195,14 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
|
||||
|
||||
if (conversionIds.Count == 0)
|
||||
{
|
||||
// TODO: add this condition to report object - same as in autocad
|
||||
throw new ConversionException("Object did not convert to any native geometry");
|
||||
// Don't throw if this DataObject was registered for instance baking
|
||||
|
||||
if (!_dataObjectInstanceRegistry.IsRegistered(obj.applicationId ?? obj.id.NotNull()))
|
||||
{
|
||||
throw new ConversionException("Object did not convert to any native geometry");
|
||||
}
|
||||
// Skip normal processing - will be handled by DataObjectInstanceGrouper
|
||||
return;
|
||||
}
|
||||
|
||||
// 4: log
|
||||
@@ -232,7 +245,10 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
|
||||
conversionResults.UnionWith(instanceConversionResults); // add instance conversion results to our list
|
||||
}
|
||||
|
||||
// 7 - Create groups
|
||||
// 7.1 Group DataObject instances and apply metadata
|
||||
_dataObjectInstanceGrouper.GroupAndApplyProperties();
|
||||
|
||||
// 7.2 Normal group creation
|
||||
if (unpackedRoot.GroupProxies is not null)
|
||||
{
|
||||
_groupBaker.BakeGroups(unpackedRoot.GroupProxies, applicationIdMap, baseLayerName);
|
||||
@@ -244,6 +260,9 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
|
||||
|
||||
private void PreReceiveDeepClean(string baseLayerName)
|
||||
{
|
||||
// Clear DataObject instance registry at start of new build
|
||||
_dataObjectInstanceRegistry.Clear();
|
||||
|
||||
// Remove all previously received layers and render materials from the document
|
||||
int rootLayerIndex = _converterSettings.Current.Document.Layers.Find(
|
||||
Guid.Empty,
|
||||
@@ -354,7 +373,7 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
|
||||
if (objCount > 1)
|
||||
{
|
||||
var groupIndex = _converterSettings.Current.Document.Groups.Add(
|
||||
$@"{originatingObject.speckle_type.Split('.').Last()} - {parentId} ({baseLayerName})",
|
||||
$"{originatingObject.speckle_type.Split('.').Last()} - {parentId} ({baseLayerName})",
|
||||
objectIds
|
||||
);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ using Speckle.Connectors.Rhino.Operations.Receive;
|
||||
using Speckle.Connectors.Rhino.Operations.Send;
|
||||
using Speckle.Connectors.Rhino.Operations.Send.Settings;
|
||||
using Speckle.Connectors.Rhino.Plugin;
|
||||
using Speckle.Converters.Common.ToHost;
|
||||
using Speckle.Sdk.Models.GraphTraversal;
|
||||
|
||||
namespace Speckle.Connectors.Rhino.DependencyInjection;
|
||||
@@ -75,7 +76,7 @@ public static class ServiceRegistration
|
||||
InstanceObjectsManager<RhinoObject, List<string>>
|
||||
>();
|
||||
|
||||
// Register unpackers and bakers
|
||||
// register unpackers and bakers
|
||||
serviceCollection.AddScoped<RhinoLayerUnpacker>();
|
||||
serviceCollection.AddScoped<RhinoLayerBaker>();
|
||||
|
||||
@@ -94,6 +95,10 @@ public static class ServiceRegistration
|
||||
serviceCollection.AddScoped<PropertiesExtractor>();
|
||||
serviceCollection.AddScoped<RevitMappingResolver>();
|
||||
|
||||
// handling proxified display values
|
||||
serviceCollection.AddScoped<IDataObjectInstanceRegistry, DataObjectInstanceRegistry>();
|
||||
serviceCollection.AddScoped<DataObjectInstanceGrouper>();
|
||||
|
||||
// register helpers
|
||||
serviceCollection.AddScoped<RhinoLayerHelper>();
|
||||
serviceCollection.AddScoped<RhinoObjectHelper>();
|
||||
|
||||
+1
@@ -23,6 +23,7 @@
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Bindings\RhinoSendBinding.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Bindings\RhinoSelectionBinding.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\AttributeExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\DataObjectInstanceGrouper.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Properties\PropertiesExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RhinoIdleManager.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RhinoLayerHelper.cs" />
|
||||
|
||||
@@ -325,6 +325,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -406,6 +406,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -406,6 +406,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -210,6 +210,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -210,6 +210,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
+8
-1
@@ -4,6 +4,7 @@ using Speckle.Objects;
|
||||
using Speckle.Objects.Data;
|
||||
using Speckle.Sdk.Common.Exceptions;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Instances;
|
||||
|
||||
namespace Speckle.Converters.AutocadShared.ToHost.Geometry;
|
||||
|
||||
@@ -42,14 +43,20 @@ public class DataObjectConverter : IToHostTopLevelConverter, ITypedConverter<Dat
|
||||
public List<(ADB.Entity a, Base b)> Convert(DataObject target)
|
||||
{
|
||||
var result = new List<(ADB.Entity a, Base b)>();
|
||||
|
||||
if (target.displayValue.Count > 0 && target.displayValue[0] is InstanceProxy)
|
||||
{
|
||||
return []; // return empty - defer to instance baker
|
||||
}
|
||||
foreach (var item in target.displayValue)
|
||||
{
|
||||
result.AddRange(ConvertDisplayObject(item));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public IEnumerable<(ADB.Entity a, Base b)> ConvertDisplayObject(Base displayObject)
|
||||
private IEnumerable<(ADB.Entity a, Base b)> ConvertDisplayObject(Base displayObject)
|
||||
{
|
||||
switch (displayObject)
|
||||
{
|
||||
|
||||
@@ -219,6 +219,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -219,6 +219,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -260,6 +260,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
+32
-13
@@ -1,19 +1,38 @@
|
||||
using Speckle.Sdk.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
using static Speckle.Converter.Navisworks.Helpers.ElementSelectionHelper;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.ToSpeckle;
|
||||
|
||||
public class DisplayValueExtractor(GeometryToSpeckleConverter geometryConverter)
|
||||
public class DisplayValueExtractor
|
||||
{
|
||||
internal List<Base> GetDisplayValue(NAV.ModelItem modelItem) =>
|
||||
modelItem == null
|
||||
? throw new ArgumentNullException(nameof(modelItem))
|
||||
: !modelItem.HasGeometry
|
||||
? ([])
|
||||
: !IsElementVisible(modelItem)
|
||||
? []
|
||||
:
|
||||
// this can be meshes or the instance reference objects
|
||||
// the un transformed objects stored in a separate collection
|
||||
geometryConverter.Convert(modelItem);
|
||||
private readonly IConverterSettingsStore<NavisworksConversionSettings> _converterSettings;
|
||||
private readonly ILogger<DisplayValueExtractor> _logger;
|
||||
private readonly GeometryToSpeckleConverter _geometryConverter;
|
||||
|
||||
public DisplayValueExtractor(
|
||||
IConverterSettingsStore<NavisworksConversionSettings> converterSettings,
|
||||
ILogger<DisplayValueExtractor> logger
|
||||
)
|
||||
{
|
||||
_converterSettings = converterSettings;
|
||||
_logger = logger;
|
||||
_geometryConverter = new GeometryToSpeckleConverter(_converterSettings.Current);
|
||||
}
|
||||
|
||||
internal List<Base> GetDisplayValue(NAV.ModelItem modelItem)
|
||||
{
|
||||
if (modelItem == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelItem));
|
||||
}
|
||||
if (!modelItem.HasGeometry)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return !IsElementVisible(modelItem) ? [] : _geometryConverter.Convert(modelItem);
|
||||
}
|
||||
}
|
||||
|
||||
+24
-11
@@ -1,10 +1,14 @@
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converter.Navisworks.Services;
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converters.Common;
|
||||
using static Speckle.Converter.Navisworks.Helpers.PropertyHelpers;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.ToSpeckle;
|
||||
|
||||
public class PropertySetsExtractor(IConverterSettingsStore<NavisworksConversionSettings> settingsStore)
|
||||
public class PropertySetsExtractor(
|
||||
IConverterSettingsStore<NavisworksConversionSettings> settingsStore,
|
||||
IPropertyConverter propertyConverter
|
||||
)
|
||||
{
|
||||
internal Dictionary<string, object?>? GetPropertySets(NAV.ModelItem modelItem)
|
||||
{
|
||||
@@ -18,6 +22,17 @@ public class PropertySetsExtractor(IConverterSettingsStore<NavisworksConversionS
|
||||
return propertyDictionary;
|
||||
}
|
||||
|
||||
private static NAV.Units GetModelUnits(NAV.ModelItem modelItem)
|
||||
{
|
||||
NAV.ModelItem? ancestor = modelItem;
|
||||
while (ancestor != null && !ancestor.HasModel)
|
||||
{
|
||||
ancestor = ancestor.Parent;
|
||||
}
|
||||
|
||||
return ancestor != null ? ancestor.Model.Units : NAV.Units.Meters;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts property sets from a NAV.ModelItem and adds them to a dictionary,
|
||||
/// PropertySets are specific to the host application source appended to Navisworks and therefore
|
||||
@@ -28,6 +43,9 @@ public class PropertySetsExtractor(IConverterSettingsStore<NavisworksConversionS
|
||||
private Dictionary<string, object?> ExtractPropertySets(NAV.ModelItem modelItem)
|
||||
{
|
||||
var propertySetDictionary = new Dictionary<string, object?>();
|
||||
var modelUnits = GetModelUnits(modelItem);
|
||||
|
||||
propertyConverter.Reset();
|
||||
|
||||
foreach (var propertyCategory in modelItem.PropertyCategories)
|
||||
{
|
||||
@@ -40,23 +58,18 @@ public class PropertySetsExtractor(IConverterSettingsStore<NavisworksConversionS
|
||||
|
||||
foreach (var property in propertyCategory.Properties)
|
||||
{
|
||||
string sanitizedName = SanitizePropertyName(property.DisplayName);
|
||||
var propertyValue = ConvertPropertyValue(property.Value, settingsStore.Current.Derived.SpeckleUnits);
|
||||
|
||||
var sanitizedName = SanitizePropertyName(property.DisplayName);
|
||||
var propertyValue = propertyConverter.ConvertPropertyValue(property.Value, modelUnits, property.DisplayName);
|
||||
if (propertyValue != null)
|
||||
{
|
||||
propertySet[sanitizedName] = propertyValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (propertySet.Count <= 0)
|
||||
if (propertySet.Count > 0)
|
||||
{
|
||||
continue;
|
||||
propertySetDictionary[SanitizePropertyName(propertyCategory.DisplayName)] = propertySet;
|
||||
}
|
||||
|
||||
string categoryName = SanitizePropertyName(propertyCategory.DisplayName);
|
||||
|
||||
propertySetDictionary[categoryName] = propertySet;
|
||||
}
|
||||
|
||||
return propertySetDictionary;
|
||||
|
||||
+13
-11
@@ -1,8 +1,10 @@
|
||||
using static Speckle.Converter.Navisworks.Helpers.PropertyHelpers;
|
||||
using Speckle.Converter.Navisworks.Services;
|
||||
using Speckle.InterfaceGenerator;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.ToSpeckle;
|
||||
|
||||
public sealed class RevitBuiltInCategoryExtractor
|
||||
[GenerateAutoInterface]
|
||||
public class RevitBuiltInCategoryExtractor(IPropertyConverter converter) : IRevitBuiltInCategoryExtractor
|
||||
{
|
||||
private const int ANCESTOR_AND_SELF_COUNT = 4; // It seems like this is the maximum depth found needed in practice
|
||||
private const string REVIT_CAT_GROUP = "LcRevitData_Element";
|
||||
@@ -13,28 +15,28 @@ public sealed class RevitBuiltInCategoryExtractor
|
||||
/// Attempts to map a Navisworks/Revit display category from the given model item or its ancestors
|
||||
/// to a known Revit built-in category constant (e.g., "OST_Walls").
|
||||
/// </summary>
|
||||
internal static bool TryGetBuiltInCategory(
|
||||
NAV.ModelItem item,
|
||||
out string mapped,
|
||||
int maxDepth = ANCESTOR_AND_SELF_COUNT
|
||||
)
|
||||
public bool TryGetBuiltInCategory(NAV.ModelItem item, out string mapped, int maxDepth = ANCESTOR_AND_SELF_COUNT)
|
||||
{
|
||||
mapped = string.Empty;
|
||||
|
||||
// Look up the category value, starting at this item and walking up to maxDepth ancestors
|
||||
// Find the category VariantData up the hierarchy
|
||||
var v = FindRevitCategoryInHierarchy(item, maxDepth);
|
||||
if (v == null)
|
||||
if (v is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var name = ConvertPropertyValue(v, "")?.ToString();
|
||||
converter.Reset();
|
||||
|
||||
// Convert using per-object model units and current UI units
|
||||
var nameObj = converter.ConvertPropertyValue(v, item.Model.Units, item.DisplayName);
|
||||
var name = nameObj?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
name = name?.Trim();
|
||||
name = name!.Trim();
|
||||
|
||||
// Map display name to OST_* built-in category constant
|
||||
var builtInCategory = DisplayNameToRevitBuiltInCategory(name);
|
||||
|
||||
+3
-2
@@ -11,7 +11,8 @@ public class HierarchicalPropertyHandler(
|
||||
PropertySetsExtractor propertySetsExtractor,
|
||||
ModelPropertiesExtractor modelPropertiesExtractor,
|
||||
ClassPropertiesExtractor classPropertiesExtractor,
|
||||
IConverterSettingsStore<NavisworksConversionSettings> settingsStore
|
||||
IConverterSettingsStore<NavisworksConversionSettings> settingsStore,
|
||||
IRevitBuiltInCategoryExtractor revitCategoryExtractor
|
||||
) : BasePropertyHandler(propertySetsExtractor, modelPropertiesExtractor)
|
||||
{
|
||||
private static string PseudoClassPropertiesKey => "_pseudoClassProperties";
|
||||
@@ -22,7 +23,7 @@ public class HierarchicalPropertyHandler(
|
||||
var propertyDict = classPropertiesExtractor.GetClassProperties(modelItem) ?? [];
|
||||
|
||||
// Interop-lite mapping for Revit built-in categories
|
||||
if (_mapRevit && RevitBuiltInCategoryExtractor.TryGetBuiltInCategory(modelItem, out var builtInCategory))
|
||||
if (_mapRevit && revitCategoryExtractor.TryGetBuiltInCategory(modelItem, out var builtInCategory))
|
||||
{
|
||||
PropertyHelpers.AddPropertyIfNotNullOrEmpty(
|
||||
propertyDict,
|
||||
|
||||
-13
@@ -46,19 +46,6 @@ public static class NavisworksConverterServiceRegistration
|
||||
serviceCollection.AddScoped<DisplayValueExtractor>();
|
||||
serviceCollection.AddScoped<GeometryToSpeckleConverter>();
|
||||
|
||||
// Register dual shared geometry stores for instancing pattern (.NET Framework compatible)
|
||||
// Store 1: For geometry definitions (Mesh, Curve, etc.) - Store 2: For InstanceDefinitionProxy objects
|
||||
serviceCollection.AddScoped<InstanceStoreManager>();
|
||||
|
||||
// Register ISharedGeometryStore interface using the geometry definitions store for backward compatibility
|
||||
serviceCollection.AddScoped<ISharedGeometryStore>(provider =>
|
||||
provider.GetRequiredService<InstanceStoreManager>().GeometryDefinitionsStore);
|
||||
|
||||
// Register settings resolved from factory
|
||||
serviceCollection.AddScoped<NavisworksConversionSettings>(sp =>
|
||||
sp.GetRequiredService<INavisworksConversionSettingsFactory>().Current
|
||||
);
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// ComScope - Because Navisworks COM objects are like vampires that never die unless you tell them to.
|
||||
///
|
||||
/// This is a RAII (Resource Acquisition Is Initialization) wrapper for COM objects.
|
||||
/// Think of it as a babysitter that makes sure COM objects get cleaned up properly
|
||||
/// when you're done with them, preventing memory leaks that would otherwise
|
||||
/// slowly consume your machine's RAM like a digital Pac-Man.
|
||||
///
|
||||
/// Why do we need this?
|
||||
/// - Navisworks COM API creates objects that live forever unless explicitly released
|
||||
/// - Forgetting to call Marshal.ReleaseComObject() = memory leak city
|
||||
/// - Using statements + IDisposable = automatic cleanup when scope ends
|
||||
/// - One less thing to remember = fewer bugs = happier developers
|
||||
///
|
||||
/// Usage: Wrap it in a 'using' statement and let C# handle the cleanup:
|
||||
/// using var comThing = new ComScope<SomeComType>(myComObject);
|
||||
/// // Do stuff with comThing.Value
|
||||
/// // Automatic cleanup happens here when using block ends
|
||||
///
|
||||
/// Pro tip: This prevents the "why is Navisworks eating all my RAM?" conversations
|
||||
/// that happen way too often with COM interop code.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The COM object type we're babysitting</typeparam>
|
||||
public readonly struct ComScope<T>(T comObject, bool shouldRelease = true) : IDisposable
|
||||
where T : class
|
||||
{
|
||||
public ComScope(T comObject)
|
||||
: this(comObject, false) { }
|
||||
|
||||
private T ComObject { get; } = comObject;
|
||||
private bool ShouldRelease { get; } = shouldRelease;
|
||||
|
||||
public T Value => ComObject;
|
||||
|
||||
/// <summary>
|
||||
/// The magic cleanup method. This gets called automatically when the 'using' block ends.
|
||||
/// It tells the COM object "your services are no longer required" in a polite way
|
||||
/// that doesn't crash the application.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
// Only release if we're supposed to AND the object actually exists
|
||||
if (ShouldRelease && ComObject != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This is the important bit - tells COM runtime to decrement reference count
|
||||
Marshal.ReleaseComObject(ComObject);
|
||||
}
|
||||
catch (InvalidComObjectException)
|
||||
{
|
||||
// Sometimes the object is already gone (maybe someonereleased, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
-2
@@ -1,4 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Speckle.Objects.Geometry;
|
||||
|
||||
@@ -8,6 +8,9 @@ public static class PropertyHelpers
|
||||
{
|
||||
private static readonly HashSet<string> s_excludedCategories = ["Geometry", "Metadata"];
|
||||
|
||||
/// <summary>
|
||||
/// Adds a property to an object (either a Base object or a Dictionary) if the value is not null or empty.
|
||||
/// </summary>
|
||||
private static readonly Dictionary<NAV.VariantDataType, Func<NAV.VariantData, string, dynamic?>> s_typeHandlers =
|
||||
new()
|
||||
{
|
||||
@@ -48,7 +51,9 @@ public static class PropertyHelpers
|
||||
}
|
||||
|
||||
// Default case for unsupported types
|
||||
return value.DataType is NAV.VariantDataType.None or NAV.VariantDataType.Point2D ? null : value.ToString();
|
||||
return value.DataType == NAV.VariantDataType.None || value.DataType == NAV.VariantDataType.Point2D
|
||||
? null
|
||||
: value.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -109,3 +114,27 @@ public static class PropertyHelpers
|
||||
internal static bool IsCategoryToBeSkipped(NAV.PropertyCategory propertyCategory) =>
|
||||
s_excludedCategories.Contains(propertyCategory.DisplayName);
|
||||
}
|
||||
|
||||
internal static class UnitLabels
|
||||
{
|
||||
internal static string Linear(NAV.Units u) =>
|
||||
u switch
|
||||
{
|
||||
NAV.Units.Kilometers => "Kilometers",
|
||||
NAV.Units.Meters => "Metres",
|
||||
NAV.Units.Centimeters => "Centimeters",
|
||||
NAV.Units.Millimeters => "Millimeters",
|
||||
NAV.Units.Micrometers => "Micrometers",
|
||||
NAV.Units.Miles => "Miles",
|
||||
NAV.Units.Yards => "Yards",
|
||||
NAV.Units.Feet => "Feet",
|
||||
NAV.Units.Inches => "Inches",
|
||||
NAV.Units.Mils => "Mils",
|
||||
NAV.Units.Microinches => "Microinches",
|
||||
_ => "Metres"
|
||||
};
|
||||
|
||||
internal static string Area(NAV.Units u) => $"Square {Linear(u).ToLower()}";
|
||||
|
||||
public static string Volume(NAV.Units u) => $"Cubic {Linear(u).ToLower()}";
|
||||
}
|
||||
|
||||
-154
@@ -1,154 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Instances;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Simple wrapper class that manages two SharedGeometryStores instances for dual instancing pattern.
|
||||
/// Provides easy access to both mesh definitions store and instance definition proxies store.
|
||||
/// </summary>
|
||||
public class InstanceStoreManager(ILogger<InstanceStoreManager> logger)
|
||||
{
|
||||
private readonly ILogger<InstanceStoreManager> _logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
/// <summary>
|
||||
/// Store for geometry definitions (geometry data) - untransformed base geometries.
|
||||
/// </summary>
|
||||
internal SharedGeometryStore GeometryDefinitionsStore { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Store for InstanceDefinitionProxy objects that reference geometry definitions.
|
||||
/// </summary>
|
||||
internal SharedGeometryStore InstanceDefinitionProxiesStore { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Clears both stores for a new conversion session.
|
||||
/// Should be called at the start of each conversion.
|
||||
/// </summary>
|
||||
public void ClearAll()
|
||||
{
|
||||
GeometryDefinitionsStore.Clear();
|
||||
InstanceDefinitionProxiesStore.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all instance definition proxies from the store, cast to their specific type.
|
||||
/// Useful for adding to root collection at end of conversion.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<InstanceDefinitionProxy> GetInstanceDefinitionProxies()
|
||||
{
|
||||
var proxies = InstanceDefinitionProxiesStore.Geometries.OfType<InstanceDefinitionProxy>().ToList().AsReadOnly();
|
||||
_logger.LogDebug("GetInstanceDefinitionProxies returning {Count} proxies", proxies.Count);
|
||||
return proxies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all geometry definitions from the geometry definitions store.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<Base> GetGeometryDefinitions() => [.. GeometryDefinitionsStore.Geometries.ToList().AsReadOnly()];
|
||||
|
||||
/// <summary>
|
||||
/// Gets a geometry definition by its application ID from the geometry definitions store.
|
||||
/// </summary>
|
||||
/// <returns>The geometry if found, null otherwise.</returns>
|
||||
public Base? GetGeometryDefinition(string fragmentId) =>
|
||||
GeometryDefinitionsStore.Geometries.FirstOrDefault(g => g.applicationId == $"geom_{fragmentId}");
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance definition proxy by its application ID.
|
||||
/// </summary>
|
||||
/// <returns>The instance definition proxy if found, null otherwise.</returns>
|
||||
public InstanceDefinitionProxy? GetInstanceDefinitionProxy(string fragmentId) =>
|
||||
InstanceDefinitionProxiesStore
|
||||
.Geometries.OfType<InstanceDefinitionProxy>()
|
||||
.FirstOrDefault(p => p.applicationId == $"def_{fragmentId}");
|
||||
|
||||
/// <summary>
|
||||
/// Adds a geometry definition and corresponding instance definition proxy for shared geometry.
|
||||
/// This is a convenience method that handles both stores in one call.
|
||||
/// </summary>
|
||||
/// <param name="fragmentId">The fragment-based application ID.</param>
|
||||
/// <param name="geometry">The untransformed base geometry.</param>
|
||||
/// <returns>True if both were added (new geometry), false if they already existed.</returns>
|
||||
public bool AddSharedGeometry(string fragmentId, Base geometry)
|
||||
{
|
||||
_logger.LogDebug("AddSharedGeometry called for FragmentId={FragmentId}", fragmentId);
|
||||
|
||||
bool geometryAdded = false;
|
||||
bool proxyAdded = false;
|
||||
|
||||
// Create prefixed IDs for 1:1:1 relationship using base fragment hash
|
||||
var geometryId = $"geom_{fragmentId}";
|
||||
var definitionId = $"def_{fragmentId}";
|
||||
|
||||
_logger.LogDebug("Using GeometryId={GeometryId}, DefinitionId={DefinitionId}", geometryId, definitionId);
|
||||
|
||||
// Add geometry definition if not exists
|
||||
if (!GeometryDefinitionsStore.Contains(geometryId))
|
||||
{
|
||||
geometry.applicationId = geometryId;
|
||||
geometryAdded = GeometryDefinitionsStore.Add(geometry);
|
||||
_logger.LogDebug("Added geometry definition: {GeometryId}, Success={Success}", geometryId, geometryAdded);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("Geometry definition already exists: {GeometryId}", geometryId);
|
||||
}
|
||||
|
||||
// Add instance definition proxy if not exists
|
||||
if (!InstanceDefinitionProxiesStore.Contains(definitionId))
|
||||
{
|
||||
if (geometry.applicationId == null)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Cannot create instance definition proxy - geometry.id is null for FragmentId={FragmentId}",
|
||||
fragmentId
|
||||
);
|
||||
var result = geometryAdded || proxyAdded;
|
||||
_logger.LogDebug(
|
||||
"AddSharedGeometry completed: FragmentId={FragmentId}, Result={Result}, GeometryAdded={GeometryAdded}, ProxyAdded={ProxyAdded}",
|
||||
fragmentId,
|
||||
result,
|
||||
geometryAdded,
|
||||
proxyAdded
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
var definitionProxy = new InstanceDefinitionProxy
|
||||
{
|
||||
applicationId = definitionId,
|
||||
name = $"Shared Geometry {fragmentId[..8]}...", // Show first 8 chars for readability
|
||||
objects = [geometry.applicationId],
|
||||
maxDepth = 0
|
||||
};
|
||||
proxyAdded = InstanceDefinitionProxiesStore.Add(definitionProxy);
|
||||
_logger.LogDebug("Added instance definition proxy: {DefinitionId}, Success={Success}", definitionId, proxyAdded);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("Instance definition proxy already exists: {DefinitionId}", definitionId);
|
||||
}
|
||||
|
||||
var conversionSucceededResult = geometryAdded || proxyAdded;
|
||||
_logger.LogDebug(
|
||||
"AddSharedGeometry completed: FragmentId={FragmentId}, Result={Result}, GeometryAdded={GeometryAdded}, ProxyAdded={ProxyAdded}",
|
||||
fragmentId,
|
||||
conversionSucceededResult,
|
||||
geometryAdded,
|
||||
proxyAdded
|
||||
);
|
||||
return conversionSucceededResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if shared geometry already exists in both stores.
|
||||
/// </summary>
|
||||
/// <param name="fragmentId">The fragment-based application ID.</param>
|
||||
/// <returns>True if geometry definition exists in both stores.</returns>
|
||||
public bool ContainsSharedGeometry(string fragmentId) => GeometryDefinitionsStore.Contains($"geom_{fragmentId}")
|
||||
// && InstanceDefinitionProxiesStore.Contains($"def_{fragmentId}")
|
||||
;
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
using System.Globalization;
|
||||
using Speckle.Converter.Navisworks.Helpers;
|
||||
using Speckle.InterfaceGenerator;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.Services;
|
||||
|
||||
[GenerateAutoInterface]
|
||||
public class PropertyConverter(IUiUnitsCache uiUnitsCache) : IPropertyConverter
|
||||
{
|
||||
public void Reset() => uiUnitsCache.Reset();
|
||||
|
||||
public object? ConvertPropertyValue(NAV.VariantData? value, NAV.Units modelUnits, string propDisplayName) =>
|
||||
value == null
|
||||
? null
|
||||
: _handlers.TryGetValue(value.DataType, out var f)
|
||||
? f(value, (modelUnits, propDisplayName))
|
||||
: value.DataType is NAV.VariantDataType.None or NAV.VariantDataType.Point2D
|
||||
? null
|
||||
: value.ToString();
|
||||
|
||||
private readonly Dictionary<
|
||||
NAV.VariantDataType,
|
||||
Func<NAV.VariantData, (NAV.Units model, string name), object?>
|
||||
> _handlers =
|
||||
new()
|
||||
{
|
||||
{ NAV.VariantDataType.Boolean, (v, _) => v.ToBoolean() },
|
||||
{ NAV.VariantDataType.DisplayString, (v, _) => v.ToDisplayString() },
|
||||
{ NAV.VariantDataType.IdentifierString, (v, _) => v.ToIdentifierString() },
|
||||
{ NAV.VariantDataType.Int32, (v, _) => v.ToInt32() },
|
||||
{ NAV.VariantDataType.Double, (v, _) => v.ToDouble() },
|
||||
// Angle as dictionary with units
|
||||
{ NAV.VariantDataType.DoubleAngle, (v, t) => NumObj(t.name, v.ToDoubleAngle(), "Degrees") },
|
||||
// Length → dictionary in UI units
|
||||
{
|
||||
NAV.VariantDataType.DoubleLength,
|
||||
(v, t) =>
|
||||
{
|
||||
var uiUnits = uiUnitsCache.Ensure();
|
||||
|
||||
var k = NAV.UnitConversion.ScaleFactor(t.model, uiUnits);
|
||||
return NumObj(t.name, v.ToDoubleLength() * k, UnitLabels.Linear(uiUnits));
|
||||
}
|
||||
},
|
||||
// Area → dictionary in UI units^2
|
||||
{
|
||||
NAV.VariantDataType.DoubleArea,
|
||||
(v, t) =>
|
||||
{
|
||||
var uiUnits = uiUnitsCache.Ensure();
|
||||
var k = NAV.UnitConversion.ScaleFactor(t.model, uiUnits);
|
||||
k *= k;
|
||||
return NumObj(t.name, v.ToDoubleArea() * k, UnitLabels.Area(uiUnits));
|
||||
}
|
||||
},
|
||||
// Volume → dictionary in UI units^3
|
||||
{
|
||||
NAV.VariantDataType.DoubleVolume,
|
||||
(v, t) =>
|
||||
{
|
||||
var uiUnits = uiUnitsCache.Ensure();
|
||||
var k = NAV.UnitConversion.ScaleFactor(t.model, uiUnits);
|
||||
k = k * k * k;
|
||||
return NumObj(t.name, v.ToDoubleVolume() * k, UnitLabels.Volume(uiUnits));
|
||||
}
|
||||
},
|
||||
{ NAV.VariantDataType.DateTime, (v, _) => v.ToDateTime().ToString(CultureInfo.InvariantCulture) },
|
||||
{ NAV.VariantDataType.NamedConstant, (v, _) => v.ToNamedConstant().DisplayName },
|
||||
{ NAV.VariantDataType.None, (_, _) => null },
|
||||
{ NAV.VariantDataType.Point2D, (_, _) => null },
|
||||
{
|
||||
NAV.VariantDataType.Point3D,
|
||||
(v, t) =>
|
||||
{
|
||||
var uiUnits = uiUnitsCache.Ensure();
|
||||
var k = NAV.UnitConversion.ScaleFactor(t.model, uiUnits);
|
||||
var p = v.ToPoint3D();
|
||||
|
||||
return new Speckle.Objects.Geometry.Point(p.X * k, p.Y * k, p.Z * k, UnitLabels.Linear(uiUnits));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static Dictionary<string, object> NumObj(string name, double value, string units) =>
|
||||
new()
|
||||
{
|
||||
["name"] = name,
|
||||
["value"] = value,
|
||||
["units"] = units
|
||||
};
|
||||
}
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.Services;
|
||||
|
||||
[GenerateAutoInterface]
|
||||
public class SharedGeometryStore : ISharedGeometryStore
|
||||
{
|
||||
private readonly HashSet<Base> _geometries = new();
|
||||
private readonly Dictionary<string, Base> _geometriesByApplicationId = new();
|
||||
private readonly object _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only collection of all stored geometries.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<Base> Geometries
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _geometries.ToList().AsReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a geometry to the store if it doesn't already exist.
|
||||
/// </summary>
|
||||
/// <param name="geometry">The geometry to add.</param>
|
||||
/// <returns>True if the geometry was added, false if it already existed.</returns>
|
||||
public bool Add(Base geometry)
|
||||
{
|
||||
if (geometry == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(geometry));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(geometry.applicationId))
|
||||
{
|
||||
throw new ArgumentException("Geometry must have an applicationId for deduplication", nameof(geometry));
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (geometry.applicationId != null && _geometriesByApplicationId.ContainsKey(geometry.applicationId))
|
||||
{
|
||||
return false; // Already exists
|
||||
}
|
||||
|
||||
_geometries.Add(geometry);
|
||||
if (geometry.applicationId != null)
|
||||
{
|
||||
_geometriesByApplicationId[geometry.applicationId] = geometry;
|
||||
}
|
||||
|
||||
return true; // Added successfully
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a geometry with the specified application ID already exists in the store.
|
||||
/// </summary>
|
||||
/// <param name="applicationId">The application ID to check for.</param>
|
||||
/// <returns>True if a geometry with the application ID exists, false otherwise.</returns>
|
||||
public bool Contains(string applicationId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(applicationId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
var contains = _geometriesByApplicationId.ContainsKey(applicationId);
|
||||
|
||||
return contains;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all stored geometries for a new conversion session.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_geometries.Clear();
|
||||
_geometriesByApplicationId.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Autodesk.Navisworks.Api.Interop;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using static Autodesk.Navisworks.Api.Interop.LcUOption;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.Services;
|
||||
|
||||
[GenerateAutoInterface]
|
||||
public class UiUnitsCache : IUiUnitsCache
|
||||
{
|
||||
private NAV.Units? _ui;
|
||||
|
||||
public NAV.Units Ensure()
|
||||
{
|
||||
if (_ui.HasValue)
|
||||
{
|
||||
return _ui.Value;
|
||||
}
|
||||
|
||||
UiUnitsUtil.TryGetUiLinearUnits(out var ui);
|
||||
_ui = ui;
|
||||
return _ui.Value;
|
||||
}
|
||||
|
||||
public void Reset() => _ui = null;
|
||||
}
|
||||
|
||||
public static class UiUnitsUtil
|
||||
{
|
||||
// disp_units: 0=linear_format
|
||||
public static bool TryGetUiLinearUnits(out NAV.Units uiUnits)
|
||||
{
|
||||
using var opt = new LcUOptionLock();
|
||||
var root = GetRoot(opt);
|
||||
var disp = root.GetSubOptions("interface").GetSubOptions("disp_units");
|
||||
|
||||
int code = -1;
|
||||
|
||||
using var v = new NAV.VariantData();
|
||||
disp.GetValue(0, v);
|
||||
var s = v.ToString();
|
||||
var colon = s.LastIndexOf(':');
|
||||
var open = s.IndexOf('(', colon + 1);
|
||||
if (colon >= 0 && open > colon && !int.TryParse(s.Substring(colon + 1, open - colon - 1), out code))
|
||||
{
|
||||
code = -1;
|
||||
}
|
||||
|
||||
uiUnits = code switch
|
||||
{
|
||||
0 => NAV.Units.Kilometers,
|
||||
1 => NAV.Units.Meters,
|
||||
2 => NAV.Units.Centimeters,
|
||||
3 => NAV.Units.Millimeters,
|
||||
4 => NAV.Units.Micrometers,
|
||||
5 => NAV.Units.Miles,
|
||||
6 => NAV.Units.Miles,
|
||||
7 => NAV.Units.Yards,
|
||||
8 => NAV.Units.Yards,
|
||||
9 => NAV.Units.Feet,
|
||||
10 => NAV.Units.Feet,
|
||||
11 => NAV.Units.Feet,
|
||||
12 => NAV.Units.Inches,
|
||||
13 => NAV.Units.Inches,
|
||||
14 => NAV.Units.Mils,
|
||||
15 => NAV.Units.Microinches,
|
||||
_ => NAV.Units.Meters
|
||||
};
|
||||
|
||||
return code >= 0;
|
||||
}
|
||||
}
|
||||
+33
-34
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
@@ -9,38 +9,37 @@
|
||||
<Import_RootNamespace>Speckle.Converters.NavisworksShared</Import_RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\ClassPropertiesExtractor.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\DisplayValueExtractor.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\ModelPropertiesExtractor.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\PropertySetsExtractor.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\RevitBuiltInCategoryExtractor.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataHandlers\BasePropertyHandler.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataHandlers\HierarchicalPropertyHandler.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataHandlers\IPropertyHandler.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataHandlers\StandardPropertyHandler.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DependencyInjection\NavisworksConverterServiceRegistration.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\ArrayExtensions.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Geometries\Primitives.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Geometries\TransformMatrix.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ColorConverter.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ComScope.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\HierarchyHelper.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ElementSelectionHelper.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\PrimitiveProcessor.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\PropertyHelpers.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)GlobalUsing.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\GeometryHelpers.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)PathConstants.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Services\SharedGeometryStores.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Services\InstanceStoreManager.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\GeometryToSpeckleConverter.cs"/>
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Models\"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Services\NavisworksToSpeckleUnitConverter.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Settings\ConversionModes.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Settings\NavisworksConversionSettings.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Settings\NavisworksConversionSettingsFactory.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\NavisworksRootToSpeckleConverter.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\BoundingBoxToSpeckleRawConverter.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\TopLevel\ModelItemTopLevelConverterToSpeckle.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\ClassPropertiesExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\DisplayValueExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\ModelPropertiesExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\PropertySetsExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\RevitBuiltInCategoryExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataHandlers\BasePropertyHandler.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataHandlers\HierarchicalPropertyHandler.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataHandlers\IPropertyHandler.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataHandlers\StandardPropertyHandler.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DependencyInjection\NavisworksConverterServiceRegistration.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\ArrayExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Geometries\Primitives.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Geometries\TransformMatrix.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ColorConverter.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\HierarchyHelper.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ElementSelectionHelper.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\PrimitiveProcessor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\PropertyHelpers.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)GlobalUsing.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\GeometryHelpers.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)PathConstants.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\GeometryToSpeckleConverter.cs" />
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Models\" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Services\NavisworksToSpeckleUnitConverter.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Settings\ConversionModes.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Settings\NavisworksConversionSettings.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Settings\NavisworksConversionSettingsFactory.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\NavisworksRootToSpeckleConverter.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\BoundingBoxToSpeckleRawConverter.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\TopLevel\ModelItemTopLevelConverterToSpeckle.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Services\PropertyConversion.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Services\UIUnits.cs"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
+70
-585
@@ -1,15 +1,11 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Autodesk.Navisworks.Api.Interop.ComApi;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Converter.Navisworks.Extensions;
|
||||
using Speckle.Converter.Navisworks.Geometry;
|
||||
using Speckle.Converter.Navisworks.Helpers;
|
||||
using Speckle.Converter.Navisworks.Services;
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.DoubleNumerics;
|
||||
using Speckle.Objects.Geometry;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Instances;
|
||||
using ComApiBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.ToSpeckle;
|
||||
@@ -25,59 +21,23 @@ namespace Speckle.Converter.Navisworks.ToSpeckle;
|
||||
/// 3. Process each InwOaFragment3 to generate primitives
|
||||
/// 4. Convert those primitives to Speckle geometry with appropriate transforms
|
||||
/// </summary>
|
||||
public class GeometryToSpeckleConverter(
|
||||
NavisworksConversionSettings settings,
|
||||
InstanceStoreManager instanceStoreManager,
|
||||
ILogger<GeometryToSpeckleConverter> logger
|
||||
)
|
||||
public class GeometryToSpeckleConverter
|
||||
{
|
||||
private readonly NavisworksConversionSettings _settings =
|
||||
settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
|
||||
private readonly bool _isUpright = settings.Derived.IsUpright;
|
||||
private readonly SafeVector _transformVector = settings.Derived.TransformVector;
|
||||
private readonly NavisworksConversionSettings _settings;
|
||||
private readonly bool _isUpright;
|
||||
private readonly SafeVector _transformVector;
|
||||
private const double SCALE = 1.0; // Default scale factor
|
||||
|
||||
private readonly InstanceStoreManager _instanceStoreManager =
|
||||
instanceStoreManager ?? throw new ArgumentNullException(nameof(instanceStoreManager));
|
||||
|
||||
private readonly ILogger<GeometryToSpeckleConverter> _logger =
|
||||
logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
// Fragment ID cache for performance optimization
|
||||
private readonly ConcurrentDictionary<int, string> _fragmentIdCache = new();
|
||||
|
||||
// Geometry cache for repeated items
|
||||
private readonly ConcurrentDictionary<string, List<Base>> _geometryCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Clears all internal caches. Should be called when starting a new conversion session.
|
||||
/// </summary>
|
||||
public void ClearCaches()
|
||||
public GeometryToSpeckleConverter(NavisworksConversionSettings settings)
|
||||
{
|
||||
_fragmentIdCache.Clear();
|
||||
_geometryCache.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets cache statistics for performance monitoring.
|
||||
/// </summary>
|
||||
/// <returns>A record containing cache hit counts and sizes</returns>
|
||||
public (int FragmentIdCacheSize, int GeometryCacheSize, double CacheMemoryEstimateMB) GetCacheStatistics()
|
||||
{
|
||||
var fragmentCacheSize = _fragmentIdCache.Count;
|
||||
var geometryCacheSize = _geometryCache.Count;
|
||||
|
||||
// Rough memory estimate (fragment IDs ~50 bytes, geometry objects ~10KB average)
|
||||
var estimatedMemoryMb = (fragmentCacheSize * 50 + geometryCacheSize * 10240) / (1024.0 * 1024.0);
|
||||
|
||||
return (fragmentCacheSize, geometryCacheSize, Math.Round(estimatedMemoryMb, 2));
|
||||
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
_isUpright = settings.Derived.IsUpright;
|
||||
_transformVector = settings.Derived.TransformVector;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a ModelItem's geometry to Speckle display geometry by accessing the underlying COM objects.
|
||||
/// When path.Fragments().Count > 1, extracts untransformed base geometry once, stores in SharedGeometryStore,
|
||||
/// and returns instance references. Otherwise, returns transformed geometry directly.
|
||||
/// Applies necessary transformations and unit scaling.
|
||||
/// </summary>
|
||||
internal List<Base> Convert(NAV.ModelItem modelItem)
|
||||
{
|
||||
@@ -91,134 +51,59 @@ public class GeometryToSpeckleConverter(
|
||||
return [];
|
||||
}
|
||||
|
||||
// Check geometry cache first
|
||||
var itemId = modelItem.InstanceGuid.ToString();
|
||||
if (_geometryCache.TryGetValue(itemId, out var cachedGeometry))
|
||||
{
|
||||
return cachedGeometry;
|
||||
}
|
||||
|
||||
using var comSelection = new ComScope<InwOpSelection>(ComApiBridge.ToInwOpSelection([modelItem]));
|
||||
var fragmentStack = new Stack<InwOaFragment3>();
|
||||
|
||||
using var paths = new ComScope<InwSelectionPathsColl>(comSelection.Value.Paths());
|
||||
|
||||
var comSelection = ComApiBridge.ToInwOpSelection([modelItem]);
|
||||
try
|
||||
{
|
||||
// Check if this geometry is shared across multiple instances
|
||||
List<Base> result;
|
||||
if (paths.Value.Count > 0)
|
||||
var fragmentStack = new Stack<InwOaFragment3>();
|
||||
var paths = comSelection.Paths();
|
||||
try
|
||||
{
|
||||
var firstPath = paths.Value.Cast<InwOaPath>().First();
|
||||
var fragmentsCollection = firstPath.Fragments();
|
||||
|
||||
if (fragmentsCollection.Count > 1)
|
||||
// Populate fragment stack with all fragments
|
||||
foreach (InwOaPath path in paths)
|
||||
{
|
||||
// Shared geometry - extract base geometry once and return instance reference
|
||||
result = ProcessSharedGeometry(paths.Value, fragmentStack);
|
||||
CollectFragments(path, fragmentStack);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Single instance geometry - process normally with transforms
|
||||
foreach (InwOaPath path in paths.Value)
|
||||
{
|
||||
CollectFragments(path, fragmentStack);
|
||||
}
|
||||
|
||||
result = ProcessFragments(fragmentStack, paths.Value, true);
|
||||
return ProcessFragments(fragmentStack, paths);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (paths != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(paths);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = [];
|
||||
}
|
||||
|
||||
// Cache the result for future use
|
||||
if (result.Count > 0)
|
||||
{
|
||||
_geometryCache.TryAdd(itemId, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (COMException ex)
|
||||
finally
|
||||
{
|
||||
_logger.LogError(ex, "COM exception converting geometry for ModelItem {ItemId}", itemId);
|
||||
return [];
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Invalid operation converting geometry for ModelItem {ItemId}", itemId);
|
||||
return [];
|
||||
if (comSelection != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(comSelection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void CollectFragments(InwOaPath path, Stack<InwOaFragment3> fragmentStack)
|
||||
{
|
||||
using var fragments = new ComScope<InwNodeFragsColl>(path.Fragments());
|
||||
|
||||
foreach (var fragment in fragments.Value.OfType<InwOaFragment3>())
|
||||
var fragments = path.Fragments();
|
||||
foreach (var fragment in fragments.OfType<InwOaFragment3>())
|
||||
{
|
||||
if (ValidateFragmentPath(fragment, path))
|
||||
if (fragment.path?.ArrayData is not Array pathData1 || path.ArrayData is not Array pathData2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var pathArray1 = pathData1.ToArray<int>();
|
||||
var pathArray2 = pathData2.ToArray<int>();
|
||||
|
||||
if (pathArray1.Length == pathArray2.Length && pathArray1.SequenceEqual(pathArray2))
|
||||
{
|
||||
fragmentStack.Push(fragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Base> ProcessSharedGeometry(InwSelectionPathsColl paths, Stack<InwOaFragment3> fragmentStack)
|
||||
{
|
||||
// Generate ID from fragment data for shared geometry
|
||||
var fragmentId = GenerateFragmentId(paths);
|
||||
|
||||
if (string.IsNullOrEmpty(fragmentId))
|
||||
{
|
||||
// Fallback to normal processing if we can't generate ID
|
||||
foreach (InwOaPath path in paths)
|
||||
{
|
||||
CollectFragments(path, fragmentStack);
|
||||
}
|
||||
|
||||
return ProcessFragments(fragmentStack, paths, true);
|
||||
}
|
||||
|
||||
// Check if shared geometry already exists in store
|
||||
if (_instanceStoreManager.ContainsSharedGeometry(fragmentId))
|
||||
{
|
||||
// Return instance reference to existing geometry
|
||||
return CreateInstanceReference(fragmentId, paths);
|
||||
}
|
||||
|
||||
// Extract untransformed base geometry
|
||||
foreach (InwOaPath path in paths)
|
||||
{
|
||||
CollectFragments(path, fragmentStack);
|
||||
}
|
||||
|
||||
var baseGeometry = ExtractUntransformedGeometry(fragmentStack);
|
||||
|
||||
if (baseGeometry == null)
|
||||
{
|
||||
return ProcessFragments(fragmentStack, paths);
|
||||
}
|
||||
|
||||
// Store both the geometry definition and create the instance definition proxy
|
||||
if (!_instanceStoreManager.AddSharedGeometry(fragmentId, baseGeometry))
|
||||
{
|
||||
return ProcessFragments(fragmentStack, paths);
|
||||
}
|
||||
|
||||
// Return instance reference to the newly stored geometry
|
||||
return CreateInstanceReference(fragmentId, paths);
|
||||
|
||||
// Fallback to normal processing if store failed
|
||||
}
|
||||
|
||||
private List<Base> ProcessFragments(
|
||||
Stack<InwOaFragment3> fragmentStack,
|
||||
InwSelectionPathsColl paths,
|
||||
bool isSingleObject = false
|
||||
)
|
||||
private List<Base> ProcessFragments(Stack<InwOaFragment3> fragmentStack, InwSelectionPathsColl paths)
|
||||
{
|
||||
var callbackListeners = new List<PrimitiveProcessor>();
|
||||
|
||||
@@ -226,52 +111,41 @@ public class GeometryToSpeckleConverter(
|
||||
{
|
||||
var processor = new PrimitiveProcessor(_isUpright);
|
||||
|
||||
using var pathFragments = new ComScope<InwNodeFragsColl>(path.Fragments());
|
||||
var fragmentCount = pathFragments.Value.Count;
|
||||
|
||||
foreach (var fragment in fragmentStack)
|
||||
{
|
||||
try
|
||||
if (!ValidateFragmentPath(fragment, path))
|
||||
{
|
||||
var matrix = fragment.GetLocalToWorldMatrix();
|
||||
var transform = matrix as InwLTransform3f3;
|
||||
if (transform?.Matrix is not Array matrixArray)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
double[] makeNoChange = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
||||
double[] transformMatrix = ConvertArrayToDouble(matrixArray);
|
||||
|
||||
if (isSingleObject || fragmentCount == 1)
|
||||
{
|
||||
// Apply coordinate system transformation
|
||||
processor.LocalToWorldTransformation = transformMatrix;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For multiple objects, process geometry without transforms
|
||||
processor.LocalToWorldTransformation = makeNoChange;
|
||||
}
|
||||
|
||||
fragment.GenerateSimplePrimitives(nwEVertexProperty.eNORMAL, processor);
|
||||
continue;
|
||||
}
|
||||
catch (COMException ex)
|
||||
|
||||
var matrix = fragment.GetLocalToWorldMatrix();
|
||||
var transform = matrix as InwLTransform3f3;
|
||||
if (transform?.Matrix is not Array matrixArray)
|
||||
{
|
||||
_logger.LogWarning(ex, "COM exception processing fragment, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
processor.LocalToWorldTransformation = ConvertArrayToDouble(matrixArray);
|
||||
fragment.GenerateSimplePrimitives(nwEVertexProperty.eNORMAL, processor);
|
||||
}
|
||||
|
||||
callbackListeners.Add(processor);
|
||||
}
|
||||
|
||||
return ProcessGeometries(callbackListeners);
|
||||
var baseGeometries = ProcessGeometries(callbackListeners);
|
||||
|
||||
return baseGeometries;
|
||||
}
|
||||
|
||||
private static bool ValidateFragmentPath(InwOaFragment3 fragment, InwOaPath path) =>
|
||||
fragment.path?.ArrayData is Array fragmentPathData
|
||||
&& path.ArrayData is Array pathData
|
||||
&& IsSameFragmentPath(fragmentPathData, pathData);
|
||||
private static bool ValidateFragmentPath(InwOaFragment3 fragment, InwOaPath path)
|
||||
{
|
||||
if (fragment.path?.ArrayData is not Array fragmentPathData || path.ArrayData is not Array pathData)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsSameFragmentPath(fragmentPathData, pathData);
|
||||
}
|
||||
|
||||
private List<Base> ProcessGeometries(List<PrimitiveProcessor> processors)
|
||||
{
|
||||
@@ -330,8 +204,9 @@ public class GeometryToSpeckleConverter(
|
||||
}
|
||||
|
||||
private List<Line> CreateLines(IReadOnlyList<SafeLine> lines) =>
|
||||
lines
|
||||
.Select(line => new Line
|
||||
(
|
||||
from line in lines
|
||||
select new Line
|
||||
{
|
||||
start = new Point(
|
||||
(line.Start.X + _transformVector.X) * SCALE,
|
||||
@@ -346,395 +221,10 @@ public class GeometryToSpeckleConverter(
|
||||
_settings.Derived.SpeckleUnits
|
||||
),
|
||||
units = _settings.Derived.SpeckleUnits
|
||||
})
|
||||
.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Generates an idempotent ID from fragment path data for shared geometry.
|
||||
/// Uses the path.Fragments() collection to create a reproducible hash.
|
||||
/// </summary>
|
||||
public string GenerateFragmentId(InwSelectionPathsColl paths)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (paths.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
).ToList();
|
||||
|
||||
// Generate a fast hash code for cache lookup
|
||||
var pathsHashCode = GenerateFastPathsHashCode(paths);
|
||||
|
||||
// Check cache first
|
||||
if (_fragmentIdCache.TryGetValue(pathsHashCode, out var cachedId))
|
||||
{
|
||||
return cachedId;
|
||||
}
|
||||
|
||||
var fragmentHashes = new List<string>();
|
||||
|
||||
foreach (InwOaPath path in paths)
|
||||
{
|
||||
var fragments = path.Fragments();
|
||||
|
||||
var fragmentIndex = 0;
|
||||
foreach (InwOaFragment3 fragment in fragments.OfType<InwOaFragment3>())
|
||||
{
|
||||
if (fragment.path?.ArrayData is not Array pathData)
|
||||
{
|
||||
fragmentIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pathData.Length == 0)
|
||||
{
|
||||
fragmentIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Check array rank first - COM arrays might be multidimensional
|
||||
if (pathData.Rank != 1)
|
||||
{
|
||||
// Try simple enumeration fallback
|
||||
var fragmentHashFallback = TrySimpleArrayEnumeration(pathData, fragmentIndex);
|
||||
if (!string.IsNullOrEmpty(fragmentHashFallback))
|
||||
{
|
||||
fragmentHashes.Add(fragmentHashFallback);
|
||||
}
|
||||
|
||||
fragmentIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var lowerBound = pathData.GetLowerBound(0);
|
||||
var upperBound = pathData.GetUpperBound(0);
|
||||
|
||||
var arrayLength = upperBound - lowerBound + 1;
|
||||
var pathInts = new int[arrayLength];
|
||||
|
||||
for (int i = lowerBound; i <= upperBound; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = pathData.GetValue(i);
|
||||
var arrayIndex = i - lowerBound;
|
||||
pathInts[arrayIndex] = System.Convert.ToInt32(value);
|
||||
}
|
||||
catch (Exception ex) when (ex is InvalidCastException or OverflowException or FormatException)
|
||||
{
|
||||
// Skip invalid array values
|
||||
}
|
||||
}
|
||||
|
||||
var fragmentHash = string.Join("_", pathInts);
|
||||
fragmentHashes.Add(fragmentHash);
|
||||
}
|
||||
catch (Exception ex) when (ex is InvalidCastException or IndexOutOfRangeException or RankException)
|
||||
{
|
||||
// Try simple enumeration as fallback
|
||||
var fragmentHash = TrySimpleArrayEnumeration(pathData, fragmentIndex);
|
||||
if (!string.IsNullOrEmpty(fragmentHash))
|
||||
{
|
||||
fragmentHashes.Add(fragmentHash);
|
||||
}
|
||||
|
||||
fragmentIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
fragmentIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
string fragmentId;
|
||||
if (fragmentHashes.Count > 0)
|
||||
{
|
||||
// Sort to ensure consistent ordering
|
||||
fragmentHashes.Sort();
|
||||
var rawData = string.Join("__", fragmentHashes);
|
||||
fragmentId = HashRawData(rawData);
|
||||
}
|
||||
else
|
||||
{
|
||||
fragmentId = string.Empty;
|
||||
}
|
||||
|
||||
// Cache the result for future use
|
||||
if (!string.IsNullOrEmpty(fragmentId))
|
||||
{
|
||||
_fragmentIdCache.TryAdd(pathsHashCode, fragmentId);
|
||||
}
|
||||
|
||||
return fragmentId;
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (ex
|
||||
is InvalidCastException
|
||||
or IndexOutOfRangeException
|
||||
or OverflowException
|
||||
or ArgumentException
|
||||
or COMException
|
||||
)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to generate fragment ID due to {ExceptionType}", ex.GetType().Name);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple array enumeration fallback when bounds access fails.
|
||||
/// Tries to enumerate array by simple sequential access.
|
||||
/// </summary>
|
||||
private string TrySimpleArrayEnumeration(Array pathData, int fragmentIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
var values = new List<string>();
|
||||
var maxAttempts = Math.Min(pathData.Length, 20); // Limit attempts to avoid infinite loops
|
||||
|
||||
for (int i = 0; i < maxAttempts; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = pathData.GetValue(i);
|
||||
var convertedValue = System.Convert.ToInt32(value);
|
||||
values.Add(convertedValue.ToString());
|
||||
_logger.LogDebug("Fragment {FragmentIndex} simple enum[{Index}] = {Value}", fragmentIndex, i, convertedValue);
|
||||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
// Hit the end of valid indices
|
||||
_logger.LogDebug("Fragment {FragmentIndex} reached end of array at index {Index}", fragmentIndex, i);
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (ex is InvalidCastException or OverflowException or FormatException or ArgumentException)
|
||||
{
|
||||
_logger.LogWarning(ex, "Fragment {FragmentIndex} failed to convert value at index {Index}", fragmentIndex, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (values.Count <= 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var hash = string.Join("_", values);
|
||||
return hash;
|
||||
}
|
||||
catch (Exception ex) when (ex is COMException or InvalidCastException or ArgumentException)
|
||||
{
|
||||
_logger.LogWarning(ex, "Simple enumeration failed for fragment {FragmentIndex}", fragmentIndex);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a fast hash code for paths collection for caching purposes
|
||||
/// </summary>
|
||||
private static int GenerateFastPathsHashCode(InwSelectionPathsColl paths)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 23 + paths.Count;
|
||||
|
||||
var processed = 0;
|
||||
foreach (InwOaPath path in paths)
|
||||
{
|
||||
if (path.ArrayData is Array { Length: > 0 } pathData)
|
||||
{
|
||||
// Sample first few elements for performance
|
||||
var sampleSize = Math.Min(pathData.Length, 4);
|
||||
for (int i = 0; i < sampleSize; i++)
|
||||
{
|
||||
hash = hash * 23 + (pathData.GetValue(i)?.GetHashCode() ?? 0);
|
||||
}
|
||||
|
||||
hash = hash * 23 + pathData.Length;
|
||||
}
|
||||
|
||||
// Limit processing for performance
|
||||
if (++processed >= 8)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a fast hash of the raw fragment data using .NET's HashCode struct.
|
||||
/// For performance, we use HashCode instead of SHA256 for fragment IDs.
|
||||
/// </summary>
|
||||
/// <returns>Hash as hex string</returns>
|
||||
private static string HashRawData(string rawData)
|
||||
{
|
||||
var hashCode = rawData.GetHashCode();
|
||||
return hashCode.ToString("X8");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts untransformed base geometry from fragments.
|
||||
/// This geometry will be stored once and referenced by instances.
|
||||
/// </summary>
|
||||
private Base? ExtractUntransformedGeometry(Stack<InwOaFragment3> fragmentStack)
|
||||
{
|
||||
var processor = new PrimitiveProcessor(_isUpright);
|
||||
|
||||
// Process fragments without transforms to get base geometry
|
||||
foreach (var fragment in fragmentStack)
|
||||
{
|
||||
// Use identity transform to get untransformed geometry
|
||||
double[] identityTransform = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
||||
processor.LocalToWorldTransformation = identityTransform;
|
||||
|
||||
fragment.GenerateSimplePrimitives(nwEVertexProperty.eNORMAL, processor);
|
||||
}
|
||||
|
||||
// Create mesh from untransformed geometry
|
||||
return processor.Triangles.Count > 0 ? CreateMesh(processor.Triangles) : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance reference to shared geometry stored in the InstanceStoreManager.
|
||||
/// This is returned instead of full geometry for shared instances.
|
||||
/// </summary>
|
||||
private List<Base> CreateInstanceReference(string fragmentId, InwSelectionPathsColl paths)
|
||||
{
|
||||
var transform = ExtractInstanceTransform(paths);
|
||||
|
||||
var instanceReference = new InstanceProxy
|
||||
{
|
||||
definitionId = $"def_{fragmentId}",
|
||||
transform = transform,
|
||||
units = _settings.Derived.SpeckleUnits,
|
||||
maxDepth = 0,
|
||||
applicationId = Guid.NewGuid().ToString()
|
||||
};
|
||||
|
||||
return [instanceReference];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the transform matrix from the first path's fragments for instance placement.
|
||||
/// </summary>
|
||||
private Matrix4x4 ExtractInstanceTransform(InwSelectionPathsColl paths)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (paths.Count == 0)
|
||||
{
|
||||
return new Matrix4x4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
var firstPath = paths.Cast<InwOaPath>().First();
|
||||
using var fragments = new ComScope<InwNodeFragsColl>(firstPath.Fragments());
|
||||
|
||||
if (fragments.Value.Count == 0)
|
||||
{
|
||||
return new Matrix4x4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
var fragmentStack = new Stack<InwOaFragment3>();
|
||||
// Get the first fragment's transform matrix
|
||||
foreach (var frag in fragments.Value.OfType<InwOaFragment3>())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (frag.path?.ArrayData is not Array pathData1 || firstPath.ArrayData is not Array pathData2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use IsSameFragmentPath for consistency and performance
|
||||
if (IsSameFragmentPath(pathData1, pathData2))
|
||||
{
|
||||
fragmentStack.Push(frag);
|
||||
}
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "COM exception accessing fragment path data, skipping fragment");
|
||||
}
|
||||
}
|
||||
|
||||
if (fragmentStack.Count == 0)
|
||||
{
|
||||
_logger.LogWarning("No valid fragments found for transform extraction");
|
||||
return new Matrix4x4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
var fragment = fragmentStack.First();
|
||||
var matrix = fragment.GetLocalToWorldMatrix();
|
||||
|
||||
if (matrix is InwLTransform3f3 { Matrix: Array matrixArray })
|
||||
{
|
||||
var transformArray = ConvertArrayToDouble(matrixArray);
|
||||
|
||||
// Apply coordinate system transformation
|
||||
var transformedMatrix = ApplyCoordinateTransform(transformArray);
|
||||
|
||||
var newMatrix = new Matrix4x4(
|
||||
transformedMatrix[0],
|
||||
transformedMatrix[1],
|
||||
transformedMatrix[2],
|
||||
transformedMatrix[3],
|
||||
transformedMatrix[4],
|
||||
transformedMatrix[5],
|
||||
transformedMatrix[6],
|
||||
transformedMatrix[7],
|
||||
transformedMatrix[8],
|
||||
transformedMatrix[9],
|
||||
transformedMatrix[10],
|
||||
transformedMatrix[11],
|
||||
transformedMatrix[12],
|
||||
transformedMatrix[13],
|
||||
transformedMatrix[14],
|
||||
transformedMatrix[15]
|
||||
);
|
||||
|
||||
return Matrix4x4.Transpose(newMatrix);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (ex
|
||||
is COMException
|
||||
or InvalidCastException
|
||||
or IndexOutOfRangeException
|
||||
or ArgumentException
|
||||
or NullReferenceException
|
||||
)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
ex,
|
||||
"Failed to extract instance transform ({ExceptionType}) - returning identity matrix",
|
||||
ex.GetType().Name
|
||||
);
|
||||
}
|
||||
|
||||
return new Matrix4x4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
private double[] ApplyCoordinateTransform(double[] matrixArray)
|
||||
{
|
||||
// Apply scale and coordinate transformation
|
||||
var result = new double[16];
|
||||
Array.Copy(matrixArray, result, 16);
|
||||
|
||||
// Apply translation transformation
|
||||
result[12] = (result[12] + _transformVector.X) * SCALE;
|
||||
result[13] = (result[13] + _transformVector.Y) * SCALE;
|
||||
result[14] = (result[14] + _transformVector.Z) * SCALE;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static double[] ConvertArrayToDouble(Array arr)
|
||||
private static double[]? ConvertArrayToDouble(Array arr)
|
||||
{
|
||||
if (arr.Rank != 1)
|
||||
{
|
||||
@@ -751,10 +241,5 @@ public class GeometryToSpeckleConverter(
|
||||
}
|
||||
|
||||
private static bool IsSameFragmentPath(Array a1, Array a2) =>
|
||||
a1.Length == a2.Length
|
||||
&& (
|
||||
a1.Length > 4
|
||||
? a1.Cast<object>().SequenceEqual(a2.Cast<object>())
|
||||
: !a1.Cast<object>().Where((_, i) => !Equals(a1.GetValue(i), a2.GetValue(i))).Any()
|
||||
);
|
||||
a1.Length == a2.Length && a1.Cast<int>().SequenceEqual(a2.Cast<int>());
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converter.Navisworks.ToSpeckle.PropertyHandlers;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.Common.Objects;
|
||||
|
||||
@@ -32,4 +32,64 @@ public static class ElementExtensions
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
public static IEnumerable<ElementId> GetKnownChildrenElements(this Element element) =>
|
||||
element switch
|
||||
{
|
||||
Wall wall => GetWallChildren(wall),
|
||||
FootPrintRoof roof => GetFootPrintRoofChildren(roof),
|
||||
DBA.Railing railing => GetRailingChildren(railing),
|
||||
_ => []
|
||||
};
|
||||
|
||||
private static IEnumerable<ElementId> GetWallChildren(Wall wall)
|
||||
{
|
||||
if (wall.CurtainGrid is CurtainGrid grid)
|
||||
{
|
||||
foreach (var id in grid.GetMullionIds())
|
||||
{
|
||||
yield return id;
|
||||
}
|
||||
|
||||
foreach (var id in grid.GetPanelIds())
|
||||
{
|
||||
yield return id;
|
||||
}
|
||||
}
|
||||
else if (wall.IsStackedWall)
|
||||
{
|
||||
foreach (var id in wall.GetStackedWallMemberIds())
|
||||
{
|
||||
yield return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<ElementId> GetFootPrintRoofChildren(FootPrintRoof footPrintRoof)
|
||||
{
|
||||
if (footPrintRoof.CurtainGrids is { } grids)
|
||||
{
|
||||
foreach (CurtainGrid grid in grids)
|
||||
{
|
||||
foreach (var id in grid.GetMullionIds())
|
||||
{
|
||||
yield return id;
|
||||
}
|
||||
|
||||
foreach (var id in grid.GetPanelIds())
|
||||
{
|
||||
yield return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<ElementId> GetRailingChildren(DBA.Railing railing)
|
||||
{
|
||||
// TODO: Consider adding HandRail support (railing.GetHandRails())
|
||||
if (railing.TopRail != ElementId.InvalidElementId)
|
||||
{
|
||||
yield return railing.TopRail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,15 +87,6 @@ public sealed class DisplayValueExtractor
|
||||
// curtain and stacked walls should have their display values in their children
|
||||
case DB.Wall wall:
|
||||
return wall.CurtainGrid is not null || wall.IsStackedWall ? new() : GetGeometryDisplayValue(element);
|
||||
// railings should also include toprail which need to be retrieved separately
|
||||
case DBA.Railing railing:
|
||||
List<DisplayValueResult> railingDisplay = GetGeometryDisplayValue(railing);
|
||||
if (railing.TopRail != DB.ElementId.InvalidElementId)
|
||||
{
|
||||
var topRail = _converterSettings.Current.Document.GetElement(railing.TopRail);
|
||||
railingDisplay.AddRange(GetGeometryDisplayValue(topRail));
|
||||
}
|
||||
return railingDisplay;
|
||||
|
||||
// POC: footprint roofs can have curtain walls in them. Need to check if they can also have non-curtain wall parts, bc currently not skipping anything.
|
||||
// case DB.FootPrintRoof footPrintRoof:
|
||||
@@ -200,20 +191,51 @@ public sealed class DisplayValueExtractor
|
||||
);
|
||||
}
|
||||
|
||||
// add rest of geometry (always without transform)
|
||||
// transform curves, polylines, and points to world coordinates before conversion.
|
||||
// Unlike meshes/solids which are proxified with transform matrices, these geometry
|
||||
// types must have their final world coordinates baked directly into their geometry.
|
||||
foreach (var curve in collections.Curves)
|
||||
{
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(GetCurveDisplayValue(curve)));
|
||||
if (localToWorld is not null)
|
||||
{
|
||||
using var transformedCurve = curve.CreateTransformed(localToWorld);
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(GetCurveDisplayValue(transformedCurve)));
|
||||
}
|
||||
else
|
||||
{
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(GetCurveDisplayValue(curve)));
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Creating new polyline/point instances for transformation isn't ideal for perf,
|
||||
// but Revit API doesn't provide in-place transform methods. Trade-off is acceptable since
|
||||
// family instances typically don't have massive numbers of raw polylines/points in their geometry.
|
||||
foreach (var polyline in collections.Polylines)
|
||||
{
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(_polylineConverter.Convert(polyline)));
|
||||
if (localToWorld is not null)
|
||||
{
|
||||
var coords = polyline.GetCoordinates();
|
||||
var transformedCoords = coords.Select(coord => localToWorld.OfPoint(coord)).ToList();
|
||||
using var transformedPolyline = DB.PolyLine.Create(transformedCoords);
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(_polylineConverter.Convert(transformedPolyline)));
|
||||
}
|
||||
else
|
||||
{
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(_polylineConverter.Convert(polyline)));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var point in collections.Points)
|
||||
{
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(_pointConverter.Convert(point)));
|
||||
if (localToWorld is not null)
|
||||
{
|
||||
using var transformedPoint = DB.Point.Create(localToWorld.OfPoint(point.Coord));
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(_pointConverter.Convert(transformedPoint)));
|
||||
}
|
||||
else
|
||||
{
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(_pointConverter.Convert(point)));
|
||||
}
|
||||
}
|
||||
|
||||
return displayValue;
|
||||
@@ -355,7 +377,8 @@ public sealed class DisplayValueExtractor
|
||||
collections.Meshes.Add(mesh);
|
||||
break;
|
||||
|
||||
//Note, we're not applying transforms to curves/polylines/points because ProcessGeometryCollections expects them in world coordinates
|
||||
// curves, polylines, and points are transformed to world space in ProcessGeometryCollections,
|
||||
// not here, because they cannot be proxified like meshes.
|
||||
case DB.Curve curve:
|
||||
collections.Curves.Add(curve);
|
||||
break;
|
||||
@@ -582,8 +605,9 @@ public sealed class DisplayValueExtractor
|
||||
/// and reduce the risk of parameter ordering errors.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="Solids"/> and <see cref="Meshes"/> potentially in local coordinate space.
|
||||
/// For now, <see cref="Curves"/>, <see cref="Polylines"/>, <see cref="Points"/> will always be in world space
|
||||
/// <see cref="Solids"/> and <see cref="Meshes"/> are transformed to local coordinate space in SortGeometry.
|
||||
/// <see cref="Curves"/>, <see cref="Polylines"/>, and <see cref="Points"/> remain in their original coordinate space
|
||||
/// and are transformed to world space during processing in ProcessGeometryCollections.
|
||||
/// </remarks>
|
||||
private sealed record GeometryCollections
|
||||
{
|
||||
|
||||
+11
-2
@@ -49,11 +49,20 @@ public class MaterialQuantitiesToSpeckleLite : ITypedConverter<DB.Element, Dicti
|
||||
switch (target)
|
||||
{
|
||||
case DBA.Railing railing:
|
||||
// railings can have subelements including top rails, hand rails, and balusters.
|
||||
// railings can have sub-elements including top rails, handrails, and balusters.
|
||||
// they also do *not* have any materials associated with their category.
|
||||
List<DB.ElementId> railingElementIds = [railing.GetTypeId(), railing.TopRail, .. railing.GetHandRails()];
|
||||
// TopRail is now a separate child element with its own material quantities (see CNX-2806)
|
||||
List<DB.ElementId> railingElementIds = [railing.GetTypeId(), .. railing.GetHandRails()];
|
||||
ProcessMaterialsByElementTypes(railingElementIds, quantities);
|
||||
break;
|
||||
|
||||
case DBA.TopRail topRail:
|
||||
// TopRail/HandRail doesn't expose materials via HasMaterialQuantities
|
||||
// must extract materials from the type parameters instead
|
||||
List<DB.ElementId> railElementIds = [topRail.GetTypeId()];
|
||||
ProcessMaterialsByElementTypes(railElementIds, quantities);
|
||||
break;
|
||||
|
||||
default:
|
||||
ProcessMaterialsByCategory(target, quantities);
|
||||
break;
|
||||
|
||||
+5
-55
@@ -137,61 +137,11 @@ public class ElementTopLevelConverterToSpeckle : IToSpeckleTopLevelConverter
|
||||
|
||||
private IEnumerable<RevitObject> GetElementChildren(DB.Element element)
|
||||
{
|
||||
switch (element)
|
||||
var childrenIds = element.GetKnownChildrenElements();
|
||||
foreach (var childrenId in childrenIds)
|
||||
{
|
||||
case DB.Wall wall:
|
||||
var wallChildren = GetWallChildren(wall);
|
||||
foreach (var child in wallChildren)
|
||||
{
|
||||
yield return child;
|
||||
}
|
||||
break;
|
||||
|
||||
case DB.FootPrintRoof footPrintRoof:
|
||||
var footPrintRoofChildren = GetFootPrintRoofChildren(footPrintRoof);
|
||||
foreach (var child in footPrintRoofChildren)
|
||||
{
|
||||
yield return child;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<RevitObject> GetWallChildren(DB.Wall wall)
|
||||
{
|
||||
List<DB.ElementId> wallChildrenIds = new();
|
||||
if (wall.CurtainGrid is DB.CurtainGrid grid)
|
||||
{
|
||||
wallChildrenIds.AddRange(grid.GetMullionIds());
|
||||
wallChildrenIds.AddRange(grid.GetPanelIds());
|
||||
}
|
||||
else if (wall.IsStackedWall)
|
||||
{
|
||||
wallChildrenIds.AddRange(wall.GetStackedWallMemberIds());
|
||||
}
|
||||
|
||||
foreach (var childId in wallChildrenIds)
|
||||
{
|
||||
yield return Convert(_converterSettings.Current.Document.GetElement(childId));
|
||||
}
|
||||
}
|
||||
|
||||
// Shockingly, roofs can have curtain grids on them. I guess it makes sense: https://en.wikipedia.org/wiki/Louvre_Pyramid
|
||||
private IEnumerable<RevitObject> GetFootPrintRoofChildren(DB.FootPrintRoof footPrintRoof)
|
||||
{
|
||||
List<DB.ElementId> footPrintRoofChildrenIds = new();
|
||||
if (footPrintRoof.CurtainGrids is { } gs)
|
||||
{
|
||||
foreach (DB.CurtainGrid grid in gs)
|
||||
{
|
||||
footPrintRoofChildrenIds.AddRange(grid.GetMullionIds());
|
||||
footPrintRoofChildrenIds.AddRange(grid.GetPanelIds());
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var childId in footPrintRoofChildrenIds)
|
||||
{
|
||||
yield return Convert(_converterSettings.Current.Document.GetElement(childId));
|
||||
var childElement = _converterSettings.Current.Document.GetElement(childrenId);
|
||||
yield return Convert(childElement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +155,7 @@ public class ElementTopLevelConverterToSpeckle : IToSpeckleTopLevelConverter
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is a bit of a code smell. This method is doing to much, "this ... AND this...".
|
||||
/// This is a bit of a code smell. This method is doing too much, "this ... AND this...".
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// But, given a mesh:
|
||||
|
||||
+34
-20
@@ -1,9 +1,11 @@
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.Common.Objects;
|
||||
using Speckle.Converters.Common.ToHost;
|
||||
using Speckle.Objects.Data;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Common.Exceptions;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Instances;
|
||||
|
||||
namespace Speckle.Converters.Rhino.ToHost.TopLevel;
|
||||
|
||||
@@ -27,6 +29,7 @@ public class DataObjectConverter
|
||||
private readonly ITypedConverter<SOG.Region, RG.Hatch> _regionConverter;
|
||||
private readonly ITypedConverter<SOG.SubDX, List<RG.GeometryBase>> _subdConverter;
|
||||
private readonly IConverterSettingsStore<RhinoConversionSettings> _settingsStore;
|
||||
private readonly IDataObjectInstanceRegistry _dataObjectInstanceRegistry;
|
||||
|
||||
public DataObjectConverter(
|
||||
ITypedConverter<SOG.Arc, RG.ArcCurve> arcConverter,
|
||||
@@ -43,7 +46,8 @@ public class DataObjectConverter
|
||||
ITypedConverter<SOG.Polycurve, RG.PolyCurve> polycurveConverter,
|
||||
ITypedConverter<SOG.Region, RG.Hatch> regionConverter,
|
||||
ITypedConverter<SOG.SubDX, List<RG.GeometryBase>> subdConverter,
|
||||
IConverterSettingsStore<RhinoConversionSettings> settingsStore
|
||||
IConverterSettingsStore<RhinoConversionSettings> settingsStore,
|
||||
IDataObjectInstanceRegistry dataObjectInstanceRegistry
|
||||
)
|
||||
{
|
||||
_arcConverter = arcConverter;
|
||||
@@ -61,13 +65,40 @@ public class DataObjectConverter
|
||||
_regionConverter = regionConverter;
|
||||
_subdConverter = subdConverter;
|
||||
_settingsStore = settingsStore;
|
||||
_dataObjectInstanceRegistry = dataObjectInstanceRegistry;
|
||||
}
|
||||
|
||||
public object Convert(Base target) => Convert((DataObject)target);
|
||||
|
||||
private List<RG.GeometryBase> GetConvertedGeometry(Base b)
|
||||
public List<(RG.GeometryBase a, Base b)> Convert(DataObject target)
|
||||
{
|
||||
return b switch
|
||||
var resultPairs = new List<(RG.GeometryBase, Base)>();
|
||||
|
||||
// check if displayValue contains ANY InstanceProxies - register for special handling
|
||||
var instanceProxies = target.displayValue.OfType<InstanceProxy>().ToList();
|
||||
if (instanceProxies.Count > 0)
|
||||
{
|
||||
_dataObjectInstanceRegistry.Register(target.applicationId ?? target.id.NotNull(), target, instanceProxies);
|
||||
|
||||
return resultPairs;
|
||||
}
|
||||
|
||||
// normal display value conversion
|
||||
foreach (var item in target.displayValue)
|
||||
{
|
||||
var converted = GetConvertedGeometry(item);
|
||||
foreach (var geom in converted)
|
||||
{
|
||||
geom.Transform(GetUnitsTransform(item));
|
||||
resultPairs.Add((geom, item));
|
||||
}
|
||||
}
|
||||
|
||||
return resultPairs;
|
||||
}
|
||||
|
||||
private List<RG.GeometryBase> GetConvertedGeometry(Base b) =>
|
||||
b switch
|
||||
{
|
||||
SOG.Arc arc => new() { _arcConverter.Convert(arc) },
|
||||
SOG.BrepX brep => _brepConverter.Convert(brep),
|
||||
@@ -85,23 +116,6 @@ public class DataObjectConverter
|
||||
SOG.SubDX subd => _subdConverter.Convert(subd),
|
||||
_ => throw new ConversionException($"Found unsupported fallback geometry: {b.GetType()}")
|
||||
};
|
||||
}
|
||||
|
||||
public List<(RG.GeometryBase a, Base b)> Convert(DataObject target)
|
||||
{
|
||||
var result = new List<RG.GeometryBase>();
|
||||
foreach (var item in target.displayValue)
|
||||
{
|
||||
var converted = GetConvertedGeometry(item);
|
||||
foreach (var x in converted)
|
||||
{
|
||||
x.Transform(GetUnitsTransform(item));
|
||||
result.Add(x);
|
||||
}
|
||||
}
|
||||
|
||||
return result.Zip(target.displayValue, (a, b) => (a, b)).ToList();
|
||||
}
|
||||
|
||||
private RG.Transform GetUnitsTransform(Base speckleObject)
|
||||
{
|
||||
|
||||
@@ -314,6 +314,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
@@ -329,6 +330,13 @@
|
||||
"speckle.connectors.logging": {
|
||||
"type": "Project"
|
||||
},
|
||||
"speckle.converters.common": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )"
|
||||
}
|
||||
},
|
||||
"speckle.testing": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
@@ -274,6 +275,13 @@
|
||||
"speckle.connectors.logging": {
|
||||
"type": "Project"
|
||||
},
|
||||
"speckle.converters.common": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.2.0, )",
|
||||
@@ -549,6 +557,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
@@ -564,6 +573,13 @@
|
||||
"speckle.connectors.logging": {
|
||||
"type": "Project"
|
||||
},
|
||||
"speckle.converters.common": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.2.0, )",
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
@@ -267,6 +268,13 @@
|
||||
"speckle.connectors.logging": {
|
||||
"type": "Project"
|
||||
},
|
||||
"speckle.converters.common": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.2.0, )",
|
||||
@@ -536,6 +544,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
@@ -544,6 +553,13 @@
|
||||
"speckle.connectors.logging": {
|
||||
"type": "Project"
|
||||
},
|
||||
"speckle.converters.common": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.2.0, )",
|
||||
|
||||
@@ -493,6 +493,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -254,6 +254,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -308,6 +308,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
@@ -316,6 +317,13 @@
|
||||
"speckle.connectors.logging": {
|
||||
"type": "Project"
|
||||
},
|
||||
"speckle.converters.common": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )"
|
||||
}
|
||||
},
|
||||
"speckle.testing": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
|
||||
@@ -22,7 +22,6 @@ public static class ContainerRegistration
|
||||
serviceCollection.AddSingleton<IAccountService, AccountService>();
|
||||
serviceCollection.AddSingleton<IMixPanelManager, MixPanelManager>();
|
||||
serviceCollection.AddSingleton<ISerializationOptions, SerializationOptions>();
|
||||
|
||||
serviceCollection.AddTransient(typeof(ILogger<>), typeof(Logger<>));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,22 +30,22 @@ public class RootObjectUnpacker
|
||||
TryGetLevelProxies(root)
|
||||
);
|
||||
|
||||
public IReadOnlyCollection<TraversalContext> GetObjectsToConvert(Base root) =>
|
||||
private IReadOnlyCollection<TraversalContext> GetObjectsToConvert(Base root) =>
|
||||
_traverseFunction.Traverse(root).Where(obj => obj.Current is not Collection).ToArray();
|
||||
|
||||
public IReadOnlyCollection<ColorProxy>? TryGetColorProxies(Base root) =>
|
||||
private IReadOnlyCollection<ColorProxy>? TryGetColorProxies(Base root) =>
|
||||
TryGetProxies<ColorProxy>(root, ProxyKeys.COLOR);
|
||||
|
||||
public IReadOnlyCollection<RenderMaterialProxy>? TryGetRenderMaterialProxies(Base root) =>
|
||||
private IReadOnlyCollection<RenderMaterialProxy>? TryGetRenderMaterialProxies(Base root) =>
|
||||
TryGetProxies<RenderMaterialProxy>(root, ProxyKeys.RENDER_MATERIAL);
|
||||
|
||||
public IReadOnlyCollection<InstanceDefinitionProxy>? TryGetInstanceDefinitionProxies(Base root) =>
|
||||
private IReadOnlyCollection<InstanceDefinitionProxy>? TryGetInstanceDefinitionProxies(Base root) =>
|
||||
TryGetProxies<InstanceDefinitionProxy>(root, ProxyKeys.INSTANCE_DEFINITION);
|
||||
|
||||
public IReadOnlyCollection<GroupProxy>? TryGetGroupProxies(Base root) =>
|
||||
private IReadOnlyCollection<GroupProxy>? TryGetGroupProxies(Base root) =>
|
||||
TryGetProxies<GroupProxy>(root, ProxyKeys.GROUP);
|
||||
|
||||
public IReadOnlyCollection<LevelProxy>? TryGetLevelProxies(Base root) =>
|
||||
private IReadOnlyCollection<LevelProxy>? TryGetLevelProxies(Base root) =>
|
||||
TryGetProxies<LevelProxy>(root, ProxyKeys.LEVEL);
|
||||
|
||||
public (
|
||||
@@ -59,11 +59,11 @@ public class RootObjectUnpacker
|
||||
{
|
||||
if (tc.Current is IInstanceComponent)
|
||||
{
|
||||
instanceComponents.Add(tc);
|
||||
instanceComponents.Add(tc); // handles actual blocks / instances
|
||||
}
|
||||
else
|
||||
{
|
||||
atomicObjects.Add(tc);
|
||||
atomicObjects.Add(tc); // handles DataObject which INCLUDES DataObject with proxified displayValue(s)
|
||||
}
|
||||
|
||||
if (tc.Current is DataObject dataObject)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<ProjectReference Include="..\Speckle.Connectors.Logging\Speckle.Connectors.Logging.csproj" />
|
||||
<ProjectReference Include="..\Speckle.Converters.Common\Speckle.Converters.Common.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net48'">
|
||||
<Reference Include="System.Net.Http" />
|
||||
|
||||
@@ -292,6 +292,13 @@
|
||||
"speckle.connectors.logging": {
|
||||
"type": "Project"
|
||||
},
|
||||
"speckle.converters.common": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.2.0, )",
|
||||
@@ -557,6 +564,13 @@
|
||||
"speckle.connectors.logging": {
|
||||
"type": "Project"
|
||||
},
|
||||
"speckle.converters.common": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.2.0, )",
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using Speckle.Objects.Data;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models.Instances;
|
||||
|
||||
namespace Speckle.Converters.Common.ToHost;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks DataObjects with InstanceProxy display values that need special handling during on load.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In Rhino-land: converter registers these instead of returning geometry, and the instance baker uses this to create
|
||||
/// grouped block instances with proper metadata applied.
|
||||
/// </remarks>
|
||||
public sealed class DataObjectInstanceRegistry : IDataObjectInstanceRegistry
|
||||
{
|
||||
private readonly Dictionary<string, DataObjectInstanceEntry> _entries = new();
|
||||
private readonly Dictionary<string, string> _instanceProxyToDataObject = new();
|
||||
private readonly Dictionary<string, List<string>> _dataObjectToBakedInstances = new();
|
||||
|
||||
public void Register(string dataObjectId, DataObject dataObject, List<InstanceProxy> instanceProxies)
|
||||
{
|
||||
_entries[dataObjectId] = new DataObjectInstanceEntry(dataObject, instanceProxies);
|
||||
|
||||
// track reverse mapping for each proxy
|
||||
foreach (var proxy in instanceProxies)
|
||||
{
|
||||
var proxyId = proxy.applicationId ?? proxy.id.NotNull();
|
||||
_instanceProxyToDataObject[proxyId] = dataObjectId;
|
||||
}
|
||||
|
||||
_dataObjectToBakedInstances[dataObjectId] = new List<string>();
|
||||
}
|
||||
|
||||
public bool IsRegistered(string dataObjectId) => _entries.ContainsKey(dataObjectId);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<string, DataObjectInstanceEntry> GetEntries() => _entries;
|
||||
|
||||
public void LinkInstanceToDataObject(string instanceProxyId, string bakedInstanceId)
|
||||
{
|
||||
if (_instanceProxyToDataObject.TryGetValue(instanceProxyId, out var dataObjectId))
|
||||
{
|
||||
_dataObjectToBakedInstances[dataObjectId].Add(bakedInstanceId);
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> GetInstanceIdsForDataObject(string dataObjectId) =>
|
||||
_dataObjectToBakedInstances.TryGetValue(dataObjectId, out var ids) ? ids : new List<string>();
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_entries.Clear();
|
||||
_instanceProxyToDataObject.Clear();
|
||||
_dataObjectToBakedInstances.Clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Speckle.Objects.Data;
|
||||
using Speckle.Sdk.Models.Instances;
|
||||
|
||||
namespace Speckle.Converters.Common.ToHost;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks DataObjects with InstanceProxy display values that need special handling during instance baking.
|
||||
/// </summary>
|
||||
public interface IDataObjectInstanceRegistry
|
||||
{
|
||||
void Register(string dataObjectId, DataObject dataObject, List<InstanceProxy> instanceProxies);
|
||||
bool IsRegistered(string dataObjectId);
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of data object id to DataObjectInstanceEntry (which holds the DataObject and instance proxies
|
||||
/// that form the display value of that data object).
|
||||
/// </summary>
|
||||
IReadOnlyDictionary<string, DataObjectInstanceEntry> GetEntries();
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Links a baked instance ID back to its parent DataObject.
|
||||
/// Called after instance baking to track which instances belong to which DataObject.
|
||||
/// </summary>
|
||||
void LinkInstanceToDataObject(string instanceProxyId, string bakedInstanceId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all baked instance IDs for a given DataObject.
|
||||
/// </summary>
|
||||
List<string> GetInstanceIdsForDataObject(string dataObjectId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a DataObject with InstanceProxy display values awaiting instance baking.
|
||||
/// </summary>
|
||||
public sealed record DataObjectInstanceEntry(DataObject DataObject, List<InstanceProxy> InstanceProxies);
|
||||
Reference in New Issue
Block a user