【运动控制】CNC三轴小线段路径规划

news2024/12/26 0:36:32

CNC三轴小线段路径规划

文章目录

  • CNC三轴小线段路径规划
    • 一、项目说明
    • 二、具体实现
      • 1、速度规划
      • 2、小线段插补
      • 3、运动学逆解刀轴插补点
      • 4、差分处理得到实际的速度和加速度
      • 5、加速度滑动平均
      • 6、实现的效果如图所示
    • 三、Reference

写在前面,本文是作为一个练手小项目的总结,方便以后自己查看,也欢迎大家批评指正。项目地址: GitHub

一、项目说明

参照论文《An optimal feedrate model and solution algorithm for a high-speed machine of small line blocks with look-ahead 》给出的方法给五轴机床做速度规划。输入原始的刀心数据(x,y,z,i,j,k),对其进行速度规划和插补,获得插补后的数据(x,y,z,i,j,k),然后通过运动学逆解转化成刀轴数据(x,y,z,b,c)。通过差分计算实际的速度和加速度与规划的速度进行对比,最后观察平滑滤波后的实际加速度。

二、具体实现

1、速度规划

首先进行速度规划,按照如上的流程图(参考论文)对刀心的速度进行规划,计算得到每个刀心点对应的 ,具体的计算过程是先设定前瞻数,这里设定为4,然后通过判断上述循环条件,在

公式中选取对应的值作为当前的 ,具体的实现是代码中的speed_planning函数,函数的详细解释如下:

def speed_planning(traj_data, max_speed=MAX_SPEED, max_accel=MAX_ACCEL, corner_time=CORNER_TIME, period=PERIOD):
    """
    :@func:  参考《An optimal feedrate model and solution algorithm for a 
                    high-speed machine of small line blocks with look-ahead》给出的方法,进行速度规划
    :param traj_data: 路径信息shape(num,3) (x,y,z)
    :param max_speed=0.05: 最大合成速率 m/s
    :param max_accel=0.5: 最大合成加速度 m/s^2
    :param corner_time=0.003: 拐弯时间 s
    :param period = 0.001: 插补周期 s
    :return:
    """
    # 定义常量
    block_max = 4   # 最大前瞻数
    num = len(traj_data)
    # 开辟存储空间
    a = np.zeros([num])
    b = np.zeros([num])
    e = np.zeros([num])
    c = np.zeros([num])
    d = np.power(max_speed,2)
    Vs = np.zeros([num,1])
    # 节点差分
    dp = np.diff(traj_data, axis=0)
    # 初始化
    e[0] = d
    b[0] = 0
    b[1] = 2*max_accel*np.sqrt(np.power(dp[0],2).sum())

    for i in range(1,num-1):  # Vs中第一个速度和最后一个速度都为0
        j = 0
        if j < block_max:
            if i+j <num:
                j=j+1
                b[i+j] = 2*max_accel*np.sqrt(np.power(dp,2).sum())
                e[i+j-1] = corner_time*max_accel*max_accel / (2*(1-(dp[i+j-2,0]*dp[i+j-1,0]
                                                                    +dp[i+j-2,1]*dp[i+j-1,1]
                                                                    +dp[i+j-2,2]*dp[i+j-1,2])
                                                                    /(np.sqrt(np.power(dp[i+j-2],2).sum())
                                                                      *np.sqrt(np.power(dp[i+j-1],2).sum()))))
                c[i+j-1] = np.min([e[i+j-1],d])
                if c[i+j-1] > b[i+j]:
                    sum = 0
                    for k in range(i+1,i+j):
                        sum += b[k]
                    if sum >= c[i]:
                        Vs[i]=np.sqrt(np.min([Vs[i-1]*Vs[i-1]+b[i], e[i]]))
                elif c[i+j-1] <= b[i+j]:
                    sum = 0
                    for k in range(i+1,i+j):
                        sum += b[k]
                    Vs[i] = np.sqrt(np.min([sum+c[i+j-1], Vs[i-1]*Vs[i-1]+b[i], e[i]]))

            elif i+j >= num:
                sum = 0
                for k in range(i+1,i+j):
                    sum += b[k]
                Vs[i] = np.sqrt(np.min([sum, Vs[i-1]*Vs[i-1]+b[i], e[i]]))

        elif j>= block_max:
            sum = 0
            for k in range(i+1,i+j):
                sum += b[k]
                Vs[i] = np.sqrt(np.min([sum, Vs[i-1]*Vs[i-1]+b[i], e[i]]))
    return Vs

