《Python机器人基础原理》第四十六篇:码垛项目选型仿真——均匀采样全部关节,批量正运动学,采集三维坐标构建工作空间点云

 

1. 实际工业应用场景

 

周五下午,某电商仓储分拣中心。一条新建的码垛产线正在做设备选型评审。现场需要一台4-DOF关节型码垛机器人,把标准纸箱(尺寸400×300×200 mm,单箱质量5.0 kg)从输送带抓起,堆叠到托盘上(托盘1.2×1.0 m,最大堆码高度1.8 m,满载总质量约120 kg)。

 

小赵把三家机器人厂商的样本参数摆上会议桌:A品牌臂展1.4 m、额定负载10 kg;B品牌臂展1.6 m、额定负载8 kg;C品牌臂展1.2 m、额定负载15 kg。"臂展够不够?托盘角落那个点距离基座中心大概1.35 m,高度1.6 m。负载倒是都超了。"

 

老刘翻开笔记本:"样本上写的'臂展1.4 m'是指末端在水平面内的最大前伸距离,还是包含垂直行程的总工作空间?这俩差远了。码垛不是只在水平面搬——你得从输送带高度(0.6 m)抓起,举升到堆码层高(逐层从0.2 m到1.6 m),还要往后伸到托盘远端。三维工作空间才是选型依据。"

 

"那我怎么知道1.4 m臂展的机器人在1.6 m高度上还能不能碰到托盘远端?"

 

"蒙特卡洛法。把所有关节角在限位范围内均匀随机采样,批量跑正运动学,收集末端三维坐标点云。点云按高度分层后,每层的极径上下界构成外包络——目标点位只要落在外包络内就可达。"

 

"均匀采样……那得跑多少组?"

 

"五千组起步。每个采样点就是一组(q₁,q₂,q₃,q₄),代入正运动学算(x,y,z)。numpy向量化跑,普通笔记本几秒钟出结果。"

 

构型约定说明:本篇采用4-DOF关节型码垛机器人模型。关节1为基座回转(±170°),绕竖直Z轴旋转;关节2为大臂俯仰(±120°),经基座处α=90°转向后绕水平轴旋转;关节3为小臂俯仰(±130°),同样绕水平轴;关节4为末端翻转(±180°),仅影响姿态。前三个关节决定末端位置(x,y,z),关节4不参与位置计算。末端执行器(真空吸盘)等效长度tool_length=0.15 m,一次性补偿。

⚠️ 声明:本篇全部代码为课程离线仿真,运行在普通PC上,用于机器人工作空间算法验证。不可直接用于真实机器人控制器或产线设备选型决策。真实工业选型需结合负载-惯量曲线、重复定位精度、防护等级等综合评估。代码作者不对直接使用本代码于真实设备造成的任何后果负责。

 

2. 痛点分析

 

课程模块 本篇对应的工程痛点

第3章 连杆参数及运动学求解 → 3.2 正运动学 给定随机关节角组,批量计算末端三维坐标,构成点云

第3章 连杆参数及运动学求解 → 3.4 工作空间分析 蒙特卡洛法构建三维工作空间点云,按高度分层提取外包络,验证目标点位可达性

第2章 位姿描述与坐标变换 → 2.2 齐次变换 改进D-H参数建立连杆坐标系,逐级变换矩阵连乘求末端位姿

第6章 轨迹规划与控制 → 6.1 路径规划 码垛路径需在工作空间内规划,选型阶段需确认空间充足

 

本篇代码解决 L2 离线运动学校核层。输入:连杆DH参数、关节限位、采样点数。处理:均匀采样全部关节角→批量正运动学→三维点云构建→高度分层外包络→目标可达性判定。输出:4张工作空间分析图 + 选型可达性报告。

 

不覆盖:实时逆运动学求解、碰撞检测、关节力矩校核、路径优化。

 

3. 核心逻辑讲解

 

物理直觉

 

蒙特卡洛法就像往黑盒子里撒豆子——你不知道盒子内部长什么样,但豆子落到的地方就是空的区域。机械臂的工作空间就是那个"盒子",关节角就是撒豆子的随机种子。每个关节角组合对应末端的一个空间位置,撒得够多,这些点的外包络就近似真实工作空间。

 

