需求列表

  • 截面创建(页面)
    • 标题栏按钮(截面测量、点-点、点-线、线-线等10项)
    • 截面创建(前期准备)右侧界面1:特征基准面选择
      • 加载所有特征
      • 旋转角度设置
      • 测量主界面选中后特效交互(对象树选中、渲染选中)
    • 截面创建(前期准备页面)右侧界面2:坐标系为基准面(XY平面、XZ平面等)
      • 加载所有特征
      • 旋转角度设置
      • 测量主界面选中后特效交互(对象树选中、渲染选中)
    • 截面创建(前期准备页面)右侧界面2:坐标系为基准面
      • 加载所有特征
    • 截面创建(前期准备页面)右侧界面3:截面轮廓线配置
      • 垂直、水平、直线、垂直线、平行线(折线、园、同心圆、圆弧暂不做
    • 截面创建三视口(前期准备页面)
      • 左侧3D状态视口(简单)
      • 右侧横条拖动交换页面(较难)
      • 底部截面分析页面(截面坐标系、2D交互、拖拽、缩放等)(最难)
  • 截面对象树节点(TreeNode)设计
    • 序列化、反序列化(工程加载)
    • 截面测量元数据设计&开发: QMap<int,SectionMeta> metaDatas;
  • 截面恢复、撤销功能(覆盖面最广)
    • 所有截面测量操作的撤销恢复(创建距离、角度、直线、圆等)
  • 截面特征创建操作(交互最复杂部分)
    • 创建2d点
    • 创建线:1.双击创建起点。2.鼠标拖动生成线区域范围。3.鼠标双击创建末点。4.提取2d线区域范围,抽取点云、CAD网格。5.创建线高亮区域标记。6.保存截面操作数据到对象树数据中
    • 创建2d圆:1. 双击创建第一个点。2.鼠标双击创建第二个点。3.鼠标拖动生成圆环区域范围。4.鼠标双击创建圆环。5. 提取圆环区域范围,抽取点云、CAD网格。6.保存2d圆截面操作数据到对象树数据中
    • 创建文本标注
  • 截面特征测量操作(算法、交互最复杂部分)
    • 创建点-点距离(3种不同算法)
      • 垂直距离
      • 水平距离
      • 最近距离
    • 创建点到线的距离
    • 创建线到线的距离
    • 创建圆到线的距离
    • 创建点到圆的距离(3种不同算法)
      • 最近距离
      • 圆心距离
      • 最远距离
    • 创建圆到圆的距离(3种不同算法)
      • 最近距离
      • 圆心距离
      • 最远距离
    • 创建角度
    • 创建圆弧角度
    • 创建圆半径
    • 创建圆周长
  • 截面报告生成
    • 截面3视图生成
    • 界面特征测量数据表导出(例如:1.圆 特征索引id、类型、圆周长;2. 线-圆的距离 特征索引id、线索引id、圆索引id、 类型、距离值、偏差值(与cad对比))
  • 截面测量恢复&重建
    • 打开工程(已经有截面测量节点)时,后台恢复(可能会直接导出报告,如果选中且并没有修改过截面测量)
    • 截面测量创建过程中,切换到其他页面后继续进行截面测量的恢复重建

截面最底层数据结构设计

依据原理

  • 怎么存(对象树、工程的序列)
  • 怎么恢复(从数据结构到2d/3d视角、模型、actor等)
  • 恢复什么数据

恢复什么数据需要弄清楚截面测量干什么,核心目的是什么,界面元素有什么?

  • 截面测量主要是测量3d数据中根据某些规则对数据切割后投影到2d坐标系的切割边界中的元素间的:1.距离、2.角度、3.圆弧角度、4.圆半径、5.圆周长等数据;能够导出对应报告;能够关机、关闭软件后继续测量
    总结就是:
    • 最核心点:测量切割截面的那5个数据
    • 次要是能导出报告
    • 再者能够恢复加载

界面元素中包含了以下需要存储的数据:

  1. 3d视口中模型的位姿、相机位姿
  2. 2d视口中选取的类型:坐标系作为基准面(xy,yz,xz)、或者特征作为基准面,特征的数据、旋转角度,横线(切割线)位置
  3. 底部2d视口中坐标的缩放值

汇总所有需要存储的数据包括以下三种:
a. 对象树内部存储针对测量的数据:

  1. 距离数据
  2. 角度数据
  3. 原数角度数据
  4. 圆半径
  5. 圆周周长
  6. 三个特征(点、圆、线)

b. 辅助视口数据:

  1. 3d视口模型位姿
  2. 3d视口相机位姿
  3. 视口截取参数:基准平面、旋转角度,截取直线坐标(直线方程)
  4. 2d视口缩放比例
  5. 2d视口坐标系

c. 选中的点云、网格数据

初步设计的类图如下:

主要按照上述三种数据进行划分:

CrossSectionMeasurement (截面测量模块)
    ├── CrossSectionData (截面数据)
    │   ├── CuttingParameters (切割参数)
    │   └── SectionCurve (截面曲线 - VTK数据)
    │
    ├── ViewportStates (视口状态)
    │   ├── Viewport3DState
    │   ├── ViewportProjectionState
    │   └── ViewportCrossSectionState
    │
    └── Measurement2DData (2D测量数据)
        ├── Features2D (2D特征)
        │   ├── Point2D
        │   ├── Line2D
        │   └── Circle2D
        │
        └── Measurements2D (2D测量)
            ├── Distance2D
            ├── Angle2D
            ├── ArcAngle2D
            ├── Radius2D
            └── Circumference2D

截面创建2DActor设计


创建的2D元素,可以作为独立的Demo程序做验证,包括:

  • 使用VTK在Qt界面中创建2D坐标系
  • 创建2d点特征元素
  • 创建2d线特征元素
  • 创建2d圆特征元素
  • 创建2d文字标注元素
  • 创建2d圆弧元素
  • 创建3中圆-圆距离元素(最近距离、圆心距离、最远距离)

等,其他元素不再赘述,当前章节主要设计对应的架构,尽可能将这些标注内容易于扩展。
设计架构基本秉持以下两种要求:

  1. 职责分离
  2. 易于扩展

交互流程设计

以绘制直线为例:

用户操作流程:
│
├── 1. 选择工具
│   │
│   ├── 用户点击"绘制直线"按钮
│   │   ↓
│   ├── ToolManager.setCurrentTool(LineDrawTool)
│   │   ↓
│   ├── 激活LineDrawTool
│   │   ↓
│   └── 状态 → WAITING_FIRST_POINT
│
├── 2. 鼠标移动(寻找第一点)
│   │
│   ├── 鼠标在视口移动
│   │   ↓
│   ├── InteractionManager捕获鼠标事件
│   │   ↓
│   ├── 转发到当前工具: LineDrawTool.onMouseMove(x, y)
│   │   ↓
│   ├── LineDrawTool处理
│   │   ├── 将屏幕坐标转换为世界坐标
│   │   ├── 执行捕捉检测
│   │   │   ├── 检测附近的点
│   │   │   ├── 检测附近的线
│   │   │   └── 检测网格点
│   │   ├── 在OverlayLayer绘制捕捉点预览
│   │   │   └── overlayLayer->addSnapPoint(x, y, type)
│   │   └── 更新光标样式
│   │       ↓
│   └── RenderEngine.render() → 刷新显示
│
├── 3. 点击第一点
│   │
│   ├── 鼠标左键点击
│   │   ↓
│   ├── InteractionManager.onMousePress(x, y, LEFT)
│   │   ↓
│   ├── LineDrawTool.onMousePress(x, y)
│   │   ↓
│   ├── LineDrawTool处理
│   │   ├── 记录第一点坐标 point1 = (x, y)
│   │   ├── 在OverlayLayer显示第一点标记
│   │   │   └── overlayLayer->addEditHandle(id, pointActor)
│   │   └── 状态转换 → WAITING_SECOND_POINT
│   │       ↓
│   └── RenderEngine.render()
│
├── 4. 鼠标移动(拖拽第二点)
│   │
│   ├── 鼠标移动
│   │   ↓
│   ├── LineDrawTool.onMouseMove(x, y)
│   │   ↓
│   ├── LineDrawTool处理
│   │   ├── 创建临时线对象
│   │   │   └── tempLine = new Line2DDrawable(point1, current)
│   │   ├── 添加到OverlayLayer
│   │   │   └── overlayLayer->setPreviewDrawable(tempLine)
│   │   ├── 计算并显示辅助信息
│   │   │   ├── 长度 = distance(point1, current)
│   │   │   ├── 角度 = angle(point1, current)
│   │   │   └── 在OverlayLayer显示文本
│   │   ├── 应用约束(如果按住Shift)
│   │   │   ├── 水平约束
│   │   │   ├── 垂直约束
│   │   │   └── 45度约束
│   │   └── 捕捉检测
│   │       ↓
│   └── RenderEngine.render() → 实时更新预览
│
├── 5. 点击第二点(完成绘制)
│   │
│   ├── 鼠标左键点击
│   │   ↓
│   ├── LineDrawTool.onMousePress(x, y)
│   │   ↓
│   ├── LineDrawTool处理
│   │   ├── 记录第二点坐标 point2 = (x, y)
│   │   ├── 创建数据对象
│   │   │   └── line2D = new Line2D(point1, point2)
│   │   ├── 添加到数据管理器
│   │   │   └── measurementData.addFeature(line2D)
│   │   ├── 创建永久绘制对象
│   │   │   └── drawable = new Line2DDrawable(line2D)
│   │   ├── 添加到ContentLayer
│   │   │   └── contentLayer.addFeatureDrawable(id, drawable)
│   │   ├── 清除OverlayLayer
│   │   │   ├── overlayLayer->clearPreview()
│   │   │   ├── overlayLayer->clearEditHandles()
│   │   │   └── overlayLayer->clearSnapPoints()
│   │   └── 状态转换 → COMPLETED
│   │       ↓
│   ├── 触发完成事件
│   │   └── emit lineCreated(line2D.getId())
│   │       ↓
│   └── RenderEngine.render()
│
└── 6. 重置或继续
    │
    ├── 如果工具设置为"连续绘制"
    │   └── 状态 → WAITING_FIRST_POINT (重新开始)
    │
    └── 如果工具设置为"单次绘制"
        └── ToolManager.setCurrentTool(SelectTool)

抽离的最小模块如下:
DrawableLayer(绘制层系统)
主要负责的功能为管理所有的Drawable对象,对不同的对象进行分层。绘制层系统不做任何的创建、不做任何的处理交互(鼠标移动、双击)。
如何对所有的Drawable对象进行有效管理?
Drawable对象有不同的用途:背景对象(坐标轴)、特征对象(点、直线、圆)、临时对象(鼠标交互时预览对象)、高亮对象(选中、悬停),可以根据这些不同的用途进行分层显示:

  • Layer 0: DecorationLayer (装饰层,显示坐标系、刻度等)
  • Layer 1: ContentLayer (内容层,已确认的特征、测量)
  • Layer 2: OverlayLayer (覆盖层,临时预览、高亮等)

绘制工具Tool基类设计

Tool (工具抽象基类)
│
├── 状态管理
│   ├── ToolState (工具状态枚举)
│   │   ├── IDLE (空闲)
│   │   ├── ACTIVE (激活)
│   │   ├── DRAWING (绘制中)
│   │   └── EDITING (编辑中)
│   │
│   └── 状态转换
│       ├── activate() → ACTIVE
│       ├── deactivate() → IDLE
│       └── reset() → 初始状态
│
├── 鼠标事件处理
│   ├── onMousePress(x, y, button)
│   ├── onMouseMove(x, y)
│   ├── onMouseRelease(x, y, button)
│   ├── onMouseDoubleClick(x, y)
│   └── onMouseWheel(delta)
│
├── 键盘事件处理
│   ├── onKeyPress(key)
│   ├── onKeyRelease(key)
│   └── 快捷键
│       ├── ESC → 取消当前操作
│       ├── Enter → 确认
│       └── Delete → 删除
│
├── 绘制接口
│   ├── drawPreview() → 在临时层绘制预览
│   ├── drawFeedback() → 绘制交互反馈
│   └── clearPreview() → 清除预览
│
└── 辅助功能
    ├── 捕捉功能
    │   ├── snapToPoint() → 捕捉到点
    │   ├── snapToLine() → 捕捉到线
    │   ├── snapToGrid() → 捕捉到网格
    │   └── snapToIntersection() → 捕捉到交点
    │
    └── 约束功能
        ├── horizontalConstraint() → 水平约束
        ├── verticalConstraint() → 垂直约束
        └── angleConstraint() → 角度约束

绘制工具的状态机设计
在绘制直线时,绘制过程的状态如下:

LineDrawTool 状态转换
│
├── State: WAITING_FIRST_POINT (等待第一点)
│   │
│   ├── onMouseMove(x, y)
│   │   └── 显示光标位置的捕捉点
│   │
│   └── onMousePress(x, y)
│       ├── 记录第一点坐标
│       ├── 在覆盖层创建预览线
│       └── 转换到 WAITING_SECOND_POINT
│
├── State: WAITING_SECOND_POINT (等待第二点)
│   │
│   ├── onMouseMove(x, y)
│   │   ├── 更新预览线的第二点
│   │   ├── 显示距离标注
│   │   └── 显示角度提示
│   │
│   ├── onMousePress(x, y)
│   │   ├── 记录第二点坐标
│   │   ├── 创建Line2D对象
│   │   ├── 添加到内容层
│   │   ├── 清除覆盖层
│   │   └── 转换到 COMPLETED
│   │
│   └── onKeyPress(ESC)
│       ├── 清除覆盖层
│       └── 转换到 WAITING_FIRST_POINT
│
└── State: COMPLETED (完成)
    └── 触发完成事件

在绘制圆时,绘制过程的状态转换如下:

状态转换流程:

IDLE
  │
  │ activate()
  ▼
WAITING_FIRST_POINT
  │
  │ 鼠标移动
  ├─→ onMouseMove(x, y)
  │   └─→ 显示捕捉点预览
  │
  │ 鼠标点击
  ├─→ onMousePress(x, y)
  │   ├─→ 记录第一点 point1 = (x, y)
  │   ├─→ 在OverlayLayer显示第一点标记
  │   └─→ 转换到 WAITING_SECOND_POINT
  │
  │ ESC键
  └─→ onKeyPress(ESC)
      └─→ 转换到 IDLE

WAITING_SECOND_POINT
  │
  │ 鼠标移动
  ├─→ onMouseMove(x, y)
  │   ├─→ 显示从point1到当前位置的线段
  │   └─→ 显示捕捉点预览
  │
  │ 鼠标点击
  ├─→ onMousePress(x, y)
  │   ├─→ 检查是否与point1重合
  │   │   └─→ 如果重合,提示错误,保持当前状态
  │   ├─→ 记录第二点 point2 = (x, y)
  │   ├─→ 在OverlayLayer显示第二点标记
  │   └─→ 转换到 WAITING_THIRD_POINT
  │
  │ ESC键
  └─→ onKeyPress(ESC)
      ├─→ 清除第一点标记
      └─→ 转换到 WAITING_FIRST_POINT

WAITING_THIRD_POINT
  │
  │ 鼠标移动
  ├─→ onMouseMove(x, y)
  │   ├─→ 使用point1, point2, current计算圆
  │   ├─→ 如果三点共线
  │   │   └─→ 显示警告:"三点不能共线"
  │   ├─→ 如果三点不共线
  │   │   ├─→ 计算圆心和半径
  │   │   ├─→ 在OverlayLayer显示预览圆
  │   │   └─→ 显示圆的参数(半径、直径)
  │   └─→ 显示捕捉点预览
  │
  │ 鼠标点击
  ├─→ onMousePress(x, y)
  │   ├─→ 检查三点是否共线
  │   │   └─→ 如果共线,提示错误,保持当前状态
  │   ├─→ 记录第三点 point3 = (x, y)
  │   ├─→ 计算最终圆的参数
  │   ├─→ 创建Circle2D数据对象
  │   ├─→ 添加到ContentLayer
  │   ├─→ 清除OverlayLayer的预览
  │   └─→ 转换到 COMPLETED
  │
  │ ESC键
  └─→ onKeyPress(ESC)
      ├─→ 清除前两点标记
      └─→ 转换到 WAITING_FIRST_POINT

COMPLETED
  │
  │ 触发完成事件
  ├─→ emit circleCreated(circleId)
  │
  │ 如果设置为连续绘制模式
  ├─→ reset()
  │   └─→ 转换到 WAITING_FIRST_POINT
  │
  │ 如果设置为单次绘制模式
  └─→ deactivate()
      └─→ 转换到 IDLE
整体架构层次
┌─────────────────────────────────────────────────────────────────┐
│                    ViewportCrossSection                          │
│                   (2D视口 - Qt Widget)                           │
└─────────────────────────────────────────────────────────────────┘
                            │
                            │ contains
                            ▼
┌─────────────────────────────────────────────────────────────────┐
│                    Scene2DManager                                │
│                   (2D场景管理器)                                  │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │
│  │ DrawableLayer│  │InteractionMgr│  │ RenderEngine │          │
│  │  (绘制层)     │  │  (交互管理)   │  │  (渲染引擎)   │          │
│  └──────────────┘  └──────────────┘  └──────────────┘          │
└─────────────────────────────────────────────────────────────────┘
        │                   │                   │
        │                   │                   │
        ▼                   ▼                   ▼
┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│DrawableLayer │    │InteractionMgr│    │RenderEngine  │
│              │    │              │    │              │
│ ┌──────────┐ │    │ ┌──────────┐ │    │ ┌──────────┐ │
│ │Permanent │ │    │ │ToolMgr   │ │    │ │Renderer  │ │
│ │Layer     │ │    │ └──────────┘ │    │ │Pipeline  │ │
│ └──────────┘ │    │      │       │    │ └──────────┘ │
│              │    │      │       │    │              │
│ ┌──────────┐ │    │      ▼       │    │ ┌──────────┐ │
│ │Temporary │ │    │ ┌──────────┐ │    │ │Camera    │ │
│ │Layer     │ │    │ │DrawTools │ │    │ │Control   │ │
│ └──────────┘ │    │ └──────────┘ │    │ └──────────┘ │
│              │    │      │       │    │              │
│ ┌──────────┐ │    │      ▼       │    │ ┌──────────┐ │
│ │Highlight │ │    │ ┌──────────┐ │    │ │Coordinate│ │
│ │Layer     │ │    │ │EditTools │ │    │ │System    │ │
│ └──────────┘ │    │ └──────────┘ │    │ └──────────┘ │
└──────────────┘    └──────────────┘    └──────────────┘

核心模块设计

Scene2DManager(场景管理器)

Scene2DManager
├── 职责
│   ├── 管理所有绘制层
│   ├── 协调交互和渲染
│   ├── 处理场景状态切换
│   └── 提供统一的对外接口
│
├── 主要组件
│   ├── DrawableLayerManager (绘制层管理)
│   ├── InteractionManager (交互管理)
│   ├── RenderEngine (渲染引擎)
│   └── StateManager (状态管理)
│
└── 核心功能
    ├── 初始化场景
    ├── 设置交互模式
    ├── 更新场景
    └── 渲染场景

2.2 DrawableLayer(绘制层系统)

DrawableLayerManager
│
├── PermanentLayer (永久层)
│   ├── 用途:显示已确认的特征和测量
│   ├── 内容
│   │   ├── 特征对象 (Point, Line, Circle)
│   │   └── 测量对象 (Distance, Angle, etc.)
│   └── 特点
│       ├── 持久化存储
│       ├── 可编辑
│       └── 可序列化
│
├── TemporaryLayer (临时层)
│   ├── 用途:显示正在创建的对象
│   ├── 内容
│   │   ├── 预览图形
│   │   ├── 辅助线
│   │   └── 捕捉点
│   └── 特点
│       ├── 动态更新
│       ├── 鼠标移动时实时刷新
│       └── 确认后转移到永久层
│
├── HighlightLayer (高亮层)
│   ├── 用途:显示选中、悬停效果
│   ├── 内容
│   │   ├── 选中对象的高亮
│   │   ├── 悬停对象的预览
│   │   └── 编辑手柄
│   └── 特点
│       ├── 覆盖在其他层之上
│       ├── 不同的渲染样式
│       └── 交互反馈
│
└── BackgroundLayer (背景层)
    ├── 用途:显示背景元素
    ├── 内容
    │   ├── 截面曲线
    │   ├── 网格
    │   └── 坐标轴
    └── 特点
        ├── 最底层
        ├── 不可交互
        └── 参考作用

2.3 InteractionManager(交互管理)

InteractionManager
│
├── ToolManager (工具管理器)
│   ├── 管理所有交互工具
│   ├── 切换当前工具
│   └── 工具状态机
│
├── DrawingTools (绘制工具)
│   ├── PointDrawTool
│   │   ├── 状态:等待点击
│   │   ├── 鼠标移动:显示预览点
│   │   ├── 鼠标点击:创建点
│   │   └── 完成:添加到永久层
│   │
│   ├── LineDrawTool
│   │   ├── 状态:等待第一点 → 等待第二点
│   │   ├── 鼠标移动:显示预览线
│   │   ├── 鼠标点击:确定端点
│   │   └── 完成:添加到永久层
│   │
│   ├── CircleDrawTool
│   │   ├── 模式1:中心+半径
│   │   │   ├── 状态:等待中心 → 等待半径点
│   │   │   ├── 鼠标移动:显示预览圆
│   │   │   └── 完成:添加到永久层
│   │   │
│   │   └── 模式2:三点定圆
│   │       ├── 状态:等待点1 → 点2 → 点3
│   │       ├── 鼠标移动:显示预览圆
│   │       └── 完成:添加到永久层
│   │
│   └── MeasurementDrawTools
│       ├── DistanceDrawTool
│       ├── AngleDrawTool
│       ├── RadiusDrawTool
│       └── ...
│
├── EditingTools (编辑工具)
│   ├── SelectTool (选择工具)
│   │   ├── 单击选择
│   │   ├── 框选
│   │   └── 多选
│   │
│   ├── MoveTool (移动工具)
│   │   ├── 拖拽对象
│   │   ├── 显示移动预览
│   │   └── 确认位置
│   │
│   ├── EditTool (编辑工具)
│   │   ├── 显示控制点
│   │   ├── 拖拽控制点
│   │   └── 修改几何
│   │
│   └── DeleteTool (删除工具)
│
└── NavigationTools (导航工具)
    ├── PanTool (平移)
    ├── ZoomTool (缩放)
    └── FitTool (适应窗口)

2.4 Tool(工具基类)设计

Tool (工具抽象基类)
│
├── 状态管理
│   ├── ToolState (工具状态枚举)
│   │   ├── IDLE (空闲)
│   │   ├── ACTIVE (激活)
│   │   ├── DRAWING (绘制中)
│   │   └── EDITING (编辑中)
│   │
│   └── 状态转换
│       ├── activate() → ACTIVE
│       ├── deactivate() → IDLE
│       └── reset() → 初始状态
│
├── 鼠标事件处理
│   ├── onMousePress(x, y, button)
│   ├── onMouseMove(x, y)
│   ├── onMouseRelease(x, y, button)
│   ├── onMouseDoubleClick(x, y)
│   └── onMouseWheel(delta)
│
├── 键盘事件处理
│   ├── onKeyPress(key)
│   ├── onKeyRelease(key)
│   └── 快捷键
│       ├── ESC → 取消当前操作
│       ├── Enter → 确认
│       └── Delete → 删除
│
├── 绘制接口
│   ├── drawPreview() → 在临时层绘制预览
│   ├── drawFeedback() → 绘制交互反馈
│   └── clearPreview() → 清除预览
│
└── 辅助功能
    ├── 捕捉功能
    │   ├── snapToPoint() → 捕捉到点
    │   ├── snapToLine() → 捕捉到线
    │   ├── snapToGrid() → 捕捉到网格
    │   └── snapToIntersection() → 捕捉到交点
    │
    └── 约束功能
        ├── horizontalConstraint() → 水平约束
        ├── verticalConstraint() → 垂直约束
        └── angleConstraint() → 角度约束

2.5 绘制工具的状态机示例

LineDrawTool 状态机

LineDrawTool 状态转换
│
├── State: WAITING_FIRST_POINT (等待第一点)
│   │
│   ├── onMouseMove(x, y)
│   │   └── 显示光标位置的捕捉点
│   │
│   └── onMousePress(x, y)
│       ├── 记录第一点坐标
│       ├── 在临时层创建预览线
│       └── 转换到 WAITING_SECOND_POINT
│
├── State: WAITING_SECOND_POINT (等待第二点)
│   │
│   ├── onMouseMove(x, y)
│   │   ├── 更新预览线的第二点
│   │   ├── 显示距离标注
│   │   └── 显示角度提示
│   │
│   ├── onMousePress(x, y)
│   │   ├── 记录第二点坐标
│   │   ├── 创建Line2D对象
│   │   ├── 添加到永久层
│   │   ├── 清除临时层
│   │   └── 转换到 COMPLETED
│   │
│   └── onKeyPress(ESC)
│       ├── 清除临时层
│       └── 转换到 WAITING_FIRST_POINT
│
└── State: COMPLETED (完成)
    └── 触发完成事件

CircleDrawTool 状态机(中心+半径模式)

CircleDrawTool 状态转换
│
├── State: WAITING_CENTER (等待中心点)
│   │
│   ├── onMouseMove(x, y)
│   │   └── 显示中心点预览
│   │
│   └── onMousePress(x, y)
│       ├── 记录中心点坐标
│       ├── 在临时层创建预览圆(半径=0)
│       └── 转换到 WAITING_RADIUS
│
├── State: WAITING_RADIUS (等待半径点)
│   │
│   ├── onMouseMove(x, y)
│   │   ├── 计算半径 = distance(center, current)
│   │   ├── 更新预览圆的半径
│   │   └── 显示半径数值
│   │
│   ├── onMousePress(x, y)
│   │   ├── 计算最终半径
│   │   ├── 创建Circle2D对象
│   │   ├── 添加到永久层
│   │   ├── 清除临时层
│   │   └── 转换到 COMPLETED
│   │
│   └── onKeyPress(ESC)
│       ├── 清除临时层
│       └── 转换到 WAITING_CENTER
│
└── State: COMPLETED (完成)
    └── 触发完成事件

2.6 RenderEngine(渲染引擎)

RenderEngine
│
├── RendererPipeline (渲染管线)
│   ├── vtkRenderer (主渲染器)
│   ├── 多层渲染支持
│   │   ├── Layer 0: Background
│   │   ├── Layer 1: Permanent
│   │   ├── Layer 2: Temporary
│   │   └── Layer 3: Highlight
│   └── 渲染顺序控制
│
├── CameraController (相机控制)
│   ├── 2D正交相机设置
│   ├── 缩放控制
│   ├── 平移控制
│   └── 视图适配
│
├── CoordinateSystem (坐标系)
│   ├── vtkCubeAxesActor
│   ├── 网格显示
│   ├── 刻度标注
│   └── 单位显示
│
└── RenderOptimization (渲染优化)
    ├── 脏区域标记
    ├── 按需渲染
    ├── LOD (细节层次)
    └── 性能监控

3. 交互流程设计

3.1 绘制流程(以绘制直线为例)

用户操作流程:
│
├── 1. 选择工具
│   │
│   ├── 用户点击"绘制直线"按钮
│   │   ↓
│   ├── ToolManager.setCurrentTool(LineDrawTool)
│   │   ↓
│   ├── 激活LineDrawTool
│   │   ↓
│   └── 状态 → WAITING_FIRST_POINT
│
├── 2. 鼠标移动(寻找第一点)
│   │
│   ├── 鼠标在视口移动
│   │   ↓
│   ├── InteractionManager捕获鼠标事件
│   │   ↓
│   ├── 转发到当前工具: LineDrawTool.onMouseMove(x, y)
│   │   ↓
│   ├── LineDrawTool处理
│   │   ├── 将屏幕坐标转换为世界坐标
│   │   ├── 执行捕捉检测
│   │   │   ├── 检测附近的点
│   │   │   ├── 检测附近的线
│   │   │   └── 检测网格点
│   │   ├── 在TemporaryLayer绘制捕捉点预览
│   │   └── 更新光标样式
│   │       ↓
│   └── RenderEngine.render() → 刷新显示
│
├── 3. 点击第一点
│   │
│   ├── 鼠标左键点击
│   │   ↓
│   ├── InteractionManager.onMousePress(x, y, LEFT)
│   │   ↓
│   ├── LineDrawTool.onMousePress(x, y)
│   │   ↓
│   ├── LineDrawTool处理
│   │   ├── 记录第一点坐标 point1 = (x, y)
│   │   ├── 创建临时线对象
│   │   │   └── tempLine = new Line2DDrawable()
│   │   ├── 添加到TemporaryLayer
│   │   │   └── temporaryLayer.add(tempLine)
│   │   └── 状态转换 → WAITING_SECOND_POINT
│   │       ↓
│   └── RenderEngine.render()
│
├── 4. 鼠标移动(拖拽第二点)
│   │
│   ├── 鼠标移动
│   │   ↓
│   ├── LineDrawTool.onMouseMove(x, y)
│   │   ↓
│   ├── LineDrawTool处理
│   │   ├── 更新临时线的第二点
│   │   │   └── tempLine.setPoint2(x, y)
│   │   ├── 计算并显示辅助信息
│   │   │   ├── 长度 = distance(point1, current)
│   │   │   ├── 角度 = angle(point1, current)
│   │   │   └── 在TemporaryLayer显示文本
│   │   ├── 应用约束(如果按住Shift)
│   │   │   ├── 水平约束
│   │   │   ├── 垂直约束
│   │   │   └── 45度约束
│   │   └── 捕捉检测
│   │       ↓
│   └── RenderEngine.render() → 实时更新预览
│
├── 5. 点击第二点(完成绘制)
│   │
│   ├── 鼠标左键点击
│   │   ↓
│   ├── LineDrawTool.onMousePress(x, y)
│   │   ↓
│   ├── LineDrawTool处理
│   │   ├── 记录第二点坐标 point2 = (x, y)
│   │   ├── 创建数据对象
│   │   │   └── line2D = new Line2D(point1, point2)
│   │   ├── 添加到数据管理器
│   │   │   └── measurementData.addFeature(line2D)
│   │   ├── 创建永久绘制对象
│   │   │   └── drawable = new Line2DDrawable(line2D)
│   │   ├── 添加到PermanentLayer
│   │   │   └── permanentLayer.add(drawable)
│   │   ├── 清除TemporaryLayer
│   │   │   └── temporaryLayer.clear()
│   │   └── 状态转换 → COMPLETED
│   │       ↓
│   ├── 触发完成事件
│   │   └── emit lineCreated(line2D.getId())
│   │       ↓
│   └── RenderEngine.render()
│
└── 6. 重置或继续
    │
    ├── 如果工具设置为"连续绘制"
    │   └── 状态 → WAITING_FIRST_POINT (重新开始)
    │
    └── 如果工具设置为"单次绘制"
        └── ToolManager.setCurrentTool(SelectTool)

3.2 编辑流程(移动特征)

编辑流程:
│
├── 1. 选择对象
│   │
│   ├── 用户点击某个特征
│   │   ↓
│   ├── SelectTool.onMousePress(x, y)
│   │   ↓
│   ├── 点击测试
│   │   ├── 遍历PermanentLayer中的所有Drawable
│   │   ├── 调用drawable.hitTest(x, y)
│   │   └── 找到被点击的对象
│   │       ↓
│   ├── 选中对象
│   │   ├── drawable.setSelected(true)
│   │   ├── 在HighlightLayer显示选中效果
│   │   │   ├── 高亮边框
│   │   │   └── 控制点
│   │   └── emit objectSelected(id)
│   │       ↓
│   └── RenderEngine.render()
│
├── 2. 开始拖拽
│   │
│   ├── 鼠标按下并移动
│   │   ↓
│   ├── MoveTool.onMousePress(x, y)
│   │   ↓
│   ├── 检查是否点击在选中对象上
│   │   ├── 如果是 → 进入拖拽模式
│   │   │   ├── 记录初始位置
│   │   │   ├── 记录鼠标起始位置
│   │   │   └── 状态 → DRAGGING
│   │   └── 如果否 → 取消选中
│
├── 3. 拖拽过程
│   │
│   ├── 鼠标移动
│   │   ↓
│   ├── MoveTool.onMouseMove(x, y)
│   │   ↓
│   ├── 计算偏移
│   │   ├── deltaX = currentX - startX
│   │   ├── deltaY = currentY - startY
│   │   └── newPos = initialPos + delta
│   │       ↓
│   ├── 在TemporaryLayer显示移动预览
│   │   ├── 创建预览对象(虚线显示)
│   │   ├── 设置新位置
│   │   └── 显示位移数值
│   │       ↓
│   └── RenderEngine.render() → 实时预览
│
├── 4. 完成拖拽
│   │
│   ├── 鼠标释放
│   │   ↓
│   ├── MoveTool.onMouseRelease(x, y)
│   │   ↓
│   ├── 更新数据
│   │   ├── 获取Feature2D对象
│   │   ├── 更新坐标数据
│   │   │   └── feature.setPosition(newX, newY)
│   │   └── 通知数据变化
│   │       └── emit featureDataChanged(id)
│   │           ↓
│   ├── 更新绘制
│   │   ├── drawable.updateGeometry()
│   │   ├── 清除TemporaryLayer
│   │   └── 更新相关测量
│   │       ├── 查找使用该特征的测量
│   │       └── 重新计算测量值
│   │           ↓
│   └── RenderEngine.render()
│
└── 5. 撤销/重做支持
    │
    ├── 记录操作到命令栈
    │   └── CommandManager.execute(MoveCommand)
    │
    └── 支持Ctrl+Z撤销

3.3 测量创建流程(距离测量)

距离测量创建流程:
│
├── 1. 激活距离测量工具
│   │
│   ├── 用户点击"距离测量"按钮
│   │   ↓
│   ├── ToolManager.setCurrentTool(DistanceDrawTool)
│   │   ↓
│   └── 状态 → WAITING_FIRST_FEATURE
│
├── 2. 选择第一个特征
│   │
│   ├── 鼠标移动
│   │   ↓
│   ├── DistanceDrawTool.onMouseMove(x, y)
│   │   ↓
│   ├── 高亮悬停的特征
│   │   ├── 检测鼠标下的特征
│   │   ├── 在HighlightLayer显示高亮
│   │   └── 显示提示:"点击选择第一个点"
│   │       ↓
│   ├── 鼠标点击
│   │   ↓
│   ├── DistanceDrawTool.onMousePress(x, y)
│   │   ↓
│   ├── 选中特征
│   │   ├── 记录第一个特征ID: featureId1
│   │   ├── 在HighlightLayer标记选中
│   │   └── 状态 → WAITING_SECOND_FEATURE
│
├── 3. 选择第二个特征
│   │
│   ├── 鼠标移动
│   │   ↓
│   ├── DistanceDrawTool.onMouseMove(x, y)
│   │   ↓
│   ├── 显示预览
│   │   ├── 获取第一个特征的位置
│   │   ├── 获取当前鼠标位置
│   │   ├── 在TemporaryLayer绘制预览线
│   │   └── 显示距离数值
│   │       ↓
│   ├── 鼠标点击
│   │   ↓
│   ├── DistanceDrawTool.onMousePress(x, y)
│   │   ↓
│   ├── 选中第二个特征
│   │   └── 记录第二个特征ID: featureId2
│
├── 4. 创建测量对象
│   │
│   ├── 创建Distance2D数据对象
│   │   ├── distance = new Distance2D()
│   │   ├── distance.setFeatures(featureId1, featureId2)
│   │   └── 计算距离值
│   │       ├── 获取两个特征的几何数据
│   │       ├── 计算距离
│   │       └── distance.setValue(value)
│   │           ↓
│   ├── 添加到数据管理器
│   │   └── measurementData.addMeasurement(distance)
│   │       ↓
│   ├── 创建绘制对象
│   │   ├── drawable = new Distance2DDrawable(distance, data)
│   │   ├── drawable创建VTK对象
│   │   │   ├── 标注线
│   │   │   ├── 箭头
│   │   │   └── 文本标注
│   │   └── 添加到PermanentLayer
│   │       ↓
│   ├── 清除临时层
│   │   └── temporaryLayer.clear()
│   │       ↓
│   └── 状态 → COMPLETED
│
└── 5. 自动更新机制
    │
    ├── 当特征位置改变时
    │   ↓
    ├── 监听featureDataChanged信号
    │   ↓
    ├── 查找使用该特征的测量
    │   └── measurements = data.getMeasurementsUsingFeature(featureId)
    │       ↓
    ├── 重新计算测量值
    │   ├── 获取最新的特征位置
    │   ├── 重新计算距离
    │   └── measurement.setValue(newValue)
    │       ↓
    ├── 更新绘制
    │   ├── drawable.updateGeometry()
    │   └── drawable.updateMeasurementText()
    │       ↓
    └── RenderEngine.render()

4. 数据流和事件流

4.1 数据流

数据流向:

用户输入
    ↓
InteractionManager (捕获事件)
    ↓
CurrentTool (处理事件)
    ↓
┌─────────────────┬─────────────────┐
│                 │                 │
▼                 ▼                 ▼
创建临时绘制      修改数据对象      查询数据
    ↓                 ↓                 ↓
TemporaryLayer    Measurement2DData  返回结果
    ↓                 ↓
显示预览          创建/更新Drawable
    ↓                 ↓
RenderEngine      PermanentLayer
    ↓                 ↓
刷新显示  ←───────────┘

4.2 事件流

事件系统:

Scene2DManager
    │
    ├── 发出事件
    │   ├── featureCreated(id)
    │   ├── featureModified(id)
    │   ├── featureDeleted(id)
    │   ├── measurementCreated(id)
    │   ├── measurementModified(id)
    │   ├── measurementDeleted(id)
    │   ├── selectionChanged(ids)
    │   └── toolChanged(toolType)
    │
    └── 监听事件
        ├── 来自Tool的事件
        ├── 来自DrawableLayer的事件
        └── 来自外部的命令

5. 渲染优化策略

5.1 分层渲染

渲染层次(从底到顶):

Layer 0: Background (背景层)
    ├── 截面曲线 (不常变化)
    ├── 坐标轴 (不常变化)
    └── 网格 (不常变化)
    → 渲染频率:低

Layer 1: Permanent (永久层)
    ├── 确认的特征 (偶尔变化)
    └── 确认的测量 (偶尔变化)
    → 渲染频率:中

Layer 2: Temporary (临时层)
    ├── 预览图形 (频繁变化)
    ├── 辅助线 (频繁变化)
    └── 捕捉点 (频繁变化)
    → 渲染频率:高

Layer 3: Highlight (高亮层)
    ├── 选中效果 (按需变化)
    ├── 悬停效果 (频繁变化)
    └── 编辑手柄 (按需变化)
    → 渲染频率:高

5.2 脏区域标记

DirtyRegion 系统:

每个Layer维护自己的脏标记
    │
    ├── Layer.markDirty()
    │   └── 标记该层需要重新渲染
    │
    ├── Layer.isDirty()
    │   └── 查询是否需要渲染
    │
    └── RenderEngine.render()
        ├── 只渲染标记为dirty的层
        └── 渲染后清除dirty标记

优势:
    ├── 减少不必要的渲染
    ├── 提高交互响应速度
    └── 降低CPU/GPU负载

6. 坐标系统

6.1 坐标转换

坐标系统:

Screen Coordinates (屏幕坐标)
    ↕ (转换)
World Coordinates (世界坐标)
    ↕ (转换)
Data Coordinates (数据坐标)

转换管理器:CoordinateTransform
    │
    ├── screenToWorld(screenX, screenY) → (worldX, worldY)
    │   └── 使用vtkCoordinate转换
    │
    ├── worldToScreen(worldX, worldY) → (screenX, screenY)
    │   └── 使用vtkCoordinate转换
    │
    └── worldToData(worldX, worldY) → (dataX, dataY)
        └── 考虑缩放和平移

7. 架构总览图

┌─────────────────────────────────────────────────────────────────┐
│                    ViewportCrossSection                          │
│                         (Qt Widget)                              │
└─────────────────────────────────────────────────────────────────┘
                            │
                            │ owns
                            ▼
┌─────────────────────────────────────────────────────────────────┐
│                      Scene2DManager                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────┐ │
│  │DrawableLayerMgr  │  │InteractionManager│  │RenderEngine  │ │
│  ├──────────────────┤  ├──────────────────┤  ├──────────────┤ │
│  │ ┌──────────────┐ │  │ ┌──────────────┐ │  │ ┌──────────┐ │ │
│  │ │Background    │ │  │ │ToolManager   │ │  │ │Renderer  │ │ │
│  │ │Layer         │ │  │ └──────────────┘ │  │ │Pipeline  │ │ │
│  │ └──────────────┘ │  │        │         │  │ └──────────┘ │ │
│  │ ┌──────────────┐ │  │        ▼         │  │ ┌──────────┐ │ │
│  │ │Permanent     │ │  │ ┌──────────────┐ │  │ │Camera    │ │ │
│  │ │Layer         │ │  │ │CurrentTool   │ │  │ │Control   │ │ │
│  │ └──────────────┘ │  │ │              │ │  │ └──────────┘ │ │
│  │ ┌──────────────┐ │  │ │ DrawingTools │ │  │ ┌──────────┐ │ │
│  │ │Temporary     │ │  │ │ EditingTools │ │  │ │Coord     │ │ │
│  │ │Layer         │ │  │ │NavigationTool│ │  │ │System    │ │ │
│  │ └──────────────┘ │  │ └──────────────┘ │  │ └──────────┘ │ │
│  │ ┌──────────────┐ │  │                  │  │              │ │
│  │ │Highlight     │ │  │ ┌──────────────┐ │  │              │ │
│  │ │Layer         │ │  │ │SnapManager   │ │  │              │ │
│  │ └──────────────┘ │  │ └──────────────┘ │  │              │ │
│  └──────────────────┘  └──────────────────┘  └──────────────┘ │
│                                                                  │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                  StateManager                             │  │
│  │  - 管理场景状态                                            │  │
│  │  - 撤销/重做                                               │  │
│  │  - 历史记录                                                │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
                            │
                            │ uses
                            ▼
┌─────────────────────────────────────────────────────────────────┐
│                   Measurement2DData                              │
│                   (数据管理器)                                    │
├─────────────────────────────────────────────────────────────────┤
│  - features: QMap<int, Feature2D*>                              │
│  - measurements: QMap<int, Measurement2D*>                      │
└─────────────────────────────────────────────────────────────────┘

8. 关键设计要点

8.1 职责分离

Scene2DManager
    ├── 负责:整体协调
    └── 不负责:具体绘制、具体交互

DrawableLayer
    ├── 负责:管理Drawable对象
    └── 不负责:创建Drawable、处理交互

InteractionManager
    ├── 负责:事件分发、工具管理
    └── 不负责:具体的绘制逻辑

Tool
    ├── 负责:处理特定的交互逻辑
    └── 不负责:数据管理、渲染管理

Drawable
    ├── 负责:VTK对象的创建和更新
    └── 不负责:数据存储、交互处理

8.2 扩展性

添加新的绘制工具:
    1. 继承Tool基类
    2. 实现状态机
    3. 实现鼠标/键盘事件处理
    4. 在ToolManager中注册

添加新的特征类型:
    1. 继承Feature2D
    2. 继承Drawable2D
    3. 在DrawableFactory中添加创建逻辑
    4. 可选:创建对应的DrawTool

添加新的测量类型:
    1. 继承Measurement2D
    2. 继承Measurement2DDrawable
    3. 在DrawableFactory中添加创建逻辑
    4. 可选:创建对应的MeasurementDrawTool

8.3 性能优化

渲染优化:
    ├── 分层渲染(只渲染变化的层)
    ├── 脏区域标记(避免不必要的渲染)
    ├── LOD(根据缩放级别调整细节)
    └── 批量渲染(合并多个小对象)

交互优化:
    ├── 事件节流(限制鼠标移动事件频率)
    ├── 异步计算(复杂计算放到后台线程)
    ├── 缓存计算结果(避免重复计算)
    └── 空间索引(加速点击测试)
作者:admin  创建时间:2025-12-09 17:28
最后编辑:admin  更新时间:2025-12-10 18:39