From 6260c175d49fe0296f85dc4970c6bb11e86ccd45 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Sun, 31 May 2026 23:26:54 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=88=AB=E5=90=8D=E6=A0=91=20IndexPath?= =?UTF-8?q?=20=E6=94=B9=E7=94=A8=20Navisworks=20=E5=AE=98=E6=96=B9=20API?= =?UTF-8?q?=20CreatePathId/ResolvePathId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AliasNodeIdentity.FromModelItem: 用 doc.Models.CreatePathId 替代 GetModelItemIndexPaths - 存储格式统一为 'modelIndex:PathId'(冒号分隔,PathId 为斜杠分隔) - FindModelItemByNodeKey: 用 Models.ResolvePathId 替代手写 LocateByIndexPath - 删除 LocateByIndexPath、LocateByDisplayPath、LoadPathChild、EnsureChildLoaded - 与 PathAnimationManager/BatchQueueManager 使用完全一致的 PathId 格式 --- src/Core/AliasTree/AliasNodeIdentity.cs | 8 +- src/UI/WPF/Views/AliasTreeControl.xaml.cs | 252 +++++++++++++--------- 2 files changed, 153 insertions(+), 107 deletions(-) diff --git a/src/Core/AliasTree/AliasNodeIdentity.cs b/src/Core/AliasTree/AliasNodeIdentity.cs index 38ef7e1..7442ecf 100644 --- a/src/Core/AliasTree/AliasNodeIdentity.cs +++ b/src/Core/AliasTree/AliasNodeIdentity.cs @@ -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; } diff --git a/src/UI/WPF/Views/AliasTreeControl.xaml.cs b/src/UI/WPF/Views/AliasTreeControl.xaml.cs index 31585cc..a02383e 100644 --- a/src/UI/WPF/Views/AliasTreeControl.xaml.cs +++ b/src/UI/WPF/Views/AliasTreeControl.xaml.cs @@ -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 - // 策略:IndexPath(COM 树索引路径)优先,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; } } - /// - /// 按 COM 索引路径定位 ModelItem - /// 路径格式:"模型索引,一级子索引,二级子索引,..." - /// - 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; - } - - /// - /// 按 DisplayPath 降级定位 - /// - 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 } } + /// + /// 确保所有已存别名的节点路径在树中可见。 + /// 在 RebuildTree 后调用,对深层别名节点逐级加载其祖先。 + /// + private void EnsureAliasedPathsVisible(Dictionary 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} 个别名节点路径可见"); + } + + /// + /// 沿 ModelItem 父链逐层加载节点到树中(不选中)。 + /// 逻辑与 ExpandAndSelect 相同,只是最终不选中。 + /// + private bool EnsurePathLoaded(Autodesk.Navisworks.Api.ModelItem item) + { + // 1. 构建祖先链 + var chain = new System.Collections.Generic.List(); + 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; + 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; + } + /// /// 从 TreeView 根开始,沿 parent 链找 ViewModel 对应的 TreeViewItem。 /// 虚拟化下可能返回 null。