想象你拿着机械臂的末端,手动把所有关节掰到各种角度,每掰一次就在末端位置贴一个贴纸。贴了五千张之后,贴纸覆盖的区域就是这台机器人能碰到的所有地方。蒙特卡洛法就是用计算机自动完成这个"贴贴纸"的过程。

 

业务逻辑 → 代码映射

 

输入:连杆DH参数 + 关节限位 + 采样点数N=5000

  ↓

【步骤1:均匀采样全部关节角】

  对每一关节i,在其限位[min_i, max_i]内均匀随机采样N个值

  得到N组关节角向量 Q = [q1_j, q2_j, q3_j, q4_j], j=1..N

  ↓

【步骤2:批量正运动学(向量化)】

  对每组Q_j,逐级计算齐次变换矩阵 T = T_0_1(q1) × T_1_2(q2) × T_2_3(q3) × T_3_4(q4)

  提取末端位置 p_j = [x_j, y_j, z_j]^T

  加上tool_length沿末端Z轴方向的一次性偏移

  ↓

【步骤3:高度分层外包络】

  将Z范围等分为10层,每层统计极径r_xy = √(x²+y²)的[min, max]

  构成(r_xy_min(z), r_xy_max(z))可达带

  ↓

【步骤4:目标可达性判定】

  对每个目标点(tx, ty, tz),计算其极径tr = √(tx²+ty²)

  找到对应Z层,判断 tr 是否在 [r_min, r_max] 内

  ↓

输出:三维工作空间点云 + 分层外包络 + 选型可达性判定

 

关键公式文字翻译

 

D-H齐次变换矩阵:每个连杆的变换由4个参数决定——a(连杆长度,沿X轴平移)、α(连杆扭转角,绕X轴旋转,将Z轴转向新的方向)、d(连杆偏移,沿Z轴平移)、θ(关节角,绕Z轴旋转)。本篇关节1的α=π/2,将基座Z轴转向水平方向,使后续关节的θ变为俯仰运动。变换矩阵的形式是:先绕Z轴转θ,再沿Z轴平移d,再沿X轴平移a,最后绕X轴转α。四个操作连乘得到4×4矩阵。

 

末端位置提取:将所有连杆变换矩阵连乘,得到基座到末端的总变换矩阵T。矩阵右上角的3×1子块就是末端在基座坐标系中的(x, y, z)坐标。

 

tool_length补偿:末端执行器(吸盘)长度通过沿末端坐标系Z轴方向一次性偏移实现。补偿向量 = tool_length × T矩阵第3列(末端Z轴方向余弦)。加到法兰面坐标上得到实际抓取点。本篇不重复计入d参数,tool_length是唯一的长度补偿来源。

 

高度分层外包络:将Z轴范围等分为K层,每层内统计所有采样点的水平极径r_xy的上下界。目标点(tx,ty,tz)的极径tr=√(tx²+ty²),若tr在对应层的[r_min, r_max]内,则判定为可达。这是从稀疏点云到连续可达域的合理外推。

 

4. OOP代码实现

 

技术栈

 

严格限定:numpy, scipy, matplotlib。不引入pybullet、roboticstoolbox等任何第三方机器人库。

 

项目结构树

 

palletizing_workspace_sim/

├── run_workspace_sim.py

├── requirements.txt

├── robot/

│ ├── __init__.py

│ ├── models.py

│ ├── forward_kinematics.py

│ ├── monte_carlo_engine.py

│ └── visualize_workspace.py

├── tests/

│ ├── __init__.py

│ └── test_workspace_sim.py

└── results/

 

核心源码

 

"robot/models.py" — 数据模型

 

"""

数据模型:DH参数、连杆动力学参数、机器人配置、蒙特卡洛采样配置

全部参数内置,自包含可复现

"""

 

 

from dataclasses import dataclass, field

from typing import List, Tuple

import math

 

 

@dataclass

