feat: 完善 STEP 编辑器最小系统和稳定性
This commit is contained in:
+157
-112
@@ -54,7 +54,7 @@ class WindowCoreMixin:
|
||||
self.interactor.AddObserver("MouseMoveEvent", self.on_mouse_move)
|
||||
|
||||
light = vtk.vtkLight()
|
||||
light.SetLightTypeToSceneLight()
|
||||
light.SetLightTypeToHeadlight()
|
||||
light.SetPosition(1, 1, 1)
|
||||
light.SetIntensity(0.9)
|
||||
self.renderer.AddLight(light)
|
||||
@@ -62,20 +62,30 @@ class WindowCoreMixin:
|
||||
self.interactor.Initialize()
|
||||
|
||||
def closeEvent(self, event) -> None:
|
||||
if self.load_in_progress:
|
||||
load_thread_running = bool(
|
||||
(self.load_thread is not None and self.load_thread.isRunning())
|
||||
or (self.load_refine_thread is not None and self.load_refine_thread.isRunning())
|
||||
)
|
||||
if self.load_in_progress or load_thread_running:
|
||||
self.statusBar().showMessage("STEP background loading is still running.")
|
||||
event.ignore()
|
||||
return
|
||||
if self.operation_in_progress:
|
||||
edit_thread_running = bool(self.edit_thread is not None and self.edit_thread.isRunning())
|
||||
if self.operation_in_progress or edit_thread_running:
|
||||
self.statusBar().showMessage("后台编辑正在计算,请等待当前操作完成后再关闭窗口。")
|
||||
event.ignore()
|
||||
return
|
||||
if self.scan_in_progress:
|
||||
self.statusBar().showMessage("后台扫描正在进行,请等待扫描完成后再关闭窗口。")
|
||||
scan_thread_running = bool(self.scan_thread is not None and self.scan_thread.isRunning())
|
||||
if self.scan_in_progress or scan_thread_running:
|
||||
self.statusBar().showMessage("扫描正在进行,请等待扫描完成后再关闭窗口。")
|
||||
event.ignore()
|
||||
return
|
||||
super().closeEvent(event)
|
||||
|
||||
def _request_thread_quit(self, thread: QThread | None) -> None:
|
||||
if thread is not None and thread.isRunning():
|
||||
thread.quit()
|
||||
|
||||
def load_step(self, path: str | Path, *, background: bool = True) -> None:
|
||||
self._load_step_background_or_sync(path, background=background)
|
||||
return
|
||||
@@ -110,53 +120,62 @@ class WindowCoreMixin:
|
||||
return
|
||||
new_path = Path(path)
|
||||
if not background:
|
||||
self.statusBar().showMessage(f"Loading {new_path.name}...")
|
||||
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
|
||||
QApplication.processEvents()
|
||||
try:
|
||||
result = self._load_step_result(
|
||||
new_path,
|
||||
deflection=0.8,
|
||||
show_internal_edges=self._show_same_domain_internal_edges(),
|
||||
)
|
||||
except Exception as exc:
|
||||
QMessageBox.critical(self, "Load failed", str(exc))
|
||||
self.statusBar().showMessage("STEP load failed; current model was left unchanged.")
|
||||
return
|
||||
finally:
|
||||
QApplication.restoreOverrideCursor()
|
||||
self._apply_loaded_model_result(result, reset_camera=True)
|
||||
self.statusBar().showMessage(f"Loaded {self.step_path.name}")
|
||||
self._load_step_sync(
|
||||
new_path,
|
||||
deflection=0.8,
|
||||
show_internal_edges=self._show_same_domain_internal_edges(),
|
||||
status_prefix="Loading",
|
||||
)
|
||||
return
|
||||
|
||||
self.load_in_progress = True
|
||||
self.pending_load_path = new_path
|
||||
self.statusBar().showMessage(f"Loading {new_path.name} in background...")
|
||||
self.statusBar().showMessage(f"Preparing fast load for {new_path.name}...")
|
||||
self._clear_hover(render=True)
|
||||
self._update_action_states()
|
||||
QTimer.singleShot(0, lambda path=new_path: self._run_deferred_initial_load(path))
|
||||
|
||||
def action():
|
||||
return self._load_step_result(
|
||||
def _load_step_sync(
|
||||
self,
|
||||
new_path: Path,
|
||||
*,
|
||||
deflection: float,
|
||||
show_internal_edges: bool,
|
||||
status_prefix: str,
|
||||
) -> bool:
|
||||
self.statusBar().showMessage(f"{status_prefix} {new_path.name}...")
|
||||
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
|
||||
QApplication.processEvents()
|
||||
try:
|
||||
result = self._load_step_result(
|
||||
new_path,
|
||||
deflection=deflection,
|
||||
show_internal_edges=show_internal_edges,
|
||||
)
|
||||
result["display"] = "ready"
|
||||
except Exception as exc:
|
||||
QMessageBox.critical(self, "Load failed", str(exc))
|
||||
self.statusBar().showMessage("STEP load failed; current model was left unchanged.")
|
||||
return False
|
||||
finally:
|
||||
QApplication.restoreOverrideCursor()
|
||||
self._apply_loaded_model_result(result, reset_camera=True)
|
||||
self.statusBar().showMessage(f"Loaded {self.step_path.name}")
|
||||
return True
|
||||
|
||||
@Slot(object)
|
||||
def _run_deferred_initial_load(self, expected_path: Path) -> None:
|
||||
if self.pending_load_path != expected_path:
|
||||
return
|
||||
try:
|
||||
self._load_step_sync(
|
||||
expected_path,
|
||||
deflection=self.initial_load_deflection,
|
||||
show_internal_edges=True,
|
||||
build_polydata=False,
|
||||
status_prefix="Fast loading",
|
||||
)
|
||||
|
||||
thread = QThread(self)
|
||||
worker = LoadWorker(action)
|
||||
worker.moveToThread(thread)
|
||||
thread.started.connect(worker.run)
|
||||
worker.finished.connect(self._finish_initial_load)
|
||||
worker.failed.connect(self._fail_initial_load)
|
||||
worker.finished.connect(thread.quit)
|
||||
worker.failed.connect(thread.quit)
|
||||
thread.finished.connect(worker.deleteLater)
|
||||
thread.finished.connect(thread.deleteLater)
|
||||
thread.finished.connect(self._forget_load_thread)
|
||||
self.load_thread = thread
|
||||
self.load_worker = worker
|
||||
thread.start()
|
||||
finally:
|
||||
self._end_load_task()
|
||||
|
||||
def _apply_loaded_model_result(self, result: dict[str, object], *, reset_camera: bool) -> None:
|
||||
new_path = Path(result["path"])
|
||||
@@ -194,11 +213,12 @@ class WindowCoreMixin:
|
||||
"faces": stats.faces,
|
||||
"edges": stats.edges,
|
||||
"vertices": stats.vertices,
|
||||
"display": "quick preview" if self.load_in_progress else "ready",
|
||||
"display": result.get("display", "quick preview" if self.load_in_progress else "ready"),
|
||||
}
|
||||
)
|
||||
self._update_action_states()
|
||||
|
||||
@Slot(object)
|
||||
def _finish_initial_load(self, result: object) -> None:
|
||||
try:
|
||||
if not isinstance(result, dict):
|
||||
@@ -210,49 +230,21 @@ class WindowCoreMixin:
|
||||
self._end_load_task()
|
||||
QMessageBox.critical(self, "Load failed", str(exc))
|
||||
self.statusBar().showMessage("STEP load failed; current model was left unchanged.")
|
||||
finally:
|
||||
self._request_thread_quit(self.load_thread)
|
||||
|
||||
@Slot(str)
|
||||
def _fail_initial_load(self, message: str) -> None:
|
||||
self._end_load_task()
|
||||
QMessageBox.critical(self, "Load failed", message)
|
||||
self.statusBar().showMessage("STEP load failed; current model was left unchanged.")
|
||||
|
||||
def _start_load_refine(self, initial_result: dict[str, object]) -> None:
|
||||
model = initial_result.get("model")
|
||||
if model is None or model is not self.model:
|
||||
self._end_load_task()
|
||||
return
|
||||
path = Path(initial_result["path"])
|
||||
stats = initial_result["stats"]
|
||||
show_internal_edges = self._show_same_domain_internal_edges()
|
||||
|
||||
def action():
|
||||
return {
|
||||
"path": path,
|
||||
"model": model,
|
||||
"stats": stats,
|
||||
"model_polydata": model.build_face_polydata(deflection=0.8),
|
||||
"edge_polydata": model.build_edge_polydata(
|
||||
deflection=0.8,
|
||||
show_same_domain_internal_edges=show_internal_edges,
|
||||
),
|
||||
"show_internal_edges": show_internal_edges,
|
||||
}
|
||||
|
||||
thread = QThread(self)
|
||||
worker = LoadWorker(action)
|
||||
worker.moveToThread(thread)
|
||||
thread.started.connect(worker.run)
|
||||
worker.finished.connect(self._finish_load_refine)
|
||||
worker.failed.connect(self._fail_load_refine)
|
||||
worker.finished.connect(thread.quit)
|
||||
worker.failed.connect(thread.quit)
|
||||
thread.finished.connect(worker.deleteLater)
|
||||
thread.finished.connect(thread.deleteLater)
|
||||
thread.finished.connect(self._forget_load_refine_thread)
|
||||
self.load_refine_thread = thread
|
||||
self.load_refine_worker = worker
|
||||
thread.start()
|
||||
self._end_load_task()
|
||||
if self.model is not None:
|
||||
self.statusBar().showMessage(f"Loaded {self.step_path.name}")
|
||||
|
||||
@Slot(object)
|
||||
def _finish_load_refine(self, result: object) -> None:
|
||||
try:
|
||||
if isinstance(result, dict) and result.get("model") is self.model:
|
||||
@@ -277,6 +269,7 @@ class WindowCoreMixin:
|
||||
finally:
|
||||
self._end_load_task()
|
||||
|
||||
@Slot(str)
|
||||
def _fail_load_refine(self, message: str) -> None:
|
||||
self._end_load_task()
|
||||
self.statusBar().showMessage(f"Quick preview is available; display refinement failed: {message}")
|
||||
@@ -285,6 +278,8 @@ class WindowCoreMixin:
|
||||
self.load_in_progress = False
|
||||
self.pending_load_path = None
|
||||
self._update_action_states()
|
||||
self._request_thread_quit(self.load_thread)
|
||||
self._request_thread_quit(self.load_refine_thread)
|
||||
|
||||
def _forget_load_thread(self) -> None:
|
||||
self.load_thread = None
|
||||
@@ -319,6 +314,8 @@ class WindowCoreMixin:
|
||||
self.clear_diff_preview(render=False)
|
||||
if hasattr(self, "history_list"):
|
||||
self.history_list.clear()
|
||||
if hasattr(self, "export_all_button"):
|
||||
self._update_action_states()
|
||||
|
||||
def _refresh_history_list(self) -> None:
|
||||
was_blocked = self.history_list.blockSignals(True)
|
||||
@@ -328,6 +325,7 @@ class WindowCoreMixin:
|
||||
self.history_list.addItem(f"{index}. {entry.summary}")
|
||||
finally:
|
||||
self.history_list.blockSignals(was_blocked)
|
||||
self._update_action_states()
|
||||
|
||||
def on_history_row_changed(self, row: int) -> None:
|
||||
if self._edit_busy("编辑计算中,暂时不能查看历史记录详情。"):
|
||||
@@ -407,6 +405,10 @@ class WindowCoreMixin:
|
||||
"index": index,
|
||||
"summary": record.summary,
|
||||
"detail": record.detail,
|
||||
"operation_name": record.operation_name,
|
||||
"target": record.target,
|
||||
"parameters": safe_value(record.parameters or {}),
|
||||
"result_message": record.result_message,
|
||||
"target_kind": record.target_kind,
|
||||
"target_id": record.target_id,
|
||||
"target_logical_id": record.target_logical_id,
|
||||
@@ -417,7 +419,7 @@ class WindowCoreMixin:
|
||||
}
|
||||
)
|
||||
payload = {
|
||||
"format": "step-editor-operation-history-v1",
|
||||
"format": "step-editor-operation-history-v2",
|
||||
"generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"source_file": str(self.step_path),
|
||||
"record_count": len(records),
|
||||
@@ -558,27 +560,27 @@ 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"Part {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)
|
||||
label = f"solid {self.selected_solid_id}"
|
||||
label = f"Solid {self.selected_solid_id}"
|
||||
elif self.selected_kind == "feature" and self.selected_face_id is not None:
|
||||
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"Feature 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)
|
||||
label = f"face {self.selected_face_id}"
|
||||
label = f"Face {self.selected_face_id}"
|
||||
elif self.selected_kind == "edge" and self.selected_edge_id is not None:
|
||||
info = self.model.edge_info(self.selected_edge_id)
|
||||
face_ids = _int_values(info.get("adjacent_face_ids"))
|
||||
edge_ids = [self.selected_edge_id]
|
||||
label = f"edge {self.selected_edge_id}"
|
||||
label = f"Edge {self.selected_edge_id}"
|
||||
else:
|
||||
QMessageBox.information(self, "未选择对象", "请先选择 part、solid、face、edge 或 feature。")
|
||||
QMessageBox.information(self, "未选择对象", "请先选择 Part、Solid、Face、Edge 或 Feature。")
|
||||
return
|
||||
|
||||
model_polydata = self.model.build_face_polydata(face_ids=face_ids, part_ids=part_ids)
|
||||
@@ -608,7 +610,7 @@ class WindowCoreMixin:
|
||||
return
|
||||
bounds = self._selected_focus_bounds()
|
||||
if bounds is None:
|
||||
QMessageBox.information(self, "未选择对象", "请先选择 part、solid、face、edge 或 feature。")
|
||||
QMessageBox.information(self, "未选择对象", "请先选择 Part、Solid、Face、Edge 或 Feature。")
|
||||
return
|
||||
self._fit_camera_to_bounds(bounds)
|
||||
self.render_window.Render()
|
||||
@@ -772,6 +774,11 @@ class WindowCoreMixin:
|
||||
self.model_actor.GetProperty().SetSpecular(0.25)
|
||||
self.model_actor.GetProperty().SetSpecularPower(18)
|
||||
self.model_actor.GetProperty().SetInterpolationToPhong()
|
||||
backface_property = vtk.vtkProperty()
|
||||
backface_property.SetColor(0.58, 0.62, 0.64)
|
||||
backface_property.SetDiffuse(0.9)
|
||||
backface_property.SetAmbient(0.28)
|
||||
self.model_actor.SetBackfaceProperty(backface_property)
|
||||
self.renderer.AddActor(self.model_actor)
|
||||
|
||||
self.edge_polydata = edge_polydata
|
||||
@@ -816,7 +823,7 @@ class WindowCoreMixin:
|
||||
self.statusBar().showMessage("编辑计算中,请等待当前操作完成")
|
||||
return
|
||||
if self.scan_in_progress:
|
||||
self.statusBar().showMessage("后台扫描中,请等待扫描完成后再选择对象。")
|
||||
self.statusBar().showMessage("扫描中,请等待扫描完成后再选择对象。")
|
||||
return
|
||||
if self.model is None:
|
||||
return
|
||||
@@ -876,7 +883,16 @@ class WindowCoreMixin:
|
||||
self._queue_hover_position(x, y)
|
||||
|
||||
def _queue_hover_position(self, x: int, y: int) -> None:
|
||||
self.pending_hover_position = (int(x), int(y))
|
||||
position = (int(x), int(y))
|
||||
threshold = int(getattr(self, "hover_move_threshold_px", 0) or 0)
|
||||
if threshold > 0:
|
||||
previous = self.pending_hover_position or self.last_hover_pick_position
|
||||
if previous is not None:
|
||||
dx = abs(position[0] - int(previous[0]))
|
||||
dy = abs(position[1] - int(previous[1]))
|
||||
if max(dx, dy) < threshold:
|
||||
return
|
||||
self.pending_hover_position = position
|
||||
if not self.hover_timer.isActive():
|
||||
self.hover_timer.start(self.hover_interval_ms)
|
||||
|
||||
@@ -1102,7 +1118,7 @@ class WindowCoreMixin:
|
||||
self.select_part(int(target_id))
|
||||
|
||||
def on_cylinder_row_clicked(self, row: int, _column: int) -> None:
|
||||
if self._edit_busy("编辑计算中,暂时不能切换圆柱候选。"):
|
||||
if self._edit_busy("编辑计算中,暂时不能切换圆柱面候选。"):
|
||||
return
|
||||
item = self.cylinder_table.item(row, 0)
|
||||
if item is None:
|
||||
@@ -1127,47 +1143,51 @@ class WindowCoreMixin:
|
||||
if action == "resize_cylinder":
|
||||
self.mode_combo.setCurrentText("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可调整孔径候选 face {target_id}")
|
||||
self.statusBar().showMessage(f"已选择可调整孔径候选Face {target_id}")
|
||||
elif action == "resize_boss":
|
||||
self.mode_combo.setCurrentText("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可调整凸台直径候选 face {target_id}")
|
||||
self.statusBar().showMessage(f"已选择可调整凸台直径候选Face {target_id}")
|
||||
elif action == "resize_slot_width":
|
||||
self.mode_combo.setCurrentText("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可调整槽/半孔宽度候选 face {target_id}")
|
||||
self.statusBar().showMessage(f"已选择可调整槽/半孔宽度候选Face {target_id}")
|
||||
elif action == "resize_shell_thickness":
|
||||
self.mode_combo.setCurrentText("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可调整薄壁厚度候选Face {target_id}")
|
||||
elif action == "suppress_cylinder":
|
||||
self.mode_combo.setCurrentText("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可封堵圆柱孔 face {target_id}")
|
||||
self.statusBar().showMessage(f"已选择可封堵圆柱孔Face {target_id}")
|
||||
elif action == "resize_depth":
|
||||
self.mode_combo.setCurrentText("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可调整盲孔深度候选 face {target_id}")
|
||||
self.statusBar().showMessage(f"已选择可调整盲孔深度候选Face {target_id}")
|
||||
elif action == "inspect_existing_fillet":
|
||||
self.mode_combo.setCurrentText("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择已有圆角/倒圆候选 face {target_id}")
|
||||
self.statusBar().showMessage(f"已选择已有圆角/倒圆候选Face {target_id}")
|
||||
elif action == "fillet_edge":
|
||||
self.mode_combo.setCurrentText("Edge")
|
||||
self.select_edge(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可添加圆角 edge {target_id}")
|
||||
self.statusBar().showMessage(f"已选择可添加圆角Edge {target_id}")
|
||||
elif action == "chamfer_edge":
|
||||
self.mode_combo.setCurrentText("Edge")
|
||||
self.select_edge(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可添加倒角 edge {target_id}")
|
||||
self.statusBar().showMessage(f"已选择可添加倒角Edge {target_id}")
|
||||
elif action == "resize_edge_length":
|
||||
self.mode_combo.setCurrentText("Edge")
|
||||
self.select_edge(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可尝试调整长度的直线 edge {target_id}")
|
||||
self.statusBar().showMessage(f"已选择可尝试调整长度的Edge {target_id}")
|
||||
elif target_kind == "edge":
|
||||
self.mode_combo.setCurrentText("Edge")
|
||||
self.select_edge(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择 edge {target_id}")
|
||||
self.statusBar().showMessage(f"已选择Edge {target_id}")
|
||||
else:
|
||||
self.mode_combo.setCurrentText("Face")
|
||||
self.select_face(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可推拉平面 face {target_id}")
|
||||
self.statusBar().showMessage(f"已选择可推拉平面Face {target_id}")
|
||||
|
||||
def select_by_id(self, kind: str | None = None) -> None:
|
||||
if self.model is None:
|
||||
@@ -1240,7 +1260,7 @@ class WindowCoreMixin:
|
||||
) -> None:
|
||||
if self.model is None:
|
||||
return
|
||||
if self._edit_busy("编辑计算中,暂时不能选择 solid。"):
|
||||
if self._edit_busy("编辑计算中,暂时不能选择Solid。"):
|
||||
return
|
||||
info = self.model.solid_info(solid_id)
|
||||
self._reset_selection(clear_highlight=False)
|
||||
@@ -1254,7 +1274,7 @@ class WindowCoreMixin:
|
||||
self._sync_id_picker("Solid", solid_id)
|
||||
self.set_info(self._with_pick_info(info, pick_position))
|
||||
self._update_action_states()
|
||||
self.statusBar().showMessage(self._selection_status(f"已选择 solid {solid_id}", pick_position))
|
||||
self.statusBar().showMessage(self._selection_status(f"已选择Solid {solid_id}", pick_position))
|
||||
|
||||
def select_face(
|
||||
self,
|
||||
@@ -1264,7 +1284,7 @@ class WindowCoreMixin:
|
||||
) -> None:
|
||||
if self.model is None:
|
||||
return
|
||||
if self._edit_busy("编辑计算中,暂时不能选择 face。"):
|
||||
if self._edit_busy("编辑计算中,暂时不能选择Face。"):
|
||||
return
|
||||
self._reset_selection(clear_highlight=False)
|
||||
self.selected_kind = "feature" if feature_mode else "face"
|
||||
@@ -1279,9 +1299,10 @@ class WindowCoreMixin:
|
||||
if len(highlight_face_ids) > 1:
|
||||
info["same_domain_face_ids"] = tuple(highlight_face_ids)
|
||||
info["same_domain_face_count"] = len(highlight_face_ids)
|
||||
info["same_domain_note"] = "已高亮属于同一几何面且范围相接/重叠的连续 face 区域。"
|
||||
info["same_domain_note"] = "已高亮属于同一几何面且范围相接/重叠的连续Face区域。"
|
||||
logical_id = self.model.face_region_logical_id(face_id)
|
||||
self._sync_cylindrical_edit_inputs(info)
|
||||
input_info = self.model.feature_info(face_id) if info.get("surface") == "plane" else info
|
||||
self._sync_cylindrical_edit_inputs(input_info)
|
||||
self.selected_part_id = int(info["part_id"])
|
||||
self.selected_solid_id = int(info["solid_id"]) if int(info["solid_id"]) >= 0 else None
|
||||
self._highlight_faces(face_ids=highlight_face_ids or [face_id])
|
||||
@@ -1289,9 +1310,9 @@ class WindowCoreMixin:
|
||||
self._sync_id_picker("Feature" if feature_mode else "Face", face_id if feature_mode else logical_id)
|
||||
self.set_info(self._with_pick_info(info, pick_position))
|
||||
self._update_action_states()
|
||||
suffix = f",同域区域 {len(highlight_face_ids)} 个 face" if not feature_mode and len(highlight_face_ids) > 1 else ""
|
||||
raw_note = f"(拓扑 face {face_id})" if not feature_mode and logical_id != face_id else ""
|
||||
message = f"已选择逻辑面区域 {logical_id}{raw_note}{suffix}" if not feature_mode else f"已选择 face {face_id}"
|
||||
suffix = f",同域区域 {len(highlight_face_ids)} 个Face" if not feature_mode and len(highlight_face_ids) > 1 else ""
|
||||
raw_note = f"(拓扑Face {face_id})" if not feature_mode and logical_id != face_id else ""
|
||||
message = f"已选择逻辑面区域 {logical_id}{raw_note}{suffix}" if not feature_mode else f"已选择Face {face_id}"
|
||||
self.statusBar().showMessage(self._selection_status(message, pick_position))
|
||||
|
||||
def select_feature(self, face_id: int, pick_position: tuple[float, float, float] | None = None) -> None:
|
||||
@@ -1314,10 +1335,16 @@ class WindowCoreMixin:
|
||||
self.set_info(self._with_pick_info(info, pick_position))
|
||||
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))
|
||||
self.statusBar().showMessage(self._selection_status(f"已选择 {feature_type},来源Face {face_id}", pick_position))
|
||||
|
||||
def _sync_cylindrical_edit_inputs(self, info: dict[str, object]) -> None:
|
||||
fillet_radius_suggestion: str | None = None
|
||||
shell_thickness = _float_or_none(info.get("shell_thickness_estimate"))
|
||||
if hasattr(self, "shell_thickness_input"):
|
||||
if info.get("shell_region_status") == "candidate" and shell_thickness is not None and shell_thickness > 0:
|
||||
self.shell_thickness_input.setText(_format_float(shell_thickness * 1.2))
|
||||
else:
|
||||
self.shell_thickness_input.clear()
|
||||
if "diameter" in info:
|
||||
feature_guess = str(info.get("feature_guess", ""))
|
||||
if feature_guess == "boss/outer-round candidate":
|
||||
@@ -1373,6 +1400,8 @@ class WindowCoreMixin:
|
||||
|
||||
def _sync_edge_edit_inputs(self, info: dict[str, object]) -> None:
|
||||
self.hole_diameter_input.clear()
|
||||
if hasattr(self, "shell_thickness_input"):
|
||||
self.shell_thickness_input.clear()
|
||||
if hasattr(self, "slot_width_input"):
|
||||
self.slot_width_input.clear()
|
||||
self.boss_diameter_input.clear()
|
||||
@@ -1395,7 +1424,7 @@ class WindowCoreMixin:
|
||||
def select_edge(self, edge_id: int, pick_position: tuple[float, float, float] | None = None) -> None:
|
||||
if self.model is None:
|
||||
return
|
||||
if self._edit_busy("编辑计算中,暂时不能选择 edge。"):
|
||||
if self._edit_busy("编辑计算中,暂时不能选择Edge。"):
|
||||
return
|
||||
self._reset_selection(clear_highlight=False)
|
||||
self.selected_kind = "edge"
|
||||
@@ -1410,7 +1439,7 @@ class WindowCoreMixin:
|
||||
self._sync_id_picker("Edge", edge_id)
|
||||
self.set_info(self._with_pick_info(info, pick_position))
|
||||
self._update_action_states()
|
||||
self.statusBar().showMessage(self._selection_status(f"已选择 edge {edge_id}", pick_position))
|
||||
self.statusBar().showMessage(self._selection_status(f"已选择Edge {edge_id}", pick_position))
|
||||
|
||||
def _highlight_faces(self, face_ids=None, part_ids=None) -> None:
|
||||
if self.model is None:
|
||||
@@ -1635,6 +1664,19 @@ class WindowCoreMixin:
|
||||
self._start_edit_preview_pulse()
|
||||
self.render_window.Render()
|
||||
|
||||
def _show_shell_thickness_preview(self, face_id: int, target_thickness: float) -> None:
|
||||
if self.model is None:
|
||||
return
|
||||
self.clear_edit_preview(render=False)
|
||||
try:
|
||||
polydata = self.model.shell_thickness_preview_polydata(face_id, target_thickness)
|
||||
except Exception as exc:
|
||||
self.statusBar().showMessage(f"薄壁厚度调整预览不可用:{exc}")
|
||||
return
|
||||
self._add_edit_preview_actor(polydata, (0.0, 0.72, 0.78), opacity=0.34)
|
||||
self._start_edit_preview_pulse()
|
||||
self.render_window.Render()
|
||||
|
||||
def _show_cylinder_resize_preview(self, face_id: int, diameter: float) -> None:
|
||||
if self.model is None:
|
||||
return
|
||||
@@ -1740,7 +1782,7 @@ class WindowCoreMixin:
|
||||
try:
|
||||
polydata = self.model.straight_edge_length_preview_polydata(edge_id, target_length, anchor_mode=anchor_mode)
|
||||
except Exception as exc:
|
||||
self.statusBar().showMessage(f"边长直接修改预览不可用:{exc}")
|
||||
self.statusBar().showMessage(f"Edge长度修改预览不可用:{exc}")
|
||||
return
|
||||
if isinstance(polydata, list):
|
||||
for preview in polydata:
|
||||
@@ -1771,7 +1813,7 @@ class WindowCoreMixin:
|
||||
ids.InsertNextId(cell_id)
|
||||
break
|
||||
if ids.GetNumberOfIds() == 0:
|
||||
self.statusBar().showMessage(f"{label}不可用:没有找到选中的 edge。")
|
||||
self.statusBar().showMessage(f"{label}不可用:没有找到选中的Edge。")
|
||||
return
|
||||
extract = vtk.vtkExtractCells()
|
||||
extract.SetInputData(polydata)
|
||||
@@ -1817,6 +1859,8 @@ class WindowCoreMixin:
|
||||
if render and hasattr(self, "render_window"):
|
||||
self.render_window.Render()
|
||||
self.statusBar().showMessage("已清除差异预览")
|
||||
if hasattr(self, "export_all_button"):
|
||||
self._update_action_states()
|
||||
|
||||
def _show_operation_diff(self, record: OperationRecord) -> str:
|
||||
if self.model is None:
|
||||
@@ -1838,6 +1882,7 @@ class WindowCoreMixin:
|
||||
for actor in self.diff_actors:
|
||||
self.renderer.AddViewProp(actor)
|
||||
self.render_window.Render()
|
||||
self._update_action_states()
|
||||
if heatmap_stats:
|
||||
record.diff_stats = heatmap_stats
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user