fix: 别名树 IndexPath 改用 Navisworks 官方 API CreatePathId/ResolvePathId

- AliasNodeIdentity.FromModelItem: 用 doc.Models.CreatePathId 替代 GetModelItemIndexPaths
- 存储格式统一为 'modelIndex:PathId'(冒号分隔,PathId 为斜杠分隔)
- FindModelItemByNodeKey: 用 Models.ResolvePathId 替代手写 LocateByIndexPath
- 删除 LocateByIndexPath、LocateByDisplayPath、LoadPathChild、EnsureChildLoaded
- 与 PathAnimationManager/BatchQueueManager 使用完全一致的 PathId 格式
This commit is contained in:
tian 2026-05-31 23:26:54 +08:00
parent f7544a2095
commit 6260c175d4
2 changed files with 153 additions and 107 deletions

View File

@ -53,16 +53,16 @@ namespace NavisworksTransport.Core.AliasTree
if (System.Threading.Thread.CurrentThread.GetApartmentState() != System.Threading.ApartmentState.STA)
LogManager.Error($"[别名树] FromModelItem 不在 STA 线程!");
// 1. IndexPath — 复用 ModelItemAnalysisHelper
// 1. IndexPath — 使用 Navisworks 官方 API CreatePathId
string indexPath;
try
{
var indexPaths = ModelItemAnalysisHelper.GetModelItemIndexPaths(item);
indexPath = indexPaths.FirstOrDefault() ?? string.Empty;
var pid = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(item);
indexPath = $"{pid.ModelIndex}:{pid.PathId}";
}
catch (Exception ex)
{
LogManager.Error($"[别名树] GetModelItemIndexPaths 异常: {ex.Message}");
LogManager.Error($"[别名树] CreatePathId 异常: {ex.Message}");
indexPath = string.Empty;
}

View File

