Skip to content

Commit

Permalink
feat(Navisworks): CNX-1064 - Add hierarchy path property to converted…
Browse files Browse the repository at this point in the history
… Navisworks objects (#500)

* Add DisplayPathHelper class for generating display paths in Navisworks

This commit adds a new file, `DisplayPathHelper.cs`, which contains a helper class for generating display paths by traversing Navisworks model item ancestors. The `GenerateDisplayPath` method takes a `ModelItem` as input and returns a string representing the full path of display names from ancestors, stopping at the first object ancestor. If no first object ancestor is found, it returns just the model item's display name.

The helper class uses a constant `PATH_DELIMITER` to separate the display names in the generated path. It throws an exception if the input `modelItem` is null.

The algorithm works by starting from the first object ancestor and traversing backwards to the root, collecting non-empty display names along the way. Finally, it reverses the collected names and joins them with the delimiter to form the final display path string.

These changes improve code reusability and readability by encapsulating logic related to generating display paths in Navisworks models.

* Update NavisworksRootObjectBuilder.cs

- Modified the CreateNavisworksObject method to handle cases where the convertedBase or groupKey is null and return null in those cases
- Added a new private method GetDisplayPath to retrieve the modelItem and displayPath based on the applicationId
- Updated the CreateNavisworksObject methods to include the displayNamePath property in the returned NavisworksObject

* Refactor NavisworksRootObjectBuilder.cs: Add FindMeaningfulAncestorName method

This commit adds a new private method, FindMeaningfulAncestorName, to the NavisworksRootObjectBuilder class. This method is used to find the name of the most meaningful ancestor of a given NAV.ModelItem object. It iterates through the ancestors until it finds an ancestor with a non-empty DisplayName and no geometry. The found name is then used as the "name" property in the created NavisworksObject instances. Additionally, the "displayNamePath" property has been renamed to "path" for consistency.

* Refactor display path generation for Navisworks model items

- Update the helper class to generate display paths by traversing meaningful Navisworks model item ancestors.
- Skip nodes without meaningful names or geometry nodes when generating the display path.
- Reverse the collected names to build the path from root to leaf.

* Rename DisplayPathHelper.cs to HierarchyHelper.cs
- Renamed the file from "DisplayPathHelper.cs" to "HierarchyHelper.cs"
- Updated all references to the renamed file

Refactor helper class for generating display paths
- Refactored the helper class to extract hierarchical context from Navisworks model items in a single traversal
- Changed the method name from "GenerateDisplayPath" to "ExtractContext"
- Modified the method signature to return a tuple of strings (Name, Path)
- Updated the implementation logic accordingly

Improve traversal and extraction logic
- Improved the traversal logic by collecting both name and path information while traversing up the tree once
- Extracted meaningful name and path information from model items in a more efficient way
- Handled cases where model item has no meaningful name or is a geometry node

* Refactor NavisworksRootObjectBuilder.cs for improved context extraction

- Extracted the GetContext method to retrieve the name and path of a model item
- Replaced calls to GetDisplayPath with calls to GetContext for consistency
- Updated CreateNavisworksObject and ConvertBase methods to use the extracted context information instead of finding meaningful ancestor names
- Updated the "path" property in the returned NavisworksObject instances with the extracted path
  • Loading branch information
jsdbroughton authored Jan 29, 2025
1 parent bb252a5 commit 8ca43a1
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 9 deletions.
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

0 comments on commit 8ca43a1

Please sign in to comment.