← Back to Blog
manipulationblendingvelocityrobot-armsmooth-motion

Velocity Blending: Smooth Transitions Between Motion Segments

Velocity blending techniques for smooth transitions between motion segments — reduce cycle time and eliminate stop-start jitter.

Nguyễn Anh Tuấn24 tháng 3, 20268 min read
Velocity Blending: Smooth Transitions Between Motion Segments

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.

Smooth robot motion

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:

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:

  1. Keep linear segments between waypoints
  2. At each waypoint, replace the sharp corner with a parabolic curve
  3. 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 (%)

Industrial robot

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

Conclusion

Velocity blending is an essential technique for optimizing cycle time:

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

Related Posts

TutorialIntegration: ROS 2 MoveIt2, URScript và Real Robot Deploy
ros2moveit2urscriptrobot-armdeploymentPart 8

Integration: ROS 2 MoveIt2, URScript và Real Robot Deploy

Tổng hợp toàn bộ series: tích hợp trajectory planning với MoveIt2, URScript và deploy lên robot thật qua ros2_control.

5/4/202611 min read
ISO 10218 thực hành: Risk Assessment cho robot hàn
safetyrobot-armstandards

ISO 10218 thực hành: Risk Assessment cho robot hàn

Hướng dẫn thực hiện risk assessment theo ISO 10218 cho cell robot hàn — từ hazard identification đến safety measures.

28/3/202613 min read
ROS 2 từ A đến Z (Phần 4): ros2_control và Hardware
ros2tutorialrobot-armPart 4

ROS 2 từ A đến Z (Phần 4): ros2_control và Hardware

Kết nối ROS 2 với phần cứng thực — viết hardware interface cho motor driver và đọc encoder với ros2_control framework.

26/3/202611 min read