2、小线段插补

然后再按照上图的方法计算每一段插值时所需要的信息

具体实现函数是calc_lines_info

def calc_lines_info(path_data, plan_vels, max_speed=MAX_SPEED, max_accel=MAX_ACCEL):
    """
    :@func: 三轴小线段计算中间信息函数
    :param path_data:  初始路径信息 shape(n,6) (x,y,z,i,j,k)
    :param plan_vels:  初始路径点的合成规划速度 shape(n,1)
    :param max_speed = 0.05: 三轴最大合成速度  m/s
    :param max_accel = 0.5: 三轴最大合成加速度 m/s^2
    :return : 

    """
    
    num = path_data.shape[0]
    # distances = np.zeros([num -1, 1])  #插值点间的距离shape(n-1, 3)
    dp = np.diff(path_data[:,0:3],axis=0)
    # 开辟存储空间
    vels_m = np.zeros([num -1])
    s1 = np.zeros([num -1])
    s2 = np.zeros([num -1])
    s3 = np.zeros([num -1])
    ta = np.zeros([num -1])
    td = np.zeros([num -1])
    tl = np.zeros([num -1])
    for i in range(num -1):
        # 计算距离
        # distances[i] = np.sqrt(np.power(path_data[i+1,0:3] - path_data[i,0:3],2).sum()) 
        dis_i = np.sqrt(np.power(dp[i],2).sum())
        # 计算合成速度
        vi = plan_vels[i]
        vi_1 = plan_vels[i+1]
        
        # vels_m[i] = min(np.sqrt((np.power(vi,2) + np.power(vi_1,2) + 2*max_accel*distances[i])/2), max_speed)
        vels_m[i] = min(np.sqrt((np.power(vi,2) + np.power(vi_1,2) + 2*max_accel*dis_i)/2), max_speed)
        s1[i] = (np.power(vels_m[i],2) - np.power(vi,2))/2
        s3[i] = (np.power(vels_m[i],2) - np.power(vi_1,2))/2
        s2[i] = dis_i - s1[i] -s3[i]
        ta[i] = (vels_m[i] - vi) / max_accel
        td[i] = (vels_m[i] - vi_1) / max_accel
        tl[i] = s2[i] / vels_m[i]
    
    return vels_m, ta, td, tl

然后我们进行单条线段的插补,函数实现是calc_axis_point

