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:
parent
f7544a2095
commit
6260c175d4
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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。
|
||||
|
||||
Loading…
Reference in New Issue
Block a user