feat: 完善一级关系参数化建模与参数导出

This commit is contained in:
2026-08-11 18:28:16 +08:00
parent 6cb99a1273
commit 19364d81b5
20 changed files with 917 additions and 167 deletions
+105 -4
View File
@@ -1,15 +1,18 @@
from __future__ import annotations
import json
import math
import os
from pathlib import Path
import sys
import tempfile
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PySide6.QtCore import QEvent, QObject
from PySide6.QtWidgets import (
QApplication,
QCheckBox,
QFrame,
QHBoxLayout,
QLabel,
@@ -30,8 +33,10 @@ from step_editor.window_actions import WindowActionMixin
from step_editor.window_core import WindowCoreMixin
from step_editor.window_state import (
PROPERTY_CURRENT_COLUMN,
PROPERTY_INPUT_COLUMN,
PROPERTY_LABEL_COLUMN,
PROPERTY_SCOPE_COLUMN,
PROPERTY_TABLE_HEADERS,
PROPERTY_TARGET_COLUMN,
WindowStateMixin,
)
@@ -66,8 +71,8 @@ class _PropertyTableProbe(QWidget, WindowStateMixin):
layout = QVBoxLayout(self)
self.object_edit_box = self
self.property_table = QTableWidget(0, 4)
self.property_table.setHorizontalHeaderLabels(["尺寸参数", "当前值", "建模意图", "目标值"])
self.property_table = QTableWidget(0, len(PROPERTY_TABLE_HEADERS))
self.property_table.setHorizontalHeaderLabels(list(PROPERTY_TABLE_HEADERS))
layout.addWidget(self.property_table)
self.property_card_scroll = QScrollArea()
@@ -85,6 +90,7 @@ class _PropertyTableProbe(QWidget, WindowStateMixin):
self.property_command_help_label = QLabel()
self.current_capability_headline = QLabel()
self.apply_property_button = QPushButton()
self.export_parameters_button = QPushButton()
@staticmethod
def _plane_info() -> dict[str, object]:
@@ -121,6 +127,44 @@ class _ActionMessageProbe(WindowActionMixin):
pass
class _ParameterExportActionProbe(WindowActionMixin):
def __init__(self, output_path: Path) -> None:
self.output_path = output_path
self.status_bar = _StatusBarProbe()
self.info_text = ""
self.export_state_updates = 0
def _parameter_export_output_path(self) -> Path:
return self.output_path
def _selected_parameter_export_rows(self) -> list[dict[str, str]]:
return [
{
"name": "面内长度",
"displayName": "面内长度",
"type": "number",
"ioRole": "input",
"default": "151",
},
{
"name": "偏移",
"displayName": "偏移",
"type": "number",
"ioRole": "input",
"default": "57.5",
},
]
def _update_parameter_export_state(self) -> None:
self.export_state_updates += 1
def statusBar(self) -> _StatusBarProbe:
return self.status_bar
def set_plain_info(self, text: str) -> None:
self.info_text = text
class _TimerProbe:
def stop(self) -> None:
pass
@@ -240,7 +284,7 @@ def _assert_property_table_editor(probe: _PropertyTableProbe) -> None:
probe.property_table.horizontalHeaderItem(column).text()
for column in range(probe.property_table.columnCount())
]
_assert(headers == ["尺寸参数", "当前值", "建模意图", "目标值"], f"unexpected table headers: {headers}")
_assert(headers == list(PROPERTY_TABLE_HEADERS), f"unexpected table headers: {headers}")
_assert(probe.property_table.rowCount() == len(probe.property_editor_specs), "table row count should match specs")
header_height = int(probe.property_table.horizontalHeader().height())
frame = int(probe.property_table.frameWidth()) * 2
@@ -262,8 +306,10 @@ def _assert_property_table_editor(probe: _PropertyTableProbe) -> None:
if probe.property_table.item(row, PROPERTY_LABEL_COLUMN) is not None
]
actionable_labels = [str(spec.get("label", "")) for _row, spec in probe._actionable_property_rows()]
for label in ("面内长度", "面内宽度", "中心", "偏移"):
for label in ("面内长度", "面内宽度", "偏移"):
_assert(label in actionable_labels, f"feature parameter table did not expose {label}")
_assert("中心" not in actionable_labels, "center should be temporarily hidden from editable parameters")
_assert("中心" not in table_labels, "center should not appear in the feature parameter table")
for legacy_label in ("面积", "U向尺寸", "V向尺寸", "偏移变换"):
_assert(legacy_label not in actionable_labels, f"{legacy_label} should not be exposed as an editable parameter")
_assert(legacy_label not in table_labels, f"{legacy_label} should not appear in the feature parameter table")
@@ -273,13 +319,38 @@ def _assert_property_table_editor(probe: _PropertyTableProbe) -> None:
target_row = _row_by_label(probe, "面内长度")
target_widget = probe.property_table.cellWidget(target_row, PROPERTY_TARGET_COLUMN)
scope_widget = probe.property_table.cellWidget(target_row, PROPERTY_SCOPE_COLUMN)
input_checkbox = probe._property_input_checkbox(target_row)
_assert(isinstance(target_widget, QLineEdit), "editable table row should have a target editor")
_assert(isinstance(scope_widget, NoWheelComboBox), "editable table row should have a modeling-intent combo")
_assert(isinstance(input_checkbox, QCheckBox), "editable table row should have an input-parameter checkbox")
_assert(not input_checkbox.isChecked(), "input-parameter checkbox should be unchecked by default")
_assert(
probe.property_table.cellWidget(target_row, PROPERTY_INPUT_COLUMN) is not None,
"input-parameter checkbox should be hosted in the input column",
)
_assert(not probe.export_parameters_button.isEnabled(), "parameter export button should start disabled")
_assert(
not probe.property_table.findChildren(QPushButton),
"feature parameter table should not contain per-row apply buttons",
)
input_checkbox.setChecked(True)
QApplication.processEvents()
selected_rows = probe._selected_parameter_export_rows()
_assert(probe.export_parameters_button.isEnabled(), "parameter export button should enable after a row is checked")
_assert(
selected_rows == [
{
"name": "面内长度",
"displayName": "面内长度",
"type": "number",
"ioRole": "input",
"default": "10",
}
],
f"unexpected parameter export payload: {selected_rows}",
)
target_widget.setText("12")
probe._update_property_apply_state()
changed = probe._changed_property_rows()
@@ -470,6 +541,35 @@ def _assert_user_facing_failure_messages() -> None:
_assert("隔离子进程" not in empty_message and "子进程" not in empty_message, "empty internal failure should stay user-facing")
def _assert_parameter_export_action() -> None:
with tempfile.TemporaryDirectory() as temp_dir:
output_path = Path(temp_dir) / "data.json"
probe = _ParameterExportActionProbe(output_path)
probe.export_selected_parameters()
payload = json.loads(output_path.read_text(encoding="utf-8"))
_assert(
payload == [
{
"name": "面内长度",
"displayName": "面内长度",
"type": "number",
"ioRole": "input",
"default": "151",
},
{
"name": "偏移",
"displayName": "偏移",
"type": "number",
"ioRole": "input",
"default": "57.5",
},
],
f"parameter export action wrote unexpected JSON: {payload}",
)
_assert(probe.status_bar.messages and "已导出 2 个输入参数" in probe.status_bar.messages[-1], "parameter export status should mention exported count")
_assert("data.json" in probe.info_text and "参数数量:2" in probe.info_text, "parameter export info panel summary should be useful")
def main() -> int:
app = QApplication.instance() or QApplication([])
top_level_label_probe = _TopLevelPropertyLabelProbe()
@@ -497,6 +597,7 @@ def main() -> int:
_assert_mouse_selection_guards()
_assert_quick_blind_depth_spec()
_assert_user_facing_failure_messages()
_assert_parameter_export_action()
print("property table editor UI ok")
if QApplication.instance() is app: