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

26 KiB
Raw Blame History

别名树(节点重命名)实施方案

基于 节点重命名参考方案.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 复合标识方案(双字段)

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(),与项目路径功能(BatchQueueManagerPathAnimationManager)完全一致:

// 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 表

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 接口

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

<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 模式):

<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

<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 颜色方案

// 别名高亮色:半透明青色
static readonly Color AliasHighlightColor = Color.FromArgb(80, 0, 150, 200);

9.3 实现

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"

ModelHighlightHelper.HighlightItems("AliasHighlight", itemCollection);

可与物流分类高亮、碰撞高亮共存,互不覆盖。


10. JSON 导入/导出

10.1 格式

{
  "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 点击别名树 → 选中模型

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 选中变化 → 别名树导航

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 链)

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 插件注册

// 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 天)

  1. AliasNodeViewModel — INotifyPropertyChanged
  2. AliasTreeControl.xaml — TreeView + 工具栏布局
  3. 延迟加载(占位节点 + Expanded 事件)
  4. 顶部工具栏(当前名 + 输入框 + 保存)

阶段 3编辑功能1.5 天)

  1. 内联编辑(文本框 + 铅笔按钮)
  2. 右键菜单(编辑/清除/聚焦/复制原名)
  3. BatchAliasDialog — 四种批量模式
  4. AliasGenerator — 前缀生成(复用参考方案逻辑)

阶段 4联动 + 高亮1.5 天)

  1. 点击别名树 → 选中模型(防循环)
  2. 选中模型 → 展开别名树并高亮
  3. 别名节点 3D 颜色高亮 + 清别名时清除

阶段 5导入导出 + 清理1 天)

  1. JSON 导出
  2. JSON 导入(合并模式)
  3. 孤儿别名检测 + 清理
  4. Cleanup 方法

阶段 6集成测试1 天)

  1. 大模型性能测试10 万+ 节点)
  2. 文档切换生命周期测试
  3. 同名字兄弟节点去重测试
  4. 回归:不影响现有物流分类/碰撞检测

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,不持有自己的连接