feat: 完善参数化编辑和孔修改稳定性

This commit is contained in:
2026-07-28 18:30:58 +08:00
parent 7c263d1e96
commit 720c2dc970
13 changed files with 2190 additions and 690 deletions
+109 -44
View File
@@ -21,7 +21,24 @@ from .ui_helpers import * # noqa: F403
from .workers import EditWorker, LoadWorker, ScanWorker
VIEW_BACKGROUND_COLOR = (226 / 255.0, 238 / 255.0, 247 / 255.0)
class WindowCoreMixin:
@Slot(object)
def _run_ui_task(self, callback) -> None:
callback()
def _is_ui_thread(self) -> bool:
return QThread.currentThread() == self.thread()
def _invoke_on_ui_thread(self, callback) -> None:
if self._is_ui_thread():
callback()
return
if hasattr(self, "ui_task_requested"):
self.ui_task_requested.emit(callback)
def eventFilter(self, watched, event):
if watched is getattr(self, "vtk_widget", None):
event_type = event.type()
@@ -37,7 +54,7 @@ class WindowCoreMixin:
def _build_vtk(self) -> None:
self.renderer = vtk.vtkRenderer()
self.renderer.SetBackground(0.11, 0.13, 0.15)
self.renderer.SetBackground(*VIEW_BACKGROUND_COLOR)
self.render_window = self.vtk_widget.GetRenderWindow()
self.render_window.AddRenderer(self.renderer)
@@ -185,7 +202,10 @@ class WindowCoreMixin:
self._clear_history()
if hasattr(self, "measure_text"):
self.clear_measurement()
self.path_label.setText(str(self.step_path))
path_text = str(self.step_path)
self.path_label.setText(path_text)
self.path_label.setToolTip(path_text)
self.path_label.setCursorPosition(0)
self._populate_part_tree()
self._reset_selection()
model_polydata = result.get("model_polydata")
@@ -314,10 +334,13 @@ class WindowCoreMixin:
self.clear_diff_preview(render=False)
if hasattr(self, "history_list"):
self.history_list.clear()
if hasattr(self, "export_all_button"):
if hasattr(self, "mode_combo"):
self._update_action_states()
def _refresh_history_list(self) -> None:
if not hasattr(self, "history_list"):
self._update_action_states()
return
was_blocked = self.history_list.blockSignals(True)
try:
self.history_list.clear()
@@ -346,6 +369,9 @@ class WindowCoreMixin:
return
if self._edit_busy("编辑计算中,暂时不能导出差异报告。"):
return
if not hasattr(self, "history_list"):
QMessageBox.information(self, "操作历史未启用", "当前界面没有构建操作历史面板。")
return
row = self.history_list.currentRow()
if row < 0 or row >= len(self.operation_history):
QMessageBox.information(self, "未选择历史记录", "请先选择一条操作历史。")
@@ -436,6 +462,8 @@ class WindowCoreMixin:
self.set_plain_info(f"已导出编辑历史:{target}\n\n记录数:{len(records)}")
def _populate_part_tree(self) -> None:
if not hasattr(self, "part_tree"):
return
was_blocked = self.part_tree.blockSignals(True)
try:
self.part_tree.clear()
@@ -501,11 +529,11 @@ class WindowCoreMixin:
def _part_tree_part_tooltip(self, part_id: int) -> str:
if self.model is None:
return f"Part ID: {part_id}"
return f"零件 ID: {part_id}"
try:
info = self.model.part_info(part_id)
except Exception:
return f"Part ID: {part_id}"
return f"零件 ID: {part_id}"
lines = [
f"ID: {part_id}",
f"类型: {_part_tree_kind_label(str(info.get('kind', '')))}",
@@ -560,7 +588,7 @@ class WindowCoreMixin:
if self.selected_kind == "part" and self.selected_part_id is not None:
part_ids = [self.selected_part_id]
label = f"Part {self.selected_part_id}"
label = f"零件 {self.selected_part_id}"
elif self.selected_kind == "solid" and self.selected_solid_id is not None:
face_ids = [index for index, solid_id in enumerate(self.model.face_solid_ids) if solid_id == self.selected_solid_id]
edge_ids = self.model.edge_ids_for_solid(self.selected_solid_id)
@@ -569,7 +597,7 @@ class WindowCoreMixin:
info = self.model.feature_info(self.selected_face_id)
face_ids = _int_values(info.get("feature_highlight_face_ids")) or [self.selected_face_id]
edge_ids = _int_values(info.get("feature_boundary_edge_ids"))
label = f"Feature Face {self.selected_face_id}"
label = f"特征 Face {self.selected_face_id}"
elif self.selected_kind == "face" and self.selected_face_id is not None:
face_ids = self.model.connected_same_domain_face_ids(self.selected_face_id) or [self.selected_face_id]
edge_ids = self.model.face_region_boundary_edge_ids(self.selected_face_id)
@@ -580,7 +608,7 @@ class WindowCoreMixin:
edge_ids = [self.selected_edge_id]
label = f"Edge {self.selected_edge_id}"
else:
QMessageBox.information(self, "未选择对象", "请先选择 Part、Solid、Face、Edge 或 Feature")
QMessageBox.information(self, "未选择对象", "请先选择零件、Solid、Face、Edge 或特征")
return
model_polydata = self.model.build_face_polydata(face_ids=face_ids, part_ids=part_ids)
@@ -610,7 +638,7 @@ class WindowCoreMixin:
return
bounds = self._selected_focus_bounds()
if bounds is None:
QMessageBox.information(self, "未选择对象", "请先选择 Part、Solid、Face、Edge 或 Feature")
QMessageBox.information(self, "未选择对象", "请先选择零件、Solid、Face、Edge 或特征")
return
self._fit_camera_to_bounds(bounds)
self.render_window.Render()
@@ -758,7 +786,7 @@ class WindowCoreMixin:
def _rebuild_scene_from_polydata(self, model_polydata, edge_polydata, reset_camera: bool = False) -> None:
self._clear_overlay_polydata_cache()
self.renderer.RemoveAllViewProps()
self.renderer.SetBackground(0.11, 0.13, 0.15)
self.renderer.SetBackground(*VIEW_BACKGROUND_COLOR)
self.scene_isolated = False
self.model_polydata = _smooth_surface_polydata(model_polydata)
@@ -808,10 +836,20 @@ class WindowCoreMixin:
def _on_mode_changed(self, mode: str) -> None:
self._clear_hover(render=True)
if hasattr(self, "id_mode_display"):
self.id_mode_display.setText(mode)
if hasattr(self, "_update_id_select_title"):
self._update_id_select_title(mode)
def on_left_click(self, _obj, _event) -> None:
try:
x, y = self.interactor.GetEventPosition()
except Exception:
x, y = 0, 0
if not self._is_ui_thread():
self._invoke_on_ui_thread(lambda x=int(x), y=int(y): self._handle_left_click(x, y))
return
self._handle_left_click(int(x), int(y))
def _handle_left_click(self, x: int, y: int) -> None:
self.pointer_button_down = True
self.pending_hover_position = None
self.last_hover_pick_position = None
@@ -827,8 +865,7 @@ class WindowCoreMixin:
return
if self.model is None:
return
mode = self.mode_combo.currentText()
x, y = self.interactor.GetEventPosition()
mode = self._current_selection_mode()
target = self._pick_selection_target(mode, x, y)
if target is None:
self.statusBar().showMessage("未选中对象")
@@ -838,6 +875,12 @@ class WindowCoreMixin:
self._select_pick_target(target)
def on_pointer_button_press(self, _obj, _event) -> None:
if not self._is_ui_thread():
self._invoke_on_ui_thread(self._handle_pointer_button_press)
return
self._handle_pointer_button_press()
def _handle_pointer_button_press(self) -> None:
self.pointer_button_down = True
self.pending_hover_position = None
self.last_hover_pick_position = None
@@ -845,11 +888,27 @@ class WindowCoreMixin:
self._clear_hover(render=True)
def on_pointer_button_release(self, _obj, _event) -> None:
if not self._is_ui_thread():
self._invoke_on_ui_thread(self._handle_pointer_button_release)
return
self._handle_pointer_button_release()
def _handle_pointer_button_release(self) -> None:
self.pointer_button_down = False
self.pending_hover_position = None
self.last_hover_pick_position = None
def on_mouse_move(self, _obj, _event) -> None:
try:
x, y = self.interactor.GetEventPosition()
except Exception:
x, y = 0, 0
if not self._is_ui_thread():
self._invoke_on_ui_thread(lambda x=int(x), y=int(y): self._handle_mouse_move(x, y))
return
self._handle_mouse_move(int(x), int(y))
def _handle_mouse_move(self, x: int, y: int) -> None:
if QApplication.mouseButtons() != Qt.MouseButton.NoButton:
self.pointer_button_down = True
return
@@ -863,8 +922,7 @@ class WindowCoreMixin:
):
self._clear_hover(render=True)
return
x, y = self.interactor.GetEventPosition()
self._queue_hover_position(int(x), int(y))
self._queue_hover_position(x, y)
def _queue_hover_from_qt_event(self, event) -> None:
if (
@@ -883,6 +941,9 @@ class WindowCoreMixin:
self._queue_hover_position(x, y)
def _queue_hover_position(self, x: int, y: int) -> None:
if not self._is_ui_thread():
self._invoke_on_ui_thread(lambda x=int(x), y=int(y): self._queue_hover_position(x, y))
return
position = (int(x), int(y))
threshold = int(getattr(self, "hover_move_threshold_px", 0) or 0)
if threshold > 0:
@@ -909,7 +970,7 @@ class WindowCoreMixin:
return
x, y = self.pending_hover_position
self.last_hover_pick_position = (x, y)
target = self._pick_selection_target(self.mode_combo.currentText(), x, y)
target = self._pick_selection_target(self._current_selection_mode(), x, y)
self._show_hover_target(target)
def _pick_selection_target(self, mode: str, x: int, y: int) -> dict[str, object] | None:
@@ -1101,7 +1162,7 @@ class WindowCoreMixin:
self.select_edge(edge_id, pick_position=pick_position)
def on_part_tree_select(self, current: QTreeWidgetItem | None, _previous: QTreeWidgetItem | None) -> None:
if self._edit_busy("编辑计算中,暂时不能切换零件树选择。"):
if self._edit_busy("编辑计算中,暂时不能切换结构树选择。"):
return
if current is None:
return
@@ -1110,14 +1171,16 @@ class WindowCoreMixin:
if node_kind == "solid" and target_id is not None:
part_id = current.data(0, PART_TREE_PART_ID_ROLE)
if part_id is not None:
self.mode_combo.setCurrentText("Solid")
self._set_selection_mode("Solid")
self.select_solid(int(target_id), int(part_id))
return
if node_kind in {"part", "assembly"} and target_id is not None:
self.mode_combo.setCurrentText("Part")
self._set_selection_mode("Part")
self.select_part(int(target_id))
def on_cylinder_row_clicked(self, row: int, _column: int) -> None:
if not hasattr(self, "cylinder_table"):
return
if self._edit_busy("编辑计算中,暂时不能切换圆柱面候选。"):
return
item = self.cylinder_table.item(row, 0)
@@ -1126,10 +1189,12 @@ class WindowCoreMixin:
face_id = item.data(Qt.UserRole)
if face_id is None:
return
self.mode_combo.setCurrentText("Feature")
self._set_selection_mode("Feature")
self.select_feature(int(face_id))
def on_editable_row_clicked(self, row: int, _column: int) -> None:
if not hasattr(self, "editable_table"):
return
if self._edit_busy("编辑计算中,暂时不能切换可编辑对象。"):
return
item = self.editable_table.item(row, 0)
@@ -1141,51 +1206,51 @@ class WindowCoreMixin:
if target_id is None:
return
if action == "resize_cylinder":
self.mode_combo.setCurrentText("Feature")
self._set_selection_mode("Feature")
self.select_feature(int(target_id))
self.statusBar().showMessage(f"已选择可调整孔径候选Face {target_id}")
elif action == "resize_boss":
self.mode_combo.setCurrentText("Feature")
self._set_selection_mode("Feature")
self.select_feature(int(target_id))
self.statusBar().showMessage(f"已选择可调整凸台直径候选Face {target_id}")
elif action == "resize_slot_width":
self.mode_combo.setCurrentText("Feature")
self._set_selection_mode("Feature")
self.select_feature(int(target_id))
self.statusBar().showMessage(f"已选择可调整槽/半孔宽度候选Face {target_id}")
elif action == "resize_shell_thickness":
self.mode_combo.setCurrentText("Feature")
self._set_selection_mode("Feature")
self.select_feature(int(target_id))
self.statusBar().showMessage(f"已选择可调整薄壁厚度候选Face {target_id}")
elif action == "suppress_cylinder":
self.mode_combo.setCurrentText("Feature")
self._set_selection_mode("Feature")
self.select_feature(int(target_id))
self.statusBar().showMessage(f"已选择可封堵圆柱孔Face {target_id}")
elif action == "resize_depth":
self.mode_combo.setCurrentText("Feature")
self._set_selection_mode("Feature")
self.select_feature(int(target_id))
self.statusBar().showMessage(f"已选择可调整盲孔深度候选Face {target_id}")
elif action == "inspect_existing_fillet":
self.mode_combo.setCurrentText("Feature")
self._set_selection_mode("Feature")
self.select_feature(int(target_id))
self.statusBar().showMessage(f"已选择已有圆角/倒圆候选Face {target_id}")
elif action == "fillet_edge":
self.mode_combo.setCurrentText("Edge")
self._set_selection_mode("Edge")
self.select_edge(int(target_id))
self.statusBar().showMessage(f"已选择可添加圆角Edge {target_id}")
elif action == "chamfer_edge":
self.mode_combo.setCurrentText("Edge")
self._set_selection_mode("Edge")
self.select_edge(int(target_id))
self.statusBar().showMessage(f"已选择可添加倒角Edge {target_id}")
elif action == "resize_edge_length":
self.mode_combo.setCurrentText("Edge")
self._set_selection_mode("Edge")
self.select_edge(int(target_id))
self.statusBar().showMessage(f"已选择可尝试调整长度的Edge {target_id}")
elif target_kind == "edge":
self.mode_combo.setCurrentText("Edge")
self._set_selection_mode("Edge")
self.select_edge(int(target_id))
self.statusBar().showMessage(f"已选择Edge {target_id}")
else:
self.mode_combo.setCurrentText("Face")
self._set_selection_mode("Face")
self.select_face(int(target_id))
self.statusBar().showMessage(f"已选择可推拉平面Face {target_id}")
@@ -1197,38 +1262,38 @@ class WindowCoreMixin:
try:
target_id = int(self.id_input.text())
except ValueError:
QMessageBox.information(self, "ID 无效", "请输入整数 ID。Part ID 从 1 开始,Solid/Face/Edge ID 从 0 开始。")
QMessageBox.information(self, "ID 无效", "请输入整数 ID。零件 ID 从 1 开始,Solid/Face/Edge ID 从 0 开始。")
return
kind = kind or self.mode_combo.currentText()
kind = _selection_mode_value(kind or self._current_selection_mode())
try:
if kind == "Part":
if self.model.part_by_id(target_id) is None:
raise ValueError(f"不存在 Part ID {target_id}")
self.mode_combo.setCurrentText("Part")
raise ValueError(f"不存在零件 ID {target_id}")
self._set_selection_mode("Part")
self.select_part(target_id)
elif kind == "Solid":
if target_id < 0 or target_id >= len(self.model.solids):
raise ValueError(f"不存在 Solid ID {target_id}")
part_id = self.model.solids[target_id][0]
self.mode_combo.setCurrentText("Solid")
self._set_selection_mode("Solid")
self.select_solid(target_id, part_id)
elif kind == "Face":
resolved_face_id = self.model.resolve_face_selection_id(target_id)
if resolved_face_id is None:
raise ValueError(f"不存在 Face/逻辑 Face ID {target_id}")
self.mode_combo.setCurrentText("Face")
self._set_selection_mode("Face")
self.select_face(resolved_face_id)
elif kind == "Feature":
resolved_face_id = self.model.resolve_face_selection_id(target_id)
if resolved_face_id is None:
raise ValueError(f"不存在 Feature 来源 Face/逻辑 Face ID {target_id}")
self.mode_combo.setCurrentText("Feature")
raise ValueError(f"不存在特征来源 Face/逻辑 Face ID {target_id}")
self._set_selection_mode("Feature")
self.select_feature(resolved_face_id)
elif kind == "Edge":
if target_id < 0 or target_id >= len(self.model.edges):
raise ValueError(f"不存在 Edge ID {target_id}")
self.mode_combo.setCurrentText("Edge")
self._set_selection_mode("Edge")
self.select_edge(target_id)
self.last_id_kind = kind
except ValueError as exc:
@@ -1333,7 +1398,7 @@ class WindowCoreMixin:
self._show_pick_marker(pick_position)
self._sync_id_picker("Feature", face_id)
self.set_info(self._with_pick_info(info, pick_position))
feature_type = str(info.get("feature_type", "局部特征候选"))
feature_type = str(info.get("feature_type", "特征候选"))
self._update_action_states()
self.statusBar().showMessage(self._selection_status(f"已选择 {feature_type},来源Face {face_id}", pick_position))
@@ -1859,7 +1924,7 @@ class WindowCoreMixin:
if render and hasattr(self, "render_window"):
self.render_window.Render()
self.statusBar().showMessage("已清除差异预览")
if hasattr(self, "export_all_button"):
if hasattr(self, "mode_combo"):
self._update_action_states()
def _show_operation_diff(self, record: OperationRecord) -> str: