dxf文件在2D图形中使用广泛,将图形文件自动转换为机械手可识别的轨迹代码是机械手全自动化中一个软件衔接节点。理想的轨迹自动化转换程序可以在电脑里面直接虚拟仿真生成机械手轨迹,简化现场人员机械手示教流程,在效率和远程支持上有着实际的应用前景。
但是类似dxf格式的图形文件无法直接被机械手使用,原因是:一 存在格式解析问题;二 dxf文件需要针对不同应用进行一些参数调整和轨迹增删后才具备实用价值。开发一套能够将CAD图形文件转换为库卡机械手可识别的src格式文本的软件是有其实际的实用性。本文讨论借用现有的FreeCAD进行显示,通过编写插件解析的方式来实现dxf文件转库卡机械手src。
首先要了解dxf文件的特点:
dxf文件是以entity为单位的基本形状的组合体,较简单的图形应用中则主要以line和circle这样的基本单元,其中的line只有起点和终点,circle则由中心点,半径,起始和结束角组成。理想的dxf文件应该是人眼观察到的每个单元头尾相连构成一个完整的2D/3D图形,但是实际中图形文件可能隐含非常短的小线段或很小的面积(作图人员的随意)、各个单元的数据也可能没有按照头尾相连的顺序(里面的元素是按照作图人员画图顺序存放的),对于要求精度不高且对机械手运行速度有要求的应用场景,机械手轨迹无需考虑这些小段和面积(过多的细线段会导致更多的点发给机械手,导致机械手运行速度降低),需要算法对dxf文件的entity进行过滤、排序才可用于后续分析。
3D的stp文件则存在一个在3D曲面上选取线的问题,由于3d曲面也是由多个面组成,对面(face)的过滤规则可以考虑选用面的‘脊’来作为轨迹路线,比如限定基于Z轴方向最高的面作为轨迹选取面,可以通过求取该面两边的中值作为轨迹点的值。但是这种假设要求提供的图形必须按照这个规律做好预处理,否则无法正确提取轨迹。
不同应用对生成的轨迹要求不同,我对已知机械手的轨迹分为三类:
1 巡边类 该类轨迹并非是dxf文件展示的图形,而是由dxf的轨迹数据结合特定特征点比如拐角通过算法生成额外的空间补偿数据组合生成(自动插入额外点)。这类需求一般对走位的精度要求不高,但是要求能够自行生成dxf文件提供的额外的插补点位置,同时要求在保证合适精度前提下尽量减少点的数目以确保机械手运行速度。实现对应功能该类算法需要考虑:机械手执行速度、额外的轨迹补偿点。比如下图左上角两个空中轨迹点就是巡边过程中算法在空间插入的额外轨迹点:
2 画图类 一般是机械手在一个面上进行类似画图的移动操作,该类轨迹为以非闭合多段线为主,多数对精度有要求,故需要尽可能还原原始轨迹数据点且不能自行插入额外数据。比如下图就是机械手点的轨迹图:
3 3D 轨迹类 这类作业面为非平面,故需要对3D的图形进行分析和3D的空间轨迹规划。
三类文件的共性需求包括:
中点重新定位 生成的机械手轨迹坐标选取到目标本身是合理的,这样图形坐标系和机械手坐标系才可以进行关联和转换,在提供的dxf文件多数坐标系零点并非构建在目标物体身上,故需要进行偏移转换。
旋转 由于现场目标物体的摆放位置可能具有一定的旋转,生成的轨迹坐标也需要支持旋转功能。
进出点 对于闭合曲线,必须能够定义机械手进入和退出的点位信息。
单独编辑功能 用户应该可以自行修改生成点位的位置信息。
考虑到同时要支持2D和3D且图形编辑需要尽可能的自由性,可以选用开源软件FreeCAD作为主体软件,通过编写FreeCAD的插件实现上述各类图形文件的解析过程。不同于原始dxf信息,我们直接使用被FreeCAD转化过的图形元素。
技能要求:
1 python编程含调试
2 平面和立体几何
3 了解机械手的运动特点
4 了解几种轨迹的路线要求
下面简单介绍一下python如何和FreeCAD的dxf文件进行交互。
objs=App.ActiveDocument.Objects --->获取当前激活的dxf文档的对象数组
Gui.activateWorkbench("DraftWorkbench")-->切换到draft工作环境
Gui.ActiveDocument.ActiveView.setAxisCross(True)-->显示坐标轴
sel = FreeCADGui.Selection.getSelection()--->获取图形元素,注意dxf图像中不同层会放到sel数组不同的元素里。
Draft.move(sel[0],FreeCAD.Vector(offset_x,offset_y,0),copy=False)--->重定位图形到新的坐标位置
Draft.rotate(sel[0], rotation-current_angle, FreeCAD.Vector(0.0, 0.0, 0.0), axis=FreeCAD.Vector(0.0, 0.0, 1.0), copy=False) 旋转图形到指定角度
w0 =sel[0][0].Shape 获取shape
shape是由许多edge组成,比如下面代码段可以提取长度超过1的edges。
for e in w0.Edges:
if e.Length >=1:
useful_edges.append(e)
对提取的edges数组可以进行拼接:
cedges=OpenSCAD2Dgeom.findConnectedEdges(useful_edges,eps=1)
对拼接的cedges进行分析拆出里面具体元素的信息:
for edges in cedges:
tmp_points=[]
last_points=None #used for decide if that segment should be reversed.
for e in edges:
typ=None
center=None
if (str(e.Curve)[1:5] == "Line"):
points = [e.Vertexes[0].Point, e.Vertexes[1].Point]
typ='LIN'
elif (str(e.Curve)[0:6] == "Circle"):
points=e.discretize(Number=3)
typ='CIRC'
center=e.Curve.Center
if last_points!=None:
if self.pointsequals(last_points[-1],points[-1]):
points.reverse()
last_points=points
tmp_points.append((typ,accurate_vertexs(points),center))
if revers ==0:
for i in tmp_points:
i[1].reverse()
tmp_points.reverse()
contour_points=[]
end_point=None
end_angle=None
for cp in tmp_points: #calculate the angle
if cp[0]=='LIN':
v=cp[1][1].sub(cp[1][0])
angle=self.get_relative_angle(v)
contour_points.append((cp[0],cp[1][0],round(angle,2)))
end_point=cp[1][1]
end_angle=angle
#self.place_arrow(cp[1][0],angle)
elif cp[0]=='CIRC':
clockwise=self.Is3PointClockWise(cp[2],cp[1][0],cp[1][1])
v1=cp[1][0].sub(cp[2])
v2=cp[1][1].sub(cp[2])
v3=cp[1][1].sub(cp[1][0])
angle1=v2.getAngle(v1)*180/pi
rangle=self.get_relative_angle(v3)
if clockwise:
angle1=rangle+angle1/2
else:
angle1=rangle-angle1/2
v1=cp[1][1].sub(cp[2])
v2=cp[1][2].sub(cp[2])
v3=cp[1][2].sub(cp[1][1])
angle2=v2.getAngle(v1)*180/pi
rangle=self.get_relative_angle(v3)
if clockwise:
angle2=angle2/2+rangle
else:
angle2=rangle-angle2/2
contour_points.append((cp[0],cp[1][0],round(angle1,2),cp[1][1],round(angle2,2)))
end_point=cp[1][2]
end_angle=round(angle2,2)
#self.place_arrow(cp[1][0],angle1)
#self.place_arrow(cp[1][1],angle2)
self.progressBar.setValue(cedges.index(edges)+1)
contour_points.append(('LIN',end_point,end_angle)) #since there are only one point available, we use 'lin' to implement it.
layout_contours.append(contour_points)
layout_contours=self.sortTracks(layout_contours)
进一步对layout_contours数据分析并转化为机械手的代码(这里以库卡机械手为例):
def get_layout_data(self):
targetData=''
index=0
seq=0
point=layout_contours[0][0][1]
angle=layout_contours[0][0][2]
stmp='LIN {E6POS: X '+str(point.x)+', Y '+str(point.y)+', Z 30.000, A '+str(angle) +', B 0.000, C 0.000, E1 0.000} C_VEL'+';'
targetData=targetData+stmp+'\n'
seq+=1
stmp='LIN {E6POS: X '+str(point.x)+', Y '+str(point.y)+', Z 0.000, A '+str(angle)+', B 0.000, C 0.000, E1 0.000} C_VEL'+';'+str(seq)
targetData=targetData+stmp+'\n'
seq+=1
for tr in layout_contours:
targetData+=';*******************track:'+str(layout_contours.index(tr))+'*************************'+'\n'
for e in tr:
if e[0]=='LIN':
point=e[1]
angle=e[2]
stmp='LIN {E6POS: X '+str(point.x)+', Y '+str(point.y)+', Z 0.000, A '+str(angle)+', B 0.000, C 0.000, E1 0.000} C_VEL'+';'+str(seq)
targetData=targetData+stmp+'\n'
seq+=1
elif e[0]=='CIRC':
point1=e[1]
angle1=e[2]
point2=e[3]
angle2=e[4]
stmp ='CIRC {POS: X ' + str(point1.x) + ', Y ' + str(point1.y) + ', Z 0.0, A ' +str(angle1) + ', B 0.0, C 0.0},'+'{POS: X ' + str(point2.x) + ', Y ' + str(point2.y) + ', Z 0.0, A ' + str(angle2) + ', B 0.0, C 0.0} C_VEL' + ';' + str(seq)
targetData=targetData+stmp+'\n'
seq+=2
targetData=targetData+'END\n'
return targetData
转换过程由于需要输入参数,可以自己设计一个界面:
最后生成的src文件大致如下: