【运动控制】CNC三轴小线段路径规划
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] = db[0] = 0b[1] = 2*max_accel*np.sqrt(np.power(dp[0],2).sum())for i in range(1,num-1): # Vs中第一个速度和最后一个速度都为0j = 0if j < block_max:if i+j <num:j=j+1b[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 = 0for 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 = 0for 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 = 0for 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 = 0for 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))/2s3[i] = (np.power(vels_m[i],2) - np.power(vi_1,2))/2s2[i] = dis_i - s1[i] -s3[i]ta[i] = (vels_m[i] - vi) / max_acceltd[i] = (vels_m[i] - vi_1) / max_acceltl[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 = 1V1 = Vi #起始点速度V2 = Vi_1 #终点速度# 开辟存储空间line_points = np.zeros([1,6])dis = 0while(t_count*period <= timea+timed+timel and dis<=dist):# 计算加速时间内的插值点if t_count*period <= timea:dis += V1*period + 0.5*max_accel*period*periodV1 += max_accel*periodif V1 > Vm:V1 = Vmt_count += 1delta_vec = delta * t_count * perioddelta_xyz = np.concatenate([k1*dis, k2*dis, k3*dis], axis=0)dp = np.concatenate([delta_xyz, delta_vec], axis=0)p = start + dpline_points = np.vstack([line_points, p])# 计算匀速时间内的插值点elif timea < t_count*period <= timea+timel and timel!=0:dis += V1 * periodt_count += 1delta_vec = delta * t_count * perioddelta_xyz = np.concatenate([k1*dis, k2*dis, k3*dis], axis=0)dp = np.concatenate([delta_xyz, delta_vec], axis=0)p = start + dpline_points = np.vstack([line_points, p])# 计算减速时间内的插值点elif timea+timel < t_count*period <= timea+timel+timed:dis += V1*period - 0.5*max_accel*period*periodV1 = V1 - max_accel*periodif V1 < V2:V1 = V2t_count += 1delta_vec = delta * t_count * perioddelta_xyz = np.concatenate([k1*dis, k2*dis, k3*dis], axis=0)dp = np.concatenate([delta_xyz, delta_vec], axis=0)p = start + dpline_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) - 1for 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 / periodreal_vels = np.sqrt(np.power(dv,2).sum(axis=1))delta_v = np.diff(dv, axis=0)da = delta_v / periodreal_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 》
相关文章:

【运动控制】CNC三轴小线段路径规划
CNC三轴小线段路径规划 文章目录CNC三轴小线段路径规划一、项目说明二、具体实现1、速度规划2、小线段插补3、运动学逆解刀轴插补点4、差分处理得到实际的速度和加速度5、加速度滑动平均6、实现的效果如图所示三、Reference写在前面,本文是作为一个练手小项目的总结…...

渗透测试之DNS域名信息探测实验
渗透测试之DNS域名信息探测实验实验目的一、实验原理1.1 域名1.2 .域名的构成1.3 域名的基本类型1.4 域名级别二、实验环境2.1 操作机器三、实验步骤1. 使用sp查询域名信息2. 进行探测实验实验目的 掌握使用nslookup进行DNS域名信息探测的原理和方式了解子域名查询网站 一、实…...

ASE140N04-ASEMI低压MOS管ASE140N04
编辑-Z ASE140N04在TO-220F封装里的静态漏极源导通电阻(RDS(ON))为4mΩ,是一款N沟道低压MOS管。ASE140N04的最大脉冲正向电流ISM为400A,零栅极电压漏极电流(IDSS)为1uA,其工作时耐温度范围为-55~175摄氏度。ASE140N04…...

