feat: 完善参数化关系式与SCDM模型修改验证

This commit is contained in:
2026-08-17 18:53:03 +08:00
parent a633b5a338
commit 722256a41f
7 changed files with 1798 additions and 83 deletions
@@ -80,6 +80,63 @@ def _isolated_hole_resize(model: StepModel, face_id: int, diameter: float, root:
return str(response.get("message") or "")
def _assert_isolated_hole_resize_disables_stale_external_regions(
model: StepModel,
face_id: int,
diameter: float,
root: Path,
) -> None:
input_path = root / "face87_stale_input.brep"
output_path = root / "face87_stale_output.brep"
request_path = root / "face87_stale_resize_request.json"
model.export_internal_brep(input_path)
request_path.write_text(
json.dumps(
{
"input_path": str(input_path),
"output_path": str(output_path),
"input_format": "brep",
"output_format": "brep",
"operation": "resize_cylindrical_hole",
"args": [face_id, diameter],
},
ensure_ascii=False,
indent=2,
),
encoding="utf-8",
)
code = run_request(request_path)
response = json.loads(request_path.with_suffix(".response.json").read_text(encoding="utf-8"))
_assert(code == 0 and response.get("ok") is True, f"isolated stale-region resize should pass: {response}")
message = str(response.get("message") or "")
marker = "verified_face="
marker_index = message.find(marker)
_assert(marker_index >= 0, f"isolated resize should report verified_face: {message}")
verified_text = message[marker_index + len(marker):].split(".", 1)[0].strip()
verified_face = int(float(verified_text))
result_model = StepModel.load_internal_brep(output_path)
result_model.filename = MODEL_PATH
stale_region = result_model.face_region_ids(verified_face)
_assert(
len(stale_region) > 1,
f"test fixture should expose the stale external-region bug before marking: {stale_region}",
)
result_model.mark_external_recognition_stale("isolated-edit-result")
current_region = result_model.face_region_ids(verified_face)
_assert(
current_region == [verified_face],
f"isolated edit result should not reuse original STEP Analysis Situs hole groups: {current_region}",
)
result_model.assign_logical_face_region_exclusive(face_id, current_region)
_assert(
result_model.face_ids_for_logical_id(face_id) == current_region,
f"logical Face {face_id} should only attach to the edited hole, not a neighboring hole: "
f"{result_model.face_ids_for_logical_id(face_id)}",
)
def _isolated_hole_axis_move(
model: StepModel,
face_id: int,
@@ -331,6 +388,7 @@ def main() -> int:
with tempfile.TemporaryDirectory(prefix="icepak_face87_isolated_") as temp_dir:
worker_message = _isolated_hole_resize(model, 87, 0.3, Path(temp_dir))
_assert("diameter 0.5 -> 0.3" in worker_message, "Face 87 isolated diameter shrink should complete")
_assert_isolated_hole_resize_disables_stale_external_regions(model, 87, 0.3, Path(temp_dir))
with tempfile.TemporaryDirectory(prefix="icepak_face85_axis_isolated_") as temp_dir:
worker_message = _isolated_hole_axis_move(model, 85, axis_move_target, Path(temp_dir))
+705 -7
View File
@@ -9,7 +9,9 @@ import tempfile
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PySide6.QtCore import QEvent, QObject, QStringListModel
from PySide6.QtCore import QEvent, QObject, Qt, QStringListModel
from PySide6.QtGui import QKeyEvent
from PySide6.QtTest import QTest
from PySide6.QtWidgets import (
QApplication,
QCheckBox,
@@ -22,6 +24,7 @@ from PySide6.QtWidgets import (
QPushButton,
QScrollArea,
QTableWidget,
QTableWidgetItem,
QVBoxLayout,
QWidget,
)
@@ -31,6 +34,7 @@ if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from step_editor.widgets import NoWheelComboBox
from step_editor.relation_formulas import ObjectParameterRef, parse_relation_formula
from step_editor.window_actions import WindowActionMixin
from step_editor.window_core import WindowCoreMixin
from step_editor.window_state import (
@@ -40,6 +44,7 @@ from step_editor.window_state import (
PROPERTY_SCOPE_COLUMN,
PROPERTY_TABLE_HEADERS,
PROPERTY_TARGET_COLUMN,
RELATION_FORMULA_OBJECT_COMPLETION_LIMIT,
WindowStateMixin,
)
@@ -71,6 +76,7 @@ class _PropertyTableProbe(QWidget, WindowStateMixin):
self.selected_solid_id = None
self.selected_face_id = 0
self.selected_edge_id = None
self.selected_pick_position = None
self.step_path = PROJECT_ROOT / "assets" / "models" / "probe.step"
self.current_info_values = self._plane_info()
self.executed_property_actions: list[tuple[str, str]] = []
@@ -100,7 +106,14 @@ class _PropertyTableProbe(QWidget, WindowStateMixin):
self.relation_formula_input = QLineEdit()
self.relation_formula_completer_model = QStringListModel(self)
self.relation_formula_completer = QCompleter(self.relation_formula_completer_model, self)
self.relation_formula_input.setCompleter(self.relation_formula_completer)
self.relation_formula_completer.setMaxVisibleItems(12)
self.relation_formula_completer.activated[str].connect(self._on_relation_formula_completion_activated)
relation_popup = self.relation_formula_completer.popup()
if relation_popup is not None:
relation_popup.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self.relation_formula_completer.setWidget(self.relation_formula_input)
self.relation_formula_input.textChanged.connect(self._on_relation_formula_input_changed)
layout.addWidget(self.relation_formula_input)
self.add_relation_formula_button = QPushButton()
self.remove_relation_formula_button = QPushButton()
self.relation_formula_list = QListWidget()
@@ -136,6 +149,9 @@ class _PropertyTableProbe(QWidget, WindowStateMixin):
def _set_control_state(self, widget, enabled: bool, _enabled_tip: str, _disabled_tip: str) -> None:
widget.setEnabled(enabled)
def setTitle(self, title: str) -> None:
self.object_edit_title = title
def resize_face_width_local(self) -> None:
self.executed_property_actions.append(("resize_face_width_local", self.face_width_input.text()))
self._after_property_edit_finished(success=True)
@@ -148,6 +164,81 @@ class _PropertyTableProbe(QWidget, WindowStateMixin):
return _StatusBar()
class _RelationFormulaEventProbe(WindowCoreMixin, _PropertyTableProbe):
pass
class _GlobalRelationCompletionModel:
def __init__(self) -> None:
self.faces = [object() for _index in range(100)]
self.edges = [object() for _index in range(12)]
self.face_logical_ids = list(range(len(self.faces)))
self.face_region_call_count = 0
def face_logical_id(self, face_id: int) -> int:
return int(self.face_logical_ids[int(face_id)])
def face_region_logical_id(self, face_id: int) -> int:
self.face_region_call_count += 1
return int(face_id)
def resolve_face_selection_id(self, object_id: int) -> int | None:
if 0 <= int(object_id) < len(self.faces):
return int(object_id)
return None
def quick_face_info(self, face_id: int) -> dict[str, object]:
return {"surface": "cylinder" if int(face_id) in {85, 87} else "plane"}
class _LargeRelationCompletionModel(_GlobalRelationCompletionModel):
def __init__(self) -> None:
self.faces = [object() for _index in range(10000)]
self.edges = [object() for _index in range(1000)]
self.face_logical_ids = list(range(len(self.faces)))
self.face_region_call_count = 0
def face_region_logical_id(self, face_id: int) -> int:
self.face_region_call_count += 1
raise AssertionError("relation formula completion should not call face_region_logical_id")
class _RelationFormulaRemapModel:
def __init__(self, face_infos: dict[int, dict[str, object]], face_count: int = 120) -> None:
self.faces = [object() for _index in range(face_count)]
self.edges = []
self.face_logical_ids = list(range(face_count))
self.face_part_ids = [0 for _index in range(face_count)]
self.face_solid_ids = [0 for _index in range(face_count)]
self.face_infos = dict(face_infos)
def face_logical_id(self, face_id: int) -> int:
return int(self.face_logical_ids[int(face_id)])
def face_region_logical_id(self, face_id: int) -> int:
return self.face_logical_id(int(face_id))
def face_ids_for_logical_id(self, logical_id: int) -> list[int]:
return [face_id for face_id, item in enumerate(self.face_logical_ids) if int(item) == int(logical_id)]
def resolve_face_selection_id(self, object_id: int) -> int | None:
matches = self.face_ids_for_logical_id(int(object_id))
if matches:
return int(matches[0])
if 0 <= int(object_id) < len(self.faces):
return int(object_id)
return None
def quick_face_info(self, face_id: int) -> dict[str, object]:
info = dict(self.face_infos.get(int(face_id), {}))
if not info:
info = {"surface": "plane", "area_center": (1000.0 + int(face_id), 0.0, 0.0), "area": 1.0}
info.setdefault("part_id", 0)
info.setdefault("solid_id", 0)
info.setdefault("axis", (0.0, 0.0, 1.0))
return info
class _ActionMessageProbe(WindowActionMixin):
pass
@@ -463,26 +554,627 @@ def _assert_relation_formula_editor() -> None:
completions = set(probe.relation_formula_completer_model.stringList())
_assert("Face0.面内长度" in completions, f"relation formula completion missing face length: {completions}")
_assert("Face0.面内宽度" in completions, f"relation formula completion missing face width: {completions}")
probe.relation_formula_input.setText("F")
probe.relation_formula_input.setCursorPosition(1)
probe._update_relation_formula_completions()
f_completions = set(probe.relation_formula_completer_model.stringList())
_assert(
"Face" in f_completions or "Face0" in f_completions,
f"relation formula should suggest face object tokens after 'F': {f_completions}",
)
_assert(probe.relation_formula_completer.completionCount() > 0, "relation formula completer should have matches after 'F'")
_assert(probe._accept_relation_formula_completion(), "Tab should accept relation formula completion after 'F'")
_assert(
probe.relation_formula_input.text() in {"Face", "Face0"},
f"Tab should replace 'F' with a face completion: {probe.relation_formula_input.text()}",
)
if probe.relation_formula_input.text() == "Face":
probe._update_relation_formula_completions()
face_object_completions = set(probe.relation_formula_completer_model.stringList())
_assert(
"Face0" in face_object_completions,
f"relation formula should suggest concrete Face IDs after completing Face: {face_object_completions}",
)
_assert(probe._accept_relation_formula_completion(), "second Tab should accept a concrete Face ID completion")
_assert(
probe.relation_formula_input.text() == "Face0",
f"second Tab should complete Face to Face0: {probe.relation_formula_input.text()}",
)
probe._update_relation_formula_completions()
exact_object_completions = set(probe.relation_formula_completer_model.stringList())
_assert(
any(str(item).startswith("Face0.") for item in exact_object_completions),
f"concrete object completion should suggest editable parameters: {exact_object_completions}",
)
event_probe = _RelationFormulaEventProbe()
event_probe.show()
event_probe._refresh_property_editor()
event_probe.relation_formula_input.setFocus(Qt.FocusReason.OtherFocusReason)
QApplication.processEvents()
event_probe.relation_formula_input.setText("F")
event_probe.relation_formula_input.setCursorPosition(1)
tab_event = QKeyEvent(QEvent.Type.ShortcutOverride, Qt.Key.Key_Tab, Qt.KeyboardModifier.NoModifier)
_assert(
event_probe.eventFilter(event_probe.relation_formula_input, tab_event),
"relation formula input should consume Tab before Qt moves focus",
)
_assert(
event_probe.relation_formula_input.text() in {"Face", "Face0"},
f"ShortcutOverride Tab should accept the face completion: {event_probe.relation_formula_input.text()}",
)
QApplication.processEvents()
_assert(event_probe.relation_formula_input.hasFocus(), "relation formula input should keep focus after Tab completion")
popup = event_probe.relation_formula_completer.popup()
if popup is not None:
popup.installEventFilter(event_probe)
before_backspace = event_probe.relation_formula_input.text()
popup_backspace = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_Backspace, Qt.KeyboardModifier.NoModifier)
_assert(
event_probe.eventFilter(popup, popup_backspace),
"relation formula completer popup should forward Backspace to the input",
)
_assert(
event_probe.relation_formula_input.text() == before_backspace[:-1],
f"Backspace should edit the input even if popup receives it: {event_probe.relation_formula_input.text()}",
)
_assert(event_probe.relation_formula_input.hasFocus(), "relation formula input should keep focus after popup Backspace")
global_probe = _PropertyTableProbe()
global_probe.model = _GlobalRelationCompletionModel()
global_probe.selected_kind = "face"
global_probe.selected_face_id = 0
global_probe.show()
global_probe._refresh_property_editor()
global_probe.relation_formula_input.setFocus(Qt.FocusReason.OtherFocusReason)
QApplication.processEvents()
global_probe.model.face_region_call_count = 0
global_probe.relation_formula_input.setText("Face")
global_probe.relation_formula_input.setCursorPosition(len("Face"))
global_probe._update_relation_formula_completions()
global_probe._show_relation_formula_completion_popup()
QApplication.processEvents()
global_face_completion_list = list(global_probe.relation_formula_completer_model.stringList())
global_face_completions = set(global_face_completion_list)
_assert("Face85" in global_face_completions, f"global Face completion should include Face85: {global_face_completions}")
_assert("Face85." not in global_face_completions, f"object completion should not show trailing dot: {global_face_completions}")
_assert(
global_face_completion_list.index("Face2") < global_face_completion_list.index("Face10") < global_face_completion_list.index("Face85"),
f"Face completions should be sorted by numeric ID: {global_face_completion_list[:20]}",
)
global_probe.relation_formula_input.setText("Face8")
global_probe.relation_formula_input.setCursorPosition(len("Face8"))
global_probe._update_relation_formula_completions()
face8_completion_list = list(global_probe.relation_formula_completer_model.stringList())
_assert("Face87" in face8_completion_list, f"Face8 should still suggest longer Face IDs: {face8_completion_list[:20]}")
_assert(
face8_completion_list[0] != "Face8",
f"Face8 completion should prefer longer IDs instead of no-op self completion: {face8_completion_list[:20]}",
)
_assert(
face8_completion_list.index("Face80") < face8_completion_list.index("Face87"),
f"Face8 completions should keep numeric order: {face8_completion_list[:20]}",
)
face8_tab_probe = _PropertyTableProbe()
face8_tab_probe.model = _GlobalRelationCompletionModel()
face8_tab_probe.selected_kind = "face"
face8_tab_probe.selected_face_id = 0
face8_tab_probe._refresh_property_editor()
face8_tab_probe.relation_formula_input.setText("Face8")
face8_tab_probe.relation_formula_input.setCursorPosition(len("Face8"))
_assert(face8_tab_probe._accept_relation_formula_completion(), "Tab should accept a longer Face8 completion")
_assert(
face8_tab_probe.relation_formula_input.text().startswith("Face8")
and face8_tab_probe.relation_formula_input.text() != "Face8",
f"Tab after Face8 should complete to a longer ID, not stay unchanged: {face8_tab_probe.relation_formula_input.text()}",
)
face_tab_probe = _RelationFormulaEventProbe()
face_tab_probe.model = _GlobalRelationCompletionModel()
face_tab_probe.selected_kind = "face"
face_tab_probe.selected_face_id = 0
face_tab_probe.show()
face_tab_probe._refresh_property_editor()
face_tab_probe.relation_formula_input.setFocus(Qt.FocusReason.OtherFocusReason)
QApplication.processEvents()
face_tab_probe.relation_formula_input.setText("F")
face_tab_probe.relation_formula_input.setCursorPosition(len("F"))
face_tab_event = QKeyEvent(QEvent.Type.ShortcutOverride, Qt.Key.Key_Tab, Qt.KeyboardModifier.NoModifier)
_assert(face_tab_probe.eventFilter(face_tab_probe.relation_formula_input, face_tab_event), "F + Tab should be consumed")
for _index in range(4):
QApplication.processEvents()
_assert(face_tab_probe.relation_formula_input.text() == "Face", f"F + Tab should complete to Face: {face_tab_probe.relation_formula_input.text()}")
_assert("Face87" in set(face_tab_probe.relation_formula_completer_model.stringList()), "Face layer should suggest Face87 after F + Tab")
_assert(face_tab_probe.relation_formula_completer.popup().isVisible(), "Face layer popup should open after F + Tab")
_assert(
face_tab_probe.relation_formula_completer.popup().minimumWidth()
>= max(face_tab_probe.relation_formula_input.width(), 320),
"Face completion popup should be wide enough to show text",
)
face_manual_probe = _RelationFormulaEventProbe()
face_manual_probe.model = _GlobalRelationCompletionModel()
face_manual_probe.selected_kind = "face"
face_manual_probe.selected_face_id = 0
face_manual_probe.show()
face_manual_probe._refresh_property_editor()
face_manual_probe.relation_formula_input.setFocus(Qt.FocusReason.OtherFocusReason)
QApplication.processEvents()
face_manual_probe.relation_formula_input.setText("Face")
face_manual_probe.relation_formula_input.setCursorPosition(len("Face"))
for _index in range(4):
QApplication.processEvents()
_assert("Face87" in set(face_manual_probe.relation_formula_completer_model.stringList()), "manual Face input should suggest Face87")
_assert(face_manual_probe.relation_formula_completer.popup().isVisible(), "manual Face input should open the next completion layer")
_assert(
face_manual_probe.relation_formula_completer.popup().minimumWidth()
>= max(face_manual_probe.relation_formula_input.width(), 320),
"manual Face popup should not collapse to cursor width",
)
global_probe.relation_formula_input.setText("Face87")
global_probe.relation_formula_input.setCursorPosition(len("Face87"))
global_probe._update_relation_formula_completions()
face87_completion_list = list(global_probe.relation_formula_completer_model.stringList())
face87_ref = next((item for item in face87_completion_list if str(item).startswith("Face87.")), "")
_assert(face87_ref, f"exact Face ID should suggest editable parameters: {face87_completion_list}")
face87_radius_ref = next((item for item in face87_completion_list if str(item).endswith("半径")), "")
_assert(face87_radius_ref, f"exact Face ID should suggest radius: {face87_completion_list}")
radius_probe = _PropertyTableProbe()
radius_probe.model = _GlobalRelationCompletionModel()
radius_probe.selected_kind = "face"
radius_probe.selected_face_id = 0
radius_probe.show()
radius_probe._refresh_property_editor()
radius_probe.relation_formula_input.setFocus(Qt.FocusReason.OtherFocusReason)
QApplication.processEvents()
radius_probe.relation_formula_input.setText("Face87")
radius_probe.relation_formula_input.setCursorPosition(len("Face87"))
radius_probe._update_relation_formula_completions()
radius_probe._show_relation_formula_completion_popup()
QApplication.processEvents()
radius_completion_list = list(radius_probe.relation_formula_completer_model.stringList())
radius_row = radius_completion_list.index(face87_radius_ref)
radius_model = radius_probe.relation_formula_completer.model()
radius_popup = radius_probe.relation_formula_completer.popup()
radius_probe.relation_formula_completer.setCurrentRow(radius_row)
radius_popup.setCurrentIndex(radius_model.index(radius_row, 0))
_assert(radius_probe._accept_relation_formula_completion(), "Tab should accept the popup-selected radius completion")
_assert(
radius_probe.relation_formula_input.text() == face87_radius_ref,
f"Tab should keep the popup-selected completion, not reset to default: {radius_probe.relation_formula_input.text()}",
)
global_probe.relation_formula_input.setText(face87_ref)
global_probe.relation_formula_input.setCursorPosition(len(face87_ref))
global_probe._update_relation_formula_completions()
_assert(" = " in set(global_probe.relation_formula_completer_model.stringList()), "complete Face parameter should suggest '='")
global_probe._on_relation_formula_completion_activated(" = ")
_assert(
global_probe.relation_formula_input.text() == f"{face87_ref} = ",
f"mouse activation should insert '=' without replacing the whole formula: {global_probe.relation_formula_input.text()}",
)
global_probe.relation_formula_input.setText(face87_ref)
global_probe.relation_formula_input.setCursorPosition(len(face87_ref))
global_probe._update_relation_formula_completions()
global_probe.relation_formula_input.setText(f"{face87_ref} = F")
global_probe.relation_formula_input.setCursorPosition(len(f"{face87_ref} = F"))
global_probe._update_relation_formula_completions()
rhs_f_completion_list = list(global_probe.relation_formula_completer_model.stringList())
_assert(
"Face85" in rhs_f_completion_list and "Face87" in rhs_f_completion_list,
f"right-hand expression should suggest global Face IDs after F: {rhs_f_completion_list[:20]}",
)
global_probe.relation_formula_input.setText(f"{face87_ref} = Face8")
global_probe.relation_formula_input.setCursorPosition(len(f"{face87_ref} = Face8"))
global_probe._update_relation_formula_completions()
rhs_face8_completion_list = list(global_probe.relation_formula_completer_model.stringList())
_assert("Face87" in rhs_face8_completion_list, f"right-hand Face8 should suggest Face87: {rhs_face8_completion_list[:20]}")
global_probe.relation_formula_input.setText(f"{face87_ref} = Face87")
global_probe.relation_formula_input.setCursorPosition(len(f"{face87_ref} = Face87"))
global_probe._update_relation_formula_completions()
rhs_face87_completion_list = list(global_probe.relation_formula_completer_model.stringList())
_assert(
any(str(item).startswith("Face87.") for item in rhs_face87_completion_list),
f"right-hand exact Face ID should suggest parameters: {rhs_face87_completion_list}",
)
global_probe.relation_formula_input.setText("Edge")
global_probe.relation_formula_input.setCursorPosition(len("Edge"))
global_probe._update_relation_formula_completions()
global_edge_completion_list = list(global_probe.relation_formula_completer_model.stringList())
global_edge_completions = set(global_edge_completion_list)
_assert("Edge0" in global_edge_completions, f"Edge completion should be available even in Face selection mode: {global_edge_completions}")
_assert(
global_edge_completion_list.index("Edge2") < global_edge_completion_list.index("Edge10"),
f"Edge completions should be sorted by numeric ID: {global_edge_completion_list}",
)
_assert(
global_probe.model.face_region_call_count == 0,
f"relation formula typing should not call heavy face_region_logical_id: {global_probe.model.face_region_call_count}",
)
large_probe = _PropertyTableProbe()
large_probe.model = _LargeRelationCompletionModel()
large_probe.selected_kind = "face"
large_probe.selected_face_id = 0
large_probe._refresh_property_editor()
large_probe.model.face_region_call_count = 0
large_probe.relation_formula_input.setText("F")
large_probe.relation_formula_input.setCursorPosition(len("F"))
large_probe._update_relation_formula_completions()
large_completions = list(large_probe.relation_formula_completer_model.stringList())
_assert("Face87" in large_completions, f"large model F completion should still include common Face IDs: {large_completions[:20]}")
_assert(
len(large_completions) <= RELATION_FORMULA_OBJECT_COMPLETION_LIMIT + 2,
f"large model broad completion should be capped: {len(large_completions)}",
)
_assert(
large_probe.model.face_region_call_count == 0,
f"large model completion should not call heavy face_region_logical_id: {large_probe.model.face_region_call_count}",
)
probe.relation_formula_input.setText("Face0.")
probe.relation_formula_input.setCursorPosition(len("Face0."))
probe._update_relation_formula_completions()
face_completions = set(probe.relation_formula_completer_model.stringList())
_assert("Face0.面内长度" in face_completions, f"relation formula should suggest parameters after Face0.: {face_completions}")
probe.relation_formula_input.setText("Face0.面内宽度")
probe.relation_formula_input.setCursorPosition(len("Face0.面内宽度"))
probe._update_relation_formula_completions()
_assert(" = " in set(probe.relation_formula_completer_model.stringList()), "relation formula should suggest '=' after a target parameter")
probe.relation_formula_input.setText("Face0.面内宽度 = Face0.面内长度 * 1.2")
probe.add_relation_formula()
_assert(len(probe.relation_formula_items) == 1, f"formula was not stored: {probe.relation_formula_items}")
_assert(probe.relation_formula_list.count() == 1, "formula list should display the stored formula")
probe._update_property_apply_state()
_assert(probe.apply_property_button.isEnabled(), "formula targeting current table should enable parametric modeling")
probe.apply_current_property_edit()
for _index in range(6):
QApplication.processEvents()
if not getattr(probe, "property_batch_active", False):
if not getattr(probe, "relation_formula_replay_active", False):
break
_assert(
probe.executed_property_actions == [("resize_face_height_local", "12")],
f"formula should fill target value and execute the existing row action: {probe.executed_property_actions}",
f"formula should immediately fill target value and execute the existing row action: {probe.executed_property_actions}",
)
width_row = _row_by_label(probe, "面内宽度")
width_widget = probe.property_table.cellWidget(width_row, PROPERTY_TARGET_COLUMN)
_assert(isinstance(width_widget, QLineEdit), "formula target row should still have a target editor")
_assert(width_widget.text().strip() == "12", "formula result should be written back to the target value cell")
_assert(str(probe.relation_formula_items[0].get("status")) == "applied", "formula should be marked as applied")
for _index in range(4):
QApplication.processEvents()
_assert(probe.relation_formula_input.isEnabled(), "relation formula input should stay enabled after one formula is applied")
probe.relation_formula_input.setText("F")
_assert(probe.add_relation_formula_button.isEnabled(), "existing formulas should not disable adding another formula")
def _assert_relation_radius_formula_proxy() -> None:
probe = _PropertyTableProbe()
probe.model = _GlobalRelationCompletionModel()
probe.selected_kind = "face"
probe.selected_face_id = 87
probe.property_table.setRowCount(1)
probe.property_editor_specs = [
{
"key": "diameter",
"label": "直径",
"current_raw": 1.0,
"current_text": "1",
"target_text": "1",
"editable": True,
"enabled": True,
"action": "resize_hole",
"value_type": "number",
"source_face_id": 87,
}
]
probe.property_table.setItem(0, PROPERTY_LABEL_COLUMN, QTableWidgetItem("直径"))
target = QLineEdit("1")
probe.property_table.setCellWidget(0, PROPERTY_TARGET_COLUMN, target)
ref = ObjectParameterRef("Face", 87, "半径")
visible = probe._relation_visible_spec_for_ref(ref)
_assert(visible is not None, "Face radius formula should fall back to an executable diameter row")
_assert(
abs(float(probe._relation_value_for_ref(ref)) - 0.5) <= 1e-9,
f"radius formula should read half of the diameter: {probe._relation_value_for_ref(ref)}",
)
row = probe._set_relation_target_value(ref, 0.6)
_assert(row == 0, f"radius proxy should write to the diameter row: {row}")
_assert(target.text() == "1.2", f"radius proxy should convert target radius to diameter: {target.text()}")
def _assert_relation_formula_input_remains_editable_while_replaying() -> None:
probe = _PropertyTableProbe()
probe.model = _GlobalRelationCompletionModel()
probe.selected_kind = "face"
probe.selected_face_id = 87
probe._refresh_property_editor()
probe.operation_in_progress = True
probe.relation_formula_replay_active = True
probe.relation_formula_replay_queue = []
probe.relation_formula_replay_total = 1
probe.relation_formula_input.setEnabled(False)
probe._update_relation_formula_buttons()
_assert(probe.relation_formula_input.isEnabled(), "relation formula input should stay editable while formulas replay")
probe.relation_formula_input.setText("Face87.半径 = Face85.半径")
probe.add_relation_formula()
_assert(len(probe.relation_formula_items) == 1, f"formula should be stored while replaying: {probe.relation_formula_items}")
_assert(len(probe.relation_formula_replay_queue) == 1, f"formula should be queued while replaying: {probe.relation_formula_replay_queue}")
_assert(probe.relation_formula_input.text() == "", "queued formula should clear the input for the next formula")
_assert(probe.relation_formula_input.isEnabled(), "relation formula input should remain enabled after queuing")
_assert(probe.relation_formula_replay_active, "adding a queued formula should not stop the current replay")
def _assert_relation_formula_input_clickable_after_existing_formula() -> None:
probe = _RelationFormulaEventProbe()
probe.relation_formula_input.installEventFilter(probe)
probe.show()
probe._refresh_property_editor()
probe.relation_formula_input.setText("Face0.面内宽度 = Face0.面内长度 * 1.2")
probe.add_relation_formula()
for _index in range(12):
QApplication.processEvents()
if not getattr(probe, "relation_formula_replay_active", False):
break
_assert(len(probe.relation_formula_items) == 1, f"formula should exist before click test: {probe.relation_formula_items}")
_assert(str(probe.relation_formula_items[0].get("status")) == "applied", "formula should be applied before click test")
probe.activateWindow()
probe.raise_()
QApplication.processEvents()
QTest.mouseClick(probe.relation_formula_input, Qt.MouseButton.LeftButton)
QApplication.processEvents()
_assert(probe.relation_formula_input.isEnabled(), "relation formula input should stay enabled with an existing formula")
_assert(probe.relation_formula_input.hasFocus(), "clicking the relation formula input should focus it with an existing formula")
probe.relation_formula_input.setText("F")
_assert(probe.add_relation_formula_button.isEnabled(), "clickable input should allow adding another formula")
def _assert_relation_formula_input_is_selection_independent() -> None:
probe = _RelationFormulaEventProbe()
probe.relation_formula_input.installEventFilter(probe)
probe.model = _GlobalRelationCompletionModel()
probe.selected_kind = None
probe.selected_face_id = None
probe.selected_edge_id = None
probe.relation_formula_items = []
probe.relation_formula_input.setEnabled(False)
probe._refresh_relation_formula_list()
_assert(probe.relation_formula_input.isEnabled(), "relation formula input should be editable without a selected face")
_assert(not probe.relation_formula_input.isReadOnly(), "relation formula input should not become read-only without a selected face")
_assert(not probe.add_relation_formula_button.isEnabled(), "empty relation formula should not enable the add button")
probe.show()
probe.activateWindow()
probe.raise_()
QApplication.processEvents()
QTest.mouseClick(probe.relation_formula_input, Qt.MouseButton.LeftButton)
QApplication.processEvents()
probe.relation_formula_input.setText("Face8")
probe.relation_formula_input.setCursorPosition(len("Face8"))
probe._update_relation_formula_completions()
object_completions = list(probe.relation_formula_completer_model.stringList())
_assert("Face85" in object_completions and "Face87" in object_completions, f"global face completions should not require selection: {object_completions[:20]}")
probe.relation_formula_input.setText("Face85")
probe.relation_formula_input.setCursorPosition(len("Face85"))
probe._update_relation_formula_completions()
parameter_completions = list(probe.relation_formula_completer_model.stringList())
_assert(any(str(item).startswith("Face85.") for item in parameter_completions), f"Face parameter completions should not require selection: {parameter_completions}")
def _assert_relation_formula_input_recovers_after_loading() -> None:
probe = _RelationFormulaEventProbe()
probe.model = _GlobalRelationCompletionModel()
probe.mode_combo = NoWheelComboBox()
probe.mode_combo.addItem("Face", "Face")
probe.load_thread = None
probe.load_refine_thread = None
probe.pending_load_path = None
probe.load_in_progress = True
probe._update_relation_formula_buttons()
_assert(not probe.relation_formula_input.isEnabled(), "relation formula input should be disabled while loading")
probe._end_load_task()
_assert(probe.relation_formula_input.isEnabled(), "relation formula input should recover after loading without selecting a face")
def _assert_relation_formula_ids_follow_model_remap() -> None:
old_model = _RelationFormulaRemapModel(
{
85: {"surface": "cylinder", "area_center": (0.5, 1.0, 9.5), "bbox_center": (0.5, 1.0, 9.5), "radius": 0.25, "area": 8.0},
87: {"surface": "cylinder", "area_center": (2.0, 1.0, 9.5), "bbox_center": (2.0, 1.0, 9.5), "radius": 0.25, "area": 8.0},
}
)
new_model = _RelationFormulaRemapModel(
{
90: {"surface": "cylinder", "area_center": (0.5, 1.0, 9.5), "bbox_center": (0.5, 1.0, 9.5), "radius": 0.25, "area": 8.0},
92: {"surface": "cylinder", "area_center": (2.0, 1.0, 9.5), "bbox_center": (2.0, 1.0, 9.5), "radius": 0.15, "area": 5.0},
}
)
probe = _PropertyTableProbe()
formula_text = "Face87.直径 = Face85.半径"
probe.model = old_model
formula = parse_relation_formula(formula_text)
probe.relation_formula_items = [
{
"id": 1,
"text": formula_text,
"enabled": True,
"status": "applied",
"message": "已修改模型。",
"signatures": probe._relation_signatures_for_formula(formula),
}
]
probe.model = new_model
note = probe._refresh_relation_formulas_after_model_edit()
_assert(
probe.relation_formula_items[0]["text"] == "Face92.直径 = Face90.半径",
f"relation formula should follow remapped visible Face IDs: {probe.relation_formula_items}",
)
_assert("Face ID" in note, f"relation remap note should mention updated Face IDs: {note}")
old_model = _RelationFormulaRemapModel(
{
85: {"surface": "cylinder", "area_center": (0.5, 1.0, 9.5), "bbox_center": (0.5, 1.0, 9.5), "radius": 0.25, "area": 8.0},
87: {"surface": "cylinder", "area_center": (0.5, 1.0, 0.5), "bbox_center": (0.5, 1.0, 0.5), "radius": 0.25, "area": 8.0},
}
)
new_model = _RelationFormulaRemapModel(
{
85: {"surface": "cylinder", "area_center": (0.5, 1.0, 9.5), "bbox_center": (0.5, 1.0, 9.5), "radius": 0.125, "area": 4.0},
11: {"surface": "cylinder", "area_center": (0.5, 1.0, 0.5), "bbox_center": (0.5, 1.0, 0.5), "radius": 0.25, "area": 8.0},
}
)
probe = _PropertyTableProbe()
formula_text = "Face85.直径 = Face87.半径"
probe.model = old_model
formula = parse_relation_formula(formula_text)
probe.relation_formula_items = [
{
"id": 1,
"text": formula_text,
"enabled": True,
"status": "applied",
"message": "已修改模型。",
"signatures": probe._relation_signatures_for_formula(formula),
}
]
probe.model = new_model
probe._refresh_relation_formulas_after_model_edit()
_assert(
probe.relation_formula_items[0]["text"] == "Face85.直径 = Face11.半径",
f"relation formula should remap a changed reference ID even when the target ID is preserved: {probe.relation_formula_items}",
)
old_model = _RelationFormulaRemapModel(
{
85: {"surface": "cylinder", "area_center": (0.5, 1.0, 9.5), "bbox_center": (0.5, 1.0, 9.5), "radius": 0.25, "area": 8.0},
87: {"surface": "cylinder", "area_center": (0.5, 1.0, 0.5), "bbox_center": (0.5, 1.0, 0.5), "radius": 0.25, "area": 8.0},
}
)
new_model = _RelationFormulaRemapModel(
{
85: {"surface": "cylinder", "area_center": (0.5, 1.0, 0.5), "bbox_center": (0.5, 1.0, 0.5), "radius": 0.5, "area": 16.0},
89: {"surface": "cylinder", "area_center": (0.5, 1.0, 9.5), "bbox_center": (0.5, 1.0, 9.5), "radius": 0.125, "area": 4.0},
91: {"surface": "cylinder", "area_center": (0.5, 1.0, 0.5), "bbox_center": (0.5, 1.0, 0.5), "radius": 0.25, "area": 8.0},
}
)
new_model.face_logical_ids[89] = 85
probe = _PropertyTableProbe()
formula_text = "Face85.直径 = Face87.半径"
probe.model = old_model
formula = parse_relation_formula(formula_text)
probe.relation_formula_items = [
{
"id": 1,
"text": formula_text,
"enabled": True,
"status": "applied",
"message": "已修改模型。",
"signatures": probe._relation_signatures_for_formula(formula),
}
]
probe.model = new_model
probe._refresh_relation_formulas_after_model_edit()
_assert(
probe.relation_formula_items[0]["text"] == "Face85.直径 = Face91.半径",
f"relation remap should prefer the old reference radius over a same-center changed target: {probe.relation_formula_items}",
)
old_model = _RelationFormulaRemapModel(
{
9: {"surface": "cylinder", "area_center": (0.5, 1.0, 9.5), "bbox_center": (0.5, 1.0, 9.5), "radius": 0.25, "area": 8.0},
87: {"surface": "cylinder", "area_center": (0.5, 1.0, 0.5), "bbox_center": (0.5, 1.0, 0.5), "radius": 0.15, "area": 4.8},
}
)
new_model = _RelationFormulaRemapModel(
{
14: {"surface": "cylinder", "area_center": (0.5, 1.0, 9.5), "bbox_center": (0.5, 1.0, 9.5), "radius": 0.4, "area": 12.8},
87: {"surface": "cylinder", "area_center": (0.5, 1.0, 0.5), "bbox_center": (0.5, 1.0, 0.5), "radius": 0.15, "area": 4.8},
}
)
new_model.face_logical_ids[14] = 9
probe = _PropertyTableProbe()
formula_text = "Face87.直径 = Face9.半径"
probe.model = old_model
formula = parse_relation_formula(formula_text)
probe.relation_formula_items = [
{
"id": 1,
"text": formula_text,
"enabled": True,
"status": "applied",
"message": "已修改模型。",
"signatures": probe._relation_signatures_for_formula(formula),
}
]
probe.model = new_model
probe._refresh_relation_formulas_after_model_edit(
context={"target_kind": "face", "target_id": 9, "target_logical_id": 9}
)
_assert(
probe.relation_formula_items[0]["text"] == "Face87.直径 = Face9.半径",
f"edited right-hand dependency should keep its logical Face ID instead of becoming invalid: {probe.relation_formula_items}",
)
_assert(
str(probe.relation_formula_items[0].get("status")) != "invalid",
f"edited right-hand dependency should remain valid: {probe.relation_formula_items}",
)
_assert(
getattr(probe, "_last_relation_formula_refresh_affected_ids", []) == [1],
f"edited right-hand dependency should mark the formula for reapply: {probe.relation_formula_items}",
)
old_model = _RelationFormulaRemapModel(
{
9: {"surface": "cylinder", "area_center": (0.5, 1.0, 9.5), "bbox_center": (0.5, 1.0, 9.5), "radius": 0.25, "area": 8.0},
87: {"surface": "cylinder", "area_center": (2.0, 1.0, 9.5), "bbox_center": (2.0, 1.0, 9.5), "radius": 0.125, "area": 4.0},
}
)
new_model = _RelationFormulaRemapModel(
{
1: {"surface": "cylinder", "area_center": (2.0, 1.0, 9.5), "bbox_center": (2.0, 1.0, 9.5), "radius": 0.5, "area": 16.0},
9: {"surface": "cylinder", "area_center": (0.5, 1.0, 9.5), "bbox_center": (0.5, 1.0, 9.5), "radius": 0.4, "area": 12.8},
11: {"surface": "cylinder", "area_center": (2.0, 1.0, 9.5), "bbox_center": (2.0, 1.0, 9.5), "radius": 0.125, "area": 4.0},
}
)
new_model.face_logical_ids[9] = 9
probe = _PropertyTableProbe()
formula_text = "Face87.\u76f4\u5f84 = Face9.\u534a\u5f84"
probe.model = old_model
formula = parse_relation_formula(formula_text)
probe.relation_formula_items = [
{
"id": 1,
"text": formula_text,
"enabled": True,
"status": "applied",
"message": "applied",
"signatures": probe._relation_signatures_for_formula(formula),
}
]
probe.model = new_model
probe._refresh_relation_formulas_after_model_edit(
context={"target_kind": "face", "target_id": 9, "target_logical_id": 9}
)
_assert(
probe.relation_formula_items[0]["text"] == "Face11.\u76f4\u5f84 = Face9.\u534a\u5f84",
f"unchanged formula target should stay strict while a dependency changes: {probe.relation_formula_items}",
)
_assert(
str(probe.relation_formula_items[0].get("status")) != "invalid",
f"strict target remap should keep the dependency formula valid: {probe.relation_formula_items}",
)
_assert(
getattr(probe, "_last_relation_formula_refresh_affected_ids", []) == [1],
f"edited dependency should still queue the formula for reapply after target remap: {probe.relation_formula_items}",
)
def _unexpected_restore() -> bool:
raise AssertionError("dependency relation replay must not restore the formula base snapshot")
probe.relation_formula_base_snapshot = {"sentinel": object()}
probe._restore_relation_formula_base_snapshot = _unexpected_restore
probe._start_relation_formula_reapply(reason="dependency", restore_base=False, formula_ids=[1])
_assert(
[int(item.get("id", -1)) for item in probe.relation_formula_replay_queue] == [1],
f"dependency replay should queue only the affected formula: {probe.relation_formula_replay_queue}",
)
probe.relation_formula_replay_active = False
probe.relation_formula_replay_queue = []
def _assert_mouse_selection_guards() -> None:
@@ -724,6 +1416,12 @@ def main() -> int:
_assert_diagnostics_stay_out_of_parameter_table(probe)
_assert_relation_formula_editor()
_assert_relation_radius_formula_proxy()
_assert_relation_formula_input_remains_editable_while_replaying()
_assert_relation_formula_input_clickable_after_existing_formula()
_assert_relation_formula_input_is_selection_independent()
_assert_relation_formula_input_recovers_after_loading()
_assert_relation_formula_ids_follow_model_remap()
_assert_mouse_selection_guards()
_assert_quick_blind_depth_spec()
_assert_user_facing_failure_messages()
+20 -3
View File
@@ -287,6 +287,16 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
self.property_command_buttons: dict[str, QPushButton] = {}
self.relation_formula_items: list[dict[str, object]] = []
self.relation_formula_next_id = 1
self.relation_formula_base_snapshot: dict[object, object] | None = None
self.relation_formula_replay_queue: list[dict[str, object]] = []
self.relation_formula_replay_total = 0
self.relation_formula_replay_done = 0
self.relation_formula_replay_active = False
self.relation_formula_replay_current_id: int | None = None
self.relation_formula_replay_callback_seen = False
self._relation_formula_replay_running_action = False
self._relation_formula_object_label_cache_key: object = None
self._relation_formula_object_label_cache: dict[str, object] = {}
self._build_ui()
self._build_vtk()
@@ -1292,7 +1302,7 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
self.relation_formula_box.setObjectName("relationFormulaBox")
help_tip(
self.relation_formula_box,
"用 FaceID.参数 = 表达式 的形式建立关系式。第一版会先计算公式并回填当前参数表目标值,再执行参数化建模",
"用 FaceID.参数 = 表达式 的形式建立关系式。添加后会立即按当前公式组重新计算并修改模型",
)
relation_layout = QVBoxLayout(self.relation_formula_box)
relation_layout.setContentsMargins(6, 8, 6, 6)
@@ -1305,13 +1315,20 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
self.relation_formula_input.setPlaceholderText("Face87.直径 = Face85.直径")
help_tip(
self.relation_formula_input,
"示例:Face87.直径 = Face85.直径,或 Face87.位置 = Face85.位置 + (0, 0, -3.5)。输入 Face87. 后会提示当前可用参数。",
"示例:Face87.直径 = Face85.直径,或 Face87.位置 = Face85.位置 + (0, 0, -3.5)。输入 Face87. 后会提示可用参数。",
)
self.relation_formula_completer_model = QStringListModel(self)
self.relation_formula_completer = QCompleter(self.relation_formula_completer_model, self)
self.relation_formula_completer.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
self.relation_formula_completer.setFilterMode(Qt.MatchFlag.MatchStartsWith)
self.relation_formula_completer.setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
self.relation_formula_input.setCompleter(self.relation_formula_completer)
self.relation_formula_completer.setMaxVisibleItems(12)
self.relation_formula_completer.activated[str].connect(self._on_relation_formula_completion_activated)
relation_popup = self.relation_formula_completer.popup()
if relation_popup is not None:
relation_popup.setFocusPolicy(Qt.FocusPolicy.NoFocus)
relation_popup.installEventFilter(self)
self.relation_formula_completer.setWidget(self.relation_formula_input)
self.relation_formula_input.installEventFilter(self)
self.relation_formula_input.textChanged.connect(self._on_relation_formula_input_changed)
self.relation_formula_input.returnPressed.connect(self.add_relation_formula)
+18
View File
@@ -4808,6 +4808,24 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
self._editable_feature_candidates_cache.clear()
self._cylindrical_feature_candidates_cache.clear()
def mark_external_recognition_stale(self, reason: str = "modified-topology") -> None:
self._topology_refresh_generation = max(int(getattr(self, "_topology_refresh_generation", 0) or 0), 2)
self._asitus_hole_regions_attempted = False
self._asitus_hole_regions_loading = False
self._asitus_hole_region_cache.clear()
self._asitus_face_relation_cache.clear()
self._asitus_adjacency_relation_cache.clear()
self._asitus_geometric_relation_cache.clear()
self._asitus_hole_recognition_info.clear()
self._asitus_hole_recognition_info.update(
{
"ok": False,
"reason": str(reason or "modified-topology"),
"message": "Analysis Situs result ignored because the model topology has changed.",
}
)
self._clear_same_domain_dependent_caches()
def _install_asitus_relation_summary(self, result: dict[str, object]) -> None:
faces = result.get("faces", ())
if isinstance(faces, (tuple, list)):
+17 -1
View File
@@ -8257,6 +8257,10 @@ class WindowActionMixin:
new_model.filename = self.step_path
except Exception:
pass
try:
new_model.mark_external_recognition_stale("isolated-edit-result")
except Exception:
pass
child_message = str(response.get("message") or "隔离子进程编辑完成。")
self._preserve_isolated_face_logical_id(new_model, context, child_message)
started = time.perf_counter()
@@ -9186,8 +9190,11 @@ class WindowActionMixin:
timings["finish_ui"] = time.perf_counter() - finish_started
locator_note = self._locate_operation_record(record)
relation_note = ""
relation_dependency_ids: list[int] = []
relation_replay_active = bool(getattr(self, "relation_formula_replay_active", False))
if hasattr(self, "_refresh_relation_formulas_after_model_edit"):
relation_note = self._refresh_relation_formulas_after_model_edit()
relation_note = self._refresh_relation_formulas_after_model_edit(context=context)
relation_dependency_ids = list(getattr(self, "_last_relation_formula_refresh_affected_ids", []) or [])
if relation_note:
locator_note = f"{locator_note}\n{relation_note}" if locator_note else relation_note
except Exception as exc:
@@ -9220,6 +9227,15 @@ class WindowActionMixin:
self.set_plain_info(f"{record.detail}{timing_detail}\n\n{locator_note}")
if hasattr(self, "_after_property_edit_finished"):
self._after_property_edit_finished(success=True)
if (
relation_dependency_ids
and not relation_replay_active
and hasattr(self, "_queue_relation_formula_dependency_reapply")
and hasattr(self, "_run_pending_relation_formula_dependency_reapply")
):
self._queue_relation_formula_dependency_reapply(relation_dependency_ids)
if not bool(getattr(self, "property_batch_active", False)):
self._run_pending_relation_formula_dependency_reapply()
@Slot(str)
def _fail_edit_action(self, message: str) -> None:
+43 -1
View File
@@ -179,12 +179,48 @@ 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 self._relation_formula_popup_widget():
if event.type() == QEvent.Type.KeyPress and event.key() == Qt.Key.Key_Backspace:
editor = getattr(self, "relation_formula_input", None)
popup = self._relation_formula_popup_widget()
if editor is not None and hasattr(editor, "backspace"):
if popup is not None and hasattr(popup, "hide"):
popup.hide()
if hasattr(editor, "setFocus"):
editor.setFocus(Qt.FocusReason.OtherFocusReason)
editor.backspace()
if hasattr(self, "_update_relation_formula_completions"):
QTimer.singleShot(0, self._update_relation_formula_completions)
event.accept()
return True
elif watched is getattr(self, "relation_formula_input", None):
if event.type() == QEvent.Type.KeyPress and event.key() == Qt.Key.Key_Tab:
if event.type() == QEvent.Type.MouseButtonPress:
if hasattr(self, "_hide_relation_formula_completion_popup"):
self._hide_relation_formula_completion_popup()
if hasattr(watched, "setFocus"):
watched.setFocus(Qt.FocusReason.MouseFocusReason)
if event.type() in {QEvent.Type.ShortcutOverride, QEvent.Type.KeyPress} and event.key() == Qt.Key.Key_Tab:
if bool(getattr(self, "_relation_formula_tab_completion_accepted", False)):
self._relation_formula_tab_completion_accepted = False
event.accept()
return True
if hasattr(self, "_accept_relation_formula_completion") and self._accept_relation_formula_completion():
self._relation_formula_tab_completion_accepted = event.type() == QEvent.Type.ShortcutOverride
if self._relation_formula_tab_completion_accepted:
QTimer.singleShot(0, lambda: setattr(self, "_relation_formula_tab_completion_accepted", False))
event.accept()
return True
return super().eventFilter(watched, event)
def _relation_formula_popup_widget(self):
completer = getattr(self, "relation_formula_completer", None)
if completer is None or not hasattr(completer, "popup"):
return None
try:
return completer.popup()
except RuntimeError:
return None
def _should_suppress_transient_tooltip(self, watched) -> bool:
if not isinstance(watched, QWidget):
return False
@@ -1274,6 +1310,8 @@ class WindowCoreMixin:
self.load_in_progress = False
self.pending_load_path = None
self._update_action_states()
if hasattr(self, "_update_relation_formula_buttons"):
self._update_relation_formula_buttons()
if float(getattr(self, "preview_load_deflection", 0.0) or 0.0) > float(
getattr(self, "initial_load_deflection", 0.0) or 0.0
):
@@ -1337,6 +1375,8 @@ class WindowCoreMixin:
self.load_in_progress = False
self.pending_load_path = None
self._update_action_states()
if hasattr(self, "_update_relation_formula_buttons"):
self._update_relation_formula_buttons()
self._request_thread_quit(self.load_thread)
self._request_thread_quit(self.load_refine_thread)
@@ -1387,6 +1427,8 @@ class WindowCoreMixin:
self.redo_stack.clear()
self.operation_history.clear()
self.redo_history.clear()
if hasattr(self, "_clear_relation_formula_runtime_state"):
self._clear_relation_formula_runtime_state(clear_items=True)
self.clear_diff_preview(render=False)
if hasattr(self, "history_list"):
self.history_list.clear()
File diff suppressed because it is too large Load Diff