feat: 完善参数化编辑和孔修改稳定性
This commit is contained in:
+447
-279
@@ -7,15 +7,17 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
import vtk
|
||||
from PySide6.QtCore import Qt, QThread, QTimer, Slot
|
||||
from PySide6.QtCore import Qt, QThread, QTimer, Signal, Slot
|
||||
from PySide6.QtWidgets import (
|
||||
QAbstractItemView,
|
||||
QApplication,
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
QFileDialog,
|
||||
QFrame,
|
||||
QGridLayout,
|
||||
QGroupBox,
|
||||
QHeaderView,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
@@ -25,6 +27,7 @@ from PySide6.QtWidgets import (
|
||||
QPushButton,
|
||||
QPlainTextEdit,
|
||||
QScrollArea,
|
||||
QSizePolicy,
|
||||
QTableWidget,
|
||||
QTableWidgetItem,
|
||||
QTabWidget,
|
||||
@@ -52,6 +55,16 @@ _CRASH_LOG_HANDLE = None
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
DEFAULT_MODEL_PATH = PROJECT_ROOT / "assets" / "models" / "geom_extract.step"
|
||||
|
||||
# Panel build switches. These work like a Python-side "#if 0": code in a
|
||||
# disabled branch is kept for later, but the widgets are not constructed.
|
||||
ENABLE_STRUCTURE_TREE_PANEL = False
|
||||
ENABLE_VIEW_PANEL = False
|
||||
ENABLE_MEASURE_PANEL = False
|
||||
ENABLE_EXPORT_PANEL = False
|
||||
ENABLE_EDITABLE_OBJECTS_PANEL = False
|
||||
ENABLE_CYLINDER_CANDIDATES_PANEL = False
|
||||
ENABLE_HISTORY_PANEL = False
|
||||
|
||||
|
||||
def _enable_crash_log() -> None:
|
||||
global _CRASH_LOG_HANDLE
|
||||
@@ -66,9 +79,12 @@ def _enable_crash_log() -> None:
|
||||
|
||||
|
||||
class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, InfoPanelMixin, QMainWindow):
|
||||
ui_task_requested = Signal(object)
|
||||
|
||||
def __init__(self, step_path: str | Path, *, background_load: bool = True):
|
||||
super().__init__()
|
||||
self.setWindowTitle("STEP 零件查看与编辑原型")
|
||||
self.ui_task_requested.connect(self._run_ui_task, Qt.ConnectionType.QueuedConnection)
|
||||
self.setWindowTitle("几何参数化")
|
||||
self.resize(1280, 820)
|
||||
|
||||
self.model: StepModel | None = None
|
||||
@@ -143,6 +159,10 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
|
||||
self.pending_load_path: Path | None = None
|
||||
self.initial_load_deflection = 2.0
|
||||
self.last_id_kind = "Face"
|
||||
self.property_editor_updating = False
|
||||
self.property_editor_specs: list[dict[str, object]] = []
|
||||
self.property_table_expanded = False
|
||||
self.property_table_collapsed_rows = 6
|
||||
|
||||
self._build_ui()
|
||||
self._build_vtk()
|
||||
@@ -237,17 +257,31 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
|
||||
border-color: #b8dfd6;
|
||||
color: #0f766e;
|
||||
}
|
||||
QGroupBox#modeSection,
|
||||
QGroupBox#idSection {
|
||||
background: #f7f5ff;
|
||||
border: 1px solid #cbc4ff;
|
||||
border-left: 5px solid #6b5cff;
|
||||
QFrame#modeSection {
|
||||
background: #f6f3ff;
|
||||
border: 1px solid #a99bff;
|
||||
border-left: 5px solid #5b4bdb;
|
||||
border-radius: 7px;
|
||||
}
|
||||
QGroupBox#modeSection::title,
|
||||
QGroupBox#idSection::title {
|
||||
background: #eeeaff;
|
||||
border-color: #cbc4ff;
|
||||
color: #5642c7;
|
||||
QLabel#modeSectionTitle {
|
||||
background: #ebe7ff;
|
||||
border: 1px solid #a99bff;
|
||||
border-radius: 5px;
|
||||
color: #4736b3;
|
||||
font-weight: 700;
|
||||
padding: 1px 8px;
|
||||
}
|
||||
QFrame#modeIdSeparator {
|
||||
background: #ded8ff;
|
||||
border-left: 2px solid #5b4bdb;
|
||||
border-right: 1px solid #b8adff;
|
||||
max-width: 8px;
|
||||
min-width: 8px;
|
||||
}
|
||||
QLabel#mouseModeLabel,
|
||||
QLabel#idSelectLabel {
|
||||
color: #4736b3;
|
||||
font-weight: 700;
|
||||
}
|
||||
QGroupBox#viewSection {
|
||||
background: #f1faff;
|
||||
@@ -337,12 +371,6 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
|
||||
QComboBox:focus {
|
||||
border: 1px solid #3478f6;
|
||||
}
|
||||
QLineEdit#idModeDisplay {
|
||||
background: #eef2ff;
|
||||
border-color: #93a5e8;
|
||||
color: #2d3a8c;
|
||||
font-weight: 700;
|
||||
}
|
||||
QPushButton {
|
||||
background: #f8fafc;
|
||||
border: 1px solid #c8d2df;
|
||||
@@ -365,6 +393,24 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
|
||||
color: #8f99a8;
|
||||
font-weight: 500;
|
||||
}
|
||||
QPushButton#propertyRowEditButton {
|
||||
background: #fff3d9;
|
||||
border: 1px solid #d97706;
|
||||
border-radius: 5px;
|
||||
color: #92400e;
|
||||
font-weight: 700;
|
||||
min-height: 18px;
|
||||
padding: 1px 6px;
|
||||
}
|
||||
QPushButton#propertyRowEditButton:hover {
|
||||
background: #ffe6b8;
|
||||
border-color: #b45309;
|
||||
}
|
||||
QPushButton#propertyRowEditButton:disabled {
|
||||
background: #eef2f6;
|
||||
border: 1px dashed #bcc7d4;
|
||||
color: #8f99a8;
|
||||
}
|
||||
QLineEdit:disabled,
|
||||
QComboBox:disabled,
|
||||
QPlainTextEdit:disabled {
|
||||
@@ -372,6 +418,26 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
|
||||
border: 1px dashed #bcc7d4;
|
||||
color: #8f99a8;
|
||||
}
|
||||
QLineEdit#idSelectInput {
|
||||
background: #ffffff;
|
||||
border: 1px solid #8f82ea;
|
||||
color: #172033;
|
||||
placeholder-text-color: #64748b;
|
||||
}
|
||||
QLineEdit#idSelectInput:focus {
|
||||
background: #ffffff;
|
||||
border: 1px solid #5b4bdb;
|
||||
}
|
||||
QLineEdit#idSelectInput:disabled {
|
||||
background: #ffffff;
|
||||
border: 1px solid #c8d2df;
|
||||
color: #172033;
|
||||
}
|
||||
QLineEdit#stepPathDisplay {
|
||||
background: #ffffff;
|
||||
border: 1px solid #bdd3ff;
|
||||
color: #172033;
|
||||
}
|
||||
QCheckBox:disabled,
|
||||
QTabWidget:disabled,
|
||||
QTreeWidget:disabled,
|
||||
@@ -423,178 +489,273 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
|
||||
|
||||
file_box = QGroupBox("STEP 文件")
|
||||
file_box.setObjectName("fileSection")
|
||||
help_tip(file_box, "打开、重新加载和查看当前 STEP 文件路径。")
|
||||
help_tip(file_box, "导入、查看路径并读取当前 STEP 几何模型。")
|
||||
file_layout = QVBoxLayout(file_box)
|
||||
self.path_label = QLabel("")
|
||||
self.path_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
|
||||
self.path_label.setWordWrap(True)
|
||||
help_tip(self.path_label, "当前打开的 STEP 文件路径。可以选中文字复制路径。")
|
||||
file_layout.addWidget(self.path_label)
|
||||
|
||||
file_buttons = QHBoxLayout()
|
||||
self.open_button = QPushButton("打开")
|
||||
help_tip(self.open_button, "选择并打开一个 .step 或 .stp 文件。打开失败时会保留当前模型。")
|
||||
file_buttons.setSpacing(6)
|
||||
self.open_button = QPushButton("导入几何模型")
|
||||
self.open_button.setMinimumWidth(96)
|
||||
help_tip(self.open_button, "选择并导入一个 .step 或 .stp 几何模型。打开失败时会保留当前模型。")
|
||||
self.open_button.clicked.connect(self.open_step)
|
||||
self.reload_button = QPushButton("重新加载")
|
||||
help_tip(self.reload_button, "从磁盘重新读取当前 STEP 文件,用于放弃本次会话里的临时查看状态。")
|
||||
self.path_label = QLineEdit(str(self.step_path))
|
||||
self.path_label.setObjectName("stepPathDisplay")
|
||||
self.path_label.setReadOnly(True)
|
||||
self.path_label.setMinimumWidth(120)
|
||||
help_tip(self.path_label, "当前 STEP 文件的完整路径。可以选中文字复制路径。")
|
||||
self.path_label.setToolTip(str(self.step_path))
|
||||
self.path_label.setCursorPosition(0)
|
||||
self.reload_button = QPushButton("读取模型")
|
||||
self.reload_button.setMinimumWidth(78)
|
||||
help_tip(self.reload_button, "从显示的路径重新读取 STEP 模型,用于放弃本次会话里的临时查看状态。")
|
||||
self.reload_button.clicked.connect(self.reload_step)
|
||||
file_buttons.addWidget(self.open_button)
|
||||
file_buttons.addWidget(self.path_label, stretch=1)
|
||||
file_buttons.addWidget(self.reload_button)
|
||||
file_layout.addLayout(file_buttons)
|
||||
panel_layout.addWidget(file_box)
|
||||
|
||||
tree_box = QGroupBox("模型结构树")
|
||||
tree_box.setObjectName("treeSection")
|
||||
help_tip(tree_box, "查看 STEP 里的装配、零件和实体层级,并从树上直接选中对象。")
|
||||
tree_layout = QVBoxLayout(tree_box)
|
||||
self.part_tree = QTreeWidget()
|
||||
self.part_tree.setHeaderLabels(["对象", "内容"])
|
||||
self.part_tree.setMinimumHeight(180)
|
||||
self.part_tree.setAlternatingRowColors(True)
|
||||
help_tip(self.part_tree, "模型里的装配、零件和Solid列表。点击一行可以选中并高亮对应对象。")
|
||||
self.part_tree.currentItemChanged.connect(self.on_part_tree_select)
|
||||
tree_layout.addWidget(self.part_tree)
|
||||
panel_layout.addWidget(tree_box)
|
||||
if ENABLE_STRUCTURE_TREE_PANEL:
|
||||
tree_box = QGroupBox("模型结构树")
|
||||
tree_box.setObjectName("treeSection")
|
||||
help_tip(tree_box, "查看 STEP 里的装配、零件和实体层级,并从树上直接选中对象。")
|
||||
tree_layout = QVBoxLayout(tree_box)
|
||||
self.part_tree = QTreeWidget()
|
||||
self.part_tree.setHeaderLabels(["对象", "内容"])
|
||||
self.part_tree.setMinimumHeight(180)
|
||||
self.part_tree.setAlternatingRowColors(True)
|
||||
help_tip(self.part_tree, "模型里的装配、零件和Solid列表。点击一行可以选中并高亮对应对象。")
|
||||
self.part_tree.currentItemChanged.connect(self.on_part_tree_select)
|
||||
tree_layout.addWidget(self.part_tree)
|
||||
panel_layout.addWidget(tree_box)
|
||||
|
||||
mode_box = QGroupBox("鼠标选择模式")
|
||||
mode_box.setObjectName("modeSection")
|
||||
help_tip(mode_box, "决定鼠标点模型时选中 Part、Solid、Face、Edge,还是识别局部特征。")
|
||||
mode_layout = QVBoxLayout(mode_box)
|
||||
mode_box = QWidget()
|
||||
mode_box.setMinimumHeight(62)
|
||||
help_tip(mode_box, "决定鼠标点模型时选中零件、Solid、Face、Edge,还是识别几何特征。")
|
||||
self.mode_section_title = QLabel("选择模式", mode_box)
|
||||
self.mode_section_title.setObjectName("modeSectionTitle")
|
||||
self.mode_section_title.setFixedSize(74, 20)
|
||||
self.mode_section_title.move(12, 0)
|
||||
self.mode_section_title.raise_()
|
||||
help_tip(self.mode_section_title, "选择鼠标点模型时按什么对象类型选择。")
|
||||
mode_outer_layout = QVBoxLayout(mode_box)
|
||||
mode_outer_layout.setContentsMargins(0, 9, 0, 0)
|
||||
mode_outer_layout.setSpacing(0)
|
||||
self.mode_section_frame = QFrame()
|
||||
self.mode_section_frame.setObjectName("modeSection")
|
||||
self.mode_section_frame.setMinimumHeight(50)
|
||||
mode_outer_layout.addWidget(self.mode_section_frame)
|
||||
self.mode_section_title.raise_()
|
||||
mode_layout = QHBoxLayout(self.mode_section_frame)
|
||||
mode_layout.setSpacing(0)
|
||||
mode_layout.setContentsMargins(0, 0, 0, 0)
|
||||
mode_pick_panel = QWidget()
|
||||
mode_pick_layout = QHBoxLayout(mode_pick_panel)
|
||||
mode_pick_layout.setContentsMargins(8, 10, 8, 6)
|
||||
mode_pick_layout.setSpacing(6)
|
||||
self.mouse_mode_label = QLabel("按鼠标:")
|
||||
self.mouse_mode_label.setObjectName("mouseModeLabel")
|
||||
self.mouse_mode_label.setAlignment(Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignRight)
|
||||
self.mouse_mode_label.setMaximumWidth(62)
|
||||
help_tip(self.mouse_mode_label, "这里决定鼠标点击 3D 模型时按哪种对象类型选择。")
|
||||
self.mode_combo = NoWheelComboBox()
|
||||
self.mode_combo.addItems(["Part", "Solid", "Face", "Edge", "Feature"])
|
||||
self.mode_combo.setCurrentText("Face")
|
||||
for mode in ("Part", "Solid", "Face", "Edge", "Feature"):
|
||||
self.mode_combo.addItem(_selection_mode_label(mode), mode)
|
||||
self._set_selection_mode("Face")
|
||||
self.mode_combo.setMinimumWidth(94)
|
||||
self.mode_combo.setMaximumWidth(112)
|
||||
help_tip(
|
||||
self.mode_combo,
|
||||
"选择鼠标点击模型时要选什么:Part、Solid、Face、Edge,或把 Face 解释成孔/槽/圆角等特征候选。",
|
||||
"选择模式决定鼠标点击模型时要选什么:零件、Solid、Face、Edge,或把 Face 解释成孔/槽/圆角等几何特征候选。",
|
||||
)
|
||||
self.mode_combo.currentTextChanged.connect(self._on_mode_changed)
|
||||
mode_layout.addWidget(self.mode_combo)
|
||||
self.mode_combo.currentIndexChanged.connect(lambda _index: self._on_mode_changed(self._current_selection_mode()))
|
||||
mode_pick_layout.addWidget(self.mouse_mode_label)
|
||||
mode_pick_layout.addWidget(self.mode_combo)
|
||||
mode_layout.addWidget(mode_pick_panel)
|
||||
|
||||
mode_separator = QFrame()
|
||||
mode_separator.setFrameShape(QFrame.Shape.NoFrame)
|
||||
mode_separator.setObjectName("modeIdSeparator")
|
||||
mode_separator.setFixedWidth(8)
|
||||
mode_separator.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding)
|
||||
self.mode_id_separator = mode_separator
|
||||
help_tip(mode_separator, "左侧是鼠标点选模式,右侧是按当前模式输入 ID 选择。")
|
||||
mode_layout.addWidget(mode_separator)
|
||||
|
||||
id_select_panel = QWidget()
|
||||
select_layout = QHBoxLayout(id_select_panel)
|
||||
select_layout.setContentsMargins(8, 10, 8, 6)
|
||||
select_layout.setSpacing(6)
|
||||
self.id_select_label = QLabel(f"按ID:{_selection_mode_label(self._current_selection_mode())}")
|
||||
self.id_select_label.setObjectName("idSelectLabel")
|
||||
self.id_select_label.setMinimumWidth(78)
|
||||
help_tip(self.id_select_label, "按当前选择模式解释输入的 ID。")
|
||||
self.id_input = QLineEdit("")
|
||||
self.id_input.setObjectName("idSelectInput")
|
||||
self.id_input.setPlaceholderText("ID")
|
||||
self.id_input.setMaximumWidth(70)
|
||||
help_tip(self.id_input, "输入要选中的对象 ID。ID 类型显示在左侧标签中,并会跟随选择模式变化。")
|
||||
self.select_id_button = QPushButton("选择")
|
||||
self.select_id_button.setMaximumWidth(58)
|
||||
help_tip(self.select_id_button, "按当前选择模式跳转到输入的 ID,并在 3D 视图中高亮它。")
|
||||
self.select_id_button.clicked.connect(lambda _checked=False: self.select_by_id(self._current_selection_mode()))
|
||||
self.id_input.textChanged.connect(lambda _text: self._update_action_states())
|
||||
self.id_input.returnPressed.connect(lambda: self.select_by_id(self._current_selection_mode()))
|
||||
select_layout.addWidget(self.id_select_label)
|
||||
select_layout.addWidget(self.id_input)
|
||||
select_layout.addWidget(self.select_id_button)
|
||||
mode_layout.addWidget(id_select_panel, stretch=1)
|
||||
panel_layout.addWidget(mode_box)
|
||||
|
||||
select_box = QGroupBox("按 ID 选择")
|
||||
select_box.setObjectName("idSection")
|
||||
help_tip(select_box, "知道对象 ID 时,可以直接输入 ID 跳转选择。")
|
||||
select_layout = QGridLayout(select_box)
|
||||
self.id_input = QLineEdit("")
|
||||
self.id_input.setPlaceholderText("输入 ID")
|
||||
help_tip(self.id_input, "输入要选中的对象 ID。ID 的类型由右侧显示的选择模式决定。")
|
||||
self.id_mode_display = QLineEdit(self.mode_combo.currentText())
|
||||
self.id_mode_display.setObjectName("idModeDisplay")
|
||||
self.id_mode_display.setReadOnly(True)
|
||||
self.id_mode_display.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
||||
self.id_mode_display.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.id_mode_display.setMinimumWidth(70)
|
||||
help_tip(self.id_mode_display, "当前按这个鼠标选择模式解释输入的 ID。它会跟随鼠标选择模式自动变化。")
|
||||
self.select_id_button = QPushButton("选择")
|
||||
help_tip(self.select_id_button, "按当前选择模式跳转到输入的 ID,并在 3D 视图中高亮它。")
|
||||
self.select_id_button.clicked.connect(lambda _checked=False: self.select_by_id(self.mode_combo.currentText()))
|
||||
self.id_input.textChanged.connect(lambda _text: self._update_action_states())
|
||||
self.id_input.returnPressed.connect(lambda: self.select_by_id(self.mode_combo.currentText()))
|
||||
select_layout.addWidget(QLabel("ID"), 0, 0)
|
||||
select_layout.addWidget(self.id_input, 0, 1)
|
||||
select_layout.addWidget(self.id_mode_display, 0, 2)
|
||||
select_layout.addWidget(self.select_id_button, 0, 3)
|
||||
panel_layout.addWidget(select_box)
|
||||
if ENABLE_VIEW_PANEL:
|
||||
view_box = QGroupBox("显示")
|
||||
view_box.setObjectName("viewSection")
|
||||
help_tip(view_box, "只改变视图显示方式,不会修改 STEP 几何。")
|
||||
view_layout = QVBoxLayout(view_box)
|
||||
view_buttons = QHBoxLayout()
|
||||
self.isolate_button = QPushButton("只显示选中")
|
||||
help_tip(self.isolate_button, "把视图临时隔离到当前选中的零件、Solid、Face、Edge 或几何特征区域。不会修改模型。")
|
||||
self.isolate_button.clicked.connect(self.isolate_selected)
|
||||
self.fit_button = QPushButton("对准选中")
|
||||
help_tip(self.fit_button, "把相机移动到当前选中对象附近,方便看清细节。不会修改模型。")
|
||||
self.fit_button.clicked.connect(self.fit_selected)
|
||||
self.show_all_button = QPushButton("显示全部")
|
||||
help_tip(self.show_all_button, "取消隔离显示,恢复查看完整模型。不会修改模型。")
|
||||
self.show_all_button.clicked.connect(self.show_all_geometry)
|
||||
view_buttons.addWidget(self.isolate_button)
|
||||
view_buttons.addWidget(self.fit_button)
|
||||
view_buttons.addWidget(self.show_all_button)
|
||||
view_layout.addLayout(view_buttons)
|
||||
self.show_internal_edges_checkbox = QCheckBox("显示同域内部边")
|
||||
help_tip(
|
||||
self.show_internal_edges_checkbox,
|
||||
"显示同一平面或同一圆柱面内部的拓扑分割边。关闭时会隐藏布尔推拉后常见的视觉接缝线。",
|
||||
)
|
||||
self.show_internal_edges_checkbox.toggled.connect(self._on_internal_edges_toggled)
|
||||
view_layout.addWidget(self.show_internal_edges_checkbox)
|
||||
|
||||
view_box = QGroupBox("显示")
|
||||
view_box.setObjectName("viewSection")
|
||||
help_tip(view_box, "只改变视图显示方式,不会修改 STEP 几何。")
|
||||
view_layout = QVBoxLayout(view_box)
|
||||
view_buttons = QHBoxLayout()
|
||||
self.isolate_button = QPushButton("只显示选中")
|
||||
help_tip(self.isolate_button, "把视图临时隔离到当前选中的 Part、Solid、Face、Edge 或特征区域。不会修改模型。")
|
||||
self.isolate_button.clicked.connect(self.isolate_selected)
|
||||
self.fit_button = QPushButton("对准选中")
|
||||
help_tip(self.fit_button, "把相机移动到当前选中对象附近,方便看清细节。不会修改模型。")
|
||||
self.fit_button.clicked.connect(self.fit_selected)
|
||||
self.show_all_button = QPushButton("显示全部")
|
||||
help_tip(self.show_all_button, "取消隔离显示,恢复查看完整模型。不会修改模型。")
|
||||
self.show_all_button.clicked.connect(self.show_all_geometry)
|
||||
view_buttons.addWidget(self.isolate_button)
|
||||
view_buttons.addWidget(self.fit_button)
|
||||
view_buttons.addWidget(self.show_all_button)
|
||||
view_layout.addLayout(view_buttons)
|
||||
self.show_internal_edges_checkbox = QCheckBox("显示同域内部边")
|
||||
if ENABLE_MEASURE_PANEL:
|
||||
measure_box = QGroupBox("测量")
|
||||
measure_box.setObjectName("measureSection")
|
||||
help_tip(measure_box, "把当前选中对象的拾取点或中心设为 A/B,计算两点距离和 X/Y/Z 差值。不会修改模型。")
|
||||
measure_layout = QVBoxLayout(measure_box)
|
||||
self.measure_text = QPlainTextEdit()
|
||||
self.measure_text.setReadOnly(True)
|
||||
self.measure_text.setMaximumHeight(90)
|
||||
help_tip(self.measure_text, "显示 A 点、B 点、两点距离和各方向差值。")
|
||||
measure_buttons = QGridLayout()
|
||||
self.set_measure_a_button = QPushButton("设为 A")
|
||||
help_tip(self.set_measure_a_button, "把当前选中对象的拾取点设为测量点 A;没有拾取点时使用对象中心。")
|
||||
self.set_measure_a_button.clicked.connect(lambda _checked=False: self.set_measure_point("A"))
|
||||
self.set_measure_b_button = QPushButton("设为 B")
|
||||
help_tip(self.set_measure_b_button, "把当前选中对象的拾取点设为测量点 B;没有拾取点时使用对象中心。")
|
||||
self.set_measure_b_button.clicked.connect(lambda _checked=False: self.set_measure_point("B"))
|
||||
self.copy_measure_button = QPushButton("复制测量")
|
||||
help_tip(self.copy_measure_button, "复制当前测量结果文本,方便记录尺寸。")
|
||||
self.copy_measure_button.clicked.connect(self.copy_measurement)
|
||||
self.clear_measure_button = QPushButton("清除测量")
|
||||
help_tip(self.clear_measure_button, "清除 A/B 测量点和 3D 测量线。不会影响模型。")
|
||||
self.clear_measure_button.clicked.connect(self.clear_measurement)
|
||||
measure_buttons.addWidget(self.set_measure_a_button, 0, 0)
|
||||
measure_buttons.addWidget(self.set_measure_b_button, 0, 1)
|
||||
measure_buttons.addWidget(self.copy_measure_button, 1, 0)
|
||||
measure_buttons.addWidget(self.clear_measure_button, 1, 1)
|
||||
measure_layout.addWidget(self.measure_text)
|
||||
measure_layout.addLayout(measure_buttons)
|
||||
self._refresh_measurement_panel()
|
||||
|
||||
if ENABLE_EXPORT_PANEL:
|
||||
export_box = QGroupBox("导出")
|
||||
export_box.setObjectName("exportSection")
|
||||
help_tip(export_box, "把当前模型或选中对象导出为 STEP,也可以做基础质量检查和修复。")
|
||||
export_layout = QVBoxLayout(export_box)
|
||||
self.export_all_button = QPushButton("导出当前完整 STEP")
|
||||
help_tip(self.export_all_button, "把当前编辑后的整个模型导出为 STEP 文件。导出前会做基础质量检查。")
|
||||
self.export_all_button.clicked.connect(self.export_all)
|
||||
self.export_part_button = QPushButton("导出选中零件")
|
||||
help_tip(self.export_part_button, "只导出当前选中的零件。适合从装配里拆出一个单独零件。")
|
||||
self.export_part_button.clicked.connect(self.export_selected_part)
|
||||
self.export_solid_button = QPushButton("导出选中Solid")
|
||||
help_tip(self.export_solid_button, "只导出当前选中的实体Solid。适合检查或单独保存某个实体。")
|
||||
self.export_solid_button.clicked.connect(self.export_selected_solid)
|
||||
self.export_face_button = QPushButton("导出选中面区域")
|
||||
help_tip(self.export_face_button, "导出当前选中的面区域;同域高亮的共面/同圆柱区域也会一起导出。")
|
||||
self.export_face_button.clicked.connect(self.export_selected_face)
|
||||
self.export_feature_button = QPushButton("导出选中特征区域")
|
||||
help_tip(self.export_feature_button, "导出特征模式识别到的区域,例如孔、槽、圆角或凸台候选。")
|
||||
self.export_feature_button.clicked.connect(self.export_selected_feature)
|
||||
self.export_edge_button = QPushButton("导出选中Edge")
|
||||
help_tip(self.export_edge_button, "导出当前选中的Edge。主要用于调试、定位或把边界单独拿出去检查。")
|
||||
self.export_edge_button.clicked.connect(self.export_selected_edge)
|
||||
self.export_check_button = QPushButton("检查导出质量")
|
||||
help_tip(self.export_check_button, "检查当前导出对象是否像有效实体:B-Rep、Face/Edge 数量、体积和包围盒等。")
|
||||
self.export_check_button.clicked.connect(self.check_export_quality)
|
||||
self.repair_model_button = QPushButton("修复当前模型")
|
||||
help_tip(self.repair_model_button, "对完整模型尝试 ShapeFix 和同域面/边合并。会写入历史,可撤销。")
|
||||
self.repair_model_button.clicked.connect(self.repair_model)
|
||||
self.repair_selected_button = QPushButton("修复选中零件/Solid")
|
||||
help_tip(self.repair_selected_button, "只修复当前选中的零件或Solid,范围比修复完整模型更小。会写入历史,可撤销。")
|
||||
self.repair_selected_button.clicked.connect(self.repair_selected_shape)
|
||||
export_layout.addWidget(self.export_all_button)
|
||||
export_layout.addWidget(self.export_part_button)
|
||||
export_layout.addWidget(self.export_solid_button)
|
||||
export_layout.addWidget(self.export_face_button)
|
||||
export_layout.addWidget(self.export_feature_button)
|
||||
export_layout.addWidget(self.export_edge_button)
|
||||
export_layout.addWidget(self.export_check_button)
|
||||
export_layout.addWidget(self.repair_model_button)
|
||||
export_layout.addWidget(self.repair_selected_button)
|
||||
|
||||
self.object_edit_box = QGroupBox("当前选中对象:未选择")
|
||||
self.object_edit_box.setObjectName("editSection")
|
||||
help_tip(
|
||||
self.show_internal_edges_checkbox,
|
||||
"显示同一平面或同一圆柱面内部的拓扑分割边。关闭时会隐藏布尔推拉后常见的视觉接缝线。",
|
||||
self.object_edit_box,
|
||||
"选择模型对象后,这里会列出当前对象的属性和值;能改的行可以输入目标值,不能改的行只读。",
|
||||
)
|
||||
self.show_internal_edges_checkbox.toggled.connect(self._on_internal_edges_toggled)
|
||||
view_layout.addWidget(self.show_internal_edges_checkbox)
|
||||
object_edit_layout = QVBoxLayout(self.object_edit_box)
|
||||
self.property_table = QTableWidget(0, 4)
|
||||
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.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
self.property_table.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
self.property_table.verticalHeader().setVisible(False)
|
||||
self.property_table.verticalHeader().setDefaultSectionSize(24)
|
||||
self.property_table.verticalHeader().setMinimumSectionSize(22)
|
||||
property_header = self.property_table.horizontalHeader()
|
||||
property_header.setMinimumSectionSize(44)
|
||||
property_header.setStretchLastSection(False)
|
||||
property_header.setSectionResizeMode(0, QHeaderView.ResizeMode.Fixed)
|
||||
property_header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
|
||||
property_header.setSectionResizeMode(2, QHeaderView.ResizeMode.Fixed)
|
||||
property_header.setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch)
|
||||
self.property_table.setColumnWidth(0, 106)
|
||||
self.property_table.setColumnWidth(1, 92)
|
||||
self.property_table.setColumnWidth(2, 92)
|
||||
self.property_table.setColumnWidth(3, 62)
|
||||
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("展开全部属性")
|
||||
help_tip(self.property_expand_button, "当前选中对象属性较多时,默认只显示前 6 行;点击这里展开或收起属性表。")
|
||||
self.property_expand_button.clicked.connect(self.toggle_property_table_expanded)
|
||||
self.property_expand_button.setVisible(False)
|
||||
object_edit_layout.addWidget(self.property_expand_button)
|
||||
self.apply_property_button = QPushButton("参数化建模")
|
||||
help_tip(self.apply_property_button, "按当前属性表里修改过的目标值执行参数化建模;一次只执行一个几何修改,成功后可撤销。")
|
||||
self.apply_property_button.clicked.connect(self.apply_current_property_edit)
|
||||
object_edit_layout.addWidget(self.apply_property_button)
|
||||
|
||||
measure_box = QGroupBox("测量")
|
||||
measure_box.setObjectName("measureSection")
|
||||
help_tip(measure_box, "把当前选中对象的拾取点或中心设为 A/B,计算两点距离和 X/Y/Z 差值。不会修改模型。")
|
||||
measure_layout = QVBoxLayout(measure_box)
|
||||
self.measure_text = QPlainTextEdit()
|
||||
self.measure_text.setReadOnly(True)
|
||||
self.measure_text.setMaximumHeight(90)
|
||||
help_tip(self.measure_text, "显示 A 点、B 点、两点距离和各方向差值。")
|
||||
measure_buttons = QGridLayout()
|
||||
self.set_measure_a_button = QPushButton("设为 A")
|
||||
help_tip(self.set_measure_a_button, "把当前选中对象的拾取点设为测量点 A;没有拾取点时使用对象中心。")
|
||||
self.set_measure_a_button.clicked.connect(lambda _checked=False: self.set_measure_point("A"))
|
||||
self.set_measure_b_button = QPushButton("设为 B")
|
||||
help_tip(self.set_measure_b_button, "把当前选中对象的拾取点设为测量点 B;没有拾取点时使用对象中心。")
|
||||
self.set_measure_b_button.clicked.connect(lambda _checked=False: self.set_measure_point("B"))
|
||||
self.copy_measure_button = QPushButton("复制测量")
|
||||
help_tip(self.copy_measure_button, "复制当前测量结果文本,方便记录尺寸。")
|
||||
self.copy_measure_button.clicked.connect(self.copy_measurement)
|
||||
self.clear_measure_button = QPushButton("清除测量")
|
||||
help_tip(self.clear_measure_button, "清除 A/B 测量点和 3D 测量线。不会影响模型。")
|
||||
self.clear_measure_button.clicked.connect(self.clear_measurement)
|
||||
measure_buttons.addWidget(self.set_measure_a_button, 0, 0)
|
||||
measure_buttons.addWidget(self.set_measure_b_button, 0, 1)
|
||||
measure_buttons.addWidget(self.copy_measure_button, 1, 0)
|
||||
measure_buttons.addWidget(self.clear_measure_button, 1, 1)
|
||||
measure_layout.addWidget(self.measure_text)
|
||||
measure_layout.addLayout(measure_buttons)
|
||||
self._refresh_measurement_panel()
|
||||
|
||||
export_box = QGroupBox("导出")
|
||||
export_box.setObjectName("exportSection")
|
||||
help_tip(export_box, "把当前模型或选中对象导出为 STEP,也可以做基础质量检查和修复。")
|
||||
export_layout = QVBoxLayout(export_box)
|
||||
self.export_all_button = QPushButton("导出当前完整 STEP")
|
||||
help_tip(self.export_all_button, "把当前编辑后的整个模型导出为 STEP 文件。导出前会做基础质量检查。")
|
||||
self.export_all_button.clicked.connect(self.export_all)
|
||||
self.export_part_button = QPushButton("导出选中零件")
|
||||
help_tip(self.export_part_button, "只导出当前选中的零件。适合从装配里拆出一个单独零件。")
|
||||
self.export_part_button.clicked.connect(self.export_selected_part)
|
||||
self.export_solid_button = QPushButton("导出选中Solid")
|
||||
help_tip(self.export_solid_button, "只导出当前选中的实体Solid。适合检查或单独保存某个实体。")
|
||||
self.export_solid_button.clicked.connect(self.export_selected_solid)
|
||||
self.export_face_button = QPushButton("导出选中面区域")
|
||||
help_tip(self.export_face_button, "导出当前选中的面区域;同域高亮的共面/同圆柱区域也会一起导出。")
|
||||
self.export_face_button.clicked.connect(self.export_selected_face)
|
||||
self.export_feature_button = QPushButton("导出选中特征区域")
|
||||
help_tip(self.export_feature_button, "导出 Feature 模式识别到的局部特征区域,例如孔、槽、圆角或凸台候选。")
|
||||
self.export_feature_button.clicked.connect(self.export_selected_feature)
|
||||
self.export_edge_button = QPushButton("导出选中Edge")
|
||||
help_tip(self.export_edge_button, "导出当前选中的Edge。主要用于调试、定位或把边界单独拿出去检查。")
|
||||
self.export_edge_button.clicked.connect(self.export_selected_edge)
|
||||
self.export_check_button = QPushButton("检查导出质量")
|
||||
help_tip(self.export_check_button, "检查当前导出对象是否像有效实体:B-Rep、Face/Edge 数量、体积和包围盒等。")
|
||||
self.export_check_button.clicked.connect(self.check_export_quality)
|
||||
self.repair_model_button = QPushButton("修复当前模型")
|
||||
help_tip(self.repair_model_button, "对完整模型尝试 ShapeFix 和同域面/边合并。会写入历史,可撤销。")
|
||||
self.repair_model_button.clicked.connect(self.repair_model)
|
||||
self.repair_selected_button = QPushButton("修复选中零件/Solid")
|
||||
help_tip(self.repair_selected_button, "只修复当前选中的零件或Solid,范围比修复完整模型更小。会写入历史,可撤销。")
|
||||
self.repair_selected_button.clicked.connect(self.repair_selected_shape)
|
||||
export_layout.addWidget(self.export_all_button)
|
||||
export_layout.addWidget(self.export_part_button)
|
||||
export_layout.addWidget(self.export_solid_button)
|
||||
export_layout.addWidget(self.export_face_button)
|
||||
export_layout.addWidget(self.export_feature_button)
|
||||
export_layout.addWidget(self.export_edge_button)
|
||||
export_layout.addWidget(self.export_check_button)
|
||||
export_layout.addWidget(self.repair_model_button)
|
||||
export_layout.addWidget(self.repair_selected_button)
|
||||
|
||||
edit_box = QGroupBox("编辑")
|
||||
edit_box = QGroupBox(panel)
|
||||
edit_box.setObjectName("editSection")
|
||||
help_tip(edit_box, "这里的按钮会真实修改当前 B-Rep 模型;执行前通常会预览,成功后可撤销。部分能力仍是当前版本的几何近似。")
|
||||
edit_box.setVisible(False)
|
||||
self.hidden_edit_controls_box = edit_box
|
||||
edit_layout = QGridLayout(edit_box)
|
||||
edit_layout.addWidget(QLabel("Face偏移"), 0, 0)
|
||||
self.offset_input = QLineEdit("5.0")
|
||||
@@ -770,118 +931,125 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
|
||||
edit_layout.addWidget(self.rotate_part_button, 24, 0, 1, 2)
|
||||
edit_layout.addWidget(self.rotate_solid_button, 25, 0, 1, 2)
|
||||
|
||||
self.cylinders_button = QPushButton("扫描圆柱面")
|
||||
help_tip(self.cylinders_button, "扫描模型里的圆柱面候选,并粗略判断它们像孔、槽、圆角、凸台还是普通圆柱。")
|
||||
self.cylinders_button.clicked.connect(self.list_cylinders)
|
||||
edit_layout.addWidget(self.cylinders_button, 26, 0, 1, 2)
|
||||
panel_layout.addWidget(self.object_edit_box)
|
||||
if ENABLE_EXPORT_PANEL:
|
||||
panel_layout.addWidget(export_box)
|
||||
if ENABLE_VIEW_PANEL:
|
||||
panel_layout.addWidget(view_box)
|
||||
if ENABLE_MEASURE_PANEL:
|
||||
panel_layout.addWidget(measure_box)
|
||||
|
||||
self.undo_button = QPushButton("撤销")
|
||||
help_tip(self.undo_button, "撤销上一次成功编辑,把模型恢复到编辑前快照。")
|
||||
self.undo_button.clicked.connect(self.undo_edit)
|
||||
self.redo_button = QPushButton("重做")
|
||||
help_tip(self.redo_button, "重做刚刚撤销的编辑。")
|
||||
self.redo_button.clicked.connect(self.redo_edit)
|
||||
edit_layout.addWidget(self.undo_button, 27, 0)
|
||||
edit_layout.addWidget(self.redo_button, 27, 1)
|
||||
panel_layout.addWidget(edit_box)
|
||||
panel_layout.addWidget(export_box)
|
||||
panel_layout.addWidget(view_box)
|
||||
panel_layout.addWidget(measure_box)
|
||||
if ENABLE_EDITABLE_OBJECTS_PANEL:
|
||||
editable_box = QGroupBox("可编辑对象")
|
||||
editable_box.setObjectName("editableSection")
|
||||
help_tip(editable_box, "自动找出当前版本可以尝试编辑的Face或Edge,方便不用手动到处点。")
|
||||
editable_layout = QVBoxLayout(editable_box)
|
||||
editable_button_row = QHBoxLayout()
|
||||
self.editable_refresh_button = QPushButton("扫描对象")
|
||||
help_tip(self.editable_refresh_button, "快速扫描一批可以尝试编辑的对象,并列在下面表格里。")
|
||||
self.editable_refresh_button.clicked.connect(
|
||||
lambda _checked=False: self.refresh_editable_candidates(show_info=True)
|
||||
)
|
||||
self.editable_deep_scan_button = QPushButton("深度扫描")
|
||||
help_tip(self.editable_deep_scan_button, "扫描更多候选对象,结果更全但更慢。默认列表找不到目标时再用。")
|
||||
self.editable_deep_scan_button.clicked.connect(
|
||||
lambda _checked=False: self.refresh_editable_candidates(show_info=True, deep_scan=True)
|
||||
)
|
||||
editable_button_row.addWidget(self.editable_refresh_button)
|
||||
editable_button_row.addWidget(self.editable_deep_scan_button)
|
||||
editable_layout.addLayout(editable_button_row)
|
||||
self.editable_table = QTableWidget(0, 8)
|
||||
self.editable_table.setHorizontalHeaderLabels(
|
||||
["操作", "ID", "对象", "当前值", "状态", "风险", "置信度", "说明"]
|
||||
)
|
||||
self.editable_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
self.editable_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
||||
self.editable_table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
||||
self.editable_table.setMinimumHeight(130)
|
||||
self.editable_table.setAlternatingRowColors(True)
|
||||
help_tip(self.editable_table, "这里列出可尝试编辑的对象。点击一行会选中模型里的对应Face或Edge,并自动填入相关输入框。")
|
||||
self.editable_table.cellClicked.connect(self.on_editable_row_clicked)
|
||||
editable_layout.addWidget(self.editable_table)
|
||||
panel_layout.addWidget(editable_box)
|
||||
|
||||
editable_box = QGroupBox("可编辑对象")
|
||||
editable_box.setObjectName("editableSection")
|
||||
help_tip(editable_box, "自动找出当前版本可以尝试编辑的Face或Edge,方便不用手动到处点。")
|
||||
editable_layout = QVBoxLayout(editable_box)
|
||||
editable_button_row = QHBoxLayout()
|
||||
self.editable_refresh_button = QPushButton("扫描对象")
|
||||
help_tip(self.editable_refresh_button, "快速扫描一批可以尝试编辑的对象,并列在下面表格里。")
|
||||
self.editable_refresh_button.clicked.connect(lambda _checked=False: self.refresh_editable_candidates(show_info=True))
|
||||
self.editable_deep_scan_button = QPushButton("深度扫描")
|
||||
help_tip(self.editable_deep_scan_button, "扫描更多候选对象,结果更全但更慢。默认列表找不到目标时再用。")
|
||||
self.editable_deep_scan_button.clicked.connect(
|
||||
lambda _checked=False: self.refresh_editable_candidates(show_info=True, deep_scan=True)
|
||||
)
|
||||
editable_button_row.addWidget(self.editable_refresh_button)
|
||||
editable_button_row.addWidget(self.editable_deep_scan_button)
|
||||
editable_layout.addLayout(editable_button_row)
|
||||
self.editable_table = QTableWidget(0, 8)
|
||||
self.editable_table.setHorizontalHeaderLabels(
|
||||
["操作", "ID", "对象", "当前值", "状态", "风险", "置信度", "说明"]
|
||||
)
|
||||
self.editable_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
self.editable_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
||||
self.editable_table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
||||
self.editable_table.setMinimumHeight(130)
|
||||
self.editable_table.setAlternatingRowColors(True)
|
||||
help_tip(self.editable_table, "这里列出可尝试编辑的对象。点击一行会选中模型里的对应Face或Edge,并自动填入相关输入框。")
|
||||
self.editable_table.cellClicked.connect(self.on_editable_row_clicked)
|
||||
editable_layout.addWidget(self.editable_table)
|
||||
panel_layout.addWidget(editable_box)
|
||||
if ENABLE_CYLINDER_CANDIDATES_PANEL:
|
||||
candidate_box = QGroupBox("圆柱面候选")
|
||||
candidate_box.setObjectName("candidateSection")
|
||||
help_tip(candidate_box, "列出圆柱面候选,并粗略判断它们像孔、槽、圆角、凸台还是普通圆柱。")
|
||||
candidate_layout = QVBoxLayout(candidate_box)
|
||||
filter_layout = QHBoxLayout()
|
||||
self.cylinders_button = QPushButton("扫描圆柱面")
|
||||
help_tip(self.cylinders_button, "扫描模型里的圆柱面候选,并粗略判断它们像孔、槽、圆角、凸台还是普通圆柱。")
|
||||
self.cylinders_button.clicked.connect(self.list_cylinders)
|
||||
filter_layout.addWidget(self.cylinders_button)
|
||||
filter_layout.addWidget(QLabel("类型"))
|
||||
self.candidate_filter_combo = NoWheelComboBox()
|
||||
self.candidate_filter_combo.addItems(
|
||||
[
|
||||
"All",
|
||||
"Hole/Groove",
|
||||
"Round/Fillet",
|
||||
"Boss/Outer",
|
||||
"Unclear",
|
||||
]
|
||||
)
|
||||
help_tip(
|
||||
self.candidate_filter_combo,
|
||||
"筛选下面的圆柱面候选:孔/槽、圆角、凸台/外圆,或暂时无法明确分类的圆柱面。",
|
||||
)
|
||||
self.candidate_filter_combo.currentTextChanged.connect(
|
||||
lambda _text: self._filter_cached_cylinder_candidates(show_info=True)
|
||||
)
|
||||
filter_layout.addWidget(self.candidate_filter_combo)
|
||||
candidate_layout.addLayout(filter_layout)
|
||||
self.cylinder_table = QTableWidget(0, 8)
|
||||
self.cylinder_table.setHorizontalHeaderLabels(
|
||||
["Face", "Guess", "Diameter", "Span", "Height", "Confidence", "Risk", "零件"]
|
||||
)
|
||||
self.cylinder_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
self.cylinder_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
||||
self.cylinder_table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
||||
self.cylinder_table.setMinimumHeight(140)
|
||||
self.cylinder_table.setAlternatingRowColors(True)
|
||||
help_tip(self.cylinder_table, "这里显示圆柱面候选。点击一行会选中对应Face,并在属性区显示可用操作和风险。")
|
||||
self.cylinder_table.cellClicked.connect(self.on_cylinder_row_clicked)
|
||||
candidate_layout.addWidget(self.cylinder_table)
|
||||
panel_layout.addWidget(candidate_box)
|
||||
|
||||
candidate_box = QGroupBox("圆柱面候选")
|
||||
candidate_box.setObjectName("candidateSection")
|
||||
help_tip(candidate_box, "列出圆柱面候选,并粗略判断它们像孔、槽、圆角、凸台还是普通圆柱。")
|
||||
candidate_layout = QVBoxLayout(candidate_box)
|
||||
filter_layout = QHBoxLayout()
|
||||
filter_layout.addWidget(QLabel("类型"))
|
||||
self.candidate_filter_combo = NoWheelComboBox()
|
||||
self.candidate_filter_combo.addItems(
|
||||
[
|
||||
"All",
|
||||
"Hole/Groove",
|
||||
"Round/Fillet",
|
||||
"Boss/Outer",
|
||||
"Unclear",
|
||||
]
|
||||
)
|
||||
help_tip(
|
||||
self.candidate_filter_combo,
|
||||
"筛选下面的圆柱面候选:孔/槽、圆角、凸台/外圆,或暂时无法明确分类的圆柱面。",
|
||||
)
|
||||
self.candidate_filter_combo.currentTextChanged.connect(
|
||||
lambda _text: self._filter_cached_cylinder_candidates(show_info=True)
|
||||
)
|
||||
filter_layout.addWidget(self.candidate_filter_combo)
|
||||
candidate_layout.addLayout(filter_layout)
|
||||
self.cylinder_table = QTableWidget(0, 8)
|
||||
self.cylinder_table.setHorizontalHeaderLabels(
|
||||
["Face", "Guess", "Diameter", "Span", "Height", "Confidence", "Risk", "Part"]
|
||||
)
|
||||
self.cylinder_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
self.cylinder_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
||||
self.cylinder_table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
||||
self.cylinder_table.setMinimumHeight(140)
|
||||
self.cylinder_table.setAlternatingRowColors(True)
|
||||
help_tip(self.cylinder_table, "这里显示圆柱面候选。点击一行会选中对应Face,并在属性区显示可用操作和风险。")
|
||||
self.cylinder_table.cellClicked.connect(self.on_cylinder_row_clicked)
|
||||
candidate_layout.addWidget(self.cylinder_table)
|
||||
panel_layout.addWidget(candidate_box)
|
||||
|
||||
history_box = QGroupBox("操作历史")
|
||||
history_box.setObjectName("historySection")
|
||||
help_tip(history_box, "查看已经成功执行的编辑、差异预览,并导出报告或 JSON 历史。")
|
||||
history_layout = QVBoxLayout(history_box)
|
||||
self.history_list = QListWidget()
|
||||
self.history_list.setMinimumHeight(110)
|
||||
help_tip(self.history_list, "成功执行过的编辑会出现在这里。点击一条记录可查看参数、差异预览和目标位置。")
|
||||
self.history_list.currentRowChanged.connect(self.on_history_row_changed)
|
||||
self.history_list.currentRowChanged.connect(lambda _row: self._update_action_states())
|
||||
history_buttons = QHBoxLayout()
|
||||
self.clear_diff_button = QPushButton("清除差异预览")
|
||||
help_tip(self.clear_diff_button, "关闭历史记录产生的红/绿差异叠加和热力图显示。不会修改模型。")
|
||||
self.clear_diff_button.clicked.connect(lambda _checked=False: self.clear_diff_preview())
|
||||
self.export_diff_button = QPushButton("导出差异报告")
|
||||
help_tip(self.export_diff_button, "把当前选中的历史记录导出成文本报告,包含参数、拓扑变化和几何差异统计。")
|
||||
self.export_diff_button.clicked.connect(self.export_diff_report)
|
||||
self.export_history_button = QPushButton("导出编辑历史")
|
||||
help_tip(self.export_history_button, "把本次会话里的所有编辑记录导出为 JSON,方便留档或后续复盘。")
|
||||
self.export_history_button.clicked.connect(self.export_operation_history)
|
||||
history_layout.addWidget(self.history_list)
|
||||
history_buttons.addWidget(self.clear_diff_button)
|
||||
history_buttons.addWidget(self.export_diff_button)
|
||||
history_buttons.addWidget(self.export_history_button)
|
||||
history_layout.addLayout(history_buttons)
|
||||
panel_layout.addWidget(history_box)
|
||||
if ENABLE_HISTORY_PANEL:
|
||||
history_box = QGroupBox("操作历史")
|
||||
history_box.setObjectName("historySection")
|
||||
help_tip(history_box, "查看已经成功执行的编辑、差异预览,并导出报告或 JSON 历史。")
|
||||
history_layout = QVBoxLayout(history_box)
|
||||
self.history_list = QListWidget()
|
||||
self.history_list.setMinimumHeight(110)
|
||||
help_tip(self.history_list, "成功执行过的编辑会出现在这里。点击一条记录可查看参数、差异预览和目标位置。")
|
||||
self.history_list.currentRowChanged.connect(self.on_history_row_changed)
|
||||
self.history_list.currentRowChanged.connect(lambda _row: self._update_action_states())
|
||||
history_buttons = QHBoxLayout()
|
||||
self.undo_button = QPushButton("撤销")
|
||||
help_tip(self.undo_button, "撤销上一次成功编辑,把模型恢复到编辑前快照。")
|
||||
self.undo_button.clicked.connect(self.undo_edit)
|
||||
self.redo_button = QPushButton("重做")
|
||||
help_tip(self.redo_button, "重做刚刚撤销的编辑。")
|
||||
self.redo_button.clicked.connect(self.redo_edit)
|
||||
self.clear_diff_button = QPushButton("清除差异预览")
|
||||
help_tip(self.clear_diff_button, "关闭历史记录产生的红/绿差异叠加和热力图显示。不会修改模型。")
|
||||
self.clear_diff_button.clicked.connect(lambda _checked=False: self.clear_diff_preview())
|
||||
self.export_diff_button = QPushButton("导出差异报告")
|
||||
help_tip(self.export_diff_button, "把当前选中的历史记录导出成文本报告,包含参数、拓扑变化和几何差异统计。")
|
||||
self.export_diff_button.clicked.connect(self.export_diff_report)
|
||||
self.export_history_button = QPushButton("导出编辑历史")
|
||||
help_tip(self.export_history_button, "把本次会话里的所有编辑记录导出为 JSON,方便留档或后续复盘。")
|
||||
self.export_history_button.clicked.connect(self.export_operation_history)
|
||||
history_layout.addWidget(self.history_list)
|
||||
history_buttons.addWidget(self.undo_button)
|
||||
history_buttons.addWidget(self.redo_button)
|
||||
history_buttons.addWidget(self.clear_diff_button)
|
||||
history_buttons.addWidget(self.export_diff_button)
|
||||
history_buttons.addWidget(self.export_history_button)
|
||||
history_layout.addLayout(history_buttons)
|
||||
panel_layout.addWidget(history_box)
|
||||
|
||||
info_box = QGroupBox("选中对象信息")
|
||||
info_box.setObjectName("infoSection")
|
||||
@@ -889,7 +1057,7 @@ class StepEditorWindow(WindowCoreMixin, WindowStateMixin, WindowActionMixin, Inf
|
||||
info_layout = QVBoxLayout(info_box)
|
||||
info_buttons = QHBoxLayout()
|
||||
self.copy_id_button = QPushButton("复制 ID")
|
||||
help_tip(self.copy_id_button, "复制当前选中对象的 ID。Face/Feature 会优先复制逻辑 Face ID。")
|
||||
help_tip(self.copy_id_button, "复制当前选中对象的 ID。Face/特征会优先复制逻辑 Face ID。")
|
||||
self.copy_id_button.clicked.connect(self.copy_selected_id)
|
||||
self.copy_pick_button = QPushButton("复制坐标")
|
||||
help_tip(self.copy_pick_button, "复制最近一次鼠标点到模型上的三维坐标。")
|
||||
|
||||
Reference in New Issue
Block a user