4. 五轴样条插补
任务目标
在五轴加工中,刀尖点路径与刀轴方向需同步平滑变化。本案例使用三次均匀B样条曲线对刀尖点位置序列进行插补,同时采用四元数球面线性插值(Slerp)对刀轴方向序列进行平滑过渡,生成连续且无突变的五轴插补路径。
核心实现
import numpy as np
from scipy.interpolate import splprep, splev
def bspline_interpolate(points, num_samples=100, smoothness=0):
"""
对刀尖点序列进行B样条插补
points: (N,3) 原始刀尖点
返回: (num_samples,3) 平滑后的刀尖点路径
"""
points = np.asarray(points, dtype=float)
# 使用参数化B样条插值(s=0表示严格通过所有点)
tck, u = splprep([points[:,0], points[:,1], points[:,2]],
s=smoothness, k=3)
u_new = np.linspace(0, 1, num_samples)
x, y, z = splev(u_new, tck)
return np.column_stack((x, y, z))
def quaternion_slerp(q_start, q_end, t):
"""四元数球面线性插值"""
q_start = q_start / np.linalg.norm(q_start)
q_end = q_end / np.linalg.norm(q_end)
dot = np.clip(np.dot(q_start, q_end), -1.0, 1.0)
if dot < 0: # 确保最短路径
q_end = -q_end
dot = -dot
if dot > 0.9995: # 角度过小时线性插值
result = q_start + t * (q_end - q_start)
return result / np.linalg.norm(result)
theta_0 = np.arccos(dot)
theta = theta_0 * t
sin_theta = np.sin(theta)
sin_theta_0 = np.sin(theta_0)
s0 = np.cos(theta) - dot * sin_theta / sin_theta_0
s1 = sin_theta / sin_theta_0
return s0 * q_start + s1 * q_end
def axis_to_quaternion(axis_vectors):
"""将刀轴方向向量转换为四元数(绕X轴旋转对齐)"""
quats = []
for v in axis_vectors:
v = v / np.linalg.norm(v)
# 将方向向量映射为旋转四元数(绕X轴旋转使Z轴对齐到v)
z_axis = np.array([0, 0, 1.0])
axis = np.cross(z_axis, v)
axis_norm = np.linalg.norm(axis)
if axis_norm < 1e-8:
quats.append(np.array([1.0, 0, 0, 0])) # 无旋转
else:
axis = axis / axis_norm
angle = np.arccos(np.clip(np.dot(z_axis, v), -1, 1))
q = np.append(np.cos(angle/2), np.sin(angle/2) * axis)
quats.append(q)
return np.array(quats)
def generate_five_axis_path(pos_points, axis_vectors, num_samples=100):
"""
五轴样条插补主函数
pos_points: (N,3) 刀尖点序列
axis_vectors: (N,3) 刀轴方向序列
返回: 平滑后的位置路径与刀轴方向
"""
# 1. 位置B样条插补
smooth_pos = bspline_interpolate(pos_points, num_samples)
# 2. 刀轴方向四元数Slerp插补
quats = axis_to_quaternion(axis_vectors)
t_vals = np.linspace(0, 1, num_samples)
smooth_axes = []
# 将参数区间映射到原始点段
n_segments = len(quats) - 1
for t in t_vals:
seg_pos = t * n_segments
idx = min(int(seg_pos), n_segments - 1)
local_t = seg_pos - idx
q_interp = quaternion_slerp(quats[idx], quats[idx+1], local_t)
# 四元数转方向向量
v = np.array([
2*(q_interp[1]*q_interp[3] + q_interp[0]*q_interp[2]),
2*(q_interp[2]*q_interp[3] - q_interp[0]*q_interp[1]),
q_interp[0]**2 - q_interp[1]**2 - q_interp[2]**2 + q_interp[3]**2
])
smooth_axes.append(v / np.linalg.norm(v))
return smooth_pos, np.array(smooth_axes)
# ========== 最小验证示例 ==========
if __name__ == "__main__":
# 输入:5个刀尖点与对应刀轴方向
pos_input = np.array([
[0, 0, 0], [10, 5, 2], [20, 8, 5],
[30, 5, 3], [40, 0, 0]
])
axis_input = np.array([
[0, 0, 1], [0.2, 0.1, 0.98], [0.3, 0.2, 0.93],
[0.2, 0.3, 0.93], [0, 0.5, 0.87]
])
smooth_pos, smooth_axis = generate_five_axis_path(pos_input, axis_input, num_samples=50)
# 验证1:插补点数量正确
assert smooth_pos.shape == (50, 3), "位置插补点数量错误"
assert smooth_axis.shape == (50, 3), "刀轴插补点数量错误"
# 验证2:刀轴方向保持单位长度
norms = np.linalg.norm(smooth_axis, axis=1)
assert np.allclose(norms, 1.0, atol=1e-6), "刀轴方向未归一化"
# 验证3:刀轴方向连续性(相邻点夹角不超过阈值)
max_angle = 0
for i in range(len(smooth_axis)-1):
cos_angle = np.dot(smooth_axis[i], smooth_axis[i+1])
angle = np.degrees(np.arccos(np.clip(cos_angle, -1, 1)))
max_angle = max(max_angle, angle)
print(f"最大相邻刀轴夹角: {max_angle:.4f}°")
assert max_angle < 5.0, "刀轴方向变化过陡"
# 验证4:首末点与输入一致(B样条插值通过所有点)
assert np.allclose(smooth_pos[0], pos_input[0], atol=1e-3), "起点不匹配"
assert np.allclose(smooth_pos[-1], pos_input[-1], atol=1e-3), "终点不匹配"
print("验证通过:五轴样条插补路径生成成功")
print(f"插补点数: {len(smooth_pos)}")
print(f"位置范围: X[{smooth_pos[:,0].min():.1f}, {smooth_pos[:,0].max():.1f}]")
运行与验证
环境依赖:NumPy、SciPy(仅用于B样条插值)。
运行方式:
python five_axis_spline.py
通过条件:程序输出最大相邻刀轴夹角小于5°,且打印“验证通过”即表示插补路径满足平滑性要求。首末位置点与输入一致,证明B样条插值严格通过原始控制点。
关键说明
- 位置插补采用三次B样条(
s=0强制插值通过所有输入点),保证刀尖路径的C²连续性。 - 刀轴方向使用四元数Slerp插值,避免欧拉角插值可能出现的万向锁问题,确保方向过渡自然无突变。
- 验证代码中的最大夹角阈值(5°)可根据实际加工需求调整;若需更平滑的刀轴变化,可增加插补采样点数。