At this point in the series, you know MoveJ, MoveL and MoveC. But when chaining multiple motion segments, the robot stops at every waypoint — wasting cycle time and causing mechanical vibration. Velocity blending (or motion blending) allows the robot to transition smoothly between segments without stopping.
This is a technique every robot integrator must master — it can reduce 20-40% cycle time compared to stop-and-go.
Why Blending Matters
Stop-and-Go vs Blended Motion
Without blending:
MoveL → STOP → MoveL → STOP → MoveL → STOP
↑ 0.1-0.5s wasted per stop
With blending:
MoveL ——→ blend zone ——→ MoveL ——→ blend zone ——→ MoveL
↑ Smooth transition, no stopping
Benefits of blending:
| Criterion | Without Blending | With Blending |
|---|---|---|
| Cycle time | Baseline | 20-40% reduction |
| Vibration | High (stop-start) | Low |
| Mechanical wear | Faster | Slower |
| Path quality | Stops precisely at each point | "Cuts corners" at waypoints |
The Fundamental Tradeoff
Blending always involves a tradeoff between speed and accuracy:
- Large blend radius — Faster but more corner cutting — larger position deviation
- Small blend radius — More accurate but slower — approaching stop-and-go
- Blend radius = 0 — Complete stop at waypoint
Blend Radius — The Core Concept
Blend radius ($r$) is the radius of the transition zone around a waypoint. When TCP approaches a waypoint within distance $r$, the controller begins transitioning to the next segment instead of reaching the exact waypoint.
import numpy as np
import matplotlib.pyplot as plt
def visualize_blend_radius():
"""Visualize effect of different blend radii."""
waypoints = np.array([
[0.0, 0.0],
[0.3, 0.0],
[0.3, 0.3],
])
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
blend_radii = [0, 0.03, 0.08]
titles = ['r = 0 (Stop)', 'r = 0.03m (Small)', 'r = 0.08m (Large)']
for ax, r, title in zip(axes, blend_radii, titles):
ax.plot(waypoints[:, 0], waypoints[:, 1], 'b--', alpha=0.3)
ax.scatter(waypoints[:, 0], waypoints[:, 1], c='red', s=80, zorder=5)
if r > 0:
circle = plt.Circle(waypoints[1], r, color='orange',
fill=True, alpha=0.2)
ax.add_patch(circle)
n = 50
theta = np.linspace(np.pi, np.pi/2, n)
blend_x = waypoints[1, 0] - r + r * (1 - np.cos(theta - np.pi))
blend_y = waypoints[1, 1] + r * np.sin(theta - np.pi) + r
t = np.linspace(0, 1, 20)
pre_blend = np.column_stack([t * (waypoints[1, 0] - r), np.zeros(20)])
post_blend = np.column_stack([
np.full(20, waypoints[1, 0]),
np.linspace(r, waypoints[2, 1], 20)
])
ax.plot(pre_blend[:, 0], pre_blend[:, 1], 'b-', linewidth=2)
ax.plot(blend_x, blend_y, 'r-', linewidth=2, label='Blend')
ax.plot(post_blend[:, 0], post_blend[:, 1], 'b-', linewidth=2)
else:
ax.plot(waypoints[:, 0], waypoints[:, 1], 'b-', linewidth=2)
ax.set_title(title)
ax.set_xlim(-0.05, 0.4)
ax.set_ylim(-0.05, 0.35)
ax.set_aspect('equal')
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('blend_radius_comparison.png', dpi=150)
plt.show()
visualize_blend_radius()
Parabolic Blending — The Classical Method
Principle
Parabolic blending (linear segments with parabolic blend) is the most common method:
- Keep linear segments between waypoints
- At each waypoint, replace the sharp corner with a parabolic curve
- The parabola ensures continuous velocity (C1 continuity)
Mathematics
In the blend region (duration $t_b$), the trajectory is parabolic:
q(t) = q_i + q̇_i × t + 0.5 × q̈ × t²
Where acceleration $\ddot{q}$ is chosen to transition velocity from the previous segment to the next.
import numpy as np
import matplotlib.pyplot as plt
def parabolic_blend_trajectory(waypoints, durations, blend_time, dt=0.001):
"""
Generate trajectory with parabolic blending.
Args:
waypoints: list of joint positions
durations: time for each segment
blend_time: blend duration at each waypoint
dt: time step
"""
n_segments = len(waypoints) - 1
velocities = []
for i in range(n_segments):
v = (waypoints[i+1] - waypoints[i]) / durations[i]
velocities.append(v)
t_list, q_list, qd_list, qdd_list = [], [], [], []
t_current = 0
for seg in range(n_segments):
tb = blend_time
if seg == 0:
accel = velocities[0] / tb
t_blend = np.arange(0, tb, dt)
q_blend = waypoints[0] + 0.5 * accel * t_blend**2
qd_blend = accel * t_blend
qdd_blend = np.full_like(t_blend, accel)
t_list.extend(t_current + t_blend)
q_list.extend(q_blend)
qd_list.extend(qd_blend)
qdd_list.extend(qdd_blend)
t_current += tb
t_linear_duration = durations[seg] - tb
if seg < n_segments - 1:
t_linear_duration -= tb / 2
if t_linear_duration > 0:
t_linear = np.arange(0, t_linear_duration, dt)
q_at_start = q_list[-1] if q_list else waypoints[seg]
q_linear = q_at_start + velocities[seg] * t_linear
t_list.extend(t_current + t_linear)
q_list.extend(q_linear)
qd_list.extend(np.full_like(t_linear, velocities[seg]))
qdd_list.extend(np.zeros_like(t_linear))
t_current += t_linear_duration
if seg < n_segments - 1:
v_prev = velocities[seg]
v_next = velocities[seg + 1]
accel = (v_next - v_prev) / tb
t_blend = np.arange(0, tb, dt)
q_at_start = q_list[-1]
q_blend = q_at_start + v_prev * t_blend + 0.5 * accel * t_blend**2
t_list.extend(t_current + t_blend)
q_list.extend(q_blend)
qd_list.extend(v_prev + accel * t_blend)
qdd_list.extend(np.full_like(t_blend, accel))
t_current += tb
# Final deceleration
accel = -velocities[-1] / blend_time
t_blend = np.arange(0, blend_time, dt)
q_at_start = q_list[-1]
q_blend = q_at_start + velocities[-1] * t_blend + 0.5 * accel * t_blend**2
t_list.extend(t_current + t_blend)
q_list.extend(q_blend)
qd_list.extend(velocities[-1] + accel * t_blend)
qdd_list.extend(np.full_like(t_blend, accel))
return np.array(t_list), np.array(q_list), np.array(qd_list), np.array(qdd_list)
waypoints = [0, 30, 60, 90]
durations = [1.0, 1.5, 1.0]
blend_time = 0.3
t, q, qd, qdd = parabolic_blend_trajectory(
np.array(waypoints, dtype=float), durations, blend_time
)
fig, axes = plt.subplots(3, 1, figsize=(12, 8), sharex=True)
axes[0].plot(t, q, 'b-', linewidth=2)
axes[0].set_ylabel('Position (deg)')
axes[0].set_title('Parabolic Blend Trajectory')
axes[0].grid(True)
axes[1].plot(t, qd, 'r-', linewidth=2)
axes[1].set_ylabel('Velocity (deg/s)')
axes[1].grid(True)
axes[2].plot(t, qdd, 'g-', linewidth=2)
axes[2].set_ylabel('Acceleration (deg/s²)')
axes[2].set_xlabel('Time (s)')
axes[2].grid(True)
plt.tight_layout()
plt.savefig('parabolic_blend.png', dpi=150)
plt.show()
How Major Robot Brands Implement Blending
Universal Robots — blend_radius
UR uses the r parameter (blend radius) in MoveJ/MoveL/MoveC:
# URScript — palletizing with blending
def palletize_row(positions, blend_r=0.05):
for i, pos in enumerate(positions):
r = 0 if i == len(positions) - 1 else blend_r
movel(pos, a=1.2, v=0.25, r=r)
| Blend radius | Application | Max deviation |
|---|---|---|
| 0 mm | Precision placement | 0 mm |
| 5 mm | Light assembly | ~2.5 mm |
| 20 mm | Pick-and-place | ~10 mm |
| 50 mm | Palletizing | ~25 mm |
| 100 mm | Material handling | ~50 mm |
KUKA — Approximation ($APO)
; KUKA KRL
PTP P1 C_PTP ; Approximate (no stop)
PTP P2 C_PTP
LIN P3 ; No approximation (stop)
$APO.CPTP = 50 ; 50% approximation for PTP
$APO.CDIS = 20 ; 20mm distance approximation for LIN
ABB — Zone ($zone)
! ABB RAPID
MoveL p1, v500, z50, tool0; ! z50 = 50mm zone
MoveL p2, v500, z20, tool0; ! z20 = 20mm zone
MoveL p3, v500, fine, tool0; ! fine = stop at point
| Brand | Parameter | Units |
|---|---|---|
| UR | blend_radius (r) | meters |
| KUKA | $APO.CDIS | mm or % |
| ABB | zone (z) | mm |
| Fanuc | CNT | 0-100 (%) |
Multi-Segment Blending — Chained MoveL with Blends
Practical application: palletizing — robot places items onto a pallet in a grid pattern.
import numpy as np
import matplotlib.pyplot as plt
def generate_pallet_path(origin, rows, cols, spacing_x, spacing_y,
approach_height, blend_radius):
"""Generate palletizing path with blending."""
path = []
blend_flags = []
for row in range(rows):
for col in range(cols):
x = origin[0] + col * spacing_x
y = origin[1] + row * spacing_y
z = origin[2]
path.append([x, y, z + approach_height])
blend_flags.append(blend_radius)
path.append([x, y, z])
blend_flags.append(0) # Stop to place
path.append([x, y, z + approach_height])
blend_flags.append(blend_radius)
blend_flags[-1] = 0
return np.array(path), blend_flags
path, blends = generate_pallet_path(
origin=[0.2, -0.3, 0.05], rows=3, cols=4,
spacing_x=0.08, spacing_y=0.1,
approach_height=0.15, blend_radius=0.03
)
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111, projection='3d')
for i in range(len(path) - 1):
color = 'green' if blends[i] > 0 else 'red'
ax.plot(path[i:i+2, 0], path[i:i+2, 1], path[i:i+2, 2],
color=color, linewidth=1.5, alpha=0.7)
ax.scatter(path[::3, 0], path[::3, 1], path[::3, 2],
c='blue', s=30, label='Waypoints')
ax.set_title('Palletizing Path (green=blended, red=stop)')
ax.legend()
plt.tight_layout()
plt.savefig('pallet_blend_path.png', dpi=150)
plt.show()
Velocity Blending vs Position Blending
| Type | Description | Priority |
|---|---|---|
| Position blending | "Cut corners" in position space | Most common, used for MoveL |
| Velocity blending | Interpolate velocity between segments | Smoother, more complex |
| Time blending | Overlap time between segments | Used in trajectory streaming |
References
- Biagiotti, L., Melchiorri, C. Trajectory Planning for Automatic Machines and Robots. Springer, 2008. DOI: 10.1007/978-3-540-85629-0
- Gasparetto, A. et al. "Path Planning and Trajectory Planning Algorithms." Springer, 2015. DOI: 10.1007/978-3-319-14705-5_1
- Universal Robots. URScript Manual. Documentation
- Lynch, K.M., Park, F.C. Modern Robotics. Cambridge, 2017. Link
Conclusion
Velocity blending is an essential technique for optimizing cycle time:
- Blend radius determines how much "corner cutting" occurs at waypoints
- Parabolic blending is a classical but effective method
- Each robot brand has its own implementation (UR:
r, KUKA:$APO, ABB: zone) - There is always a speed vs accuracy tradeoff
In the next post, we will dive deep into motion profiles — trapezoidal, S-curve and polynomial — to understand how robots generate smooth velocity profiles.
Related Posts
- MoveJ & MoveL Explained — Blend radius is a key parameter of MoveJ/MoveL
- PID Control for Robots — PID controller tracking blended trajectories
- Top 5 Cobot Comparison — Comparing how different brands handle blending