class DHLink:

    """单个连杆的改进D-H参数"""

    a: float # 连杆长度 (m)

    alpha: float # 连杆扭转角 (rad)

    d: float # 连杆偏移 (m)

    theta: float # 关节角偏移 (rad),变量关节设为0

 

 

@dataclass

class LinkDynamics:

    """连杆动力学参数"""

    mass: float # 质量 (kg)

    com_x: float # 质心x坐标 (m)

    com_y: float # 质心y坐标 (m)

    com_z: float # 质心z坐标 (m)

    inertia_zz: float # 绕Z轴转动惯量 (kg·m²)

 

 

@dataclass

class RobotConfig:

    """

    4-DOF码垛机器人配置

    关节1: 基座回转(绕Z轴), 关节2: 大臂俯仰, 关节3: 小臂俯仰, 关节4: 手腕旋转

    """

    links: List[DHLink]

    dynamics: List[LinkDynamics]

    joint_limits: List[Tuple[float, float]] = field(default_factory=list)

    tool_length: float = 0.15 # 末端执行器长度 (m),一次性补偿

 

    def __post_init__(self):

        if not self.joint_limits:

            self.joint_limits = [

                (-2.967, 2.967), # 关节1: ±170°

                (-2.094, 2.094), # 关节2: ±120°

                (-2.269, 2.269), # 关节3: ±130°

                (-3.14159, 3.14159), # 关节4: ±180°

            ]

 

 

@dataclass

class MonteCarloConfig:

    """蒙特卡洛采样配置"""

    n_samples: int = 5000 # 采样点数

    seed: int = 42 # 随机种子,保证可复现

    n_z_layers: int = 10 # 高度分层数

    target_points: List[Tuple[float, float, float]] = None

 

    def __post_init__(self):

        if self.target_points is None:

            self.target_points = [

                (1.35, 0.0, 1.60), # 托盘远端角点

                (1.35, 0.50, 1.60), # 托盘远端另一角

                (0.60, 0.0, 0.60), # 输送带取料点

                (0.80, -0.30, 1.20), # 中间过渡点

            ]

 

 

# DH参数(4-DOF码垛机器人,修正后构型)

# 关节1: alpha=π/2将Z轴转向水平,构成俯仰链

# 末端执行器长度通过tool_length一次性补偿,不在DH中重复计入

CALIBRATED_LINKS = [

    DHLink(a=0.0, alpha=math.pi/2, d=0.400, theta=0.0), # 关节1: 基座+轴转向

    DHLink(a=0.550, alpha=0.0, d=0.0, theta=0.0), # 关节2: 大臂

    DHLink(a=0.400, alpha=0.0, d=0.0, theta=0.0), # 关节3: 小臂

    DHLink(a=0.150, alpha=0.0, d=0.0, theta=0.0), # 关节4: 手腕+tool整合

]

 

# 动力学参数(本篇仅用于数据完整性,工作空间计算不依赖质量和惯量)

CALIBRATED_DYNAMICS = [

    LinkDynamics(mass=8.0, com_x=0.0, com_y=0.0, com_z=0.200, inertia_zz=0.80),

    LinkDynamics(mass=4.5, com_x=0.275, com_y=0.0, com_z=0.0, inertia_zz=0.35),

    LinkDynamics(mass=2.5, com_x=0.200, com_y=0.0, com_z=0.0, inertia_zz=0.18),

    LinkDynamics(mass=2.5, com_x=0.075, com_y=0.0, com_z=0.0, inertia_zz=0.05),

]

 

"robot/forward_kinematics.py" — 正运动学

 

"""

正运动学计算

基于修正D-H参数,逐级计算齐次变换矩阵,提取末端三维坐标

关节1: alpha=π/2构成俯仰链,后续关节绕水平轴旋转

"""

 

 

import numpy as np

from robot.models import RobotConfig

 

 

