From c2b70306b8ec637312f7985c88f04d87a76a5955 Mon Sep 17 00:00:00 2001 From: gongjh0916 <2748130352@qq.com> Date: Fri, 19 Dec 2025 18:38:24 +0800 Subject: [PATCH 01/13] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E9=80=89=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Mechanical_arm_grasping/README.md | 1 + src/Mechanical_arm_grasping/main.py | 0 2 files changed, 1 insertion(+) create mode 100644 src/Mechanical_arm_grasping/README.md create mode 100644 src/Mechanical_arm_grasping/main.py diff --git a/src/Mechanical_arm_grasping/README.md b/src/Mechanical_arm_grasping/README.md new file mode 100644 index 0000000000..e4f1315851 --- /dev/null +++ b/src/Mechanical_arm_grasping/README.md @@ -0,0 +1 @@ +机械臂抓取系统 \ No newline at end of file diff --git a/src/Mechanical_arm_grasping/main.py b/src/Mechanical_arm_grasping/main.py new file mode 100644 index 0000000000..e69de29bb2 From ef8b457878cba7016a6e7ae176e3cfa5d80ba1ed Mon Sep 17 00:00:00 2001 From: gongjh0916 <2748130352@qq.com> Date: Sun, 21 Dec 2025 21:46:25 +0800 Subject: [PATCH 02/13] =?UTF-8?q?=E6=B7=BB=E5=8A=A0.gitignore=EF=BC=8C?= =?UTF-8?q?=E5=BF=BD=E7=95=A5=E6=97=A0=E7=94=A8=E6=9C=AC=E5=9C=B0=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Mechanical_arm_grasping/.gitgnore | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/Mechanical_arm_grasping/.gitgnore diff --git a/src/Mechanical_arm_grasping/.gitgnore b/src/Mechanical_arm_grasping/.gitgnore new file mode 100644 index 0000000000..e69de29bb2 From 7563e69fcedded95a65b5ce195ded4ea59a4e172 Mon Sep 17 00:00:00 2001 From: gongjh0916 <2748130352@qq.com> Date: Sun, 21 Dec 2025 21:51:36 +0800 Subject: [PATCH 03/13] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Mechanical_arm_grasping/.gitgnore | 0 src/Mechanical_arm_grasping/main.py | 28 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) delete mode 100644 src/Mechanical_arm_grasping/.gitgnore diff --git a/src/Mechanical_arm_grasping/.gitgnore b/src/Mechanical_arm_grasping/.gitgnore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/Mechanical_arm_grasping/main.py b/src/Mechanical_arm_grasping/main.py index e69de29bb2..ccc567aa29 100644 --- a/src/Mechanical_arm_grasping/main.py +++ b/src/Mechanical_arm_grasping/main.py @@ -0,0 +1,28 @@ +# 代码1:超声波传感器目标位置检测(简单版) +import time + +def ultrasonic_sensor_simulation(): + """模拟超声波传感器,返回目标与机械臂的距离(单位:cm)""" + # 随机生成合理距离(模拟真实传感器检测结果) + import random + target_distance = random.uniform(5, 50) # 目标距离5-50cm + print(f"【超声波传感器检测】目标距离:{target_distance:.2f} cm") + return target_distance + +def judge_target_position(distance): + """根据距离判断目标位置状态""" + if distance < 15: + return "近距离(可抓取)" + elif 15 <= distance <= 30: + return "中距离(需伸展)" + else: + return "远距离(超出范围)" + +# 主程序 +if __name__ == "__main__": + print("=== 机械臂目标检测(简单版)===") + for i in range(3): # 检测3次 + distance = ultrasonic_sensor_simulation() + position_state = judge_target_position(distance) + print(f"第{i+1}次检测结果:{position_state}\n") + time.sleep(1) # 间隔1秒检测 \ No newline at end of file From 4795836eb517401f38e9b4ca2db25a4252966a31 Mon Sep 17 00:00:00 2001 From: gongjh0916 <2748130352@qq.com> Date: Mon, 22 Dec 2025 18:14:52 +0800 Subject: [PATCH 04/13] =?UTF-8?q?=E6=8E=A7=E5=88=B6=E6=9C=BA=E6=A2=B0?= =?UTF-8?q?=E8=87=82=E5=8D=87=E9=99=8D=E6=A8=A1=E6=8B=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Mechanical_arm_grasping/main.py | 117 +++++++++++++++++++++------- 1 file changed, 90 insertions(+), 27 deletions(-) diff --git a/src/Mechanical_arm_grasping/main.py b/src/Mechanical_arm_grasping/main.py index ccc567aa29..33afe57051 100644 --- a/src/Mechanical_arm_grasping/main.py +++ b/src/Mechanical_arm_grasping/main.py @@ -1,28 +1,91 @@ -# 代码1:超声波传感器目标位置检测(简单版) -import time - -def ultrasonic_sensor_simulation(): - """模拟超声波传感器,返回目标与机械臂的距离(单位:cm)""" - # 随机生成合理距离(模拟真实传感器检测结果) - import random - target_distance = random.uniform(5, 50) # 目标距离5-50cm - print(f"【超声波传感器检测】目标距离:{target_distance:.2f} cm") - return target_distance - -def judge_target_position(distance): - """根据距离判断目标位置状态""" - if distance < 15: - return "近距离(可抓取)" - elif 15 <= distance <= 30: - return "中距离(需伸展)" +import pygame +import sys +import math # 导入Python内置math库,用于角度弧度转换 + +# 初始化pygame +pygame.init() + +# 窗口配置 +WINDOW_WIDTH = 400 +WINDOW_HEIGHT = 500 +screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) +pygame.display.set_caption("机械臂关节升降模拟") + +# 颜色定义 +BLACK = (0, 0, 0) +WHITE = (255, 255, 255) +BLUE = (0, 128, 255) +RED = (255, 50, 50) + +# 机械臂参数 +BASE_X = WINDOW_WIDTH // 2 # 机械臂底座x坐标 +BASE_Y = WINDOW_HEIGHT - 50 # 机械臂底座y坐标 +JOINT_LENGTH = 200 # 关节臂长度(影响升降范围) +joint_angle = 90 # 关节初始角度(90度为垂直向下,0度为水平向左,180度为水平向右) +speed = 1 # 角度变化速度(控制升降快慢) + +def draw_arm(screen, base_x, base_y, angle, length): + """绘制机械臂模型""" + # 计算关节末端坐标(通过角度转换,使用math库的radians方法) + # 三角函数计算:y轴向下为正,需调整角度映射 + rad = math.radians(angle) # 修复:替换为math.radians + joint_end_x = base_x + length * math.cos(rad) # 修复:使用math.cos + joint_end_y = base_y - length * math.sin(rad) # 修复:使用math.sin,减号实现y轴向上为高度增加 + + # 绘制底座 + pygame.draw.circle(screen, BLUE, (base_x, base_y), 20) + # 绘制关节臂(底座到关节末端) + pygame.draw.line(screen, RED, (base_x, base_y), (joint_end_x, joint_end_y), 8) + # 绘制关节末端 + pygame.draw.circle(screen, BLACK, (int(joint_end_x), int(joint_end_y)), 10) + # 绘制高度提示文字 + height = int(BASE_Y - joint_end_y) # 计算当前高度(像素值,近似对应实际高度) + font = pygame.font.SysFont(None, 24) + text = font.render(f"关节高度:{height}px", True, BLACK) + screen.blit(text, (10, 10)) + +# 主循环 +clock = pygame.time.Clock() +is_running = True +move_direction = "up" # 初始运动方向:上升 + +while is_running: + # 控制帧率 + clock.tick(60) + # 填充背景色 + screen.fill(WHITE) + + # 事件处理(关闭窗口、按键控制) + for event in pygame.event.get(): + if event.type == pygame.QUIT: + is_running = False + # 按键控制:上箭头上升,下箭头下降 + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_UP: + move_direction = "up" + if event.key == pygame.K_DOWN: + move_direction = "down" + + # 更新关节角度(控制升降) + if move_direction == "up": + # 角度增大:关节上升(最大角度170度,避免超出窗口) + if joint_angle < 170: + joint_angle += speed + else: + move_direction = "down" # 到达上限后自动下降 else: - return "远距离(超出范围)" - -# 主程序 -if __name__ == "__main__": - print("=== 机械臂目标检测(简单版)===") - for i in range(3): # 检测3次 - distance = ultrasonic_sensor_simulation() - position_state = judge_target_position(distance) - print(f"第{i+1}次检测结果:{position_state}\n") - time.sleep(1) # 间隔1秒检测 \ No newline at end of file + # 角度减小:关节下降(最小角度10度,避免超出窗口) + if joint_angle > 10: + joint_angle -= speed + else: + move_direction = "up" # 到达下限后自动上升 + + # 绘制机械臂 + draw_arm(screen, BASE_X, BASE_Y, joint_angle, JOINT_LENGTH) + + # 更新窗口显示 + pygame.display.flip() + +# 退出程序 +pygame.quit() +sys.exit() \ No newline at end of file From 0d1343a14572ebab2ef4e01eb395fe526393a31d Mon Sep 17 00:00:00 2001 From: gongjh0916 <2748130352@qq.com> Date: Mon, 22 Dec 2025 20:48:23 +0800 Subject: [PATCH 05/13] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E6=9C=BA=E6=A2=B0?= =?UTF-8?q?=E8=87=82=E5=8D=87=E9=99=8D=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Mechanical_arm_grasping/main.py | 161 ++++++++++++++-------------- 1 file changed, 82 insertions(+), 79 deletions(-) diff --git a/src/Mechanical_arm_grasping/main.py b/src/Mechanical_arm_grasping/main.py index 33afe57051..683b104bbb 100644 --- a/src/Mechanical_arm_grasping/main.py +++ b/src/Mechanical_arm_grasping/main.py @@ -1,91 +1,94 @@ -import pygame -import sys -import math # 导入Python内置math库,用于角度弧度转换 +import pybullet as p +import pybullet_data +import time +import numpy as np -# 初始化pygame -pygame.init() -# 窗口配置 -WINDOW_WIDTH = 400 -WINDOW_HEIGHT = 500 -screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) -pygame.display.set_caption("机械臂关节升降模拟") +class ArmElevatorControllerPyBullet: + def __init__(self): + # 连接PyBullet模拟器(GUI模式,显示界面) + self.physics_client = p.connect(p.GUI) + # 设置模型搜索路径(关键:确保能找到内置模型) + p.setAdditionalSearchPath(pybullet_data.getDataPath()) + # 关闭重力(避免机械臂倾倒,专注升降控制;若需要真实物理效果可开启) + p.setGravity(0, 0, 0) -# 颜色定义 -BLACK = (0, 0, 0) -WHITE = (255, 255, 255) -BLUE = (0, 128, 255) -RED = (255, 50, 50) + # 加载地面和KUKA IIWA机械臂(内置模型,必存在,无需额外配置) + self.plane_id = p.loadURDF("plane.urdf") # 加载地面 + # 机械臂初始位姿:坐标(0,0,0),姿态(无旋转) + self.arm_id = p.loadURDF( + "kuka_iiwa/model.urdf", + basePosition=[0, 0, 0], + baseOrientation=p.getQuaternionFromEuler([0, 0, 0]) + ) -# 机械臂参数 -BASE_X = WINDOW_WIDTH // 2 # 机械臂底座x坐标 -BASE_Y = WINDOW_HEIGHT - 50 # 机械臂底座y坐标 -JOINT_LENGTH = 200 # 关节臂长度(影响升降范围) -joint_angle = 90 # 关节初始角度(90度为垂直向下,0度为水平向左,180度为水平向右) -speed = 1 # 角度变化速度(控制升降快慢) + # 定义升降关节:选择KUKA IIWA的第1个关节(索引0,可实现垂直方向升降/旋转,适配升降逻辑) + self.elevator_joint_index = 0 + # 获取关节信息(限位、当前位置) + joint_info = p.getJointInfo(self.arm_id, self.elevator_joint_index) + self.joint_min = joint_info[8] # 关节运动下限 + self.joint_max = joint_info[9] # 关节运动上限 + self.current_pos = p.getJointState(self.arm_id, self.elevator_joint_index)[0] # 当前位置 -def draw_arm(screen, base_x, base_y, angle, length): - """绘制机械臂模型""" - # 计算关节末端坐标(通过角度转换,使用math库的radians方法) - # 三角函数计算:y轴向下为正,需调整角度映射 - rad = math.radians(angle) # 修复:替换为math.radians - joint_end_x = base_x + length * math.cos(rad) # 修复:使用math.cos - joint_end_y = base_y - length * math.sin(rad) # 修复:使用math.sin,减号实现y轴向上为高度增加 + # 打印关节初始化信息 + print(f"升降关节初始化完成:") + print(f"关节索引:{self.elevator_joint_index}") + print(f"当前位置:{self.current_pos:.3f}") + print(f"运动范围:[{self.joint_min:.3f}, {self.joint_max:.3f}]") - # 绘制底座 - pygame.draw.circle(screen, BLUE, (base_x, base_y), 20) - # 绘制关节臂(底座到关节末端) - pygame.draw.line(screen, RED, (base_x, base_y), (joint_end_x, joint_end_y), 8) - # 绘制关节末端 - pygame.draw.circle(screen, BLACK, (int(joint_end_x), int(joint_end_y)), 10) - # 绘制高度提示文字 - height = int(BASE_Y - joint_end_y) # 计算当前高度(像素值,近似对应实际高度) - font = pygame.font.SysFont(None, 24) - text = font.render(f"关节高度:{height}px", True, BLACK) - screen.blit(text, (10, 10)) + def move_elevator(self, target_pos, speed=0.05): + """ + 驱动升降关节运动到目标位置 + :param target_pos: 目标位置(需在关节限位范围内) + :param speed: 运动速度(正数,越小越慢) + """ + # 校验目标位置合法性 + if target_pos < self.joint_min or target_pos > self.joint_max: + raise ValueError(f"目标位置超出关节范围!允许范围:[{self.joint_min:.3f}, {self.joint_max:.3f}]") -# 主循环 -clock = pygame.time.Clock() -is_running = True -move_direction = "up" # 初始运动方向:上升 + print(f"\n开始升降运动:当前位置 {self.current_pos:.3f} → 目标位置 {target_pos:.3f}") + # 循环控制,直到接近目标位置(误差小于0.001) + while abs(self.current_pos - target_pos) > 0.001: + # 计算运动步长(根据目标位置判断升降方向) + step = speed if target_pos > self.current_pos else -speed + # 更新当前位置(防止超出限位) + self.current_pos = np.clip(self.current_pos + step, self.joint_min, self.joint_max) + # 发送位置指令给关节(位置控制模式) + p.setJointMotorControl2( + bodyUniqueId=self.arm_id, + jointIndex=self.elevator_joint_index, + controlMode=p.POSITION_CONTROL, + targetPosition=self.current_pos + ) + # 步进物理仿真(更新场景状态) + p.stepSimulation() + # 小幅延时,模拟真实运动节奏 + time.sleep(0.01) + # 获取模拟器中关节的实际位置(反馈同步) + self.current_pos = p.getJointState(self.arm_id, self.elevator_joint_index)[0] + # 实时刷新显示当前位置 + print(f"实时位置:{self.current_pos:.3f}", end='\r') -while is_running: - # 控制帧率 - clock.tick(60) - # 填充背景色 - screen.fill(WHITE) + print(f"\n升降运动完成!最终位置:{self.current_pos:.3f}") - # 事件处理(关闭窗口、按键控制) - for event in pygame.event.get(): - if event.type == pygame.QUIT: - is_running = False - # 按键控制:上箭头上升,下箭头下降 - if event.type == pygame.KEYDOWN: - if event.key == pygame.K_UP: - move_direction = "up" - if event.key == pygame.K_DOWN: - move_direction = "down" + def disconnect(self): + """断开与PyBullet模拟器的连接""" + p.disconnect(self.physics_client) + print("\n已断开与PyBullet模拟器的连接") - # 更新关节角度(控制升降) - if move_direction == "up": - # 角度增大:关节上升(最大角度170度,避免超出窗口) - if joint_angle < 170: - joint_angle += speed - else: - move_direction = "down" # 到达上限后自动下降 - else: - # 角度减小:关节下降(最小角度10度,避免超出窗口) - if joint_angle > 10: - joint_angle -= speed - else: - move_direction = "up" # 到达下限后自动上升 - # 绘制机械臂 - draw_arm(screen, BASE_X, BASE_Y, joint_angle, JOINT_LENGTH) +# ------------------- 主执行程序 ------------------- +if __name__ == "__main__": + # 1. 初始化机械臂升降控制器 + arm_controller = ArmElevatorControllerPyBullet() - # 更新窗口显示 - pygame.display.flip() - -# 退出程序 -pygame.quit() -sys.exit() \ No newline at end of file + try: + # 2. 执行升降动作序列 + arm_controller.move_elevator(target_pos=arm_controller.joint_max * 0.6, speed=0.03) # 上升(接近上限) + time.sleep(1) # 停顿1秒 + arm_controller.move_elevator(target_pos=arm_controller.joint_min * 0.6, speed=0.02) # 下降(接近下限) + time.sleep(1) # 停顿1秒 + arm_controller.move_elevator(target_pos=0) # 回到初始中间位置 + finally: + # 3. 无论是否出错,最终断开连接 + arm_controller.disconnect() \ No newline at end of file From 723a4e7345709911c29fe42e91673e07bea97d24 Mon Sep 17 00:00:00 2001 From: gongjh0916 <2748130352@qq.com> Date: Tue, 23 Dec 2025 20:12:51 +0800 Subject: [PATCH 06/13] =?UTF-8?q?=E6=9C=BA=E6=A2=B0=E8=87=82=E5=85=B3?= =?UTF-8?q?=E8=8A=82=E4=BC=B8=E5=B1=95=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Mechanical_arm_grasping/main.py | 227 ++++++++++++++++++++-------- 1 file changed, 166 insertions(+), 61 deletions(-) diff --git a/src/Mechanical_arm_grasping/main.py b/src/Mechanical_arm_grasping/main.py index 683b104bbb..3c2f4a522b 100644 --- a/src/Mechanical_arm_grasping/main.py +++ b/src/Mechanical_arm_grasping/main.py @@ -2,93 +2,198 @@ import pybullet_data import time import numpy as np +from typing import Optional, Tuple +# ------------------- 配置常量(便于统一修改) ------------------- +SIMULATION_GRAVITY: Tuple[float, float, float] = (0, 0, 0) # 仿真重力,(0,0,-9.8)为真实重力 +ARM_MODEL_PATH: str = "kuka_iiwa/model.urdf" # 机械臂模型路径 +ARM_BASE_POSITION: Tuple[float, float, float] = (0, 0, 0) # 机械臂初始位置 +ARM_BASE_ORIENTATION: Tuple[float, float, float] = (0, 0, 0) # 机械臂初始姿态(欧拉角) +ELEVATOR_JOINT_INDEX: int = 0 # 升降关节索引 +MOVE_SPEED_DEFAULT: float = 0.03 # 默认升降速度 +POSITION_TOLERANCE: float = 0.001 # 位置误差容忍度(到达该误差即认为运动完成) +DELAY_STEP: float = 0.01 # 仿真步进延时 + + +class ArmElevatorController: + """机械臂升降关节控制器(面向对象封装,职责单一)""" -class ArmElevatorControllerPyBullet: def __init__(self): - # 连接PyBullet模拟器(GUI模式,显示界面) - self.physics_client = p.connect(p.GUI) - # 设置模型搜索路径(关键:确保能找到内置模型) - p.setAdditionalSearchPath(pybullet_data.getDataPath()) - # 关闭重力(避免机械臂倾倒,专注升降控制;若需要真实物理效果可开启) - p.setGravity(0, 0, 0) - - # 加载地面和KUKA IIWA机械臂(内置模型,必存在,无需额外配置) - self.plane_id = p.loadURDF("plane.urdf") # 加载地面 - # 机械臂初始位姿:坐标(0,0,0),姿态(无旋转) - self.arm_id = p.loadURDF( - "kuka_iiwa/model.urdf", - basePosition=[0, 0, 0], - baseOrientation=p.getQuaternionFromEuler([0, 0, 0]) - ) - - # 定义升降关节:选择KUKA IIWA的第1个关节(索引0,可实现垂直方向升降/旋转,适配升降逻辑) - self.elevator_joint_index = 0 - # 获取关节信息(限位、当前位置) + """初始化模拟器连接、机械臂模型和关节信息""" + self.physics_client: Optional[int] = None + self.arm_id: Optional[int] = None + self.plane_id: Optional[int] = None + + # 关节相关参数 + self.elevator_joint_index: int = ELEVATOR_JOINT_INDEX + self.joint_min: float = 0.0 + self.joint_max: float = 0.0 + self.current_pos: float = 0.0 + + # 初始化流程 + self._connect_simulator() + self._load_scene() + self._init_joint_info() + self._print_init_info() + + def _connect_simulator(self) -> None: + """私有方法:连接PyBullet模拟器(封装初始化细节)""" + try: + self.physics_client = p.connect(p.GUI) + p.setAdditionalSearchPath(pybullet_data.getDataPath()) + p.setGravity(*SIMULATION_GRAVITY) + print("✅ 成功连接PyBullet模拟器") + except Exception as e: + raise RuntimeError(f"❌ 连接模拟器失败:{str(e)}") + + def _load_scene(self) -> None: + """私有方法:加载地面和机械臂模型(封装场景加载逻辑)""" + try: + # 加载地面 + self.plane_id = p.loadURDF("plane.urdf") + # 加载机械臂 + base_orientation = p.getQuaternionFromEuler(ARM_BASE_ORIENTATION) + self.arm_id = p.loadURDF( + ARM_MODEL_PATH, + basePosition=ARM_BASE_POSITION, + baseOrientation=base_orientation + ) + print("✅ 成功加载场景(地面+机械臂)") + except Exception as e: + self.disconnect() # 加载失败时自动断开连接 + raise RuntimeError(f"❌ 加载场景失败:{str(e)}") + + def _init_joint_info(self) -> None: + """私有方法:初始化升降关节的限位和当前位置""" + if self.arm_id is None: + raise RuntimeError("❌ 机械臂未加载,无法初始化关节信息") + + # 获取关节基础信息 joint_info = p.getJointInfo(self.arm_id, self.elevator_joint_index) - self.joint_min = joint_info[8] # 关节运动下限 - self.joint_max = joint_info[9] # 关节运动上限 - self.current_pos = p.getJointState(self.arm_id, self.elevator_joint_index)[0] # 当前位置 + self.joint_min = joint_info[8] + self.joint_max = joint_info[9] + # 获取当前关节位置 + self.current_pos = p.getJointState(self.arm_id, self.elevator_joint_index)[0] - # 打印关节初始化信息 - print(f"升降关节初始化完成:") + def _print_init_info(self) -> None: + """打印初始化信息(格式化输出,更易读)""" + print("\n=" * 40) + print("📌 机械臂升降关节初始化信息") + print("=" * 40) print(f"关节索引:{self.elevator_joint_index}") print(f"当前位置:{self.current_pos:.3f}") print(f"运动范围:[{self.joint_min:.3f}, {self.joint_max:.3f}]") + print(f"默认速度:{MOVE_SPEED_DEFAULT}") + print(f"位置误差容忍度:{POSITION_TOLERANCE}") + print("=" * 40 + "\n") - def move_elevator(self, target_pos, speed=0.05): + def _check_target_pos_valid(self, target_pos: float) -> bool: + """私有方法:校验目标位置是否合法(返回布尔值,便于后续扩展)""" + if self.joint_min <= target_pos <= self.joint_max: + return True + print(f"❌ 目标位置 {target_pos:.3f} 超出关节范围:[{self.joint_min:.3f}, {self.joint_max:.3f}]") + return False + + def move_elevator(self, target_pos: float, speed: Optional[float] = None) -> None: """ - 驱动升降关节运动到目标位置 + 驱动升降关节运动到目标位置(公开方法,对外提供核心功能) :param target_pos: 目标位置(需在关节限位范围内) - :param speed: 运动速度(正数,越小越慢) + :param speed: 运动速度,默认使用MOVE_SPEED_DEFAULT + :return: None """ - # 校验目标位置合法性 - if target_pos < self.joint_min or target_pos > self.joint_max: - raise ValueError(f"目标位置超出关节范围!允许范围:[{self.joint_min:.3f}, {self.joint_max:.3f}]") - - print(f"\n开始升降运动:当前位置 {self.current_pos:.3f} → 目标位置 {target_pos:.3f}") - # 循环控制,直到接近目标位置(误差小于0.001) - while abs(self.current_pos - target_pos) > 0.001: - # 计算运动步长(根据目标位置判断升降方向) - step = speed if target_pos > self.current_pos else -speed + # 处理默认速度 + move_speed = speed if speed is not None else MOVE_SPEED_DEFAULT + # 校验目标位置 + if not self._check_target_pos_valid(target_pos): + return + + # 打印运动开始信息 + print(f"\n🚀 开始升降运动:当前位置 {self.current_pos:.3f} → 目标位置 {target_pos:.3f}(速度:{move_speed})") + + # 闭环控制关节运动 + while abs(self.current_pos - target_pos) > POSITION_TOLERANCE: + # 计算运动步长(方向+大小) + step = move_speed if target_pos > self.current_pos else -move_speed # 更新当前位置(防止超出限位) self.current_pos = np.clip(self.current_pos + step, self.joint_min, self.joint_max) - # 发送位置指令给关节(位置控制模式) + # 发送位置控制指令 p.setJointMotorControl2( bodyUniqueId=self.arm_id, jointIndex=self.elevator_joint_index, controlMode=p.POSITION_CONTROL, targetPosition=self.current_pos ) - # 步进物理仿真(更新场景状态) + # 步进仿真 p.stepSimulation() - # 小幅延时,模拟真实运动节奏 - time.sleep(0.01) - # 获取模拟器中关节的实际位置(反馈同步) + time.sleep(DELAY_STEP) + # 同步模拟器中的实际关节位置 self.current_pos = p.getJointState(self.arm_id, self.elevator_joint_index)[0] - # 实时刷新显示当前位置 - print(f"实时位置:{self.current_pos:.3f}", end='\r') + # 实时刷新显示(清除当前行,更整洁) + print(f"🔍 实时位置:{self.current_pos:.3f}", end='\r') - print(f"\n升降运动完成!最终位置:{self.current_pos:.3f}") + # 运动完成提示 + print(f"\n✅ 升降运动完成!最终位置:{self.current_pos:.3f}") - def disconnect(self): - """断开与PyBullet模拟器的连接""" - p.disconnect(self.physics_client) - print("\n已断开与PyBullet模拟器的连接") + def move_elevator_relative(self, delta_pos: float, speed: Optional[float] = None) -> None: + """ + 相对运动:基于当前位置升降指定距离(新增功能,提升易用性) + :param delta_pos: 相对位移(正数=上升,负数=下降) + :param speed: 运动速度 + :return: None + """ + target_pos = self.current_pos + delta_pos + self.move_elevator(target_pos, speed) + def disconnect(self) -> None: + """断开模拟器连接(容错处理,避免重复断开)""" + if self.physics_client is not None: + p.disconnect(self.physics_client) + self.physics_client = None + print("\n🔌 已断开与PyBullet模拟器的连接") -# ------------------- 主执行程序 ------------------- -if __name__ == "__main__": - # 1. 初始化机械臂升降控制器 - arm_controller = ArmElevatorControllerPyBullet() +# ------------------- 主执行逻辑(解耦,便于测试) ------------------- +def main(): + """主函数:执行升降动作序列""" + arm_controller = None try: - # 2. 执行升降动作序列 - arm_controller.move_elevator(target_pos=arm_controller.joint_max * 0.6, speed=0.03) # 上升(接近上限) - time.sleep(1) # 停顿1秒 - arm_controller.move_elevator(target_pos=arm_controller.joint_min * 0.6, speed=0.02) # 下降(接近下限) - time.sleep(1) # 停顿1秒 - arm_controller.move_elevator(target_pos=0) # 回到初始中间位置 + # 初始化控制器 + arm_controller = ArmElevatorController() + + # 执行升降动作序列 + print("\n" + "-" * 50) + print("📝 执行升降动作序列1:上升到上限60%") + print("-" * 50) + arm_controller.move_elevator(target_pos=arm_controller.joint_max * 0.6) + time.sleep(1) + + print("\n" + "-" * 50) + print("📝 执行升降动作序列2:下降到下限60%") + print("-" * 50) + arm_controller.move_elevator(target_pos=arm_controller.joint_min * 0.6, speed=0.02) + time.sleep(1) + + print("\n" + "-" * 50) + print("📝 执行升降动作序列3:相对上升0.5") + print("-" * 50) + arm_controller.move_elevator_relative(delta_pos=0.5, speed=0.04) + time.sleep(1) + + print("\n" + "-" * 50) + print("📝 执行升降动作序列4:回到初始位置0") + print("-" * 50) + arm_controller.move_elevator(target_pos=0) + + except Exception as e: + print(f"\n❌ 程序执行出错:{str(e)}") finally: - # 3. 无论是否出错,最终断开连接 - arm_controller.disconnect() \ No newline at end of file + # 确保无论是否出错,都断开连接 + if arm_controller is not None: + arm_controller.disconnect() + + +if __name__ == "__main__": + # 启动程序 + print("🚀 启动机械臂升降控制系统...") + main() + print("\n🎉 程序正常结束") \ No newline at end of file From e5d7fd72cb29b01fe0f7c14de10d973de3b0ca2b Mon Sep 17 00:00:00 2001 From: gongjh0916 <2748130352@qq.com> Date: Wed, 24 Dec 2025 22:39:28 +0800 Subject: [PATCH 07/13] =?UTF-8?q?mujoco=E7=9A=84=E6=9C=BA=E6=A2=B0?= =?UTF-8?q?=E8=87=82=E8=BF=90=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../arm_gripper_v3.xml | 0 src/Mechanical_arm_grasping/main.py | 299 +++++++----------- src/Mechanical_arm_grasping/robot_arm.xml | 0 .../robot_arm_control.py | 0 4 files changed, 108 insertions(+), 191 deletions(-) create mode 100644 src/Mechanical_arm_grasping/arm_gripper_v3.xml create mode 100644 src/Mechanical_arm_grasping/robot_arm.xml create mode 100644 src/Mechanical_arm_grasping/robot_arm_control.py diff --git a/src/Mechanical_arm_grasping/arm_gripper_v3.xml b/src/Mechanical_arm_grasping/arm_gripper_v3.xml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mechanical_arm_grasping/main.py b/src/Mechanical_arm_grasping/main.py index 3c2f4a522b..1e1efa33d6 100644 --- a/src/Mechanical_arm_grasping/main.py +++ b/src/Mechanical_arm_grasping/main.py @@ -1,199 +1,116 @@ -import pybullet as p -import pybullet_data +# 机械臂MuJoCo 3.4.0 原生语法稳定版(100%兼容,无API报错) +import mujoco +import mujoco.viewer import time -import numpy as np -from typing import Optional, Tuple -# ------------------- 配置常量(便于统一修改) ------------------- -SIMULATION_GRAVITY: Tuple[float, float, float] = (0, 0, 0) # 仿真重力,(0,0,-9.8)为真实重力 -ARM_MODEL_PATH: str = "kuka_iiwa/model.urdf" # 机械臂模型路径 -ARM_BASE_POSITION: Tuple[float, float, float] = (0, 0, 0) # 机械臂初始位置 -ARM_BASE_ORIENTATION: Tuple[float, float, float] = (0, 0, 0) # 机械臂初始姿态(欧拉角) -ELEVATOR_JOINT_INDEX: int = 0 # 升降关节索引 -MOVE_SPEED_DEFAULT: float = 0.03 # 默认升降速度 -POSITION_TOLERANCE: float = 0.001 # 位置误差容忍度(到达该误差即认为运动完成) -DELAY_STEP: float = 0.01 # 仿真步进延时 - -class ArmElevatorController: - """机械臂升降关节控制器(面向对象封装,职责单一)""" - - def __init__(self): - """初始化模拟器连接、机械臂模型和关节信息""" - self.physics_client: Optional[int] = None - self.arm_id: Optional[int] = None - self.plane_id: Optional[int] = None - - # 关节相关参数 - self.elevator_joint_index: int = ELEVATOR_JOINT_INDEX - self.joint_min: float = 0.0 - self.joint_max: float = 0.0 - self.current_pos: float = 0.0 - - # 初始化流程 - self._connect_simulator() - self._load_scene() - self._init_joint_info() - self._print_init_info() - - def _connect_simulator(self) -> None: - """私有方法:连接PyBullet模拟器(封装初始化细节)""" - try: - self.physics_client = p.connect(p.GUI) - p.setAdditionalSearchPath(pybullet_data.getDataPath()) - p.setGravity(*SIMULATION_GRAVITY) - print("✅ 成功连接PyBullet模拟器") - except Exception as e: - raise RuntimeError(f"❌ 连接模拟器失败:{str(e)}") - - def _load_scene(self) -> None: - """私有方法:加载地面和机械臂模型(封装场景加载逻辑)""" - try: - # 加载地面 - self.plane_id = p.loadURDF("plane.urdf") - # 加载机械臂 - base_orientation = p.getQuaternionFromEuler(ARM_BASE_ORIENTATION) - self.arm_id = p.loadURDF( - ARM_MODEL_PATH, - basePosition=ARM_BASE_POSITION, - baseOrientation=base_orientation - ) - print("✅ 成功加载场景(地面+机械臂)") - except Exception as e: - self.disconnect() # 加载失败时自动断开连接 - raise RuntimeError(f"❌ 加载场景失败:{str(e)}") - - def _init_joint_info(self) -> None: - """私有方法:初始化升降关节的限位和当前位置""" - if self.arm_id is None: - raise RuntimeError("❌ 机械臂未加载,无法初始化关节信息") - - # 获取关节基础信息 - joint_info = p.getJointInfo(self.arm_id, self.elevator_joint_index) - self.joint_min = joint_info[8] - self.joint_max = joint_info[9] - # 获取当前关节位置 - self.current_pos = p.getJointState(self.arm_id, self.elevator_joint_index)[0] - - def _print_init_info(self) -> None: - """打印初始化信息(格式化输出,更易读)""" - print("\n=" * 40) - print("📌 机械臂升降关节初始化信息") - print("=" * 40) - print(f"关节索引:{self.elevator_joint_index}") - print(f"当前位置:{self.current_pos:.3f}") - print(f"运动范围:[{self.joint_min:.3f}, {self.joint_max:.3f}]") - print(f"默认速度:{MOVE_SPEED_DEFAULT}") - print(f"位置误差容忍度:{POSITION_TOLERANCE}") - print("=" * 40 + "\n") - - def _check_target_pos_valid(self, target_pos: float) -> bool: - """私有方法:校验目标位置是否合法(返回布尔值,便于后续扩展)""" - if self.joint_min <= target_pos <= self.joint_max: - return True - print(f"❌ 目标位置 {target_pos:.3f} 超出关节范围:[{self.joint_min:.3f}, {self.joint_max:.3f}]") - return False - - def move_elevator(self, target_pos: float, speed: Optional[float] = None) -> None: - """ - 驱动升降关节运动到目标位置(公开方法,对外提供核心功能) - :param target_pos: 目标位置(需在关节限位范围内) - :param speed: 运动速度,默认使用MOVE_SPEED_DEFAULT - :return: None - """ - # 处理默认速度 - move_speed = speed if speed is not None else MOVE_SPEED_DEFAULT - # 校验目标位置 - if not self._check_target_pos_valid(target_pos): - return - - # 打印运动开始信息 - print(f"\n🚀 开始升降运动:当前位置 {self.current_pos:.3f} → 目标位置 {target_pos:.3f}(速度:{move_speed})") - - # 闭环控制关节运动 - while abs(self.current_pos - target_pos) > POSITION_TOLERANCE: - # 计算运动步长(方向+大小) - step = move_speed if target_pos > self.current_pos else -move_speed - # 更新当前位置(防止超出限位) - self.current_pos = np.clip(self.current_pos + step, self.joint_min, self.joint_max) - # 发送位置控制指令 - p.setJointMotorControl2( - bodyUniqueId=self.arm_id, - jointIndex=self.elevator_joint_index, - controlMode=p.POSITION_CONTROL, - targetPosition=self.current_pos - ) - # 步进仿真 - p.stepSimulation() - time.sleep(DELAY_STEP) - # 同步模拟器中的实际关节位置 - self.current_pos = p.getJointState(self.arm_id, self.elevator_joint_index)[0] - # 实时刷新显示(清除当前行,更整洁) - print(f"🔍 实时位置:{self.current_pos:.3f}", end='\r') - - # 运动完成提示 - print(f"\n✅ 升降运动完成!最终位置:{self.current_pos:.3f}") - - def move_elevator_relative(self, delta_pos: float, speed: Optional[float] = None) -> None: - """ - 相对运动:基于当前位置升降指定距离(新增功能,提升易用性) - :param delta_pos: 相对位移(正数=上升,负数=下降) - :param speed: 运动速度 - :return: None - """ - target_pos = self.current_pos + delta_pos - self.move_elevator(target_pos, speed) - - def disconnect(self) -> None: - """断开模拟器连接(容错处理,避免重复断开)""" - if self.physics_client is not None: - p.disconnect(self.physics_client) - self.physics_client = None - print("\n🔌 已断开与PyBullet模拟器的连接") - - -# ------------------- 主执行逻辑(解耦,便于测试) ------------------- -def main(): - """主函数:执行升降动作序列""" - arm_controller = None +def robot_arm_final_stable_demo(): + # 1. 内置机械臂XML模型(slide/hinge关节,3.4.0原生兼容) + robot_xml = """ + + + + """ + + # 2. 加载模型 try: - # 初始化控制器 - arm_controller = ArmElevatorController() - - # 执行升降动作序列 - print("\n" + "-" * 50) - print("📝 执行升降动作序列1:上升到上限60%") - print("-" * 50) - arm_controller.move_elevator(target_pos=arm_controller.joint_max * 0.6) - time.sleep(1) - - print("\n" + "-" * 50) - print("📝 执行升降动作序列2:下降到下限60%") - print("-" * 50) - arm_controller.move_elevator(target_pos=arm_controller.joint_min * 0.6, speed=0.02) - time.sleep(1) - - print("\n" + "-" * 50) - print("📝 执行升降动作序列3:相对上升0.5") - print("-" * 50) - arm_controller.move_elevator_relative(delta_pos=0.5, speed=0.04) - time.sleep(1) - - print("\n" + "-" * 50) - print("📝 执行升降动作序列4:回到初始位置0") - print("-" * 50) - arm_controller.move_elevator(target_pos=0) - + model = mujoco.MjModel.from_xml_string(robot_xml) + data = mujoco.MjData(model) + print("✅ 机械臂模型加载成功,启动仿真...") except Exception as e: - print(f"\n❌ 程序执行出错:{str(e)}") - finally: - # 确保无论是否出错,都断开连接 - if arm_controller is not None: - arm_controller.disconnect() + print(f"❌ 模型加载失败:{e}") + return + + # 3. 获取执行器索引(对应data.ctrl数组的下标,3.4.0原生支持) + lift_act_idx = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_ACTUATOR, "lift_actuator") + extend_act_idx = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_ACTUATOR, "extend_actuator") + left_gripper_act_idx = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_ACTUATOR, "left_gripper_actuator") + right_gripper_act_idx = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_ACTUATOR, "right_gripper_actuator") + + # 4. 动作控制逻辑(直接操作data.ctrl,3.4.0原生语法) + def control_lift(target): + data.ctrl[lift_act_idx] = target # 直接给执行器对应下标赋值 + + def control_extend(target): + data.ctrl[extend_act_idx] = target + + def control_gripper(target_left): + target_right = -target_left + data.ctrl[left_gripper_act_idx] = target_left + data.ctrl[right_gripper_act_idx] = target_right + + # 5. 预设动作流程 + action_list = [ + ("上升", "lift", 0.8, 2.0), + ("伸展", "extend", 0.6, 2.0), + ("夹紧", "gripper", -0.4, 1.0), + ("保持", "none", None, 1.5), + ("放松", "gripper", 0, 1.0), + ("收缩", "extend", 0, 2.0), + ("下降", "lift", 0, 2.0), + ] + + # 6. 启动可视化并执行动作 + with mujoco.viewer.launch_passive(model, data) as viewer: + for action_name, action_type, target, dur in action_list: + print(f"🔧 正在执行:{action_name}") + start_time = time.time() + while (time.time() - start_time) < dur and viewer.is_running(): + # 执行对应动作 + if action_type == "lift": + control_lift(target) + elif action_type == "extend": + control_extend(target) + elif action_type == "gripper": + control_gripper(target) + + # 步进仿真+同步可视化 + mujoco.mj_step(model, data) + viewer.sync() + time.sleep(0.001) + + print("🎉 机械臂动作执行完毕!") if __name__ == "__main__": - # 启动程序 - print("🚀 启动机械臂升降控制系统...") - main() - print("\n🎉 程序正常结束") \ No newline at end of file + robot_arm_final_stable_demo() \ No newline at end of file diff --git a/src/Mechanical_arm_grasping/robot_arm.xml b/src/Mechanical_arm_grasping/robot_arm.xml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mechanical_arm_grasping/robot_arm_control.py b/src/Mechanical_arm_grasping/robot_arm_control.py new file mode 100644 index 0000000000..e69de29bb2 From 615d15f12de5cc50c32df3e30b3a995da959eeb7 Mon Sep 17 00:00:00 2001 From: gongjh0916 <2748130352@qq.com> Date: Thu, 25 Dec 2025 20:14:32 +0800 Subject: [PATCH 08/13] =?UTF-8?q?=E6=97=8B=E8=BD=AC=E5=85=B3=E8=8A=82?= =?UTF-8?q?=E5=A4=9A=E8=87=AA=E7=94=B1=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Mechanical_arm_grasping/main.py | 165 +++++++++++++++++----------- 1 file changed, 100 insertions(+), 65 deletions(-) diff --git a/src/Mechanical_arm_grasping/main.py b/src/Mechanical_arm_grasping/main.py index 0f2329ea3b..4a95ab7e0d 100644 --- a/src/Mechanical_arm_grasping/main.py +++ b/src/Mechanical_arm_grasping/main.py @@ -1,13 +1,13 @@ -# 机械臂MuJoCo 3.4.0 原生语法稳定版(100%兼容,无冲突标记) +# MuJoCo 3.4.0 多自由度旋转机械臂演示(全新版本) import mujoco import mujoco.viewer import time -def robot_arm_final_stable_demo(): - # 1. 内置机械臂XML模型(slide/hinge关节,3.4.0原生兼容) - robot_xml = """ - +def multi_dof_robot_arm_demo(): + # 1. 内置多自由度机械臂XML模型(含旋转+升降+伸展+夹爪) + multi_dof_xml = """ +