Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Navisworks): CNX-1064 - Add hierarchy path property to converted Navisworks objects #500

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,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.Settings;
using Speckle.Converters.Common;
using Speckle.Objects.Data;
Expand Down Expand Up @@ -194,43 +195,69 @@ HashSet<string> processedPaths
finalElements.Add(collection);
break;
default:
finalElements.Add(CreateNavisworksObject(kvp.Value));
if (CreateNavisworksObject(kvp.Value) is { } navisworksObject)
{
finalElements.Add(navisworksObject);
}

break;
}
}
}

private (string name, string path) GetContext(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) =>
new()
private NavisworksObject CreateNavisworksObject(string groupKey, List<Base> siblingBases)
{
(string name, string path) = GetContext(groupKey);

return new NavisworksObject
{
name = elementSelectionService.GetModelItemFromPath(groupKey).DisplayName ?? string.Empty,
name = name,
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
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) =>
new()
private NavisworksObject? CreateNavisworksObject(Base convertedBase)
{
if (convertedBase.applicationId == null)
{
name = convertedBase["name"] as string ?? string.Empty,
return null;
}

(string name, string path) = GetContext(convertedBase.applicationId);

return new NavisworksObject
{
name = name,
displayValue = convertedBase["displayValue"] as List<Base> ?? [],
properties = convertedBase["properties"] as Dictionary<string, object?> ?? [],
units = converterSettings.Current.Derived.SpeckleUnits,
applicationId = convertedBase.applicationId
applicationId = convertedBase.applicationId,
["path"] = path
};
}

private Task AddProxiesToCollection(
Collection rootCollection,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
namespace Speckle.Converter.Navisworks.Helpers;

/// <summary>
/// Helper class for extracting hierarchical context from Navisworks model items in a single traversal.
/// </summary>
public static class HierarchyHelper
{
private const char PATH_DELIMITER = '>';

/// <summary>
/// Extracts the meaningful name and path from a ModelItem in a single traversal.
/// </summary>
public static (string Name, string Path) ExtractContext(NAV.ModelItem modelItem)
{
if (modelItem == null)
{
throw new ArgumentNullException(nameof(modelItem));
}

var ancestors = new List<string>();
var meaningfulName = string.Empty;
var current = modelItem;

// Start with the root document name if available
if (modelItem.HasModel && !string.IsNullOrEmpty(modelItem.Model.FileName))
{
ancestors.Add(Path.GetFileNameWithoutExtension(modelItem.Model.FileName));
}

// Traverse up the tree once, collecting both name and path information
while (current != null)
{
if (IsMeaningfulNode(current))
{
// First meaningful name we find is our object name (if we haven't found one yet)
if (string.IsNullOrEmpty(meaningfulName))
{
meaningfulName = current.DisplayName;
}
// Add to ancestors if it's not a duplicate
else if (ancestors.Count == 0 || ancestors.Last() != current.DisplayName)
{
ancestors.Add(current.DisplayName);
}
}
current = current.Parent;
}

// Build path excluding the name we found
ancestors.Reverse();

var path = string.Join($" {PATH_DELIMITER} ", ancestors);

return (meaningfulName, path);
}

private static bool IsMeaningfulNode(NAV.ModelItem item) =>
!string.IsNullOrEmpty(item.DisplayName) && (!item.HasGeometry || !string.IsNullOrEmpty(item.ClassDisplayName));
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<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" />
Expand Down
Loading