def calc_axis_point(start, end, Vi, Vi_1, Vm, timea, timed, timel, max_accel=MAX_ACCEL, period=PERIOD):
    """
    :@func:  计算一段的插补点
    :param start:  shape(6,)
    :param end: shape(6,)
    :param Vm: 该段的最大速度
    :param Vi: 起始速度
    :param Vi_1: 终点速度
    :param timea: 加速时间
    :param timed: 匀速时间
    :param timel: 减速时间

    :return: 返回给定两点之间的插补点,shape(n,6)
    """

    dist = np.sqrt(np.power(end[0:3]-start[0:3],2).sum())
    # 计算三轴x,y,z分别所占的比例
    k1 = (end[0:3]-start[0:3])[0] / np.sqrt(np.power(end[0:3]-start[0:3],2).sum())
    k2 = (end[0:3]-start[0:3])[1] / np.sqrt(np.power(end[0:3]-start[0:3],2).sum())
    k3 = (end[0:3]-start[0:3])[2] / np.sqrt(np.power(end[0:3]-start[0:3],2).sum())

    # 计算法向量的比例
    delta = (end[3:]-start[3:]) / (timea+timed+timel)

    # 插值周期数
    t_count = 1
    V1 = Vi         #起始点速度
    V2 = Vi_1       #终点速度

    # 开辟存储空间
    line_points = np.zeros([1,6])
    dis = 0
    while(t_count*period <= timea+timed+timel and dis<=dist):
        # 计算加速时间内的插值点
        if t_count*period <= timea:
            dis += V1*period + 0.5*max_accel*period*period
            V1 += max_accel*period
            if V1 > Vm:
                V1 = Vm
            t_count += 1
            delta_vec = delta * t_count * period
            delta_xyz = np.concatenate([k1*dis, k2*dis, k3*dis], axis=0)
            dp = np.concatenate([delta_xyz, delta_vec], axis=0)
            p = start + dp
            line_points = np.vstack([line_points, p])

        # 计算匀速时间内的插值点
        elif timea < t_count*period <= timea+timel and timel!=0:
            dis += V1 * period
            t_count += 1
            delta_vec = delta * t_count * period
            delta_xyz = np.concatenate([k1*dis, k2*dis, k3*dis], axis=0)
            dp = np.concatenate([delta_xyz, delta_vec], axis=0)
            p = start + dp
            line_points = np.vstack([line_points, p])
        
        # 计算减速时间内的插值点
        elif timea+timel < t_count*period <= timea+timel+timed:
            dis += V1*period - 0.5*max_accel*period*period
            V1 = V1 - max_accel*period
            if V1 < V2:
                V1 = V2
            t_count += 1
            delta_vec = delta * t_count * period
            delta_xyz = np.concatenate([k1*dis, k2*dis, k3*dis], axis=0)
            dp = np.concatenate([delta_xyz, delta_vec], axis=0)
            p = start + dp
            line_points = np.vstack([line_points, p])
    
    return line_points[1:]

再进行多条线段插补,函数实现是calc_axis_points

def calc_axis_points(path_data, plan_vels, max_speed=MAX_SPEED, max_accel=MAX_ACCEL, corner_time=CORNER_TIME, period=PERIOD):
    """
    :@func:  计算所有的插补点
    :param path_data:  shape(num,6)
    :param plan_vels: shape(num,)
    :param max_speed = 0.05: 三轴最大合成速度  m/s
    :param max_accel = 0.5: 三轴最大合成加速度 m/s^2
    :param corner_time = 0.003: 拐弯时间 s
    :param period = 0.001: 固定插补周期 s

    :return: 所有的插补点,shape(n,6)
    """
    vels_m, ta, td, tl = calc_lines_info(path_data, plan_vels, max_speed, max_accel)

    # 开辟存储空间
    axis_points = np.zeros([1,6])
    num = len(path_data) - 1
    for i in range(num):
        line_points = calc_axis_point(path_data[i], path_data[i+1], plan_vels[i], plan_vels[i+1], vels_m[i], ta[i], td[i], tl[i], max_accel, period)
        axis_points = np.vstack([axis_points, line_points])
    
    return axis_points[1:]

3、运动学逆解刀轴插补点

然后使用运动学逆解,求出刀轴的坐标,函数实现是inv_kinema

