Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 604dc00ef0 | |||
| 1c24a81d58 | |||
| b751dd0756 | |||
| 39ba1f8066 | |||
| 31a40931c7 | |||
| 60c1811fa6 | |||
| c68ac2a63d | |||
| dbd35ac31a | |||
| 0deea75204 | |||
| 44bec42198 | |||
| 6fd1f05680 | |||
| 3861da4347 | |||
| 90c38a28a5 | |||
| e4de8c47b5 | |||
| 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, )"
|
||||
|
||||
@@ -2,33 +2,63 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<UseWpf>true</UseWpf>
|
||||
|
||||
<Description>NextGen Speckle Connector for Autodesk Navisworks Manage</Description>
|
||||
<Authors>$(Authors) jonathon@speckle.systems</Authors>
|
||||
<PackageTags>$(PackageTags) connector nwd nwc nwf navisworks manage</PackageTags>
|
||||
|
||||
<PluginBundleTarget>$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Navisworks.bundle</PluginBundleTarget>
|
||||
<PluginVersionContentTarget>$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Navisworks.bundle\Contents\$(NavisworksVersion)</PluginVersionContentTarget>
|
||||
<PluginVersionContentTarget>$(PluginBundleTarget)\Contents\$(NavisworksVersion)</PluginVersionContentTarget>
|
||||
<RootNamespace>Speckle.Connector.Navisworks</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Post Builds -->
|
||||
<ItemGroup>
|
||||
<RibbonFiles Include="$(OutDir)Plugin\NavisworksRibbon.*"/>
|
||||
<ResourceFiles Include="$(OutDir)Resources\**\*.png"/>
|
||||
<ResourceFiles Include="$(OutDir)Resources\**\*.ico"/>
|
||||
<AllFiles Include="$(OutDir)*"/>
|
||||
</ItemGroup>
|
||||
<Target Name="PostBuild"
|
||||
AfterTargets="Build"
|
||||
Condition="'$(OS)' == 'Windows_NT' and '$(NavisworksVersion)' != ''">
|
||||
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="Build" Condition="'$(NavisworksVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
|
||||
<Message Text="Navisworks Version $(NavisworksVersion)" Importance="high"/>
|
||||
|
||||
<RemoveDir Directories="$(PluginVersionContentTarget)" Condition="Exists('$(PluginVersionContentTarget)')"/>
|
||||
|
||||
<Copy SourceFiles="$(OutDir)Plugin\PackageContents.xml" DestinationFolder="$(PluginBundleTarget)\"/>
|
||||
<Copy SourceFiles="@(RibbonFiles)" DestinationFolder="$(PluginVersionContentTarget)\en-US\"/>
|
||||
<Copy SourceFiles="@(ResourceFiles)" DestinationFolder="$(PluginVersionContentTarget)\Resources\"/>
|
||||
|
||||
<Copy SourceFiles="@(AllFiles)" DestinationFolder="$(PluginVersionContentTarget)\" />
|
||||
|
||||
<MakeDir Directories="
|
||||
$(PluginBundleTarget);
|
||||
$(PluginBundleTarget)\Contents;
|
||||
$(PluginVersionContentTarget);
|
||||
$(PluginVersionContentTarget)\en-US;
|
||||
$(PluginVersionContentTarget)\Resources"/>
|
||||
|
||||
<!-- Re-evaluate outputs at execution time -->
|
||||
<ItemGroup>
|
||||
<PackageXml Include="$(OutDir)Plugin\PackageContents.xml"/>
|
||||
<RibbonFiles Include="$(OutDir)Plugin\NavisworksRibbon.*"/>
|
||||
<ResourceFiles Include="$(OutDir)Resources\**\*.png;$(OutDir)Resources\**\*.ico"/>
|
||||
<AllFiles Include="$(OutDir)**\*.*"/>
|
||||
|
||||
<Message Text="AllFiles count: @(AllFiles->Count())" Importance="high"/>
|
||||
<Warning Condition="'@(AllFiles)' == ''" Text="No files in $(OutDir) at PostBuild time."/>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<Copy SourceFiles="@(PackageXml)"
|
||||
DestinationFolder="$(PluginBundleTarget)\"
|
||||
SkipUnchangedFiles="true"/>
|
||||
|
||||
<Copy SourceFiles="@(RibbonFiles)"
|
||||
DestinationFolder="$(PluginVersionContentTarget)\en-US\"
|
||||
SkipUnchangedFiles="true"/>
|
||||
|
||||
<Copy SourceFiles="@(ResourceFiles)"
|
||||
DestinationFiles="@(ResourceFiles->'$(PluginVersionContentTarget)\Resources\%(RecursiveDir)%(Filename)%(Extension)')"
|
||||
SkipUnchangedFiles="true"/>
|
||||
|
||||
<Copy SourceFiles="@(AllFiles)"
|
||||
DestinationFiles="@(AllFiles->'$(PluginVersionContentTarget)\%(RecursiveDir)%(Filename)%(Extension)')"
|
||||
SkipUnchangedFiles="true"/>
|
||||
|
||||
<Message Text="Copied build to $(PluginVersionContentTarget)" Importance="high"/>
|
||||
</Target>
|
||||
|
||||
<Target Name="ValidateNavisworksVersion" BeforeTargets="PostBuild"
|
||||
Condition="'$(NavisworksVersion)' == '' and '$(OS)' == 'Windows_NT'">
|
||||
<Error Text="NavisworksVersion property is required for PostBuild packaging."/>
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
@@ -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, )"
|
||||
|
||||
+1
-1
@@ -15,8 +15,8 @@ using Speckle.Connectors.DUI.Bridge;
|
||||
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.Converter.Navisworks.Settings;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Sdk.Models.GraphTraversal;
|
||||
|
||||
|
||||
+38
-18
@@ -16,7 +16,8 @@ public class NavisworksColorUnpacker(
|
||||
IElementSelectionService selectionService
|
||||
)
|
||||
{
|
||||
private static T Select<T>(RepresentationMode mode, T active, T permanent, T original, T defaultValue) =>
|
||||
private static T SelectByRepresentationMode<T>(
|
||||
RepresentationMode mode, T active, T permanent, T original, T defaultValue) =>
|
||||
mode switch
|
||||
{
|
||||
RepresentationMode.Active => active,
|
||||
@@ -71,14 +72,14 @@ public class NavisworksColorUnpacker(
|
||||
|
||||
using var defaultColor = new NAV.Color(1.0, 1.0, 1.0);
|
||||
|
||||
var representationColor = Select(
|
||||
var representationColor = SelectByRepresentationMode(
|
||||
mode,
|
||||
geometry.ActiveColor,
|
||||
geometry.PermanentColor,
|
||||
geometry.OriginalColor,
|
||||
defaultColor
|
||||
);
|
||||
var colorId = Select(
|
||||
var colorId = SelectByRepresentationMode(
|
||||
mode,
|
||||
$"{geometry.ActiveColor.GetHashCode()}_{geometry.ActiveTransparency}".GetHashCode(),
|
||||
$"{geometry.PermanentColor.GetHashCode()}_{geometry.PermanentTransparency}".GetHashCode(),
|
||||
@@ -124,30 +125,49 @@ public class NavisworksColorUnpacker(
|
||||
var comSelection = ComBridge.ToInwOpSelection([modelItem]);
|
||||
try
|
||||
{
|
||||
foreach (ComApi.InwOaPath path in comSelection.Paths())
|
||||
var pathsCollection = comSelection.Paths();
|
||||
try
|
||||
{
|
||||
GC.KeepAlive(path);
|
||||
|
||||
foreach (ComApi.InwOaFragment3 fragment in path.Fragments())
|
||||
foreach (ComApi.InwOaPath path in pathsCollection)
|
||||
{
|
||||
GC.KeepAlive(fragment);
|
||||
|
||||
fragment.GenerateSimplePrimitives(ComApi.nwEVertexProperty.eNORMAL, primitiveChecker);
|
||||
|
||||
// Exit early if triangles are found
|
||||
if (primitiveChecker.HasTriangles)
|
||||
var fragmentsCollection = path.Fragments();
|
||||
try
|
||||
{
|
||||
return false;
|
||||
foreach (ComApi.InwOaFragment3 fragment in fragmentsCollection.OfType<ComApi.InwOaFragment3>())
|
||||
{
|
||||
fragment.GenerateSimplePrimitives(ComApi.nwEVertexProperty.eNORMAL, primitiveChecker);
|
||||
|
||||
if (primitiveChecker.HasTriangles)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fragmentsCollection != null)
|
||||
{
|
||||
System.Runtime.InteropServices.Marshal.ReleaseComObject(fragmentsCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if any 2D primitives are found
|
||||
return primitiveChecker.HasLines || primitiveChecker.HasPoints || primitiveChecker.HasSnapPoints;
|
||||
return primitiveChecker.HasLines || primitiveChecker.HasPoints || primitiveChecker.HasSnapPoints;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (pathsCollection != null)
|
||||
{
|
||||
System.Runtime.InteropServices.Marshal.ReleaseComObject(pathsCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
System.Runtime.InteropServices.Marshal.ReleaseComObject(comSelection);
|
||||
if (comSelection != null)
|
||||
{
|
||||
System.Runtime.InteropServices.Marshal.ReleaseComObject(comSelection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+63
-20
@@ -2,6 +2,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.Constants;
|
||||
using Speckle.Converter.Navisworks.Helpers;
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converter.Navisworks.ToSpeckle;
|
||||
@@ -18,9 +19,8 @@ public class NavisworksMaterialUnpacker(
|
||||
GeometryToSpeckleConverter converter
|
||||
)
|
||||
{
|
||||
// Helper function to select a property based on the representation mode
|
||||
// Selector method for individual properties
|
||||
private static T Select<T>(RepresentationMode mode, T active, T permanent, T original, T defaultValue) =>
|
||||
private static T SelectByRepresentationMode<T>(
|
||||
RepresentationMode mode, T active, T permanent, T original, T defaultValue) =>
|
||||
mode switch
|
||||
{
|
||||
RepresentationMode.Active => active,
|
||||
@@ -69,16 +69,61 @@ 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)
|
||||
try
|
||||
{
|
||||
var fragmentId = converter.GenerateFragmentId(paths);
|
||||
hashId = $"geom_{fragmentId}";
|
||||
var item = selectionService.GetModelItemFromPath(finalId);
|
||||
var comSelection = ComApiBridge.ToInwOpSelection([item]);
|
||||
try
|
||||
{
|
||||
var paths = comSelection.Paths();
|
||||
try
|
||||
{
|
||||
if (paths.Count > 0)
|
||||
{
|
||||
var firstPath = paths.OfType<InwOaPath>().FirstOrDefault();
|
||||
if (firstPath != null)
|
||||
{
|
||||
var fragments = firstPath.Fragments();
|
||||
try
|
||||
{
|
||||
if (fragments.Count > 1)
|
||||
{
|
||||
var fragmentId = converter.GenerateFragmentId(paths);
|
||||
hashId = $"{InstanceConstants.GEOMETRY_ID_PREFIX}{fragmentId}";
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fragments != null)
|
||||
{
|
||||
System.Runtime.InteropServices.Marshal.ReleaseComObject(fragments);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (paths != null)
|
||||
{
|
||||
System.Runtime.InteropServices.Marshal.ReleaseComObject(paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (comSelection != null)
|
||||
{
|
||||
System.Runtime.InteropServices.Marshal.ReleaseComObject(comSelection);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal())
|
||||
{
|
||||
// If COM interop fails during hash generation, fall back to using finalId
|
||||
logger.LogWarning(ex, "Failed to generate fragment hash ID for item {ItemId}, using finalId as fallback", finalId);
|
||||
hashId = "";
|
||||
}
|
||||
|
||||
var geometry = navisworksObject.Geometry;
|
||||
@@ -86,21 +131,21 @@ public class NavisworksMaterialUnpacker(
|
||||
|
||||
using var defaultColor = new NAV.Color(1.0, 1.0, 1.0);
|
||||
|
||||
var renderColor = Select(
|
||||
var renderColor = SelectByRepresentationMode(
|
||||
mode,
|
||||
geometry.ActiveColor,
|
||||
geometry.PermanentColor,
|
||||
geometry.OriginalColor,
|
||||
defaultColor
|
||||
);
|
||||
var renderTransparency = Select(
|
||||
var renderTransparency = SelectByRepresentationMode(
|
||||
mode,
|
||||
geometry.ActiveTransparency,
|
||||
geometry.PermanentTransparency,
|
||||
geometry.OriginalTransparency,
|
||||
0.0
|
||||
);
|
||||
var renderMaterialId = Select(
|
||||
var renderMaterialId = SelectByRepresentationMode(
|
||||
mode,
|
||||
$"{geometry.ActiveColor.GetHashCode()}_{geometry.ActiveTransparency}".GetHashCode(),
|
||||
$"{geometry.PermanentColor.GetHashCode()}_{geometry.PermanentTransparency}".GetHashCode(),
|
||||
@@ -109,9 +154,8 @@ public class NavisworksMaterialUnpacker(
|
||||
);
|
||||
|
||||
var materialName =
|
||||
$"NavisworksMaterial_{Math.Abs(ColorConverter.NavisworksColorToColor(renderColor).ToArgb())}";
|
||||
$"{MaterialConstants.DEFAULT_MATERIAL_NAME_PREFIX}{Math.Abs(ColorConverter.NavisworksColorToColor(renderColor).ToArgb())}";
|
||||
|
||||
// Check Item category for material name
|
||||
var itemCategory = navisworksObject.PropertyCategories.FindCategoryByDisplayName("Item");
|
||||
if (itemCategory != null)
|
||||
{
|
||||
@@ -123,7 +167,6 @@ public class NavisworksMaterialUnpacker(
|
||||
}
|
||||
}
|
||||
|
||||
// Check Material category for material name
|
||||
var materialPropertyCategory = navisworksObject.PropertyCategories.FindCategoryByDisplayName("Material");
|
||||
if (materialPropertyCategory != null)
|
||||
{
|
||||
@@ -143,7 +186,7 @@ public class NavisworksMaterialUnpacker(
|
||||
{
|
||||
renderMaterialProxies[renderMaterialId.ToString()] = new RenderMaterialProxy()
|
||||
{
|
||||
value = ConvertRenderColorAndTransparencyToSpeckle(
|
||||
value = CreateRenderMaterial(
|
||||
materialName,
|
||||
renderTransparency,
|
||||
renderColor,
|
||||
@@ -162,7 +205,7 @@ public class NavisworksMaterialUnpacker(
|
||||
return renderMaterialProxies.Values.ToList();
|
||||
}
|
||||
|
||||
private static RenderMaterial ConvertRenderColorAndTransparencyToSpeckle(
|
||||
private static RenderMaterial CreateRenderMaterial(
|
||||
string name,
|
||||
double transparency,
|
||||
NAV.Color navisworksColor,
|
||||
@@ -173,7 +216,7 @@ public class NavisworksMaterialUnpacker(
|
||||
|
||||
var speckleRenderMaterial = new RenderMaterial()
|
||||
{
|
||||
name = !string.IsNullOrEmpty(name) ? name : $"NavisworksMaterial_{Math.Abs(color.ToArgb())}",
|
||||
name = !string.IsNullOrEmpty(name) ? name : $"{MaterialConstants.DEFAULT_MATERIAL_NAME_PREFIX}{Math.Abs(color.ToArgb())}",
|
||||
opacity = 1 - transparency,
|
||||
metalness = 0,
|
||||
roughness = 1,
|
||||
|
||||
+6
-50
@@ -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;
|
||||
@@ -42,19 +42,14 @@ 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;
|
||||
#endif
|
||||
using var activity = activityFactory.Start("Build");
|
||||
|
||||
ValidateInputs(navisworksModelItems, projectId, onOperationProgressed);
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -66,8 +61,6 @@ public class NavisworksRootObjectBuilder(
|
||||
|
||||
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",
|
||||
@@ -159,12 +152,10 @@ public class NavisworksRootObjectBuilder(
|
||||
Dictionary<string, List<NAV.ModelItem>> groupedNodes
|
||||
)
|
||||
{
|
||||
// First build the grouped nodes as before
|
||||
var finalElements = new List<Base>();
|
||||
var processedPaths = new HashSet<string>();
|
||||
AddGroupedElements(finalElements, convertedBases, groupedNodes, processedPaths);
|
||||
|
||||
// If hierarchy mode is enabled, reorganize into proper nested structure
|
||||
if (converterSettings.Current.User.PreserveModelHierarchy)
|
||||
{
|
||||
var hierarchyBuilder = new NavisworksHierarchyBuilder(
|
||||
@@ -173,12 +164,9 @@ public class NavisworksRootObjectBuilder(
|
||||
elementSelectionService
|
||||
);
|
||||
|
||||
var hierarchy = hierarchyBuilder.BuildHierarchy();
|
||||
|
||||
return hierarchy;
|
||||
return hierarchyBuilder.BuildHierarchy();
|
||||
}
|
||||
|
||||
// Otherwise continue with flat mode
|
||||
AddRemainingElements(finalElements, convertedBases, processedPaths);
|
||||
return finalElements;
|
||||
}
|
||||
@@ -235,24 +223,17 @@ public class NavisworksRootObjectBuilder(
|
||||
}
|
||||
}
|
||||
|
||||
private (string name, string path) GetContext(string applicationId)
|
||||
private (string name, string path) GetElementNameAndPath(string applicationId)
|
||||
{
|
||||
var modelItem = elementSelectionService.GetModelItemFromPath(applicationId);
|
||||
var context = HierarchyHelper.ExtractContext(modelItem);
|
||||
return (context.Name, context.Path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes and adds any remaining non-grouped elements.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Handles both Collection and Base type elements differently.
|
||||
/// Only processes elements that weren't handled in grouped processing.
|
||||
/// </remarks>
|
||||
private NavisworksObject CreateNavisworksObject(string groupKey, List<Base> siblingBases)
|
||||
{
|
||||
string cleanParentPath = ElementSelectionHelper.GetCleanPath(groupKey);
|
||||
(string name, string path) = GetContext(cleanParentPath);
|
||||
(string name, string path) = GetElementNameAndPath(cleanParentPath);
|
||||
|
||||
return new NavisworksObject
|
||||
{
|
||||
@@ -260,16 +241,11 @@ public class NavisworksRootObjectBuilder(
|
||||
displayValue = siblingBases.SelectMany(b => b["displayValue"] as List<Base> ?? []).ToList(),
|
||||
properties = siblingBases.First()["properties"] as Dictionary<string, object?> ?? [],
|
||||
units = converterSettings.Current.Derived.SpeckleUnits,
|
||||
applicationId = groupKey, // Use the full composite key as applicationId to preserve uniqueness
|
||||
applicationId = groupKey,
|
||||
["path"] = path
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NavisworksObject from a single converted base.
|
||||
/// </summary>
|
||||
/// <param name="convertedBase">The converted Speckle Base object.</param>
|
||||
/// <returns>A new NavisworksObject containing the converted data.</returns>
|
||||
private NavisworksObject? CreateNavisworksObject(Base convertedBase)
|
||||
{
|
||||
if (convertedBase.applicationId == null)
|
||||
@@ -277,7 +253,7 @@ public class NavisworksRootObjectBuilder(
|
||||
return null;
|
||||
}
|
||||
|
||||
(string name, string path) = GetContext(convertedBase.applicationId);
|
||||
(string name, string path) = GetElementNameAndPath(convertedBase.applicationId);
|
||||
|
||||
return new NavisworksObject
|
||||
{
|
||||
@@ -310,36 +286,16 @@ 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a single Navisworks item to a Speckle object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Attempts to retrieve from cache first.
|
||||
/// Falls back to fresh conversion if not cached.
|
||||
/// Logs errors but doesn't throw exceptions.
|
||||
/// </remarks>
|
||||
/// <returns>A SendConversionResult indicating success or failure.</returns>
|
||||
private SendConversionResult ConvertNavisworksItem(
|
||||
NAV.ModelItem navisworksItem,
|
||||
Dictionary<string, Base?> convertedBases,
|
||||
|
||||
@@ -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, )"
|
||||
|
||||
@@ -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, )"
|
||||
|
||||
+1
-8
@@ -18,20 +18,13 @@ public class PropertySetsExtractor(IConverterSettingsStore<NavisworksConversionS
|
||||
return propertyDictionary;
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// arbitrary in nature.
|
||||
/// </summary>
|
||||
/// <param name="modelItem">The NAV.ModelItem from which property sets are extracted.</param>
|
||||
/// <returns>A dictionary containing property sets of the modelItem.</returns>
|
||||
private Dictionary<string, object?> ExtractPropertySets(NAV.ModelItem modelItem)
|
||||
{
|
||||
var propertySetDictionary = new Dictionary<string, object?>();
|
||||
|
||||
foreach (var propertyCategory in modelItem.PropertyCategories)
|
||||
{
|
||||
if (IsCategoryToBeSkipped(propertyCategory))
|
||||
if (ShouldSkipCategory(propertyCategory))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
+2
-1
@@ -52,7 +52,8 @@ public static class NavisworksConverterServiceRegistration
|
||||
|
||||
// Register ISharedGeometryStore interface using the geometry definitions store for backward compatibility
|
||||
serviceCollection.AddScoped<ISharedGeometryStore>(provider =>
|
||||
provider.GetRequiredService<InstanceStoreManager>().GeometryDefinitionsStore);
|
||||
provider.GetRequiredService<InstanceStoreManager>().GeometryDefinitionsStore
|
||||
);
|
||||
|
||||
// Register settings resolved from factory
|
||||
serviceCollection.AddScoped<NavisworksConversionSettings>(sp =>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-20
@@ -47,22 +47,15 @@ public static class PropertyHelpers
|
||||
return handler(value, units);
|
||||
}
|
||||
|
||||
// Default case for unsupported types
|
||||
return value.DataType is NAV.VariantDataType.None or NAV.VariantDataType.Point2D ? null : value.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a property to an object (either a Base object or a Dictionary) if the value is not null or empty.
|
||||
/// </summary>
|
||||
/// <param name="baseObject">The object to which the property is to be added. Can be either a Base object or a Dictionary.</param>
|
||||
/// <param name="propertyName">The name of the property to add.</param>
|
||||
/// <param name="value">The value of the property.</param>
|
||||
internal static void AddPropertyIfNotNullOrEmpty(object baseObject, string propertyName, object? value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case null:
|
||||
break; // Do not add null values
|
||||
break;
|
||||
case string stringValue:
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
@@ -78,9 +71,6 @@ public static class PropertyHelpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to assign the property to the base object or dictionary.
|
||||
/// </summary>
|
||||
private static void AssignProperty(object baseObject, string propertyName, object value)
|
||||
{
|
||||
switch (baseObject)
|
||||
@@ -96,16 +86,9 @@ public static class PropertyHelpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanitizes property names by replacing invalid characters with underscores.
|
||||
/// </summary>
|
||||
internal static string SanitizePropertyName(string name) =>
|
||||
// Regex pattern from speckle-sharp/Core/Core/Models/DynamicBase.cs IsPropNameValid
|
||||
name == "Item"
|
||||
// Item is a reserved term for Indexed Properties: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/indexers/using-indexers
|
||||
? "Item"
|
||||
: Regex.Replace(name, @"[\.\/\s]", "_");
|
||||
name == "Item" ? "Item" : Regex.Replace(name, @"[\.\/\s]", "_");
|
||||
|
||||
internal static bool IsCategoryToBeSkipped(NAV.PropertyCategory propertyCategory) =>
|
||||
internal static bool ShouldSkipCategory(NAV.PropertyCategory propertyCategory) =>
|
||||
s_excludedCategories.Contains(propertyCategory.DisplayName);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Speckle.Converter.Navisworks.Constants;
|
||||
namespace Speckle.Converter.Navisworks.Constants;
|
||||
|
||||
public static class PathConstants
|
||||
{
|
||||
@@ -6,3 +6,14 @@ public static class PathConstants
|
||||
public const string MATERIAL_SEPARATOR = "::";
|
||||
public const string SET_SEPARATOR = ">";
|
||||
}
|
||||
|
||||
public static class InstanceConstants
|
||||
{
|
||||
public const string GEOMETRY_ID_PREFIX = "geom_";
|
||||
public const string DEFINITION_ID_PREFIX = "def_";
|
||||
}
|
||||
|
||||
public static class MaterialConstants
|
||||
{
|
||||
public const string DEFAULT_MATERIAL_NAME_PREFIX = "NavisworksMaterial_";
|
||||
}
|
||||
|
||||
+10
-6
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Converter.Navisworks.Constants;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Instances;
|
||||
|
||||
@@ -54,7 +55,9 @@ public class InstanceStoreManager(ILogger<InstanceStoreManager> logger)
|
||||
/// </summary>
|
||||
/// <returns>The geometry if found, null otherwise.</returns>
|
||||
public Base? GetGeometryDefinition(string fragmentId) =>
|
||||
GeometryDefinitionsStore.Geometries.FirstOrDefault(g => g.applicationId == $"geom_{fragmentId}");
|
||||
GeometryDefinitionsStore.Geometries.FirstOrDefault(g =>
|
||||
g.applicationId == $"{InstanceConstants.GEOMETRY_ID_PREFIX}{fragmentId}"
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance definition proxy by its application ID.
|
||||
@@ -63,7 +66,7 @@ public class InstanceStoreManager(ILogger<InstanceStoreManager> logger)
|
||||
public InstanceDefinitionProxy? GetInstanceDefinitionProxy(string fragmentId) =>
|
||||
InstanceDefinitionProxiesStore
|
||||
.Geometries.OfType<InstanceDefinitionProxy>()
|
||||
.FirstOrDefault(p => p.applicationId == $"def_{fragmentId}");
|
||||
.FirstOrDefault(p => p.applicationId == $"{InstanceConstants.DEFINITION_ID_PREFIX}{fragmentId}");
|
||||
|
||||
/// <summary>
|
||||
/// Adds a geometry definition and corresponding instance definition proxy for shared geometry.
|
||||
@@ -80,8 +83,8 @@ public class InstanceStoreManager(ILogger<InstanceStoreManager> logger)
|
||||
bool proxyAdded = false;
|
||||
|
||||
// Create prefixed IDs for 1:1:1 relationship using base fragment hash
|
||||
var geometryId = $"geom_{fragmentId}";
|
||||
var definitionId = $"def_{fragmentId}";
|
||||
var geometryId = $"{InstanceConstants.GEOMETRY_ID_PREFIX}{fragmentId}";
|
||||
var definitionId = $"{InstanceConstants.DEFINITION_ID_PREFIX}{fragmentId}";
|
||||
|
||||
_logger.LogDebug("Using GeometryId={GeometryId}, DefinitionId={DefinitionId}", geometryId, definitionId);
|
||||
|
||||
@@ -148,7 +151,8 @@ public class InstanceStoreManager(ILogger<InstanceStoreManager> logger)
|
||||
/// </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}")
|
||||
public bool ContainsSharedGeometry(string fragmentId) =>
|
||||
GeometryDefinitionsStore.Contains($"{InstanceConstants.GEOMETRY_ID_PREFIX}{fragmentId}")
|
||||
// && InstanceDefinitionProxiesStore.Contains($"{InstanceConstants.DEFINITION_ID_PREFIX}{fragmentId}")
|
||||
;
|
||||
}
|
||||
|
||||
-1
@@ -23,7 +23,6 @@
|
||||
<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"/>
|
||||
|
||||
+1
-7
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Converter.Navisworks.Helpers;
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converters.Common;
|
||||
@@ -8,9 +8,6 @@ using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.ToSpeckle;
|
||||
|
||||
/// <summary>
|
||||
/// Converts Navisworks ModelItem objects to Speckle Base objects.
|
||||
/// </summary>
|
||||
public class NavisworksRootToSpeckleConverter : IRootToSpeckleConverter
|
||||
{
|
||||
private readonly IConverterManager<IToSpeckleTopLevelConverter> _toSpeckle;
|
||||
@@ -41,11 +38,8 @@ public class NavisworksRootToSpeckleConverter : IRootToSpeckleConverter
|
||||
}
|
||||
|
||||
Type type = target.GetType();
|
||||
|
||||
var objectConverter = _toSpeckle.ResolveConverter(type, true);
|
||||
|
||||
Base result = objectConverter.Convert(modelItem);
|
||||
|
||||
result.applicationId = ElementSelectionHelper.ResolveModelItemToIndexPath(modelItem);
|
||||
|
||||
return result;
|
||||
|
||||
+244
-382
@@ -1,7 +1,9 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Autodesk.Navisworks.Api.Interop.ComApi;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Converter.Navisworks.Constants;
|
||||
using Speckle.Converter.Navisworks.Geometry;
|
||||
using Speckle.Converter.Navisworks.Helpers;
|
||||
using Speckle.Converter.Navisworks.Services;
|
||||
@@ -14,17 +16,11 @@ using ComApiBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.ToSpeckle;
|
||||
|
||||
/// <summary>
|
||||
/// Converts Navisworks geometry to Speckle displayable geometry.
|
||||
///
|
||||
/// Note: This class does not implement ITypedConverter{ModelGeometry, Base} because Navisworks geometry
|
||||
/// conversion requires COM interop access that isn't available through the public ModelGeometry class.
|
||||
/// The conversion process requires:
|
||||
/// 1. Convert ModelItem to InwOaPath3 via ComApiBridge
|
||||
/// 2. Use that to get InwOaFragmentList
|
||||
/// 3. Process each InwOaFragment3 to generate primitives
|
||||
/// 4. Convert those primitives to Speckle geometry with appropriate transforms
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Memory Safety: All COM objects (InwSelectionPathsColl, InwOaPath, InwOaFragmentList) are explicitly
|
||||
/// released using Marshal.ReleaseComObject in try-finally blocks to prevent memory leaks.
|
||||
/// NAV.Color objects are disposed using 'using' statements as they implement IDisposable.
|
||||
/// </remarks>
|
||||
public class GeometryToSpeckleConverter(
|
||||
NavisworksConversionSettings settings,
|
||||
InstanceStoreManager instanceStoreManager,
|
||||
@@ -36,7 +32,9 @@ public class GeometryToSpeckleConverter(
|
||||
|
||||
private readonly bool _isUpright = settings.Derived.IsUpright;
|
||||
private readonly SafeVector _transformVector = settings.Derived.TransformVector;
|
||||
private const double SCALE = 1.0; // Default scale factor
|
||||
private const double SCALE = 1.0;
|
||||
private static readonly Matrix4x4 s_identityMatrix = new(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
private static readonly double[] s_identityTransform = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
||||
|
||||
private readonly InstanceStoreManager _instanceStoreManager =
|
||||
instanceStoreManager ?? throw new ArgumentNullException(nameof(instanceStoreManager));
|
||||
@@ -44,41 +42,6 @@ public class GeometryToSpeckleConverter(
|
||||
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()
|
||||
{
|
||||
_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));
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
internal List<Base> Convert(NAV.ModelItem modelItem)
|
||||
{
|
||||
if (modelItem == null)
|
||||
@@ -91,89 +54,85 @@ 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)
|
||||
if (paths.Count > 0)
|
||||
{
|
||||
// Shared geometry - extract base geometry once and return instance reference
|
||||
result = ProcessSharedGeometry(paths.Value, fragmentStack);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Single instance geometry - process normally with transforms
|
||||
foreach (InwOaPath path in paths.Value)
|
||||
var firstPath = paths.Cast<InwOaPath>().First();
|
||||
var fragmentsCollection = firstPath.Fragments();
|
||||
try
|
||||
{
|
||||
CollectFragments(path, fragmentStack);
|
||||
if (fragmentsCollection.Count > 1)
|
||||
{
|
||||
return ProcessSharedGeometry(paths, fragmentStack);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fragmentsCollection != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(fragmentsCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = ProcessFragments(fragmentStack, paths.Value, true);
|
||||
foreach (InwOaPath path in paths)
|
||||
{
|
||||
CollectFragments(path, fragmentStack);
|
||||
}
|
||||
|
||||
return ProcessFragments(fragmentStack, paths, true);
|
||||
}
|
||||
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();
|
||||
try
|
||||
{
|
||||
if (ValidateFragmentPath(fragment, path))
|
||||
foreach (var fragment in fragments.OfType<InwOaFragment3>())
|
||||
{
|
||||
fragmentStack.Push(fragment);
|
||||
if (AreFragmentPathsEqual(fragment, path))
|
||||
{
|
||||
fragmentStack.Push(fragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fragments != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(fragments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -182,14 +141,11 @@ public class GeometryToSpeckleConverter(
|
||||
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);
|
||||
@@ -202,16 +158,12 @@ public class GeometryToSpeckleConverter(
|
||||
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(
|
||||
@@ -226,40 +178,42 @@ 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)
|
||||
{
|
||||
var matrix = fragment.GetLocalToWorldMatrix();
|
||||
var transform = matrix as InwLTransform3f3;
|
||||
if (transform?.Matrix is not Array matrixArray)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var fragmentsForCount = path.Fragments();
|
||||
int fragmentCount;
|
||||
try
|
||||
{
|
||||
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);
|
||||
fragmentCount = fragmentsForCount.Count;
|
||||
}
|
||||
catch (COMException ex)
|
||||
finally
|
||||
{
|
||||
_logger.LogWarning(ex, "COM exception processing fragment, skipping");
|
||||
if (fragmentsForCount != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(fragmentsForCount);
|
||||
}
|
||||
}
|
||||
|
||||
double[] makeNoChange = s_identityTransform;
|
||||
double[] transformMatrix = ConvertArrayToDouble(matrixArray);
|
||||
|
||||
if (isSingleObject || fragmentCount == 1)
|
||||
{
|
||||
processor.LocalToWorldTransformation = transformMatrix;
|
||||
}
|
||||
else
|
||||
{
|
||||
processor.LocalToWorldTransformation = makeNoChange;
|
||||
}
|
||||
|
||||
fragment.GenerateSimplePrimitives(nwEVertexProperty.eNORMAL, processor);
|
||||
}
|
||||
|
||||
callbackListeners.Add(processor);
|
||||
@@ -268,10 +222,10 @@ public class GeometryToSpeckleConverter(
|
||||
return ProcessGeometries(callbackListeners);
|
||||
}
|
||||
|
||||
private static bool ValidateFragmentPath(InwOaFragment3 fragment, InwOaPath path) =>
|
||||
private static bool AreFragmentPathsEqual(InwOaFragment3 fragment, InwOaPath path) =>
|
||||
fragment.path?.ArrayData is Array fragmentPathData
|
||||
&& path.ArrayData is Array pathData
|
||||
&& IsSameFragmentPath(fragmentPathData, pathData);
|
||||
&& AreFragmentPathsEqual(fragmentPathData, pathData);
|
||||
|
||||
private List<Base> ProcessGeometries(List<PrimitiveProcessor> processors)
|
||||
{
|
||||
@@ -304,7 +258,6 @@ public class GeometryToSpeckleConverter(
|
||||
{
|
||||
var triangle = triangles[t];
|
||||
|
||||
// No need to worry about disposal of COM across boundaries - we're working with our safe structs
|
||||
vertices.AddRange(
|
||||
[
|
||||
(triangle.Vertex1.X + _transformVector.X) * SCALE,
|
||||
@@ -349,10 +302,6 @@ public class GeometryToSpeckleConverter(
|
||||
})
|
||||
.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
|
||||
@@ -362,137 +311,124 @@ public class GeometryToSpeckleConverter(
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// 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)
|
||||
foreach (var fragments in from InwOaPath path in paths select path.Fragments())
|
||||
{
|
||||
var fragments = path.Fragments();
|
||||
|
||||
var fragmentIndex = 0;
|
||||
foreach (InwOaFragment3 fragment in fragments.OfType<InwOaFragment3>())
|
||||
try
|
||||
{
|
||||
if (fragment.path?.ArrayData is not Array pathData)
|
||||
var fragmentIndex = 0;
|
||||
foreach (InwOaFragment3 fragment in fragments.OfType<InwOaFragment3>())
|
||||
{
|
||||
fragmentIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pathData.Length == 0)
|
||||
{
|
||||
fragmentIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Check array rank first - COM arrays might be multidimensional
|
||||
if (pathData.Rank != 1)
|
||||
if (fragment.path?.ArrayData is not Array pathData || pathData.Length == 0)
|
||||
{
|
||||
// Try simple enumeration fallback
|
||||
var fragmentHashFallback = TrySimpleArrayEnumeration(pathData, fragmentIndex);
|
||||
if (!string.IsNullOrEmpty(fragmentHashFallback))
|
||||
fragmentIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (pathData.Rank != 1)
|
||||
{
|
||||
fragmentHashes.Add(fragmentHashFallback);
|
||||
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 COMException or InvalidCastException)
|
||||
{
|
||||
var errorType = ex is COMException ? "COM array access failed" : "Type conversion failed";
|
||||
_logger.LogDebug(ex, "{ErrorType} at index {Index}", errorType, i);
|
||||
}
|
||||
}
|
||||
|
||||
var fragmentHash = string.Join("_", pathInts);
|
||||
fragmentHashes.Add(fragmentHash);
|
||||
}
|
||||
catch (Exception ex) when (ex is COMException or IndexOutOfRangeException or RankException)
|
||||
{
|
||||
var errorType = ex switch
|
||||
{
|
||||
COMException => "COM access failed",
|
||||
IndexOutOfRangeException => "Array bounds exceeded",
|
||||
RankException => "Array rank mismatch",
|
||||
_ => "Error"
|
||||
};
|
||||
_logger.LogDebug(ex, "{ErrorType} processing fragment {FragmentIndex}, trying simple enumeration", errorType, fragmentIndex);
|
||||
|
||||
var fragmentHash = TrySimpleArrayEnumeration(pathData, fragmentIndex);
|
||||
if (!string.IsNullOrEmpty(fragmentHash))
|
||||
{
|
||||
fragmentHashes.Add(fragmentHash);
|
||||
}
|
||||
|
||||
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++;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fragments != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(fragments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string fragmentId;
|
||||
if (fragmentHashes.Count > 0)
|
||||
{
|
||||
// Sort to ensure consistent ordering
|
||||
fragmentHashes.Sort();
|
||||
var rawData = string.Join("__", fragmentHashes);
|
||||
fragmentId = HashRawData(rawData);
|
||||
var fragmentId = HashRawData(rawData);
|
||||
return fragmentId;
|
||||
}
|
||||
else
|
||||
{
|
||||
fragmentId = string.Empty;
|
||||
return 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
|
||||
)
|
||||
catch (Exception ex) when (ex is COMException or InvalidCastException or IndexOutOfRangeException)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to generate fragment ID due to {ExceptionType}", ex.GetType().Name);
|
||||
var errorType = ex switch
|
||||
{
|
||||
COMException => "COM access failed",
|
||||
InvalidCastException => "Type conversion failed",
|
||||
IndexOutOfRangeException => "Array bounds exceeded",
|
||||
_ => "Error"
|
||||
};
|
||||
_logger.LogWarning(ex, "{ErrorType} generating fragment ID", errorType);
|
||||
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
|
||||
var maxAttempts = Math.Min(pathData.Length, 20);
|
||||
|
||||
for (int i = 0; i < maxAttempts; i++)
|
||||
{
|
||||
@@ -501,18 +437,14 @@ public class GeometryToSpeckleConverter(
|
||||
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)
|
||||
catch (InvalidCastException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Fragment {FragmentIndex} failed to convert value at index {Index}", fragmentIndex, i);
|
||||
_logger.LogDebug(ex, "Type conversion failed at index {Index}", i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,96 +453,44 @@ public class GeometryToSpeckleConverter(
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var hash = string.Join("_", values);
|
||||
return hash;
|
||||
return string.Join("_", values);
|
||||
}
|
||||
catch (Exception ex) when (ex is COMException or InvalidCastException or ArgumentException)
|
||||
catch (COMException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Simple enumeration failed for fragment {FragmentIndex}", fragmentIndex);
|
||||
_logger.LogDebug(ex, "COM 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");
|
||||
using var sha256 = SHA256.Create();
|
||||
var inputBytes = Encoding.UTF8.GetBytes(rawData);
|
||||
var hashBytes = sha256.ComputeHash(inputBytes);
|
||||
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
|
||||
/// <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;
|
||||
processor.LocalToWorldTransformation = s_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}",
|
||||
definitionId = $"{InstanceConstants.DEFINITION_ID_PREFIX}{fragmentId}",
|
||||
transform = transform,
|
||||
units = _settings.Derived.SpeckleUnits,
|
||||
maxDepth = 0,
|
||||
@@ -620,113 +500,100 @@ public class GeometryToSpeckleConverter(
|
||||
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);
|
||||
return s_identityMatrix;
|
||||
}
|
||||
|
||||
var firstPath = paths.Cast<InwOaPath>().First();
|
||||
using var fragments = new ComScope<InwNodeFragsColl>(firstPath.Fragments());
|
||||
|
||||
if (fragments.Value.Count == 0)
|
||||
var fragments = firstPath.Fragments();
|
||||
try
|
||||
{
|
||||
return new Matrix4x4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
if (fragments.Count == 0)
|
||||
{
|
||||
return s_identityMatrix;
|
||||
}
|
||||
|
||||
var fragmentStack = new Stack<InwOaFragment3>();
|
||||
// Get the first fragment's transform matrix
|
||||
foreach (var frag in fragments.Value.OfType<InwOaFragment3>())
|
||||
{
|
||||
try
|
||||
var fragmentStack = new Stack<InwOaFragment3>();
|
||||
|
||||
foreach (var frag in fragments.OfType<InwOaFragment3>())
|
||||
{
|
||||
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))
|
||||
var pathArray1 = pathData1.Cast<int>().ToArray();
|
||||
var pathArray2 = pathData2.Cast<int>().ToArray();
|
||||
|
||||
if (pathArray1.Length == pathArray2.Length && pathArray1.SequenceEqual(pathArray2))
|
||||
{
|
||||
fragmentStack.Push(frag);
|
||||
}
|
||||
}
|
||||
catch (COMException ex)
|
||||
|
||||
var fragment = fragmentStack.First();
|
||||
var matrix = fragment.GetLocalToWorldMatrix();
|
||||
|
||||
if (matrix is InwLTransform3f3 { Matrix: Array matrixArray })
|
||||
{
|
||||
_logger.LogWarning(ex, "COM exception accessing fragment path data, skipping fragment");
|
||||
var transformArray = ConvertArrayToDouble(matrixArray);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (fragmentStack.Count == 0)
|
||||
finally
|
||||
{
|
||||
_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);
|
||||
if (fragments != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(fragments);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (ex
|
||||
is COMException
|
||||
or InvalidCastException
|
||||
or IndexOutOfRangeException
|
||||
or ArgumentException
|
||||
or NullReferenceException
|
||||
)
|
||||
catch (Exception ex) when (ex is COMException or InvalidCastException or NullReferenceException)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
ex,
|
||||
"Failed to extract instance transform ({ExceptionType}) - returning identity matrix",
|
||||
ex.GetType().Name
|
||||
);
|
||||
var errorType = ex switch
|
||||
{
|
||||
COMException => "COM access failed",
|
||||
InvalidCastException => "Transform matrix type conversion failed",
|
||||
NullReferenceException => "Null reference",
|
||||
_ => "Error"
|
||||
};
|
||||
_logger.LogWarning(ex, "{ErrorType} extracting instance transform", errorType);
|
||||
}
|
||||
|
||||
return new Matrix4x4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
return s_identityMatrix;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -750,11 +617,6 @@ public class GeometryToSpeckleConverter(
|
||||
return doubleArray;
|
||||
}
|
||||
|
||||
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()
|
||||
);
|
||||
private static bool AreFragmentPathsEqual(Array a1, Array a2) =>
|
||||
a1.Length == a2.Length && a1.Cast<int>().SequenceEqual(a2.Cast<int>());
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
+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:
|
||||
|
||||
+33
-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,39 @@ 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 display value contains InstanceProxies - register for special handling
|
||||
if (target.displayValue.Count > 0 && target.displayValue[0] is InstanceProxy)
|
||||
{
|
||||
var instanceProxies = target.displayValue.Cast<InstanceProxy>().ToList();
|
||||
_dataObjectInstanceRegistry.Register(target.applicationId ?? target.id.NotNull(), target, instanceProxies);
|
||||
return resultPairs; // empty - will be handled by instance baker
|
||||
}
|
||||
|
||||
// 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 +115,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