需求列表
- 截面创建(页面)
- 标题栏按钮(截面测量、点-点、点-线、线-线等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种不同算法)
- 截面报告生成
- 截面3视图生成
- 界面特征测量数据表导出(例如:1.圆 特征索引id、类型、圆周长;2. 线-圆的距离 特征索引id、线索引id、圆索引id、 类型、距离值、偏差值(与cad对比))
- 截面测量恢复&重建
- 打开工程(已经有截面测量节点)时,后台恢复(可能会直接导出报告,如果选中且并没有修改过截面测量)
- 截面测量创建过程中,切换到其他页面后继续进行截面测量的恢复重建
截面最底层数据结构设计
依据原理
- 怎么存(对象树、工程的序列)
- 怎么恢复(从数据结构到2d/3d视角、模型、actor等)
- 恢复什么数据
恢复什么数据需要弄清楚截面测量干什么,核心目的是什么,界面元素有什么?
- 截面测量主要是测量3d数据中根据某些规则对数据切割后投影到2d坐标系的切割边界中的元素间的:1.距离、2.角度、3.圆弧角度、4.圆半径、5.圆周长等数据;能够导出对应报告;能够关机、关闭软件后继续测量
总结就是:- 最核心点:测量切割截面的那5个数据
- 次要是能导出报告
- 再者能够恢复加载
界面元素中包含了以下需要存储的数据:
- 3d视口中模型的位姿、相机位姿
- 2d视口中选取的类型:坐标系作为基准面(xy,yz,xz)、或者特征作为基准面,特征的数据、旋转角度,横线(切割线)位置
- 底部2d视口中坐标的缩放值
汇总所有需要存储的数据包括以下三种:
a. 对象树内部存储针对测量的数据:
- 距离数据
- 角度数据
- 原数角度数据
- 圆半径
- 圆周周长
- 三个特征(点、圆、线)
b. 辅助视口数据:
- 3d视口模型位姿
- 3d视口相机位姿
- 视口截取参数:基准平面、旋转角度,截取直线坐标(直线方程)
- 2d视口缩放比例
- 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. 选择工具
│ │
│ ├── 用户点击"绘制直线"按钮
│ │ ↓
│ ├── 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. 可选:创建对应的MeasurementDrawTool8.3 性能优化
渲染优化:
├── 分层渲染(只渲染变化的层)
├── 脏区域标记(避免不必要的渲染)
├── LOD(根据缩放级别调整细节)
└── 批量渲染(合并多个小对象)
交互优化:
├── 事件节流(限制鼠标移动事件频率)
├── 异步计算(复杂计算放到后台线程)
├── 缓存计算结果(避免重复计算)
└── 空间索引(加速点击测试)作者:admin 创建时间:2025-12-09 17:28
最后编辑:admin 更新时间:2025-12-10 18:39
最后编辑:admin 更新时间:2025-12-10 18:39