feat: 完善一级关系编辑 UI 与视图体验

This commit is contained in:
2026-08-06 18:15:14 +08:00
parent 57d75541c3
commit eef9efcc1e
29 changed files with 3907 additions and 178 deletions
+256
View File
@@ -0,0 +1,256 @@
from __future__ import annotations
import os
from pathlib import Path
import sys
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PySide6.QtCore import QEvent, QObject
from PySide6.QtWidgets import (
QApplication,
QFrame,
QHBoxLayout,
QLabel,
QLineEdit,
QPushButton,
QScrollArea,
QTableWidget,
QVBoxLayout,
QWidget,
)
PROJECT_ROOT = Path(__file__).resolve().parent.parent
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from step_editor.widgets import NoWheelComboBox
from step_editor.window_state import WindowStateMixin
class _StatusBar:
def showMessage(self, _text: str) -> None:
pass
class _PropertyCardProbe(QWidget, WindowStateMixin):
def __init__(self) -> None:
super().__init__()
self.model = object()
self.operation_in_progress = False
self.scan_in_progress = False
self.load_in_progress = False
self.property_editor_updating = False
self.property_table_expanded = False
self.property_table_collapsed_rows = 4
self.property_editor_selected_row = None
self.property_command_active_key = ""
self.property_command_buttons = {}
self.property_editor_specs = []
self.selected_kind = "face"
self.selected_part_id = None
self.selected_solid_id = None
self.selected_face_id = 0
self.selected_edge_id = None
self.current_info_values = {"area": 100.0}
self.property_table = QTableWidget(0, 5)
self.property_card_scroll = QScrollArea()
self.property_card_container = QWidget()
self.property_card_layout = QVBoxLayout(self.property_card_container)
self.property_card_scroll.setWidget(self.property_card_container)
self.property_command_summary_label = QLabel()
self.property_command_bar = QFrame()
self.property_command_layout = QHBoxLayout(self.property_command_bar)
self.apply_property_button = QPushButton()
def _selected_action_info(self) -> dict[str, object]:
return {
"area": 100.0,
"surface": "plane",
"push_pull_status": "ready",
"first_level_boundary_edge_count": 4,
"first_level_boundary_vertex_count": 4,
"first_level_adjacent_face_count": 4,
}
def _set_control_state(self, widget, enabled: bool, _enabled_tip: str, _disabled_tip: str) -> None:
widget.setEnabled(enabled)
def statusBar(self) -> _StatusBar:
return _StatusBar()
class _TopLevelPropertyLabelProbe(QObject):
def __init__(self) -> None:
super().__init__()
self.shown_labels: list[str] = []
def eventFilter(self, watched, event):
if (
event.type() == QEvent.Type.Show
and isinstance(watched, QLabel)
and watched.isWindow()
and watched.objectName().startswith("propertyCard")
):
self.shown_labels.append(f"{type(watched).__name__}:{watched.objectName()}:{watched.text()}")
return False
def _assert(condition: bool, message: str) -> None:
if not condition:
raise AssertionError(message)
def main() -> int:
app = QApplication.instance() or QApplication([])
top_level_label_probe = _TopLevelPropertyLabelProbe()
app.installEventFilter(top_level_label_probe)
probe = _PropertyCardProbe()
probe.show()
QApplication.processEvents()
probe._refresh_property_editor()
QApplication.processEvents()
_assert(
not top_level_label_probe.shown_labels,
f"property card labels were shown as transient top-level windows: {top_level_label_probe.shown_labels}",
)
actionable_labels = [str(spec.get("label", "")) for _row, spec in probe._actionable_property_rows()]
_assert("面积" in actionable_labels, "modifiable feature list did not expose the editable area row")
_assert(not probe.property_command_buttons, "legacy command buttons should not be shown in the modifiable-feature panel")
_assert(not probe.property_command_bar.isVisible(), "legacy command bar should be hidden")
_assert("可修改项" in probe.property_command_summary_label.text(), "modifiable-feature summary is missing")
rows = getattr(probe, "property_card_rows", {})
_assert(rows, "property card rows were not built")
_assert(
not any(isinstance(widgets.get("target_editor"), QLineEdit) for widgets in rows.values()),
"modifiable feature rows should be compact until the user expands one",
)
_assert(
all(str(widgets.get("status_label").text()) == "可修改" for widgets in rows.values()),
"compact modifiable rows should end with the editable status label",
)
_assert(
all(not str(widgets.get("current_value").toolTip()) for widgets in rows.values() if widgets.get("current_value") is not None),
"compact modifiable rows should not show click-triggered tooltips",
)
target_row = probe._actionable_property_rows()[0][0]
probe._select_property_card_row(target_row)
rows = getattr(probe, "property_card_rows", {})
editable_rows = [row for row, widgets in rows.items() if isinstance(widgets.get("action_button"), QPushButton)]
target_rows = [row for row, widgets in rows.items() if isinstance(widgets.get("target_editor"), QLineEdit)]
scope_rows = [row for row, widgets in rows.items() if isinstance(widgets.get("scope_combo"), NoWheelComboBox)]
_assert(editable_rows, "editable property card button is missing")
_assert(target_rows, "property card target editor is missing")
_assert(scope_rows, "property card modeling-intent combo is missing")
probe._toggle_property_card_row(target_row)
QApplication.processEvents()
rows = getattr(probe, "property_card_rows", {})
_assert(probe.property_editor_selected_row is None, "clicking an expanded row should collapse it")
_assert(
not any(isinstance(widgets.get("target_editor"), QLineEdit) for widgets in rows.values()),
"collapsed modifiable rows should return to compact display",
)
_assert(
not any(editor.isVisible() for editor in probe.property_card_container.findChildren(QLineEdit)),
"collapsed modifiable rows should not leave stale target editors visible",
)
_assert(
not any(
label.isVisible() and "建模意图:" in label.text()
for label in probe.property_card_container.findChildren(QLabel)
),
"collapsed modifiable rows should not leave stale expanded hints visible",
)
probe._select_property_card_row(target_row)
rows = getattr(probe, "property_card_rows", {})
target_rows = [row for row, widgets in rows.items() if isinstance(widgets.get("target_editor"), QLineEdit)]
_assert(target_rows, "target editor is missing after re-expanding the row")
rows[target_rows[0]]["target_editor"].setText("144")
probe._update_property_apply_state()
changed = probe._changed_property_rows()
button = rows[target_rows[0]]["action_button"]
_assert(changed, "card target edit was not detected")
_assert(bool(button.property("changed")), "card button did not enter changed state")
_assert(button.isEnabled(), "card button should be enabled for a valid changed target")
probe.toggle_property_table_expanded()
_assert(probe.property_table_expanded, "property card expand toggle failed")
_assert(len(probe.property_card_rows) > len(rows), "expanded property card list did not reveal more rows")
expanded_editor = probe.property_card_rows[target_rows[0]].get("target_editor")
_assert(isinstance(expanded_editor, QLineEdit), "expanded target editor is missing")
_assert(expanded_editor.text().strip() == "144", "target value was not preserved after card rebuild")
long_context = "已按“相邻特征”沿共享边拓扑探测当前特征及 3 个局部关联特征;关联尺寸可在同一参数表中直接修改。"
probe.property_editor_specs = [
{
"key": "feature_context_note",
"label": "关联探测",
"current_text": long_context,
"current_raw": long_context,
"target_text": "",
"editable": False,
"enabled": False,
"status_text": "说明",
"span_value_columns": True,
"pin_top": True,
},
{
"key": "associated_face_center",
"label": "相邻平面 Face 1580 · 中心",
"current_text": "(-118.585, -23.3, -269.561)",
"current_raw": "(-118.585, -23.3, -269.561)",
"target_text": "(-118.585, -23.3, -269.561)",
"editable": True,
"enabled": True,
"action": "move_face_center",
"scope_text": "局部重建",
"status_text": "可修改",
},
]
probe.property_table_expanded = True
probe.property_editor_selected_row = None
probe._rebuild_property_cards()
QApplication.processEvents()
context_value = next(
(
label
for label in probe.property_card_rows[0]["card"].findChildren(QLabel)
if label.objectName() == "propertyCardValue"
),
None,
)
_assert(isinstance(context_value, QLabel), "long associated detection note is missing")
_assert(context_value.wordWrap(), "long associated detection note should wrap in diagnostics view")
_assert("关联尺寸可在同一参数表中直接修改" in context_value.text(), "associated detection note lost its trailing text")
associated_widgets = probe.property_card_rows[1]
associated_card = associated_widgets["card"]
associated_title = next(
(
label
for label in associated_card.findChildren(QLabel)
if label.objectName() == "propertyCardTitle"
),
None,
)
_assert(isinstance(associated_title, QLabel), "long associated row title is missing")
_assert(associated_title.wordWrap(), "long associated row title should use a two-line compact layout")
_assert(associated_card.height() > 24, "long associated row should be taller than a one-line compact row")
associated_value = associated_widgets.get("current_value")
_assert(isinstance(associated_value, QLabel), "long associated row value is missing")
_assert("(-118.585" in associated_value.text(), "long associated row value was hidden by the title")
print("property card editor UI ok")
if QApplication.instance() is app:
app.quit()
return 0
if __name__ == "__main__":
raise SystemExit(main())