feat: 完善一级关系参数化建模与参数导出
This commit is contained in:
+176
-27
@@ -7,6 +7,7 @@ import time
|
||||
from PySide6.QtCore import Qt, QThread, QTimer, Slot
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QCheckBox,
|
||||
QFileDialog,
|
||||
QFrame,
|
||||
QHBoxLayout,
|
||||
@@ -36,8 +37,15 @@ PROPERTY_LABEL_COLUMN = 0
|
||||
PROPERTY_CURRENT_COLUMN = 1
|
||||
PROPERTY_SCOPE_COLUMN = 2
|
||||
PROPERTY_TARGET_COLUMN = 3
|
||||
PROPERTY_TABLE_MIN_COLUMN_WIDTHS = (72, 118, 72, 82)
|
||||
PROPERTY_TABLE_PREFERRED_COLUMN_WIDTHS = (118, 168, 108, 104)
|
||||
PROPERTY_INPUT_COLUMN = 4
|
||||
PROPERTY_TABLE_HEADERS = ("尺寸参数", "当前值", "建模意图", "目标值", "输入参数")
|
||||
PROPERTY_TABLE_MIN_COLUMN_WIDTHS = (70, 72, 66, 62, 50)
|
||||
PROPERTY_TABLE_PREFERRED_COLUMN_WIDTHS = (108, 104, 88, 82, 66)
|
||||
PROPERTY_TEMPORARILY_HIDDEN_PARAMETER_KEYS = {
|
||||
"face_center_position",
|
||||
"edge_center_point",
|
||||
}
|
||||
PROPERTY_TEMPORARILY_HIDDEN_PARAMETER_LABELS = {"中心"}
|
||||
PROPERTY_COMMAND_ORDER = ("offset", "move", "scale", "rotate", "feature", "diagnostics")
|
||||
PROPERTY_COMMAND_LABELS = {
|
||||
"offset": "偏移",
|
||||
@@ -98,7 +106,7 @@ PROPERTY_EXPLANATION_TOOLTIPS = {
|
||||
}
|
||||
|
||||
|
||||
def _property_table_column_widths(available_width: int) -> tuple[int, int, int, int]:
|
||||
def _property_table_column_widths(available_width: int) -> tuple[int, ...]:
|
||||
"""Prefer current value visibility while keeping modeling intent and target usable."""
|
||||
column_count = len(PROPERTY_TABLE_MIN_COLUMN_WIDTHS)
|
||||
available = max(int(available_width or 0), column_count * 44)
|
||||
@@ -110,7 +118,7 @@ def _property_table_column_widths(available_width: int) -> tuple[int, int, int,
|
||||
if available >= preferred_total:
|
||||
widths = [int(value) for value in preferred]
|
||||
extra = available - preferred_total
|
||||
weights = (0.26, 0.38, 0.18, 0.18)
|
||||
weights = (0.30, 0.24, 0.18, 0.16, 0.12)
|
||||
for index, weight in enumerate(weights):
|
||||
addition = int(extra * weight)
|
||||
widths[index] += addition
|
||||
@@ -126,10 +134,10 @@ def _property_table_column_widths(available_width: int) -> tuple[int, int, int,
|
||||
widths[1] += available - sum(widths)
|
||||
return tuple(widths) # type: ignore[return-value]
|
||||
|
||||
floors = (48, 96, 44, 56)
|
||||
floors = (44, 44, 44, 44, 44)
|
||||
widths = [int(value) for value in minimum]
|
||||
deficit = min_total - available
|
||||
for index in (0, 2, 3, 1):
|
||||
for index in (0, 2, 3, 1, 4):
|
||||
if deficit <= 0:
|
||||
break
|
||||
reducible = max(0, widths[index] - floors[index])
|
||||
@@ -149,6 +157,16 @@ def _compact_property_card_text(text: str, limit: int = 220) -> str:
|
||||
return f"{compact[: max(0, limit - 1)].rstrip()}…"
|
||||
|
||||
|
||||
def _property_parameter_is_visible(spec: dict[str, object]) -> bool:
|
||||
key = str(spec.get("key", "") or "")
|
||||
label = str(spec.get("label", "") or "").strip()
|
||||
if key in PROPERTY_TEMPORARILY_HIDDEN_PARAMETER_KEYS:
|
||||
return False
|
||||
if label in PROPERTY_TEMPORARILY_HIDDEN_PARAMETER_LABELS:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _feature_dimension_keys(action_info: dict[str, object]) -> tuple[str, ...]:
|
||||
"""Return the independent, user-facing dimensions for a feature candidate."""
|
||||
surface = str(action_info.get("surface", "") or "")
|
||||
@@ -817,7 +835,7 @@ class WindowStateMixin:
|
||||
self._set_control_state(
|
||||
self.quick_export_all_button,
|
||||
has_model,
|
||||
"导出当前完整 STEP 模型。",
|
||||
"导出当前模型。",
|
||||
wait_or_load_tip,
|
||||
)
|
||||
if hasattr(self, "part_tree"):
|
||||
@@ -852,7 +870,7 @@ class WindowStateMixin:
|
||||
self._set_control_state(
|
||||
self.export_all_button,
|
||||
has_model,
|
||||
"导出当前完整 STEP 模型。",
|
||||
"导出当前模型。",
|
||||
wait_or_load_tip,
|
||||
)
|
||||
self._set_control_state(
|
||||
@@ -1548,11 +1566,11 @@ class WindowStateMixin:
|
||||
headline = "当前支持:Face、孔、槽、凸台、圆角/倒角、壳体、Edge"
|
||||
detail = ""
|
||||
tooltip = (
|
||||
"Face:面内长度/宽度、中心、偏移、壳体厚度。\n"
|
||||
"Face:面内长度/宽度、偏移、壳体厚度。\n"
|
||||
"孔/槽:孔径、轴心、封堵、盲孔/盲槽深度、槽宽/槽深/弧长/总长。\n"
|
||||
"凸台:圆柱凸台直径/高度/轴心;矩形凸台/矩形槽口袋长宽、中心、高度/深度;多台阶矩形凸台顶层规则台阶。\n"
|
||||
"凸台:圆柱凸台直径/高度/轴心;矩形凸台/矩形槽口袋长宽、高度/深度;多台阶矩形凸台顶层规则台阶。\n"
|
||||
"圆角/倒角:简单已有圆角半径/弧长、简单等半径圆角链半径/弧长、已有等距倒角距离,直线 Edge 新增圆角/倒角。\n"
|
||||
"Edge/解析曲面:直线 Edge 长度/端点/中心、圆/椭圆 Edge、简单圆锥/球/环面。\n"
|
||||
"Edge/解析曲面:直线 Edge 长度/端点、圆/椭圆 Edge、简单圆锥/球/环面。\n"
|
||||
"一级关系:Face/Edge 平行垂直事实、Face 共面/同域碎片、一级同轴圆柱事实;部分 Face/Edge 操作可选择保持关系。\n"
|
||||
"受限:复杂链式特征、二级/三级拓扑传播和原 CAD 历史恢复。"
|
||||
)
|
||||
@@ -1985,7 +2003,9 @@ class WindowStateMixin:
|
||||
editable=False,
|
||||
)
|
||||
target_item.setToolTip(self._property_target_tooltip(effective_spec, editable=editable))
|
||||
row_items = (label_item, current_item, scope_item, target_item)
|
||||
input_item = self._property_table_item("", editable=False)
|
||||
input_item.setToolTip("勾选后点击“导出参数”,会把这一行作为输入参数写入 data.json。")
|
||||
row_items = (label_item, current_item, scope_item, target_item, input_item)
|
||||
self._style_property_row_items(row_items, editable=editable, spec=effective_spec)
|
||||
for column, item in enumerate(row_items):
|
||||
item.setToolTip(item.toolTip() or item.text())
|
||||
@@ -2002,6 +2022,10 @@ class WindowStateMixin:
|
||||
self.property_table.removeCellWidget(row, PROPERTY_TARGET_COLUMN)
|
||||
else:
|
||||
self.property_table.removeCellWidget(row, PROPERTY_TARGET_COLUMN)
|
||||
self._set_property_input_checkbox(
|
||||
row,
|
||||
exportable=bool(input_editable and effective_spec.get("label")),
|
||||
)
|
||||
self._clear_property_command_bar()
|
||||
self._clear_property_cards()
|
||||
self._update_current_capability_panel()
|
||||
@@ -2032,12 +2056,13 @@ class WindowStateMixin:
|
||||
|
||||
def _style_property_row_items(
|
||||
self,
|
||||
items: tuple[QTableWidgetItem, QTableWidgetItem, QTableWidgetItem, QTableWidgetItem],
|
||||
items: tuple[QTableWidgetItem, ...],
|
||||
*,
|
||||
editable: bool,
|
||||
spec: dict[str, object] | None = None,
|
||||
) -> None:
|
||||
label_item, current_item, scope_item, target_item = items
|
||||
label_item, current_item, scope_item, target_item = items[:4]
|
||||
input_item = items[4] if len(items) > 4 else None
|
||||
if spec is not None and bool(spec.get("pin_top")):
|
||||
for item in items:
|
||||
item.setBackground(QColor("#eef6ff"))
|
||||
@@ -2045,6 +2070,8 @@ class WindowStateMixin:
|
||||
current_item.setForeground(QColor("#0f172a"))
|
||||
scope_item.setForeground(QColor("#0369a1"))
|
||||
target_item.setForeground(QColor("#64748b"))
|
||||
if input_item is not None:
|
||||
input_item.setForeground(QColor("#64748b"))
|
||||
label_font = label_item.font()
|
||||
label_font.setBold(True)
|
||||
label_item.setFont(label_font)
|
||||
@@ -2053,13 +2080,15 @@ class WindowStateMixin:
|
||||
current_item.setFont(current_font)
|
||||
return
|
||||
if editable:
|
||||
row_backgrounds = ("#fff7ed", "#fffbeb", "#fff7ed", "#fff7ed")
|
||||
row_backgrounds = ("#fff7ed", "#fffbeb", "#fff7ed", "#fff7ed", "#fff7ed")
|
||||
for item, color in zip(items, row_backgrounds):
|
||||
item.setBackground(QColor(color))
|
||||
label_item.setForeground(QColor("#7c2d12"))
|
||||
current_item.setForeground(QColor("#431407"))
|
||||
scope_item.setForeground(QColor("#9a3412"))
|
||||
target_item.setForeground(QColor("#111827"))
|
||||
if input_item is not None:
|
||||
input_item.setForeground(QColor("#7c2d12"))
|
||||
label_font = label_item.font()
|
||||
label_font.setBold(True)
|
||||
label_item.setFont(label_font)
|
||||
@@ -2070,6 +2099,9 @@ class WindowStateMixin:
|
||||
scope_item.setForeground(QColor("#64748b"))
|
||||
target_item.setBackground(QColor("#eef2f6"))
|
||||
target_item.setForeground(QColor("#8f99a8"))
|
||||
if input_item is not None:
|
||||
input_item.setBackground(QColor("#eef2f6"))
|
||||
input_item.setForeground(QColor("#8f99a8"))
|
||||
|
||||
def _property_scope_default(self, spec: dict[str, object]) -> str:
|
||||
modes = spec.get("scope_modes")
|
||||
@@ -2145,6 +2177,39 @@ class WindowStateMixin:
|
||||
editor.returnPressed.connect(self.apply_current_property_edit)
|
||||
self.property_table.setCellWidget(row, PROPERTY_TARGET_COLUMN, editor)
|
||||
|
||||
def _set_property_input_checkbox(self, row: int, *, exportable: bool) -> None:
|
||||
if not hasattr(self, "property_table"):
|
||||
return
|
||||
container = QWidget()
|
||||
container.setObjectName("propertyInputParameterCell")
|
||||
layout = QHBoxLayout(container)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
checkbox = QCheckBox(container)
|
||||
checkbox.setObjectName("propertyInputParameterCheckbox")
|
||||
checkbox.setChecked(False)
|
||||
checkbox.setEnabled(exportable)
|
||||
checkbox.setCursor(Qt.CursorShape.PointingHandCursor if exportable else Qt.CursorShape.ArrowCursor)
|
||||
tip = "勾选后点击“导出参数”,会把这一行作为输入参数写入 data.json。"
|
||||
if not exportable:
|
||||
tip = "当前行不是可导出的尺寸输入参数。"
|
||||
checkbox.setToolTip(tip)
|
||||
container.setToolTip(tip)
|
||||
checkbox.toggled.connect(lambda _checked=False: self._update_parameter_export_state())
|
||||
layout.addWidget(checkbox)
|
||||
self.property_table.setCellWidget(row, PROPERTY_INPUT_COLUMN, container)
|
||||
|
||||
def _property_input_checkbox(self, row: int) -> QCheckBox | None:
|
||||
if not hasattr(self, "property_table"):
|
||||
return None
|
||||
widget = self.property_table.cellWidget(row, PROPERTY_INPUT_COLUMN)
|
||||
if isinstance(widget, QCheckBox):
|
||||
return widget
|
||||
if isinstance(widget, QWidget):
|
||||
return widget.findChild(QCheckBox)
|
||||
return None
|
||||
|
||||
def _set_property_scope_editor(self, row: int, spec: dict[str, object]) -> None:
|
||||
modes = spec.get("scope_modes")
|
||||
if not isinstance(modes, dict) or not modes:
|
||||
@@ -2213,6 +2278,23 @@ class WindowStateMixin:
|
||||
target_item = self.property_table.item(row, PROPERTY_TARGET_COLUMN)
|
||||
if target_item is not None:
|
||||
target_item.setToolTip(self._property_target_tooltip(effective_spec, editable=editable))
|
||||
checkbox = self._property_input_checkbox(row)
|
||||
if checkbox is not None:
|
||||
exportable = bool(input_editable and effective_spec.get("label"))
|
||||
if not exportable and checkbox.isChecked():
|
||||
was_blocked = checkbox.blockSignals(True)
|
||||
try:
|
||||
checkbox.setChecked(False)
|
||||
finally:
|
||||
checkbox.blockSignals(was_blocked)
|
||||
tip = (
|
||||
"勾选后点击“导出参数”,会把这一行作为输入参数写入 data.json。"
|
||||
if exportable
|
||||
else "当前行不是可导出的尺寸输入参数。"
|
||||
)
|
||||
checkbox.setEnabled(exportable)
|
||||
checkbox.setCursor(Qt.CursorShape.PointingHandCursor if exportable else Qt.CursorShape.ArrowCursor)
|
||||
checkbox.setToolTip(tip)
|
||||
self._update_current_capability_panel()
|
||||
self._update_property_apply_state()
|
||||
|
||||
@@ -2345,6 +2427,7 @@ class WindowStateMixin:
|
||||
and bool(spec.get("enabled"))
|
||||
and bool(spec.get("action"))
|
||||
and str(spec.get("value_type", "number")) != "command"
|
||||
and _property_parameter_is_visible(spec)
|
||||
]
|
||||
|
||||
def _feature_property_specs(
|
||||
@@ -2381,6 +2464,7 @@ class WindowStateMixin:
|
||||
or not bool(spec.get("editable"))
|
||||
or (not bool(spec.get("enabled")) and not prismatic_size_key and not prismatic_center_key)
|
||||
or str(spec.get("value_type", "number")) == "command"
|
||||
or not _property_parameter_is_visible(spec)
|
||||
):
|
||||
continue
|
||||
dimension = dict(spec)
|
||||
@@ -4546,7 +4630,7 @@ class WindowStateMixin:
|
||||
"action": "resize_boss_height",
|
||||
"target_attr": "boss_height_input",
|
||||
"enabled": boss_height_can_local,
|
||||
"enabled_tip": "输入完整圆柱凸台的目标高度;点击修改时程序会再计算并确认可拉伸/切除的凸台端盖 Face。",
|
||||
"enabled_tip": "输入完整圆柱凸台的目标高度;点击修改时程序会再计算并校验可拉伸/切除的凸台端盖 Face。",
|
||||
"disabled_tip": "当前凸台候选缺少稳定高度或端盖信息,暂不放行高度修改。",
|
||||
"range_hint": relative_range_hint(current_boss_height, 0.3, 0.8),
|
||||
},
|
||||
@@ -4663,7 +4747,7 @@ class WindowStateMixin:
|
||||
"action": "resize_cylinder_height",
|
||||
"target_attr": "boss_height_input",
|
||||
"enabled": bool(is_full_cylinder and current_cylinder_height is not None),
|
||||
"enabled_tip": "输入完整圆柱面的目标高度;点击修改时程序会再计算并确认可拉伸/切除的圆柱端盖 Face。",
|
||||
"enabled_tip": "输入完整圆柱面的目标高度;点击修改时程序会再计算并校验可拉伸/切除的圆柱端盖 Face。",
|
||||
"disabled_tip": "当前圆柱面不是完整圆柱,或缺少稳定高度信息。",
|
||||
"range_hint": relative_range_hint(current_cylinder_height, 0.3, 0.8),
|
||||
},
|
||||
@@ -5775,17 +5859,70 @@ class WindowStateMixin:
|
||||
card.style().unpolish(card)
|
||||
card.style().polish(card)
|
||||
setattr(widget, "_geom_param_button_state", button_state)
|
||||
if not hasattr(self, "apply_property_button"):
|
||||
if hasattr(self, "apply_property_button"):
|
||||
changed = self._changed_property_rows()
|
||||
enabled = bool(has_model and changed)
|
||||
disabled_tip = "请先选择对象,并在属性表中修改一个可编辑目标值。"
|
||||
if changed and len(changed) > 1:
|
||||
disabled_tip = "当前一次只执行一个几何修改;请只保留一行目标值不同,再点击参数化建模。"
|
||||
self._set_control_state(
|
||||
self.apply_property_button,
|
||||
enabled and len(changed) == 1,
|
||||
"应用当前被修改的参数。",
|
||||
disabled_tip,
|
||||
)
|
||||
self._update_parameter_export_state(has_model)
|
||||
|
||||
def _selected_parameter_export_rows(self) -> list[dict[str, str]]:
|
||||
if not hasattr(self, "property_table"):
|
||||
return []
|
||||
rows: list[dict[str, str]] = []
|
||||
specs = getattr(self, "property_editor_specs", [])
|
||||
row_count = min(self.property_table.rowCount(), len(specs))
|
||||
for row in range(row_count):
|
||||
checkbox = self._property_input_checkbox(row)
|
||||
if checkbox is None or not checkbox.isEnabled() or not checkbox.isChecked():
|
||||
continue
|
||||
spec = self._effective_property_spec(specs[row], row=row)
|
||||
if str(spec.get("value_type", "number")) == "command":
|
||||
continue
|
||||
label_item = self.property_table.item(row, PROPERTY_LABEL_COLUMN)
|
||||
current_item = self.property_table.item(row, PROPERTY_CURRENT_COLUMN)
|
||||
label = (label_item.text() if label_item is not None else str(spec.get("label", ""))).strip()
|
||||
current = (
|
||||
current_item.text()
|
||||
if current_item is not None
|
||||
else str(spec.get("current_text", spec.get("current_raw", "")))
|
||||
).strip()
|
||||
if not label:
|
||||
continue
|
||||
rows.append(
|
||||
{
|
||||
"name": label,
|
||||
"displayName": label,
|
||||
"type": "number",
|
||||
"ioRole": "input",
|
||||
"default": current,
|
||||
}
|
||||
)
|
||||
return rows
|
||||
|
||||
def _update_parameter_export_state(self, has_model: bool | None = None) -> None:
|
||||
if not hasattr(self, "export_parameters_button"):
|
||||
return
|
||||
changed = self._changed_property_rows()
|
||||
enabled = bool(has_model and changed)
|
||||
disabled_tip = "请先选择对象,并在属性表中修改一个可编辑目标值。"
|
||||
if changed and len(changed) > 1:
|
||||
disabled_tip = "当前一次只执行一个几何修改;请只保留一行目标值不同,再点击参数化建模。"
|
||||
if has_model is None:
|
||||
has_model = self.model is not None and not (
|
||||
self.operation_in_progress or self.scan_in_progress or self.load_in_progress
|
||||
)
|
||||
selected_rows = self._selected_parameter_export_rows()
|
||||
if selected_rows:
|
||||
disabled_tip = "请等待当前后台任务完成后再导出参数。"
|
||||
else:
|
||||
disabled_tip = "请先在“输入参数”列勾选至少一个尺寸参数。"
|
||||
self._set_control_state(
|
||||
self.apply_property_button,
|
||||
enabled and len(changed) == 1,
|
||||
"应用当前被修改的参数。",
|
||||
self.export_parameters_button,
|
||||
bool(has_model and selected_rows),
|
||||
f"导出已勾选的 {len(selected_rows)} 个输入参数到 data.json。",
|
||||
disabled_tip,
|
||||
)
|
||||
|
||||
@@ -6060,7 +6197,19 @@ class WindowStateMixin:
|
||||
if action is None:
|
||||
QMessageBox.information(self, "暂不支持", f"当前属性没有可用的执行入口:{action_name}")
|
||||
return
|
||||
action()
|
||||
self._run_parametric_property_action_directly(action)
|
||||
|
||||
def _run_parametric_property_action_directly(self, action) -> None:
|
||||
original_question = QMessageBox.question
|
||||
|
||||
def auto_yes(*_args, **_kwargs):
|
||||
return QMessageBox.StandardButton.Yes
|
||||
|
||||
try:
|
||||
QMessageBox.question = auto_yes
|
||||
action()
|
||||
finally:
|
||||
QMessageBox.question = original_question
|
||||
|
||||
def set_manual_hole_bottom_face(self) -> None:
|
||||
self._update_action_states()
|
||||
|
||||
Reference in New Issue
Block a user