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
+187 -19
View File
@@ -8,6 +8,7 @@ import sys
from pathlib import Path
import vtk
import vtkmodules.vtkInteractionWidgets # noqa: F401
import vtkmodules.vtkInteractionStyle # noqa: F401
import vtkmodules.vtkRenderingFreeType # noqa: F401
import vtkmodules.vtkRenderingOpenGL2 # noqa: F401
@@ -57,6 +58,7 @@ from .window_state import WindowStateMixin
_CRASH_LOG_HANDLE = None
_VTK_OUTPUT_WINDOW = None
PROJECT_ROOT = Path(__file__).resolve().parent.parent
APP_USER_MODEL_ID = "GeometryParametric.StepEditor"
ISOLATED_EDIT_WORKER_ARG = "--isolated-edit-worker"
@@ -119,6 +121,18 @@ def _set_windows_taskbar_identity() -> None:
pass
def _suppress_vtk_output_window() -> None:
"""Keep VTK warnings from opening transient native output windows."""
global _VTK_OUTPUT_WINDOW
try:
vtk.vtkObject.GlobalWarningDisplayOff()
output_window = vtk.vtkStringOutputWindow()
vtk.vtkOutputWindow.SetInstance(output_window)
_VTK_OUTPUT_WINDOW = output_window
except Exception:
pass
def _isolated_edit_worker_request(argv: list[str]) -> Path | None:
if ISOLATED_EDIT_WORKER_ARG not in argv:
return None
@@ -132,6 +146,7 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
ui_task_requested = Signal(object)
def __init__(self, step_path: str | Path, *, background_load: bool = False):
_suppress_vtk_output_window()
super().__init__()
self.ui_task_requested.connect(self._run_ui_task, Qt.ConnectionType.QueuedConnection)
self.setWindowTitle("几何参数化")
@@ -149,6 +164,16 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
self.model_actor = None
self.edge_actor = None
self.scene_axes_renderer = None
self.orientation_marker_widget = None
self.orientation_marker_prop = None
self.step_coordinate_axes_actor = None
self.hide_edges_during_camera_interaction = False
self.edge_visibility_before_camera_interaction: int | None = None
self.prefer_fxaa_antialiasing = True
self.fallback_multi_samples = 2
self.interactive_multi_samples = 0
self.still_multi_samples = 0
self.highlight_actor = None
self.edge_highlight_actor = None
self.highlight_signature: tuple[object, ...] | None = None
@@ -160,6 +185,7 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
self.pending_hover_position: tuple[int, int] | None = None
self.last_hover_pick_position: tuple[int, int] | None = None
self.camera_interaction_active = False
self.camera_interaction_visual_changed = False
self.last_camera_interaction_ended_at: datetime | None = None
self.hover_after_camera_cooldown_ms = 420
self.pointer_button_down = False
@@ -233,9 +259,9 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
self.auto_load_on_show = bool(background_load)
self.vtk_interactor_started = False
self.first_show_handled = False
self.preview_load_deflection = 1.0
self.initial_load_deflection = 0.035
self.edit_result_deflection = 0.035
self.preview_load_deflection = 0.35
self.initial_load_deflection = 0.35
self.edit_result_deflection = 0.35
self.last_id_kind = "Feature"
self.feature_detection_level = "current-only"
self.property_editor_updating = False
@@ -244,7 +270,10 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
self._selected_action_info_cache_value: dict[str, object] | None = None
self.property_editor_specs: list[dict[str, object]] = []
self.property_table_expanded = False
self.property_table_collapsed_rows = 6
self.property_table_collapsed_rows = 8
self.property_editor_selected_row: int | None = None
self.property_command_active_key = ""
self.property_command_buttons: dict[str, QPushButton] = {}
self._build_ui()
self._build_vtk()
@@ -487,8 +516,8 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
border-radius: 5px;
color: #ffffff;
font-weight: 700;
min-height: 18px;
padding: 1px 3px;
min-height: 26px;
padding: 4px 8px;
}
QPushButton#propertyRowEditButton:hover {
background: #f97316;
@@ -646,6 +675,109 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
padding-left: 1px;
padding-right: 1px;
}
QLabel#propertyCommandSummary {
color: #334155;
font-size: 11px;
padding: 0 2px 1px 2px;
}
QLabel#propertyCommandHelp {
background: #fff7ed;
border: 1px solid #fed7aa;
border-radius: 6px;
color: #7c2d12;
font-size: 11px;
padding: 5px 7px;
}
QFrame#propertyCommandBar {
background: #eef2f7;
border: 1px solid #cbd5e1;
border-radius: 6px;
}
QPushButton#propertyCommandButton {
background: transparent;
border: 0;
border-radius: 4px;
color: #334155;
font-weight: 700;
min-height: 34px;
padding: 3px 6px;
}
QPushButton#propertyCommandButton:hover {
background: #e2e8f0;
}
QPushButton#propertyCommandButton[active="true"] {
background: #ffffff;
color: #0f172a;
border: 1px solid #f59e0b;
}
QScrollArea#propertyCardScroll {
background: transparent;
border: 0;
}
QWidget#propertyCardContainer {
background: transparent;
}
QFrame#propertyCard {
background: #ffffff;
border: 1px solid #d8e0eb;
border-radius: 6px;
}
QFrame#propertyCard[compact="true"] {
background: #ffffff;
}
QFrame#propertyCard[editable="true"] {
background: #fffaf2;
border-color: #f4c58a;
}
QFrame#propertyCard[selected="true"] {
background: #fffaf2;
border-color: #f59e0b;
}
QFrame#propertyCard[changed="true"] {
background: #f0fdf4;
border-color: #16a34a;
}
QFrame#propertyCard[invalid="true"] {
background: #fff1f2;
border-color: #ef4444;
}
QFrame#propertyCard[pinTop="true"] {
background: #eef6ff;
border-color: #b7d3f5;
}
QLabel#propertyCardTitle {
color: #172033;
font-weight: 700;
}
QLabel#propertyCardMetaLabel {
color: #64748b;
font-size: 11px;
font-weight: 700;
}
QLabel#propertyCardValue {
color: #172033;
}
QLabel#propertyCardCompactValue {
color: #172033;
font-weight: 600;
}
QLabel#propertyCardHint {
color: #64748b;
font-size: 11px;
}
QLabel#propertyCardCompactMeta {
color: #475569;
font-size: 11px;
}
QLineEdit#propertyCardTargetEditor {
background: #ffffff;
border: 1px solid #c8d2df;
border-radius: 4px;
padding: 3px 5px;
}
QLineEdit#propertyCardTargetEditor:focus {
border-color: #2563eb;
}
QTabWidget::pane {
border: 1px solid #d8e0eb;
border-radius: 6px;
@@ -920,20 +1052,20 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
self.object_edit_box.setObjectName("editSection")
help_tip(
self.object_edit_box,
"选择特征后,这里只列出可可靠修改的语义尺寸;面积、中心和拓扑数据位于下方诊断信息",
"选择对象后,这里以参数卡片显示当前值、建模意图、目标值和应用按钮;诊断信息默认折叠在后面",
)
object_edit_layout = QVBoxLayout(self.object_edit_box)
object_edit_layout.setContentsMargins(0, 8, 0, 4)
object_edit_layout.setSpacing(4)
self.property_table = QTableWidget(0, 5)
self.property_table = QTableWidget(0, 5, self.object_edit_box)
self.property_table.setObjectName("propertyTable")
self.property_table.setHorizontalHeaderLabels(["尺寸参数", "当前值", "建模意图", "目标值", "操作"])
self.property_table.setHorizontalHeaderLabels(["参数", "当前值", "建模意图", "目标值", "操作"])
self.property_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.property_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.property_table.setAlternatingRowColors(True)
self.property_table.setMinimumHeight(180)
self.property_table.setWordWrap(False)
self.property_table.setTextElideMode(Qt.TextElideMode.ElideMiddle)
self.property_table.setTextElideMode(Qt.TextElideMode.ElideRight)
self.property_table.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.property_table.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.property_table.verticalHeader().setVisible(False)
@@ -946,21 +1078,51 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
property_header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
property_header.setSectionResizeMode(2, QHeaderView.ResizeMode.Fixed)
property_header.setSectionResizeMode(3, QHeaderView.ResizeMode.Fixed)
property_header.setSectionResizeMode(4, QHeaderView.ResizeMode.Stretch)
self.property_table.setColumnWidth(0, 148)
self.property_table.setColumnWidth(1, 68)
self.property_table.setColumnWidth(2, 96)
self.property_table.setColumnWidth(3, 72)
self.property_table.setColumnWidth(4, 54)
property_header.setSectionResizeMode(4, QHeaderView.ResizeMode.Fixed)
self._resize_property_table_columns()
self.property_table.installEventFilter(self)
help_tip(
self.property_table,
"特征模式显示当前特征及局部关联特征的可变尺寸;建模意图决定这次修改是局部重建、拉伸/切除、端面移动还是整体缩放。",
)
self.property_table.itemChanged.connect(self._on_property_table_item_changed)
object_edit_layout.addWidget(self.property_table)
self.property_expand_button = QPushButton("展开全部参数")
self.property_table.setVisible(False)
self.property_table.setMaximumHeight(0)
self.property_command_summary_label = QLabel("未选择可编辑对象")
self.property_command_summary_label.setObjectName("propertyCommandSummary")
self.property_command_summary_label.setWordWrap(True)
object_edit_layout.addWidget(self.property_command_summary_label)
self.property_command_bar = QFrame()
self.property_command_bar.setObjectName("propertyCommandBar")
self.property_command_layout = QHBoxLayout(self.property_command_bar)
self.property_command_layout.setContentsMargins(3, 3, 3, 3)
self.property_command_layout.setSpacing(2)
object_edit_layout.addWidget(self.property_command_bar)
self.property_command_help_label = QLabel("")
self.property_command_help_label.setObjectName("propertyCommandHelp")
self.property_command_help_label.setWordWrap(True)
self.property_command_help_label.setVisible(False)
object_edit_layout.addWidget(self.property_command_help_label)
self.property_card_scroll = QScrollArea()
self.property_card_scroll.setObjectName("propertyCardScroll")
self.property_card_scroll.setWidgetResizable(True)
self.property_card_scroll.setFrameShape(QFrame.Shape.NoFrame)
self.property_card_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.property_card_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.property_card_container = QWidget()
self.property_card_container.setObjectName("propertyCardContainer")
self.property_card_layout = QVBoxLayout(self.property_card_container)
self.property_card_layout.setContentsMargins(0, 0, 0, 0)
self.property_card_layout.setSpacing(6)
self.property_card_scroll.setWidget(self.property_card_container)
help_tip(
self.property_card_scroll,
"每张卡片对应一个参数;修改目标值后,点击卡片右上角的按钮只应用这一项。",
)
object_edit_layout.addWidget(self.property_card_scroll)
self.property_expand_button = QPushButton("更多诊断信息")
self.property_expand_button.setObjectName("propertyExpandBar")
help_tip(self.property_expand_button, "参数较多时默认只显示前 6 行;点击这里展开或收起完整参数列表")
help_tip(self.property_expand_button, "默认优先显示可编辑参数和关键说明;点击这里展开或收起完整诊断信息")
self.property_expand_button.clicked.connect(self.toggle_property_table_expanded)
self.property_expand_button.setVisible(False)
self.property_expand_button.setMaximumHeight(22)
@@ -969,6 +1131,7 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
property_action_row = QHBoxLayout()
property_action_row.setContentsMargins(0, 0, 0, 0)
self.apply_property_button = QPushButton("参数化建模")
self.apply_property_button.setVisible(False)
help_tip(self.apply_property_button, "按当前属性表里修改过的目标值执行参数化建模;一次只执行一个几何修改,成功后可撤销。")
self.apply_property_button.clicked.connect(self.apply_current_property_edit)
self.quick_export_all_button = QPushButton("导出当前完整STEP")
@@ -1465,6 +1628,10 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
self.vtk_widget.setMouseTracking(True)
self.vtk_widget.installEventFilter(self)
root_layout.addWidget(self.vtk_widget, stretch=1)
app = QApplication.instance()
if app is not None and not getattr(self, "_global_tooltip_filter_installed", False):
app.installEventFilter(self)
self._global_tooltip_filter_installed = True
self.statusBar().showMessage("请选择 STEP 文件,或点击“读取模型”加载到可视化区域。")
@@ -1495,6 +1662,7 @@ def main() -> int:
_enable_crash_log()
path, smoke_test = _parse_args(sys.argv)
_set_windows_taskbar_identity()
_suppress_vtk_output_window()
app = QApplication(sys.argv)
app.setApplicationName("几何参数化")
app.setApplicationDisplayName("几何参数化")