文档版本: v2.0 (整合版) 文档位置:
gui/模型树组件详细设计报告.md适用范围: PyMeshGen GUI 中"模型树"控件(gui/model_tree.py)的设计、实现与扩展 当前日期: 2026-01-18 整合来源:
gui/模型树组件设计报告.mdgui/ModelTree_DesignReport.mdgui/temp_docs/model_tree_design.mdgui/temp_docs/模型树设计报告.md
- 概述
- 背景与目标
- 总体架构
- 数据模型与状态管理
- UI设计
- 核心功能实现
- 交互功能设计
- 性能优化策略
- 数据管理
- 与其他模块的集成
- 使用示例
- 设计模式
- 扩展性设计
- 测试建议
- 风险、边界与改进建议
- 关键实现清单
- 后续扩展建议
- 术语表
- 总结
模型树组件(ModelTreeWidget)是PyMeshGen GUI系统的核心组件之一,负责以树形结构统一展示和管理几何模型、网格数据和部件信息。该组件采用三层架构设计,分别为几何层(Geometry)、网格层(Mesh)和部件层(Parts),为用户提供直观的层次化数据管理界面。
模型树组件的设计目标包括:
- 统一展示:在一个树形控件中统一管理几何、网格和部件三层结构
- 高效性能:支持大规模数据的加载和显示,避免界面卡顿
- 交互友好:提供直观的勾选、展开/折叠、右键菜单等交互方式
- 数据关联:与视图显示、属性面板等组件紧密联动
- 扩展性强:易于添加新的元素类型和功能
- 可扩展:后续增加"边界条件""材料""求解设置"等节点类型时成本低
- 性能可控:面对大模型(>1e4 / 1e5 拓扑元素)避免 UI 冻结
- 交互闭环:树上的点击/勾选/右键菜单能驱动 3D 视图显示、对象选择、部件管理等
- GUI框架: PyQt5
- 树形控件: QTreeWidget(项目当前选择的是 item-based Tree 控件,而非 model/view 的
QTreeView + QAbstractItemModel) - 异步处理: QTimer
- 几何引擎: OpenCASCADE (pythonocc-core)
- 可视化: VTK
- 数据结构: QTreeWidget, QTreeWidgetItem
PyMeshGen GUI 需要一个统一的"模型树"来承载三类核心对象的可视化与交互:
- 几何(Geometry):来自 OpenCASCADE(OCC)的
TopoDS_Shape及其子拓扑元素 - 网格(Mesh):网格节点/边/面/体等实体(可能来自内部数据结构或导入格式)
- 部件(Parts):对几何/网格进行工程化组织(分组、命名、参数、可见性等)
- 数据规模:几何模型可能包含数万甚至数十万个拓扑元素
- 实时性:用户操作需要即时响应,不能阻塞UI线程
- 内存管理:大量树项的创建和销毁需要合理控制内存占用
- 状态同步:树项状态需要与3D视图、属性面板等保持同步
- 采用分批加载策略,避免一次性创建大量树项
- 使用延迟加载机制,超过阈值的元素不完全加载到树中
- 实现虚拟化支持,超过最大限制时显示摘要信息
- 通过回调机制实现与其他组件的松耦合
- 核心实现:
gui/model_tree.py - 可视组件: PyQt5
QTreeWidget
ModelTreeWidget 并非 QWidget 子类,而是一个"组件封装类",对外提供:
self.widget:可直接嵌入布局的容器QWidgetself.tree:内部的QTreeWidget- 一组
load_*/update_*方法:用于按业务数据重建/刷新树
这种封装结构的优点:
- 调用者只需把
model_tree.widget加到布局即可 - 组件内部可以维护额外状态(geometry_data / mesh_data / parts_data 等)
模型树采用分层设计,主要包含以下层次:
ModelTreeWidget (主控制器)
├── UI层 (QTreeWidget + QTreeWidgetItem)
├── 数据层 (几何/网格/部件数据)
├── 交互层 (事件处理、右键菜单)
└── 接口层 (对外API)
模型树固定建立三个顶层节点(TopLevelItem):
- 几何(
Qt.UserRole = "geometry") - 网格(
Qt.UserRole = "mesh") - 部件(
Qt.UserRole = "parts")
其中几何与网格下均预置四个二级分类节点:
- 点(vertices)
- 线(edges)
- 面(faces)
- 体(bodies)
对应 Qt.UserRole 存储为元组以表达节点语义,例如:
("geometry", "vertices")("mesh", "faces")
部件节点下则由 parts_data 动态创建。
根节点
├── 几何 (Geometry)
│ ├── 点 (Vertices)
│ │ ├── 点_0
│ │ ├── 点_1
│ │ └── ...
│ ├── 线 (Edges)
│ ├── 面 (Faces)
│ └── 体 (Bodies)
├── 网格 (Mesh)
│ ├── 点 (Vertices)
│ ├── 线 (Edges)
│ ├── 面 (Faces)
│ └── 体 (Bodies)
└── 部件 (Parts)
├── 部件1
├── 部件2
└── ...
- ModelTreeWidget 作为主控制器,负责整体逻辑管理
- 与 PartManager 组件紧密协作,处理部件相关操作
- 与 MeshDisplayArea 组件协同,控制可视化显示
- 通过回调机制与主窗口通信
ModelTreeWidget 维护三类数据引用及名称:
geometry_data,geometry_namemesh_data,mesh_nameparts_data,parts_name
并通过 _updating_items(或 blockSignals(True/False))规避"程序性修改 item 导致的递归事件触发"。
模型树大量依赖 QTreeWidgetItem.setData(0, Qt.UserRole, payload) 来携带业务信息。
建议在代码层面保持一致的数据结构约定:
- 顶层类目:
"geometry" | "mesh" | "parts" - 二级类目:
(domain, category)例如( "geometry", "vertices" ) - 叶子对象:
(domain, category, obj_ref, index)
其中:
domain用于区分几何/网格/部件category用于区分 vertices/edges/faces/bodiesobj_ref是 OCC 拓扑对象引用(或网格/部件对象引用)index用于稳定命名与定位(例如点_123)
说明:由于
QTreeWidgetItem本身并不适合存储大型对象图,更理想的做法是只存储轻量 ID,再通过仓库(dict / manager)映射到对象;当前实现直接存储TopoDS_*引用属于"可用但需谨慎"的方案(见第15章风险)。
几何元素使用OpenCASCADE的TopoDS对象:
- 顶点:
TopoDS_Vertex- 存储坐标信息 - 边:
TopoDS_Edge- 存储曲线信息和长度 - 面:
TopoDS_Face- 存储曲面信息和面积 - 体:
TopoDS_Solid- 存储实体信息和体积
网格元素使用自定义数据结构:
- 节点:
node_coords- 节点坐标数组 - 单元:
cell_container或cells- 单元容器
部件数据支持多种格式:
# 字典格式
{
'parts_info': [
{
'part_name': '部件名称',
'bc_type': '边界条件类型',
'geometry_elements': {...},
'mesh_elements': {...}
}
]
}
# 对象属性格式
parts_data.parts_info = [...]
parts_data.boundary_info = {...}MAX_TREE_ITEMS = 100000 # 最大树项数量,超过则使用虚拟化
LAZY_LOAD_THRESHOLD = 10000 # 延迟加载阈值
BATCH_SIZE = 1000 # 每批处理的元素数量- 两列:名称、数量
- 列宽策略:
- 第 0 列 Stretch
- 第 1 列 ResizeToContents
"数量"列承担两类含义:
- 类目节点(如 点/线/面/体)的数量统计
- 异步/分批加载时的状态提示(例如 "加载中...")
所有关键节点默认带复选框(Qt.Checked):
- 顶层(几何/网格/部件)
- 子类目(点/线/面/体)
- 叶子对象(具体点/线/面/体)
勾选动作通常语义为"显示/隐藏"或"启用/禁用"。
实现上通常需要:
- 向下传播:父节点取消勾选应影响子节点
- 向上回填:子节点部分选中时父节点应为 PartiallyChecked
当前代码通过 itemChanged + _updating_items/blockSignals 机制避免递归。
模型树在 Qt.CustomContextMenu 模式下工作:
customContextMenuRequested->_show_context_menu(pos)
常见菜单操作(取决于 parent/part_manager 是否提供 handler):
- 部件:创建/删除/重命名/设置参数/导出
- 几何:定位、显示/隐藏、(可能)隔离显示
- 网格:显示网格质量、导出某一部分
实现策略:模型树不直接执行业务逻辑,而是通过 _get_parent_handler() 反射查找:
parent.part_manager.<handler_name>parent.<handler_name>
这使得模型树与业务管理器解耦,便于复用。
- 交替行颜色:启用
setAlternatingRowColors(True)提高可读性 - 统一行高:启用
setUniformRowHeights(True)优化渲染性能 - 工具提示:为元素添加详细信息提示(坐标、长度、面积、体积等)
class ModelTreeWidget:
"""统一模型树组件 - 三层结构:几何、网格、部件"""
# 性能相关常量
MAX_TREE_ITEMS = 100000 # 最大树项数量
LAZY_LOAD_THRESHOLD = 10000 # 延迟加载阈值
def __init__(self, parent=None)
def _create_tree_widget(self)
def _setup_ui(self)
def _init_tree_structure(self)
# 数据加载方法
def load_geometry(self, shape, geometry_name="几何")
def load_mesh(self, mesh_data, mesh_name="网格")
def load_parts(self, parts_data=None)
# 元素提取方法
def _extract_geometry_elements(self, shape)
def _extract_mesh_elements(self, mesh_data)
def _extract_parts_elements(self, parts_data)
# 事件处理方法
def _on_item_changed(self, item, column)
def _on_item_clicked(self, item, column)
def _handle_visibility_change(self, item, element_data)
def _handle_selection_change(self, item, element_data)
# 右键菜单方法
def _show_context_menu(self, position)
def _show_vertex_properties(self, vertex, item)
def _show_edge_properties(self, edge, item)
def _show_face_properties(self, face, item)
def _show_solid_properties(self, solid, item)
# 可见性控制方法
def _update_child_items(self, item, check_state)
def _update_parent_item(self, item)
def set_element_visibility(self, category, element_type, element_index, visible)
def set_category_visibility(self, category, element_type, visible)
# 查询方法
def get_visible_elements(self, category=None, element_type=None)
def get_geometry_type_states(self)
def get_visible_parts(self)
# 工具方法
def clear(self)
def _get_parent_handler(self, handler_name)def _create_tree_widget(self):
"""创建树形控件"""
self.tree = QTreeWidget()
self.tree.setHeaderLabels(["名称", "数量"])
self.tree.setColumnWidth(0, 200)
self.tree.setColumnWidth(1, 60)
self.tree.setAlternatingRowColors(True)
self.tree.setUniformRowHeights(True)
header = self.tree.header()
header.setStretchLastSection(False)
header.setSectionResizeMode(0, QHeaderView.Stretch)
header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
self.tree.itemChanged.connect(self._on_item_changed)
self.tree.itemClicked.connect(self._on_item_clicked)
self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
self.tree.customContextMenuRequested.connect(self._show_context_menu)设计要点:
- 双列显示:名称列(可伸缩)和数量列(自适应内容)
- 启用交替行颜色,提高可读性
- 统一行高,优化渲染性能
- 连接信号槽,处理交互事件
def _init_tree_structure(self):
"""初始化三层模型树结构"""
self.tree.clear()
self.tree.blockSignals(True)
# 创建几何节点
geometry_item = QTreeWidgetItem(self.tree)
geometry_item.setText(0, self.geometry_name)
geometry_item.setText(1, "")
geometry_item.setExpanded(True)
geometry_item.setCheckState(0, Qt.Checked)
geometry_item.setData(0, Qt.UserRole, "geometry")
# 创建几何子节点(点、线、面、体)
geometry_vertices_item = QTreeWidgetItem(geometry_item)
geometry_vertices_item.setText(0, "点")
geometry_vertices_item.setText(1, "0")
geometry_vertices_item.setCheckState(0, Qt.Checked)
geometry_vertices_item.setExpanded(False)
geometry_vertices_item.setData(0, Qt.UserRole, ("geometry", "vertices"))
geometry_edges_item = QTreeWidgetItem(geometry_item)
geometry_edges_item.setText(0, "线")
geometry_edges_item.setText(1, "0")
geometry_edges_item.setCheckState(0, Qt.Checked)
geometry_edges_item.setExpanded(False)
geometry_edges_item.setData(0, Qt.UserRole, ("geometry", "edges"))
geometry_faces_item = QTreeWidgetItem(geometry_item)
geometry_faces_item.setText(0, "面")
geometry_faces_item.setText(1, "0")
geometry_faces_item.setCheckState(0, Qt.Checked)
geometry_faces_item.setExpanded(False)
geometry_faces_item.setData(0, Qt.UserRole, ("geometry", "faces"))
geometry_bodies_item = QTreeWidgetItem(geometry_item)
geometry_bodies_item.setText(0, "体")
geometry_bodies_item.setText(1, "0")
geometry_bodies_item.setCheckState(0, Qt.Checked)
geometry_bodies_item.setExpanded(False)
geometry_bodies_item.setData(0, Qt.UserRole, ("geometry", "bodies"))
# 创建网格节点
mesh_item = QTreeWidgetItem(self.tree)
mesh_item.setText(0, self.mesh_name)
mesh_item.setText(1, "")
mesh_item.setExpanded(True)
mesh_item.setCheckState(0, Qt.Checked)
mesh_item.setData(0, Qt.UserRole, "mesh")
# 创建网格子节点
mesh_vertices_item = QTreeWidgetItem(mesh_item)
mesh_vertices_item.setText(0, "点")
mesh_vertices_item.setText(1, "0")
mesh_vertices_item.setCheckState(0, Qt.Checked)
mesh_vertices_item.setData(0, Qt.UserRole, ("mesh", "vertices"))
mesh_edges_item = QTreeWidgetItem(mesh_item)
mesh_edges_item.setText(0, "线")
mesh_edges_item.setText(1, "0")
mesh_edges_item.setCheckState(0, Qt.Checked)
mesh_edges_item.setData(0, Qt.UserRole, ("mesh", "edges"))
mesh_faces_item = QTreeWidgetItem(mesh_item)
mesh_faces_item.setText(0, "面")
mesh_faces_item.setText(1, "0")
mesh_faces_item.setCheckState(0, Qt.Checked)
mesh_faces_item.setData(0, Qt.UserRole, ("mesh", "faces"))
mesh_bodies_item = QTreeWidgetItem(mesh_item)
mesh_bodies_item.setText(0, "体")
mesh_bodies_item.setText(1, "0")
mesh_bodies_item.setCheckState(0, Qt.Checked)
mesh_bodies_item.setData(0, Qt.UserRole, ("mesh", "bodies"))
# 创建部件节点
parts_item = QTreeWidgetItem(self.tree)
parts_item.setText(0, self.parts_name)
parts_item.setText(1, "0")
parts_item.setExpanded(True)
parts_item.setCheckState(0, Qt.Checked)
parts_item.setData(0, Qt.UserRole, "parts")
self.tree.blockSignals(False)设计要点:
- 使用
blockSignals(True)防止初始化时触发事件 - 使用
Qt.UserRole存储元素数据,便于后续访问 - 所有节点默认展开和选中状态
- 为每个节点设置正确的数据结构
def load_geometry(self, shape, geometry_name="几何"):
"""
加载几何模型到树中(使用分批加载避免阻塞)
Args:
shape: OpenCASCADE TopoDS_Shape对象
geometry_name: 几何模型名称
"""
self.geometry_data = shape
self.geometry_name = geometry_name
# 更新几何节点名称
geometry_item = self.tree.topLevelItem(0)
if geometry_item:
geometry_item.setText(0, geometry_name)
self._clear_geometry_elements()
if shape is None:
return
geometry_item = self.tree.topLevelItem(0)
if geometry_item is None:
return
vertices_item = geometry_item.child(0)
edges_item = geometry_item.child(1)
faces_item = geometry_item.child(2)
bodies_item = geometry_item.child(3)
from OCC.Core.TopExp import TopExp_Explorer
from OCC.Core.TopAbs import TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE, TopAbs_SOLID
self.tree.blockSignals(True)
vertex_count = 0
edge_count = 0
face_count = 0
body_count = 0
self.tree.blockSignals(False)
vertices_item.setText(1, "加载中...")
edges_item.setText(1, "加载中...")
faces_item.setText(1, "加载中...")
bodies_item.setText(1, "加载中...")
self._batch_load_geometry_elements(shape, vertices_item, edges_item, faces_item, bodies_item)def _batch_load_geometry_elements(self, shape, vertices_item, edges_item, faces_item, bodies_item):
"""
分批加载几何元素到树中
Args:
shape: OpenCASCADE TopoDS_Shape对象
vertices_item: 顶点树项
edges_item: 边树项
faces_item: 面树项
bodies_item: 体树项
"""
from OCC.Core.TopExp import TopExp_Explorer
from OCC.Core.TopAbs import TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE, TopAbs_SOLID
BATCH_SIZE = 1000 # 每批处理的元素数量
vertex_explorer = TopExp_Explorer(shape, TopAbs_VERTEX)
edge_explorer = TopExp_Explorer(shape, TopAbs_EDGE)
face_explorer = TopExp_Explorer(shape, TopAbs_FACE)
body_explorer = TopExp_Explorer(shape, TopAbs_SOLID)
vertex_count = 0
edge_count = 0
face_count = 0
body_count = 0
def process_batch():
nonlocal vertex_count, edge_count, face_count, body_count
self.tree.blockSignals(True)
# 处理顶点
for _ in range(BATCH_SIZE):
if vertex_explorer.More():
vertex = vertex_explorer.Current()
if vertex_count < self.LAZY_LOAD_THRESHOLD:
vertex_item = QTreeWidgetItem(vertices_item)
vertex_item.setText(0, f"点_{vertex_count}")
vertex_item.setText(1, "")
vertex_item.setCheckState(0, Qt.Checked)
vertex_item.setData(0, Qt.UserRole, ("geometry", "vertices", vertex, vertex_count))
vertex_count += 1
vertex_explorer.Next()
# 处理边、面、体(类似)
# ...
# 更新计数
vertices_item.setText(1, str(vertex_count))
edges_item.setText(1, str(edge_count))
faces_item.setText(1, str(face_count))
bodies_item.setText(1, str(body_count))
self.tree.blockSignals(False)
# 如果还有元素,继续下一批
if vertex_explorer.More() or edge_explorer.More() or face_explorer.More() or body_explorer.More():
QTimer.singleShot(0, process_batch)
else:
# 加载完成,更新显示
handler = self._get_parent_handler('_update_geometry_element_display')
if handler:
handler()
QTimer.singleShot(0, process_batch)设计要点:
- 使用
QTimer.singleShot(0, ...)实现异步加载,避免阻塞UI - 每批处理1000个元素,平衡性能和响应速度
- 超过
LAZY_LOAD_THRESHOLD(10000)的元素不创建树项,仅更新计数 - 使用
blockSignals防止加载过程中触发事件 - 加载完成后通知父级更新显示
def load_mesh(self, mesh_data, mesh_name="网格"):
"""
加载网格模型到树中
Args:
mesh_data: 网格数据对象
mesh_name: 网格模型名称
"""
self.mesh_data = mesh_data
self.mesh_name = mesh_name
mesh_item = self.tree.topLevelItem(1)
if mesh_item:
mesh_item.setText(0, mesh_name)
self._clear_mesh_elements()
self._extract_mesh_elements(mesh_data)
def _extract_mesh_elements(self, mesh_data):
"""
从网格数据中提取网格元素并添加到树中
Args:
mesh_data: 网格数据对象
"""
if mesh_data is None:
return
mesh_item = self.tree.topLevelItem(1)
if mesh_item is None:
return
vertices_item = mesh_item.child(0)
edges_item = mesh_item.child(1)
faces_item = mesh_item.child(2)
bodies_item = mesh_item.child(3)
vertex_count = 0
edge_count = 0
face_count = 0
body_count = 0
self.tree.blockSignals(True)
# 提取顶点
if hasattr(mesh_data, 'node_coords'):
node_coords = mesh_data.node_coords
vertex_count = len(node_coords)
if vertex_count <= self.LAZY_LOAD_THRESHOLD:
for i, coord in enumerate(node_coords):
vertex_item = QTreeWidgetItem(vertices_item)
vertex_item.setText(0, f"点_{i}")
vertex_item.setText(1, "")
vertex_item.setCheckState(0, Qt.Checked)
vertex_item.setData(0, Qt.UserRole, ("mesh", "vertex", i, coord))
# 添加工具提示
coord_str = f"({coord[0]:.3f}, {coord[1]:.3f}"
if len(coord) > 2:
coord_str += f", {coord[2]:.3f})"
else:
coord_str += ", 0.000)"
vertex_item.setToolTip(0, f"坐标: {coord_str}")
else:
# 超过阈值,只显示前MAX_TREE_ITEMS个
for i in range(0, min(vertex_count, self.MAX_TREE_ITEMS)):
coord = node_coords[i]
vertex_item = QTreeWidgetItem(vertices_item)
vertex_item.setText(0, f"点_{i}")
vertex_item.setText(1, "")
vertex_item.setCheckState(0, Qt.Checked)
vertex_item.setData(0, Qt.UserRole, ("mesh", "vertex", i, coord))
coord_str = f"({coord[0]:.3f}, {coord[1]:.3f}"
if len(coord) > 2:
coord_str += f", {coord[2]:.3f})"
else:
coord_str += ", 0.000)"
vertex_item.setToolTip(0, f"坐标: {coord_str}")
if vertex_count > self.MAX_TREE_ITEMS:
summary_item = QTreeWidgetItem(vertices_item)
summary_item.setText(0, f"... (还有 {vertex_count - self.MAX_TREE_ITEMS} 个节点)")
summary_item.setText(1, "")
summary_item.setCheckState(0, Qt.Checked)
summary_item.setData(0, Qt.UserRole, ("mesh", "vertex_summary", self.MAX_TREE_ITEMS, vertex_count))
# 提取边、面、体(类似)
# ...
# 更新计数
vertices_item.setText(1, str(vertex_count))
edges_item.setText(1, str(edge_count))
faces_item.setText(1, str(face_count))
bodies_item.setText(1, str(body_count))
self.tree.blockSignals(False)设计要点:
- 支持多种网格数据格式(通过
hasattr检查) - 为每个节点添加工具提示,显示坐标信息
- 超过
MAX_TREE_ITEMS时显示摘要信息 - 支持节点、边、面、体四种网格元素
def load_parts(self, parts_data=None):
"""
加载部件信息到树中
Args:
parts_data: 部件数据对象(可选,如果不提供则从mesh_data中提取)
"""
if parts_data is not None:
self.parts_data = parts_data
elif self.mesh_data is not None:
self.parts_data = self.mesh_data
self._clear_parts_elements()
self._extract_parts_elements(self.parts_data)
def _extract_parts_elements(self, parts_data):
"""
从部件数据中提取部件信息并添加到树中
Args:
parts_data: 部件数据对象
"""
if parts_data is None:
return
parts_item = self.tree.topLevelItem(2)
if parts_item is None:
return
self.tree.blockSignals(True)
part_count = 0
# 处理字典格式的部件数据
if isinstance(parts_data, dict):
for part_name, part_info in parts_data.items():
if part_name in PARTS_INFO_RESERVED_KEYS:
continue
part_item = QTreeWidgetItem(parts_item)
part_item.setText(0, part_name)
# 计算部件元素数量
node_count = part_info.get('node_count', 0)
face_count = len(part_info.get('faces', []))
total_count = node_count + face_count
part_item.setText(1, str(total_count))
part_item.setCheckState(0, Qt.Checked)
part_item.setData(0, Qt.UserRole, ("parts", part_info, part_count))
# 添加工具提示
bc_type = part_info.get('bc_type', 'unknown')
part_item.setToolTip(0, f"边界类型: {bc_type}, 节点: {node_count}, 面: {face_count}")
part_count += 1
# 处理列表格式的部件数据
elif isinstance(parts_data, list):
for i, part_info in enumerate(parts_data):
part_name = part_info.get('part_name', f'部件{i}')
part_item = QTreeWidgetItem(parts_item)
part_item.setText(0, part_name)
node_count = part_info.get('node_count', 0)
face_count = len(part_info.get('faces', []))
total_count = node_count + face_count
part_item.setText(1, str(total_count))
part_item.setCheckState(0, Qt.Checked)
part_item.setData(0, Qt.UserRole, ("parts", part_info, part_count))
bc_type = part_info.get('bc_type', 'unknown')
part_item.setToolTip(0, f"边界类型: {bc_type}, 节点: {node_count}, 面: {face_count}")
part_count += 1
parts_item.setText(1, str(part_count))
self.tree.blockSignals(False)设计要点:
- 支持字典和列表两种格式的部件数据
- 跳过保留键(
PARTS_INFO_RESERVED_KEYS) - 显示部件的边界类型、节点数和面数
- 自动计算部件元素总数
def _on_item_changed(self, item, column):
"""
树项改变时的回调
Args:
item: 改变的树项
column: 改变的列
"""
if column == 0:
if self._updating_items:
return
self._updating_items = True
self._update_child_items(item, item.checkState(0))
self._update_parent_item(item)
self._updating_items = False
element_data = item.data(0, Qt.UserRole)
self._handle_visibility_change(item, element_data)设计要点:
- 使用
_updating_items标志防止递归调用 - 同步更新子项和父项状态
- 触发可见性变化事件
def _update_child_items(self, item, check_state):
"""
更新子项的选中状态
Args:
item: 父项
check_state: 选中状态
"""
self.tree.blockSignals(True)
def update_descendants(parent_item):
for i in range(parent_item.childCount()):
child = parent_item.child(i)
child.setCheckState(0, check_state)
update_descendants(child)
update_descendants(item)
self.tree.blockSignals(False)设计要点:
- 递归更新所有后代节点
- 使用
blockSignals防止触发事件
def _update_parent_item(self, item):
"""
更新父项的选中状态
Args:
item: 子项
"""
parent = item.parent()
if parent is None:
return
all_checked = True
all_unchecked = True
for i in range(parent.childCount()):
child = parent.child(i)
if child.checkState(0) == Qt.Checked:
all_unchecked = False
elif child.checkState(0) == Qt.Unchecked:
all_checked = False
else:
all_checked = False
all_unchecked = False
self.tree.blockSignals(True)
if all_checked:
parent.setCheckState(0, Qt.Checked)
elif all_unchecked:
parent.setCheckState(0, Qt.Unchecked)
else:
parent.setCheckState(0, Qt.PartiallyChecked)
self.tree.blockSignals(False)
# 递归更新上级父项
self._update_parent_item(parent)设计要点:
- 根据子项状态确定父项状态
- 支持三种状态:全选、全不选、部分选中
- 递归更新所有祖先节点
def _on_item_clicked(self, item, column):
"""
树项点击时的回调
Args:
item: 点击的树项
column: 点击的列
"""
element_data = item.data(0, Qt.UserRole)
self._handle_selection_change(item, element_data)
def _handle_selection_change(self, item, element_data):
"""
处理选择改变
Args:
item: 树项
element_data: 元素数据
"""
handler = self._get_parent_handler('on_model_tree_selected')
if not handler:
return
if isinstance(element_data, tuple) and len(element_data) >= 3:
category = element_data[0]
# 对于部件,element_data 格式为 ("parts", part_data, part_count)
if category == 'parts' and len(element_data) >= 3:
part_data = element_data[1]
element_index = element_data[2]
# 对于部件,element_type 可以是部件名称
element_type = item.text(0)
handler(category, element_type, element_index, part_data)
elif len(element_data) >= 4:
element_type = element_data[1]
element_index = element_data[3]
element_obj = element_data[2] if len(element_data) >= 3 else None
handler(category, element_type, element_index, element_obj)设计要点:
- 区分部件和其他元素的处理逻辑
- 通过
_get_parent_handler获取父级处理函数 - 传递完整的元素信息给父级
def _show_context_menu(self, position):
"""
显示右键菜单
Args:
position: 鼠标位置
"""
item = self.tree.itemAt(position)
if item is None:
return
element_data = item.data(0, Qt.UserRole)
menu = QMenu()
# 基础菜单项
show_action = QAction("显示", self.tree)
show_action.triggered.connect(lambda: self._set_item_visibility(item, True))
menu.addAction(show_action)
hide_action = QAction("隐藏", self.tree)
hide_action.triggered.connect(lambda: self._set_item_visibility(item, False))
menu.addAction(hide_action)
expand_action = QAction("展开", self.tree)
expand_action.triggered.connect(lambda: item.setExpanded(True))
menu.addAction(expand_action)
collapse_action = QAction("折叠", self.tree)
collapse_action.triggered.connect(lambda: item.setExpanded(False))
menu.addAction(collapse_action)
# 类别特定菜单项
if element_data in ("geometry", "mesh", "parts"):
menu.addSeparator()
full_action = QAction("整体显示", self.tree)
full_action.triggered.connect(lambda: self._set_display_mode("full"))
menu.addAction(full_action)
element_action = QAction("元素显示", self.tree)
element_action.triggered.connect(lambda: self._set_display_mode("elements"))
menu.addAction(element_action)
# 几何元素特定菜单项
if isinstance(element_data, tuple) and len(element_data) >= 3:
category = element_data[0]
element_type = element_data[1]
element_obj = element_data[2]
if category == 'geometry':
menu.addSeparator()
if element_type == 'vertices':
view_coords_action = QAction("查看坐标", self.tree)
view_coords_action.triggered.connect(lambda: self._show_vertex_properties(element_obj, item))
menu.addAction(view_coords_action)
elif element_type == 'edges':
view_length_action = QAction("查看长度", self.tree)
view_length_action.triggered.connect(lambda: self._show_edge_properties(element_obj, item))
menu.addAction(view_length_action)
elif element_type == 'faces':
view_area_action = QAction("查看面积", self.tree)
view_area_action.triggered.connect(lambda: self._show_face_properties(element_obj, item))
menu.addAction(view_area_action)
elif element_type == 'bodies':
view_volume_action = QAction("查看体积", self.tree)
view_volume_action.triggered.connect(lambda: self._show_solid_properties(element_obj, item))
menu.addAction(view_volume_action)
# 部件特定菜单项
if element_data == "parts":
menu.addSeparator()
create_part_action = QAction("创建部件", self.tree)
create_part_action.triggered.connect(lambda: self._create_part_dialog())
menu.addAction(create_part_action)
if not menu.isEmpty():
menu.exec_(self.tree.mapToGlobal(position))设计要点:
- 根据元素类型动态生成菜单项
- 支持显示/隐藏、展开/折叠等基础操作
- 提供元素属性查看功能
- 支持显示模式切换(整体/元素)
def _show_vertex_properties(self, vertex, item):
"""
显示顶点属性(按需计算)
Args:
vertex: OpenCASCADE TopoDS_Vertex对象
item: 树项
"""
from OCC.Core.BRep import BRep_Tool
try:
pnt = BRep_Tool.Pnt(vertex)
coords = f"({pnt.X():.6f}, {pnt.Y():.6f}, {pnt.Z():.6f})"
if hasattr(self.parent, 'log_info'):
self.parent.log_info(f"顶点坐标: {coords}")
self.tree.blockSignals(True)
item.setToolTip(0, f"坐标: ({pnt.X():.3f}, {pnt.Y():.3f}, {pnt.Z():.3f})")
self.tree.blockSignals(False)
except Exception as e:
if hasattr(self.parent, 'log_info'):
self.parent.log_info(f"获取顶点坐标失败: {str(e)}")def _show_edge_properties(self, edge, item):
"""
显示边属性(按需计算)
Args:
edge: OpenCASCADE TopoDS_Edge对象
item: 树项
"""
from OCC.Core.GCPnts import GCPnts_AbscissaPoint
from OCC.Core.BRepAdaptor import BRepAdaptor_Curve
try:
adaptor = BRepAdaptor_Curve(edge)
length = GCPnts_AbscissaPoint.Length(adaptor)
if hasattr(self.parent, 'log_info'):
self.parent.log_info(f"边长度: {length:.6f}")
self.tree.blockSignals(True)
item.setToolTip(0, f"长度: {length:.3f}")
self.tree.blockSignals(False)
except Exception as e:
if hasattr(self.parent, 'log_info'):
self.parent.log_info(f"获取边长度失败: {str(e)}")def _show_face_properties(self, face, item):
"""
显示面属性(按需计算)
Args:
face: OpenCASCADE TopoDS_Face对象
item: 树项
"""
from OCC.Core.BRepGProp import brepgprop
from OCC.Core.GProp import GProp_GProps
try:
props = GProp_GProps()
brepgprop.SurfaceProperties(face, props)
area = props.Mass()
if hasattr(self.parent, 'log_info'):
self.parent.log_info(f"面面积: {area:.6f}")
self.tree.blockSignals(True)
item.setToolTip(0, f"面积: {area:.3f}")
self.tree.blockSignals(False)
except Exception as e:
if hasattr(self.parent, 'log_info'):
self.parent.log_info(f"获取面面积失败: {str(e)}")def _show_solid_properties(self, solid, item):
"""
显示体属性(按需计算)
Args:
solid: OpenCASCADE TopoDS_Solid对象
item: 树项
"""
from OCC.Core.BRepGProp import brepgprop
from OCC.Core.GProp import GProp_GProps
try:
props = GProp_GProps()
brepgprop.VolumeProperties(solid, props)
volume = props.Mass()
if hasattr(self.parent, 'log_info'):
self.parent.log_info(f"体体积: {volume:.6f}")
self.tree.blockSignals(True)
item.setToolTip(0, f"体积: {volume:.3f}")
self.tree.blockSignals(False)
except Exception as e:
if hasattr(self.parent, 'log_info'):
self.parent.log_info(f"获取体体积失败: {str(e)}")设计要点:
- 使用OpenCASCADE的几何计算功能
- 按需计算,避免不必要的性能开销
- 计算结果同时显示在日志和工具提示中
- 异常处理确保稳定性
BATCH_SIZE = 1000 # 每批处理的元素数量
def process_batch():
nonlocal vertex_count, edge_count, face_count, body_count
self.tree.blockSignals(True)
# 处理一批元素
for _ in range(BATCH_SIZE):
if vertex_explorer.More():
# ... 处理顶点
vertex_count += 1
vertex_explorer.Next()
# ... 处理其他元素
self.tree.blockSignals(False)
# 继续下一批
if vertex_explorer.More() or edge_explorer.More() or face_explorer.More() or body_explorer.More():
QTimer.singleShot(0, process_batch)优化效果:
- 避免一次性加载大量元素导致UI冻结
- 每批处理1000个元素,平衡性能和响应速度
- 使用
QTimer实现异步加载
LAZY_LOAD_THRESHOLD = 10000 # 延迟加载阈值
if vertex_count < self.LAZY_LOAD_THRESHOLD:
# 创建树项
vertex_item = QTreeWidgetItem(vertices_item)
vertex_item.setText(0, f"点_{vertex_count}")
vertex_item.setData(0, Qt.UserRole, ("geometry", "vertices", vertex, vertex_count))
else:
# 仅更新计数,不创建树项
pass优化效果:
- 超过阈值的元素不创建树项,减少内存占用
- 仍然显示正确的元素数量
- 避免创建过多树项导致的性能问题
self.tree.blockSignals(True)
# 批量操作
self.tree.blockSignals(False)优化效果:
- 批量操作时阻塞信号,避免频繁触发事件
- 减少不必要的UI更新
- 提高批量操作的效率
self.tree.setUniformRowHeights(True)优化效果:
- 启用统一行高,优化渲染性能
- 减少布局计算开销
- 提高滚动流畅度
MAX_TREE_ITEMS = 100000 # 最大树项数量
if vertex_count > self.MAX_TREE_ITEMS:
# 使用虚拟化或摘要显示
summary_item = QTreeWidgetItem(vertices_item)
summary_item.setText(0, f"... (还有 {vertex_count - self.MAX_TREE_ITEMS} 个节点)")优化效果:
- 超过最大限制时显示摘要信息
- 避免创建过多树项
- 保持UI响应性
使用Qt.UserRole存储元素数据:
# 类别节点
item.setData(0, Qt.UserRole, "geometry")
# 元素类型节点
item.setData(0, Qt.UserRole, ("geometry", "vertices"))
# 具体元素节点
item.setData(0, Qt.UserRole, ("geometry", "vertices", vertex, vertex_count))数据格式:
- 类别节点:字符串(如
"geometry"、"mesh"、"parts") - 元素类型节点:元组(如
("geometry", "vertices")) - 具体元素节点:元组(如
("geometry", "vertices", vertex, vertex_count))
def get_visible_elements(self, category=None, element_type=None):
"""
获取可见的元素
Args:
category: 类别("geometry", "mesh", "parts"),None表示所有类别
element_type: 元素类型("vertices", "edges", "faces", "bodies"),None表示所有类型
Returns:
可见元素字典
"""
visible_elements = {}
for i in range(self.tree.topLevelItemCount()):
category_item = self.tree.topLevelItem(i)
category_data = category_item.data(0, Qt.UserRole)
if category is not None and category_data != category:
continue
if category_data is None:
continue
if category_data not in visible_elements:
visible_elements[category_data] = {}
for j in range(category_item.childCount()):
element_type_item = category_item.child(j)
element_type_data = element_type_item.data(0, Qt.UserRole)
if isinstance(element_type_data, tuple) and len(element_type_data) >= 2:
elem_category, elem_type = element_type_data[0], element_type_data[1]
if element_type is not None and elem_type != element_type:
continue
if elem_type is None:
continue
if elem_type not in visible_elements[category_data]:
visible_elements[category_data][elem_type] = []
for k in range(element_type_item.childCount()):
element_item = element_type_item.child(k)
if element_item.checkState(0) == Qt.Checked:
element_data = element_item.data(0, Qt.UserRole)
if isinstance(element_data, tuple) and len(element_data) >= 4:
elem_category_data, elem_type_data, elem_obj, elem_index = element_data
if elem_type_data == elem_type:
visible_elements[category_data][elem_type].append((elem_index, elem_obj))
return visible_elementsdef get_geometry_type_states(self):
"""获取几何类型勾选状态"""
geometry_item = self.tree.topLevelItem(0)
if geometry_item is None:
return {}
type_order = [
('vertices', 0),
('edges', 1),
('faces', 2),
('bodies', 3)
]
states = {}
for type_name, index in type_order:
child = geometry_item.child(index)
if child is not None:
states[type_name] = child.checkState(0) != Qt.Unchecked
return statesdef get_visible_parts(self):
"""
获取可见的部件
Returns:
可见部件名称列表
"""
visible_parts = []
parts_item = self.tree.topLevelItem(2)
if parts_item is None:
return visible_parts
for i in range(parts_item.childCount()):
part_item = parts_item.child(i)
if part_item.checkState(0) == Qt.Checked:
part_name = part_item.text(0)
visible_parts.append(part_name)
return visible_partsdef set_element_visibility(self, category, element_type, element_index, visible):
"""
设置特定元素的可见性
Args:
category: 类别("geometry", "mesh", "parts")
element_type: 元素类型("vertices", "edges", "faces", "bodies")
element_index: 元素索引
visible: 是否可见
"""
for i in range(self.tree.topLevelItemCount()):
category_item = self.tree.topLevelItem(i)
category_data = category_item.data(0, Qt.UserRole)
if category_data != category:
continue
for j in range(category_item.childCount()):
element_type_item = category_item.child(j)
element_type_data = element_type_item.data(0, Qt.UserRole)
if isinstance(element_type_data, tuple) and len(element_type_data) >= 2:
elem_category, elem_type = element_type_data[0], element_type_data[1]
if elem_type != element_type:
continue
for k in range(element_type_item.childCount()):
element_item = element_type_item.child(k)
element_data = element_item.data(0, Qt.UserRole)
if isinstance(element_data, tuple) and len(element_data) >= 4:
elem_category, elem_type, elem_obj, elem_index_data = element_data
if elem_index_data == element_index:
element_item.setCheckState(0, Qt.Checked if visible else Qt.Unchecked)
returndef set_category_visibility(self, category, element_type, visible):
"""
设置类别的可见性
Args:
category: 类别("geometry", "mesh", "parts")
element_type: 元素类型("vertices", "edges", "faces", "bodies")
visible: 是否可见
"""
for i in range(self.tree.topLevelItemCount()):
category_item = self.tree.topLevelItem(i)
category_data = category_item.data(0, Qt.UserRole)
if category_data != category:
continue
for j in range(category_item.childCount()):
element_type_item = category_item.child(j)
element_type_data = element_type_item.data(0, Qt.UserRole)
if isinstance(element_type_data, tuple) and len(element_type_data) >= 2:
elem_category, elem_type = element_type_data[0], element_type_data[1]
if elem_type != element_type:
continue
element_type_item.setCheckState(0, Qt.Checked if visible else Qt.Unchecked)
return在gui_main.py中,模型树组件被集成到主窗口的左侧面板:
def _create_model_tree_widget(self):
"""创建模型树组件"""
self.model_tree_widget = ModelTreeWidget(parent=self)
model_tree_frame_container = QGroupBox("模型树")
model_tree_layout = QVBoxLayout(model_tree_frame_container)
model_tree_layout.setSpacing(2)
model_tree_layout.addWidget(self.model_tree_widget.widget)
self.model_tree_widget.widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
if hasattr(self, 'left_splitter'):
self.left_splitter.addWidget(model_tree_frame_container)模型树通过_get_parent_handler方法与部件管理器交互:
def _get_parent_handler(self, handler_name):
"""获取父级处理函数(优先使用part_manager)"""
manager = getattr(self.parent, 'part_manager', None)
if manager and hasattr(manager, handler_name):
return getattr(manager, handler_name)
if hasattr(self.parent, handler_name):
return getattr(self.parent, handler_name)
return None模型树通过以下事件与父窗口通信:
- 可见性变化事件:
on_model_tree_visibility_changed - 选择变化事件:
on_model_tree_selected - 显示模式变化事件:
on_display_mode_changed - 部件创建事件:
on_part_created
def on_model_tree_visibility_changed(self, *args):
"""模型树可见性改变的回调"""
if len(args) == 2:
category, visible = args
if category == 'geometry':
self._update_geometry_element_display()
elif category == 'mesh':
self._update_mesh_part_display()
elif category == 'parts':
self.refresh_display_all_parts()# 加载OpenCASCADE几何模型
shape = ... # TopoDS_Shape对象
model_tree_widget.load_geometry(shape, "立方体")# 加载网格数据
mesh_data = ... # 网格数据对象
model_tree_widget.load_mesh(mesh_data, "网格1")# 加载部件信息
parts_data = {
"wall1": {
"part_name": "wall1",
"bc_type": "wall",
"node_count": 100,
"faces": [...]
},
"inlet": {
"part_name": "inlet",
"bc_type": "inlet",
"node_count": 50,
"faces": [...]
}
}
model_tree_widget.load_parts(parts_data)# 获取所有可见的几何元素
visible_elements = model_tree_widget.get_visible_elements(category="geometry")
# 获取可见的网格顶点
visible_vertices = model_tree_widget.get_visible_elements(category="mesh", element_type="vertices")
# 获取可见的部件
visible_parts = model_tree_widget.get_visible_parts()# 设置特定元素的可见性
model_tree_widget.set_element_visibility("geometry", "vertices", 0, False)
# 设置整个类别的可见性
model_tree_widget.set_category_visibility("mesh", "faces", True)模型树组件通过信号机制实现观察者模式:
- 主题(Subject):模型树组件
- 观察者(Observer):父窗口(PyMeshGenGUI)
- 事件:可见性变化、选择变化、显示模式变化
# 模型树发出事件
handler = self._get_parent_handler('on_model_tree_visibility_changed')
if handler:
handler(category, element_type, element_index, visible)
# 父窗口处理事件
def on_model_tree_visibility_changed(self, *args):
# 更新3D视图
self._update_3d_display()不同类型的数据加载使用不同的策略:
- 几何数据加载策略:使用OpenCASCADE探索器
- 网格数据加载策略:直接访问网格数据属性
- 部件数据加载策略:解析字典或列表格式
def load_geometry(self, shape, geometry_name="几何"):
# 几何加载策略
self._batch_load_geometry_elements(...)
def load_mesh(self, mesh_data, mesh_name="网格"):
# 网格加载策略
self._extract_mesh_elements(mesh_data)
def load_parts(self, parts_data=None):
# 部件加载策略
self._extract_parts_elements(parts_data)元素提取使用模板方法模式:
def _extract_geometry_elements(self, shape):
# 模板方法
vertex_count = self._extract_vertices(shape, vertices_item)
edge_count = self._extract_edges(shape, edges_item)
face_count = self._extract_faces(shape, faces_item)
body_count = self._extract_bodies(shape, bodies_item)
# 更新计数
vertices_item.setText(1, str(vertex_count))
edges_item.setText(1, str(edge_count))
faces_item.setText(1, str(face_count))
bodies_item.setText(1, str(body_count))要支持新的数据类型,需要:
- 在
_init_tree_structure中添加新的顶级节点 - 实现相应的
load_*方法 - 实现
_extract_*_elements方法 - 更新事件处理逻辑
要支持新的元素类型,需要:
- 在相应的类别节点下添加新的子节点
- 在
_extract_*_elements中添加提取逻辑 - 在右键菜单中添加相应的操作
- 更新属性查看方法
可以通过重绘代理实现自定义渲染:
class CustomTreeItemDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
# 自定义绘制逻辑
pass
# 应用自定义代理
self.tree.setItemDelegate(CustomTreeItemDelegate())通过回调机制支持功能扩展:
def _get_parent_handler(self, handler_name):
"""获取父级处理函数(优先使用part_manager)"""
manager = getattr(self.parent, 'part_manager', None)
if manager and hasattr(manager, handler_name):
return getattr(manager, handler_name)
if hasattr(self.parent, handler_name):
return getattr(self.parent, handler_name)
return None- 测试树结构初始化
- 测试数据加载功能
- 测试可见性控制
- 测试事件处理
- 测试查询功能
- 测试大规模数据加载性能
- 测试分批加载效果
- 测试内存占用
- 测试UI响应速度
- 测试与主窗口的集成
- 测试与部件管理器的集成
- 测试与3D视图的集成
- 测试完整工作流程
- 小模型:几十个元素,确保叶子节点可正常创建、点击高亮可用
- 中模型:1e4 量级,验证分批加载无冻结、数量正确
- 大模型:>1e5,验证阈值策略生效、UI 不崩溃
- 勾选传播:父子勾选联动正确,不出现递归卡死
- 右键菜单:不同节点类型显示不同菜单项;handler 缺失时安全降级
风险:
- OCC 对象生命周期受 Python 包装层/底层句柄影响
- 若 shape 被释放或替换,树项中的引用可能失效
建议:
- 更稳妥:只存储可序列化的
element_id(例如 hash / index),对象由"几何仓库"统一管理。
QTreeWidget 对轻量数据简单好用,但超大数据量(>1e5)时性能瓶颈明显。
当需求上升到"百万级元素可视化"时,建议迁移到:
QTreeView + QAbstractItemModel(真正的虚拟化/按需取数)
当超过 LAZY_LOAD_THRESHOLD 不创建叶子节点时:
- 勾选类目节点仍可控制全体元素显示/隐藏
- 但用户无法点选某个具体元素
这需要在 UI 上给出明确提示,例如:
- 展开节点时显示一个占位子项:"元素数量过多,已启用懒加载/不显示明细"
- 或提供搜索/过滤/分页
问题:
- 大量树项的创建会占用较多内存
- OCC对象引用可能导致内存泄漏
建议:
- 及时清理不再使用的树项
- 使用弱引用存储对象
- 实现对象池管理
问题:
- 分批加载使用QTimer在主线程执行
- 大量计算可能阻塞UI
建议:
- 将计算密集型任务移到后台线程
- 使用QThread或QThreadPool
- 通过信号槽机制更新UI
ModelTreeWidget.__init__():初始化、创建控件、建立默认三层结构_create_tree_widget():QTreeWidget 基础属性、信号连接、菜单模式_setup_ui():设置UI布局_init_tree_structure():构建固定顶层与二级节点,并设置UserRole标签load_geometry():几何加载入口,清空旧数据并触发分批加载_batch_load_geometry_elements():分批遍历 OCC 拓扑,按阈值创建叶子或只计数load_mesh():网格加载入口_extract_mesh_elements():提取网格元素信息load_parts():部件加载入口_extract_parts_elements():提取部件信息_on_item_changed():处理复选框状态变化_on_item_clicked():处理项点击事件_handle_visibility_change():处理可见性改变_handle_selection_change():处理选择改变_show_context_menu():显示右键菜单_show_vertex_properties():显示顶点属性_show_edge_properties():显示边属性_show_face_properties():显示面属性_show_solid_properties():显示体属性_update_child_items():更新子项的选中状态_update_parent_item():更新父项的选中状态get_visible_elements():获取可见的元素get_geometry_type_states():获取几何类型勾选状态get_visible_parts():获取可见的部件set_element_visibility():设置特定元素的可见性set_category_visibility():设置类别的可见性set_all_parts_visible():设置所有部件的可见性clear():清空树_get_parent_handler():通过 parent/part_manager 解耦调用实际业务处理逻辑
- 增加"搜索框 + 过滤条件":按名称/ID/类型快速定位
- 为大数据启用"分页/虚拟节点":扩展懒加载策略
- 统一定义
UserRole payload的数据类(dataclass)并提供序列化/调试输出
- 引入"选择集(Selection Set)"节点:保存用户的一组选择
- 结合 3D 视图:双向同步(3D 选中 -> 树定位;树选中 -> 3D 高亮)
- 支持拖放操作:在树中拖动元素进行重新组织
- 迁移到
QTreeView + QAbstractItemModel:支持百万级元素 - 实现真正的虚拟化:按需加载和渲染
- 支持多选和批量操作:提高工作效率
- 添加撤销/重做功能:支持操作历史
- OCC:OpenCASCADE,几何内核
- TopoDS_Shape:OCC 的形体对象
- TopExp_Explorer:OCC 拓扑遍历器
- 批处理/分批加载(Batch Load):将大量 UI 创建工作拆分到多个事件循环周期
- 懒加载(Lazy Load):仅在需要显示时创建明细节点
- 虚拟化(Virtualization):只渲染可见项,按需创建树项
- UserRole:Qt 中用于存储自定义数据的角色
- CheckState:Qt 中复选框的状态(Checked/Unchecked/PartiallyChecked)
- Callback/Handler:回调函数/处理函数
- Signal/Slot:Qt 的信号槽机制
- QTreeWidget:Qt 的树形控件(基于项)
- QTreeView:Qt 的树形视图(基于模型)
- QAbstractItemModel:Qt 的抽象项模型
模型树组件是PyMeshGen GUI系统的核心组件之一,提供了统一的数据展示和管理界面。该组件采用三层架构设计,支持几何、网格、部件三种数据类型,实现了高效的元素可见性控制和丰富的交互功能。
- 统一架构:三种数据类型(几何、网格、部件)统一管理
- 高性能:分批加载、延迟加载、虚拟化等优化策略
- 易扩展:插件化架构、回调机制、设计模式应用
- 用户友好:直观的交互方式、丰富的功能、良好的反馈
- 异步加载:使用QTimer实现非阻塞的分批加载
- 智能阈值:根据数据规模自动选择加载策略
- 事件驱动:通过信号槽机制实现松耦合
- 设计模式:观察者模式、策略模式、模板方法模式的综合应用
模型树组件为PyMeshGen提供了:
- 直观的数据管理界面
- 高效的元素可见性控制
- 灵活的扩展机制
- 良好的用户体验
该组件的设计和实现充分考虑了性能优化和用户体验,为PyMeshGen的整体架构提供了坚实的基础。未来可以通过虚拟化、懒加载、缓存等技术进一步提升性能,通过支持更多数据类型和元素类型来扩展功能。
文档结束