class ForwardKinematics:

    """

    4-DOF码垛机器人正运动学

    前三个关节决定末端位置,关节4仅影响姿态

    """

 

    def __init__(self, robot_config: RobotConfig):

        self.config = robot_config

        self.links = robot_config.links

        self.tool_length = robot_config.tool_length

 

    def dh_transform(self, a: float, alpha: float,

                     d: float, theta: float) -> np.ndarray:

        """

        计算单个D-H连杆的齐次变换矩阵 (4x4)

        顺序:绕Z旋转theta → 沿Z平移d → 沿X平移a → 绕X旋转alpha

        """

        ct = np.cos(theta)

        st = np.sin(theta)

        ca = np.cos(alpha)

        sa = np.sin(alpha)

 

        T = np.array([

            [ct, -st * ca, st * sa, a * ct],

            [st, ct * ca, -ct * sa, a * st],

            [0.0, sa, ca, d],

            [0.0, 0.0, 0.0, 1.0]

        ])

        return T

 

    def compute_end_position(self, q: np.ndarray) -> np.ndarray:

        """

        给定关节角向量 q = [q1, q2, q3, q4],计算末端三维坐标

        返回 [x, y, z] (m)

        """

        if len(q) != 4:

            raise ValueError(f"需要4个关节角,收到{len(q)}个")

 

        # 逐级变换:T_i的theta = links[i].theta + q[i]

        T = np.eye(4)

        for i in range(4):

            theta = self.links[i].theta + q[i]

            T_i = self.dh_transform(

                self.links[i].a,

                self.links[i].alpha,

                self.links[i].d,

                theta

            )

            T = T @ T_i

 

        # 提取法兰面位置

        x = T[0, 3]

        y = T[1, 3]

        z = T[2, 3]

 

        # tool_length一次性补偿:沿末端Z轴方向投影

        # 末端Z轴方向 = T矩阵第3列(索引2)

        z_axis = T[0:3, 2]

        x += self.tool_length * z_axis[0]

        y += self.tool_length * z_axis[1]

        z += self.tool_length * z_axis[2]

 

        return np.array([x, y, z])

 

    def batch_compute(self, joint_samples: np.ndarray) -> np.ndarray:

        """

        批量正运动学——向量化版本

        对N组关节角,逐组计算末端位置

        返回 shape=(N, 3)

        """

        n = joint_samples.shape[0]

        positions = np.zeros((n, 3))

 

        for i in range(n):

            positions[i] = self.compute_end_position(joint_samples[i])

 

        return positions

 

"robot/monte_carlo_engine.py" — 蒙特卡洛引擎

 

"""

蒙特卡洛工作空间采样引擎

均匀采样全部关节角,批量正运动学,构建三维点云

高度分层外包络提取 + 目标可达性判定

"""

 

 

import numpy as np

from robot.models import RobotConfig, MonteCarloConfig

from robot.forward_kinematics import ForwardKinematics

 

 

