利用PyQt简单的实现一个机器人的关节JOG界面

在上一篇文章中如何在Python用Plot画出一个简单的机器人模型,我们介绍了如何在Python中画出一个简单的机器人3D模型,但是有的时候我们需要通过界面去控制机器人每一个轴的转动,并实时的显示出当前机器人的关节位置和末端笛卡尔位姿。
那么要实现上述功能的话,那么我就可以采用 Pyqt5 来实现,具体代码如下:


import sys
import numpy as np
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QVBoxLayout, QHBoxLayout, QLabel, QFileDialog, QLineEdit, QFormLayout, QGridLayout, QDialog, QSpinBox, QDialogButtonBox
)
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from PyQt5.QtCore import QTimer
from ShowRobot import Robot, ShowRobot
from scipy.spatial.transform import Rotation as R
from functools import partial# 设置全局字体以支持中文
plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置默认字体为黑体
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题class SettingsDialog(QDialog):"""设置对话框,用于配置数据格式化规则"""def __init__(self, parent=None):super().__init__(parent)self.setWindowTitle("数据格式化设置")self.initUI()def initUI(self):layout = QFormLayout(self)# 添加小数点位数设置self.decimal_spinbox = QSpinBox(self)self.decimal_spinbox.setRange(0, 6)  # 小数点位数范围self.decimal_spinbox.setValue(2)  # 默认值layout.addRow("小数点位数:", self.decimal_spinbox)# 添加单位设置self.unit_edit = QLineEdit(self)self.unit_edit.setPlaceholderText("例如:° 或 m")layout.addRow("单位:", self.unit_edit)# 添加确认和取消按钮buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self)buttons.accepted.connect(self.accept)buttons.rejected.connect(self.reject)layout.addRow(buttons)def get_settings(self):"""获取用户设置的格式化规则"""return {"decimal_places": self.decimal_spinbox.value(),"unit": self.unit_edit.text().strip(),}class RobotControlApp(QWidget):def __init__(self, robot : Robot, show_robot : ShowRobot):super().__init__()self.data_format = {"decimal_places": 3, "unit": "°"}  # 默认数据格式self.robot = robotself.show_robot = show_robot# 初始化定时器self.timer = QTimer(self)self.timer.timeout.connect(self.on_timer_timeout)self.current_axis = None  # 当前运动的轴self.current_direction = None  # 当前运动的方向self.initUI()def initUI(self):# 设置窗口标题和大小self.setWindowTitle('6轴机器人JOG控制')self.setGeometry(100, 100, 1000, 600)# 创建主布局main_layout = QHBoxLayout()# 左侧布局:控制面板control_layout = QVBoxLayout()# 创建标签self.status_label = QLabel('当前状态: 停止', self)control_layout.addWidget(self.status_label)# 创建6个轴的JOG控制按钮self.axis_buttons = []for i in range(6):# axis_label = QLabel(f'轴 {i+1}', self)# control_layout.addWidget(axis_label)button_layout = QHBoxLayout()positive_button = QPushButton(f'J{i+1} +', self)negative_button = QPushButton(f'J{i+1} -', self)# 连接按钮点击事件,点击一次运动一次positive_button.clicked.connect(lambda _, axis=i: self.move_axis(axis, '正向'))negative_button.clicked.connect(lambda _, axis=i: self.move_axis(axis, '负向'))# 连接按钮按下和松开事件, 使用 partial 绑定参数positive_button.pressed.connect(partial(self.start_motion, axis=i, direction='正向'))positive_button.released.connect(self.stop_motion)negative_button.pressed.connect(partial(self.start_motion, axis=i, direction='负向'))negative_button.released.connect(self.stop_motion)button_layout.addWidget(positive_button)button_layout.addWidget(negative_button)control_layout.addLayout(button_layout)# 添加保存按钮save_button = QPushButton('保存图像', self)save_button.clicked.connect(self.save_plot)control_layout.addWidget(save_button)# 添加设置按钮settings_button = QPushButton('数据格式化设置', self)settings_button.clicked.connect(self.open_settings_dialog)control_layout.addWidget(settings_button)# 添加关节角度显示控件(一行显示)joint_layout = QHBoxLayout()self.joint_angle_edits = []for i in range(6):edit = QLineEdit(self)edit.setReadOnly(True)  # 设置为只读edit.setPlaceholderText(f'关节 {i + 1} 角度')joint_layout.addWidget(edit)self.joint_angle_edits.append(edit)# 添加笛卡尔位置显示控件(一行显示)cartesian_layout = QHBoxLayout()self.cartesian_position_edits = []for label in ['X', 'Y', 'Z', 'Rx', 'Ry', 'Rz']:edit = QLineEdit(self)edit.setReadOnly(True)  # 设置为只读edit.setPlaceholderText(f'{label} 位置')cartesian_layout.addWidget(edit)self.cartesian_position_edits.append(edit)# 将关节角度和笛卡尔位置添加到控制面板control_layout.addLayout(joint_layout)control_layout.addLayout(cartesian_layout)# 将控制面板添加到主布局main_layout.addLayout(control_layout)# 右侧布局:3D 绘图窗口self.figure = Figure()self.canvas = FigureCanvas(self.figure)self.ax = self.figure.add_subplot(111, projection='3d')self.ax.set_title("机器人运动轨迹 (3D)")self.ax.set_xlabel("X 轴")self.ax.set_ylabel("Y 轴")self.ax.set_zlabel("Z 轴")self.ax.grid(True)self.show_robot.ax = self.axself.show_robot.robot = self.robot# 初始化绘图数据T_start = np.identity(4, dtype= float)T_end = np.identity(4, dtype= float)self.show_robot.ShowFrame(T_start, length=500)for joint_index in range(6):T_start = T_endT = self.robot.DH(joint_index, self.show_robot.q_list[joint_index])T_end = T_end * T# print(T_end)self.show_robot.ShowLink(joint_index, T_start, T_end)self.show_robot.ShowFrame(T_end, length=500)joint_angles = self.show_robot.q_listrotation_matrix = T_end[:3, :3]r = R.from_matrix(rotation_matrix)# 将旋转矩阵转换为 XYZ 固定角(Roll-Pitch-Yaw 角)roll, pitch, yaw = r.as_euler('xyz', degrees=True)cartesian_positions = [T_end[0, 3], T_end[1, 3], T_end[2, 3], roll, pitch, yaw]# 更新关节角度显示for i, edit in enumerate(self.joint_angle_edits):edit.setText(f"{joint_angles[i]:.{self.data_format['decimal_places']}f}{self.data_format['unit']}")# 更新笛卡尔位置显示for i, edit in enumerate(self.cartesian_position_edits):edit.setText(f"{cartesian_positions[i]:.{self.data_format['decimal_places']}f}")self.ax.set_xlim([-1000, 1000])self.ax.set_ylim([-1000, 1000])self.ax.set_zlim([-1000, 1000])# 将绘图窗口添加到主布局main_layout.addWidget(self.canvas)# 设置主布局self.setLayout(main_layout)def start_motion(self, axis, direction):"""开始运动"""self.current_axis = axisself.current_direction = directionself.timer.start(100)  # 每 100 毫秒触发一次def stop_motion(self):"""停止运动"""self.timer.stop()self.current_axis = Noneself.current_direction = Noneself.status_label.setText('当前状态: 停止')def on_timer_timeout(self):"""定时器触发时的逻辑"""if self.current_axis is not None and self.current_direction is not None:self.move_axis(self.current_axis, self.current_direction)def move_axis(self, axis, direction):# 这里是控制机器人轴运动的逻辑self.status_label.setText(f'轴 {axis+1} {direction}运动')# 模拟机器人运动并更新绘图self.update_plot(axis, direction)def update_plot(self, axis, direction):self.ax.cla()  # 清除所有轴# 模拟机器人运动数据if direction == '正向':self.show_robot.q_list[axis] += 1.0elif direction == '负向':self.show_robot.q_list[axis] -= 1.0T_start = np.identity(4, dtype= float)T_end = np.identity(4, dtype= float)self.show_robot.ShowFrame(T_start, length=500)for joint_index in range(6):T_start = T_endT = self.robot.DH(joint_index, self.show_robot.q_list[joint_index])T_end = T_end * T# print(T_end)self.show_robot.ShowLink(joint_index, T_start, T_end)self.show_robot.ShowFrame(T_end, length=500)joint_angles = self.show_robot.q_listrotation_matrix = T_end[:3, :3]r = R.from_matrix(rotation_matrix)# 将旋转矩阵转换为 XYZ 固定角(Roll-Pitch-Yaw 角)roll, pitch, yaw = r.as_euler('xyz', degrees=True)cartesian_positions = [T_end[0, 3], T_end[1, 3], T_end[2, 3], roll, pitch, yaw]# 更新关节角度显示for i, edit in enumerate(self.joint_angle_edits):edit.setText(f"{joint_angles[i]:.{self.data_format['decimal_places']}f}{self.data_format['unit']}")# 更新笛卡尔位置显示for i, edit in enumerate(self.cartesian_position_edits):edit.setText(f"{cartesian_positions[i]:.{self.data_format['decimal_places']}f}")self.ax.set_xlim([-1000, 1000])self.ax.set_ylim([-1000, 1000])self.ax.set_zlim([-1000, 1000])# 重绘图self.canvas.draw()def save_plot(self):# 打开文件对话框,让用户选择保存路径和文件格式options = QFileDialog.Options()file_name, selected_filter = QFileDialog.getSaveFileName(self, "保存图像", "", "PNG 文件 (*.png);;JPG 文件 (*.jpg);;PDF 文件 (*.pdf)", options=options)if file_name:# 根据用户选择的文件扩展名保存图像if selected_filter == "PNG 文件 (*.png)":self.figure.savefig(file_name, format='png')elif selected_filter == "JPG 文件 (*.jpg)":self.figure.savefig(file_name, format='jpg')elif selected_filter == "PDF 文件 (*.pdf)":self.figure.savefig(file_name, format='pdf')# 更新状态标签self.status_label.setText(f'图像已保存为 {file_name}')def open_settings_dialog(self):"""打开数据格式化设置对话框"""dialog = SettingsDialog(self)if dialog.exec_() == QDialog.Accepted:self.data_format = dialog.get_settings()  # 更新数据格式self.update_joint_and_cartesian_display()  # 更新显示if __name__ == '__main__':app = QApplication(sys.argv)L1 = 388L2 = 50L3 = 330L4 = 50L5 = 332L6 = 96alpha_list = [90, 0, 90, -90, 90, 0]a_list     = [L2, L3, L4, 0, 0, 0]d_list     = [L1, 0, 0, L5, 0, L6]theta_list = [0, 90, 0, 0, 0, 0]robot = Robot()show_robot = ShowRobot()robot.SetDHParamList(alpha_list, a_list, d_list, theta_list)ex = RobotControlApp(robot, show_robot)ex.show()sys.exit(app.exec_())

