from PySide6.QtCore import Qt, Signal from PySide6.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QHBoxLayout, QFormLayout, QGroupBox, QDialog, ) from qfluentwidgets import ( PushButton, ComboBox, DoubleSpinBox, SpinBox, setTheme, Theme, StrongBodyLabel, EditableComboBox, RadioButton, isDarkTheme, MessageBox, ) from ...model.point_state import PointState class StatusEditDialog(QDialog): point_state_applied = Signal(int, PointState) def __init__(self, pos_name, selected_row_idx, parent=None): super().__init__(parent) # 窗口基本设置 self.setWindowTitle("状态编辑") # 设置窗口标题 self.resize(600, 660) # 窗口大小 self.setMinimumSize(550, 560) # 最小尺寸限制 # 点位名称 self.pos_name = pos_name # 选中行的行索引 self.selected_row_idx = selected_row_idx self.__initWidget() def __initWidget(self): # 创建控件 self.__createWidget() # 设置样式 self.__initStyles() # 设置布局 self.__initLayout() # 绑定 self.__bind() # 创建相关控件 def __createWidget(self): # 1. 点位名称输入 self.name_combo = EditableComboBox() self.name_combo.addItems( ["抓取点", "破袋点", "震动点", "扔袋点", "相机/待抓点"] ) # 检查点位名称在下拉框是否已经存在 target_pos_name = self.pos_name pos_name_index = self.name_combo.findText(target_pos_name) # 若未找到(索引=-1),则添加 表单中 的点位名字 if pos_name_index == -1: self.name_combo.addItem(self.pos_name) # 选中新添加的这项 new_index = self.name_combo.count() - 1 self.name_combo.setCurrentIndex(new_index) else: # 已经存在的话就直接选中 self.name_combo.setCurrentIndex(pos_name_index) self.name_combo.setPlaceholderText("请设置点位名称") # 2. 工具坐标系id self.tool_coord_spin = SpinBox() self.tool_coord_spin.setRange(0, 99) # 0-99范围 self.tool_coord_spin.setValue(0) # 默认值 self.tool_coord_btn = PushButton("获取当前工具坐标id") # 3. 工件坐标系id self.work_coord_spin = SpinBox() self.work_coord_spin.setRange(0, 99) # 0-99范围 self.work_coord_spin.setValue(0) # 默认值 self.work_coord_btn = PushButton("获取当前工件坐标id") # 4-9. 关节坐标 J1 到 J6 self.j_spins = [] for _ in range(6): spin = DoubleSpinBox() spin.setRange(-180, 180) # 角度范围 (-180度到180度) spin.setDecimals(3) # 保留3位小数 spin.setSingleStep(0.001) # 默认步长 self.j_spins.append(spin) # 关节坐标默认值 (默认为无效值) self.j_spins[0].setValue(-9999) self.j_spins[1].setValue(-9999) self.j_spins[2].setValue(-9999) self.j_spins[3].setValue(-9999) self.j_spins[4].setValue(-9999) self.j_spins[5].setValue(-9999) # 关节坐标设置 右侧的步长设置 和 获取关节坐标按钮 self.step_group = QGroupBox("单击步长设置") self.step_input = DoubleSpinBox() self.step_input.setRange(0.001, 180.0) # 步长范围 self.step_input.setDecimals(3) # 保留3位小数 self.step_input.setValue(0.001) # 默认步长 self.get_values_btn = PushButton("获取当前J1-J6值") # 10. 速度 (移动速度) self.approach_speed_spin = DoubleSpinBox() self.approach_speed_spin.setRange(0, 100) self.approach_speed_spin.setDecimals(0) # 小数点 self.approach_speed_spin.setValue(20) self.approach_speed_spin.setSingleStep(10) # 11. 运动类型(下拉选择) self.motion_type_combo = ComboBox() self.motion_type_combo.addItems(["直线", "曲线中间点", "曲线终点", "自由路径"]) # 12. 平滑选择 self.stop_radio = RadioButton("停止") self.smooth_radio = RadioButton("平滑过渡") self.smooth_ms_spin = DoubleSpinBox() # 平滑过渡的时间(毫秒) self.smooth_ms_spin.setRange(0, 500) # 范围:0 - 500 ms self.smooth_ms_spin.setDecimals(0) # 整数毫秒 self.smooth_ms_spin.setValue(0) # 默认值0 self.smooth_ms_spin.setSingleStep(10) # 步长:10毫秒 self.smooth_ms_spin.setEnabled(False) # 初始禁用(仅“平滑过渡”选中时启用) self.stop_radio.setChecked(True) # 默认选“停止” # 13. 应用按钮 self.apply_btn = PushButton("应用") self.apply_btn.setMinimumWidth(160) # 按钮最小宽度 def __initStyles(self): # 根据主题设置样式表 if isDarkTheme(): # 深色主题 self.step_group.setStyleSheet( """ QGroupBox { color: white; /* 标题文字颜色 */ border: 1px solid white; /* 边框线条颜色和宽度 */ border-radius: 6px; /* 边框圆角 */ margin-top: 10px; /* 标题与边框的距离 */ } QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; /* 标题位置 */ left: 10px; /* 标题左边距 */ padding: 0 3px 0 3px; /* 标题内边距 */ } """ ) self.setStyleSheet("background-color: rgb(32, 32, 32);") else: # 浅色主题 self.step_group.setStyleSheet( """ QGroupBox { color: black; /* 标题文字颜色 */ border: 1px solid black; /* 边框线条颜色和宽度 */ border-radius: 6px; /* 边框圆角 */ margin-top: 10px; /* 标题与边框的距离 */ } QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; /* 标题位置 */ left: 10px; /* 标题左边距 */ padding: 0 3px 0 3px; /* 标题内边距 */ } """ ) self.setStyleSheet("background-color: rgb(243, 243, 243);") def __initLayout(self): # 主布局(直接应用于当前Widget) main_layout = QVBoxLayout(self) main_layout.setContentsMargins(24, 24, 24, 24) # 边距 main_layout.setSpacing(16) # 控件间距 # 表单布局(管理标签和输入框) form_layout = QFormLayout() form_layout.setRowWrapPolicy(QFormLayout.DontWrapRows) # 不自动换行 # 标签右对齐+垂直居中 form_layout.setLabelAlignment(Qt.AlignRight | Qt.AlignVCenter) form_layout.setSpacing(12) # 表单行间距 # 1. 添加点位名称布局 form_layout.addRow(StrongBodyLabel("点位名称"), self.name_combo) # 2. 添加工具坐标布局 tool_coord_layout = QHBoxLayout() tool_coord_layout.addWidget(self.tool_coord_spin) tool_coord_layout.addWidget(self.tool_coord_btn) form_layout.addRow(StrongBodyLabel("工具坐标id"), tool_coord_layout) # 3. 添加工件坐标布局 work_coord_layout = QHBoxLayout() # 工件坐标水平布局 work_coord_layout.addWidget(self.work_coord_spin) work_coord_layout.addWidget(self.work_coord_btn) form_layout.addRow(StrongBodyLabel("工件坐标id"), work_coord_layout) # 4-9 关节坐标布局 joint_control_layout = QHBoxLayout() # 左侧:关节角输入(J1-J6) joint_input_layout = QFormLayout() joint_input_layout.setRowWrapPolicy(QFormLayout.DontWrapRows) joint_input_layout.setLabelAlignment(Qt.AlignRight | Qt.AlignVCenter) joint_input_layout.setSpacing(12) for index in range(6): joint_input_layout.addRow( StrongBodyLabel(f"J{index + 1} (°)"), self.j_spins[index] ) # 将关节坐标输入布局 添加到 关节坐标布局 joint_control_layout.addLayout(joint_input_layout) # 右侧:步长设置和获取按钮 control_panel_layout = QVBoxLayout() control_panel_layout.setSpacing(16) step_layout = QVBoxLayout(self.step_group) step_layout.setContentsMargins(10, 15, 10, 15) step_layout.setSpacing(10) step_layout.addWidget(self.step_input) # step_layout添加到控制面板布局 control_panel_layout.addWidget(self.step_group) control_panel_layout.addWidget(self.get_values_btn) control_panel_layout.addStretch() # 拉伸项,使内容靠上 # 将 控制面板布局(右侧) 添加到 关节控制布局 joint_control_layout.addLayout(control_panel_layout) # 将关节控制水平布局添加到表单布局 form_layout.addRow(StrongBodyLabel("关节坐标"), joint_control_layout) # 10. 速度布局 form_layout.addRow(StrongBodyLabel("速度 (%)"), self.approach_speed_spin) # 11. 运动类型(下拉选择)布局 form_layout.addRow(StrongBodyLabel("运动类型"), self.motion_type_combo) # 12. "在此点" 平滑选择布局 stop_layout = QHBoxLayout() stop_layout.addWidget(self.stop_radio) stop_layout.addWidget(self.smooth_radio) stop_layout.addWidget(self.smooth_ms_spin) stop_layout.addWidget(StrongBodyLabel("ms")) stop_layout.setAlignment(Qt.AlignLeft) # 与标签左对齐 form_layout.addRow(StrongBodyLabel("在此点"), stop_layout) # 将表单布局添加到主布局 main_layout.addLayout(form_layout) # 13. 底部按钮布局(居中显示) btn_layout = QHBoxLayout() btn_layout.setAlignment(Qt.AlignHCenter) # 水平居中 btn_layout.addWidget(self.apply_btn) # 将底部按钮布局添加到主布局 main_layout.addLayout(btn_layout) # 让表单控件顶部对齐 main_layout.addStretch(1) def __bind(self): # 更新 J1 到 J6 的步长 self.step_input.valueChanged.connect(self.onUpdateStepSize) # 获取 J1 到 J6 的值(外部相关) self.get_values_btn.clicked.connect(self.onGetJointValues) # 调整平滑时间设置控件可不可用 self.stop_radio.toggled.connect( lambda checked: self.smooth_ms_spin.setEnabled(not checked) ) self.smooth_radio.toggled.connect( lambda checked: self.smooth_ms_spin.setEnabled(checked) ) # 应用按钮点击 (外部相关) self.apply_btn.clicked.connect(self.onApplyBtnClicked) # 设置状态编辑框中的 点位的状态 def setPointStateValue(self, pos_state_dict: dict): # 设置除了点位名字之外的所有 点位状态的值 self.approach_speed_spin.setValue(pos_state_dict["speed"]) self.tool_coord_spin.setValue(pos_state_dict["tool_id"]) self.work_coord_spin.setValue(pos_state_dict["work_id"]) for index in range(6): self.j_spins[index].setValue(pos_state_dict["joint_values"][index]) # 运动状态设置 # 查找目标文本对应的索引 target_motion_type = pos_state_dict["motion_type"] # 1. 查找目标文本对应的索引 motion_index = self.motion_type_combo.findText(target_motion_type) # 2. 若未找到(索引=-1),默认选中第0项;否则选中对应索引 if motion_index == -1: self.motion_type_combo.setCurrentIndex(0) else: self.motion_type_combo.setCurrentIndex(motion_index) if pos_state_dict["blend_time"] == -1: # 此时为 停止 self.stop_radio.setChecked(True) else: self.smooth_radio.setChecked(True) self.smooth_ms_spin.setValue(pos_state_dict["blend_time"]) def onUpdateStepSize(self, value): """更新所有关节角输入框的步长""" for spin in self.j_spins: spin.setSingleStep(value) def onGetJointValues(self): """获取J1-J6的值(这里用示例值模拟)""" # 实际应用中,这里应该从设备或其他数据源获取值 # 这里用随机值模拟 import random for i in range(6): # 生成一个-180到180之间的随机数,保留3位小数 value = round(random.uniform(-180, 180), 3) self.j_spins[i].setValue(value) print("已获取并更新J1-J6的值") def onApplyBtnClicked(self): """应用按钮点击事件处理""" # 1、获取点名称 pos_name = self.name_combo.text() # 2、速度 speed = self.approach_speed_spin.value() # 3、tool_id tool_id = self.tool_coord_spin.value() # 4、work_id work_id = self.work_coord_spin.value() # 5-10、所有关节坐标 J1 到 J6 joint_values = [spin.value() for spin in self.j_spins] # 11、运动类型 (直线、 曲线中间点、 曲线终点、 自由路径) motion_type = self.motion_type_combo.currentText() # 12、平滑时间(停止=-1,否则取输入值) blend_time = -1 if self.stop_radio.isChecked() else self.smooth_ms_spin.value() try: point_state = PointState( pos_name=pos_name, speed=speed, tool_id=tool_id, work_id=work_id, joint_values=joint_values, motion_type=motion_type, blend_time=blend_time, ) # print("状态编辑结果:", point_state.__str__()) # 发送信号给 表单窗口 CoordinateTableWidget对象 self.point_state_applied.emit(self.selected_row_idx, point_state) # 关闭状态编辑窗口 self.close() except ValueError as e: # 捕获校验错误,弹窗提示用户 MessageBox("状态错误", str(e), self).exec() # if __name__ == "__main__": # app = QApplication([]) # setTheme(Theme.DARK) # 设置浅色主题(可选:Theme.DARK) # widget = StatusEditWidget() # widget.show() # 显示窗口 # app.exec()