利用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 = robot
        self.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.ax
        self.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_end
            T = 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_list
        rotation_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 = axis
        self.current_direction = direction
        self.timer.start(100)  # 每 100 毫秒触发一次

    def stop_motion(self):
        """停止运动"""
        self.timer.stop()
        self.current_axis = None
        self.current_direction = None
        self.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.0
        elif direction == '负向':
            self.show_robot.q_list[axis] -= 1.0

        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_end
            T = 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_list
        rotation_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 = 388
	L2 = 50
	L3 = 330
	L4 = 50
	L5 = 332
	L6 = 96
	alpha_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轴就会停止运动,其他关节轴也是一样的。
然后在界面的下方中还是实时的显示出机器人当前的关节角度和笛卡尔末端位姿。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

制造业中的“大数据”:如何实现精准决策?

在当今全球经济竞争日趋激烈、技术变革周期不断缩短的环境下,制造业面临着全新的挑战和机遇。随着信息技术的飞速发展,“大数据”正以前所未有的速度渗透到制造业的各个环节,帮助企业实现更精准的决策、更灵活的生产组织以及更敏捷的市场响应…

【沙漠之心:揭秘尘封奇迹的终极之旅】

在地球的边缘,横亘着一片浩瀚无垠的沙漠,它既是生命的绝域,亦是奇迹孕育的秘境。这片广袤的沙漠,以其神秘莫测的面貌,自古以来便吸引着无数探险家、旅行者和梦想家的目光。它既是生命的禁区,让无数生命在这片不毛之地中消逝;同时,它也是奇迹的摇篮,孕育着无数未被发现…

线程控制(创建、终止、等待、分离)

目录 1.前言 2.创建线程 pthread_create函数 3.线程终止 pthread_exit函数 pthread_cancel函数 4.线程等待 5.线程分离 1.前言 在Linux系统中,并不存在真正的线程,只有轻量级进程。所以,Linux系统只提供了操作轻量级进程的系统调用…

有关Java中的集合(1):List<T>和Set<T>

学习目标 核心掌握List集合了解Set集合 1.List<T> ● java.util.List。有序列表。 ● List集合元素的特点&#xff1a;有序表示存取有序&#xff08;因为有索引&#xff09;而且可以重复 ● List常用实现类&#xff1a; ArrayList、LinkedList、Vector等 1.1 常用方法…

第J1周:ResNet50算法(Tensorflow版)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 目标 具体实现 &#xff08;一&#xff09;环境 语言环境&#xff1a;Python 3.10 编 译 器: PyCharm 框 架: TensorFlow &#xff08;二&#xff09;具体…

第三百七十一节 JavaFX教程 - JavaFX组合框

JavaFX教程 - JavaFX组合框 组合框允许用户选择几个选项之一。用户可以滚动到下拉列表。组合框可以是可编辑和不可编辑的。 创建组合框 以下代码将选项列表包装到ObservableList中&#xff0c;然后使用observable列表实例化ComboBox类。 ObservableList<String> optio…

《HelloGitHub》第 107 期

兴趣是最好的老师&#xff0c;HelloGitHub 让你对编程感兴趣&#xff01; 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等&#xff0c;涵盖多种编程语言 Python、…

和鲸科技推出人工智能通识课程解决方案,助力AI人才培养

2025年2月&#xff0c;教育部副部长吴岩应港澳特区政府邀请&#xff0c;率团赴港澳宣讲《教育强国建设规划纲要 (2024—2035 年)》。在港澳期间&#xff0c;吴岩阐释了教育强国目标的任务&#xff0c;并与特区政府官员交流推进人工智能人才培养的办法。这一系列行动体现出人工智…

2025 最新版鸿蒙 HarmonyOS 开发工具安装使用指南

为保证 DevEco Studio 正常运行&#xff0c;建议电脑配置满足如下要求&#xff1a; Windows 系统 操作系统&#xff1a;Windows10 64 位、Windows11 64 位内存&#xff1a;16GB 及以上硬盘&#xff1a;100GB 及以上分辨率&#xff1a;1280*800 像素及以上 macOS 系统 操作系统…