在界面中,按下 J1+, 关节1轴就会一直正向转动,松开 J1+按钮之后关节1轴就会停止运动,其他关节轴也是一样的。
然后在界面的下方中还是实时的显示出机器人当前的关节角度和笛卡尔末端位姿。

最终实现的效果如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/72268.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

iOS 使用消息转发机制实现多代理功能

在iOS开发中,我们有时候会用到多代理功能,比如我们列表的埋点事件,需要我们在列表的某个特定的时机进行埋点上报,我们当然可以用最常见的做法,就是设置代理实现代理方法,然后在对应的代理方法里面进行上报&…

XGBoost和LightGBM机器学习算法对比及实战

文章目录 1. XGBoost 原理核心思想关键技术点2. LightGBM 原理核心思想关键技术点3. XGBoost vs LightGBM 对比4. 适用场景选择5. 总结1. 数据准备2. XGBoost 示例安装库代码实现3. LightGBM 示例安装库代码实现4. 关键参数对比5. 注意事项6. 输出示例XGBoost 和 LightGBM 是两…

局域网自动识别机器名和MAC并生成文件的命令

更新版本:添加了MAC 地址 确定了设备唯一性 V1.1 局域网自动识别机器名和MAC并生成文件的批处理命令 echo off setlocal enabledelayedexpansionREM 设置输出文件 set outputFilenetwork_info.txtREM 清空或创建输出文件 echo Scanning network from 192.168.20.1…

