← Back to Blog
manipulationmovejmovelrobot-armtrajectoryurscript

MoveJ & MoveL: Joint Motion and Linear Motion Explained

Deep dive into MoveJ (joint interpolation) and MoveL (linear interpolation) — the two foundational commands in industrial robot programming.

Nguyễn Anh Tuấn16 tháng 3, 20269 min read
MoveJ & MoveL: Joint Motion and Linear Motion Explained

In the previous post, we compared Joint Space and Cartesian Space planning at a theoretical level. Now it is time for practice — MoveJ and MoveL are the two most fundamental commands that every robot engineer must master. They are the direct implementation of Joint Space and Cartesian Space trajectories.

In this post, we will dive deep into how they work, their parameters, limitations, and demonstrate with URScript and Python.

Robot arm working

MoveJ — Joint Interpolation Motion

How It Works

MoveJ interpolates in Joint Space. The controller computes a separate trajectory for each joint:

  1. Compute angle difference: $\Delta q_i = q_{target,i} - q_{current,i}$
  2. Identify the joint requiring the longest time (the "bottleneck" joint)
  3. Synchronize all joints to finish simultaneously
  4. Apply velocity profile (typically trapezoidal or S-curve)

Result: Each joint moves smoothly, but the TCP path in Cartesian space is curved and unpredictable.

When to Use MoveJ

Scenario Reason
Moving between tasks (approach) Fastest, saves cycle time
Moving to home position No straight line needed
Clearance moves around obstacles Speed is priority
Initial power-on movement Safe, avoids singularity

URScript MoveJ

# URScript — runs on UR controller
# movej(q, a=1.4, v=1.05, t=0, r=0)
# q: joint angles [rad]
# a: acceleration [rad/s²]
# v: velocity [rad/s]
# t: time [s] (0 = auto-compute)
# r: blend radius [m] (0 = stop at point)

# Move to home position
movej([0, -1.5708, 1.5708, -1.5708, -1.5708, 0], a=1.4, v=1.05)

# Slow, safe movement
movej(get_inverse_kin(p[0.4, 0.3, 0.2, 0, 3.14, 0]),
      a=0.5, v=0.3)

# Fast movement with blend radius (no stopping at waypoint)
movej(q_waypoint1, a=1.4, v=1.05, r=0.05)
movej(q_waypoint2, a=1.4, v=1.05, r=0.05)
movej(q_final, a=1.4, v=1.05, r=0)  # Stop at final point

Python with roboticstoolbox

import roboticstoolbox as rtb
import numpy as np
import matplotlib.pyplot as plt

ur5e = rtb.models.UR5()

# Start and target configurations
q_start = np.array([0, -np.pi/3, np.pi/3, -np.pi/2, np.pi/2, 0])
q_target = np.array([np.pi/2, -np.pi/4, np.pi/4, -np.pi/3, np.pi/3, np.pi/4])

# MoveJ: Joint space trajectory (quintic polynomial)
traj = rtb.jtraj(q_start, q_target, t=100)

# Compute TCP positions along trajectory
tcp_positions = []
for q in traj.q:
    T = ur5e.fkine(q)
    tcp_positions.append(T.t)
tcp_positions = np.array(tcp_positions)

# Visualize TCP path
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
ax.plot(tcp_positions[:, 0], tcp_positions[:, 1], tcp_positions[:, 2],
        'b-', linewidth=2, label='MoveJ TCP path')
ax.scatter(*tcp_positions[0], c='green', s=100, zorder=5, label='Start')
ax.scatter(*tcp_positions[-1], c='red', s=100, zorder=5, label='Target')
ax.set_xlabel('X (m)')
ax.set_ylabel('Y (m)')
ax.set_zlabel('Z (m)')
ax.set_title('MoveJ — TCP Path (curved, unpredictable)')
ax.legend()
plt.tight_layout()
plt.savefig('movej_tcp_path.png', dpi=150)
plt.show()

# Plot joint trajectories
fig, axes = plt.subplots(3, 1, figsize=(12, 8), sharex=True)
for i in range(6):
    axes[0].plot(np.degrees(traj.q[:, i]), label=f'Joint {i+1}')
    axes[1].plot(np.degrees(traj.qd[:, i]), label=f'Joint {i+1}')
    axes[2].plot(np.degrees(traj.qdd[:, i]), label=f'Joint {i+1}')

axes[0].set_ylabel('Position (deg)')
axes[0].set_title('MoveJ Joint Profiles')
axes[0].legend(ncol=3)
axes[0].grid(True)
axes[1].set_ylabel('Velocity (deg/s)')
axes[1].grid(True)
axes[2].set_ylabel('Acceleration (deg/s²)')
axes[2].set_xlabel('Time step')
axes[2].grid(True)
plt.tight_layout()
plt.savefig('movej_joint_profiles.png', dpi=150)
plt.show()

