
在三维网格生成系统中,用户需要管理的数据极其复杂:
如果没有一个统一的管理界面,用户需要在多个窗口间切换,操作效率极低。模型树的核心价值在于:
模型树采用固定的三层顶层结构:
模型树根节点├── 几何 (Geometry)│ ├── 点 (Vertices)│ ├── 线 (Edges)│ ├── 面 (Faces)│ └── 体 (Bodies)├── 网格 (Mesh)│ ├── 点 (Vertices)│ ├── 线 (Edges)│ ├── 面 (Faces)│ └── 体 (Bodies)└── 部件 (Parts) ├── 部件1 ├── 部件2 └── ...这种设计的优势:
ModelTreeWidget并非直接继承QWidget,而是一个"组件封装类":
classModelTreeWidget:def__init__(self, parent=None): self.parent = parent self.widget = QWidget() # 容器,可嵌入布局 self.tree = QTreeWidget() # 实际的树形控件# 数据引用 self.geometry_data = None self.mesh_data = None self.parts_data = None# 性能控制 self.MAX_TREE_ITEMS = 100000 self.LAZY_LOAD_THRESHOLD = 10000这种封装的好处:
model_tree.widget加到布局即可使用Qt.UserRole存储节点语义信息:
# 顶层节点geometry_item.setData(0, Qt.UserRole, "geometry")mesh_item.setData(0, Qt.UserRole, "mesh")parts_item.setData(0, Qt.UserRole, "parts")# 二级节点vertices_item.setData(0, Qt.UserRole, ("geometry", "vertices"))edges_item.setData(0, Qt.UserRole, ("geometry", "edges"))# 叶子节点vertex_item.setData(0, Qt.UserRole, ("geometry", "vertices", vertex_ref, index))这种数据结构约定使得:
面对大规模数据,一次性加载会导致UI冻结。解决方案是分批加载:
def_batch_load_geometry_elements(self, shape, vertices_item, edges_item, ...): BATCH_SIZE = 1000# 每批处理1000个元素 vertex_explorer = TopExp_Explorer(shape, TopAbs_VERTEX) vertex_count = 0defprocess_batch(): 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.setData(0, Qt.UserRole, ("geometry", "vertices", vertex, vertex_count)) vertex_count += 1 vertex_explorer.Next() vertices_item.setText(1, str(vertex_count)) self.tree.blockSignals(False)# 如果还有元素,继续下一批if vertex_explorer.More(): QTimer.singleShot(0, process_batch) process_batch()关键点:
QTimer.singleShot(0, process_batch)将下一批处理推迟到事件循环blockSignals(True/False)避免触发递归事件实现勾选状态的父子联动:
def_on_item_changed(self, item, column):if self._updating_items:return self._updating_items = True check_state = item.checkState(0)# 向下传播:更新所有子节点if item.childCount() > 0: self._update_child_items(item, check_state)# 向上回填:更新父节点状态 parent = item.parent()if parent: self._update_parent_item(parent) self._updating_items = False# 触发业务逻辑 self._handle_visibility_change(item)设计要点:
_updating_items标志避免递归调用模型树不直接执行业务逻辑,而是通过反射调用父窗口的方法:
def_show_context_menu(self, position): item = self.tree.itemAt(position)ifnot item:return menu = QMenu() element_data = item.data(0, Qt.UserRole)if isinstance(element_data, tuple) and len(element_data) >= 2: domain, category = element_data[0], element_data[1]if domain == "parts":# 部件相关菜单 handler = self._get_parent_handler("create_part")if handler: action = menu.addAction("创建部件") action.triggered.connect(handler)elif domain == "geometry":# 几何相关菜单 handler = self._get_parent_handler("locate_element")if handler: action = menu.addAction("定位") action.triggered.connect(lambda: handler(element_data)) menu.exec_(self.tree.mapToGlobal(position))def_get_parent_handler(self, handler_name):"""反射查找父窗口的handler方法"""if hasattr(self.parent, "part_manager"): part_manager = self.parent.part_managerif hasattr(part_manager, handler_name):return getattr(part_manager, handler_name)if hasattr(self.parent, handler_name):return getattr(self.parent, handler_name)returnNone这种设计实现了:
对于超过阈值的元素,只显示数量而不创建叶子节点:
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, ...)else:# 只显示数量,不创建叶子节点pass当元素数量超过最大限制时,显示摘要信息:
if total_elements > self.MAX_TREE_ITEMS: summary_item = QTreeWidgetItem(category_item) summary_item.setText(0, f"(共{total_elements}个元素,已折叠)") summary_item.setText(1, "")def_setup_ui(self): self.tree.setAlternatingRowColors(True) # 交替行颜色 self.tree.setUniformRowHeights(True) # 统一行高,优化渲染def_handle_visibility_change(self, item): element_data = item.data(0, Qt.UserRole)if isinstance(element_data, tuple) and len(element_data) >= 4: domain, category, obj_ref, index = element_data# 通知3D视图更新显示 handler = self._get_parent_handler("update_element_visibility")if handler: visible = item.checkState(0) == Qt.Checked handler(domain, category, index, visible)def_on_item_clicked(self, item, column): element_data = item.data(0, Qt.UserRole)if isinstance(element_data, tuple) and len(element_data) >= 4: domain, category, obj_ref, index = element_data# 通知属性面板显示详细信息 handler = self._get_parent_handler("show_element_properties")if handler: handler(domain, category, obj_ref, index)ModelTreeWidget作为外观,封装了复杂的树形控件操作:
# 用户只需调用简单的方法model_tree.load_geometry(shape, "几何模型")model_tree.load_mesh(mesh_data, "网格数据")model_tree.load_parts(parts_data)通过信号槽机制实现松耦合:
self.tree.itemChanged.connect(self._on_item_changed)self.tree.itemClicked.connect(self._on_item_clicked)self.tree.customContextMenuRequested.connect(self._show_context_menu)不同类型的数据使用不同的加载策略:
defload_geometry(self, shape, name):# 几何数据加载策略defload_mesh(self, mesh_data, name):# 网格数据加载策略defload_parts(self, parts_data):# 部件数据加载策略







PyMeshGen GUI的模型树组件通过以下设计实现了高性能、易扩展的三维数据管理:
这套设计不仅适用于网格生成系统,也可以应用到其他需要管理复杂数据的GUI应用中。
👇点击左下方“阅读原文”访问项目!
全文结束,感谢观看,创作不易。
😁😁
欢迎关注、留言、点赞、分享、推荐!
👇👇
【往期回顾】
我用DeepSeek-V3.1做了一下PDE编程求解,来看看结果怎么样