perf: BuildNode 延迟 CreatePathId + DisplayPath 同名去重修复
- BuildNode 不再调 FromModelItem/CreatePathId,改用父节点 DisplayPath 拼接 - CreatePathId 只在保存别名时通过 EnsureFullNodeKey 触发 - LazyLoadChildren 用 nameCount 字典计算 siblingIndex,BuildNode 追加 #N 去重 - 子节点 DisplayPath 拼接时剥离父路径中的 ||前缀和#N后缀,与 FromModelItem 一致 - LazyLoadChildren 直接用 parentNode.ModelItem,不再调 FindModelItemByNodeKey - 别名匹配去掉 fallback,精确到#N - _dataStore.LoadAll() 提升到循环外
This commit is contained in:
parent
61318b450d
commit
d670ae5c3c
@ -279,8 +279,12 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
|
||||
// 全量加载别名到内存
|
||||
Dictionary<string, string> aliasMap = null;
|
||||
Dictionary<string, string> displayPathToAlias = null;
|
||||
if (_dataStore != null)
|
||||
{
|
||||
aliasMap = _dataStore.LoadAll();
|
||||
displayPathToAlias = BuildDisplayPathAliasMap(aliasMap);
|
||||
}
|
||||
|
||||
// 遍历所有 root items
|
||||
foreach (var model in doc.Models)
|
||||
@ -288,7 +292,7 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
var rootItem = model.RootItem;
|
||||
if (rootItem != null)
|
||||
{
|
||||
var node = BuildNode(rootItem, aliasMap, 0);
|
||||
var node = BuildNode(rootItem, aliasMap, displayPathToAlias, 0, "", 0);
|
||||
if (node != null)
|
||||
rootNodes.Add(node);
|
||||
}
|
||||
@ -310,45 +314,51 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归构建节点树(主线程调用)
|
||||
/// 递归构建节点树(主线程调用)。
|
||||
/// siblingIndex 用于同名兄弟去重,0 表示第一个(不追加后缀)。
|
||||
/// </summary>
|
||||
private AliasNodeViewModel BuildNode(Autodesk.Navisworks.Api.ModelItem modelItem,
|
||||
Dictionary<string, string> aliasMap, int depth)
|
||||
Dictionary<string, string> aliasMap,
|
||||
Dictionary<string, string> displayPathToAlias,
|
||||
int depth, string parentDisplayPath, int siblingIndex)
|
||||
{
|
||||
if (modelItem == null) return null;
|
||||
|
||||
AliasNodeIdentity identity;
|
||||
try
|
||||
string displayName = modelItem.DisplayName ?? "?";
|
||||
// 父节点 key 可能是完整格式 "IndexPath||DisplayPath",也可能是 ".../name#N"
|
||||
// 只取纯 DisplayPath 部分(去掉 || 前缀和结尾的 #N 后缀)
|
||||
string parentPath = parentDisplayPath;
|
||||
int sepIdx = parentPath.IndexOf("||", StringComparison.Ordinal);
|
||||
if (sepIdx >= 0) parentPath = parentPath.Substring(sepIdx + 2);
|
||||
// 去掉结尾的 #数字 后缀(中间层不加 siblingIndex)
|
||||
int hashIdx = parentPath.LastIndexOf('#');
|
||||
if (hashIdx > 0)
|
||||
{
|
||||
identity = AliasNodeIdentity.FromModelItem(modelItem);
|
||||
string suffix = parentPath.Substring(hashIdx + 1);
|
||||
bool isNumeric = suffix.Length > 0 && suffix.All(char.IsDigit);
|
||||
if (isNumeric) parentPath = parentPath.Substring(0, hashIdx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[别名树] BuildNode FromModelItem 异常: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
string key = identity.ToKey();
|
||||
string displayPath = string.IsNullOrEmpty(parentPath)
|
||||
? displayName
|
||||
: parentPath + "/" + displayName;
|
||||
// 同名兄弟去重:第二个起追加 #索引
|
||||
string displayPathKey = siblingIndex > 0 ? displayPath + "#" + siblingIndex : displayPath;
|
||||
|
||||
// 别名匹配:精确匹配 displayPathKey
|
||||
string alias = null;
|
||||
aliasMap?.TryGetValue(key, out alias);
|
||||
|
||||
// 旧格式兼容:也尝试用纯 DisplayPath 查找
|
||||
if (alias == null && !string.IsNullOrEmpty(identity.IndexPath))
|
||||
{
|
||||
aliasMap?.TryGetValue(identity.DisplayPath, out alias);
|
||||
}
|
||||
displayPathToAlias?.TryGetValue(displayPathKey, out alias);
|
||||
|
||||
var node = new AliasNodeViewModel
|
||||
{
|
||||
NodeKey = key,
|
||||
NodeKey = displayPathKey,
|
||||
ModelItem = modelItem,
|
||||
OriginalName = modelItem.DisplayName ?? "?",
|
||||
OriginalName = displayName,
|
||||
Alias = alias,
|
||||
ChildCount = modelItem.Children.Count(),
|
||||
Depth = depth
|
||||
};
|
||||
|
||||
_nodeMap[key] = node;
|
||||
_nodeMap[displayPathKey] = node;
|
||||
|
||||
// 深度限制:超过阈值且无别名 → 不展开子节点,不加占位
|
||||
bool hasChildren = node.ChildCount > 0;
|
||||
@ -367,10 +377,8 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 懒加载子节点(在 Expanded 事件中触发)
|
||||
/// </summary>
|
||||
private void LazyLoadChildren(AliasNodeViewModel parentNode, Dictionary<string, string> aliasMap)
|
||||
private void LazyLoadChildren(AliasNodeViewModel parentNode, Dictionary<string, string> aliasMap,
|
||||
Dictionary<string, string> displayPathToAlias)
|
||||
{
|
||||
// 检查是否只有占位节点
|
||||
if (parentNode.Children.Count != 1 || parentNode.Children[0].OriginalName != "加载中...")
|
||||
@ -380,16 +388,20 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
|
||||
try
|
||||
{
|
||||
var doc = NavisApplication.ActiveDocument;
|
||||
if (doc == null) return;
|
||||
|
||||
// 从 nodeKey 反推出 ModelItem
|
||||
var item = FindModelItemByNodeKey(parentNode.NodeKey);
|
||||
var item = parentNode.ModelItem;
|
||||
if (item == null) return;
|
||||
|
||||
// 同名去重:统计每个名字已出现的次数
|
||||
var nameCount = new Dictionary<string, int>();
|
||||
foreach (var childItem in item.Children)
|
||||
{
|
||||
var childNode = BuildNode(childItem, aliasMap, parentNode.Depth + 1);
|
||||
string name = childItem.DisplayName ?? "?";
|
||||
nameCount.TryGetValue(name, out int count);
|
||||
int siblingIdx = count;
|
||||
nameCount[name] = count + 1;
|
||||
|
||||
var childNode = BuildNode(childItem, aliasMap, displayPathToAlias,
|
||||
parentNode.Depth + 1, parentNode.NodeKey, siblingIdx);
|
||||
if (childNode != null)
|
||||
{
|
||||
childNode.Parent = parentNode;
|
||||
@ -404,37 +416,84 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 从 NodeKey 回溯定位 ModelItem(仅用 IndexPath)
|
||||
/// <summary>
|
||||
/// 从 aliasMap 构建 DisplayPath → alias 索引(去掉 IndexPath|| 前缀)
|
||||
/// </summary>
|
||||
private static Dictionary<string, string> BuildDisplayPathAliasMap(Dictionary<string, string> aliasMap)
|
||||
{
|
||||
if (aliasMap == null || aliasMap.Count == 0) return null;
|
||||
var map = new Dictionary<string, string>();
|
||||
foreach (var kvp in aliasMap)
|
||||
{
|
||||
// key 格式: "IndexPath||DisplayPath" → 取 DisplayPath 部分
|
||||
int sepIdx = kvp.Key.IndexOf("||", StringComparison.Ordinal);
|
||||
string displayPath = sepIdx >= 0 ? kvp.Key.Substring(sepIdx + 2) : kvp.Key;
|
||||
map[displayPath] = kvp.Value;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
// ============================================================
|
||||
|
||||
/// <summary>
|
||||
/// 确保节点有完整 NodeKey(含 IndexPath)。首次保存别名时调用 CreatePathId 升级 key。
|
||||
/// </summary>
|
||||
private void EnsureFullNodeKey(AliasNodeViewModel node)
|
||||
{
|
||||
if (node == null || node.ModelItem == null) return;
|
||||
// 已是完整格式
|
||||
if (node.NodeKey != null && node.NodeKey.Contains("||")) return;
|
||||
|
||||
try
|
||||
{
|
||||
var identity = AliasNodeIdentity.FromModelItem(node.ModelItem);
|
||||
string newKey = identity.ToKey();
|
||||
_nodeMap.Remove(node.NodeKey);
|
||||
_nodeMap[newKey] = node;
|
||||
node.NodeKey = newKey;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[别名树] EnsureFullNodeKey 失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private Autodesk.Navisworks.Api.ModelItem FindModelItemByNodeKey(string key)
|
||||
{
|
||||
if (!AliasNodeIdentity.TryParseKey(key, out var identity))
|
||||
return null;
|
||||
if (string.IsNullOrEmpty(identity.IndexPath))
|
||||
return null;
|
||||
if (string.IsNullOrEmpty(key)) return null;
|
||||
|
||||
// 判断 key 格式:含 "||" 为完整格式 → ResolvePathId;否则为 DisplayPath → _nodeMap 查找
|
||||
int sepIdx = key.IndexOf("||", StringComparison.Ordinal);
|
||||
if (sepIdx >= 0)
|
||||
{
|
||||
if (!AliasNodeIdentity.TryParseKey(key, out var identity) || string.IsNullOrEmpty(identity.IndexPath))
|
||||
return null;
|
||||
return ResolveByIndexPath(identity.IndexPath);
|
||||
}
|
||||
|
||||
// 简化格式:DisplayPath → 查 _nodeMap
|
||||
if (_nodeMap.TryGetValue(key, out var node) && node.ModelItem != null)
|
||||
return node.ModelItem;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Autodesk.Navisworks.Api.ModelItem ResolveByIndexPath(string indexPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var doc = NavisApplication.ActiveDocument;
|
||||
if (doc == null || doc.Models == null) return null;
|
||||
|
||||
// 解析 IndexPath: "modelIndex:PathId"
|
||||
int colonIdx = identity.IndexPath.IndexOf(':');
|
||||
int colonIdx = indexPath.IndexOf(':');
|
||||
if (colonIdx < 0) return null;
|
||||
if (!int.TryParse(identity.IndexPath.Substring(0, colonIdx), out int modelIndex))
|
||||
if (!int.TryParse(indexPath.Substring(0, colonIdx), out int modelIndex))
|
||||
return null;
|
||||
string pathId = identity.IndexPath.Substring(colonIdx + 1);
|
||||
string pathId = indexPath.Substring(colonIdx + 1);
|
||||
|
||||
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;
|
||||
return doc.Models.ResolvePathId(new ModelItemPathId { ModelIndex = modelIndex, PathId = pathId });
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch
|
||||
{
|
||||
LogManager.Error($"[别名树] FindByKey 失败: {key}, {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -499,9 +558,10 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
|
||||
// 加载子节点(有占位 → 全量懒加载;否则只找目标那一个)
|
||||
var aliasMap = _dataStore?.LoadAll();
|
||||
var displayAlias = BuildDisplayPathAliasMap(aliasMap);
|
||||
if (treeNode.Children.Count == 1 && treeNode.Children[0].OriginalName == "加载中...")
|
||||
{
|
||||
LazyLoadChildren(treeNode, aliasMap);
|
||||
LazyLoadChildren(treeNode, aliasMap, displayAlias);
|
||||
}
|
||||
|
||||
// 在已加载的子节点中查找匹配
|
||||
@ -518,7 +578,8 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
// 没找到(深度超限节点)→ 直接用 ModelItem 创建
|
||||
if (child == null)
|
||||
{
|
||||
child = BuildNode(targetModelItem, aliasMap, treeNode.Depth + 1);
|
||||
child = BuildNode(targetModelItem, aliasMap, displayAlias,
|
||||
treeNode.Depth + 1, treeNode.NodeKey, 0);
|
||||
if (child != null)
|
||||
{
|
||||
child.Parent = treeNode;
|
||||
@ -696,6 +757,8 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
if (chain.Count == 1) return true;
|
||||
|
||||
// 3. 逐层加载
|
||||
var aliasMap = _dataStore?.LoadAll();
|
||||
var displayAlias = BuildDisplayPathAliasMap(aliasMap);
|
||||
for (int i = 1; i < chain.Count; i++)
|
||||
{
|
||||
var targetModelItem = chain[i];
|
||||
@ -705,7 +768,7 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
if (treeNode.Children.Count == 1 && treeNode.Children[0].OriginalName == "加载中...")
|
||||
{
|
||||
LogManager.Info($"[别名树] EnsurePathLoaded: L{i} 懒加载子节点");
|
||||
LazyLoadChildren(treeNode, _dataStore?.LoadAll());
|
||||
LazyLoadChildren(treeNode, aliasMap, displayAlias);
|
||||
}
|
||||
|
||||
AliasNodeViewModel child = null;
|
||||
@ -721,7 +784,8 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
if (child == null)
|
||||
{
|
||||
LogManager.Info($"[别名树] EnsurePathLoaded: L{i} 未匹配, BuildNode 创建");
|
||||
child = BuildNode(targetModelItem, _dataStore?.LoadAll(), treeNode.Depth + 1);
|
||||
child = BuildNode(targetModelItem, aliasMap, displayAlias,
|
||||
treeNode.Depth + 1, treeNode.NodeKey, 0);
|
||||
if (child != null)
|
||||
{
|
||||
child.Parent = treeNode;
|
||||
@ -974,6 +1038,7 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
LogManager.Info($"[别名树] 内联编辑: {newAlias}, NodeKey={node.NodeKey}");
|
||||
try
|
||||
{
|
||||
EnsureFullNodeKey(node);
|
||||
AliasNodeIdentity.TryParseKey(node.NodeKey, out var identity);
|
||||
_dataStore.SetAlias(identity, newAlias);
|
||||
}
|
||||
@ -1066,6 +1131,7 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
var node = GetSelectedNode();
|
||||
if (node == null || _dataStore == null) return;
|
||||
|
||||
EnsureFullNodeKey(node);
|
||||
AliasNodeIdentity.TryParseKey(node.NodeKey, out var identity);
|
||||
_dataStore.DeleteAlias(identity);
|
||||
node.Alias = null;
|
||||
@ -1183,7 +1249,8 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
if (!(e.OriginalSource is TreeViewItem tvi) || !(tvi.DataContext is AliasNodeViewModel node))
|
||||
return;
|
||||
var aliasMap = _dataStore?.LoadAll();
|
||||
LazyLoadChildren(node, aliasMap);
|
||||
var displayAlias = BuildDisplayPathAliasMap(aliasMap);
|
||||
LazyLoadChildren(node, aliasMap, displayAlias);
|
||||
}
|
||||
|
||||
private bool _isProgrammaticSelect;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user