feat: 完善 STEP 一级参数化编辑识别与关系式建模
This commit is contained in:
+197
-4
@@ -19,6 +19,7 @@ from PySide6.QtWidgets import (
|
||||
)
|
||||
|
||||
from .model import StepModel
|
||||
from .asitus_bridge import run_asitus_hole_recognition
|
||||
from .records import OperationRecord
|
||||
from .ui_helpers import * # noqa: F403
|
||||
from .workers import EditWorker, LoadWorker, ScanWorker
|
||||
@@ -178,6 +179,10 @@ class WindowCoreMixin:
|
||||
elif watched is getattr(self, "property_table", None):
|
||||
if event.type() == QEvent.Type.Resize and hasattr(self, "_resize_property_table_columns"):
|
||||
QTimer.singleShot(0, self._resize_property_table_columns)
|
||||
elif watched is getattr(self, "relation_formula_input", None):
|
||||
if event.type() == QEvent.Type.KeyPress and event.key() == Qt.Key.Key_Tab:
|
||||
if hasattr(self, "_accept_relation_formula_completion") and self._accept_relation_formula_completion():
|
||||
return True
|
||||
return super().eventFilter(watched, event)
|
||||
|
||||
def _should_suppress_transient_tooltip(self, watched) -> bool:
|
||||
@@ -959,6 +964,11 @@ class WindowCoreMixin:
|
||||
self.statusBar().showMessage("扫描正在进行,请等待扫描完成后再关闭窗口。")
|
||||
event.ignore()
|
||||
return
|
||||
asitus_thread_running = bool(self.asitus_thread is not None and self.asitus_thread.isRunning())
|
||||
if asitus_thread_running:
|
||||
self.statusBar().showMessage("孔组识别正在后台预热,请稍后再关闭窗口。")
|
||||
event.ignore()
|
||||
return
|
||||
super().closeEvent(event)
|
||||
|
||||
def _request_thread_quit(self, thread: QThread | None) -> None:
|
||||
@@ -1174,6 +1184,85 @@ class WindowCoreMixin:
|
||||
self._update_action_states()
|
||||
if edge_deferred:
|
||||
QTimer.singleShot(80, self._rebuild_deferred_edge_display)
|
||||
QTimer.singleShot(160, self._start_asitus_hole_recognition_preload)
|
||||
|
||||
def _start_asitus_hole_recognition_preload(self) -> None:
|
||||
if self.model is None or self.step_path is None:
|
||||
return
|
||||
if self.asitus_thread is not None and self.asitus_thread.isRunning():
|
||||
return
|
||||
if not hasattr(self.model, "begin_asitus_hole_region_load"):
|
||||
return
|
||||
if not self.model.begin_asitus_hole_region_load():
|
||||
return
|
||||
step_path = Path(self.step_path)
|
||||
context = {
|
||||
"path": step_path,
|
||||
"model_id": id(self.model),
|
||||
}
|
||||
self.pending_asitus_context = dict(context)
|
||||
|
||||
def action(path=step_path):
|
||||
return run_asitus_hole_recognition(path)
|
||||
|
||||
thread = QThread(self)
|
||||
worker = ScanWorker(action)
|
||||
worker.moveToThread(thread)
|
||||
thread.started.connect(worker.run)
|
||||
worker.finished.connect(self._finish_asitus_hole_recognition, Qt.ConnectionType.QueuedConnection)
|
||||
worker.failed.connect(self._fail_asitus_hole_recognition, Qt.ConnectionType.QueuedConnection)
|
||||
thread.finished.connect(worker.deleteLater)
|
||||
thread.finished.connect(thread.deleteLater)
|
||||
thread.finished.connect(self._forget_asitus_thread)
|
||||
self.asitus_thread = thread
|
||||
self.asitus_worker = worker
|
||||
try:
|
||||
thread.start(QThread.Priority.LowPriority)
|
||||
except TypeError:
|
||||
thread.start()
|
||||
|
||||
@Slot(object)
|
||||
def _finish_asitus_hole_recognition(self, result: object) -> None:
|
||||
try:
|
||||
context = dict(self.pending_asitus_context or {})
|
||||
if self.model is None or id(self.model) != context.get("model_id"):
|
||||
return
|
||||
if self.step_path is None or Path(context.get("path", "")) != Path(self.step_path):
|
||||
return
|
||||
mapped_groups = self.model.install_asitus_hole_recognition_result(result)
|
||||
if mapped_groups:
|
||||
self._refresh_selection_after_asitus_holes(mapped_groups)
|
||||
finally:
|
||||
self._request_thread_quit(self.asitus_thread)
|
||||
|
||||
@Slot(str)
|
||||
def _fail_asitus_hole_recognition(self, message: str) -> None:
|
||||
try:
|
||||
context = dict(self.pending_asitus_context or {})
|
||||
if self.model is not None and id(self.model) == context.get("model_id"):
|
||||
self.model.fail_asitus_hole_region_load(message)
|
||||
finally:
|
||||
self._request_thread_quit(self.asitus_thread)
|
||||
|
||||
def _refresh_selection_after_asitus_holes(self, mapped_groups: list[tuple[int, ...]]) -> None:
|
||||
if self.model is None or self.selected_face_id is None:
|
||||
return
|
||||
selected_id = int(self.selected_face_id)
|
||||
if not any(selected_id in group for group in mapped_groups):
|
||||
return
|
||||
if getattr(self, "operation_in_progress", False):
|
||||
return
|
||||
pick_position = self.selected_pick_position
|
||||
if self.selected_kind == "feature":
|
||||
self.select_feature(selected_id, pick_position=pick_position)
|
||||
elif self.selected_kind == "face":
|
||||
self.select_face(selected_id, pick_position=pick_position)
|
||||
|
||||
@Slot()
|
||||
def _forget_asitus_thread(self) -> None:
|
||||
self.asitus_thread = None
|
||||
self.asitus_worker = None
|
||||
self.pending_asitus_context = None
|
||||
|
||||
@Slot(object)
|
||||
def _finish_initial_load(self, result: object) -> None:
|
||||
@@ -1567,6 +1656,12 @@ class WindowCoreMixin:
|
||||
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"特征 Face {self.selected_face_id}"
|
||||
elif self.selected_kind == "multi_feature":
|
||||
info = self._multi_hole_selection_info() if hasattr(self, "_multi_hole_selection_info") else {}
|
||||
face_ids = _int_values(info.get("feature_highlight_face_ids"))
|
||||
if not face_ids:
|
||||
face_ids = _int_values(getattr(self, "multi_selected_feature_face_ids", []))
|
||||
label = f"多选孔 {len(getattr(self, 'multi_selected_hole_entries', []) or [])} 个"
|
||||
elif self.selected_kind == "face" and self.selected_face_id is not None:
|
||||
face_ids = [self.selected_face_id]
|
||||
edge_ids = self.model.face_boundary_edge_ids(self.selected_face_id)
|
||||
@@ -1639,6 +1734,13 @@ class WindowCoreMixin:
|
||||
face_polydata = self._cached_face_overlay_polydata(face_ids=face_ids, smooth=False)
|
||||
if edge_ids:
|
||||
edge_polydata = self.model.build_edge_polydata(edge_ids=edge_ids)
|
||||
elif self.selected_kind == "multi_feature":
|
||||
info = self._multi_hole_selection_info() if hasattr(self, "_multi_hole_selection_info") else {}
|
||||
face_ids = _int_values(info.get("feature_highlight_face_ids"))
|
||||
if not face_ids:
|
||||
face_ids = _int_values(getattr(self, "multi_selected_feature_face_ids", []))
|
||||
if face_ids:
|
||||
face_polydata = self._cached_face_overlay_polydata(face_ids=face_ids, smooth=False)
|
||||
elif self.selected_kind == "face" and self.selected_face_id is not None:
|
||||
face_ids = self._selection_same_domain_face_ids(self.selected_face_id) or [self.selected_face_id]
|
||||
face_polydata = self._cached_face_overlay_polydata(face_ids=face_ids, smooth=False)
|
||||
@@ -1675,6 +1777,13 @@ class WindowCoreMixin:
|
||||
info = {}
|
||||
face_ids = _int_values(info.get("feature_highlight_face_ids")) or [self.selected_face_id]
|
||||
self._highlight_faces(face_ids=face_ids)
|
||||
elif self.selected_kind == "multi_feature":
|
||||
info = self._multi_hole_selection_info() if hasattr(self, "_multi_hole_selection_info") else {}
|
||||
face_ids = _int_values(info.get("feature_highlight_face_ids"))
|
||||
if not face_ids:
|
||||
face_ids = _int_values(getattr(self, "multi_selected_feature_face_ids", []))
|
||||
if face_ids:
|
||||
self._highlight_faces(face_ids=face_ids)
|
||||
elif self.selected_kind == "face" and self.selected_face_id is not None:
|
||||
face_ids = self._selection_same_domain_face_ids(self.selected_face_id) or [self.selected_face_id]
|
||||
self._highlight_faces(face_ids=face_ids)
|
||||
@@ -2077,8 +2186,94 @@ class WindowCoreMixin:
|
||||
return
|
||||
|
||||
self._clear_hover(render=False)
|
||||
if self._is_multi_feature_toggle_request(target):
|
||||
self._toggle_multi_feature_selection(target, pick_position=target.get("pick_position"))
|
||||
return
|
||||
self._select_pick_target(target)
|
||||
|
||||
def _is_multi_feature_toggle_request(self, target: dict[str, object]) -> bool:
|
||||
if str(target.get("kind") or "") not in {"feature", "face"}:
|
||||
return False
|
||||
try:
|
||||
return bool(QApplication.keyboardModifiers() & Qt.KeyboardModifier.ControlModifier)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _toggle_multi_feature_selection(
|
||||
self,
|
||||
target: dict[str, object],
|
||||
*,
|
||||
pick_position: tuple[float, float, float] | None = None,
|
||||
) -> None:
|
||||
if self.model is None:
|
||||
return
|
||||
face_id = _safe_int_or_none(target.get("target_id"))
|
||||
if face_id is None:
|
||||
return
|
||||
entry = self._hole_multi_select_entry(face_id) if hasattr(self, "_hole_multi_select_entry") else None
|
||||
if entry is None:
|
||||
self.statusBar().showMessage("多选第一版只支持完整圆柱孔;请按 Ctrl 选择孔壁。")
|
||||
return
|
||||
|
||||
entries = [dict(item) for item in (getattr(self, "multi_selected_hole_entries", []) or [])]
|
||||
if not entries and self.selected_kind == "feature" and self.selected_face_id is not None:
|
||||
current_entry = self._hole_multi_select_entry(int(self.selected_face_id))
|
||||
if current_entry is not None:
|
||||
entries.append(dict(current_entry))
|
||||
|
||||
logical_id = int(entry.get("logical_id", face_id))
|
||||
existing_index = next(
|
||||
(
|
||||
index
|
||||
for index, item in enumerate(entries)
|
||||
if int(item.get("logical_id", -1)) == logical_id
|
||||
),
|
||||
None,
|
||||
)
|
||||
if existing_index is None:
|
||||
entries.append(dict(entry))
|
||||
else:
|
||||
entries.pop(existing_index)
|
||||
|
||||
if len(entries) < 2:
|
||||
if entries:
|
||||
self.select_feature(int(entries[0]["face_id"]), pick_position=pick_position)
|
||||
else:
|
||||
self._reset_selection()
|
||||
return
|
||||
|
||||
part_ids = {
|
||||
int(item["part_id"])
|
||||
for item in entries
|
||||
if item.get("part_id") not in {None, ""}
|
||||
}
|
||||
solid_ids = {
|
||||
int(item["solid_id"])
|
||||
for item in entries
|
||||
if item.get("solid_id") not in {None, ""}
|
||||
}
|
||||
self._reset_selection(clear_highlight=False, clear_info=False)
|
||||
self.multi_selected_hole_entries = entries
|
||||
self.multi_selected_feature_face_ids = [int(item["face_id"]) for item in entries]
|
||||
self.multi_selection_active = True
|
||||
self.selected_kind = "multi_feature"
|
||||
self.selected_face_id = int(entries[-1]["face_id"])
|
||||
self.selected_edge_id = None
|
||||
self.selected_part_id = next(iter(part_ids)) if len(part_ids) == 1 else None
|
||||
self.selected_solid_id = next(iter(solid_ids)) if len(solid_ids) == 1 else None
|
||||
self.selected_pick_position = pick_position
|
||||
|
||||
info = self._multi_hole_selection_info()
|
||||
if hasattr(self, "_set_selection_mode"):
|
||||
self._set_selection_mode("Feature")
|
||||
if hasattr(self, "_sync_id_picker"):
|
||||
self._sync_id_picker("Feature", int(entries[-1].get("logical_id", entries[-1]["face_id"])))
|
||||
self._highlight_faces(face_ids=_int_values(info.get("feature_highlight_face_ids")) or self.multi_selected_feature_face_ids)
|
||||
self._show_pick_marker(pick_position)
|
||||
self.set_info(self._with_pick_info(info, pick_position))
|
||||
ids_text = ", ".join(str(item.get("logical_id")) for item in entries if item.get("logical_id") is not None)
|
||||
self.statusBar().showMessage(f"已多选 {len(entries)} 个孔:{ids_text}。再次 Ctrl 点击可移除。")
|
||||
|
||||
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)
|
||||
@@ -2642,8 +2837,7 @@ class WindowCoreMixin:
|
||||
self._sync_id_picker("Feature" if feature_mode else "Face", logical_id)
|
||||
self.set_info(self._with_pick_info(input_info, pick_position))
|
||||
self._update_action_states()
|
||||
raw_note = f"(当前拓扑 Face {face_id})" if logical_id != face_id else ""
|
||||
message = f"已选择Face {logical_id}{raw_note}" if not feature_mode else f"已选择特征来源 Face {logical_id}{raw_note}"
|
||||
message = f"已选择Face {logical_id}" if not feature_mode else f"已选择特征来源 Face {logical_id}"
|
||||
self.statusBar().showMessage(self._selection_status(message, pick_position))
|
||||
|
||||
def _selection_same_domain_face_ids(self, face_id: int) -> list[int]:
|
||||
@@ -2687,8 +2881,7 @@ class WindowCoreMixin:
|
||||
if hasattr(self, "_feature_display_label"):
|
||||
feature_type = self._feature_display_label(feature_type)
|
||||
self._update_action_states()
|
||||
raw_note = f"(当前拓扑 Face {face_id})" if logical_id != face_id else ""
|
||||
self.statusBar().showMessage(self._selection_status(f"已选择 {feature_type},来源 Face {logical_id}{raw_note}", pick_position))
|
||||
self.statusBar().showMessage(self._selection_status(f"已选择 {feature_type},来源 Face {logical_id}", pick_position))
|
||||
|
||||
def _sync_cylindrical_edit_inputs(self, info: dict[str, object]) -> None:
|
||||
fillet_radius_suggestion: str | None = None
|
||||
|
||||
Reference in New Issue
Block a user