def inv_kinema(path_data):
    """
    :@func: 求解逆运动学
    :param path_data: 刀心路径点信息 shape(num,6) (x,y,z,i,j,k)
    :return :  求解的刀轴信息 shape(num,5) (x,y,z,b,c)
    """
    #加载数据
    points = read_path_data(DATA_PATH)
    curve_points = points[:,0:3]    # 路径
    normal_vectors = points[:,3:]  # 法向量


    # 旋转轴初始方向
    wc = sympy.Matrix([[0], [0], [1]])
    wb = sympy.Matrix([[0], [0.5 * sympy.sqrt(2)], [0.5 * sympy.sqrt(2)]])

    theta_b, theta_c ,x,y,z= sympy.symbols('theta_b theta_c x y z')
    # 对C旋转
    cRodrigues = Rodrigues(wc, theta_c)
    # 对B旋转
    bRodrigues = Rodrigues(wb, theta_b)
    # print(bRodrigues)
    zero=np.zeros((3,1))
    # 生成旋转矩阵
    ec=np.append(cRodrigues,zero,axis=1)
    e_bu=np.array([0,0,0,1]).reshape(1,4)
    e_c=np.append(ec,e_bu,axis=0).reshape(4,4)
    eb=np.append(bRodrigues,zero,axis=1)
    e_b=np.append(eb,e_bu,axis=0).reshape(4,4)
    # print(eb,e_b)
    e_x=np.array([1,0,0,x,0,1,0,0,0,0,1,0,0,0,0,1]).reshape(4,4)
    e_y=np.array([1,0,0,0,0,1,0,y,0,0,1,0,0,0,0,1]).reshape(4,4)
    e_z=np.array([1,0,0,0,0,1,0,0,0,0,1,z,0,0,0,1]).reshape(4,4)
    # 初始位型
    mt0 = np.array([1,0,0,10,0,1,0,20,0,0,1,100,0,0,0,1]).reshape(4,4)
    gmw=np.eye(4)
    gmt1=np.dot(e_x,e_y)
    gmt2= np.dot(gmt1,e_z)
    gmt3=np.dot(e_c,e_b)
    gmt4=np.dot(gmt3,mt0)
    gmt=np.dot(gmt2,gmt4)
    # 转移矩阵
    gm=np.dot(gmw,gmt)

    # 带入解析式,求解
    bx = normal_vectors[:,0]/np.linalg.norm(normal_vectors[:,0])#归一化
    by = normal_vectors[:,1]/np.linalg.norm(normal_vectors[:,1])
    bz = normal_vectors[:,2]/np.linalg.norm(normal_vectors[:,2])
    x = curve_points[:,0]
    y = curve_points[:,1]
    z = curve_points[:,2] 

    print(2*bz-1)
    theta_b = np.arccos(2*bz-1)
    theta_c = np.arcsin(((bz-1)*bx+np.sqrt(2)*np.sqrt(bz-bz**2)*by)/(1-bz**2))

    # 存数据
    myfile = open("path_interpolation_xyzbc.txt", "w")
    myfile.write('x   y   z   b   c\n')
    for i in range(0, normal_vectors.__len__()):
        myfile.write('{:>6.3f} '.format(x[i]))
        myfile.write('{:>6.3f} '.format(y[i]))
        myfile.write('{:>6.3f} '.format(z[i]))
        myfile.write('{:>6.3f} '.format(theta_b[i]))
        myfile.write('{:>6.3f}\n'.format(theta_c[i]))

    myfile.close()

4、差分处理得到实际的速度和加速度

接着用速度差分查看实际的速度,函数实现是diff_vel_accel

def diff_vel_accel(axis_points, period=PERIOD):
    """
    :@func:  通过五轴插补点计算实际速度和实际加速度
    :param axis_points:  实际插补点
    :param period: 插补周期

    :return: 实际速度, 实际加速度
    """

    delta_d = np.diff(axis_points[:,0:3], axis=0)
    dv = delta_d / period
    real_vels = np.sqrt(np.power(dv,2).sum(axis=1))
    delta_v = np.diff(dv, axis=0)
    da = delta_v / period
    real_accels = np.sqrt(np.power(da,2).sum(axis=1))
    # print(real_vels[0:5])
    # print(real_accels[0:5])

    # 将实际速度写入到文件real_velocities.txt中
    write2file('real_velocities.txt', real_vels, string = "real velocities")
    # 将实际加速度写入到文件real_accelerations.txt中
    write2file('real_accelerations.txt', real_accels, string="real accelerations")
    return real_vels, real_accels

5、加速度滑动平均

最后对实际的加速度进行滑动平均,与设定值进行比较,函数实现是sliding_average

def sliding_average(data, window_size):
    """
    :@func: 实现滑动平均滤波
    :param data: 滑动滤波的数据
    :param window_size:  滑动窗口大小
    """
    filtered_data = []
    for i in range(len(data)):
        if i < window_size:
            filtered_data.append(sum(data[:i+1]) / (i+1))
        else:
            filtered_data.append(sum(data[i-window_size+1:i+1]) / window_size)
    
    write2file("filtered_accelerations.txt", filtered_data, string = "filtered accelerations")
    return filtered_data

6、实现的效果如图所示

其中,蓝色的点是原始路径点,红色的线是由插补点绘制的小线段轨迹。

