We had a similar problem with our welding CAP. The welding torch is off-center of the flange. Despite what the documentation says, path offset does NOT go by TCP, it only goes by the flange (at least it had when we first started this project). My coworker who did the early development was in contact with UR and they provided him the following URScript code:
MOTION FUNCTIONS
def FrameDef(Origin,p2,p3):
v1=[0,0,0]
v2=[0,0,0]
epsilon =[0,0,0]
RotVector= [0,0,0]
v1[0]=p2[0]-Origin[0]
v1[1]=p2[1]-Origin[1]
v1[2]=p2[2]-Origin[2]
v2[0]=p3[0]-Origin[0]
v2[1]=p3[1]-Origin[1]
v2[2]=p3[2]-Origin[2]
Norm1 = norm(v1)
Norm2 = norm(v2)
if ((Norm1==0) or (Norm2==0)):
popup(“No distance between p2(p3) and the Origin!”, “Frame generation failed!”)
halt
end
v1[0]=v1[0]/Norm1
v1[1]=v1[1]/Norm1
v1[2]=v1[2]/Norm1
v2[0]=v2[0]/Norm2
v2[1]=v2[1]/Norm2
v2[2]=v2[2]/Norm2
if norm(cross_product(v1,v2))<0.1:
popup(“The 3 points are aligned!”, “Frame generation failed!”)
halt
end
Xvers = v1
Zvers = cross_product(v1,v2)
Znorm = norm(Zvers)
Zvers[0] = Zvers[0]/Znorm
Zvers[1] = Zvers[1]/Znorm
Zvers[2] = Zvers[2]/Znorm
Yvers = cross_product(Zvers,Xvers)
Ynorm = norm(Yvers)
Yvers[0] = Yvers[0]/Ynorm
Yvers[1] = Yvers[1]/Ynorm
Yvers[2] = Yvers[2]/Ynorm
eta = 0.5sqrt(norm(Xvers[0]+Yvers[1]+Zvers[2]+1))
epsilon[0] = 0.5sign(Yvers[2]-Zvers[1])sqrt(norm(Xvers[0]-Yvers[1]-Zvers[2]+1))
epsilon[1] = 0.5sign(Zvers[0]-Xvers[2])sqrt(norm(Yvers[1]-Zvers[2]-Xvers[0]+1))
epsilon[2] = 0.5sign(Xvers[1]-Yvers[0])sqrt(norm(Zvers[2]-Xvers[0]-Yvers[1]+1))
if(norm(eta)==1):
RotVector[0]=0
RotVector[1]=0
RotVector[2]=0
else:
theta = 2acos(eta)
RotVector[0] = epsilon[0]*theta/sin(theta/2)
RotVector[1] = epsilon[1]*theta/sin(theta/2)
RotVector[2] = epsilon[2]*theta/sin(theta/2)
end
return p[Origin[0],Origin[1],Origin[2],RotVector[0],RotVector[1],RotVector[2]]
end
def get_motion_frame():
sync()
P0=get_target_tcp_pose_along_path()
TargetSpeed=get_target_tcp_speed_along_path()
while(norm([TargetSpeed[0],TargetSpeed[1],TargetSpeed[2]])<0.0015):
TargetSpeed=get_target_tcp_speed_along_path()
sleep(0.01)
end
Xmotion=pose_add(P0, TargetSpeed)
Ztool=pose_trans(P0,p[0,0,1,0,0,0])
dX=pose_trans(pose_inv(P0),Xmotion)
dZ=pose_trans(pose_inv(P0),Ztool)
dY=cross_product(dZ,dX)
Ymotion=pose_trans(P0,p[dY[0],dY[1],dY[2],0,0,0])
MotionFrame=FrameDef(P0,Xmotion,Ymotion)
return MotionFrame
end
def path_offset_tcp(offset, type):
global tcpOffset = get_motion_frame()
global pathOffset = pose_trans( p[0.0, 0.0, 0.0, tcpOffset[3], tcpOffset[4], tcpOffset[5]],p[offset[0], offset[1],offset[2],offset[3],offset[4],offset[5]])
global pathOffsetRot = pose_trans(p[0.0, 0.0, 0.0, tcpOffset[3], tcpOffset[4], tcpOffset[5]], p[offset[3], offset[4], offset[5], 0.0, 0.0, 0.0])
pathOffset[3] = pathOffsetRot[0]
pathOffset[4] = pathOffsetRot[1]
pathOffset[5] = pathOffsetRot[2]
path_offset_set(pose2list(pathOffset), type)
end
Again, I won’t pretend to know what any of this is really doing, but suffice it to say, if you want to use path_offset with a tool whose TCP is not the flange, write these functions into your URScript and instead call path_offset_tcp(offset, type). You may have to change the type of offset to 1 instead of 2 as well.
If this doesn’t work for you, just get rid of it and I wish you the best of luck!