Qt——QLineEdit
QLineEdit是一个单行文本编辑控件。 使用者可以通过很多函数,输入和编辑单行文本,比如撤销、恢复、剪切、粘贴以及拖放等。 通过改变QLineEdit的 echoMode() ,可以设置其属性,比如以密码的形式输入。 文本的长度可以由 maxLength(…...

前端-HTML-zxst
HTML HTML是超文本标记语言(HyperText Mark-up Language) CSS是层叠样式表(Cascading Style Sheets) JS,即JavaScript是一种具有函数优先的轻量级,解释型或即时编译型的编程语言 <!--doctype标签声明…...

终极方案,清理 docker 占用磁盘过大问题, 亲测有效!
背景 在笔者的工作测试环境中,使用过程中突然出现根磁盘快吃满了(docker也是使用的根池盘的/var/lib/docker), wtf ? 服务用不了? 当然网上找到了一些常规的清楚docker 日志文件 但是通过df -hT 查看到over…...

puzzle(1321)时间旅人
时间旅人 最强大脑同款项目。 每个指针会带动周围2圈指针一起带动,内圈8个旋转180度,外圈16个旋转90度,全部调整为朝上则胜利。 问题本质: 很明显,问题本质就是求每个格子的点击次数,最少为…...

活动预告 | 2023 Meet TVM 开年首聚,上海我们来啦!
内容一览:从去年 12 月延期至今的 TVM 线下聚会终于来了!首站地点我们选在了上海,并邀请到了 4 位讲师结合自己的工作实践,分享 TVM 相关的开发经验,期待与大家线下相聚~ 关键词:2023 Meet TVM 线下活动 自…...

CoreIDRAW 软件的强大功能及适用性
1.1 绘图功能CoreIDRAW 软件是一种特殊的设计软件和图形绘制软件,使用方便、功能强大,在网页效果、商业插画设计、海报广告设计、平面设计等各类行业中都得到广泛的应用,在服装设计行业中,也逐渐地投入使用。由于纺织服装行业在设…...

JavaScript Window History
在 Web 开发中,JavaScript Window History(浏览器窗口历史记录)是一个非常有用的对象,它提供了一个接口来与浏览器历史记录进行交互。JavaScript Window History 对象允许您访问当前会话的历史记录,以及在会话历史记录…...

2023年人力资源管理师报名和培训费用是多少
2023年考人力资源管理师各个地区的收费标准不同,报名费用在几百元左右,培训费上千,具体看各地区人力资源管理师考试报名要求。 12023人力资源管理师考试费用 人力资源管理师考试分为四个等级,各级别费用是不同的,一般来…...

2023-2-23 刷题情况
灌溉花园的最少水龙头数目 题目描述 在 x 轴上有一个一维的花园。花园长度为 n,从点 0 开始,到点 n 结束。 花园里总共有 n 1 个水龙头,分别位于 [0, 1, …, n] 。 给你一个整数 n 和一个长度为 n 1 的整数数组 ranges ,其中…...

数据归档,存储的完美储备军
数据爆炸性增长的同时,存储成为了大家首要担心的问题大家都希望自家数据保存20年、50年后仍完好无损但是,N年后的数据量已达到一个无法预测的峰值如此大量的数据在保存时极可能存在丢失、损坏等问题这时需要提前对数据进行“备份”、“归档”备份是对数据…...

ES6-11、基本全部语法
一,变量声明:let声明变量:1.变量不可重复声明,let star 罗志祥 let star 小猪结果报错2.块级作用域,{ let girl 周扬青 }在大括号内的都属于作用域内3.不存在变量提升4.不影响作用域链const声明常量:const SCHOOL …...

Spring Boot整合Thymeleaf和FreeMarker模板
虽然目前市场上多数的开发模式采用前后端分离的技术,视图层的技术在小一些的项目中还是非常有用的,所以一直也占有一席之地,如spring官方的spring.io等网站就是使用视图层技术实现的。 目前Spring Boot支持的较好的两个视图层模板引擎是Thyme…...

SQL的四种连接-左外连接、右外连接、内连接、全连接
SQL的四种连接-左外连接、右外连接、内连接、全连接 内连接inner join…on… / join…on… 展现出来的是共同的数据 select m.Province,S.Name from member m inner join ShippingArea s on m.Provinces.ShippingAreaID; 相当于:select m.Province,S.Name from m…...

“点工”的觉悟,5年时间从7K到24K的转变,我的测试道路历程~
2015年7月我从一个90%以上的人都不知道的二本院校毕业(新媒体专业),凭借自学的软件测试(点点点)在北京找到了一份月薪7000的工作,在当时其实还算不错,毕竟我的学校起点比较差,跟大部…...

【Web安全-MSF记录篇章一】
文章目录前言msfvenom生成远控木马基本系统命令webcam 摄像头命令常用的信息收集脚本注册表设置nc后门开启 rdp&添加用户获取哈希mimikatz抓取密码前言 最近打站,可以感觉到之前的学的渗透知识忘记很多。。。。。多用多看多练,简单回顾一下 msfven…...

配置Flutter开发环境
一、在Windows上搭建Flutter开发环境 1、去flutter官网下载其最新可用的安装包,下载地址:https://flutter.dev/docs/development/tools/sdk/releases 。 注意,Flutter的渠道版本一直在不断的更新,请以Flutter官网为准。 另外&…...

23年六级缓考
【【六级674】3月六级规划+许愿成功的小伙伴记得来还愿啦!!(四六级延期考2周冲刺计划)】https://www.bilibili.com/video/BV1nx4y1w7fz?vd_source=5475f4f6010a81c8e6d4789af8e1a20f 作文...

低代码选型,论协同开发的重要性
Git是一款用于分布式版本控制的免费开源软件: 它可以跟踪到所有文件集中任意的变更,通常用于在软件开发期间,协调配合程序员之间的代码程序开发工作。 Git 最初诞生的原因源于Linux 内核的开发,2005年Linus Torvalds 编写出了Git。其他内核开…...

【第二十二部分】游标
【第二十二部分】游标 文章目录【第二十二部分】游标22. 游标22.1 游标的定义22.2 游标的使用22.3 游标优缺点总结22. 游标 22.1 游标的定义 当我们筛选条件的时候,虽然可以使用WHERE或者HAVING去选出我们想要的字段,但是去无法将一大块的结果集进行遍…...

【面试题】2023高频前端面试题20题
大厂面试题分享 面试题库前端后端面试题库 (面试必备) 推荐:★★★★★地址:前端面试题库1. 简述 TCP 连接的过程(淘系)参考答案:TCP 协议通过三次握手建立可靠的点对点连接,具体过程…...

Spring解决循环依赖为什么需要三级缓存?
前言什么是循环依赖呢?我们抛开Spring这个框架来聊下什么是循环依赖,循环依赖可能在我们平时的开发过程中是属于比较常见的。Spring容器最大的功能就是对bean的生命周期进行管理,每个bean在创建的过程中,需要得到一个完整的bean需…...

Android源码分析 - 回顾Activity启动流程
跟踪Activity启动流程 基于 Android8.0 源码跟踪 Android8/9大同小异,但Android10对activity的管理解耦交给了ATMS。 跟踪目的:ams到底在哪里发起activity的启动的?以及resume等生命周期到底是谁发起的?onResume()之后是哪里发起…...

PDMS二次开发(一)——PML类型程序类型与概念
目录前言一、PML类型与概念基础知识变量函数小例子注释PML表达式条件判断语句循环skip和break窗口程序在PDMS菜单栏中添加程序窗口自动定位PML常见控件前言 PDMS二次开发需要.net 有自带的PML语言和C# .net一般通常泛指的是C#语言 模型数据借助.NET的接口可以转换成数据库中的…...

一文揭晓:手机号码归属地api的作用是什么?
随着手机的普及,手机号码的归属地已经成为很多网站和App中调用的重要数据资源。而手机号码归属地API可以帮助开发者快速获取手机号码归属地信息。目前,这种API已经被广泛地使用,用于各种不同的应用场景。这对于用户及开发者来说是非常重要的&…...

电容的结构分类介质封装及应用场景总结
🏡《总目录》 目录 1,概述2,结构分类2.1,固定电容器2.2,可变电容器3,介质分类3.1,无机介质电容器3.2,有机介质电容器3.3,电解电容器3.4,气体介质电容器4,封装分类4.1,直插电容器4.2,贴片电容器5,总结1,概述 电容器作为一种储能元件,在电路中和电阻一样非常常用…...

数据结构初阶——时间复杂度与空间复杂度
时间复杂度与空间复杂度1. 算法效率1.1 如何衡量一个算法的好坏1.2算法的复杂度2.时间复杂度2.1 时间复杂度的概念2.2 大O的渐进表示法2.3常见时间复杂度计算举例实列1:实列2:实列3:实列4:实列5:实列6:实列…...

深度学习之“制作自定义数据”--torch.utils.data.DataLoader重写构造方法。
深度学习之“制作自定义数据”–torch.utils.data.DataLoader重写构造方法。 前言: 本文讲述重写torch.utils.data.DataLoader类的构造方法,对自定义图片制作类似MNIST数据集格式(image, label),用于自己的Pytorc…...