diff --git a/src/Robotic_arm/main.py b/src/Robotic_arm/main.py index 238d01c137..d6c12a5dac 100644 --- a/src/Robotic_arm/main.py +++ b/src/Robotic_arm/main.py @@ -1,489 +1,163 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -机械臂仿真完整单文件版本(最终完美修复版) -核心修复: -1. 统一关节数为5个(匹配XML模型) -2. 所有数组维度改为5维 -3. 移除所有硬编码的6关节逻辑 -4. 确保所有运算维度匹配 +机械臂关节控制器 - 最终兼容版 +核心优化: +1. 修复Mujoco mj_name2id API类型错误(兼容所有版本) +2. 移除所有不兼容属性和依赖 +3. 纯原生实现,无Numba/特殊依赖 +4. Windows深度适配+优雅退出 """ import sys import os import time -import logging -import argparse -from dataclasses import dataclass, field -from typing import List, Tuple, Optional, Dict, Deque -from collections import deque - -# ====================== 核心配置:统一关节数 ====================== -JOINT_COUNT = 5 # 关键:改为5个关节(匹配XML模型) - -# ====================== mujoco 版本兼容处理 ====================== -try: - import numpy as np - import mujoco - +import signal +import ctypes +import threading +import numpy as np +import mujoco + +# ====================== 全局配置 ====================== +# 系统适配(Windows优先) +if os.name == 'nt': try: - from mujoco import viewer - - MUJOCO_VIEWER_MODE = "new" - except ImportError: - if hasattr(mujoco, 'viewer'): - viewer = mujoco.viewer - MUJOCO_VIEWER_MODE = "old" - else: - raise ImportError("请安装最新版mujoco:pip install mujoco>=2.3.0") - - from scipy import interpolate - from scipy.signal import filtfilt, butter - import cvxpy as cp -except ImportError as e: - print(f"❌ 缺少依赖库:{e.name}") - print("🔧 请运行:pip install mujoco>=2.3.0 numpy scipy cvxpy ecos osqp") - sys.exit(1) - - -# ====================== 1. 配置管理模块(改为5关节) ====================== -@dataclass -class PhysicsConfig: - # 改为5个关节的限制参数 - max_vel: List[float] = field(default_factory=lambda: [1.0, 0.8, 0.8, 1.2, 0.9]) - max_acc: List[float] = field(default_factory=lambda: [0.5, 0.4, 0.4, 0.6, 0.5]) - max_jerk: List[float] = field(default_factory=lambda: [0.3, 0.2, 0.2, 0.4, 0.3]) - max_torque: List[float] = field(default_factory=lambda: [15.0, 15.0, 10.0, 5.0, 5.0]) - ctrl_limit: Tuple[float, float] = (-10.0, 10.0) - - -@dataclass -class ObstacleConfig: - base_k_att: float = 0.8 - base_k_rep: float = 0.6 - rep_radius: float = 0.3 - stagnant_threshold: float = 0.01 - stagnant_time: float = 1.0 - guide_offset: float = 0.1 - obstacle_list: List[List[float]] = field( - default_factory=lambda: [[0.6, 0.1, 0.5, 0.1], [0.55, 0.05, 0.55, 0.08], [0.4, -0.1, 0.6, 0.08]]) - safety_margin: float = 0.05 - - -@dataclass -class EfficiencyConfig: - time_weight: float = 0.6 - energy_weight: float = 0.4 - traj_interp_points: int = 20 - opt_horizon: float = 1.0 - smooth_factor: float = 0.2 - motor_efficiency: float = 0.85 - # 改为5个关节的摩擦系数 - joint_friction: List[float] = field(default_factory=lambda: [0.001, 0.002, 0.0015, 0.001, 0.0008]) - - -@dataclass -class TrajectoryConfig: - cart_waypoints: List[List[float]] = field( - default_factory=lambda: [[0.5, 0.0, 0.6], [0.6, 0.0, 0.58], [0.8, 0.1, 0.8], [0.6, 0.0, 0.58], [0.5, 0.0, 0.6]]) - - -@dataclass -class SimulationConfig: - timestep: float = 0.005 - fps: int = 60 - log_level: str = "INFO" - enable_interaction: bool = False - - -@dataclass -class RobotConfig: - physics: PhysicsConfig = field(default_factory=PhysicsConfig) - obstacle: ObstacleConfig = field(default_factory=ObstacleConfig) - efficiency: EfficiencyConfig = field(default_factory=EfficiencyConfig) - trajectory: TrajectoryConfig = field(default_factory=TrajectoryConfig) - simulation: SimulationConfig = field(default_factory=SimulationConfig) - - def validate(self): - """校验并自动修复配置参数""" - logger = logging.getLogger(__name__) - if self.simulation.fps < 1 or self.simulation.fps > 120: - logger.warning(f"⚠️ FPS {self.simulation.fps} 超出范围,自动调整为30") - self.simulation.fps = 30 - if self.efficiency.traj_interp_points < 5 or self.efficiency.traj_interp_points > 100: - logger.warning(f"⚠️ 插值点数 {self.efficiency.traj_interp_points} 超出范围,自动调整为20") - self.efficiency.traj_interp_points = 20 - weight_sum = self.efficiency.time_weight + self.efficiency.energy_weight - if not abs(weight_sum - 1.0) < 1e-6: - logger.warning(f"⚠️ 时间+能耗权重和为 {weight_sum}(应为1),自动归一化") - self.efficiency.time_weight /= weight_sum - self.efficiency.energy_weight /= weight_sum - - -# 全局配置实例 -_global_config: Optional[RobotConfig] = None - - -def get_config() -> RobotConfig: - """获取全局配置(单例+参数校验)""" - global _global_config - if _global_config is None: - _global_config = RobotConfig() - - # 应用命令行参数 - parser = argparse.ArgumentParser(description="机械臂仿真配置", add_help=False) - parser.add_argument("--fps", type=int, help="仿真帧率(1-120)") - parser.add_argument("--traj-points", type=int, dest="traj_interp_points", help="轨迹插值点数(5-100)") - parser.add_argument("--smooth-factor", type=float, help="轨迹平滑系数(0.01-1.0)") - parser.add_argument("--time-weight", type=float, help="时间权重(0-1)") - parser.add_argument("--energy-weight", type=float, help="能耗权重(0-1)") - parser.add_argument("--log-level", choices=["DEBUG", "INFO", "WARNING", "ERROR"], help="日志级别") - parser.add_argument("-h", "--help", action="store_true", help="显示帮助信息") - - args, _ = parser.parse_known_args() - - # 应用参数到配置 - if args.fps: - _global_config.simulation.fps = args.fps - if args.traj_interp_points: - _global_config.efficiency.traj_interp_points = args.traj_interp_points - if args.smooth_factor: - _global_config.efficiency.smooth_factor = args.smooth_factor - if args.time_weight: - _global_config.efficiency.time_weight = args.time_weight - if args.energy_weight: - _global_config.efficiency.energy_weight = args.energy_weight - if args.log_level: - _global_config.simulation.log_level = args.log_level - - # 校验配置 - _global_config.validate() - - # 显示帮助信息 - if args.help: - print(""" -🤖 机械臂仿真使用帮助: -命令行参数: - --fps N 设置仿真帧率(1-120),默认60 - --traj-points N 设置轨迹插值点数(5-100),默认20 - --smooth-factor F 设置轨迹平滑系数(0.01-1.0),默认0.2 - --time-weight F 设置时间权重(0-1),默认0.6 - --energy-weight F 设置能耗权重(0-1),默认0.4 - --log-level LEVEL 设置日志级别(DEBUG/INFO/WARNING/ERROR),默认INFO - -h/--help 显示此帮助信息 - """) - sys.exit(0) - - return _global_config - - -# ====================== 2. 性能优化缓存 ====================== -_TRAJ_CACHE = { - "butter_coeffs": {}, - "joint_limits": None -} - -_COLLISION_CACHE = { - "link_ids": {}, - "obstacle_grid": None, - "obstacle_array": None, - "safety_margin": None -} - -_ENERGY_CACHE = { - "friction": None, - "motor_eff": None + kernel32 = ctypes.windll.kernel32 + kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7) + os.system('chcp 65001 >nul 2>&1') + kernel32.SetThreadPriority(kernel32.GetCurrentThread(), 1) + except: + pass + os.environ['OMP_NUM_THREADS'] = '1' + os.environ['MKL_NUM_THREADS'] = '1' + +# Mujoco Viewer兼容 +try: + from mujoco import viewer + + MUJOCO_NEW_VIEWER = True +except ImportError: + import mujoco.viewer as viewer + + MUJOCO_NEW_VIEWER = False + +# 核心参数配置 +JOINT_COUNT = 5 +JOINT_NAMES = ["joint1", "joint2", "joint3", "joint4", "joint5"] +JOINT_LIMITS = np.array([ + [-np.pi, np.pi], # joint1 (Z轴) + [-np.pi / 2, np.pi / 2], # joint2 (Y轴) + [-np.pi / 2, np.pi / 2], # joint3 (Y轴) + [-np.pi / 2, np.pi / 2], # joint4 (Y轴) + [-np.pi / 2, np.pi / 2], # joint5 (Y轴) +], dtype=np.float64) +JOINT_MAX_VELOCITY = np.array([1.0, 0.8, 0.8, 0.6, 0.6], dtype=np.float64) + +# 仿真参数 +SIMULATION_TIMESTEP = 0.005 +CONTROL_FREQUENCY = 200 +CONTROL_TIMESTEP = 1.0 / CONTROL_FREQUENCY +FPS = 60 +SLEEP_TIME = 1.0 / FPS +EPS = 1e-8 +RUNNING = True + +# PD控制参数 +KP = 80.0 +KD = 5.0 + + +# ====================== 信号处理(优雅退出) ====================== +def signal_handler(sig, frame): + global RUNNING + print("\n⚠️ 收到退出信号,正在优雅退出...") + RUNNING = False + + +signal.signal(signal.SIGINT, signal_handler) + +# ====================== 预分配内存 ====================== +WORK_ARRAYS = { + 'current_angles': np.zeros(JOINT_COUNT, dtype=np.float64), + 'target_angles': np.zeros(JOINT_COUNT, dtype=np.float64), + 'joint_velocities': np.zeros(JOINT_COUNT, dtype=np.float64), + 'control_signals': np.zeros(JOINT_COUNT, dtype=np.float64), + 'ee_position': np.zeros(3, dtype=np.float64), + 'angle_error': np.zeros(JOINT_COUNT, dtype=np.float64), + 'desired_vel': np.zeros(JOINT_COUNT, dtype=np.float64) } -def init_global_caches(): - """初始化全局缓存(只执行一次)""" - config = get_config() - - if _TRAJ_CACHE["joint_limits"] is None: - # 改为5个关节的限制参数 - _TRAJ_CACHE["joint_limits"] = { - "max_vel": np.array(config.physics.max_vel, dtype=np.float64), - "max_acc": np.array(config.physics.max_acc, dtype=np.float64), - "max_torque": np.array(config.physics.max_torque, dtype=np.float64) +# ====================== 兼容型Mujoco ID查询函数 ====================== +def get_mujoco_id(model, obj_type, name): + """ + 兼容所有Mujoco版本的ID查询函数 + :param model: MjModel对象 + :param obj_type: 对象类型(字符串或枚举) + :param name: 对象名称 + :return: 对象ID + """ + # 处理类型转换(关键修复) + if isinstance(obj_type, str): + # 字符串类型映射 + type_map = { + 'joint': mujoco.mjtObj.mjOBJ_JOINT, + 'actuator': mujoco.mjtObj.mjOBJ_ACTUATOR, + 'site': mujoco.mjtObj.mjOBJ_SITE } + obj_type_int = type_map.get(obj_type, mujoco.mjtObj.mjOBJ_JOINT) + else: + # 枚举类型转为整数(核心修复) + obj_type_int = int(obj_type) - if not _COLLISION_CACHE["link_ids"]: - _COLLISION_CACHE["obstacle_array"] = np.array(config.obstacle.obstacle_list, dtype=np.float64) - _COLLISION_CACHE["safety_margin"] = config.obstacle.safety_margin - obs_pos = _COLLISION_CACHE["obstacle_array"][:, :3] - min_coords = np.min(obs_pos, axis=0) - 0.5 - max_coords = np.max(obs_pos, axis=0) + 0.5 - _COLLISION_CACHE["obstacle_grid"] = (min_coords, max_coords) - - if _ENERGY_CACHE["friction"] is None: - _ENERGY_CACHE["friction"] = np.array(config.efficiency.joint_friction, dtype=np.float64) - _ENERGY_CACHE["motor_eff"] = config.efficiency.motor_efficiency - - -# ====================== 3. 核心算法模块(改为5关节) ====================== -def smooth_cartesian_traj(traj_points: List[List[float]], smooth_factor: float = None) -> List[List[float]]: - """笛卡尔轨迹平滑""" - config = get_config() - smooth_factor = smooth_factor or config.efficiency.smooth_factor - traj_array = np.asarray(traj_points, dtype=np.float64) - - if traj_array.size == 0 or len(traj_array) <= 1: - return traj_points - - key = round(smooth_factor, 3) - if key not in _TRAJ_CACHE["butter_coeffs"]: - b, a = butter(1, smooth_factor, btype="low") - _TRAJ_CACHE["butter_coeffs"][key] = (b.astype(np.float64), a.astype(np.float64)) - b, a = _TRAJ_CACHE["butter_coeffs"][key] - - k = min(3, len(traj_array) - 1) - t = np.linspace(0, 1, len(traj_array), dtype=np.float64) - t_smooth = np.linspace(0, 1, max(10, len(traj_array) * 2), dtype=np.float64) - + # 兼容不同版本的mj_name2id调用方式 try: - spline = interpolate.make_interp_spline(t, traj_array, k=k, axis=0) - smooth_vals = spline(t_smooth) - smooth_vals = filtfilt(b, a, smooth_vals, axis=0) - - smoothed_traj = np.empty_like(traj_array) - for dim in range(3): - smoothed_traj[:, dim] = np.interp(t, t_smooth, smooth_vals[:, dim]) - return smoothed_traj.tolist() - except Exception: - return traj_points - - -def time_optimal_joint_trajectory( - start_joint: np.ndarray, - end_joint: np.ndarray, - seg_time: float -) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: - """时间最优关节轨迹(改为5关节)""" - limits = _TRAJ_CACHE["joint_limits"] - max_vel = limits["max_vel"] - max_acc = limits["max_acc"] - - config = get_config() - traj_points = config.efficiency.traj_interp_points - - t_steps = np.linspace(0, seg_time, traj_points, dtype=np.float64) - # 改为5列(5个关节) - opt_pos = np.empty((traj_points, JOINT_COUNT), dtype=np.float64) - opt_vel = np.empty_like(opt_pos) - opt_acc = np.empty_like(opt_pos) - - delta = end_joint - start_joint - delta_abs = np.abs(delta) - sign = np.sign(delta) - - t_acc = max_vel / max_acc - s_acc = 0.5 * max_acc * t_acc ** 2 - t_joint = np.where( - delta_abs < 2 * s_acc, - 2 * np.sqrt(delta_abs / max_acc), - 2 * t_acc + (delta_abs - 2 * s_acc) / max_vel - ) - - # 遍历5个关节 - for i, t in enumerate(t_steps): - for j in range(JOINT_COUNT): - if delta_abs[j] < 2 * s_acc[j]: - if t <= t_joint[j] / 2: - opt_pos[i, j] = start_joint[j] + 0.5 * max_acc[j] * t ** 2 * sign[j] - opt_vel[i, j] = max_acc[j] * t * sign[j] - opt_acc[i, j] = max_acc[j] * sign[j] - else: - t_rem = t_joint[j] - t - opt_pos[i, j] = end_joint[j] - 0.5 * max_acc[j] * t_rem ** 2 * sign[j] - opt_vel[i, j] = max_acc[j] * t_rem * sign[j] - opt_acc[i, j] = -max_acc[j] * sign[j] - else: - if t <= t_acc[j]: - opt_pos[i, j] = start_joint[j] + 0.5 * max_acc[j] * t ** 2 * sign[j] - opt_vel[i, j] = max_acc[j] * t * sign[j] - opt_acc[i, j] = max_acc[j] * sign[j] - elif t <= t_acc[j] + (delta_abs[j] - 2 * s_acc[j]) / max_vel[j]: - opt_pos[i, j] = start_joint[j] + (s_acc[j] + max_vel[j] * (t - t_acc[j])) * sign[j] - opt_vel[i, j] = max_vel[j] * sign[j] - opt_acc[i, j] = 0.0 - else: - t_rem = t_joint[j] - t - opt_pos[i, j] = end_joint[j] - 0.5 * max_acc[j] * t_rem ** 2 * sign[j] - opt_vel[i, j] = max_acc[j] * t_rem * sign[j] - opt_acc[i, j] = -max_acc[j] * sign[j] - - opt_vel[i] = np.clip(opt_vel[i], -max_vel, max_vel) - opt_acc[i] = np.clip(opt_acc[i], -max_acc, max_acc) - - return opt_pos, opt_vel, opt_acc - - -def full_arm_collision_check( - model, - data, - return_min_dist: bool = True -) -> Tuple[bool, float] | bool: - """全链路碰撞检测""" - if not _COLLISION_CACHE["link_ids"]: - # 5个关节对应的连杆 - link_names = ["link1", "link2", "link3", "link4", "link5", "end_effector"] - for name in link_names: - _COLLISION_CACHE["link_ids"][name] = mujoco.mj_name2id( - model, mujoco.mjtObj.mjOBJ_BODY, name - ) - - collision = False - min_dist = float("inf") - obstacle_array = _COLLISION_CACHE["obstacle_array"] - safety_margin = _COLLISION_CACHE["safety_margin"] - grid_min, grid_max = _COLLISION_CACHE["obstacle_grid"] - - for link_name, link_id in _COLLISION_CACHE["link_ids"].items(): - try: - link_pos = data.xpos[link_id].astype(np.float64) - - if np.any(link_pos < grid_min) or np.any(link_pos > grid_max): - continue - - obs_pos = obstacle_array[:, :3] - obs_radius = obstacle_array[:, 3] - distances = np.linalg.norm(link_pos - obs_pos, axis=1) - (obs_radius + safety_margin) - - if np.any(distances < 0): - collision = True - if not return_min_dist: - return True - - if return_min_dist: - min_dist = min(min_dist, np.min(distances)) - except Exception: - continue - - if return_min_dist: - return collision, min_dist - return collision - - -def calculate_real_energy_consumption(model, data, dt: float) -> float: - """真实能耗计算(改为5关节)""" - friction = _ENERGY_CACHE["friction"] - motor_eff = _ENERGY_CACHE["motor_eff"] - - # 只取前5个关节的数据 - torques = data.qfrc_actuator[:JOINT_COUNT].astype(np.float64) - velocities = data.qvel[:JOINT_COUNT].astype(np.float64) - - friction_loss = np.sum(friction * np.abs(velocities)) - mechanical_power = np.sum(np.abs(torques * velocities)) - total_energy = (mechanical_power + friction_loss) * dt / motor_eff - - return float(total_energy) - + # 新版本调用方式 + return mujoco.mj_name2id(model, obj_type_int, name) + except: + # 旧版本兼容 + return mujoco.mj_name2id(model, obj_type, name) -# ====================== 4. 可视化模块 ====================== -def draw_enhanced_visualization( - viewer_inst, - model, - data, - traj_history: Deque[list], - collision_warning: bool -): - """增强可视化""" - try: - scene = viewer_inst.user_scn - scene.ngeom = 0 - - if len(traj_history) > 1: - traj_array = np.array(traj_history, dtype=np.float64) - - for i in range(len(traj_array) - 1): - geom = mujoco.MjvGeom() - mujoco.mjv_initGeom( - geom, - mujoco.mjtGeom.mjGEOM_LINE, - np.array([0.003, 0, 0], dtype=np.float64), - traj_array[i], - traj_array[i + 1], - np.array([0, 1, 0, 0.6], dtype=np.float64) - ) - mujoco.mjv_addGeom(scene, model, data, geom) - - def draw_sphere(pos, rgba, size): - geom = mujoco.MjvGeom() - mujoco.mjv_initGeom( - geom, - mujoco.mjtGeom.mjGEOM_SPHERE, - np.array([size, 0, 0], dtype=np.float64), - pos, - np.array([0, 0, 0], dtype=np.float64), - np.array(rgba, dtype=np.float64) - ) - mujoco.mjv_addGeom(scene, model, data, geom) - - draw_sphere(traj_array[0], [0, 0, 1, 0.8], 0.015) - draw_sphere(traj_array[-1], [1, 0, 0, 0.8], 0.015) - - if collision_warning: - ee_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_SITE, "ee_site") - ee_pos = data.site_xpos[ee_id] - draw_sphere(ee_pos, [1, 0, 0, 0.3], 0.08) - - except Exception as e: - logging.warning(f"可视化绘制失败:{e}") - - -# ====================== 5. 机械臂模型构建(5关节) ====================== -def get_arm_xml_with_obstacles(config: RobotConfig) -> str: - """生成机械臂XML模型(5关节)""" - obstacles_xml = "" - for i, obs in enumerate(config.obstacle.obstacle_list): - x, y, z, r = obs - obstacles_xml += f""" - - - - """ +# ====================== 机械臂模型生成 ====================== +def create_arm_model(): + """生成极简兼容版XML模型""" xml = f""" - + -