@ -9,6 +9,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Autodesk.Navisworks.Api.DocumentParts;
using NavisApplication = Autodesk.Navisworks.Api.Application;
namespace NavisworksTransport.UI.WPF.Views
@ -84,6 +85,11 @@ namespace NavisworksTransport.UI.WPF.Views
RebuildTree();
else
RefreshAliasesFromStore();
// 无论树是否已构建,确保已存别名的深层节点路径被加载
var aliasMap = _dataStore?.LoadAll();
if (aliasMap != null && aliasMap.Count > 0)
EnsureAliasedPathsVisible(aliasMap);
}
private void RefreshAliasesFromStore()
@ -289,6 +295,10 @@ namespace NavisworksTransport.UI.WPF.Views
// 重新挂载选择事件(可能文档刚切换)
HookSelectionEvents();
// 对已有别名的深层节点,强制加载其路径使其可见
if (aliasMap != null && aliasMap.Count > 0)
EnsureAliasedPathsVisible(aliasMap);
LogManager.Info($"[别名树] 树构建完成,共 {_nodeMap.Count} 个节点");
}
catch (Exception ex)
@ -392,128 +402,41 @@ namespace NavisworksTransport.UI.WPF.Views
}
// ============================================================
// 从 NodeKey 回溯定位 ModelItem
// 策略IndexPathCOM 树索引路径优先DisplayPath 降级
// 从 NodeKey 回溯定位 ModelItem仅用 IndexPath
// ============================================================
private Autodesk.Navisworks.Api.ModelItem FindModelItemByNodeKey(string key)
{
if (!AliasNodeIdentity.TryParseKey(key, out var identity))
return null;
if (string.IsNullOrEmpty(identity.IndexPath))
return null;
try
{
var doc = NavisApplication.ActiveDocument;
if (doc == null || doc.Models == null) return null;
// 策略 1从 IndexPath 定位(最快、最精确)
if (!string.IsNullOrEmpty(identity.IndexPath))
{
var item = LocateByIndexPath(doc, identity.IndexPath);
if (item != null && ModelItemAnalysisHelper.IsModelItemValid(item))
return item;
}
// 解析 IndexPath: "modelIndex:PathId"
int colonIdx = identity.IndexPath.IndexOf(':');
if (colonIdx < 0) return null;
if (!int.TryParse(identity.IndexPath.Substring(0, colonIdx), out int modelIndex))
return null;
string pathId = identity.IndexPath.Substring(colonIdx + 1);
// 策略 2从 DisplayPath 降级定位
return LocateByDisplayPath(doc, identity);
LogManager.Info($"[别名树] FindByKey: IndexPath={identity.IndexPath}, modelIndex={modelIndex}, pathId={pathId}");
var result = doc.Models.ResolvePathId(new ModelItemPathId { ModelIndex = modelIndex, PathId = pathId });
LogManager.Info($"[别名树] FindByKey: ResolvePathId -> {(result != null ? result.DisplayName : "null")}");
return result;
}
catch
catch (Exception ex)
{
LogManager.Error($"[别名树] FindByKey 失败: {key}, {ex.Message}");
return null;
}
}
/// <summary>
/// 按 COM 索引路径定位 ModelItem
/// 路径格式:"模型索引,一级子索引,二级子索引,..."
/// </summary>
private static Autodesk.Navisworks.Api.ModelItem LocateByIndexPath(
Autodesk.Navisworks.Api.Document doc, string indexPath)
{
string[] parts = indexPath.Split(',');
if (parts.Length == 0) return null;
if (!int.TryParse(parts[0], out int modelIndex))
return null;
// 取模型
if (modelIndex < 0 || modelIndex >= doc.Models.Count)
return null;
var model = doc.Models[modelIndex];
if (model?.RootItem == null) return null;
Autodesk.Navisworks.Api.ModelItem current = model.RootItem;
for (int i = 1; i < parts.Length; i++)
{
if (!int.TryParse(parts[i], out int childIndex))
return null;
var childrenList = current.Children.ToList();
if (childIndex < 0 || childIndex >= childrenList.Count)
return null;
current = childrenList[childIndex];
}
return current;
}
/// <summary>
/// 按 DisplayPath 降级定位
/// </summary>
private static Autodesk.Navisworks.Api.ModelItem LocateByDisplayPath(
Autodesk.Navisworks.Api.Document doc, AliasNodeIdentity identity)
{
string[] parts = identity.GetHierarchicalPath().Split('/');
if (parts.Length == 0) return null;
// parts[0] 是模型名/根节点名,匹配模型
foreach (var model in doc.Models)
{
var current = model.RootItem;
if (current == null) continue;
if (current.DisplayName != parts[0]) continue;
for (int i = 1; i < parts.Length; i++)
{
string targetName = parts[i];
Autodesk.Navisworks.Api.ModelItem matched = null;
foreach (var child in current.Children)
{
if (child.DisplayName == targetName)
{
matched = child;
break;
}
}
if (matched == null) return null;
current = matched;
}
// 同级同名去重索引
int siblingIndex = identity.GetSiblingIndex();
if (siblingIndex > 0)
{
int idx = 0;
foreach (var child in current.Children)
{
if (child.DisplayName == parts.Last())
{
if (idx == siblingIndex) return child;
idx++;
}
}
return null;
}
return current;
}
return null;
}
// ============================================================
// 展开并高亮指定 ModelItem
// 策略:利用 ModelItem.Parent 链逐层匹配,不依赖 IndexPath。
@ -667,6 +590,129 @@ namespace NavisworksTransport.UI.WPF.Views
}
}
/// <summary>
/// 确保所有已存别名的节点路径在树中可见。
/// 在 RebuildTree 后调用,对深层别名节点逐级加载其祖先。
/// </summary>
private void EnsureAliasedPathsVisible(Dictionary<string, string> aliasMap)
{
LogManager.Info($"[别名树] EnsureAliasedPathsVisible: 共 {aliasMap.Count} 条别名");
foreach (var kvp in aliasMap)
{
LogManager.Info($"[别名树] alias: key={kvp.Key} -> \"{kvp.Value}\"");
}
int loaded = 0;
foreach (var kvp in aliasMap)
{
try
{
LogManager.Info($"[别名树] EnsureAliased: key={kvp.Key}, alias={kvp.Value}");
var item = FindModelItemByNodeKey(kvp.Key);
if (item == null)
{
LogManager.Warning($"[别名树] EnsureAliased: FindModelItemByNodeKey 返回 null, key={kvp.Key}");
continue;
}
LogManager.Info($"[别名树] EnsureAliased: 找到 ModelItem DisplayName={item.DisplayName}, ClassName={item.ClassName}");
if (EnsurePathLoaded(item))
loaded++;
else
LogManager.Warning($"[别名树] EnsureAliased: EnsurePathLoaded 返回 false, item={item.DisplayName}");
}
catch (Exception ex)
{
LogManager.Error($"[别名树] 加载别名路径失败: {kvp.Key}, {ex.Message}");
}
}
if (loaded > 0)
LogManager.Info($"[别名树] 已确保 {loaded} 个别名节点路径可见");
}
/// <summary>
/// 沿 ModelItem 父链逐层加载节点到树中(不选中)。
/// 逻辑与 ExpandAndSelect 相同,只是最终不选中。
/// </summary>
private bool EnsurePathLoaded(Autodesk.Navisworks.Api.ModelItem item)
{
// 1. 构建祖先链
var chain = new System.Collections.Generic.List<Autodesk.Navisworks.Api.ModelItem>();
var current = item;
while (current != null)
{
chain.Insert(0, current);
current = current.Parent;
}
var chainStr = string.Join(" -> ", chain.ConvertAll(c => c.DisplayName ?? "?"));
LogManager.Info($"[别名树] EnsurePathLoaded: chain=[{chainStr}], depth={chain.Count - 1}");
// 2. 匹配根节点
var rootNodes = AliasTreeView.ItemsSource
as System.Collections.ObjectModel.ObservableCollection<AliasNodeViewModel>;
if (rootNodes == null) { LogManager.Warning("[别名树] EnsurePathLoaded: rootNodes null"); return false; }
AliasNodeViewModel treeNode = null;
foreach (var rn in rootNodes)
{
if (rn.ModelItem != null && rn.ModelItem.Equals(chain[0]))
{
treeNode = rn;
break;
}
}
if (treeNode == null)
{
LogManager.Warning($"[别名树] EnsurePathLoaded: 根节点未匹配 chain[0]={chain[0].DisplayName}");
return false;
}
if (chain.Count == 1) return true;
// 3. 逐层加载
for (int i = 1; i < chain.Count; i++)
{
var targetModelItem = chain[i];
LogManager.Info($"[别名树] EnsurePathLoaded: L{i} cur={treeNode.OriginalName} target={targetModelItem.DisplayName} childCnt={treeNode.Children.Count}");
ExpandTreeNode(treeNode);
if (treeNode.Children.Count == 1 && treeNode.Children[0].OriginalName == "加载中...")
{
LogManager.Info($"[别名树] EnsurePathLoaded: L{i} 懒加载子节点");
LazyLoadChildren(treeNode, _dataStore?.LoadAll());
}
AliasNodeViewModel child = null;
foreach (var c in treeNode.Children)
{
if (c.OriginalName != "加载中..." && c.ModelItem != null && c.ModelItem.Equals(targetModelItem))
{
child = c;
break;
}
}
if (child == null)
{
LogManager.Info($"[别名树] EnsurePathLoaded: L{i} 未匹配, BuildNode 创建");
child = BuildNode(targetModelItem, _dataStore?.LoadAll(), treeNode.Depth + 1);
if (child != null)
{
child.Parent = treeNode;
treeNode.Children.Add(child);
}
}
if (child == null)
{
LogManager.Warning($"[别名树] EnsurePathLoaded: L{i} child 为 null, 中断");
return false;
}
treeNode = child;
}
return true;
}
/// <summary>
/// 从 TreeView 根开始,沿 parent 链找 ViewModel 对应的 TreeViewItem。
/// 虚拟化下可能返回 null。