MoveL — Linear Interpolation Motion

How It Works

MoveL interpolates in Cartesian space. The controller:

  1. Computes a straight line in Cartesian between current TCP and target
  2. Divides into many small interpolation points
  3. At each point: solves IK to find joint angles
  4. Applies velocity profile on Cartesian distance

Result: TCP follows a precise straight line in 3D, but joints may move in complex patterns.

When to Use MoveL

Scenario Reason
Straight-line welding Need precise weld path
Object approach for grasping Vertical approach, no collision
Flat surface spray painting Uniform distance from surface
Assembly (insertion) Need straight, precise motion
Object retreat Straight up, avoid collision

URScript MoveL

# URScript
# movel(pose, a=1.2, v=0.25, t=0, r=0)
# pose: target TCP pose [x, y, z, rx, ry, rz] (axis-angle)
# a: acceleration [m/s²]
# v: velocity [m/s]
# t: time [s] (0 = auto)
# r: blend radius [m]

# Straight-line move to pick position
movel(p[0.4, 0.3, 0.1, 0, 3.14, 0], a=1.2, v=0.1)

# Slow approach (insertion)
movel(p[0.4, 0.3, 0.05, 0, 3.14, 0], a=0.5, v=0.02)

# Retreat upward — faster
movel(p[0.4, 0.3, 0.2, 0, 3.14, 0], a=1.2, v=0.25)

Python Implementation

import roboticstoolbox as rtb
import numpy as np
import matplotlib.pyplot as plt
from spatialmath import SE3

ur5e = rtb.models.UR5()

# Start configuration
q_start = np.array([0, -np.pi/3, np.pi/3, -np.pi/2, np.pi/2, 0])
T_start = ur5e.fkine(q_start)

# Target pose (Cartesian)
T_target = SE3(0.3, 0.4, 0.2) * SE3.Rx(np.pi)

# MoveL: Cartesian straight line
n_points = 100
tcp_positions = []
joint_traj = []

for i in range(n_points):
    s = i / (n_points - 1)
    T_interp = T_start.interp(T_target, s)
    sol = ur5e.ikine_LM(T_interp, q0=q_start if i == 0 else joint_traj[-1])
    if sol.success:
        joint_traj.append(sol.q)
        T_actual = ur5e.fkine(sol.q)
        tcp_positions.append(T_actual.t)
    else:
        print(f"IK failed at s={s:.2f}")
        break

tcp_positions = np.array(tcp_positions)

fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
ax.plot(tcp_positions[:, 0], tcp_positions[:, 1], tcp_positions[:, 2],
        'r-', linewidth=2, label='MoveL TCP path')
ax.scatter(*tcp_positions[0], c='green', s=100, zorder=5, label='Start')
ax.scatter(*tcp_positions[-1], c='red', s=100, zorder=5, label='Target')
ax.set_xlabel('X (m)')
ax.set_ylabel('Y (m)')
ax.set_zlabel('Z (m)')
ax.set_title('MoveL — TCP Path (straight line guaranteed)')
ax.legend()
plt.tight_layout()
plt.savefig('movel_tcp_path.png', dpi=150)
plt.show()

Robot precision work

MoveJ vs MoveL — Visual Comparison

import roboticstoolbox as rtb
import numpy as np
import matplotlib.pyplot as plt

ur5e = rtb.models.UR5()

q_start = np.array([0, -np.pi/3, np.pi/3, -np.pi/2, np.pi/2, 0])
q_target = np.array([np.pi/2, -np.pi/4, np.pi/4, -np.pi/3, np.pi/3, np.pi/4])

n_points = 100

# === MoveJ ===
movej_tcp = []
for i in range(n_points):
    s = i / (n_points - 1)
    q = q_start + s * (q_target - q_start)
    T = ur5e.fkine(q)
    movej_tcp.append(T.t)
movej_tcp = np.array(movej_tcp)

# === MoveL ===
T_start = ur5e.fkine(q_start)
T_end = ur5e.fkine(q_target)

movel_tcp = []
for i in range(n_points):
    s = i / (n_points - 1)
    pos = T_start.t + s * (T_end.t - T_start.t)
    movel_tcp.append(pos)
movel_tcp = np.array(movel_tcp)

fig = plt.figure(figsize=(14, 6))
ax1 = fig.add_subplot(121, projection='3d')
ax1.plot(movej_tcp[:, 0], movej_tcp[:, 1], movej_tcp[:, 2], 'b-', linewidth=2)
ax1.scatter(*movej_tcp[0], c='green', s=80)
ax1.scatter(*movej_tcp[-1], c='red', s=80)
ax1.set_title('MoveJ — Curved TCP Path')

