diff --git a/src/UI/WPF/Views/AliasTreeControl.xaml.cs b/src/UI/WPF/Views/AliasTreeControl.xaml.cs index e38a78b..537c0da 100644 --- a/src/UI/WPF/Views/AliasTreeControl.xaml.cs +++ b/src/UI/WPF/Views/AliasTreeControl.xaml.cs @@ -279,8 +279,12 @@ namespace NavisworksTransport.UI.WPF.Views // 全量加载别名到内存 Dictionary aliasMap = null; + Dictionary 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 } /// - /// 递归构建节点树(主线程调用) + /// 递归构建节点树(主线程调用)。 + /// siblingIndex 用于同名兄弟去重,0 表示第一个(不追加后缀)。 /// private AliasNodeViewModel BuildNode(Autodesk.Navisworks.Api.ModelItem modelItem, - Dictionary aliasMap, int depth) + Dictionary aliasMap, + Dictionary 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; } - /// - /// 懒加载子节点(在 Expanded 事件中触发) - /// - private void LazyLoadChildren(AliasNodeViewModel parentNode, Dictionary aliasMap) + private void LazyLoadChildren(AliasNodeViewModel parentNode, Dictionary aliasMap, + Dictionary 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(); 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) + /// + /// 从 aliasMap 构建 DisplayPath → alias 索引(去掉 IndexPath|| 前缀) + /// + private static Dictionary BuildDisplayPathAliasMap(Dictionary aliasMap) + { + if (aliasMap == null || aliasMap.Count == 0) return null; + var map = new Dictionary(); + 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; + } // ============================================================ + /// + /// 确保节点有完整 NodeKey(含 IndexPath)。首次保存别名时调用 CreatePathId 升级 key。 + /// + 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;