#!/usr/bin/env python # -*- coding: utf-8 -*- ''' # @Time : 2025/08/04 # @Author : hx # @File : qt_main.py # 功能:PyQt5 GUI + 五连杆轨迹动画 + 真实电机控制(DM4310 + 轮趣切割) # 修改:使用 qiege_motor.control() 统一控制切割电机 ''' import sys import time import numpy as np import matplotlib matplotlib.use('Qt5Agg') from PyQt5.QtWidgets import ( QApplication, QDialog, QVBoxLayout, QMessageBox ) from PyQt5.QtGui import QStandardItemModel, QStandardItem from PyQt5.QtCore import Qt from matplotlib.figure import Figure from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.animation import FuncAnimation # 导入你的 UI 和算法模块 from untitled import Ui_Dialog from calculate.ik import inverseF from calculate.trajectory import circle_trajectory, line_trajectory import matplotlib.pyplot as plt # 设置中文字体和解决负号显示问题 plt.rcParams['font.sans-serif'] = ['SimHei', 'WenQuanYi Zen Hei', 'FangSong'] # 按优先级选择字体 plt.rcParams['axes.unicode_minus'] = False # 显示负号 - # ------------------------ 调试与模式开关 ------------------------ DEBUG_MODE = True # <<< 设为 False 启用真实电机控制 USE_QIEGE_MOTOR = False # <<< 设为 True 才启用 qiege_motor.py 控制切割电机 # ------------------------ 机械臂参数 ------------------------ L1 = 250 L2 = 300 L3 = 300 L4 = 250 L0 = 250 # 右侧基座偏移 # ------------------------ 电机配置 ------------------------ CAN_PORT = '/dev/ttyACM0' CAN_BAUD = 921600 DM_MOTOR1_ID = 0x01 # 左臂 DM4310 DM_MOTOR2_ID = 0x04 # 右臂 DM4310 KP = 5.0 KD = 1.0 # ------------------------ 切割参数 ------------------------ CUTTING_MIN_Y = 280 # Y > 此值才启动切割 CUT_SPEED_RAD = 10.0 # 切割转速 (rad/s),用于 qiege_motor 内部映射 # ------------------------ 全局变量 ------------------------ motor1 = motor2 = motor_control = None current_cutting_state = False # 避免重复启停切割电机 # -------------------- 导入硬件模块(仅非 DEBUG 模式) -------------------- if not DEBUG_MODE: try: from DM_CAN.DM_CAN import Motor, MotorControl, DM_Motor_Type, Control_Type import serial print("【硬件模块导入成功】: DM4310 电机") except Exception as e: print(f"【硬件模块导入失败】: {e}") sys.exit(1) else: print("【DEBUG MODE】: 不连接真实电机") # -------------------- 导入切割电机模块(仅当启用时) -------------------- if USE_QIEGE_MOTOR and not DEBUG_MODE: try: from qiege_motor import control as cutting_control print("【切割电机模块导入成功】: qiege_motor.control") except Exception as e: print(f"【qiege_motor 导入失败】: {e}") print("【警告】将使用模拟模式") USE_QIEGE_MOTOR = False else: def cutting_control(enable: bool): """模拟切割电机控制""" state = "启动" if enable else "停止" if DEBUG_MODE: print(f"[DEBUG] 切割电机: {state}") else: if USE_QIEGE_MOTOR: print(f"[WARN] qiege_motor 未启用或导入失败,control({enable}) 被忽略") class MyDialog(QDialog, Ui_Dialog): def __init__(self): super().__init__() self.setupUi(self) # 初始化 listView 模型 self.angle_model = QStandardItemModel(self.listView) self.listView.setModel(self.angle_model) # Matplotlib 图形初始化 self.figure = Figure(figsize=(8, 6), dpi=100) self.ax = self.figure.add_subplot(111) self.ax.set_xlim(-300, 500) self.ax.set_ylim(0, 500) self.ax.set_aspect('equal') self.ax.grid(True, linestyle='--', alpha=0.6) self.ax.set_title("五连杆机械臂运动控制") # 画布 self.canvas = FigureCanvas(self.figure) self.widget.setLayout(QVBoxLayout()) self.widget.layout().addWidget(self.canvas) # 机械臂线 self.line, = self.ax.plot([], [], 'r-o', linewidth=2, markersize=6) # 轨迹数据 self.x_list = [] self.y_list = [] self.ani = None # 初始化电机(仅非 DEBUG) self.init_hardware() # 连接按钮 self.pushButton.clicked.connect(self.start_line_trajectory) self.pushButton_2.clicked.connect(self.start_circle_trajectory) def init_hardware(self): global motor1, motor2, motor_control if DEBUG_MODE: print("【DEBUG】跳过 DM4310 电机初始化") return try: ser = serial.Serial(CAN_PORT, CAN_BAUD, timeout=0.5) motor_control = MotorControl(ser) motor1 = Motor(DM_Motor_Type.DM4310, DM_MOTOR1_ID, 0x11) motor2 = Motor(DM_Motor_Type.DM4310, DM_MOTOR2_ID, 0x14) motor_control.addMotor(motor1) motor_control.addMotor(motor2) motor_control.switchControlMode(motor1, Control_Type.MIT) motor_control.switchControlMode(motor2, Control_Type.MIT) time.sleep(0.1) motor_control.enable(motor1) motor_control.enable(motor2) print("✅ DM4310 电机已使能") except Exception as e: QMessageBox.critical(self, "电机初始化失败", str(e)) print(f"❌ 电机初始化失败: {e}") def control_cutting_motor(self, enable=True): """ 控制切割电机 - 仅在状态变化时调用 - 使用 qiege_motor.control() 接口 """ global current_cutting_state if enable == current_cutting_state: return # 状态未变,不重复操作 try: cutting_control(enable) print(f"✅ 切割电机: {'启动' if enable else '停止'}") except Exception as e: print(f"❌ 调用切割电机 control({enable}) 失败: {e}") current_cutting_state = enable def control_dm_motors(self, theta1, theta4): """控制两个 DM4310 电机""" pos1 = np.degrees(theta1) pos4 = np.degrees(theta4) vel = 0.8 # 可根据需要调整 if DEBUG_MODE: print(f"[DEBUG] DM4310 -> M1:{pos1:.1f}°, M2:{pos4:.1f}°") else: try: motor_control.controlMIT(motor1, KP, KD, pos1, vel, 0.0) motor_control.controlMIT(motor2, KP, KD, pos4, vel, 0.0) except Exception as e: print(f"[DM电机] 控制失败: {e}") def start_line_trajectory(self): self.angle_model.clear() self.ax.set_title("直线轨迹运动 + 电机控制") self.x_list, self.y_list = line_trajectory(start=(125, 300), end=(125, 400)) self.start_animation() def start_circle_trajectory(self): self.angle_model.clear() self.ax.set_title("圆形轨迹运动 + 电机控制") self.x_list, self.y_list = circle_trajectory(center=(100, 200), radius=40) self.start_animation() def start_animation(self): # 安全停止旧动画 try: if self.ani is not None and self.ani.event_source is not None: self.ani.event_source.stop() except: pass # ✅ 开始前:重置切割状态 global current_cutting_state current_cutting_state = False def animate_frame(i): x = self.x_list[i] y = self.y_list[i] try: theta1, theta4 = inverseF(x, y, L1, L2, L3, L4, L0) # 显示角度(角度制) theta1_deg = np.degrees(theta1) theta4_deg = np.degrees(theta4) item_text = f"点 {i}: θ1={theta1_deg:.1f}°, θ4={theta4_deg:.1f}°" if self.angle_model.rowCount() >= 10: self.angle_model.removeRow(0) self.angle_model.appendRow(QStandardItem(item_text)) # 计算五连杆坐标 x2 = L1 * np.cos(theta1) y2 = L1 * np.sin(theta1) x4 = L0 + L4 * np.cos(theta4) y4 = L4 * np.sin(theta4) x_coords = [0, x2, x, x4, L0] y_coords = [0, y2, y, y4, 0] self.line.set_data(x_coords, y_coords) # --- 控制真实电机 --- self.control_dm_motors(theta1, theta4) # --- 切割控制(进入区域才切)--- in_cut_zone = y > CUTTING_MIN_Y self.control_cutting_motor(enable=in_cut_zone) except Exception as e: print(f"第 {i} 帧错误: {e}") return self.line, # 创建动画 self.ani = FuncAnimation( self.figure, animate_frame, frames=len(self.x_list), interval=100, repeat=False, blit=False ) self.canvas.draw() # ✅ 动画结束后自动停止所有电机 from PyQt5.QtCore import QTimer timer = QTimer(self) timer.setSingleShot(True) timer.timeout.connect(self.on_animation_finish) timer.start(int(len(self.x_list) * 100 + 500)) # 动画总时长 + 0.5s 延迟 def on_animation_finish(self): """动画结束后安全关闭所有电机""" if not DEBUG_MODE: try: if 'motor_control' in globals() and motor_control: motor_control.disable(motor1) motor_control.disable(motor2) print("✅ DM4310 电机已停用") except Exception as e: print(f"❌ DM 电机停用失败: {e}") self.control_cutting_motor(False) print("✅ 所有电机已安全关闭") def closeEvent(self, event): """窗口关闭时确保电机停止""" self.control_cutting_motor(False) if not DEBUG_MODE and 'motor_control' in globals(): try: if motor_control: motor_control.disable(motor1) motor_control.disable(motor2) print("【退出】所有电机已停用") except Exception as e: print(f"【退出】电机停用异常: {e}") event.accept() if __name__ == "__main__": app = QApplication(sys.argv) dialog = MyDialog() dialog.show() sys.exit(app.exec_())