#!/usr/bin/env python # -*- coding: utf-8 -*- ''' # @Time : 2025/12/8 14:19 # @Author : reenrr # @File : ui.py # @Description: 线条长度标记与电机位置控制系统主界面 ''' import sys import time from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QGroupBox, QMessageBox, QComboBox, QTextEdit, QTableWidget, QTableWidgetItem) # 新增表格组件 from PySide6.QtCore import QTimer, Qt, Signal, Slot from PySide6.QtGui import (QFont, QPalette, QColor) class LengthMotorController(QMainWindow): """线条长度标记与电机位置控制系统主界面""" update_time_signal = Signal(str) record_data_signal = Signal(float, float, float, float) line_select_signal = Signal(str, float, float, float, float) def __init__(self): super().__init__() self.setWindowTitle("线条长度标记与电机位置控制系统") self.setMinimumSize(1200, 700) self.font1 = QFont("Microsoft YaHei", 12) self.font2 = QFont("Microsoft YaHei", 12) self.recorded_data = [] # 线条A参数 self.line_a_params = { "类型1": (0.5, 5, 0.1, 25), "类型2": (1.0, 10, 0.2, 30), "类型3": (1.2, 8, 0.15, 28), "类型4": (1.8, 12, 0.25, 32), "类型5": (0.8, 7, 0.12, 26), "类型6": (1.4, 14, 0.18, 31), "类型7": (1.6, 16, 0.22, 33), "类型8": (2.0, 18, 0.28, 34) } # 线条B参数 self.line_b_params = { "类型1": (1.5, 15, 0.3, 35), "类型2": (2.0, 20, 0.4, 40), "类型3": (1.6, 18, 0.35, 38), "类型4": (2.2, 22, 0.45, 42), "类型5": (1.7, 25, 0.32, 36), "类型6": (1.9, 28, 0.38, 39), "类型7": (2.1, 30, 0.42, 41), "类型8": (2.3, 32, 0.48, 43) } # 当前选中的线条类型 self.selected_line_a = "类型1" self.selected_line_b = "类型1" self._setup_style() central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) main_layout.setSpacing(20) main_layout.setContentsMargins(30, 30, 30, 30) # 1. 顶部标题(替代原实时时间) self._create_title(main_layout) # 2. 下方左右分栏:左侧线条选择区(表格),右侧参数+3D区 bottom_h_layout = QHBoxLayout() bottom_h_layout.setSpacing(20) # 左侧:线条选择区(表格形式) self._create_line_select_table(bottom_h_layout) # 右侧:参数设置 + 3D显示区 self._create_right_area(bottom_h_layout) main_layout.addLayout(bottom_h_layout) self._init_timer() self._connect_signals() def _setup_style(self): """设置界面样式""" palette = QPalette() palette.setColor(QPalette.Window, QColor(245, 245, 247)) palette.setColor(QPalette.WindowText, QColor(30, 30, 30)) palette.setColor(QPalette.Button, QColor(66, 133, 244)) palette.setColor(QPalette.ButtonText, Qt.white) self.setPalette(palette) self.setStyleSheet(""" QGroupBox { font-weight: bold; border: 2px solid #4285F4; border-radius: 8px; margin-top: 10px; padding-top: 10px; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 8px 0 8px; } QPushButton { border-radius: 6px; padding: 8px 16px; font-size: 12pt; } QPushButton:hover { background-color: #5294FF; } QComboBox { border: 2px solid #DDDDDD; border-radius: 6px; padding: 8px; font-size: 10pt; } QComboBox:focus { border-color: #4285F4; outline: none; } QTextEdit { border: 2px solid #DDDDDD; border-radius: 6px; background-color: white; font-size: 11pt; } QTableWidget { border: 2px solid #DDDDDD; border-radius: 6px; font-size: 11pt; } QTableWidget::item:selected { background-color: #E1F5FE; color: #212121; } """) def _create_title(self, parent_layout): """顶部标题(替代原实时时间)""" title_label = QLabel("线条长度标记与电机位置控制系统") title_label.setFont(QFont("Microsoft YaHei", 20, QFont.Bold)) title_label.setAlignment(Qt.AlignCenter) parent_layout.addWidget(title_label) def _create_line_select_table(self, parent_layout): """左侧线条选择区(表格形式,显示线条A/B的参数)""" line_select_group = QGroupBox("线条选择(参数预览)") line_select_group.setFont(self.font1) line_select_group.setMinimumWidth(self.width() * 0.5) select_layout = QVBoxLayout(line_select_group) select_layout.setSpacing(20) select_layout.setContentsMargins(20, 20, 20, 20) # 线条A表格 a_label = QLabel("线条A参数表:") a_label.setFont(self.font2) self.table_line_a = QTableWidget() self.table_line_a.setRowCount(len(self.line_a_params)) self.table_line_a.setColumnCount(5) # 类型 + a/b/c/d self.table_line_a.setHorizontalHeaderLabels(["类型", "参数a", "参数b", "参数c", "参数d"]) # 填充线条A数据 for row, (type_name, params) in enumerate(self.line_a_params.items()): self.table_line_a.setItem(row, 0, QTableWidgetItem(type_name)) self.table_line_a.setItem(row, 1, QTableWidgetItem(str(params[0]))) self.table_line_a.setItem(row, 2, QTableWidgetItem(str(params[1]))) self.table_line_a.setItem(row, 3, QTableWidgetItem(str(params[2]))) self.table_line_a.setItem(row, 4, QTableWidgetItem(str(params[3]))) self.table_line_a.setFixedHeight(220) self.table_line_a.setEditTriggers(QTableWidget.NoEditTriggers) # 禁止编辑 self.table_line_a.setSelectionBehavior(QTableWidget.SelectRows) # 按行选择 self.table_line_a.selectRow(0) # 默认选中第一行 self.table_line_a.itemSelectionChanged.connect(self._on_line_a_select) # 线条B表格 b_label = QLabel("线条B参数表:") b_label.setFont(self.font2) self.table_line_b = QTableWidget() self.table_line_b.setRowCount(len(self.line_b_params)) self.table_line_b.setColumnCount(5) self.table_line_b.setHorizontalHeaderLabels(["类型", "参数a", "参数b", "参数c", "参数d"]) # 填充线条B数据 for row, (type_name, params) in enumerate(self.line_b_params.items()): self.table_line_b.setItem(row, 0, QTableWidgetItem(type_name)) self.table_line_b.setItem(row, 1, QTableWidgetItem(str(params[0]))) self.table_line_b.setItem(row, 2, QTableWidgetItem(str(params[1]))) self.table_line_b.setItem(row, 3, QTableWidgetItem(str(params[2]))) self.table_line_b.setItem(row, 4, QTableWidgetItem(str(params[3]))) self.table_line_b.setFixedHeight(220) self.table_line_b.setEditTriggers(QTableWidget.NoEditTriggers) self.table_line_b.setSelectionBehavior(QTableWidget.SelectRows) self.table_line_b.selectRow(0) self.table_line_b.itemSelectionChanged.connect(self._on_line_b_select) # 导入按钮 import_layout = QHBoxLayout() import_a_btn = QPushButton("导入线条A选中参数") import_a_btn.setFont(self.font2) import_a_btn.clicked.connect(lambda: self._import_line_param("A")) import_b_btn = QPushButton("导入线条B选中参数") import_b_btn.setFont(self.font2) import_b_btn.clicked.connect(lambda: self._import_line_param("B")) import_layout.addWidget(import_a_btn) import_layout.addWidget(import_b_btn) # 布局 select_layout.addWidget(a_label) select_layout.addWidget(self.table_line_a) select_layout.addWidget(b_label) select_layout.addWidget(self.table_line_b) select_layout.addLayout(import_layout) select_layout.addStretch() # 填充高度 parent_layout.addWidget(line_select_group) def _on_line_a_select(self): """线条A表格选中行变化时,记录选中类型""" selected_rows = self.table_line_a.selectionModel().selectedRows() if selected_rows: # 获取选中行的第一列(类型列)文本 row = selected_rows[0].row() self.selected_line_a = self.table_line_a.item(row, 0).text() def _on_line_b_select(self): """线条B表格选中行变化时,记录选中类型""" selected_rows = self.table_line_b.selectionModel().selectedRows() if selected_rows: row = selected_rows[0].row() self.selected_line_b = self.table_line_b.item(row, 0).text() def _create_right_area(self, parent_layout): """右侧区域:参数设置 + 3D显示区""" right_v_layout = QVBoxLayout() right_v_layout.setSpacing(20) # 1. 实时时间(移到右侧顶部) time_group = QGroupBox("实时时间") time_group.setFont(self.font1) time_layout = QHBoxLayout(time_group) self.time_label = QLabel() self.time_label.setAlignment(Qt.AlignCenter) self.time_label.setFont(QFont("Microsoft YaHei", 14, QFont.Bold)) time_layout.addWidget(self.time_label) right_v_layout.addWidget(time_group) # 2. 线条类型设置区 control_group = QGroupBox("线条类型设置") control_group.setFont(self.font1) control_layout = QVBoxLayout(control_group) control_layout.setSpacing(15) control_layout.setContentsMargins(20, 20, 20, 20) # 提取所有参数a/b/c/d的可能值 # 收集参数a的所有值 # 提取所有参数a/b/c/d的可能值(统一格式) # 收集参数a的所有值(保留2位小数,去除末尾0) param_a_list = [] for params in self.line_a_params.values(): a = params[0] a_str = f"{a:.2f}".rstrip('0').rstrip('.') if '.' in f"{a:.2f}" else f"{a:.2f}" param_a_list.append(a_str) for params in self.line_b_params.values(): a = params[0] a_str = f"{a:.2f}".rstrip('0').rstrip('.') if '.' in f"{a:.2f}" else f"{a:.2f}" param_a_list.append(a_str) param_a_list = sorted(list(set(param_a_list)), key=lambda x: float(x)) # 收集参数b的所有值(整数格式) param_b_list = [] for params in self.line_a_params.values(): b_str = f"{params[1]:.0f}" param_b_list.append(b_str) for params in self.line_b_params.values(): b_str = f"{params[1]:.0f}" param_b_list.append(b_str) param_b_list = sorted(list(set(param_b_list)), key=lambda x: int(x)) # 收集参数c的所有值(保留2位小数,去除末尾0) param_c_list = [] for params in self.line_a_params.values(): c = params[2] c_str = f"{c:.2f}".rstrip('0').rstrip('.') if '.' in f"{c:.2f}" else f"{c:.2f}" param_c_list.append(c_str) for params in self.line_b_params.values(): c = params[2] c_str = f"{c:.2f}".rstrip('0').rstrip('.') if '.' in f"{c:.2f}" else f"{c:.2f}" param_c_list.append(c_str) param_c_list = sorted(list(set(param_c_list)), key=lambda x: float(x)) # 收集参数d的所有值(整数格式) param_d_list = [] for params in self.line_a_params.values(): d_str = f"{params[3]:.0f}" param_d_list.append(d_str) for params in self.line_b_params.values(): d_str = f"{params[3]:.0f}" param_d_list.append(d_str) param_d_list = sorted(list(set(param_d_list)), key=lambda x: int(x)) # 4个参数下拉框(横向布局) param_layout = QHBoxLayout() param_layout.setSpacing(20) # 参数a a_param_layout = QVBoxLayout() label_a = QLabel("参数a:") label_a.setFont(self.font2) label_a.setAlignment(Qt.AlignCenter) self.combo_a = QComboBox() self.combo_a.addItems(param_a_list) self.combo_a.setFixedWidth(100) self.combo_a.setFont(self.font2) a_param_layout.addWidget(label_a) a_param_layout.addWidget(self.combo_a) param_layout.addLayout(a_param_layout) # 参数b b_param_layout = QVBoxLayout() label_b = QLabel("参数b:") label_b.setFont(self.font2) label_b.setAlignment(Qt.AlignCenter) self.combo_b = QComboBox() self.combo_b.addItems(param_b_list) self.combo_b.setFixedWidth(100) self.combo_b.setFont(self.font2) b_param_layout.addWidget(label_b) b_param_layout.addWidget(self.combo_b) param_layout.addLayout(b_param_layout) # 参数c c_param_layout = QVBoxLayout() label_c = QLabel("参数c:") label_c.setFont(self.font2) label_c.setAlignment(Qt.AlignCenter) self.combo_c = QComboBox() self.combo_c.addItems(param_c_list) self.combo_c.setFixedWidth(100) self.combo_c.setFont(self.font2) c_param_layout.addWidget(label_c) c_param_layout.addWidget(self.combo_c) param_layout.addLayout(c_param_layout) # 参数d d_param_layout = QVBoxLayout() label_d = QLabel("参数d:") label_d.setFont(self.font2) label_d.setAlignment(Qt.AlignCenter) self.combo_d = QComboBox() self.combo_d.addItems(param_d_list) self.combo_d.setFixedWidth(100) self.combo_d.setFont(self.font2) d_param_layout.addWidget(label_d) d_param_layout.addWidget(self.combo_d) param_layout.addLayout(d_param_layout) # 按钮行 button_layout = QHBoxLayout() self.save_btn = QPushButton("保存设置") self.save_btn.setFont(self.font2) self.save_btn.setFixedSize(140, 45) # 宽140px,高45px self.clear_btn = QPushButton("清空选择") self.clear_btn.setFont(self.font2) self.clear_btn.setFixedSize(140, 45) # 宽140px,高45px self.export_btn = QPushButton("导出记录") self.export_btn.setFont(self.font2) self.export_btn.setFixedSize(140, 45) # 宽140px,高45px button_layout.addStretch() button_layout.addWidget(self.save_btn) button_layout.addWidget(self.clear_btn) button_layout.addWidget(self.export_btn) button_layout.addStretch() control_layout.addLayout(param_layout) control_layout.addLayout(button_layout) right_v_layout.addWidget(control_group) # 2. 3D效果显示占位区 self._create_3d_placeholder(right_v_layout) parent_layout.addLayout(right_v_layout) def _create_3d_placeholder(self, parent_layout): """3D效果显示占位区(缩小版)""" display_group = QGroupBox("线条3D效果显示(后续接口)") display_group.setFont(self.font1) display_layout = QVBoxLayout(display_group) display_layout.setContentsMargins(20, 20, 20, 20) placeholder_text = QTextEdit() placeholder_text.setReadOnly(True) placeholder_text.setMaximumHeight(180) placeholder_text.setText("""3D接口说明: 1. 后续集成3D模块(如Qt3DExtras) 2. 参数a→半径,参数b→长度,参数c/d→颜色 3. 保存参数触发record_data_signal更新3D""") placeholder_text.setFont(QFont("Microsoft YaHei", 10)) test_btn = QPushButton("测试参数传递") test_btn.setFont(self.font2) test_btn.setFixedWidth(150) test_btn.clicked.connect(self._test_3d_param_pass) display_layout.addWidget(placeholder_text) display_layout.addWidget(test_btn, alignment=Qt.AlignCenter) parent_layout.addWidget(display_group) def _init_timer(self): """初始化定时器""" self.timer = QTimer(self) self.timer.timeout.connect(self._update_current_time) self.timer.start(1000) self._update_current_time() def _connect_signals(self): """连接信号与槽函数""" self.save_btn.clicked.connect(self._save_settings) self.clear_btn.clicked.connect(self._clear_inputs) self.export_btn.clicked.connect(self._export_records) self.update_time_signal.connect(self.time_label.setText) self.record_data_signal.connect(self._log_param_change) self.line_select_signal.connect(self._set_line_params) def _update_current_time(self): """更新当前时间""" current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) self.update_time_signal.emit(current_time) @Slot(str) def _import_line_param(self, line_type): """导入选中线条的参数""" if line_type == "A": selected_type = self.selected_line_a params = self.line_a_params[selected_type] line_name = f"线条A-{selected_type}" else: selected_type = self.selected_line_b params = self.line_b_params[selected_type] line_name = f"线条B-{selected_type}" self.line_select_signal.emit(line_name, *params) QMessageBox.information(self, "导入成功", f"已导入{line_name}的参数") @Slot(str, float, float, float, float) def _set_line_params(self, line_name, a, b, c, d): """设置参数到下拉框(统一浮点数格式)""" # 根据参数特性设置小数位:a/c保留2位,b/d保留0位(整数) a_str = f"{a:.2f}".rstrip('0').rstrip('.') if '.' in f"{a:.2f}" else f"{a:.2f}" b_str = f"{b:.0f}" # 参数b是整数(5/7/8等) c_str = f"{c:.2f}".rstrip('0').rstrip('.') if '.' in f"{c:.2f}" else f"{c:.2f}" d_str = f"{d:.0f}" # 参数d是整数(25/26/28等) """设置参数到下拉框""" self.combo_a.setCurrentText(a_str) self.combo_b.setCurrentText(b_str) self.combo_c.setCurrentText(c_str) self.combo_d.setCurrentText(d_str) @Slot() def _save_settings(self): """保存参数并触发信号""" try: param_a = float(self.combo_a.currentText()) param_b = float(self.combo_b.currentText()) param_c = float(self.combo_c.currentText()) param_d = float(self.combo_d.currentText()) except ValueError: QMessageBox.critical(self, "输入错误", "参数格式无效!") return if param_a <= 0 or param_b <= 0 or param_c < 0 or param_d < 0: QMessageBox.warning(self, "参数错误", "参数a/b必须大于0,c/d不能小于0!") return self.record_data_signal.emit(param_a, param_b, param_c, param_d) QMessageBox.information(self, "保存成功", f"已保存设置:\n参数a:{param_a}\n参数b:{param_b}\n参数c:{param_c}\n参数d:{param_d}") @Slot(float, float, float, float) def _log_param_change(self, a, b, c, d): """记录参数变化""" self.recorded_data.append({ "a": a, "b": b, "c": c, "d": d, "time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) }) @Slot() def _test_3d_param_pass(self): """测试参数传递""" try: param_a = float(self.combo_a.currentText()) param_b = float(self.combo_b.currentText()) param_c = float(self.combo_c.currentText()) param_d = float(self.combo_d.currentText()) except ValueError: QMessageBox.critical(self, "测试失败", "当前参数格式无效!") return QMessageBox.information( self, "参数传递测试成功", f"当前参数已准备好传递给3D模块:\n" f"参数a(半径):{param_a}\n" f"参数b(长度):{param_b}\n" f"参数c(颜色通道1):{param_c}\n" f"参数d(颜色通道2):{param_d}" ) @Slot() def _clear_inputs(self): """清空所有选择""" self.combo_a.setCurrentIndex(0) self.combo_b.setCurrentIndex(0) self.combo_c.setCurrentIndex(0) self.combo_d.setCurrentIndex(0) self.table_line_a.selectRow(0) self.table_line_b.selectRow(0) @Slot() def _export_records(self): """导出参数记录""" if not self.recorded_data: QMessageBox.warning(self, "导出提示", "暂无记录可导出!") return try: filename = f"线条参数记录_{time.strftime('%Y%m%d_%H%M%S')}.txt" with open(filename, 'w', encoding='utf-8') as f: f.write("线条参数记录\n") f.write("=" * 60 + "\n") f.write(f"导出时间:{time.strftime('%Y-%m-%d %H:%M:%S')}\n") f.write("参数a\t参数b\t参数c\t参数d\t记录时间\n") f.write("-" * 60 + "\n") for data in self.recorded_data: f.write(f"{data['a']:.2f}\t{data['b']:.2f}\t{data['c']:.2f}\t{data['d']:.2f}\t{data['time']}\n") QMessageBox.information(self, "导出成功", f"记录已导出到:\n{filename}") except Exception as e: QMessageBox.critical(self, "导出失败", f"导出文件时出错:{str(e)}") def closeEvent(self, event): """关闭窗口确认""" reply = QMessageBox.question(self, "确认退出", "是否确定退出程序?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self.timer.stop() event.accept() else: event.ignore() # ----------界面对外接口----------- def ui_main(): app = QApplication(sys.argv) app.setApplicationName("长度电机控制器") app.setApplicationVersion("1.0.0") window = LengthMotorController() window.show() sys.exit(app.exec()) # ----------测试接口-------------- if __name__ == "__main__": ui_main()