基于Python+Vue开发的体育用品商城管理系统源码+开发文档+课程作业

项目简介 该项目是基于PythonVue开发的体育用品商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Python编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Python的体…

pyQT5简易教程(一):制作一个可以选择本地图片并显示的桌面应用

可以参考之前的教程安装 PyQt 和 PyQt Designer https://blog.csdn.net/smx6666668/article/details/145909326?spm=1011.2415.3001.10575&sharefrom=mp_manage_link 一、打开pycharm中的QTdesigner 二、设计界面 和之前一样,使用 PyQt Designer 来设计界面并保存为 .u…

LeetCode 解题思路 6(Hot 100)

解题思路: 初始化窗口元素: 遍历前 k 个元素,构建初始单调队列。若当前索引对应值大于等于队尾索引对应值,移除队尾索引,将当前索引加入队尾。遍历结束时当前队头索引即为当前窗口最大值,将其存入结果数组…

基于redis的位图实现签到功能

基于Redis位图实现签到功能是一种高效且节省内存的方法。以下是分步实现的详细方案&#xff1a; 1. 键设计策略 采用 sign:<userId>:<YYYYMM> 格式存储每月签到数据 # 示例&#xff1a;用户1001在2023年8月的签到数据 sign_key "sign:1001:202308"2.…

C++ Qt OpenGL渲染FFmpeg解码后的视频

本篇博客介绍使用OpenGL渲染FFmpeg解码后的视频,涉及到QOpenGLWidget、QOpenGLFunctions、OpenGL shader以及纹理相关,播放效果如下: 开发环境:Win11 C++ Qt6.8.1、FFmpeg4.0、x64   注意:Qt版本不同时,Qt OpenGL API及用法可能差别比较大,FFmpeg版本不同时API调用可能…

deepseek部署:ELK + Filebeat + Zookeeper + Kafka