not support ClassForName

com.alibaba.fastjson2.JSONException: not support ClassForName : java.lang.String, you can config JSONReader.Feature.SupportClassForName 官方说明中提到默认关闭&#xff0c; 可通过配置开启 JSON.config(JSONReader.Feature.SupportClassForName);

面试常问的压力测试问题

性能测试作为软件开发中的关键环节&#xff0c;确保系统在高负载下仍能高效运行。压力测试作为性能测试的重要类型&#xff0c;旨在通过施加超出正常负载的压力&#xff0c;观察系统在极端条件下的表现。面试中&#xff0c;相关问题常被问及&#xff0c;包括定义、重要性、与负…

《白帽子讲 Web 安全》之移动 Web 安全

目录 摘要 一、WebView 简介 二、WebView 对外暴露 WebView 对外暴露的接口风险 三、通用型 XSS - Universal XSS 介绍 四、WebView 跨域访问 五、与本地代码交互 js 5.1接口暴露风险&#xff1a; 5.2漏洞利用&#xff1a; 5.3JavaScript 与 Native 代码通信 六、Chr…

MySQL-基础篇学习总结(2025-03-02)

几个月前学习了MySQL,后来忙着准备毕业论文的事情&#xff0c;好几个月没有回顾&#xff0c;最近又开始看这块内容准备春招了&#xff0c;所以决定把学习过的东西做一下总结。 1. MySQL概述 这部分内容介绍数据库相关概念及MySQL数据库的介绍、下载、安装、启动及连接。具体的…

AUTOSAR简介

目录 核心目标 架构分层 核心优势 经典AUTOSAR vs 自适应AUTOSAR 典型应用场景 挑战与未来发展 相关企业介绍 1. 传统汽车电子供应商&#xff08;Tier1&#xff09; 2. 软件服务商与工具链企业 3. 新兴科技公司与自动驾驶企业 4. 基础软件与工具链企业 5. 高校与研…

vulnhub靶场之【digitalworld.local系列】的bravery靶机

前言 靶机&#xff1a;digitalworld.local-bravery&#xff0c;IP地址为192.168.10.8 攻击&#xff1a;kali&#xff0c;IP地址为192.168.10.6 kali采用VMware虚拟机&#xff0c;靶机采用virtualbox虚拟机&#xff0c;网卡都为桥接模式 这里官方给的有两种方式&#xff0c;…

探索AIGC的核心原理与应用前景

随着人工智能的迅猛发展&#xff0c;AIGC&#xff08;Artificial Intelligence Generated Content&#xff09;作为一个新兴领域&#xff0c;逐渐引起了广泛关注。它不仅重新定义了创作的方式&#xff0c;还为各行各业带来了诸多变革。本文将深入探讨AIGC的基本原理、技术框架以…

解码中国AI双雄突围:DeepSeek破壁与英伟达反攻背后的算力暗战

一、算力困局下的中国突围术 2024年夏季的科技界暗流涌动&#xff1a;北京中关村的服务器机房里&#xff0c;寒武纪最新MLU300X芯片正以每秒120万亿次运算支撑着自动驾驶系统的实时决策&#xff1b;上海张江的AI实验室中&#xff0c;DeepSeek团队通过神经元分块技术将模型参数压…

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

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

【Linux】进程退出 | 初始缓冲区 | 子进程回收(六)

目录 前言&#xff1a; 一、main函数的返回值 二、退出码有什么用&#xff1f; 三、perror/strerror/erron 四、erron变量 五、exit函数 六、_exit变量 七、初始缓冲区 八、wait函数和folk函数的返回值 九、父进程获取子进程退出信息waitpid函数 1.返回值 2.第一个…

【vscode-解决方案】vscode 无法登录远程服务器的两种解决办法

解决方案一&#xff1a; 查找原因 命令 ps ajx | grep vscode 可能会看到一下这堆信息&#xff08;如果没有大概率不是这个原因导致&#xff09; 这堆信息的含义&#xff1a;当你使用 vscode 远程登录服务器时&#xff0c;我们远程机器服务端要给你启动一个叫做 vscode serv…