三、Reference

1、《An optimal feedrate model and solution algorithm for a high-speed machine of small line blocks with look-ahead 》

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

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

相关文章

2023年阿里云ECS服务器S6/C6/G6/N4/R6/sn2ne/sn1ne/se1ne处理器CPU性能详解

阿里云ECS服务器S6/C6/G6/N4/R6/sn2ne/sn1ne/se1ne处理器CPU性能怎么样&#xff1f;阿里云服务器优惠活动机型有云服务器S6、计算型C6、通用型G6、内存型R6、云服务器N4、云服务器sn2ne、云服务器sn1ne、云服务器se1ne处理器CPU性能详解及使用场景说明。 1、阿里云服务器活动机…

Ollydbg简明使用指南

OllyDebug&#xff0c;简称OD&#xff0c;一种反汇编软件&#xff0c;动态追踪工具&#xff0c;将IDA与SoftICE结合起来的思想&#xff0c;Ring 3 级的调试器。OllyDebug的使用界面是可视化操作。 英文版 Read this for quick start. Consult help file for details and more …

Hive窗口函数

概述 窗口函数&#xff08;window functions&#xff09;也叫开窗函数、OLAP函数。 如果函数具有over子句&#xff0c;则它是窗口函数 窗口函数可以简单地解释为类似于聚合函数的计算函数&#xff0c;但是通过group by 子句组合的 常规聚合会隐藏正在聚合的各个…

task与function

task和function主要是有助于代码的可重用性&#xff0c;都可以在module-endmodule之外声明。 1.function 1.1.function逻辑的综合 function&#xff1a;一个只有1个wire型输出值、全是组合逻辑的函数&#xff0c;且函数名即输出信号名&#xff0c;小括号中按顺序例化输入信号。…

C语言中#include<...>和#include“...“的区别

C语言文章更新目录 C语言学习资源汇总&#xff0c;史上最全面总结&#xff0c;没有之一 C/C学习资源&#xff08;百度云盘链接&#xff09; 计算机二级资料&#xff08;过级专用&#xff09; C语言学习路线&#xff08;从入门到实战&#xff09; 编写C语言程序的7个步骤和编程…

深入浅出C++ ——哈希的应用

文章目录一、位图1. 位图的概念2. STL中的位图3. 位图的特点4. 位图的应用5. 位图的实现6. 位图的使用二、布隆过滤器1. 布隆过滤器提出2. 布隆过滤器概念3. 布隆过滤器的设计思路4. 布隆过滤器的插入5. 布隆过滤器的查找6. 布隆过滤器删除7. 布隆过滤器的优缺点8. 布隆过滤器的…

div增加鼠标点透,css设置点击穿透

需求&#xff1a;将一张照片盖到一个div上面&#xff0c;但同时下面div上面的点击事件不受影响。 这样就需要用到 CSS 的鼠标穿透属性&#xff1a;pointer-events: none&#xff0c;下面主要对pointer-events属性的值做一个简单的介绍。 pointer-event的所有属性值: pointer…

计讯物联环保数采仪全系列产品为节能降耗减碳贡献绿色力量

政策背景 近日&#xff0c;工业和信息化部、国家发展改革委、生态环境部三部门联合印发《工业领域碳达峰实施方案》&#xff08;以下简称“方案”&#xff09;。《方案》提出&#xff0c;促进中小企业绿色低碳发展。优化中小企业资源配置和生产模式&#xff0c;探索开展绿色低…

在字节跳动,造赛博古籍

“你在字节跳动哪个业务&#xff1f;”“古籍数字化。把《论语》《左传》《道德经》这些古籍变成电子版&#xff0c;让大家都能免费看。”没错&#xff0c;除了你熟悉的那些 App&#xff0c;字节跳动还在做一些小众而特别的事情&#xff0c;古籍数字化就是其中之一。在字节跳动…

Python+Selenium4元素交互1_web自动化(5)