## 1. 概述 本文档旨在指导如何在7台机器上部署ELK&#xff08;Elasticsearch, Logstash, Kibana&#xff09;堆栈、Filebeat、Zookeeper和Kafka。该部署方案适用于日志收集、处理和可视化场景。 ## 2. 环境准备 ### 2.1 机器分配 | 机器编号 | 主机名 | IP地址 | 部署组件 |-…

2.数据结构:1.Tire 字符串统计

1.Tire 字符串统计 #include<algorithm> #include<cstring> #include<iostream>using namespace std;const int N100010; int son[N][26];//至多 N 层&#xff0c;每一层至多 26 个节点&#xff08;字母&#xff09; int cnt[N];//字符串至多 N 个&#xff…

算法(四)——位运算与位图

文章目录 位运算、位图位运算基本位运算异或运算交换两个数无比较返回最大值缺失的数字唯一出现奇数次的数唯二出现奇数次的数唯一出现次数少于m次的数 位运算进阶判断一个整数是不是2的幂判断一个整数是不是3的幂大于等于n的最小的2的幂[left, right]内所有数字&的结果反转…

本地部署deepseek大模型后使用c# winform调用(可离线)

介于最近deepseek的大火&#xff0c;我就在想能不能用winform也玩一玩本地部署&#xff0c;于是经过查阅资料&#xff0c;然后了解到ollama部署deepseek,最后用ollama sharp NUGet包来实现winform调用ollama 部署的deepseek。 本项目使用Vs2022和.net 8.0开发&#xff0c;ollam…

SpringBoot原理-02.自动配置-概述

一.自动配置 所谓自动配置&#xff0c;就是Spring容器启动后&#xff0c;一些配置类、bean对象就自动存入了IOC容器当中&#xff0c;而不需要我们手动声明&#xff0c;直接从IOC容器中引入即可。省去了繁琐的配置操作。 我们可以首先将spring项目启动起来&#xff0c;里面有一…

P10265 [GESP样题 七级] 迷宫统计

题目描述 在神秘的幻想⼤陆中&#xff0c;存在着 n 个古老而神奇的迷宫&#xff0c;迷宫编号从 1 到 n。有的迷宫之间可以直接往返&#xff0c;有的可以⾛到别的迷宫&#xff0c;但是不能⾛回来。玩家小杨想挑战⼀下不同的迷宫&#xff0c;他决定从 m 号迷宫出发。现在&#x…

Spring框架中的工厂模式

在Spring框架里&#xff0c;工厂模式的运用十分广泛&#xff0c;它主要帮助我们创建和管理对象&#xff0c;让对象的创建和使用分离&#xff0c;提高代码的可维护性和可扩展性。下面为你详细介绍Spring框架中工厂模式的具体体现和示例&#xff1a; 1. BeanFactory 作为工厂模式…

音视频-WAV格式

1. WAV格式说明&#xff1a; 2. 格式说明&#xff1a; chunkId&#xff1a;通常是 “RIFF” 四个字节&#xff0c;用于标识文件类型。&#xff08;wav文件格式表示&#xff09;chunkSize&#xff1a;表示整个文件除了chunkId和chunkSize这 8 个字节外的其余部分的大小。Forma…

SQL Server Management Studio的使用

之前在https://blog.csdn.net//article/details/140961550介绍了在Windows10上安装SQL Server 2022 Express和SSMS&#xff0c;这里整理下SSMS的简单使用&#xff1a; SQL Server Management Studio(SSMS)是一种集成环境&#xff0c;提供用于配置、监视和管理SQL Server和数据…

数据集笔记:NUSMods API

1 介绍 NUSMods API 包含用于渲染 NUSMods 的数据。这些数据包括新加坡国立大学&#xff08;NUS&#xff09;提供的课程以及课程表的信息&#xff0c;还包括上课地点的详细信息。 可以使用并实验这些数据&#xff0c;它们是从教务处提供的官方 API 中提取的。 该 API 由静态的…

剑指 Offer II 031. 最近最少使用缓存

comments: true edit_url: https://github.com/doocs/leetcode/edit/main/lcof2/%E5%89%91%E6%8C%87%20Offer%20II%20031.%20%E6%9C%80%E8%BF%91%E6%9C%80%E5%B0%91%E4%BD%BF%E7%94%A8%E7%BC%93%E5%AD%98/README.md 剑指 Offer II 031. 最近最少使用缓存 题目描述 运用所掌握的…

uniapp 测试 IPA 包安装到测试 iPhone

将uniapp测试IPA包安装到测试iPhone有以下几种方法&#xff1a; 使用Xcode安装 确保计算机上安装了Xcode&#xff0c;并将iOS设备通过数据线连接到计算机。打开Xcode&#xff0c;在菜单栏中选择Window->Devices and Simulators&#xff0c;在设备列表中找到要安装的iPhone…