NavisworksTransport/doc/design/2026/别名树实施方案.md
tian fddefde1ed docs: 更新别名树实施方案 — 新增显示原则 + 修正标识/定位/联动描述
- 新增第7节「别名树显示原则」:二条件OR逻辑、深度限制、别名特殊规则、按需加载生命周期
- 第4节:标识改为 CreatePathId格式,定位改为 Models.ResolvePathId
- 第10节:JSON导出格式改为 key 字段
- 第11节:双向联动改为 ModelItem.Parent链 + ExpandAndSelect
- 清理 CleanupOnDemandNodes 逻辑文档化
2026-06-01 09:10:47 +08:00

681 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 别名树(节点重命名)实施方案
> 基于 [节点重命名参考方案.md](./节点重命名参考方案.md) 的评估与完善。
> 对应评估结论中 P0/P1/P2 的全部整改点。
---
## 1. 目标
为用户提供「给 Selection Tree 中任意节点打别名」的能力,解决 Revit 导入后 DisplayName 不可读(全叫"楼层""墙""管道")、无法靠名字快速定位的问题。
不做原生树扩展,走 **独立别名面板 + 自管数据层** 路线。
---
## 2. 架构概览
```
┌──────────────────────────┐
│ Navisworks 主窗口 │
│ ┌──────────┬──────────┐ │
│ │ Selection │ 3D View │ │
│ │ Tree │ │ │
│ │(原生只读) │ │ │
│ ├──────────┤ │ │
│ │ 别名导航树 │ │ │ ← 新增独立 DockPanePlugin
│ │(可编辑别名)│ │ │
│ └──────────┴──────────┘ │
│ │
│ SQLite: DocName.db │
│ ├─ Paths (已有) │
│ ├─ Collisions (已有) │
│ └─ AliasMap (新增) │ ← 别名持久化
└──────────────────────────┘
```
独立 `DockPanePlugin`,不并入 `LogisticsControlPanel`。用户在 **View → Docking Windows → 别名导航树** 打开后可手动 dock 到 Selection Tree 下方。
---
## 3. 文件清单(新增 9 个文件)
| 文件 | 职责 |
|------|------|
| `src/Core/AliasTree/AliasTreePlugin.cs` | DockPanePlugin 入口 + ElementHost |
| `src/Core/AliasTree/AliasNodeIdentity.cs` | 稳定的复合节点标识(替代 InstanceGuid |
| `src/Core/AliasTree/AliasNodeViewModel.cs` | 单节点 VM含 INotifyPropertyChanged |
| `src/Core/AliasTree/AliasDataStore.cs` | 别名数据的 CRUD + 与 PathDatabase 对接 |
| `src/UI/WPF/Views/AliasTreeControl.xaml` | TreeView 布局 + 图标工具栏 + 右键菜单 |
| `src/UI/WPF/Views/AliasTreeControl.xaml.cs` | 核心逻辑:联动、编辑、批量、高亮、导入导出 |
| `src/UI/WPF/Views/BatchAliasDialog.xaml` | 批量编辑对话框 |
| `src/UI/WPF/Views/BatchAliasDialog.xaml.cs` | 批量编辑逻辑 |
| `src/UI/WPF/Resources/AliasTreeIcons.xaml` | 工具栏图标Path Geometry 矢量图标) |
---
## 4. 稳定节点标识设计(核心创新点)
### 4.1 为什么不用 InstanceGuid
Revit 重新导出 NWC 再 append 进 NWD 后,`InstanceGuid` 可能整体变化。别名必须跨文件更新存活。
### 4.2 复合标识方案(双字段)
```csharp
public readonly struct AliasNodeIdentity
{
/// <summary>Navisworks 官方 PathIdCreatePathId 格式: "modelIndex:0/1/2"</summary>
public string IndexPath { get; }
/// <summary>父链 DisplayName 拼接路径(可读、调试用)</summary>
public string DisplayPath { get; }
/// <summary>序列化为 DB 主键: "IndexPath||DisplayPath"</summary>
public string ToKey() => $"{IndexPath}||{DisplayPath}";
public static AliasNodeIdentity FromModelItem(ModelItem item)
{
// IndexPath: 使用 Navisworks 官方 API CreatePathId
var pid = Application.ActiveDocument.Models.CreatePathId(item);
string indexPath = $"{pid.ModelIndex}:{pid.PathId}";
// DisplayPath: 父链 DisplayName 拼接(如 "模型/楼层/墙#0"
var parts = new List<string>();
var current = item;
while (current != null) { parts.Insert(0, current.DisplayName ?? "?"); current = current.Parent; }
string displayPath = string.Join("/", parts);
// 同级同名去重
int siblingIndex = ComputeSiblingIndex(item);
if (siblingIndex > 0) displayPath += $"#{siblingIndex}";
return new AliasNodeIdentity(indexPath, displayPath);
}
}
```
### 4.3 回溯定位
使用 Navisworks 官方 API `Models.ResolvePathId()`,与项目路径功能(`BatchQueueManager`、`PathAnimationManager`)完全一致:
```csharp
// IndexPath 格式: "modelIndex:PathId"(如 "0:0/0/35"
int colonIdx = indexPath.IndexOf(':');
int modelIndex = int.Parse(indexPath.Substring(0, colonIdx));
string pathId = indexPath.Substring(colonIdx + 1);
var pid = new ModelItemPathId { ModelIndex = modelIndex, PathId = pathId };
var item = doc.Models.ResolvePathId(pid);
```
不做名前缀匹配、不做手动树遍历。
### 4.4 容错
如果路径匹配不到目标节点(模型结构变化),标记为「孤儿别名」:
- 在别名树中以灰色 + 删除线显示
- 提供"清除孤儿别名"按钮
---
## 5. 别名数据持久化PathDatabase 扩展)
### 5.1 新增 SQLite 表
```sql
CREATE TABLE IF NOT EXISTS AliasMap (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
NodeKey TEXT NOT NULL UNIQUE, -- AliasNodeIdentity.ToKey()
AliasText TEXT NOT NULL,
CreatedAt TEXT DEFAULT (datetime('now')),
ModifiedAt TEXT DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_alias_key ON AliasMap(NodeKey);
```
### 5.2 AliasDataStore 接口
```csharp
public class AliasDataStore
{
// 生命周期跟随 PathDatabase文档打开时连接关闭时断开
string GetAlias(AliasNodeIdentity id); // 读
void SetAlias(AliasNodeIdentity id, string alias); // 写upsert
void DeleteAlias(AliasNodeIdentity id); // 删
void DeleteAliasesByPrefix(string pathPrefix); // 批量删(清子节点)
Dictionary<string, string> LoadAll(); // 全量加载到内存缓存
int Count { get; } // 统计
// 孤儿别名
List<string> FindOrphans(HashSet<string> validKeys); // 检测
void PurgeOrphans(HashSet<string> validKeys); // 清理
}
```
### 5.3 与 PathDatabase 的关系
`AliasDataStore` 不持有独立 SQLiteConnection。通过 `PathDatabase.Connection` 操作,使别名表与路径/碰撞数据共享同一事务生命周期和备份机制。
---
## 6. 面板 UI 设计
### 6.1 布局
```
┌──────────────────────────────────────────┐
│ 顶部图标工具栏24px 高) │
│ [当前: 未命名节点标题] [💾] [⏬] [⏫] [⚙] [🗑] │
│ 保存 导出 导入 自动 清除 │
│ │
│ [别名输入框 ] [✓] │
├──────────────────────────────────────────┤
│ TreeView别名导航树
│ ├─ 模型名(灰色斜体=无别名) │
│ │ ├─ 一楼 → "1F-大厅" ◆蓝色粗体 │
│ │ ├─ 一楼 → 灰色斜体 │
│ │ ├─ 一楼_001 → 灰色斜体 │
│ │ └─ ... │
│ └─ 另一个模型 │
│ └─ ... │
└──────────────────────────────────────────┘
```
视觉规范:
- **有别名**:蓝色 (#1565C0) 粗体,加 ◆ 前缀图标
- **无别名**:灰色 (#888) 斜体,显示原 DisplayName
- **编辑中**`TextBox` 覆盖 TextBlock背景浅黄
- **孤儿别名**:灰色删除线
- **鼠标悬停行**:背景浅蓝 + 显示铅笔按钮 ✎
- **工具栏**:图标按钮 24×24Navisworks 蓝色系,鼠标悬停高亮
### 6.2 顶部图标工具栏
按钮统一使用 WPF `Path` 几何图形(与项目 `MediaControlIcons.xaml` 同模式),不依赖外部图片文件。
工具栏风格参考 Visual Studio / Navisworks 内置面板:紧凑横向排列,仅图标,悬停显示 ToolTip。
| 图标 | GeometryPath Data | ToolTip | 功能 |
|------|----------------------|---------|------|
| 💾 保存 | `M15,9H5V5H15M15,19H5V15H15M17,3H3V21H17V16L21,12L17,8V3Z` | 保存别名 | 保存顶部输入框中的别名到当前选中节点 |
| ⏬ 导出 | `M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z` | 导出别名 (JSON) | 导出全量别名到 `.alias.json` |
| ⏫ 导入 | `M5,10H19V12H5M12,5L5,12H9V21H15V12H19L12,5Z` | 导入别名 (JSON) | 从 `.alias.json` 导入(合并模式) |
| ⚙ 自动 | `M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.97 19.05,5.05L16.56,6.05C16.04,5.65 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.65 7.44,6.05L4.95,5.05C4.73,4.97 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.95C7.96,18.35 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.68 16.04,18.35 16.56,17.95L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z` | 自动生成 | 按父链路径自动生成前缀别名 |
| 🗑 清除 | `M6,19V7H18V19H6M9,4H15L14.5,3H9.5L9,4M19,5V7H5V5H8L8.5,4H15.5L16,5H19Z` | 清除全部别名 | 清除所有别名和高亮,恢复原名显示 |
第二行保留文本输入区:
- `「当前: xxx」` — 选中节点的 DisplayName
- 别名输入框 — 直接输入别名
- `✓` 确认按钮 — 与回车等效,保存并刷新
### 6.3 图标按钮实现
沿用项目现有 `MediaControlIcons.xaml` 的模式 — 纯 WPF `Path` 几何图形,不依赖外部 .ico/.png 文件。
图标资源文件 `AliasTreeIcons.xaml`
```xml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- 保存图标(磁盘) -->
<Geometry x:Key="SaveIconGeometry">
M15,9H5V5H15M15,19H5V15H15M17,3H3V21H17V16L21,12L17,8V3Z
</Geometry>
<!-- 导出图标(向上箭头出盒子) -->
<Geometry x:Key="ExportIconGeometry">
M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z
</Geometry>
<!-- 导入图标(向下箭头入盒子) -->
<Geometry x:Key="ImportIconGeometry">
M5,10H19V12H5M12,5L5,12H9V21H15V12H19L12,5Z
</Geometry>
<!-- 自动生成图标(齿轮) -->
<Geometry x:Key="AutoGenIconGeometry">
M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12
A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 ...
</Geometry>
<!-- 清除图标(垃圾桶) -->
<Geometry x:Key="ClearIconGeometry">
M6,19V7H18V19H6M9,4H15L14.5,3H9.5L9,4M19,5V7H5V5H8L8.5,4H15.5L16,5H19Z
</Geometry>
<!-- 别名工具栏图标按钮样式紧凑24×24 -->
<Style x:Key="AliasToolbarButtonStyle" TargetType="Button">
<Setter Property="Width" Value="24"/>
<Setter Property="Height" Value="24"/>
<Setter Property="Margin" Value="1,0"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Foreground" Value="{StaticResource NavisworksTextBrush}"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderThickness="0"
CornerRadius="2">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background"
Value="{StaticResource NavisworksLightBrush}"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="Background"
Value="{StaticResource NavisworksButtonBrush}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="#FFBBBBBB"/>
<Setter Property="Opacity" Value="0.5"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
```
XAML 中的使用方式(参考 `MediaControlIcons.xaml` 模式):
```xml
<Button Style="{StaticResource AliasToolbarButtonStyle}"
ToolTip="保存别名" Click="OnSaveClick">
<Path Data="{StaticResource SaveIconGeometry}"
Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}"
Width="16" Height="16" Stretch="Uniform"/>
</Button>
```
合并到控件 XAML 的 `ResourceDictionary.MergedDictionaries`
```xml
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Resources/NavisworksStyles.xaml"/>
<ResourceDictionary Source="../Resources/AliasTreeIcons.xaml"/>
</ResourceDictionary.MergedDictionaries>
```
### 6.4 右键菜单
| 菜单项 | 快捷键 | 功能 |
|--------|--------|------|
| 编辑别名 | F2 | 就地编辑 TextBox |
| 清除别名 | Del | 删除别名,恢复显示原名 |
| **批量编辑子节点…** | — | 弹出批量编辑对话框(见第 7 节) |
| 在视图中聚焦 | — | 3D 视图中选中 + 聚焦该节点 |
| 复制原名 | — | 复制 DisplayName 到剪贴板 |
---
## 7. 别名树显示原则(核心规则)
别名树不是 Selection Tree 的完整镜像。显示内容按以下规则决定:
### 7.1 二条件 OR 逻辑
| 条件 | 说明 | 示例 |
|------|------|------|
| **A: 层级限制内** | 深度 ≤ `MaxFullDisplayDepth`(默认 2的节点 | 根 → 第一层子节点 → 第二层子节点 |
| **B: 用户选中** | 在 Selection Tree 中选中的节点,无论多深 | 选中第 5 层节点 → 其完整路径显示在别名树中 |
满足任意一个条件即显示。两个条件都不满足的节点不显示。
### 7.2 深度限制
- `MaxFullDisplayDepth = 2`:初始构建只到第 2 层(根为第 0 层)
- 超限节点不添加展开箭头(无占位节点),用户无法手动展开
- 展开路径中的节点懒加载:有占位符 → `LazyLoadChildren`(全部子节点);无占位符 → `BuildNode`(只创建路径上的单个节点)
### 7.3 别名节点特殊规则
- **有别名的节点**:无论深度多少,启动时自动加载其完整路径并展开。由 `EnsureAliasedPathsVisible()``RebuildTree` / `SetDataStore` 后执行
- 超限别名节点的祖先会被展开,但祖先无别名则不保留占位符子节点
### 7.4 按需加载节点的生命周期
- 用户在 Selection Tree 中选择超限无别名节点 → 路径加载到别名树
- **下次选择另一个节点时**,上一次按需加载的无别名超限节点自动清理(`CleanupOnDemandNodes`
- 有别名的节点不受清理影响,始终保留
- `_onDemandNodeKeys` HashSet 追踪每次 `ExpandAndSelect` 中通过 `BuildNode` 创建的深层无别名节点
### 7.5 例外
- 开启别名面板后,树中已有节点被展开过的,其子节点保留(属用户主动操作)
- 通过内联编辑设置别名后,该节点自动变为「条件 A」的别名节点退出按需追踪
---
## 8. 批量编辑子节点(新增功能)
### 8.1 场景
Navisworks 中常见:一个父节点下 20 个子节点全叫「楼层」或「管道」,需要统一加前缀。
### 8.2 批量编辑对话框
```
┌──────────────────────────────────┐
│ 批量编辑子节点 — 父节点: "办公楼层" │
│ │
│ 编辑模式: │
│ ○ 统一前缀 [2F- ] │
│ ● 统一后缀 [ _办公区] │
│ ○ 替换原名 [自定义名称 ] │
│ ○ 序号模板 [管道_{0:D2} ] │ ← 自动编号
│ │
│ 预览: │
│ 楼层 → 楼层_办公区 │
│ 楼层 → 楼层_001_办公区 │ ← 同名自动去重
│ 走廊 → 走廊_办公区 │
│ 电梯间 → 电梯间_办公区 │
│ ...共 12 个子节点 │
│ │
│ [应用] [取消] │
└──────────────────────────────────┘
```
### 8.3 四种编辑模式
| 模式 | 输入 | 结果 |
|------|------|------|
| 统一前缀 | "2F-" | `2F-原名` |
| 统一后缀 | "_办公区" | `原名_办公区` |
| 替换原名 | "自定义名称" | 所有子节点用同一名称 + 去重后缀 `_001` |
| 序号模板 | "管道_{0:D2}" | `管道_01`, `管道_02`... |
### 8.4 去重保证
`MakeUnique()` 逻辑与 `AliasGenerator` 复用。预览中提前展示最终名称(含去重后缀)。
---
## 9. 别名节点 3D 高亮
### 9.1 触发时机
- 给节点设置别名时,自动在该节点上加永久颜色覆盖
- 清除别名时,同时清除颜色覆盖
### 9.2 颜色方案
```csharp
// 别名高亮色:半透明青色
static readonly Color AliasHighlightColor = Color.FromArgb(80, 0, 150, 200);
```
### 9.3 实现
```csharp
public void ApplyAliasHighlight(ModelItem item)
{
var items = new ModelItemCollection { item };
var doc = Application.ActiveDocument;
doc.Models.OverridePermanentColor(items, AliasHighlightColor);
}
public void RemoveAliasHighlight(ModelItem item)
{
var items = new ModelItemCollection { item };
var doc = Application.ActiveDocument;
doc.Models.OverridePermanentColor(items, Color.Empty); // 恢复默认
}
```
### 9.4 清理
- 文档关闭时自然清理(颜色覆盖不持久化到 NWD
- 清除别名时同时清除颜色
- 全部别名清除提供「清除所有别名高亮」操作
### 9.5 与其他高亮的兼容
项目已有 `ModelHighlightHelper`,使用 category 机制管理高亮。别名高亮用专用 category `"AliasHighlight"`
```csharp
ModelHighlightHelper.HighlightItems("AliasHighlight", itemCollection);
```
可与物流分类高亮、碰撞高亮共存,互不覆盖。
---
## 10. JSON 导入/导出
### 10.1 格式
```json
{
"version": 1,
"exportedAt": "2026-05-31T10:30:00",
"documentName": "Project.nwd",
"aliases": [
{
"key": "0:0/0/0||Floor2_mobile_yup.nwd/Architecture/2ND FLOOR/3' - 3\" x 4' - 3\"",
"alias": "第一个节点"
}
]
}
```
### 10.2 导入策略
- **合并模式**JSON 中的别名覆盖现有同名 keyJSON 中没有的不动。
### 10.3 字段说明
| 字段 | 用途 |
|------|------|
| `path` | 父链 DisplayName 拼接(对应 DB 的 HierarchicalPath |
| `siblingIndex` | 同级去重索引 |
| `alias` | 用户别名 |
---
## 11. 双向联动机制
### 11.1 点击别名树 → 选中模型
```csharp
private void OnTreeViewSelected(AliasNodeViewModel node)
{
if (node.ModelItem == null) return;
_isInternalSelection = true;
var coll = new ModelItemCollection { node.ModelItem };
doc.CurrentSelection.Clear();
doc.CurrentSelection.CopyFrom(coll);
_isInternalSelection = false;
}
```
### 11.2 Navisworks 选中变化 → 别名树导航
```csharp
private void OnNavisSelectionChanged(object sender, EventArgs e)
{
if (_isInternalSelection) return;
var selected = doc.CurrentSelection.SelectedItems;
if (selected.Count == 1)
ExpandAndSelect(selected.First());
}
```
### 11.3 展开路径(沿 ModelItem.Parent 链)
```csharp
private void ExpandAndSelect(ModelItem item)
{
// 1. 沿 Parent 链构建祖先列表(根 → 目标)
// 2. 在 rootNodes 中按 ModelItem.Equals 匹配根节点
// 3. 逐层向下:有占位 → LazyLoadChildren / 无占位 → BuildNode 单点创建
// 4. SelectTreeNode 选中最终节点
}
```
不使用 IndexPath 做运行时导航。IndexPath 仅在保存/加载别名时用于持久化(`CreatePathId` / `ResolvePathId`)。
---
## 12. 线程安全
**所有 Navisworks API 调用必须在主 STA 线程**(项目第 8 节硬约束)。
| 操作 | 线程 |
|------|------|
| 遍历 ModelItem构建树/生成别名) | 主线程(必须) |
| `Application.ActiveDocument` 访问 | 主线程(必须) |
| `OverridePermanentColor` | 主线程(必须) |
| `CurrentSelection.SelectedItems` | 主线程(必须) |
| 读/写 SQLiteAliasDataStore | 可通过 `Task.Run` 在后台 + 锁保护 |
| JSON 解析/序列化 | 后台线程安全 |
| `Dispatcher.BeginInvoke` 延迟 UI 更新 | 主线程回调 |
大模型优化:
- 树构建分批次:前 200 个根节点立即构建,后续通过 `Dispatcher.BeginInvoke(DispatcherPriority.Background, ...)` 分批追加
- 首屏只加载 Viewport 内可见项(`VirtualizingPanel` 已覆盖)
- 展开时才真正递归子节点(占位符机制)
---
## 13. 文件组织与集成
### 13.1 目录结构
```
src/
├── Core/
│ └── AliasTree/
│ ├── AliasTreePlugin.cs # DockPanePlugin
│ ├── AliasNodeIdentity.cs # 复合节点标识
│ └── AliasDataStore.cs # CRUD + SQLite
├── UI/
│ └── WPF/
│ ├── Views/
│ │ ├── AliasTreeControl.xaml
│ │ ├── AliasTreeControl.xaml.cs
│ │ ├── BatchAliasDialog.xaml
│ │ └── BatchAliasDialog.xaml.cs
│ └── ViewModels/
│ └── AliasNodeViewModel.cs # 单节点 VM
```
### 13.2 命名空间
- `NavisworksTransport.Core.AliasTree` — 插件入口、数据层
- `NavisworksTransport.UI.WPF.Views` — XAML 视图
- `NavisworksTransport.UI.WPF.ViewModels` — ViewModel
### 13.3 插件注册
```csharp
// src/Core/AliasTree/AliasTreePlugin.cs
[Plugin("NavisworksTransport.AliasTree", "Tian",
DisplayName = "别名导航树",
ToolTip = "自定义节点别名管理面板")]
[DockPanePlugin(300, 500, FixedSize = false)]
public class AliasTreePlugin : DockPanePlugin
{
public override Control CreateControlPane()
{
ElementHost eh = new ElementHost { AutoSize = true };
eh.Child = new AliasTreeControl();
eh.CreateControl();
return eh;
}
public override void DestroyControlPane(Control pane)
{
if (pane is ElementHost eh && eh.Child is AliasTreeControl ctrl)
ctrl.Cleanup();
pane?.Dispose();
}
}
```
### 13.4 用户操作步骤
1. 启动 Navisworks → 打开模型
2. **View → Docking Windows → 别名导航树** 打开面板
3. 手动拖动面板dock 到内置 Selection Tree 下方
4. 选中节点后,在顶部输入框或内联编辑打别名
5. 别名自动持久化到 `DocName.db`,下次打开模型自动恢复
---
## 14. 实施步骤(按优先级)
### 阶段 1数据层2 天)
1.`AliasNodeIdentity` — 复合标识 + 测试
2.`AliasDataStore` — CRUD + 集成到 PathDatabase.Connection
3. ✅ PathDatabase 新增 `AliasMap` 表 + 迁移逻辑
4. ✅ 单元测试identity 生成、去重、回溯定位
### 阶段 2VM + TreeView 骨架2 天)
5.`AliasNodeViewModel` — INotifyPropertyChanged
6.`AliasTreeControl.xaml` — TreeView + 工具栏布局
7. ✅ 延迟加载(占位节点 + Expanded 事件)
8. ✅ 顶部工具栏(当前名 + 输入框 + 保存)
### 阶段 3编辑功能1.5 天)
9. ✅ 内联编辑(文本框 + 铅笔按钮)
10. ✅ 右键菜单(编辑/清除/聚焦/复制原名)
11.`BatchAliasDialog` — 四种批量模式
12.`AliasGenerator` — 前缀生成(复用参考方案逻辑)
### 阶段 4联动 + 高亮1.5 天)
13. ✅ 点击别名树 → 选中模型(防循环)
14. ✅ 选中模型 → 展开别名树并高亮
15. ✅ 别名节点 3D 颜色高亮 + 清别名时清除
### 阶段 5导入导出 + 清理1 天)
16. ✅ JSON 导出
17. ✅ JSON 导入(合并模式)
18. ✅ 孤儿别名检测 + 清理
19. ✅ Cleanup 方法
### 阶段 6集成测试1 天)
20. ✅ 大模型性能测试10 万+ 节点)
21. ✅ 文档切换生命周期测试
22. ✅ 同名字兄弟节点去重测试
23. ✅ 回归:不影响现有物流分类/碰撞检测
---
## 15. 风险与已知限制
| 风险 | 缓解 |
|------|------|
| 父链路径在模型结构变化后断裂 | 孤儿别名标记 + 清理功能 |
| 同名兄弟节点计数漂移 | 同级插入/删除后索引可能偏移,需提供「刷新别名索引」 |
| `Children.Any()` 触发集合枚举 | 用 `Children.Count > 0` 代替,或从 COM API 取 path 来判空 |
| 大模型首次加载 TreeView 卡顿 | 分批构建 + Dispatcher.Background + 虚拟化 |
| Navisworks 不支持编程控制 dock 位置 | 首次使用文档中明确引导用户手动拖动面板 |
---
## 16. 与现有功能的隔离
- 不修改任何现有 `PathPlanningManager` / `PathDatabase` 核心逻辑(只在 DB 中加表)
- 不侵入 `LogisticsControlPanel`(独立 `DockPanePlugin`
- 别名高亮使用独立 `ModelHighlightHelper` category不干扰物流分类高亮
- `AliasDataStore` 只读 `PathDatabase.Connection`,不持有自己的连接