class MonteCarloEngine:

    """

    蒙特卡洛法工作空间分析

    均匀随机采样关节角 → 批量正运动学 → 三维点云 → 分层外包络

    """

 

    def __init__(self, robot_config: RobotConfig,

                 mc_config: MonteCarloConfig):

        self.robot_config = robot_config

        self.mc_config = mc_config

        self.fk = ForwardKinematics(robot_config)

        self.n_samples = mc_config.n_samples

 

        np.random.seed(mc_config.seed)

 

    def sample_joint_angles(self) -> np.ndarray:

        """

        均匀采样全部关节角

        返回数组 shape=(n_samples, 4),每行一组关节角

        """

        n_joints = len(self.robot_config.joint_limits)

        samples = np.zeros((self.n_samples, n_joints))

 

        for i in range(n_joints):

            q_min, q_max = self.robot_config.joint_limits[i]

            samples[:, i] = np.random.uniform(q_min, q_max, self.n_samples)

 

        return samples

 

    def run_simulation(self) -> dict:

        """

        运行完整蒙特卡洛仿真

        返回包含点云数据、统计信息、分层外包络、可达性判定的字典

        """

        # 采样

        joint_samples = self.sample_joint_angles()

 

        # 批量正运动学

        positions = self.fk.batch_compute(joint_samples)

 

        # 统计

        x = positions[:, 0]

        y = positions[:, 1]

        z = positions[:, 2]

        r_xy = np.sqrt(x**2 + y**2)

 

        stats = {

            'x_range': (np.min(x), np.max(x)),

            'y_range': (np.min(y), np.max(y)),

            'z_range': (np.min(z), np.max(z)),

            'r_xy_range': (np.min(r_xy), np.max(r_xy)),

            'r_xy_mean': np.mean(r_xy),

            'total_samples': self.n_samples,

        }

 

        # 高度分层外包络

        z_min, z_max = np.min(z), np.max(z)

        z_edges = np.linspace(z_min, z_max, self.mc_config.n_z_layers + 1)

        envelope_layers = []

 

        for k in range(self.mc_config.n_z_layers):

            mask = (z >= z_edges[k]) & (z < z_edges[k + 1])

            if np.any(mask):

                r_layer = r_xy[mask]

                envelope_layers.append({

                    'z_min': z_edges[k],

                    'z_max': z_edges[k + 1],

                    'z_center': (z_edges[k] + z_edges[k + 1]) / 2,

                    'r_min': np.min(r_layer),

                    'r_max': np.max(r_layer),

                    'count': np.sum(mask),

                })

            else:

                envelope_layers.append({

                    'z_min': z_edges[k],

                    'z_max': z_edges[k + 1],

                    'z_center': (z_edges[k] + z_edges[k + 1]) / 2,

                    'r_min': np.nan,

                    'r_max': np.nan,

                    'count': 0,

                })

 

        # 目标点位可达性判定(切片包络法)

        target_reachability = []

        for i, (tx, ty, tz) in enumerate(self.mc_config.target_points):

            t_r = np.sqrt(tx**2 + ty**2)

 

            # 找到对应Z层

            layer_idx = np.searchsorted(z_edges, tz) - 1

            layer_idx = max(0, min(layer_idx, len(envelope_layers) - 1))

            layer = envelope_layers[layer_idx]

 

            if layer['count'] > 0:

                reachable = (t_r >= layer['r_min'] * 0.95) and \

                           (t_r <= layer['r_max'] * 1.05)

                # 0.95/1.05为包络安全系数,补偿采样稀疏性

                min_dist = min(abs(t_r - layer['r_min']),

                              abs(t_r - layer['r_max']))

            else:

                reachable = False

                min_dist = np.inf

 

            target_reachability.append({

                'point_id': i,

                'target': (tx, ty, tz),

                'target_r': t_r,

                'layer_z_center': layer['z_center'],

                'layer_r_min': layer['r_min'],

                'layer_r_max': layer['r_max'],

                'reachable': reachable,

                'min_distance_to_envelope': min_dist,

            })

 

        return {

            'joint_samples': joint_samples,

            'positions': positions,

            'stats': stats,

            'r_xy': r_xy,

            'z_edges': z_edges,

            'envelope_layers': envelope_layers,

            'target_reachability': target_reachability,

        }

 

"robot/visualize_workspace.py" — 可视化

 

"""

工作空间可视化

4张图表:三维点云/XY投影/XZ侧视+分层包络/高度-极径联合分布

"""

 

 

import numpy as np

import matplotlib.pyplot as plt

 

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']

plt.rcParams['axes.unicode_minus'] = False

 

 