ax2 = fig.add_subplot(122, projection='3d')
ax2.plot(movel_tcp[:, 0], movel_tcp[:, 1], movel_tcp[:, 2], 'r-', linewidth=2)
ax2.scatter(*movel_tcp[0], c='green', s=80)
ax2.scatter(*movel_tcp[-1], c='red', s=80)
ax2.set_title('MoveL — Straight TCP Path')

plt.tight_layout()
plt.savefig('movej_vs_movel.png', dpi=150)
plt.show()

Key Parameters

Speed and Acceleration

Parameter MoveJ MoveL Units
Velocity 0.1 — 3.14 0.001 — 1.0 rad/s / m/s
Acceleration 0.1 — 40 0.01 — 5.0 rad/s² / m/s²
Typical safe v=1.05, a=1.4 v=0.25, a=1.2
Slow approach v=0.3, a=0.5 v=0.05, a=0.3

Blend Radius

Blend radius (r) allows the robot to not stop at a waypoint but "cut the corner" — saving cycle time.

# URScript — palletizing with blend radius
def palletize():
    movej(q_above_pallet, a=1.4, v=1.05, r=0.05)
    movel(p_place, a=1.2, v=0.1, r=0)
    set_digital_out(0, True)  # Open gripper
    sleep(0.5)
    movel(p_above_place, a=1.2, v=0.25, r=0.02)
    movej(q_above_pick, a=1.4, v=1.05, r=0.05)

When MoveJ Fails

MoveJ can fail when:

  1. Joint limits: Target is outside joint limits
  2. Self-collision: Target configuration causes internal collision
  3. Path through forbidden zone: Start and end are OK, but the joint space path passes through dangerous regions

Solution: Add intermediate waypoints or use a motion planner (RRT/PRM).

When MoveL Fails

MoveL fails more often than MoveJ because:

  1. Singularity: Cartesian path passes through or near a singularity
  2. Workspace boundary: Points on the straight line are outside the workspace
  3. Joint limits: IK solution violates joint limits
  4. IK configuration flip: Robot "jumps" between two IK configurations
# Example: MoveL failure due to singularity
# Wrist singularity: theta5 ≈ 0

q_near_sing = np.array([0, -np.pi/2, 0, 0, 0.01, 0])
T = ur5e.fkine(q_near_sing)

J = ur5e.jacob0(q_near_sing)
m = np.sqrt(np.linalg.det(J @ J.T))
print(f"Manipulability at near-singularity: {m:.6f}")  # Very small!
print(f"Condition number: {np.linalg.cond(J):.1f}")    # Very large!

Practical Demo: Pick-and-Place Workflow

Here is a complete pick-and-place cycle using a combination of MoveJ and MoveL:

# URScript — Complete pick-and-place cycle
def pick_and_place():
    # === PICK PHASE ===
    # 1. MoveJ: Fast move above pick position
    movej(q_above_pick, a=1.4, v=1.05)

    # 2. MoveL: Straight approach down to pick
    movel(p_approach_pick, a=1.2, v=0.1)

    # 3. MoveL: Slow, precise final approach
    movel(p_pick, a=0.5, v=0.02)

    # 4. Grasp object
    set_digital_out(0, False)  # Close gripper
    sleep(0.3)

    # 5. MoveL: Straight retreat up (avoid collision)
    movel(p_approach_pick, a=1.2, v=0.25)

    # === PLACE PHASE ===
    # 6. MoveJ: Fast move above place position
    movej(q_above_place, a=1.4, v=1.05)

    # 7. MoveL: Straight approach down to place
    movel(p_approach_place, a=1.2, v=0.1)

    # 8. MoveL: Slow, precise placement
    movel(p_place, a=0.5, v=0.02)

    # 9. Release object
    set_digital_out(0, True)  # Open gripper
    sleep(0.3)

    # 10. MoveL: Straight retreat up
    movel(p_approach_place, a=1.2, v=0.25)

    # 11. MoveJ: Return to home
    movej(q_home, a=1.4, v=1.05)

Golden Rules for Combining MoveJ/MoveL

  1. MoveJ for free movements (TCP path does not matter)
  2. MoveL for approach and retreat (need straight vertical/horizontal path)
  3. Slow MoveL for precise operations (grasping, placement, assembly)
  4. Blend radius for consecutive MoveJ commands (save cycle time)
  5. Blend radius = 0 for final points (precise stop)

Pick and place operation

References

Conclusion

MoveJ and MoveL are the two foundational commands in robot programming:

In the next post, we will explore MoveC — circular/arc motion for welding, polishing, and screw driving applications.

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
Deep DiveAdvanced Trajectories: Spline, B-Spline và Time-Optimal Planning
splineb-splinetime-optimaltrajectoryPart 7

Advanced Trajectories: Spline, B-Spline và Time-Optimal Planning

Kỹ thuật nâng cao: cubic spline, B-spline interpolation và TOPPRA time-optimal trajectory planning cho robot arm.

1/4/202610 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