Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b246cf95c | |||
| 003e310089 | |||
| 5917530761 | |||
| 349009314b | |||
| 659a29a294 | |||
| 1ffeddbc2c | |||
| 9d9a27d9cb | |||
| b2a885c193 | |||
| 60c1811fa6 | |||
| 5365809172 |
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -210,6 +210,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -210,6 +210,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -268,6 +268,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -268,6 +268,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -268,6 +268,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -219,6 +219,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -219,6 +219,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -210,6 +210,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -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, )"
|
||||
|
||||
+4
@@ -15,6 +15,7 @@ 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.Services;
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Sdk.Models.GraphTraversal;
|
||||
@@ -52,6 +53,9 @@ public static class NavisworksConnectorServiceRegistration
|
||||
serviceCollection.AddScoped<NavisworksMaterialUnpacker>();
|
||||
serviceCollection.AddScoped<NavisworksColorUnpacker>();
|
||||
|
||||
// Register dual shared geometry stores for instancing pattern
|
||||
serviceCollection.AddScoped<InstanceStoreManager>();
|
||||
|
||||
serviceCollection.AddSingleton<IAppIdleManager, NavisworksIdleManager>();
|
||||
|
||||
// Sending operations
|
||||
|
||||
+43
-18
@@ -16,7 +16,13 @@ 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 +77,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 +130,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+85
-20
@@ -1,7 +1,11 @@
|
||||
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;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Sdk;
|
||||
@@ -11,12 +15,17 @@ namespace Speckle.Connector.Navisworks.HostApp;
|
||||
public class NavisworksMaterialUnpacker(
|
||||
ILogger<NavisworksMaterialUnpacker> logger,
|
||||
IConverterSettingsStore<NavisworksConversionSettings> converterSettings,
|
||||
IElementSelectionService selectionService
|
||||
IElementSelectionService selectionService,
|
||||
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,
|
||||
@@ -64,26 +73,87 @@ public class NavisworksMaterialUnpacker(
|
||||
|
||||
var navisworksObjectId = selectionService.GetModelItemPath(navisworksObject);
|
||||
var finalId = mergedIds.TryGetValue(navisworksObjectId, out var mergedId) ? mergedId : navisworksObjectId;
|
||||
|
||||
string hashId = "";
|
||||
try
|
||||
{
|
||||
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;
|
||||
var mode = converterSettings.Current.User.VisualRepresentationMode;
|
||||
|
||||
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(),
|
||||
@@ -92,9 +162,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)
|
||||
{
|
||||
@@ -106,7 +175,6 @@ public class NavisworksMaterialUnpacker(
|
||||
}
|
||||
}
|
||||
|
||||
// Check Material category for material name
|
||||
var materialPropertyCategory = navisworksObject.PropertyCategories.FindCategoryByDisplayName("Material");
|
||||
if (materialPropertyCategory != null)
|
||||
{
|
||||
@@ -120,19 +188,14 @@ public class NavisworksMaterialUnpacker(
|
||||
|
||||
if (renderMaterialProxies.TryGetValue(renderMaterialId.ToString(), out RenderMaterialProxy? value))
|
||||
{
|
||||
value.objects.Add(finalId);
|
||||
value.objects.Add(!string.IsNullOrEmpty(hashId) ? hashId : finalId);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderMaterialProxies[renderMaterialId.ToString()] = new RenderMaterialProxy()
|
||||
{
|
||||
value = ConvertRenderColorAndTransparencyToSpeckle(
|
||||
materialName,
|
||||
renderTransparency,
|
||||
renderColor,
|
||||
renderMaterialId
|
||||
),
|
||||
objects = [finalId]
|
||||
value = CreateRenderMaterial(materialName, renderTransparency, renderColor, renderMaterialId),
|
||||
objects = [!string.IsNullOrEmpty(hashId) ? hashId : finalId]
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -145,7 +208,7 @@ public class NavisworksMaterialUnpacker(
|
||||
return renderMaterialProxies.Values.ToList();
|
||||
}
|
||||
|
||||
private static RenderMaterial ConvertRenderColorAndTransparencyToSpeckle(
|
||||
private static RenderMaterial CreateRenderMaterial(
|
||||
string name,
|
||||
double transparency,
|
||||
NAV.Color navisworksColor,
|
||||
@@ -156,7 +219,9 @@ 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,
|
||||
|
||||
+43
-44
@@ -6,6 +6,7 @@ using Speckle.Connectors.Common.Caching;
|
||||
using Speckle.Connectors.Common.Conversion;
|
||||
using Speckle.Connectors.Common.Operations;
|
||||
using Speckle.Converter.Navisworks.Helpers;
|
||||
using Speckle.Converter.Navisworks.Services;
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Objects.Data;
|
||||
@@ -25,7 +26,9 @@ public class NavisworksRootObjectBuilder(
|
||||
ISdkActivityFactory activityFactory,
|
||||
NavisworksMaterialUnpacker materialUnpacker,
|
||||
NavisworksColorUnpacker colorUnpacker,
|
||||
IElementSelectionService elementSelectionService
|
||||
IElementSelectionService elementSelectionService,
|
||||
IUiUnitsCache uiUnitsCache,
|
||||
InstanceStoreManager instanceStoreManager
|
||||
) : IRootObjectBuilder<NAV.ModelItem>
|
||||
{
|
||||
private bool SkipNodeMerging { get; set; }
|
||||
@@ -40,32 +43,45 @@ 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 = false;
|
||||
SkipNodeMerging = true;
|
||||
#endif
|
||||
using var activity = activityFactory.Start("Build");
|
||||
|
||||
ValidateInputs(navisworksModelItems, projectId, onOperationProgressed);
|
||||
|
||||
// 2. Initialize root collection
|
||||
var rootCollection = InitializeRootCollection();
|
||||
|
||||
// 3. Convert all model items and store results
|
||||
var (convertedElements, conversionResults) = await ConvertModelItemsAsync(
|
||||
navisworksModelItems,
|
||||
projectId,
|
||||
onOperationProgressed,
|
||||
cancellationToken
|
||||
);
|
||||
(Dictionary<string, Base?> convertedElements, List<SendConversionResult> conversionResults) =
|
||||
await ConvertModelItemsAsync(navisworksModelItems, projectId, onOperationProgressed, cancellationToken);
|
||||
|
||||
ValidateConversionResults(conversionResults);
|
||||
|
||||
var groupedNodes = SkipNodeMerging ? [] : GroupSiblingGeometryNodes(navisworksModelItems);
|
||||
var finalElements = BuildFinalElements(convertedElements, groupedNodes);
|
||||
List<Base> geometryDefinitions = instanceStoreManager.GetGeometryDefinitions();
|
||||
|
||||
await AddProxiesToCollection(rootCollection, navisworksModelItems, groupedNodes);
|
||||
|
||||
rootCollection.elements = finalElements;
|
||||
var geometryDefinitionsCollection = new Collection
|
||||
{
|
||||
name = "Geometry Definitions",
|
||||
["units"] = converterSettings.Current.Derived.SpeckleUnits,
|
||||
elements = geometryDefinitions
|
||||
};
|
||||
|
||||
var mainElementsCollection = new Collection
|
||||
{
|
||||
name = rootCollection.name,
|
||||
["units"] = converterSettings.Current.Derived.SpeckleUnits,
|
||||
elements = finalElements
|
||||
};
|
||||
|
||||
rootCollection.elements = [mainElementsCollection];
|
||||
if (geometryDefinitions.Count > 0)
|
||||
{
|
||||
rootCollection.elements.Add(geometryDefinitionsCollection);
|
||||
}
|
||||
|
||||
return new RootObjectBuilderResult(rootCollection, conversionResults);
|
||||
}
|
||||
|
||||
@@ -137,12 +153,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(
|
||||
@@ -151,12 +165,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;
|
||||
}
|
||||
@@ -213,24 +224,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
|
||||
{
|
||||
@@ -238,16 +242,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)
|
||||
@@ -255,14 +254,16 @@ public class NavisworksRootObjectBuilder(
|
||||
return null;
|
||||
}
|
||||
|
||||
(string name, string path) = GetContext(convertedBase.applicationId);
|
||||
(string name, string path) = GetElementNameAndPath(convertedBase.applicationId);
|
||||
|
||||
var units = uiUnitsCache.Ensure();
|
||||
|
||||
return new NavisworksObject
|
||||
{
|
||||
name = name,
|
||||
displayValue = convertedBase["displayValue"] as List<Base> ?? [],
|
||||
properties = convertedBase["properties"] as Dictionary<string, object?> ?? [],
|
||||
units = converterSettings.Current.Derived.SpeckleUnits,
|
||||
units = units.ToString(),
|
||||
applicationId = convertedBase.applicationId,
|
||||
["path"] = path
|
||||
};
|
||||
@@ -288,18 +289,16 @@ public class NavisworksRootObjectBuilder(
|
||||
rootCollection[ProxyKeys.COLOR] = colors;
|
||||
}
|
||||
|
||||
var instanceDefinitionProxies = instanceStoreManager.GetInstanceDefinitionProxies();
|
||||
|
||||
if (instanceDefinitionProxies.Count > 0)
|
||||
{
|
||||
rootCollection[ProxyKeys.INSTANCE_DEFINITION] = instanceDefinitionProxies.ToList();
|
||||
}
|
||||
|
||||
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
-1
@@ -58,6 +58,7 @@ public static class ServiceRegistration
|
||||
serviceCollection.AddScoped<SendOperation<DocumentToConvert>>();
|
||||
serviceCollection.AddScoped<ElementUnpacker>();
|
||||
serviceCollection.AddScoped<LevelUnpacker>();
|
||||
serviceCollection.AddScoped<ViewUnpacker>();
|
||||
serviceCollection.AddScoped<SendCollectionManager>();
|
||||
serviceCollection.AddScoped<IRootObjectBuilder<DocumentToConvert>, RevitRootObjectBuilder>();
|
||||
serviceCollection.AddSingleton<ISendConversionCache, SendConversionCache>();
|
||||
@@ -74,7 +75,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();
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
using Autodesk.Revit.DB;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Sdk;
|
||||
|
||||
namespace Speckle.Connectors.Revit.HostApp;
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks Revit Views for sending
|
||||
/// </summary>
|
||||
|
||||
public class ViewUnpacker
|
||||
{
|
||||
private readonly ILogger<ViewUnpacker> _logger;
|
||||
private readonly Converters.Common.IRootToSpeckleConverter _rootToSpeckleConverter;
|
||||
|
||||
public ViewUnpacker(Converters.Common.IRootToSpeckleConverter rootToSpeckleConverter, ILogger<ViewUnpacker> logger)
|
||||
{
|
||||
_rootToSpeckleConverter = rootToSpeckleConverter;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private Camera? ConvertViewToCamera(View3D view)
|
||||
{
|
||||
try
|
||||
{
|
||||
var converted = (Camera)_rootToSpeckleConverter.Convert(view);
|
||||
if (converted is null)
|
||||
{
|
||||
_logger.LogError("Failed to create a view from {view}", view.Name);
|
||||
return null;
|
||||
}
|
||||
|
||||
return converted;
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal())
|
||||
{
|
||||
_logger.LogError(ex, "Failed to create a view from {view}", view.Name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates through the 3D views in the provided document to create cameras
|
||||
/// </summary>
|
||||
/// <param name="doc">Document to retrieve 3D views from</param>
|
||||
/// <returns></returns>
|
||||
public List<Camera> Unpack(Document doc)
|
||||
{
|
||||
List<Camera> cameras = new();
|
||||
using FilteredElementCollector collector = new(doc);
|
||||
List<View> views = collector
|
||||
.WhereElementIsNotElementType()
|
||||
.OfCategory(BuiltInCategory.OST_Views)
|
||||
.Cast<View>()
|
||||
.Where(x => x.ViewType == ViewType.ThreeD)
|
||||
.ToList();
|
||||
|
||||
foreach (View view in views)
|
||||
{
|
||||
if (view is not View3D view3D)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// not supporting parallel project yet, since it is too complex to match in the viewer for now
|
||||
try
|
||||
{
|
||||
if (!view3D.IsPerspective)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (Autodesk.Revit.Exceptions.InvalidOperationException)
|
||||
{
|
||||
continue; // some threed views will throw an exception: returns true if view is not a view template
|
||||
}
|
||||
|
||||
if (ConvertViewToCamera(view3D) is Camera camera)
|
||||
{
|
||||
cameras.Add(camera);
|
||||
}
|
||||
}
|
||||
|
||||
return cameras;
|
||||
}
|
||||
}
|
||||
+9
@@ -24,6 +24,7 @@ public class RevitRootObjectBuilder(
|
||||
ISendConversionCache sendConversionCache,
|
||||
ElementUnpacker elementUnpacker,
|
||||
LevelUnpacker levelUnpacker,
|
||||
ViewUnpacker viewUnpacker,
|
||||
IThreadContext threadContext,
|
||||
SendCollectionManager sendCollectionManager,
|
||||
ILogger<RevitRootObjectBuilder> logger,
|
||||
@@ -240,6 +241,7 @@ public class RevitRootObjectBuilder(
|
||||
throw new SpeckleException("Failed to convert all objects.");
|
||||
}
|
||||
|
||||
// STEP 5: Unpack proxies to attach to root collection
|
||||
var flatElements = atomicObjectsByDocumentAndTransform.SelectMany(t => t.Elements).ToList();
|
||||
var idsAndSubElementIds = elementUnpacker.GetElementsAndSubelementIdsFromAtomicObjects(flatElements);
|
||||
|
||||
@@ -260,6 +262,13 @@ public class RevitRootObjectBuilder(
|
||||
}
|
||||
);
|
||||
|
||||
// STEP 6: Unpack all other objects to attach to root collection
|
||||
List<Objects.Other.Camera> views = viewUnpacker.Unpack(converterSettings.Current.Document);
|
||||
if (views.Count > 0)
|
||||
{
|
||||
rootObject[RootKeys.VIEW] = views;
|
||||
}
|
||||
|
||||
// NOTE: these are currently not used anywhere, we'll skip them until someone calls for it back
|
||||
// rootObject[ProxyKeys.PARAMETER_DEFINITIONS] = _parameterDefinitionHandler.Definitions;
|
||||
|
||||
|
||||
+2
-1
@@ -24,6 +24,7 @@
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\LevelUnpacker.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\LinkedModelHandler.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitMaterialBaker.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\ViewUnpacker.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\SupportedCategoriesUtils.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitViewManager.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\HideWarningsFailuresPreprocessor.cs" />
|
||||
@@ -60,4 +61,4 @@
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Plugin\RevitCefPlugin.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Plugin\SpeckleRevitTaskException.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -325,6 +325,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -325,6 +325,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
+79
-63
@@ -447,73 +447,89 @@ public sealed class ReceiveComponentWorker : WorkerInstance<ReceiveAsyncComponen
|
||||
}
|
||||
|
||||
using var scope = PriorityLoader.CreateScopeForActiveDocument();
|
||||
Root = await scope
|
||||
.Get<GrasshopperReceiveOperation>()
|
||||
.ReceiveCommitObject(receiveInfo, progress, CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
CancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
SpecklePropertyGroupGoo? rootPropertiesGoo = null;
|
||||
if (Root is RootCollection rootCollection && rootCollection.properties.Count > 0)
|
||||
try
|
||||
{
|
||||
rootPropertiesGoo = new SpecklePropertyGroupGoo(rootCollection.properties);
|
||||
Root = await scope
|
||||
.Get<GrasshopperReceiveOperation>()
|
||||
.ReceiveCommitObject(receiveInfo, progress, CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
CancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
SpecklePropertyGroupGoo? rootPropertiesGoo = null;
|
||||
if (Root is RootCollection rootCollection && rootCollection.properties.Count > 0)
|
||||
{
|
||||
rootPropertiesGoo = new SpecklePropertyGroupGoo(rootCollection.properties);
|
||||
}
|
||||
|
||||
// Step 2 - CONVERT
|
||||
//receiveComponent.Message = $"Unpacking...";
|
||||
SpeckleConversionContext.SetupCurrent(scope);
|
||||
|
||||
var unpackedRoot = scope.Get<RootObjectUnpacker>().Unpack(Root);
|
||||
|
||||
// separate atomic objects from block instances
|
||||
var (atomicObjects, blockInstances) = scope
|
||||
.Get<RootObjectUnpacker>()
|
||||
.SplitAtomicObjectsAndInstances(unpackedRoot.ObjectsToConvert);
|
||||
|
||||
// initialize unpackers and collection builder (data holders - created with new)
|
||||
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
|
||||
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
|
||||
var collectionRebuilder = new GrasshopperCollectionRebuilder(
|
||||
(Root as Collection) ?? new Collection { name = "unnamed" }
|
||||
);
|
||||
|
||||
// get handler from DI and initialize with per-operation data
|
||||
var mapHandler = scope
|
||||
.Get<LocalToGlobalMapHandler>()
|
||||
.Initialize(
|
||||
scope.Get<TraversalContextUnpacker>(),
|
||||
colorUnpacker,
|
||||
materialUnpacker,
|
||||
collectionRebuilder,
|
||||
unpackedRoot.DefinitionProxies
|
||||
);
|
||||
|
||||
// handler deals with two-pass conversion: normal objects first, then DataObjects with InstanceProxies
|
||||
mapHandler.ConvertAtomicObjects(atomicObjects);
|
||||
|
||||
// process block instances using converted atomic objects
|
||||
// internally filters out InstanceProxies that belong to registered DataObjects
|
||||
// block processing needs converted objects, but object filtering needs block definitions.
|
||||
mapHandler.ConvertBlockInstances(blockInstances);
|
||||
|
||||
Result = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
|
||||
RootProperties = rootPropertiesGoo;
|
||||
|
||||
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
|
||||
var customProperties = new Dictionary<string, object>()
|
||||
{
|
||||
{ "isAsync", true },
|
||||
{ "sourceHostApp", HostApplications.GetSlugFromHostAppNameAndVersion(receiveInfo.SourceApplication) },
|
||||
{ "auto", Parent.AutoReceive }
|
||||
};
|
||||
if (receiveInfo.WorkspaceId != null)
|
||||
{
|
||||
customProperties.Add("workspace_id", receiveInfo.WorkspaceId);
|
||||
}
|
||||
|
||||
if (receiveInfo.SelectedVersionUserId != null)
|
||||
{
|
||||
customProperties.Add(
|
||||
"isMultiplayer",
|
||||
receiveInfo.SelectedVersionUserId != Parent.ApiClient.Account.userInfo.id
|
||||
);
|
||||
}
|
||||
|
||||
await scope
|
||||
.Get<IMixPanelManager>()
|
||||
.TrackEvent(MixPanelEvents.Receive, Parent.ApiClient.Account, customProperties);
|
||||
}
|
||||
|
||||
// Step 2 - CONVERT
|
||||
//receiveComponent.Message = $"Unpacking...";
|
||||
TraversalContextUnpacker traversalContextUnpacker = new();
|
||||
var unpackedRoot = scope.Get<RootObjectUnpacker>().Unpack(Root);
|
||||
|
||||
// separate atomic objects from block instances
|
||||
var (atomicObjects, blockInstances) = scope
|
||||
.Get<RootObjectUnpacker>()
|
||||
.SplitAtomicObjectsAndInstances(unpackedRoot.ObjectsToConvert);
|
||||
|
||||
// initialize unpackers and collection builder
|
||||
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
|
||||
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
|
||||
var collectionRebuilder = new GrasshopperCollectionRebuilder(
|
||||
(Root as Collection) ?? new Collection { name = "unnamed" }
|
||||
);
|
||||
|
||||
// convert atomic objects directly
|
||||
var mapHandler = new LocalToGlobalMapHandler(
|
||||
traversalContextUnpacker,
|
||||
collectionRebuilder,
|
||||
colorUnpacker,
|
||||
materialUnpacker
|
||||
);
|
||||
|
||||
foreach (var atomicContext in atomicObjects)
|
||||
finally
|
||||
{
|
||||
mapHandler.ConvertAtomicObject(atomicContext);
|
||||
SpeckleConversionContext.EndCurrent();
|
||||
}
|
||||
|
||||
// process block instances using converted atomic objects
|
||||
// block processing needs converted objects, but object filtering needs block definitions.
|
||||
mapHandler.ConvertBlockInstances(blockInstances, unpackedRoot.DefinitionProxies);
|
||||
|
||||
Result = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
|
||||
RootProperties = rootPropertiesGoo;
|
||||
|
||||
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
|
||||
var customProperties = new Dictionary<string, object>()
|
||||
{
|
||||
{ "isAsync", true },
|
||||
{ "sourceHostApp", HostApplications.GetSlugFromHostAppNameAndVersion(receiveInfo.SourceApplication) },
|
||||
{ "auto", Parent.AutoReceive }
|
||||
};
|
||||
if (receiveInfo.WorkspaceId != null)
|
||||
{
|
||||
customProperties.Add("workspace_id", receiveInfo.WorkspaceId);
|
||||
}
|
||||
|
||||
if (receiveInfo.SelectedVersionUserId != null)
|
||||
{
|
||||
customProperties.Add("isMultiplayer", receiveInfo.SelectedVersionUserId != Parent.ApiClient.Account.userInfo.id);
|
||||
}
|
||||
await scope.Get<IMixPanelManager>().TrackEvent(MixPanelEvents.Receive, Parent.ApiClient.Account, customProperties);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+91
-84
@@ -143,95 +143,102 @@ public class ReceiveComponent : SpeckleTaskCapableComponent<ReceiveComponentInpu
|
||||
}
|
||||
|
||||
using var scope = PriorityLoader.CreateScopeForActiveDocument();
|
||||
var clientFactory = scope.ServiceProvider.GetRequiredService<IClientFactory>();
|
||||
var receiveOperation = scope.ServiceProvider.GetRequiredService<GrasshopperReceiveOperation>();
|
||||
|
||||
// Do the thing 👇🏼
|
||||
|
||||
Account? account = input.Resource.Account.GetAccount(scope);
|
||||
if (account is null)
|
||||
try
|
||||
{
|
||||
throw new SpeckleAccountManagerException("No default account was found");
|
||||
var clientFactory = scope.ServiceProvider.GetRequiredService<IClientFactory>();
|
||||
var receiveOperation = scope.ServiceProvider.GetRequiredService<GrasshopperReceiveOperation>();
|
||||
|
||||
// Do the thing 👇🏼
|
||||
|
||||
Account? account = input.Resource.Account.GetAccount(scope);
|
||||
if (account is null)
|
||||
{
|
||||
throw new SpeckleAccountManagerException("No default account was found");
|
||||
}
|
||||
|
||||
using var client = clientFactory.Create(account);
|
||||
var receiveInfo = await input.Resource.GetReceiveInfo(client, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// store version id for tracking
|
||||
_lastVersionId = receiveInfo.SelectedVersionId;
|
||||
|
||||
var progress = new Progress<CardProgress>(_ =>
|
||||
{
|
||||
// TODO: Progress only makes sense in non-blocking async receive, which is not supported yet.
|
||||
// Message = $"{progress.Status}: {progress.Progress}";
|
||||
});
|
||||
|
||||
var root = await receiveOperation
|
||||
.ReceiveCommitObject(receiveInfo, progress, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// extract model-wide root properties (see cnx-2722)
|
||||
SpecklePropertyGroupGoo? rootPropertiesGoo = null;
|
||||
if (root is RootCollection rootCollection && rootCollection.properties.Count > 0)
|
||||
{
|
||||
rootPropertiesGoo = new SpecklePropertyGroupGoo(rootCollection.properties);
|
||||
}
|
||||
|
||||
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
|
||||
var customProperties = new Dictionary<string, object>
|
||||
{
|
||||
{ "isAsync", false },
|
||||
{ "sourceHostApp", HostApplications.GetSlugFromHostAppNameAndVersion(receiveInfo.SourceApplication) }
|
||||
};
|
||||
if (receiveInfo.WorkspaceId != null)
|
||||
{
|
||||
customProperties.Add("workspace_id", receiveInfo.WorkspaceId);
|
||||
}
|
||||
if (receiveInfo.SelectedVersionUserId != null)
|
||||
{
|
||||
customProperties.Add("isMultiplayer", receiveInfo.SelectedVersionUserId != client.Account.userInfo.id);
|
||||
}
|
||||
var mixpanel = PriorityLoader.Container.GetRequiredService<IMixPanelManager>();
|
||||
await mixpanel.TrackEvent(MixPanelEvents.Receive, account, customProperties);
|
||||
|
||||
// Setup conversion context BEFORE unpacking (which triggers DataObjectConverter)
|
||||
SpeckleConversionContext.SetupCurrent(scope);
|
||||
|
||||
var rootObjectUnpacker = scope.ServiceProvider.GetService<RootObjectUnpacker>();
|
||||
var unpackedRoot = rootObjectUnpacker.Unpack(root);
|
||||
|
||||
// split atomic objects from block components before conversion
|
||||
var (atomicObjects, blockInstances) = rootObjectUnpacker.SplitAtomicObjectsAndInstances(
|
||||
unpackedRoot.ObjectsToConvert
|
||||
);
|
||||
|
||||
// Initialize unpackers and collection builder (data holders - created with new)
|
||||
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
|
||||
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
|
||||
var collectionRebuilder = new GrasshopperCollectionRebuilder(
|
||||
(root as Collection) ?? new Collection { name = "unnamed" }
|
||||
);
|
||||
|
||||
// get handler from DI and initialize with per-operation data
|
||||
var mapHandler = scope
|
||||
.ServiceProvider.GetRequiredService<LocalToGlobalMapHandler>()
|
||||
.Initialize(
|
||||
scope.ServiceProvider.GetRequiredService<TraversalContextUnpacker>(),
|
||||
colorUnpacker,
|
||||
materialUnpacker,
|
||||
collectionRebuilder,
|
||||
unpackedRoot.DefinitionProxies
|
||||
);
|
||||
|
||||
// two-pass conversion: normal objects first, then DataObjects with InstanceProxies
|
||||
mapHandler.ConvertAtomicObjects(atomicObjects);
|
||||
|
||||
// process block instances (internally filters InstanceProxies belonging to registered DataObjects)
|
||||
mapHandler.ConvertBlockInstances(blockInstances);
|
||||
|
||||
var goo = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
|
||||
return new ReceiveComponentOutput { RootObject = goo, RootProperties = rootPropertiesGoo };
|
||||
}
|
||||
|
||||
using var client = clientFactory.Create(account);
|
||||
var receiveInfo = await input.Resource.GetReceiveInfo(client, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// store version id for tracking
|
||||
_lastVersionId = receiveInfo.SelectedVersionId;
|
||||
|
||||
var progress = new Progress<CardProgress>(_ =>
|
||||
finally
|
||||
{
|
||||
// TODO: Progress only makes sense in non-blocking async receive, which is not supported yet.
|
||||
// Message = $"{progress.Status}: {progress.Progress}";
|
||||
});
|
||||
|
||||
var root = await receiveOperation
|
||||
.ReceiveCommitObject(receiveInfo, progress, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// extract model-wide root properties (see cnx-2722)
|
||||
SpecklePropertyGroupGoo? rootPropertiesGoo = null;
|
||||
if (root is RootCollection rootCollection && rootCollection.properties.Count > 0)
|
||||
{
|
||||
rootPropertiesGoo = new SpecklePropertyGroupGoo(rootCollection.properties);
|
||||
SpeckleConversionContext.EndCurrent();
|
||||
}
|
||||
|
||||
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
|
||||
var customProperties = new Dictionary<string, object>
|
||||
{
|
||||
{ "isAsync", false },
|
||||
{ "sourceHostApp", HostApplications.GetSlugFromHostAppNameAndVersion(receiveInfo.SourceApplication) }
|
||||
};
|
||||
if (receiveInfo.WorkspaceId != null)
|
||||
{
|
||||
customProperties.Add("workspace_id", receiveInfo.WorkspaceId);
|
||||
}
|
||||
if (receiveInfo.SelectedVersionUserId != null)
|
||||
{
|
||||
customProperties.Add("isMultiplayer", receiveInfo.SelectedVersionUserId != client.Account.userInfo.id);
|
||||
}
|
||||
var mixpanel = PriorityLoader.Container.GetRequiredService<IMixPanelManager>();
|
||||
await mixpanel.TrackEvent(MixPanelEvents.Receive, account, customProperties);
|
||||
|
||||
// We need to rethink these lovely unpackers, there's a bit too many of 'em
|
||||
var rootObjectUnpacker = scope.ServiceProvider.GetService<RootObjectUnpacker>();
|
||||
var traversalContextUnpacker = new TraversalContextUnpacker();
|
||||
|
||||
var unpackedRoot = rootObjectUnpacker.Unpack(root);
|
||||
|
||||
// split atomic objects from block components before conversion
|
||||
var (atomicObjects, blockInstances) = rootObjectUnpacker.SplitAtomicObjectsAndInstances(
|
||||
unpackedRoot.ObjectsToConvert
|
||||
);
|
||||
|
||||
// Initialize unpackers and collection builder
|
||||
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
|
||||
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
|
||||
var collectionRebuilder = new GrasshopperCollectionRebuilder(
|
||||
(root as Collection) ?? new Collection { name = "unnamed" }
|
||||
);
|
||||
|
||||
// convert atomic objects directly
|
||||
var mapHandler = new LocalToGlobalMapHandler(
|
||||
traversalContextUnpacker,
|
||||
collectionRebuilder,
|
||||
colorUnpacker,
|
||||
materialUnpacker
|
||||
);
|
||||
|
||||
foreach (var atomicContext in atomicObjects)
|
||||
{
|
||||
mapHandler.ConvertAtomicObject(atomicContext);
|
||||
}
|
||||
|
||||
// process block instances using converted atomic objects
|
||||
// block processing needs converted objects, but object filtering needs block definitions.
|
||||
mapHandler.ConvertBlockInstances(blockInstances, unpackedRoot.DefinitionProxies);
|
||||
|
||||
// var x = new SpeckleCollectionGoo { Value = collGen.RootCollection };
|
||||
var goo = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
|
||||
return new ReceiveComponentOutput { RootObject = goo, RootProperties = rootPropertiesGoo };
|
||||
}
|
||||
|
||||
private void SetupSubscription(SpeckleUrlModelResource resource)
|
||||
|
||||
+3
-2
@@ -29,13 +29,13 @@ public class SpeckleConversionContext(IRootToSpeckleConverter speckleConverter,
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetupCurrent()
|
||||
public static void SetupCurrent(IServiceScope? scope = null)
|
||||
{
|
||||
if (s_currentContext != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
s_scope = PriorityLoader.CreateScopeForActiveDocument();
|
||||
s_scope = scope ?? PriorityLoader.CreateScopeForActiveDocument();
|
||||
s_currentContext = s_scope.Get<SpeckleConversionContext>();
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ public class SpeckleConversionContext(IRootToSpeckleConverter speckleConverter,
|
||||
{
|
||||
GeometryBase geometry => [(geometry, input)],
|
||||
List<GeometryBase> geometryList => geometryList.Select(o => ((object)o, input)).ToList(),
|
||||
List<(GeometryBase, Base)> pairList when pairList.Count == 0 => [],
|
||||
IEnumerable<(GeometryBase, Base)> fallbackConversionResult
|
||||
=> fallbackConversionResult.Select(o => ((object)o.Item1, o.Item2)).ToList(),
|
||||
object obj => [(obj, input)],
|
||||
|
||||
+328
-118
@@ -1,12 +1,18 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Rhino.Geometry;
|
||||
using Speckle.Connectors.Common.Operations.Receive;
|
||||
using Speckle.Connectors.GrasshopperShared.HostApp;
|
||||
using Speckle.Connectors.GrasshopperShared.Operations.Receive;
|
||||
using Speckle.Connectors.GrasshopperShared.Parameters;
|
||||
using Speckle.Converters.Common.ToHost;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Collections;
|
||||
using Speckle.Sdk.Models.GraphTraversal;
|
||||
using Speckle.Sdk.Models.Instances;
|
||||
using DataObject = Speckle.Objects.Data.DataObject;
|
||||
|
||||
namespace Speckle.Connectors.GrasshopperShared.Operations.Receive;
|
||||
|
||||
/// <summary>
|
||||
/// Handles conversion of atomic objects from TraversalContexts into Grasshopper wrapper objects.
|
||||
@@ -19,184 +25,388 @@ using Speckle.Sdk.Models.Instances;
|
||||
internal sealed class LocalToGlobalMapHandler
|
||||
{
|
||||
public Dictionary<string, SpeckleGeometryWrapper> ConvertedObjectsMap { get; } = new();
|
||||
public readonly GrasshopperCollectionRebuilder CollectionRebuilder;
|
||||
|
||||
private readonly TraversalContextUnpacker _traversalContextUnpacker;
|
||||
private readonly GrasshopperColorUnpacker _colorUnpacker;
|
||||
private readonly GrasshopperMaterialUnpacker _materialUnpacker;
|
||||
// injected via constructor (DI-managed)
|
||||
private readonly IDataObjectInstanceRegistry _dataObjectInstanceRegistry;
|
||||
private readonly ILogger<LocalToGlobalMapHandler> _logger;
|
||||
|
||||
// set via Initialize() method (per-operation data)
|
||||
private TraversalContextUnpacker _traversalContextUnpacker = null!;
|
||||
private GrasshopperColorUnpacker _colorUnpacker = null!;
|
||||
private GrasshopperMaterialUnpacker _materialUnpacker = null!;
|
||||
private IReadOnlyCollection<InstanceDefinitionProxy>? _definitionProxies;
|
||||
|
||||
// auto property (fixes IDE0032)
|
||||
public GrasshopperCollectionRebuilder CollectionRebuilder { get; private set; } = null!;
|
||||
|
||||
public LocalToGlobalMapHandler(
|
||||
IDataObjectInstanceRegistry dataObjectInstanceRegistry,
|
||||
ILogger<LocalToGlobalMapHandler> logger
|
||||
)
|
||||
{
|
||||
_dataObjectInstanceRegistry = dataObjectInstanceRegistry;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the handler with per-operation data.
|
||||
/// Must be called before using ConvertAtomicObjects or ConvertBlockInstances.
|
||||
/// </summary>
|
||||
public LocalToGlobalMapHandler Initialize(
|
||||
TraversalContextUnpacker traversalContextUnpacker,
|
||||
GrasshopperCollectionRebuilder collectionRebuilder,
|
||||
GrasshopperColorUnpacker colorUnpacker,
|
||||
GrasshopperMaterialUnpacker materialUnpacker
|
||||
GrasshopperMaterialUnpacker materialUnpacker,
|
||||
GrasshopperCollectionRebuilder collectionRebuilder,
|
||||
IReadOnlyCollection<InstanceDefinitionProxy>? definitionProxies
|
||||
)
|
||||
{
|
||||
_traversalContextUnpacker = traversalContextUnpacker;
|
||||
_colorUnpacker = colorUnpacker;
|
||||
_materialUnpacker = materialUnpacker;
|
||||
CollectionRebuilder = collectionRebuilder;
|
||||
_definitionProxies = definitionProxies;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts atomic object from TraversalContext to SpeckleObjectWrapper.
|
||||
/// Converts all atomic objects in two passes:
|
||||
/// Pass 1 - Convert normal objects and populate ConvertedObjectsMap
|
||||
/// Pass 2 - Resolve registered DataObjects with InstanceProxies using the populated map
|
||||
/// </summary>
|
||||
public void ConvertAtomicObject(TraversalContext atomicContext)
|
||||
public void ConvertAtomicObjects(IEnumerable<TraversalContext> atomicContexts)
|
||||
{
|
||||
// Cache to avoid re-iterating for registered check
|
||||
var atomicList = atomicContexts as IList<TraversalContext> ?? atomicContexts.ToList();
|
||||
|
||||
// Pass 1: Convert all non-registered DataObjects to populate ConvertedObjectsMap
|
||||
foreach (var atomicContext in atomicList)
|
||||
{
|
||||
ConvertObjectToCache(atomicContext);
|
||||
}
|
||||
|
||||
// Pass 2: Process registered DataObjects (definitions now available in ConvertedObjectsMap)
|
||||
foreach (var atomicContext in atomicList)
|
||||
{
|
||||
if (atomicContext.Current is DataObject dataObject)
|
||||
{
|
||||
var dataObjectId = dataObject.applicationId ?? dataObject.id;
|
||||
if (dataObjectId is not null && _dataObjectInstanceRegistry.IsRegistered(dataObjectId))
|
||||
{
|
||||
ResolveDataObjectInstanceProxies(atomicContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts and caches an atomic object for later lookup.
|
||||
/// Skips registered DataObjects (displayValue is InstanceProxy) - they are resolved in ResolveDataObjectInstanceProxies.
|
||||
/// </summary>
|
||||
private void ConvertObjectToCache(TraversalContext atomicContext)
|
||||
{
|
||||
var obj = atomicContext.Current;
|
||||
var objId = obj.applicationId ?? obj.id;
|
||||
|
||||
if (objId == null || ConvertedObjectsMap.ContainsKey(objId))
|
||||
if (objId is null || ConvertedObjectsMap.ContainsKey(objId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// skip registered DataObjects - they'll be processed in second pass
|
||||
if (obj is DataObject dataObject)
|
||||
{
|
||||
var id = dataObject.applicationId ?? dataObject.id.NotNull();
|
||||
if (_dataObjectInstanceRegistry.IsRegistered(id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
List<(object, Base)> converted = SpeckleConversionContext.Current.ConvertToHost(obj);
|
||||
|
||||
if (converted.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// get path and collection
|
||||
var path = _traversalContextUnpacker.GetCollectionPath(atomicContext).ToList();
|
||||
|
||||
// Always create collection - consumed objects will be cleaned up later
|
||||
var objectCollection = CollectionRebuilder.GetOrCreateSpeckleCollectionFromPath(
|
||||
path,
|
||||
_colorUnpacker,
|
||||
_materialUnpacker
|
||||
);
|
||||
|
||||
if (obj is Speckle.Objects.Data.DataObject dataObject)
|
||||
// nothing converted - nothing to do
|
||||
if (converted.Count == 0)
|
||||
{
|
||||
// get color and mat on dataobject first
|
||||
Color? dataObjColor = _colorUnpacker.Cache.TryGetValue(
|
||||
dataObject.applicationId ?? "",
|
||||
out var cachedDataObjColor
|
||||
)
|
||||
? cachedDataObjColor
|
||||
: null;
|
||||
|
||||
SpeckleMaterialWrapper? dataObjMat = _materialUnpacker.Cache.TryGetValue(
|
||||
dataObject.applicationId ?? "",
|
||||
out var cachedDataObjMaterial
|
||||
)
|
||||
? cachedDataObjMaterial
|
||||
: null;
|
||||
|
||||
// get geometries
|
||||
List<SpeckleGeometryWrapper> geometries = new();
|
||||
foreach ((object convertedObj, Base original) in converted)
|
||||
{
|
||||
if (convertedObj is GeometryBase geometryBase)
|
||||
{
|
||||
SpeckleGeometryWrapper wrapper =
|
||||
new()
|
||||
{
|
||||
Base = original,
|
||||
GeometryBase = geometryBase,
|
||||
Color = _colorUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjColor)
|
||||
? cachedObjColor
|
||||
: dataObjColor,
|
||||
Material = _materialUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjMaterial)
|
||||
? cachedObjMaterial
|
||||
: dataObjMat,
|
||||
};
|
||||
|
||||
geometries.Add(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
SpecklePropertyGroupGoo propertyGroup = new();
|
||||
propertyGroup.CastFrom(dataObject.properties);
|
||||
|
||||
// remove the displayvalue of the original dataobject since these are now processed and stored on the wrapper
|
||||
// to prevent storing of duplicate Base
|
||||
dataObject.displayValue.Clear();
|
||||
|
||||
var dataObjectWrapper = new SpeckleDataObjectWrapper()
|
||||
{
|
||||
Base = dataObject,
|
||||
Geometries = geometries,
|
||||
Path = path.Select(p => p.name).ToList(),
|
||||
Parent = objectCollection,
|
||||
Name = dataObject.name,
|
||||
Properties = propertyGroup,
|
||||
ApplicationId = dataObject.applicationId,
|
||||
};
|
||||
|
||||
// Add to collections (not to map since these won't be definition objects)
|
||||
CollectionRebuilder.AppendSpeckleGrasshopperObject(dataObjectWrapper, path, _colorUnpacker, _materialUnpacker);
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
// handle normal DataObject (has converted geometry)
|
||||
if (obj is DataObject normalDataObject)
|
||||
{
|
||||
SpecklePropertyGroupGoo propertyGroup = new();
|
||||
if (obj[Constants.PROPERTIES_PROP] is Dictionary<string, object?> props)
|
||||
{
|
||||
propertyGroup.CastFrom(props);
|
||||
}
|
||||
var geometries = ConvertToGeometryWrappers(converted);
|
||||
var dataObjectWrapper = CreateDataObjectWrapper(normalDataObject, geometries, path, objectCollection);
|
||||
|
||||
foreach ((object convertedObj, Base original) in converted)
|
||||
CollectionRebuilder.AppendSpeckleGrasshopperObject(dataObjectWrapper, path, _colorUnpacker, _materialUnpacker);
|
||||
return;
|
||||
}
|
||||
|
||||
// handle normal geometry (not DataObject)
|
||||
SpecklePropertyGroupGoo propertyGroup = new();
|
||||
if (obj[Constants.PROPERTIES_PROP] is Dictionary<string, object?> props)
|
||||
{
|
||||
propertyGroup.CastFrom(props);
|
||||
}
|
||||
|
||||
foreach ((object convertedObj, Base original) in converted)
|
||||
{
|
||||
if (convertedObj is GeometryBase geometryBase)
|
||||
{
|
||||
if (convertedObj is GeometryBase geometryBase)
|
||||
var wrapper = new SpeckleGeometryWrapper()
|
||||
{
|
||||
var wrapper = new SpeckleGeometryWrapper()
|
||||
{
|
||||
Base = original,
|
||||
Path = path.Select(p => p.name).ToList(),
|
||||
Parent = objectCollection,
|
||||
GeometryBase = geometryBase,
|
||||
Properties = propertyGroup,
|
||||
Name = obj[Constants.NAME_PROP] as string ?? "",
|
||||
Color = _colorUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjColor)
|
||||
? cachedObjColor
|
||||
: null,
|
||||
Material = _materialUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjMaterial)
|
||||
? cachedObjMaterial
|
||||
: null,
|
||||
ApplicationId = objId
|
||||
};
|
||||
Base = original,
|
||||
Path = path.Select(p => p.name).ToList(),
|
||||
Parent = objectCollection,
|
||||
GeometryBase = geometryBase,
|
||||
Properties = propertyGroup,
|
||||
Name = obj[Constants.NAME_PROP] as string ?? "",
|
||||
Color = _colorUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjColor)
|
||||
? cachedObjColor
|
||||
: null,
|
||||
Material = _materialUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjMaterial)
|
||||
? cachedObjMaterial
|
||||
: null,
|
||||
ApplicationId = objId
|
||||
};
|
||||
|
||||
// Always add to both map and collections
|
||||
ConvertedObjectsMap[objId] = wrapper;
|
||||
CollectionRebuilder.AppendSpeckleGrasshopperObject(wrapper, path, _colorUnpacker, _materialUnpacker);
|
||||
}
|
||||
ConvertedObjectsMap[objId] = wrapper;
|
||||
CollectionRebuilder.AppendSpeckleGrasshopperObject(wrapper, path, _colorUnpacker, _materialUnpacker);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal())
|
||||
{
|
||||
// TODO: throw?
|
||||
// don't throw - continue processing other objects
|
||||
_logger.LogError(ex, "Failed to convert object {objectId} of type {objectType}", objId, obj.speckle_type);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a registered DataObject by transforming its InstanceProxy definition objects.
|
||||
/// Requires definition objects to exist in ConvertedObjectsMap (populated by ConvertObjectToCache).
|
||||
/// </summary>
|
||||
private void ResolveDataObjectInstanceProxies(TraversalContext atomicContext)
|
||||
{
|
||||
var obj = atomicContext.Current;
|
||||
if (obj is not DataObject dataObject)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dataObjectId = dataObject.applicationId ?? dataObject.id.NotNull();
|
||||
if (!_dataObjectInstanceRegistry.IsRegistered(dataObjectId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var path = _traversalContextUnpacker.GetCollectionPath(atomicContext).ToList();
|
||||
var objectCollection = CollectionRebuilder.GetOrCreateSpeckleCollectionFromPath(
|
||||
path,
|
||||
_colorUnpacker,
|
||||
_materialUnpacker
|
||||
);
|
||||
|
||||
var entry = _dataObjectInstanceRegistry.GetEntries()[dataObjectId];
|
||||
var resolvedGeometries = ResolveInstanceProxiesToGeometries(entry.InstanceProxies);
|
||||
var dataObjectWrapper = CreateDataObjectWrapper(dataObject, resolvedGeometries, path, objectCollection);
|
||||
|
||||
CollectionRebuilder.AppendSpeckleGrasshopperObject(dataObjectWrapper, path, _colorUnpacker, _materialUnpacker);
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal())
|
||||
{
|
||||
// don't throw - continue processing other DataObjects
|
||||
_logger.LogError(ex, "Failed to resolve DataObject {dataObjectId} with InstanceProxies", dataObjectId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts block instances and definitions from traversal contexts into Grasshopper wrapper objects.
|
||||
/// Automatically filters out InstanceProxies belonging to registered DataObjects.
|
||||
/// Automatically handles cleanup of consumed objects from the collection hierarchy.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Deliberately handles both block conversion AND consumed object cleanup in a single operation.
|
||||
/// Too much, I know, BUT it ensures the cleanup always occurs immediately after block processing without
|
||||
/// requiring receive components to call a separate cleanup method in the correct order.
|
||||
/// </remarks>
|
||||
public void ConvertBlockInstances(
|
||||
IReadOnlyCollection<TraversalContext> blocks,
|
||||
IReadOnlyCollection<InstanceDefinitionProxy>? definitionProxies
|
||||
)
|
||||
public void ConvertBlockInstances(IReadOnlyCollection<TraversalContext> blockInstances)
|
||||
{
|
||||
// build set of registered InstanceProxy IDs for fast lookup
|
||||
var registeredProxyIds = new HashSet<string>();
|
||||
foreach (var entry in _dataObjectInstanceRegistry.GetEntries().Values)
|
||||
{
|
||||
foreach (var proxy in entry.InstanceProxies)
|
||||
{
|
||||
var proxyId = proxy.applicationId ?? proxy.id;
|
||||
if (proxyId is not null)
|
||||
{
|
||||
registeredProxyIds.Add(proxyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// filter out InstanceProxies that belong to registered DataObjects
|
||||
var filteredBlockInstances = blockInstances
|
||||
.Where(tc =>
|
||||
{
|
||||
if (tc.Current is InstanceProxy proxy)
|
||||
{
|
||||
var proxyId = proxy.applicationId ?? proxy.id;
|
||||
return proxyId is null || !registeredProxyIds.Contains(proxyId);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var blockUnpacker = new GrasshopperBlockUnpacker(_traversalContextUnpacker, _colorUnpacker, _materialUnpacker);
|
||||
|
||||
// Get consumed object IDs from unpacker
|
||||
// get consumed object IDs from unpacker
|
||||
var consumedObjectIds = blockUnpacker.UnpackBlocks(
|
||||
blocks,
|
||||
definitionProxies,
|
||||
filteredBlockInstances,
|
||||
_definitionProxies,
|
||||
ConvertedObjectsMap,
|
||||
CollectionRebuilder
|
||||
);
|
||||
|
||||
// Clean up consumed objects from collections
|
||||
// clean up consumed objects from collections
|
||||
CollectionRebuilder.RemoveConsumedObjects(consumedObjectIds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a DataObjectWrapper from a DataObject and its geometries.
|
||||
/// Handles color/material inheritance and property extraction.
|
||||
/// </summary>
|
||||
private SpeckleDataObjectWrapper CreateDataObjectWrapper(
|
||||
DataObject dataObject,
|
||||
List<SpeckleGeometryWrapper> geometries,
|
||||
List<Collection> path,
|
||||
SpeckleCollectionWrapper objectCollection
|
||||
)
|
||||
{
|
||||
// Get color and material on DataObject
|
||||
Color? dataObjColor = _colorUnpacker.Cache.TryGetValue(dataObject.applicationId ?? "", out var cachedDataObjColor)
|
||||
? cachedDataObjColor
|
||||
: null;
|
||||
|
||||
SpeckleMaterialWrapper? dataObjMat = _materialUnpacker.Cache.TryGetValue(
|
||||
dataObject.applicationId ?? "",
|
||||
out var cachedDataObjMaterial
|
||||
)
|
||||
? cachedDataObjMaterial
|
||||
: null;
|
||||
|
||||
// Apply DataObject color/material to geometries that don't have their own
|
||||
foreach (var geometry in geometries)
|
||||
{
|
||||
geometry.Color ??= dataObjColor;
|
||||
geometry.Material ??= dataObjMat;
|
||||
}
|
||||
|
||||
// Create property group
|
||||
SpecklePropertyGroupGoo propertyGroup = new();
|
||||
propertyGroup.CastFrom(dataObject.properties);
|
||||
|
||||
// Clear the displayValue to prevent storing duplicate Base
|
||||
dataObject.displayValue.Clear();
|
||||
|
||||
return new SpeckleDataObjectWrapper()
|
||||
{
|
||||
Base = dataObject,
|
||||
Geometries = geometries,
|
||||
Path = path.Select(p => p.name).ToList(),
|
||||
Parent = objectCollection,
|
||||
Name = dataObject.name,
|
||||
Properties = propertyGroup,
|
||||
ApplicationId = dataObject.applicationId,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves InstanceProxy displayValues to transformed geometries.
|
||||
/// Returns the list of resolved geometries that can be used as DataObject displayValue replacements.
|
||||
/// </summary>
|
||||
private List<SpeckleGeometryWrapper> ResolveInstanceProxiesToGeometries(List<InstanceProxy> instanceProxies)
|
||||
{
|
||||
var resolvedGeometries = new List<SpeckleGeometryWrapper>();
|
||||
|
||||
// build a lookup of definitionId -> definition objects for quick access
|
||||
var definitionObjectsMap = new Dictionary<string, List<string>>();
|
||||
if (_definitionProxies is not null)
|
||||
{
|
||||
foreach (var defProxy in _definitionProxies)
|
||||
{
|
||||
var defId = defProxy.applicationId ?? defProxy.id;
|
||||
if (defId is not null)
|
||||
{
|
||||
definitionObjectsMap[defId] = defProxy.objects;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var instanceProxy in instanceProxies)
|
||||
{
|
||||
// get the definition objects for this instance
|
||||
if (!definitionObjectsMap.TryGetValue(instanceProxy.definitionId, out var definitionObjectIds))
|
||||
{
|
||||
continue; // definition not found, skip this proxy
|
||||
}
|
||||
|
||||
// get transform from the instance proxy
|
||||
var transform = GrasshopperHelpers.MatrixToTransform(instanceProxy.transform, instanceProxy.units);
|
||||
|
||||
// apply transform to each definition object
|
||||
foreach (var objectId in definitionObjectIds)
|
||||
{
|
||||
if (ConvertedObjectsMap.TryGetValue(objectId, out var definitionObject))
|
||||
{
|
||||
// deep copy and transform the geometry
|
||||
var transformedWrapper = definitionObject.DeepCopy();
|
||||
transformedWrapper.GeometryBase.NotNull().Transform(transform);
|
||||
resolvedGeometries.Add(transformedWrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedGeometries;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the raw converted objects to SpeckleGeometryWrappers for DataObject display values.
|
||||
/// Does NOT apply DataObject-level colors/materials - that's handled by CreateDataObjectWrapper.
|
||||
/// </summary>
|
||||
private List<SpeckleGeometryWrapper> ConvertToGeometryWrappers(List<(object, Base)> converted)
|
||||
{
|
||||
var geometries = new List<SpeckleGeometryWrapper>();
|
||||
|
||||
foreach ((object convertedObj, Base original) in converted)
|
||||
{
|
||||
if (convertedObj is GeometryBase geometryBase)
|
||||
{
|
||||
SpeckleGeometryWrapper wrapper =
|
||||
new()
|
||||
{
|
||||
Base = original,
|
||||
GeometryBase = geometryBase,
|
||||
// try to get color/material from the individual geometry first
|
||||
Color = _colorUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjColor)
|
||||
? cachedObjColor
|
||||
: null,
|
||||
Material = _materialUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjMaterial)
|
||||
? cachedObjMaterial
|
||||
: null,
|
||||
};
|
||||
|
||||
geometries.Add(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
return geometries;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ using Speckle.Connectors.GrasshopperShared.Operations.Send;
|
||||
using Speckle.Connectors.GrasshopperShared.Parameters;
|
||||
using Speckle.Connectors.GrasshopperShared.Properties;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.Common.ToHost;
|
||||
using Speckle.Converters.Rhino;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Models.GraphTraversal;
|
||||
@@ -58,6 +59,8 @@ public class PriorityLoader : GH_AssemblyPriority
|
||||
services.AddTransient<GrasshopperReceiveOperation>();
|
||||
services.AddSingleton(DefaultTraversal.CreateTraversalFunc());
|
||||
services.AddTransient<TraversalContextUnpacker>();
|
||||
services.AddScoped<IDataObjectInstanceRegistry, DataObjectInstanceRegistry>();
|
||||
services.AddTransient<LocalToGlobalMapHandler>();
|
||||
|
||||
// send
|
||||
services.AddTransient<IRootObjectBuilder<SpeckleCollectionWrapperGoo>, GrasshopperRootObjectBuilder>();
|
||||
|
||||
@@ -306,6 +306,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -306,6 +306,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -235,6 +235,7 @@
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
|
||||
"Speckle.Connectors.Logging": "[1.0.0, )",
|
||||
"Speckle.Converters.Common": "[1.0.0, )",
|
||||
"Speckle.Objects": "[3.9.0, )",
|
||||
"Speckle.Sdk": "[3.9.0, )",
|
||||
"Speckle.Sdk.Dependencies": "[3.9.0, )"
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Connectors.Rhino.Extensions;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.Common.ToHost;
|
||||
using Speckle.Converters.Rhino;
|
||||
using Speckle.Sdk;
|
||||
|
||||
namespace Speckle.Connectors.Rhino.HostApp;
|
||||
|
||||
/// <summary>
|
||||
/// Groups block instances created from DataObject with InstanceProxies as display values and applies DataObject metadata.
|
||||
/// </summary>
|
||||
public class DataObjectInstanceGrouper
|
||||
{
|
||||
private readonly IConverterSettingsStore<RhinoConversionSettings> _converterSettings;
|
||||
private readonly ILogger<DataObjectInstanceGrouper> _logger;
|
||||
private readonly IDataObjectInstanceRegistry _dataObjectInstanceRegistry;
|
||||
|
||||
public DataObjectInstanceGrouper(
|
||||
IConverterSettingsStore<RhinoConversionSettings> converterSettings,
|
||||
ILogger<DataObjectInstanceGrouper> logger,
|
||||
IDataObjectInstanceRegistry dataObjectInstanceRegistry
|
||||
)
|
||||
{
|
||||
_converterSettings = converterSettings;
|
||||
_logger = logger;
|
||||
_dataObjectInstanceRegistry = dataObjectInstanceRegistry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// After all instances have been created, we then run through the data object instance registry to see which instances
|
||||
/// belonged to a data object. The method then groups all instances to "re-assemble" the original data object and
|
||||
/// applies the properties of the data object on to the instances.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a deferred action and can only occur once the RhinoInstanceBaker has done its thing.
|
||||
/// </remarks>
|
||||
public void GroupAndApplyProperties()
|
||||
{
|
||||
var doc = _converterSettings.Current.Document;
|
||||
var entries = _dataObjectInstanceRegistry.GetEntries(); // see docstring
|
||||
|
||||
foreach (var kvp in entries)
|
||||
{
|
||||
var dataObjectId = kvp.Key;
|
||||
var entry = kvp.Value;
|
||||
try
|
||||
{
|
||||
var instanceIds = _dataObjectInstanceRegistry.GetInstanceIdsForDataObject(dataObjectId);
|
||||
if (instanceIds.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// create group, name the group and apply properties
|
||||
using var dataObjectAtts = entry.DataObject.GetAttributes();
|
||||
var groupName = dataObjectAtts.Name;
|
||||
var groupIndex = doc.Groups.Add(groupName, instanceIds.Select(id => new Guid(id)));
|
||||
|
||||
if (groupIndex >= 0)
|
||||
{
|
||||
// apply properties to each instance (doing this on an instance level because setting to group doesn't work)
|
||||
foreach (var instanceId in instanceIds)
|
||||
{
|
||||
var rhinoObj = doc.Objects.FindId(new Guid(instanceId));
|
||||
if (rhinoObj != null)
|
||||
{
|
||||
// set the name from DataObject
|
||||
rhinoObj.Attributes.Name = dataObjectAtts.Name;
|
||||
|
||||
// copy all user strings
|
||||
foreach (var key in dataObjectAtts.GetUserStrings().AllKeys)
|
||||
{
|
||||
rhinoObj.Attributes.SetUserString(key, dataObjectAtts.GetUserString(key));
|
||||
}
|
||||
rhinoObj.CommitChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal())
|
||||
{
|
||||
_logger.LogError(ex, "Failed to group DataObject instances {dataObjectId}", dataObjectId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using Speckle.Connectors.Common.Conversion;
|
||||
using Speckle.Connectors.Common.Instances;
|
||||
using Speckle.Connectors.Common.Operations;
|
||||
using Speckle.Connectors.Rhino.Extensions;
|
||||
using Speckle.Converters.Common.ToHost;
|
||||
using Speckle.DoubleNumerics;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Common;
|
||||
@@ -22,18 +23,21 @@ public class RhinoInstanceBaker : IInstanceBaker<IReadOnlyCollection<string>>
|
||||
private readonly RhinoLayerBaker _layerBaker;
|
||||
private readonly RhinoColorBaker _colorBaker;
|
||||
private readonly ILogger<RhinoInstanceBaker> _logger;
|
||||
private readonly IDataObjectInstanceRegistry _dataObjectInstanceRegistry;
|
||||
|
||||
public RhinoInstanceBaker(
|
||||
RhinoLayerBaker layerBaker,
|
||||
RhinoMaterialBaker rhinoMaterialBaker,
|
||||
RhinoColorBaker colorBaker,
|
||||
ILogger<RhinoInstanceBaker> logger
|
||||
ILogger<RhinoInstanceBaker> logger,
|
||||
IDataObjectInstanceRegistry dataObjectInstanceRegistry
|
||||
)
|
||||
{
|
||||
_layerBaker = layerBaker;
|
||||
_materialBaker = rhinoMaterialBaker;
|
||||
_colorBaker = colorBaker;
|
||||
_logger = logger;
|
||||
_dataObjectInstanceRegistry = dataObjectInstanceRegistry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -155,6 +159,9 @@ public class RhinoInstanceBaker : IInstanceBaker<IReadOnlyCollection<string>>
|
||||
applicationIdMap[instanceProxyId] = new List<string>() { id.ToString() };
|
||||
createdObjectIds.Add(id.ToString());
|
||||
conversionResults.Add(new(Status.SUCCESS, instanceProxy, id.ToString(), "Instance (Block)"));
|
||||
|
||||
// link this baked instance back to its DataObject if it came from one (the method handles the check)
|
||||
_dataObjectInstanceRegistry.LinkInstanceToDataObject(instanceProxyId, id.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal())
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Rhino.DocObjects;
|
||||
using Rhino.DocObjects.Tables;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Sdk;
|
||||
|
||||
namespace Speckle.Connectors.Rhino.HostApp;
|
||||
|
||||
public class RhinoViewUnpacker
|
||||
{
|
||||
private readonly IRootToSpeckleConverter _rootToSpeckleConverter;
|
||||
private readonly ILogger<RhinoViewUnpacker> _logger;
|
||||
|
||||
public RhinoViewUnpacker(IRootToSpeckleConverter rootToSpeckleConverter, ILogger<RhinoViewUnpacker> logger)
|
||||
{
|
||||
_rootToSpeckleConverter = rootToSpeckleConverter;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private Camera? ConvertViewToCamera(ViewInfo view)
|
||||
{
|
||||
try
|
||||
{
|
||||
var converted = (Speckle.Objects.Other.Camera)_rootToSpeckleConverter.Convert(view);
|
||||
if (converted is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return converted;
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal())
|
||||
{
|
||||
_logger.LogError(ex, "Failed to create a view from {view}", view.Name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates through a given set of rhino named views to create proxies
|
||||
/// </summary>
|
||||
/// <param name="views">current document named views</param>
|
||||
/// <returns></returns>
|
||||
public List<Camera> UnpackViews(NamedViewTable views)
|
||||
{
|
||||
List<Camera> cameras = new();
|
||||
foreach (ViewInfo view in views)
|
||||
{
|
||||
// skip isometric views for now.
|
||||
// getting the orthographic match between host apps and the viewer requires too much effort atm.
|
||||
if (view.Viewport.IsParallelProjection)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ConvertViewToCamera(view) is Camera camera)
|
||||
{
|
||||
cameras.Add(camera);
|
||||
}
|
||||
}
|
||||
|
||||
return cameras;
|
||||
}
|
||||
}
|
||||
+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
|
||||
);
|
||||
|
||||
|
||||
+15
-2
@@ -33,6 +33,7 @@ public class RhinoRootObjectBuilder : IRootObjectBuilder<RhinoObject>
|
||||
private readonly RhinoGroupUnpacker _groupUnpacker;
|
||||
private readonly RhinoMaterialUnpacker _materialUnpacker;
|
||||
private readonly RhinoColorUnpacker _colorUnpacker;
|
||||
private readonly RhinoViewUnpacker _viewUnpacker;
|
||||
private readonly PropertiesExtractor _propertiesExtractor;
|
||||
private readonly ILogger<RhinoRootObjectBuilder> _logger;
|
||||
private readonly ISdkActivityFactory _activityFactory;
|
||||
@@ -46,6 +47,7 @@ public class RhinoRootObjectBuilder : IRootObjectBuilder<RhinoObject>
|
||||
RhinoGroupUnpacker groupUnpacker,
|
||||
RhinoMaterialUnpacker materialUnpacker,
|
||||
RhinoColorUnpacker colorUnpacker,
|
||||
RhinoViewUnpacker viewUnpacker,
|
||||
PropertiesExtractor propertiesExtractor,
|
||||
ILogger<RhinoRootObjectBuilder> logger,
|
||||
ISdkActivityFactory activityFactory
|
||||
@@ -59,6 +61,7 @@ public class RhinoRootObjectBuilder : IRootObjectBuilder<RhinoObject>
|
||||
_rootToSpeckleConverter = rootToSpeckleConverter;
|
||||
_materialUnpacker = materialUnpacker;
|
||||
_colorUnpacker = colorUnpacker;
|
||||
_viewUnpacker = viewUnpacker;
|
||||
_propertiesExtractor = propertiesExtractor;
|
||||
_logger = logger;
|
||||
_activityFactory = activityFactory;
|
||||
@@ -122,20 +125,30 @@ public class RhinoRootObjectBuilder : IRootObjectBuilder<RhinoObject>
|
||||
throw new SpeckleException("Failed to convert all objects."); // fail fast instead creating empty commit! It will appear as model card error with red color.
|
||||
}
|
||||
|
||||
// 4 - Unpack all proxies for the root
|
||||
// Get all layers from the created collections on the root object commit for proxy processing
|
||||
List<Layer> layers = _layerUnpacker.GetUsedLayers().ToList();
|
||||
|
||||
using (var _ = _activityFactory.Start("UnpackRenderMaterials"))
|
||||
{
|
||||
// 4 - Unpack the render material proxies
|
||||
rootObjectCollection[ProxyKeys.RENDER_MATERIAL] = _materialUnpacker.UnpackRenderMaterials(atomicObjects, layers);
|
||||
}
|
||||
|
||||
using (var _ = _activityFactory.Start("UnpackColors"))
|
||||
{
|
||||
// 5 - Unpack the color proxies
|
||||
rootObjectCollection[ProxyKeys.COLOR] = _colorUnpacker.UnpackColors(atomicObjects, layers);
|
||||
}
|
||||
|
||||
// 5 - Unpack all other objects for the root
|
||||
using (var _ = _activityFactory.Start("UnpackViews"))
|
||||
{
|
||||
List<Objects.Other.Camera> views = _viewUnpacker.UnpackViews(_converterSettings.Current.Document.NamedViews);
|
||||
if (views.Count > 0)
|
||||
{
|
||||
rootObjectCollection[RootKeys.VIEW] = views;
|
||||
}
|
||||
}
|
||||
|
||||
return new RootObjectBuilderResult(rootObjectCollection, results);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -91,9 +92,15 @@ public static class ServiceRegistration
|
||||
serviceCollection.AddScoped<RhinoColorBaker>();
|
||||
serviceCollection.AddScoped<RhinoColorUnpacker>();
|
||||
|
||||
serviceCollection.AddScoped<RhinoViewUnpacker>();
|
||||
|
||||
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>();
|
||||
|
||||
+3
-1
@@ -23,7 +23,9 @@
|
||||
<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\RhinoViewUnpacker.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RhinoIdleManager.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RhinoLayerHelper.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RhinoObjectHelper.cs" />
|
||||
@@ -62,4 +64,4 @@
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Plugin\Speckle.Connectors.RhinoCommand.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Plugin\Speckle.Connectors.RhinoPlugin.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
+1
-1
@@ -17,4 +17,4 @@
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Helpers\EtabsShellSectionResolver.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\TopLevel\EtabsObjectToSpeckleConverter.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -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, )"
|
||||
|
||||
+13
-32
@@ -1,38 +1,19 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models;
|
||||
using static Speckle.Converter.Navisworks.Helpers.ElementSelectionHelper;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.ToSpeckle;
|
||||
|
||||
public class DisplayValueExtractor
|
||||
public class DisplayValueExtractor(GeometryToSpeckleConverter geometryConverter)
|
||||
{
|
||||
private readonly IConverterSettingsStore<NavisworksConversionSettings> _converterSettings;
|
||||
private readonly ILogger<DisplayValueExtractor> _logger;
|
||||
private readonly GeometryToSpeckleConverter _geometryConverter;
|
||||
|
||||
public DisplayValueExtractor(
|
||||
IConverterSettingsStore<NavisworksConversionSettings> converterSettings,
|
||||
ILogger<DisplayValueExtractor> logger
|
||||
)
|
||||
{
|
||||
_converterSettings = converterSettings;
|
||||
_logger = logger;
|
||||
_geometryConverter = new GeometryToSpeckleConverter(_converterSettings.Current);
|
||||
}
|
||||
|
||||
internal List<Base> GetDisplayValue(NAV.ModelItem modelItem)
|
||||
{
|
||||
if (modelItem == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelItem));
|
||||
}
|
||||
if (!modelItem.HasGeometry)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return !IsElementVisible(modelItem) ? [] : _geometryConverter.Convert(modelItem);
|
||||
}
|
||||
internal List<Base> GetDisplayValue(NAV.ModelItem modelItem) =>
|
||||
modelItem == null
|
||||
? throw new ArgumentNullException(nameof(modelItem))
|
||||
: !modelItem.HasGeometry
|
||||
? ([])
|
||||
: !IsElementVisible(modelItem)
|
||||
? []
|
||||
:
|
||||
// this can be meshes or the instance reference objects
|
||||
// the un transformed objects stored in a separate collection
|
||||
geometryConverter.Convert(modelItem);
|
||||
}
|
||||
|
||||
+25
-12
@@ -1,10 +1,14 @@
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converter.Navisworks.Services;
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converters.Common;
|
||||
using static Speckle.Converter.Navisworks.Helpers.PropertyHelpers;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.ToSpeckle;
|
||||
|
||||
public class PropertySetsExtractor(IConverterSettingsStore<NavisworksConversionSettings> settingsStore)
|
||||
public class PropertySetsExtractor(
|
||||
IConverterSettingsStore<NavisworksConversionSettings> settingsStore,
|
||||
IPropertyConverter propertyConverter
|
||||
)
|
||||
{
|
||||
internal Dictionary<string, object?>? GetPropertySets(NAV.ModelItem modelItem)
|
||||
{
|
||||
@@ -18,6 +22,17 @@ public class PropertySetsExtractor(IConverterSettingsStore<NavisworksConversionS
|
||||
return propertyDictionary;
|
||||
}
|
||||
|
||||
private static NAV.Units GetModelUnits(NAV.ModelItem modelItem)
|
||||
{
|
||||
NAV.ModelItem? ancestor = modelItem;
|
||||
while (ancestor != null && !ancestor.HasModel)
|
||||
{
|
||||
ancestor = ancestor.Parent;
|
||||
}
|
||||
|
||||
return ancestor != null ? ancestor.Model.Units : NAV.Units.Meters;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts property sets from a NAV.ModelItem and adds them to a dictionary,
|
||||
/// PropertySets are specific to the host application source appended to Navisworks and therefore
|
||||
@@ -28,10 +43,13 @@ public class PropertySetsExtractor(IConverterSettingsStore<NavisworksConversionS
|
||||
private Dictionary<string, object?> ExtractPropertySets(NAV.ModelItem modelItem)
|
||||
{
|
||||
var propertySetDictionary = new Dictionary<string, object?>();
|
||||
var modelUnits = GetModelUnits(modelItem);
|
||||
|
||||
propertyConverter.Reset();
|
||||
|
||||
foreach (var propertyCategory in modelItem.PropertyCategories)
|
||||
{
|
||||
if (IsCategoryToBeSkipped(propertyCategory))
|
||||
if (ShouldSkipCategory(propertyCategory))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -40,23 +58,18 @@ public class PropertySetsExtractor(IConverterSettingsStore<NavisworksConversionS
|
||||
|
||||
foreach (var property in propertyCategory.Properties)
|
||||
{
|
||||
string sanitizedName = SanitizePropertyName(property.DisplayName);
|
||||
var propertyValue = ConvertPropertyValue(property.Value, settingsStore.Current.Derived.SpeckleUnits);
|
||||
|
||||
var sanitizedName = SanitizePropertyName(property.DisplayName);
|
||||
var propertyValue = propertyConverter.ConvertPropertyValue(property.Value, modelUnits, property.DisplayName);
|
||||
if (propertyValue != null)
|
||||
{
|
||||
propertySet[sanitizedName] = propertyValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (propertySet.Count <= 0)
|
||||
if (propertySet.Count > 0)
|
||||
{
|
||||
continue;
|
||||
propertySetDictionary[SanitizePropertyName(propertyCategory.DisplayName)] = propertySet;
|
||||
}
|
||||
|
||||
string categoryName = SanitizePropertyName(propertyCategory.DisplayName);
|
||||
|
||||
propertySetDictionary[categoryName] = propertySet;
|
||||
}
|
||||
|
||||
return propertySetDictionary;
|
||||
|
||||
+13
-11
@@ -1,8 +1,10 @@
|
||||
using static Speckle.Converter.Navisworks.Helpers.PropertyHelpers;
|
||||
using Speckle.Converter.Navisworks.Services;
|
||||
using Speckle.InterfaceGenerator;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.ToSpeckle;
|
||||
|
||||
public sealed class RevitBuiltInCategoryExtractor
|
||||
[GenerateAutoInterface]
|
||||
public class RevitBuiltInCategoryExtractor(IPropertyConverter converter) : IRevitBuiltInCategoryExtractor
|
||||
{
|
||||
private const int ANCESTOR_AND_SELF_COUNT = 4; // It seems like this is the maximum depth found needed in practice
|
||||
private const string REVIT_CAT_GROUP = "LcRevitData_Element";
|
||||
@@ -13,28 +15,28 @@ public sealed class RevitBuiltInCategoryExtractor
|
||||
/// Attempts to map a Navisworks/Revit display category from the given model item or its ancestors
|
||||
/// to a known Revit built-in category constant (e.g., "OST_Walls").
|
||||
/// </summary>
|
||||
internal static bool TryGetBuiltInCategory(
|
||||
NAV.ModelItem item,
|
||||
out string mapped,
|
||||
int maxDepth = ANCESTOR_AND_SELF_COUNT
|
||||
)
|
||||
public bool TryGetBuiltInCategory(NAV.ModelItem item, out string mapped, int maxDepth = ANCESTOR_AND_SELF_COUNT)
|
||||
{
|
||||
mapped = string.Empty;
|
||||
|
||||
// Look up the category value, starting at this item and walking up to maxDepth ancestors
|
||||
// Find the category VariantData up the hierarchy
|
||||
var v = FindRevitCategoryInHierarchy(item, maxDepth);
|
||||
if (v == null)
|
||||
if (v is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var name = ConvertPropertyValue(v, "")?.ToString();
|
||||
converter.Reset();
|
||||
|
||||
// Convert using per-object model units and current UI units
|
||||
var nameObj = converter.ConvertPropertyValue(v, item.Model.Units, item.DisplayName);
|
||||
var name = nameObj?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
name = name?.Trim();
|
||||
name = name!.Trim();
|
||||
|
||||
// Map display name to OST_* built-in category constant
|
||||
var builtInCategory = DisplayNameToRevitBuiltInCategory(name);
|
||||
|
||||
+3
-2
@@ -11,7 +11,8 @@ public class HierarchicalPropertyHandler(
|
||||
PropertySetsExtractor propertySetsExtractor,
|
||||
ModelPropertiesExtractor modelPropertiesExtractor,
|
||||
ClassPropertiesExtractor classPropertiesExtractor,
|
||||
IConverterSettingsStore<NavisworksConversionSettings> settingsStore
|
||||
IConverterSettingsStore<NavisworksConversionSettings> settingsStore,
|
||||
IRevitBuiltInCategoryExtractor revitCategoryExtractor
|
||||
) : BasePropertyHandler(propertySetsExtractor, modelPropertiesExtractor)
|
||||
{
|
||||
private static string PseudoClassPropertiesKey => "_pseudoClassProperties";
|
||||
@@ -22,7 +23,7 @@ public class HierarchicalPropertyHandler(
|
||||
var propertyDict = classPropertiesExtractor.GetClassProperties(modelItem) ?? [];
|
||||
|
||||
// Interop-lite mapping for Revit built-in categories
|
||||
if (_mapRevit && RevitBuiltInCategoryExtractor.TryGetBuiltInCategory(modelItem, out var builtInCategory))
|
||||
if (_mapRevit && revitCategoryExtractor.TryGetBuiltInCategory(modelItem, out var builtInCategory))
|
||||
{
|
||||
PropertyHelpers.AddPropertyIfNotNullOrEmpty(
|
||||
propertyDict,
|
||||
|
||||
+14
@@ -46,6 +46,20 @@ public static class NavisworksConverterServiceRegistration
|
||||
serviceCollection.AddScoped<DisplayValueExtractor>();
|
||||
serviceCollection.AddScoped<GeometryToSpeckleConverter>();
|
||||
|
||||
// Register dual shared geometry stores for instancing pattern (.NET Framework compatible)
|
||||
// Store 1: For geometry definitions (Mesh, Curve, etc.) - Store 2: For InstanceDefinitionProxy objects
|
||||
serviceCollection.AddScoped<InstanceStoreManager>();
|
||||
|
||||
// Register ISharedGeometryStore interface using the geometry definitions store for backward compatibility
|
||||
serviceCollection.AddScoped<ISharedGeometryStore>(provider =>
|
||||
provider.GetRequiredService<InstanceStoreManager>().GeometryDefinitionsStore
|
||||
);
|
||||
|
||||
// Register settings resolved from factory
|
||||
serviceCollection.AddScoped<NavisworksConversionSettings>(sp =>
|
||||
sp.GetRequiredService<INavisworksConversionSettingsFactory>().Current
|
||||
);
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
}
|
||||
|
||||
+32
-24
@@ -1,4 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Speckle.Objects.Geometry;
|
||||
|
||||
@@ -8,6 +8,9 @@ public static class PropertyHelpers
|
||||
{
|
||||
private static readonly HashSet<string> s_excludedCategories = ["Geometry", "Metadata"];
|
||||
|
||||
/// <summary>
|
||||
/// Adds a property to an object (either a Base object or a Dictionary) if the value is not null or empty.
|
||||
/// </summary>
|
||||
private static readonly Dictionary<NAV.VariantDataType, Func<NAV.VariantData, string, dynamic?>> s_typeHandlers =
|
||||
new()
|
||||
{
|
||||
@@ -47,24 +50,15 @@ public static class PropertyHelpers
|
||||
return handler(value, units);
|
||||
}
|
||||
|
||||
// Default case for unsupported types
|
||||
return value.DataType == NAV.VariantDataType.None || value.DataType == NAV.VariantDataType.Point2D
|
||||
? null
|
||||
: value.ToString();
|
||||
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))
|
||||
@@ -80,9 +74,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)
|
||||
@@ -98,16 +89,33 @@ 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);
|
||||
}
|
||||
|
||||
internal static class UnitLabels
|
||||
{
|
||||
internal static string Linear(NAV.Units u) =>
|
||||
u switch
|
||||
{
|
||||
NAV.Units.Kilometers => "Kilometers",
|
||||
NAV.Units.Meters => "Metres",
|
||||
NAV.Units.Centimeters => "Centimeters",
|
||||
NAV.Units.Millimeters => "Millimeters",
|
||||
NAV.Units.Micrometers => "Micrometers",
|
||||
NAV.Units.Miles => "Miles",
|
||||
NAV.Units.Yards => "Yards",
|
||||
NAV.Units.Feet => "Feet",
|
||||
NAV.Units.Inches => "Inches",
|
||||
NAV.Units.Mils => "Mils",
|
||||
NAV.Units.Microinches => "Microinches",
|
||||
_ => "Metres"
|
||||
};
|
||||
|
||||
internal static string Area(NAV.Units u) => $"Square {Linear(u).ToLower()}";
|
||||
|
||||
public static string Volume(NAV.Units u) => $"Cubic {Linear(u).ToLower()}";
|
||||
}
|
||||
|
||||
@@ -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_";
|
||||
}
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Converter.Navisworks.Constants;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Instances;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Simple wrapper class that manages two SharedGeometryStores instances for dual instancing pattern.
|
||||
/// Provides easy access to both mesh definitions store and instance definition proxies store.
|
||||
/// </summary>
|
||||
public class InstanceStoreManager(ILogger<InstanceStoreManager> logger)
|
||||
{
|
||||
private readonly ILogger<InstanceStoreManager> _logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
/// <summary>
|
||||
/// Store for geometry definitions (geometry data) - untransformed base geometries.
|
||||
/// </summary>
|
||||
internal SharedGeometryStore GeometryDefinitionsStore { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Store for InstanceDefinitionProxy objects that reference geometry definitions.
|
||||
/// </summary>
|
||||
internal SharedGeometryStore InstanceDefinitionProxiesStore { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Clears both stores for a new conversion session.
|
||||
/// Should be called at the start of each conversion.
|
||||
/// </summary>
|
||||
public void ClearAll()
|
||||
{
|
||||
GeometryDefinitionsStore.Clear();
|
||||
InstanceDefinitionProxiesStore.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all instance definition proxies from the store, cast to their specific type.
|
||||
/// Useful for adding to root collection at end of conversion.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<InstanceDefinitionProxy> GetInstanceDefinitionProxies()
|
||||
{
|
||||
var proxies = InstanceDefinitionProxiesStore.Geometries.OfType<InstanceDefinitionProxy>().ToList().AsReadOnly();
|
||||
_logger.LogDebug("GetInstanceDefinitionProxies returning {Count} proxies", proxies.Count);
|
||||
return proxies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all geometry definitions from the geometry definitions store.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<Base> GetGeometryDefinitions() => [.. GeometryDefinitionsStore.Geometries.ToList().AsReadOnly()];
|
||||
|
||||
/// <summary>
|
||||
/// Gets a geometry definition by its application ID from the geometry definitions store.
|
||||
/// </summary>
|
||||
/// <returns>The geometry if found, null otherwise.</returns>
|
||||
public Base? GetGeometryDefinition(string fragmentId) =>
|
||||
GeometryDefinitionsStore.Geometries.FirstOrDefault(g =>
|
||||
g.applicationId == $"{InstanceConstants.GEOMETRY_ID_PREFIX}{fragmentId}"
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance definition proxy by its application ID.
|
||||
/// </summary>
|
||||
/// <returns>The instance definition proxy if found, null otherwise.</returns>
|
||||
public InstanceDefinitionProxy? GetInstanceDefinitionProxy(string fragmentId) =>
|
||||
InstanceDefinitionProxiesStore
|
||||
.Geometries.OfType<InstanceDefinitionProxy>()
|
||||
.FirstOrDefault(p => p.applicationId == $"{InstanceConstants.DEFINITION_ID_PREFIX}{fragmentId}");
|
||||
|
||||
/// <summary>
|
||||
/// Adds a geometry definition and corresponding instance definition proxy for shared geometry.
|
||||
/// This is a convenience method that handles both stores in one call.
|
||||
/// </summary>
|
||||
/// <param name="fragmentId">The fragment-based application ID.</param>
|
||||
/// <param name="geometry">The untransformed base geometry.</param>
|
||||
/// <returns>True if both were added (new geometry), false if they already existed.</returns>
|
||||
public bool AddSharedGeometry(string fragmentId, Base geometry)
|
||||
{
|
||||
_logger.LogDebug("AddSharedGeometry called for FragmentId={FragmentId}", fragmentId);
|
||||
|
||||
bool geometryAdded = false;
|
||||
bool proxyAdded = false;
|
||||
|
||||
// Create prefixed IDs for 1:1:1 relationship using base fragment hash
|
||||
var geometryId = $"{InstanceConstants.GEOMETRY_ID_PREFIX}{fragmentId}";
|
||||
var definitionId = $"{InstanceConstants.DEFINITION_ID_PREFIX}{fragmentId}";
|
||||
|
||||
_logger.LogDebug("Using GeometryId={GeometryId}, DefinitionId={DefinitionId}", geometryId, definitionId);
|
||||
|
||||
// Add geometry definition if not exists
|
||||
if (!GeometryDefinitionsStore.Contains(geometryId))
|
||||
{
|
||||
geometry.applicationId = geometryId;
|
||||
geometryAdded = GeometryDefinitionsStore.Add(geometry);
|
||||
_logger.LogDebug("Added geometry definition: {GeometryId}, Success={Success}", geometryId, geometryAdded);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("Geometry definition already exists: {GeometryId}", geometryId);
|
||||
}
|
||||
|
||||
// Add instance definition proxy if not exists
|
||||
if (!InstanceDefinitionProxiesStore.Contains(definitionId))
|
||||
{
|
||||
var definitionProxy = new InstanceDefinitionProxy
|
||||
{
|
||||
applicationId = definitionId,
|
||||
name = $"Shared Geometry {fragmentId[..8]}...", // Show first 8 chars for readability
|
||||
objects = [geometry.applicationId],
|
||||
maxDepth = 0
|
||||
};
|
||||
proxyAdded = InstanceDefinitionProxiesStore.Add(definitionProxy);
|
||||
_logger.LogDebug("Added instance definition proxy: {DefinitionId}, Success={Success}", definitionId, proxyAdded);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("Instance definition proxy already exists: {DefinitionId}", definitionId);
|
||||
}
|
||||
|
||||
var conversionSucceededResult = geometryAdded || proxyAdded;
|
||||
_logger.LogDebug(
|
||||
"AddSharedGeometry completed: FragmentId={FragmentId}, Result={Result}, GeometryAdded={GeometryAdded}, ProxyAdded={ProxyAdded}",
|
||||
fragmentId,
|
||||
conversionSucceededResult,
|
||||
geometryAdded,
|
||||
proxyAdded
|
||||
);
|
||||
return conversionSucceededResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if shared geometry already exists in both stores.
|
||||
/// </summary>
|
||||
/// <param name="fragmentId">The fragment-based application ID.</param>
|
||||
/// <returns>True if geometry definition exists in both stores.</returns>
|
||||
public bool ContainsSharedGeometry(string fragmentId) =>
|
||||
GeometryDefinitionsStore.Contains($"{InstanceConstants.GEOMETRY_ID_PREFIX}{fragmentId}")
|
||||
// && InstanceDefinitionProxiesStore.Contains($"{InstanceConstants.DEFINITION_ID_PREFIX}{fragmentId}")
|
||||
;
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
using System.Globalization;
|
||||
using Speckle.Converter.Navisworks.Helpers;
|
||||
using Speckle.InterfaceGenerator;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.Services;
|
||||
|
||||
[GenerateAutoInterface]
|
||||
public class PropertyConverter(IUiUnitsCache uiUnitsCache) : IPropertyConverter
|
||||
{
|
||||
public void Reset() => uiUnitsCache.Reset();
|
||||
|
||||
public object? ConvertPropertyValue(NAV.VariantData? value, NAV.Units modelUnits, string propDisplayName) =>
|
||||
value == null
|
||||
? null
|
||||
: _handlers.TryGetValue(value.DataType, out var f)
|
||||
? f(value, (modelUnits, propDisplayName))
|
||||
: value.DataType is NAV.VariantDataType.None or NAV.VariantDataType.Point2D
|
||||
? null
|
||||
: value.ToString();
|
||||
|
||||
private readonly Dictionary<
|
||||
NAV.VariantDataType,
|
||||
Func<NAV.VariantData, (NAV.Units model, string name), object?>
|
||||
> _handlers =
|
||||
new()
|
||||
{
|
||||
{ NAV.VariantDataType.Boolean, (v, _) => v.ToBoolean() },
|
||||
{ NAV.VariantDataType.DisplayString, (v, _) => v.ToDisplayString() },
|
||||
{ NAV.VariantDataType.IdentifierString, (v, _) => v.ToIdentifierString() },
|
||||
{ NAV.VariantDataType.Int32, (v, _) => v.ToInt32() },
|
||||
{ NAV.VariantDataType.Double, (v, _) => v.ToDouble() },
|
||||
// Angle as dictionary with units
|
||||
{ NAV.VariantDataType.DoubleAngle, (v, t) => NumObj(t.name, v.ToDoubleAngle(), "Degrees") },
|
||||
// Length → dictionary in UI units
|
||||
{
|
||||
NAV.VariantDataType.DoubleLength,
|
||||
(v, t) =>
|
||||
{
|
||||
var uiUnits = uiUnitsCache.Ensure();
|
||||
|
||||
var k = NAV.UnitConversion.ScaleFactor(t.model, uiUnits);
|
||||
return NumObj(t.name, v.ToDoubleLength() * k, UnitLabels.Linear(uiUnits));
|
||||
}
|
||||
},
|
||||
// Area → dictionary in UI units^2
|
||||
{
|
||||
NAV.VariantDataType.DoubleArea,
|
||||
(v, t) =>
|
||||
{
|
||||
var uiUnits = uiUnitsCache.Ensure();
|
||||
var k = NAV.UnitConversion.ScaleFactor(t.model, uiUnits);
|
||||
k *= k;
|
||||
return NumObj(t.name, v.ToDoubleArea() * k, UnitLabels.Area(uiUnits));
|
||||
}
|
||||
},
|
||||
// Volume → dictionary in UI units^3
|
||||
{
|
||||
NAV.VariantDataType.DoubleVolume,
|
||||
(v, t) =>
|
||||
{
|
||||
var uiUnits = uiUnitsCache.Ensure();
|
||||
var k = NAV.UnitConversion.ScaleFactor(t.model, uiUnits);
|
||||
k = k * k * k;
|
||||
return NumObj(t.name, v.ToDoubleVolume() * k, UnitLabels.Volume(uiUnits));
|
||||
}
|
||||
},
|
||||
{ NAV.VariantDataType.DateTime, (v, _) => v.ToDateTime().ToString(CultureInfo.InvariantCulture) },
|
||||
{ NAV.VariantDataType.NamedConstant, (v, _) => v.ToNamedConstant().DisplayName },
|
||||
{ NAV.VariantDataType.None, (_, _) => null },
|
||||
{ NAV.VariantDataType.Point2D, (_, _) => null },
|
||||
{
|
||||
NAV.VariantDataType.Point3D,
|
||||
(v, t) =>
|
||||
{
|
||||
var uiUnits = uiUnitsCache.Ensure();
|
||||
var k = NAV.UnitConversion.ScaleFactor(t.model, uiUnits);
|
||||
var p = v.ToPoint3D();
|
||||
|
||||
return new Speckle.Objects.Geometry.Point(p.X * k, p.Y * k, p.Z * k, UnitLabels.Linear(uiUnits));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static Dictionary<string, object> NumObj(string name, double value, string units) =>
|
||||
new()
|
||||
{
|
||||
["name"] = name,
|
||||
["value"] = value,
|
||||
["units"] = units
|
||||
};
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.Services;
|
||||
|
||||
[GenerateAutoInterface]
|
||||
public class SharedGeometryStore : ISharedGeometryStore
|
||||
{
|
||||
private readonly HashSet<Base> _geometries = new();
|
||||
private readonly Dictionary<string, Base> _geometriesByApplicationId = new();
|
||||
private readonly object _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only collection of all stored geometries.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<Base> Geometries
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _geometries.ToList().AsReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a geometry to the store if it doesn't already exist.
|
||||
/// </summary>
|
||||
/// <param name="geometry">The geometry to add.</param>
|
||||
/// <returns>True if the geometry was added, false if it already existed.</returns>
|
||||
public bool Add(Base geometry)
|
||||
{
|
||||
if (geometry == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(geometry));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(geometry.applicationId))
|
||||
{
|
||||
throw new ArgumentException("Geometry must have an applicationId for deduplication", nameof(geometry));
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (geometry.applicationId != null && _geometriesByApplicationId.ContainsKey(geometry.applicationId))
|
||||
{
|
||||
return false; // Already exists
|
||||
}
|
||||
|
||||
_geometries.Add(geometry);
|
||||
if (geometry.applicationId != null)
|
||||
{
|
||||
_geometriesByApplicationId[geometry.applicationId] = geometry;
|
||||
}
|
||||
|
||||
return true; // Added successfully
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a geometry with the specified application ID already exists in the store.
|
||||
/// </summary>
|
||||
/// <param name="applicationId">The application ID to check for.</param>
|
||||
/// <returns>True if a geometry with the application ID exists, false otherwise.</returns>
|
||||
public bool Contains(string applicationId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(applicationId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
var contains = _geometriesByApplicationId.ContainsKey(applicationId);
|
||||
|
||||
return contains;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all stored geometries for a new conversion session.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_geometries.Clear();
|
||||
_geometriesByApplicationId.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Autodesk.Navisworks.Api.Interop;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using static Autodesk.Navisworks.Api.Interop.LcUOption;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.Services;
|
||||
|
||||
[GenerateAutoInterface]
|
||||
public class UiUnitsCache : IUiUnitsCache
|
||||
{
|
||||
private NAV.Units? _ui;
|
||||
|
||||
public NAV.Units Ensure()
|
||||
{
|
||||
if (_ui.HasValue)
|
||||
{
|
||||
return _ui.Value;
|
||||
}
|
||||
|
||||
UiUnitsUtil.TryGetUiLinearUnits(out var ui);
|
||||
_ui = ui;
|
||||
return _ui.Value;
|
||||
}
|
||||
|
||||
public void Reset() => _ui = null;
|
||||
}
|
||||
|
||||
public static class UiUnitsUtil
|
||||
{
|
||||
// disp_units: 0=linear_format
|
||||
public static bool TryGetUiLinearUnits(out NAV.Units uiUnits)
|
||||
{
|
||||
using var opt = new LcUOptionLock();
|
||||
var root = GetRoot(opt);
|
||||
var disp = root.GetSubOptions("interface").GetSubOptions("disp_units");
|
||||
|
||||
int code = -1;
|
||||
|
||||
using var v = new NAV.VariantData();
|
||||
disp.GetValue(0, v);
|
||||
var s = v.ToString();
|
||||
var colon = s.LastIndexOf(':');
|
||||
var open = s.IndexOf('(', colon + 1);
|
||||
if (colon >= 0 && open > colon && !int.TryParse(s.Substring(colon + 1, open - colon - 1), out code))
|
||||
{
|
||||
code = -1;
|
||||
}
|
||||
|
||||
uiUnits = code switch
|
||||
{
|
||||
0 => NAV.Units.Kilometers,
|
||||
1 => NAV.Units.Meters,
|
||||
2 => NAV.Units.Centimeters,
|
||||
3 => NAV.Units.Millimeters,
|
||||
4 => NAV.Units.Micrometers,
|
||||
5 => NAV.Units.Miles,
|
||||
6 => NAV.Units.Miles,
|
||||
7 => NAV.Units.Yards,
|
||||
8 => NAV.Units.Yards,
|
||||
9 => NAV.Units.Feet,
|
||||
10 => NAV.Units.Feet,
|
||||
11 => NAV.Units.Feet,
|
||||
12 => NAV.Units.Inches,
|
||||
13 => NAV.Units.Inches,
|
||||
14 => NAV.Units.Mils,
|
||||
15 => NAV.Units.Microinches,
|
||||
_ => NAV.Units.Meters
|
||||
};
|
||||
|
||||
return code >= 0;
|
||||
}
|
||||
}
|
||||
+35
-31
@@ -9,35 +9,39 @@
|
||||
<Import_RootNamespace>Speckle.Converters.NavisworksShared</Import_RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\ClassPropertiesExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\DisplayValueExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\ModelPropertiesExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\PropertySetsExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\RevitBuiltInCategoryExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataHandlers\BasePropertyHandler.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataHandlers\HierarchicalPropertyHandler.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataHandlers\IPropertyHandler.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataHandlers\StandardPropertyHandler.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DependencyInjection\NavisworksConverterServiceRegistration.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\ArrayExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Geometries\Primitives.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Geometries\TransformMatrix.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ColorConverter.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\HierarchyHelper.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ElementSelectionHelper.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\PrimitiveProcessor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\PropertyHelpers.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)GlobalUsing.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\GeometryHelpers.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)PathConstants.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\GeometryToSpeckleConverter.cs" />
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Models\" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Services\NavisworksToSpeckleUnitConverter.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Settings\ConversionModes.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Settings\NavisworksConversionSettings.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Settings\NavisworksConversionSettingsFactory.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\NavisworksRootToSpeckleConverter.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\BoundingBoxToSpeckleRawConverter.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\TopLevel\ModelItemTopLevelConverterToSpeckle.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\ClassPropertiesExtractor.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\DisplayValueExtractor.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\ModelPropertiesExtractor.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\PropertySetsExtractor.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataExtractors\RevitBuiltInCategoryExtractor.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataHandlers\BasePropertyHandler.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataHandlers\HierarchicalPropertyHandler.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataHandlers\IPropertyHandler.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataHandlers\StandardPropertyHandler.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DependencyInjection\NavisworksConverterServiceRegistration.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\ArrayExtensions.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Geometries\Primitives.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Geometries\TransformMatrix.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ColorConverter.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\HierarchyHelper.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ElementSelectionHelper.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\PrimitiveProcessor.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\PropertyHelpers.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)GlobalUsing.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\GeometryHelpers.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)PathConstants.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Services\PropertyConversion.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Services\UIUnits.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Services\SharedGeometryStores.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Services\InstanceStoreManager.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\GeometryToSpeckleConverter.cs"/>
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Models\"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Services\NavisworksToSpeckleUnitConverter.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Settings\ConversionModes.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Settings\NavisworksConversionSettings.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Settings\NavisworksConversionSettingsFactory.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\NavisworksRootToSpeckleConverter.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\BoundingBoxToSpeckleRawConverter.cs"/>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\TopLevel\ModelItemTopLevelConverterToSpeckle.cs"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
+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;
|
||||
|
||||
+440
-74
@@ -1,45 +1,47 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Autodesk.Navisworks.Api.Interop.ComApi;
|
||||
using Speckle.Converter.Navisworks.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Converter.Navisworks.Constants;
|
||||
using Speckle.Converter.Navisworks.Geometry;
|
||||
using Speckle.Converter.Navisworks.Helpers;
|
||||
using Speckle.Converter.Navisworks.Services;
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.DoubleNumerics;
|
||||
using Speckle.Objects.Geometry;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Instances;
|
||||
using ComApiBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge;
|
||||
|
||||
namespace Speckle.Converter.Navisworks.ToSpeckle;
|
||||
|
||||
/// <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>
|
||||
public class GeometryToSpeckleConverter
|
||||
/// <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,
|
||||
ILogger<GeometryToSpeckleConverter> logger
|
||||
)
|
||||
{
|
||||
private readonly NavisworksConversionSettings _settings;
|
||||
private readonly bool _isUpright;
|
||||
private readonly SafeVector _transformVector;
|
||||
private const double SCALE = 1.0; // Default scale factor
|
||||
private readonly NavisworksConversionSettings _settings =
|
||||
settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
|
||||
public GeometryToSpeckleConverter(NavisworksConversionSettings settings)
|
||||
{
|
||||
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
_isUpright = settings.Derived.IsUpright;
|
||||
_transformVector = settings.Derived.TransformVector;
|
||||
}
|
||||
private readonly bool _isUpright = settings.Derived.IsUpright;
|
||||
private readonly SafeVector _transformVector = settings.Derived.TransformVector;
|
||||
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];
|
||||
|
||||
/// <summary>
|
||||
/// Converts a ModelItem's geometry to Speckle display geometry by accessing the underlying COM objects.
|
||||
/// Applies necessary transformations and unit scaling.
|
||||
/// </summary>
|
||||
internal List<Base> Convert(NAV.ModelItem modelItem)
|
||||
private readonly InstanceStoreManager _instanceStoreManager =
|
||||
instanceStoreManager ?? throw new ArgumentNullException(nameof(instanceStoreManager));
|
||||
|
||||
private readonly ILogger<GeometryToSpeckleConverter> _logger =
|
||||
logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
internal List<SSM.Base> Convert(NAV.ModelItem modelItem)
|
||||
{
|
||||
if (modelItem == null)
|
||||
{
|
||||
@@ -56,22 +58,42 @@ public class GeometryToSpeckleConverter
|
||||
{
|
||||
var fragmentStack = new Stack<InwOaFragment3>();
|
||||
var paths = comSelection.Paths();
|
||||
if (paths == null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
try
|
||||
{
|
||||
// Populate fragment stack with all fragments
|
||||
if (paths.Count > 0)
|
||||
{
|
||||
var firstPath = paths.Cast<InwOaPath>().First();
|
||||
var fragmentsCollection = firstPath.Fragments();
|
||||
try
|
||||
{
|
||||
if (fragmentsCollection.Count > 1)
|
||||
{
|
||||
return ProcessSharedGeometry(paths, fragmentStack);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fragmentsCollection != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(fragmentsCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (InwOaPath path in paths)
|
||||
{
|
||||
CollectFragments(path, fragmentStack);
|
||||
}
|
||||
|
||||
return ProcessFragments(fragmentStack, paths);
|
||||
return ProcessFragments(fragmentStack, paths, true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (paths != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(paths);
|
||||
}
|
||||
Marshal.ReleaseComObject(paths);
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -86,24 +108,61 @@ public class GeometryToSpeckleConverter
|
||||
private static void CollectFragments(InwOaPath path, Stack<InwOaFragment3> fragmentStack)
|
||||
{
|
||||
var fragments = path.Fragments();
|
||||
foreach (var fragment in fragments.OfType<InwOaFragment3>())
|
||||
try
|
||||
{
|
||||
if (fragment.path?.ArrayData is not Array pathData1 || path.ArrayData is not Array pathData2)
|
||||
foreach (var fragment in fragments.OfType<InwOaFragment3>())
|
||||
{
|
||||
continue;
|
||||
if (AreFragmentPathsEqual(fragment, path))
|
||||
{
|
||||
fragmentStack.Push(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
var pathArray1 = pathData1.ToArray<int>();
|
||||
var pathArray2 = pathData2.ToArray<int>();
|
||||
|
||||
if (pathArray1.Length == pathArray2.Length && pathArray1.SequenceEqual(pathArray2))
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fragments != null)
|
||||
{
|
||||
fragmentStack.Push(fragment);
|
||||
Marshal.ReleaseComObject(fragments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Base> ProcessFragments(Stack<InwOaFragment3> fragmentStack, InwSelectionPathsColl paths)
|
||||
private List<SSM.Base> ProcessSharedGeometry(InwSelectionPathsColl paths, Stack<InwOaFragment3> fragmentStack)
|
||||
{
|
||||
var fragmentId = GenerateFragmentId(paths);
|
||||
|
||||
if (string.IsNullOrEmpty(fragmentId))
|
||||
{
|
||||
foreach (InwOaPath path in paths)
|
||||
{
|
||||
CollectFragments(path, fragmentStack);
|
||||
}
|
||||
|
||||
return ProcessFragments(fragmentStack, paths, true);
|
||||
}
|
||||
|
||||
if (_instanceStoreManager.ContainsSharedGeometry(fragmentId))
|
||||
{
|
||||
return CreateInstanceReference(fragmentId, paths);
|
||||
}
|
||||
|
||||
foreach (InwOaPath path in paths)
|
||||
{
|
||||
CollectFragments(path, fragmentStack);
|
||||
}
|
||||
|
||||
var baseGeometry = ExtractUntransformedGeometry(fragmentStack);
|
||||
|
||||
return baseGeometry == null || !_instanceStoreManager.AddSharedGeometry(fragmentId, baseGeometry)
|
||||
? ProcessFragments(fragmentStack, paths) // default false flag for isSingleObject
|
||||
: CreateInstanceReference(fragmentId, paths);
|
||||
}
|
||||
|
||||
private List<SSM.Base> ProcessFragments(
|
||||
Stack<InwOaFragment3> fragmentStack,
|
||||
InwSelectionPathsColl paths,
|
||||
bool isSingleObject = false
|
||||
)
|
||||
{
|
||||
var callbackListeners = new List<PrimitiveProcessor>();
|
||||
|
||||
@@ -113,11 +172,6 @@ public class GeometryToSpeckleConverter
|
||||
|
||||
foreach (var fragment in fragmentStack)
|
||||
{
|
||||
if (!ValidateFragmentPath(fragment, path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var matrix = fragment.GetLocalToWorldMatrix();
|
||||
var transform = matrix as InwLTransform3f3;
|
||||
if (transform?.Matrix is not Array matrixArray)
|
||||
@@ -125,31 +179,43 @@ public class GeometryToSpeckleConverter
|
||||
continue;
|
||||
}
|
||||
|
||||
processor.LocalToWorldTransformation = ConvertArrayToDouble(matrixArray);
|
||||
var fragmentsForCount = path.Fragments();
|
||||
int fragmentCount;
|
||||
try
|
||||
{
|
||||
fragmentCount = fragmentsForCount?.Count ?? 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fragmentsForCount != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(fragmentsForCount);
|
||||
}
|
||||
}
|
||||
|
||||
double[] makeNoChange = s_identityTransform;
|
||||
double[] transformMatrix = ConvertArrayToDouble(matrixArray);
|
||||
|
||||
processor.LocalToWorldTransformation =
|
||||
isSingleObject || fragmentCount == 1 ? transformMatrix : (IEnumerable<double>)makeNoChange;
|
||||
|
||||
fragment.GenerateSimplePrimitives(nwEVertexProperty.eNORMAL, processor);
|
||||
}
|
||||
|
||||
callbackListeners.Add(processor);
|
||||
}
|
||||
|
||||
var baseGeometries = ProcessGeometries(callbackListeners);
|
||||
|
||||
return baseGeometries;
|
||||
return ProcessGeometries(callbackListeners);
|
||||
}
|
||||
|
||||
private static bool ValidateFragmentPath(InwOaFragment3 fragment, InwOaPath path)
|
||||
{
|
||||
if (fragment.path?.ArrayData is not Array fragmentPathData || path.ArrayData is not Array pathData)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
private static bool AreFragmentPathsEqual(InwOaFragment3 fragment, InwOaPath path) =>
|
||||
fragment.path?.ArrayData is Array fragmentPathData
|
||||
&& path.ArrayData is Array pathData
|
||||
&& AreFragmentPathsEqual(fragmentPathData, pathData);
|
||||
|
||||
return IsSameFragmentPath(fragmentPathData, pathData);
|
||||
}
|
||||
|
||||
private List<Base> ProcessGeometries(List<PrimitiveProcessor> processors)
|
||||
private List<SSM.Base> ProcessGeometries(List<PrimitiveProcessor> processors)
|
||||
{
|
||||
var baseGeometries = new List<Base>();
|
||||
var baseGeometries = new List<SSM.Base>();
|
||||
|
||||
foreach (var processor in processors)
|
||||
{
|
||||
@@ -178,7 +244,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,
|
||||
@@ -204,9 +269,8 @@ public class GeometryToSpeckleConverter
|
||||
}
|
||||
|
||||
private List<Line> CreateLines(IReadOnlyList<SafeLine> lines) =>
|
||||
(
|
||||
from line in lines
|
||||
select new Line
|
||||
lines
|
||||
.Select(line => new Line
|
||||
{
|
||||
start = new Point(
|
||||
(line.Start.X + _transformVector.X) * SCALE,
|
||||
@@ -221,10 +285,312 @@ public class GeometryToSpeckleConverter
|
||||
_settings.Derived.SpeckleUnits
|
||||
),
|
||||
units = _settings.Derived.SpeckleUnits
|
||||
}
|
||||
).ToList();
|
||||
})
|
||||
.ToList();
|
||||
|
||||
private static double[]? ConvertArrayToDouble(Array arr)
|
||||
public string GenerateFragmentId(InwSelectionPathsColl paths)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (paths.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var fragmentHashes = new List<string>();
|
||||
|
||||
foreach (var fragments in from InwOaPath path in paths select path.Fragments())
|
||||
{
|
||||
try
|
||||
{
|
||||
var fragmentIndex = 0;
|
||||
foreach (InwOaFragment3 fragment in fragments.OfType<InwOaFragment3>())
|
||||
{
|
||||
if (fragment.path?.ArrayData is not Array pathData || pathData.Length == 0)
|
||||
{
|
||||
fragmentIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (pathData.Rank != 1)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
fragmentIndex++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fragments != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(fragments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fragmentHashes.Count > 0)
|
||||
{
|
||||
fragmentHashes.Sort();
|
||||
var rawData = string.Join("__", fragmentHashes);
|
||||
var fragmentId = HashRawData(rawData);
|
||||
return fragmentId;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
catch (Exception ex) when (ex is COMException or InvalidCastException or IndexOutOfRangeException)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private string TrySimpleArrayEnumeration(Array pathData, int fragmentIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
var values = new List<string>();
|
||||
var maxAttempts = Math.Min(pathData.Length, 20);
|
||||
|
||||
for (int i = 0; i < maxAttempts; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = pathData.GetValue(i);
|
||||
var convertedValue = System.Convert.ToInt32(value);
|
||||
values.Add(convertedValue.ToString());
|
||||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (InvalidCastException ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "Type conversion failed at index {Index}", i);
|
||||
}
|
||||
}
|
||||
|
||||
if (values.Count <= 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return string.Join("_", values);
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "COM enumeration failed for fragment {FragmentIndex}", fragmentIndex);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static string HashRawData(string rawData)
|
||||
{
|
||||
using var sha256 = SHA256.Create();
|
||||
var inputBytes = Encoding.UTF8.GetBytes(rawData);
|
||||
var hashBytes = sha256.ComputeHash(inputBytes);
|
||||
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
|
||||
private SSM.Base? ExtractUntransformedGeometry(Stack<InwOaFragment3> fragmentStack)
|
||||
{
|
||||
var processor = new PrimitiveProcessor(_isUpright);
|
||||
|
||||
foreach (var fragment in fragmentStack)
|
||||
{
|
||||
processor.LocalToWorldTransformation = s_identityTransform;
|
||||
|
||||
fragment.GenerateSimplePrimitives(nwEVertexProperty.eNORMAL, processor);
|
||||
}
|
||||
|
||||
return processor.Triangles.Count > 0 ? CreateMesh(processor.Triangles) : null;
|
||||
}
|
||||
|
||||
private List<SSM.Base> CreateInstanceReference(string fragmentId, InwSelectionPathsColl paths)
|
||||
{
|
||||
var transform = ExtractInstanceTransform(paths);
|
||||
|
||||
var instanceReference = new InstanceProxy
|
||||
{
|
||||
definitionId = $"{InstanceConstants.DEFINITION_ID_PREFIX}{fragmentId}",
|
||||
transform = transform,
|
||||
units = _settings.Derived.SpeckleUnits,
|
||||
maxDepth = 0,
|
||||
applicationId = Guid.NewGuid().ToString()
|
||||
};
|
||||
|
||||
return [instanceReference];
|
||||
}
|
||||
|
||||
private Matrix4x4 ExtractInstanceTransform(InwSelectionPathsColl paths)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (paths.Count == 0)
|
||||
{
|
||||
return s_identityMatrix;
|
||||
}
|
||||
|
||||
var firstPath = paths.Cast<InwOaPath>().First();
|
||||
var fragments = firstPath.Fragments();
|
||||
try
|
||||
{
|
||||
if (fragments.Count == 0)
|
||||
{
|
||||
return s_identityMatrix;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
var pathArray1 = pathData1.Cast<int>().ToArray();
|
||||
var pathArray2 = pathData2.Cast<int>().ToArray();
|
||||
|
||||
if (pathArray1.Length == pathArray2.Length && pathArray1.SequenceEqual(pathArray2))
|
||||
{
|
||||
fragmentStack.Push(frag);
|
||||
}
|
||||
}
|
||||
|
||||
var fragment = fragmentStack.First();
|
||||
var matrix = fragment.GetLocalToWorldMatrix();
|
||||
|
||||
if (matrix is InwLTransform3f3 { Matrix: Array matrixArray })
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fragments != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(fragments);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is COMException or InvalidCastException or NullReferenceException)
|
||||
{
|
||||
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 s_identityMatrix;
|
||||
}
|
||||
|
||||
private double[] ApplyCoordinateTransform(double[] matrixArray)
|
||||
{
|
||||
var result = new double[16];
|
||||
Array.Copy(matrixArray, result, 16);
|
||||
|
||||
result[12] = (result[12] + _transformVector.X) * SCALE;
|
||||
result[13] = (result[13] + _transformVector.Y) * SCALE;
|
||||
result[14] = (result[14] + _transformVector.Z) * SCALE;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static double[] ConvertArrayToDouble(Array arr)
|
||||
{
|
||||
if (arr.Rank != 1)
|
||||
{
|
||||
@@ -240,6 +606,6 @@ public class GeometryToSpeckleConverter
|
||||
return doubleArray;
|
||||
}
|
||||
|
||||
private static bool IsSameFragmentPath(Array a1, Array a2) =>
|
||||
private static bool AreFragmentPathsEqual(Array a1, Array a2) =>
|
||||
a1.Length == a2.Length && a1.Cast<int>().SequenceEqual(a2.Cast<int>());
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converter.Navisworks.Settings;
|
||||
using Speckle.Converter.Navisworks.ToSpeckle.PropertyHandlers;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.Common.Objects;
|
||||
|
||||
@@ -32,4 +32,64 @@ public static class ElementExtensions
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
public static IEnumerable<ElementId> GetKnownChildrenElements(this Element element) =>
|
||||
element switch
|
||||
{
|
||||
Wall wall => GetWallChildren(wall),
|
||||
FootPrintRoof roof => GetFootPrintRoofChildren(roof),
|
||||
DBA.Railing railing => GetRailingChildren(railing),
|
||||
_ => []
|
||||
};
|
||||
|
||||
private static IEnumerable<ElementId> GetWallChildren(Wall wall)
|
||||
{
|
||||
if (wall.CurtainGrid is CurtainGrid grid)
|
||||
{
|
||||
foreach (var id in grid.GetMullionIds())
|
||||
{
|
||||
yield return id;
|
||||
}
|
||||
|
||||
foreach (var id in grid.GetPanelIds())
|
||||
{
|
||||
yield return id;
|
||||
}
|
||||
}
|
||||
else if (wall.IsStackedWall)
|
||||
{
|
||||
foreach (var id in wall.GetStackedWallMemberIds())
|
||||
{
|
||||
yield return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<ElementId> GetFootPrintRoofChildren(FootPrintRoof footPrintRoof)
|
||||
{
|
||||
if (footPrintRoof.CurtainGrids is { } grids)
|
||||
{
|
||||
foreach (CurtainGrid grid in grids)
|
||||
{
|
||||
foreach (var id in grid.GetMullionIds())
|
||||
{
|
||||
yield return id;
|
||||
}
|
||||
|
||||
foreach (var id in grid.GetPanelIds())
|
||||
{
|
||||
yield return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<ElementId> GetRailingChildren(DBA.Railing railing)
|
||||
{
|
||||
// TODO: Consider adding HandRail support (railing.GetHandRails())
|
||||
if (railing.TopRail != ElementId.InvalidElementId)
|
||||
{
|
||||
yield return railing.TopRail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,15 +87,6 @@ public sealed class DisplayValueExtractor
|
||||
// curtain and stacked walls should have their display values in their children
|
||||
case DB.Wall wall:
|
||||
return wall.CurtainGrid is not null || wall.IsStackedWall ? new() : GetGeometryDisplayValue(element);
|
||||
// railings should also include toprail which need to be retrieved separately
|
||||
case DBA.Railing railing:
|
||||
List<DisplayValueResult> railingDisplay = GetGeometryDisplayValue(railing);
|
||||
if (railing.TopRail != DB.ElementId.InvalidElementId)
|
||||
{
|
||||
var topRail = _converterSettings.Current.Document.GetElement(railing.TopRail);
|
||||
railingDisplay.AddRange(GetGeometryDisplayValue(topRail));
|
||||
}
|
||||
return railingDisplay;
|
||||
|
||||
// POC: footprint roofs can have curtain walls in them. Need to check if they can also have non-curtain wall parts, bc currently not skipping anything.
|
||||
// case DB.FootPrintRoof footPrintRoof:
|
||||
@@ -200,20 +191,51 @@ public sealed class DisplayValueExtractor
|
||||
);
|
||||
}
|
||||
|
||||
// add rest of geometry (always without transform)
|
||||
// transform curves, polylines, and points to world coordinates before conversion.
|
||||
// Unlike meshes/solids which are proxified with transform matrices, these geometry
|
||||
// types must have their final world coordinates baked directly into their geometry.
|
||||
foreach (var curve in collections.Curves)
|
||||
{
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(GetCurveDisplayValue(curve)));
|
||||
if (localToWorld is not null)
|
||||
{
|
||||
using var transformedCurve = curve.CreateTransformed(localToWorld);
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(GetCurveDisplayValue(transformedCurve)));
|
||||
}
|
||||
else
|
||||
{
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(GetCurveDisplayValue(curve)));
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Creating new polyline/point instances for transformation isn't ideal for perf,
|
||||
// but Revit API doesn't provide in-place transform methods. Trade-off is acceptable since
|
||||
// family instances typically don't have massive numbers of raw polylines/points in their geometry.
|
||||
foreach (var polyline in collections.Polylines)
|
||||
{
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(_polylineConverter.Convert(polyline)));
|
||||
if (localToWorld is not null)
|
||||
{
|
||||
var coords = polyline.GetCoordinates();
|
||||
var transformedCoords = coords.Select(coord => localToWorld.OfPoint(coord)).ToList();
|
||||
using var transformedPolyline = DB.PolyLine.Create(transformedCoords);
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(_polylineConverter.Convert(transformedPolyline)));
|
||||
}
|
||||
else
|
||||
{
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(_polylineConverter.Convert(polyline)));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var point in collections.Points)
|
||||
{
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(_pointConverter.Convert(point)));
|
||||
if (localToWorld is not null)
|
||||
{
|
||||
using var transformedPoint = DB.Point.Create(localToWorld.OfPoint(point.Coord));
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(_pointConverter.Convert(transformedPoint)));
|
||||
}
|
||||
else
|
||||
{
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(_pointConverter.Convert(point)));
|
||||
}
|
||||
}
|
||||
|
||||
return displayValue;
|
||||
@@ -355,7 +377,8 @@ public sealed class DisplayValueExtractor
|
||||
collections.Meshes.Add(mesh);
|
||||
break;
|
||||
|
||||
//Note, we're not applying transforms to curves/polylines/points because ProcessGeometryCollections expects them in world coordinates
|
||||
// curves, polylines, and points are transformed to world space in ProcessGeometryCollections,
|
||||
// not here, because they cannot be proxified like meshes.
|
||||
case DB.Curve curve:
|
||||
collections.Curves.Add(curve);
|
||||
break;
|
||||
@@ -582,8 +605,9 @@ public sealed class DisplayValueExtractor
|
||||
/// and reduce the risk of parameter ordering errors.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="Solids"/> and <see cref="Meshes"/> potentially in local coordinate space.
|
||||
/// For now, <see cref="Curves"/>, <see cref="Polylines"/>, <see cref="Points"/> will always be in world space
|
||||
/// <see cref="Solids"/> and <see cref="Meshes"/> are transformed to local coordinate space in SortGeometry.
|
||||
/// <see cref="Curves"/>, <see cref="Polylines"/>, and <see cref="Points"/> remain in their original coordinate space
|
||||
/// and are transformed to world space during processing in ProcessGeometryCollections.
|
||||
/// </remarks>
|
||||
private sealed record GeometryCollections
|
||||
{
|
||||
|
||||
+2
-1
@@ -81,8 +81,9 @@
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\TopLevel\RevitElementTopLevelConverterToSpeckle.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ParameterValueExtractor.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\RevitContext.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\TopLevel\View3DTopLevelConverterToSpeckle.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\Helpers\SendSelection.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Services\RevitToSpeckleUnitConverter.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)RevitRootToSpeckleConverter.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
+11
@@ -88,6 +88,17 @@ public class ClassPropertiesExtractor
|
||||
{
|
||||
elementProperties.Add("spaceId", familyInstance.Space.Id.ToString());
|
||||
}
|
||||
|
||||
// get toRoom and fromRoom for FamilyInstance elements with those properties (e.g. Doors)
|
||||
if (familyInstance.ToRoom is not null)
|
||||
{
|
||||
elementProperties.Add("toRoomId", familyInstance.ToRoom.Id.ToString());
|
||||
}
|
||||
|
||||
if (familyInstance.FromRoom is not null)
|
||||
{
|
||||
elementProperties.Add("fromRoomId", familyInstance.FromRoom.Id.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception e) when (!e.IsFatal())
|
||||
{
|
||||
|
||||
+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:
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.Common.Objects;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Sdk.Common.Exceptions;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Converters.RevitShared.ToSpeckle;
|
||||
|
||||
[NameAndRankValue(typeof(DB.View3D), 0)]
|
||||
public class View3DTopLevelConverterToSpeckle : IToSpeckleTopLevelConverter, ITypedConverter<DB.View3D, Camera>
|
||||
{
|
||||
private readonly ITypedConverter<DB.XYZ, SOG.Point> _xyzToPointConverter;
|
||||
private readonly ITypedConverter<DB.XYZ, SOG.Vector> _xyzToVectorConverter;
|
||||
|
||||
public View3DTopLevelConverterToSpeckle(
|
||||
ITypedConverter<DB.XYZ, SOG.Point> xyzToPointConverter,
|
||||
ITypedConverter<DB.XYZ, SOG.Vector> xyzToVectorConverter
|
||||
)
|
||||
{
|
||||
_xyzToPointConverter = xyzToPointConverter;
|
||||
_xyzToVectorConverter = xyzToVectorConverter;
|
||||
}
|
||||
|
||||
public Base Convert(object target) => Convert((DB.View3D)target);
|
||||
|
||||
public Camera Convert(DB.View3D target)
|
||||
{
|
||||
if (!target.IsPerspective)
|
||||
{
|
||||
throw new ConversionException("Non-Perspective views are not supported");
|
||||
}
|
||||
|
||||
// some views have null origin, not sure why
|
||||
if (target.Origin == null)
|
||||
{
|
||||
throw new ConversionException("Views with no origin are not supported");
|
||||
}
|
||||
|
||||
DB.ViewOrientation3D orientation = target.GetSavedOrientation();
|
||||
|
||||
return new()
|
||||
{
|
||||
name = target.Title,
|
||||
position = _xyzToPointConverter.Convert(target.Origin),
|
||||
forward = _xyzToVectorConverter.Convert(orientation.ForwardDirection),
|
||||
up = _xyzToVectorConverter.Convert(orientation.UpDirection)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2,4 +2,5 @@ global using RG = Rhino.Geometry;
|
||||
global using SA = Speckle.Objects.Annotation;
|
||||
global using SO = Speckle.Objects.Other;
|
||||
global using SOG = Speckle.Objects.Geometry;
|
||||
global using SOO = Speckle.Objects.Other;
|
||||
global using SOP = Speckle.Objects.Primitive;
|
||||
|
||||
+34
-20
@@ -1,9 +1,11 @@
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.Common.Objects;
|
||||
using Speckle.Converters.Common.ToHost;
|
||||
using Speckle.Objects.Data;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Common.Exceptions;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Instances;
|
||||
|
||||
namespace Speckle.Converters.Rhino.ToHost.TopLevel;
|
||||
|
||||
@@ -27,6 +29,7 @@ public class DataObjectConverter
|
||||
private readonly ITypedConverter<SOG.Region, RG.Hatch> _regionConverter;
|
||||
private readonly ITypedConverter<SOG.SubDX, List<RG.GeometryBase>> _subdConverter;
|
||||
private readonly IConverterSettingsStore<RhinoConversionSettings> _settingsStore;
|
||||
private readonly IDataObjectInstanceRegistry _dataObjectInstanceRegistry;
|
||||
|
||||
public DataObjectConverter(
|
||||
ITypedConverter<SOG.Arc, RG.ArcCurve> arcConverter,
|
||||
@@ -43,7 +46,8 @@ public class DataObjectConverter
|
||||
ITypedConverter<SOG.Polycurve, RG.PolyCurve> polycurveConverter,
|
||||
ITypedConverter<SOG.Region, RG.Hatch> regionConverter,
|
||||
ITypedConverter<SOG.SubDX, List<RG.GeometryBase>> subdConverter,
|
||||
IConverterSettingsStore<RhinoConversionSettings> settingsStore
|
||||
IConverterSettingsStore<RhinoConversionSettings> settingsStore,
|
||||
IDataObjectInstanceRegistry dataObjectInstanceRegistry
|
||||
)
|
||||
{
|
||||
_arcConverter = arcConverter;
|
||||
@@ -61,13 +65,40 @@ public class DataObjectConverter
|
||||
_regionConverter = regionConverter;
|
||||
_subdConverter = subdConverter;
|
||||
_settingsStore = settingsStore;
|
||||
_dataObjectInstanceRegistry = dataObjectInstanceRegistry;
|
||||
}
|
||||
|
||||
public object Convert(Base target) => Convert((DataObject)target);
|
||||
|
||||
private List<RG.GeometryBase> GetConvertedGeometry(Base b)
|
||||
public List<(RG.GeometryBase a, Base b)> Convert(DataObject target)
|
||||
{
|
||||
return b switch
|
||||
var resultPairs = new List<(RG.GeometryBase, Base)>();
|
||||
|
||||
// check if displayValue contains ANY InstanceProxies - register for special handling
|
||||
var instanceProxies = target.displayValue.OfType<InstanceProxy>().ToList();
|
||||
if (instanceProxies.Count > 0)
|
||||
{
|
||||
_dataObjectInstanceRegistry.Register(target.applicationId ?? target.id.NotNull(), target, instanceProxies);
|
||||
|
||||
return resultPairs;
|
||||
}
|
||||
|
||||
// normal display value conversion
|
||||
foreach (var item in target.displayValue)
|
||||
{
|
||||
var converted = GetConvertedGeometry(item);
|
||||
foreach (var geom in converted)
|
||||
{
|
||||
geom.Transform(GetUnitsTransform(item));
|
||||
resultPairs.Add((geom, item));
|
||||
}
|
||||
}
|
||||
|
||||
return resultPairs;
|
||||
}
|
||||
|
||||
private List<RG.GeometryBase> GetConvertedGeometry(Base b) =>
|
||||
b switch
|
||||
{
|
||||
SOG.Arc arc => new() { _arcConverter.Convert(arc) },
|
||||
SOG.BrepX brep => _brepConverter.Convert(brep),
|
||||
@@ -85,23 +116,6 @@ public class DataObjectConverter
|
||||
SOG.SubDX subd => _subdConverter.Convert(subd),
|
||||
_ => throw new ConversionException($"Found unsupported fallback geometry: {b.GetType()}")
|
||||
};
|
||||
}
|
||||
|
||||
public List<(RG.GeometryBase a, Base b)> Convert(DataObject target)
|
||||
{
|
||||
var result = new List<RG.GeometryBase>();
|
||||
foreach (var item in target.displayValue)
|
||||
{
|
||||
var converted = GetConvertedGeometry(item);
|
||||
foreach (var x in converted)
|
||||
{
|
||||
x.Transform(GetUnitsTransform(item));
|
||||
result.Add(x);
|
||||
}
|
||||
}
|
||||
|
||||
return result.Zip(target.displayValue, (a, b) => (a, b)).ToList();
|
||||
}
|
||||
|
||||
private RG.Transform GetUnitsTransform(Base speckleObject)
|
||||
{
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
using Rhino.DocObjects;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.Common.Objects;
|
||||
using Speckle.Sdk.Common.Exceptions;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Converters.Rhino.ToSpeckle.TopLevel;
|
||||
|
||||
[NameAndRankValue(typeof(ViewInfo), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)]
|
||||
public class ViewInfoToSpeckleTopLevelConverter : IToSpeckleTopLevelConverter, ITypedConverter<ViewInfo, SOO.Camera>
|
||||
{
|
||||
private readonly ITypedConverter<RG.Point3d, SOG.Point> _pointConverter;
|
||||
private readonly ITypedConverter<RG.Vector3d, SOG.Vector> _vectorConverter;
|
||||
|
||||
public ViewInfoToSpeckleTopLevelConverter(
|
||||
ITypedConverter<RG.Point3d, SOG.Point> pointConverter,
|
||||
ITypedConverter<RG.Vector3d, SOG.Vector> vectorConverter
|
||||
)
|
||||
{
|
||||
_pointConverter = pointConverter;
|
||||
_vectorConverter = vectorConverter;
|
||||
}
|
||||
|
||||
public Base Convert(object target) => Convert((ViewInfo)target);
|
||||
|
||||
public SOO.Camera Convert(ViewInfo target)
|
||||
{
|
||||
if (target.Viewport.IsParallelProjection)
|
||||
{
|
||||
throw new ConversionException("Parallel projection views are not supported.");
|
||||
}
|
||||
|
||||
return new()
|
||||
{
|
||||
name = target.Name,
|
||||
position = _pointConverter.Convert(target.Viewport.CameraLocation),
|
||||
up = _vectorConverter.Convert(target.Viewport.CameraY),
|
||||
forward = _vectorConverter.Convert(target.Viewport.CameraZ),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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, )",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Speckle.Connectors.DUI.Bridge;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Sdk.Credentials;
|
||||
using Speckle.Sdk.SQLite;
|
||||
|
||||
namespace Speckle.Connectors.DUI.Bindings;
|
||||
|
||||
@@ -9,14 +11,25 @@ public class AccountBinding : IBinding
|
||||
public IBrowserBridge Parent { get; }
|
||||
|
||||
private readonly IAccountManager _accountManager;
|
||||
private readonly ISqLiteJsonCacheManager _jsonCacheManager;
|
||||
|
||||
public AccountBinding(IBrowserBridge bridge, IAccountManager accountManager)
|
||||
public AccountBinding(
|
||||
IBrowserBridge bridge,
|
||||
IAccountManager accountManager,
|
||||
ISqLiteJsonCacheManagerFactory sqLiteJsonCacheManagerFactory
|
||||
)
|
||||
{
|
||||
Parent = bridge;
|
||||
_accountManager = accountManager;
|
||||
_jsonCacheManager = sqLiteJsonCacheManagerFactory.CreateForUser("Accounts");
|
||||
}
|
||||
|
||||
public Account[] GetAccounts() => _accountManager.GetAccounts().ToArray();
|
||||
|
||||
public void AddAccount(string accountId, Account account)
|
||||
{
|
||||
_jsonCacheManager.SaveObject(accountId, JsonConvert.SerializeObject(account));
|
||||
}
|
||||
|
||||
public void RemoveAccount(string accountId) => _accountManager.RemoveAccount(accountId);
|
||||
}
|
||||
|
||||
@@ -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, )"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user