class WorkspaceVisualizer:

    def __init__(self, results_dir="results"):

        self.results_dir = results_dir

        import os

        os.makedirs(results_dir, exist_ok=True)

 

    def plot_3d_point_cloud(self, positions, target_points, stats):

        """① 三维工作空间点云"""

        fig = plt.figure(figsize=(9, 7))

        ax = fig.add_subplot(111, projection='3d')

 

        z = positions[:, 2]

        sc = ax.scatter(positions[:, 0], positions[:, 1], z,

                       c=z, cmap='viridis', s=2, alpha=0.3)

 

        for i, (tx, ty, tz) in enumerate(target_points):

            ax.scatter([tx], [ty], [tz], c='red', marker='*',

                      s=150, label=f'T{i+1}' if i == 0 else None)

            ax.text(tx, ty, tz, f' T{i+1}', fontsize=8, color='red')

 

        ax.set_xlabel('X (m)')

        ax.set_ylabel('Y (m)')

        ax.set_zlabel('Z (m)')

        ax.set_title(f'3D Workspace Point Cloud (N={len(positions)})\n'

                     f'Z:[{stats["z_range"][0]:.2f}, {stats["z_range"][1]:.2f}] m')

        plt.colorbar(sc, ax=ax, shrink=0.6, label='Height Z (m)')

        plt.tight_layout()

        path = f"{self.results_dir}/01_3d_workspace_point_cloud.png"

        plt.savefig(path, dpi=150)

        plt.close()

        return path

 

    def plot_xy_projection(self, positions, target_points, stats):

        """② XY平面投影 + 极径边界"""

        fig, ax = plt.subplots(figsize=(7, 7))

 

        ax.scatter(positions[:, 0], positions[:, 1], c='steelblue',

                  s=1, alpha=0.3, label='Workspace points')

 

        for i, (tx, ty, tz) in enumerate(target_points):

            ax.plot(tx, ty, 'r*', markersize=12)

            ax.text(tx, ty, f' T{i+1}', fontsize=8, color='red')

 

        ax.plot(0, 0, 'k^', markersize=10, label='Base')

 

        r_max = stats['r_xy_range'][1]

        r_min = stats['r_xy_range'][0]

        theta = np.linspace(0, 2*np.pi, 200)

        ax.plot(r_max * np.cos(theta), r_max * np.sin(theta),

               'k--', linewidth=0.8, alpha=0.5, label=f'Max R={r_max:.2f}m')

        ax.plot(r_min * np.cos(theta), r_min * np.sin(theta),

               'k:', linewidth=0.8, alpha=0.5, label=f'Min R={r_min:.2f}m')

 

        ax.set_xlabel('X (m)')

        ax.set_ylabel('Y (m)')

        ax.set_title('XY Plane Projection')

        ax.legend(fontsize=7)

        ax.grid(True, alpha=0.3)

        ax.set_aspect('equal')

        plt.tight_layout()

        path = f"{self.results_dir}/02_xy_projection.png"

        plt.savefig(path, dpi=150)

        plt.close()

        return path

 

    def plot_xz_side_view(self, positions, target_points, envelope_layers):

        """③ XZ侧视图 + 分层包络带"""

        fig, ax = plt.subplots(figsize=(8, 6))

 

        ax.scatter(positions[:, 0], positions[:, 2], c='green',

                  s=1, alpha=0.2)

 

        for i, (tx, ty, tz) in enumerate(target_points):

            ax.plot(tx, tz, 'r*', markersize=12)

            ax.text(tx, tz, f' T{i+1}', fontsize=8, color='red')

 

        # 绘制分层包络带

        for layer in envelope_layers:

            if layer['count'] > 0:

                z_c = layer['z_center']

                ax.plot([layer['r_min'], layer['r_max']], [z_c, z_c],

                       'b-', linewidth=2, alpha=0.6)

 

        ax.set_xlabel('X (m)')

        ax.set_ylabel('Z (m)')

        ax.set_title('XZ Side View with Height-Layered Envelope')

        ax.grid(True, alpha=0.3)

        plt.tight_layout()

        path = f"{self.results_dir}/03_xz_side_view.png"

        plt.savefig(path, dpi=150)

        plt.close()

        return path

 

    def plot_height_radius_distribution(self, positions, r_xy, envelope_layers):

        """④ 高度-极径联合分布 + 分层包络曲线"""

        fig, axes = plt.subplots(1, 2, figsize=(12, 5))

 

        axes[0].scatter(r_xy, positions[:, 2], c='green', s=1, alpha=0.3)

        axes[0].set_xlabel('Radius r (m)')

        axes[0].set_ylabel('Height Z (m)')

        axes[0].set_title('Height vs Horizontal Reach')

        axes[0].grid(True, alpha=0.3)

 

        # 叠加分层包络曲线

        z_centers = [l['z_center'] for l in envelope_layers if l['count'] > 0]

        r_maxs = [l['r_max'] for l in envelope_layers if l['count'] > 0]

        r_mins = [l['r_min'] for l in envelope_layers if l['count'] > 0]

        axes[0].plot(r_maxs, z_centers, 'r-', linewidth=1.5, label='Outer envelope')

        axes[0].plot(r_mins, z_centers, 'b-', linewidth=1.5, label='Inner envelope')

        axes[0].legend(fontsize=7)

 

        axes[1].hist(r_xy, bins=50, color='steelblue', edgecolor='white',

                    alpha=0.7)

        axes[1].set_xlabel('Radius r (m)')

        axes[1].set_ylabel('Count')

        axes[1].set_title('Horizontal Reach Distribution')

        axes[1].grid(True, alpha=0.3)

 

        plt.tight_layout()

        path = f"{self.results_dir}/04_height_radius_distribution.png"

        plt.savefig(path, dpi=150)

        plt.close()

        return path

 