目录 0. 上节回顾 1. 内置的等待条件 2. 元素属性 1. Python对象属性 2. HTML元素属性 3. 元素的交互 1. 输入框 2. 按钮 3. 单选框和复选框 0. 上节回顾 DEBUG的方式&#xff1a;JS断点 Python断点编程语言提供的等待方式&#xff1a;sleepselenium提供的等待方式&…

CDGA|浅谈“以治促用,以用促治”的数据治理战略

数据治理夯实企业数字化转型基础。采取“以治促用&#xff0c;以用促治”的数据治理战略&#xff0c;可以充分释放了企业核心运行要素的活力。 “以治促用”是指通过建立在数据治理链路及用户多维评估系统的基础上&#xff0c;对数据资产重新进行价值识别&#xff0c;推进高价值…

30岁测试开发年薪不足50万,被面试官嘲讽混得太差?

近日&#xff0c;有网友发帖称&#xff1a;“30岁去应聘测试开发&#xff0c;拿不到七八十万的年薪确实有点丢人了&#xff0c;还被面试官diss混得太差了”&#xff0c;网友们看完都炸了。 来看看网友们都是怎么说的。 有网友说&#xff1a; 扯淡 有网友气到&#xff1a; 那拿…

接口自动化

为了实现真正意义上的接口自动化&#xff0c;一般使用yaml文件存储测试用例&#xff0c;代码调用里面的数据来发送请求 Controller RequestMapping("/send") public class Login {ResponseBodyRequestMapping("/login")public State login(String name,Str…

ES6-ES11基本全部语法

在进入es6语法之前&#xff0c;先走一波es5遍历迭代Api&#xff0c;&#xff0c;它们的作用&#xff0c;应用场景&#xff0c;参数&#xff0c;以及返回值分别是什么。&#xff08;forEach、map、some、every、filter&#xff09;我们统一设定一个初始数组&#xff1a;let arra…

Prophet 处理时间序列数据

Prophet 处理时间序列数据 flyfish 论文地址 https://peerj.com/preprints/3190/ 官网 https://facebook.github.io/prophet/ 源码地址 https://github.com/facebook/prophet hon import pandas as pd from prophet import Prophet df pd.read_csv(https://raw.githubuse…

2月23号作业

题目&#xff1a;题目一&#xff1a;通过操作Cortex-A7核&#xff0c;串口输入相应的命令&#xff0c;控制LED灯进行工作--->上传CSDN 1.例如在串口输入led1on,开饭led1灯点亮 2.例如在串口输入led1off,开饭led1灯熄灭 3.例如在串口输入led2on,开饭led2灯点亮 4.例如在串口输…

[HarekazeCTF2019]Easy Notes

知识点&#xff1a;session 反序列化&#xff0c;代码审计代码分析 flag.php 中有个 is_admin 函数的判断。 在 lib.php 中有 is_admin 函数&#xff0c;需要 session[admin] 为 true&#xff0c;或者通过文件读取的方式。 在 index.php 中的 include 并不能使用伪协议读取 …

JVM回顾与Java虚拟机的内存管理

目录 什么是JVM&#xff1f; 主流虚拟机 JVM与操作系统关系 JVM、JRE、JDK的关系 Java程序的执行过程 JVM翻译字节码有三种执行方式 Java虚拟机的内存管理 JVM整体架构图 JVM运行时内存 Java7和Java8内存结构的不同主要体现在方法区的实现 对于Java8&#xff0c;HotSp…

Hadoop MapReduce基本概念与详细流程

Hadoop MapReduce是Hadoop 中一个批量计算的框架&#xff0c;在整个mapreduce作业的过程中&#xff0c;包括从数据的输入&#xff0c;数据的处理&#xff0c;数据的数据输入这些部分&#xff0c;而其中数据的处理部分就要map&#xff0c;reduce&#xff0c;combiner等操作组成。…

刚接手的APP项目需要优化,需要从哪些方向入手?

对于每个Android 开发团队来说产品上线&#xff0c;是让人喜忧参半的一件事。**喜指的是&#xff1a;付出了大量的时间&#xff0c;产品终于上线了&#xff1b;而忧指的是&#xff1a;担心中间会不会出现一些性能相关的问题&#xff0c;比如卡顿、内存泄漏、崩溃……等&#xf…