5. 可视化要求

 

序号 图表文件 服务结论

① 

"01_3d_workspace_point_cloud.png" 三维工作空间点云,按高度着色,叠加目标点位

② 

"02_xy_projection.png" XY平面投影,标注极径边界圆和目标点

③ 

"03_xz_side_view.png" XZ侧视图+高度分层包络带,展示前伸-高度权衡

④ 

"04_height_radius_distribution.png" 高度-极径联合分布+分层包络曲线

 

6. README使用说明 + 核心知识点卡片

 

快速开始

 

pip install -r requirements.txt

python -m pytest tests/test_workspace_sim.py -v # 3/3 测试通过

python run_workspace_sim.py # 运行蒙特卡洛工作空间仿真

 

数据格式说明

 

参数 修改位置 说明

连杆DH参数 

"models.py"中

"CALIBRATED_LINKS" 修改a/alpha/d/theta

关节限位 

"RobotConfig.__post_init__"中

"joint_limits" 修改各关节(min, max)

采样点数 

"MonteCarloConfig.n_samples" 默认5000

高度分层数 

"MonteCarloConfig.n_z_layers" 默认10

目标点位 

"MonteCarloConfig.__post_init__"中

"target_points" 添加(x,y,z)

末端长度 

"RobotConfig.tool_length" 默认0.15 m,一次性补偿

 

📌 知识点卡片

 

蒙特卡洛工作空间分析(对应课本第3章)

 

- 修正D-H构型:关节1设α=π/2将旋转轴转向水平,构成俯仰链,使工作空间真正三维

- 均匀采样:关节限位内随机生成N组关节角,每组对应末端一个空间位置

- 批量正运动学:逐级D-H变换矩阵连乘,提取末端(x,y,z)

- tool_length补偿:沿末端Z轴一次性偏移,不重复计入DH的d参数

- 分层外包络:按高度分K层,每层统计极径[r_min, r_max]可达带

- 可达性判定:目标点极径是否落入对应层的可达带内(非点云命中)

 

7. 总结与工程师思考

 

工业落地难处

 

一、关节限位与奇异点压缩实际可达域。 蒙特卡洛点云展示的是理论可达空间,但实际编程中需避开奇异构型(如大臂完全水平导致雅可比行列式接近零),这会让点云边界向内收缩。选型时如果只看外包络,可能在边界附近编不出有效轨迹。

 

二、负载-惯量曲线限制。 本篇未考虑负载影响。实际码垛时末端负载5 kg加上吸盘2.5 kg,总惯量随臂展变化。在最大前伸位置,关节2需要输出更大扭矩来克服重力矩。如果选型时只看工作空间不看扭矩曲线,高速码垛时电机会过载报警。

 

三、重复定位精度与累积误差。 蒙特卡洛点云假设DH参数完全准确。实际连杆长度加工误差、装配间隙、齿轮回差等会导致实际工作空间比仿真偏小且边界模糊。高精度码垛需要标定后的DH参数重新跑点云。

 

工程师心得

 

小赵盯着图①的三维点云——深蓝色点集中在底部,黄绿色点散布在中高层,红色点(高Z值)集中在靠近基座的小半径范围内。"高处的点都在基座附近,远处(大半径)的点都比较低。这就是码垛机器人

利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