Forward Kinematics
Denavit-Hartenberg parameters provide a standard way to map coordinate frames between robot links. Only 4 parameters (theta, d, a, alpha) are needed to describe the transformation from one joint to the next.
DH Params:
1# Denavit-Hartenberg (DH) Parameters2# 4 parameters describe the transformation from link i-1 to link i.34class DH_Param:5 def __init__(self, theta, d, a, alpha):6 self.theta = theta # Joint angle (rotation about Z)7 self.d = d # Link offset (translation along Z)8 self.a = a # Link length (translation along X)9 self.alpha = alpha # Link twist (rotation about X)
The 4 DH parameters are plugged into a standard 4x4 homogenous transformation matrix. This single matrix encapsulates all the necessary rotations and translations to move from link i-1 to link i.
DH Matrix:
1# Constructing the DH Transformation Matrix2import numpy as np34def dh_matrix(theta, d, a, alpha):5 """Returns 4x4 homogenous transform matrix."""6 c_t, s_t = np.cos(theta), np.sin(theta)7 c_a, s_a = np.cos(alpha), np.sin(alpha)89 return np.array([10 [c_t, -s_t*c_a, s_t*s_a, a*c_t],11 [s_t, c_t*c_a, -c_t*s_a, a*s_t],12 [0, s_a, c_a, d],13 [0, 0, 0, 1]14 ])
To find the position of any link in the world space, we multiply the local DH matrices sequentially from the base. This process is called Forward Kinematics (FK).
Matrix Multiplication:
1# Forward Kinematics Transform Concatenation2def calculate_forward_kinematics(dh_params, joint_angles):3 """Computes poses for all links in the chain."""4 T_curr = np.eye(4)5 poses = [T_curr]67 for i, p in enumerate(dh_params):8 # Update theta with current joint angle9 theta_i = p.theta + joint_angles[i]1011 # Get local transform for this link12 T_local = dh_matrix(theta_i, p.d, p.a, p.alpha)1314 # Multiply with accumulated transform15 T_curr = np.dot(T_curr, T_local)16 poses.append(T_curr)1718 return poses
In the Denavit-Hartenberg convention, the Z-axis of the local coordinate frame is always aligned with the axis of rotation (or translation for prismatic joints). Extracting the Z-column from the transform matrix gives the axis direction in world space.
Axis Extraction:
1# Visualizing Joint Axes2def get_joint_axis(T_matrix):3 """Extract the Z-axis vector from the transform matrix."""4 # In DH convention, the Z-axis is the axis of rotation5 # It is stored in the 3rd column of the rotation matrix (index 2)6 z_dir = T_matrix[0:3, 2]7 pos = T_matrix[0:3, 3]8 return pos, z_dir
The Tool Center Point (TCP) is the functional tip of the robot (e.g., the nozzle of an extruder or the jaws of a gripper). It is defined as a fixed offset from the last joint's coordinate frame.
TCP Offset:
1# TCP (Tool Center Point) Configuration2# The end effector has its own local offset from the final joint.34def get_tcp_pose(final_joint_matrix, tcp_offset_vector):5 """Calculate the pose of the tool center point."""6 import numpy as np78 # Create translation matrix for the TCP offset9 T_tcp = np.eye(4)10 T_tcp[0:3, 3] = tcp_offset_vector1112 # Final pose is joint matrix multiplied by tool offset matrix13 return np.dot(final_joint_matrix, T_tcp)
By sweeping through all possible joint angle combinations within their limits, we can map out the entire reachable workspace of the robot arm. This is a brute-force application of Forward Kinematics.
Workspace Generation:
1# Configuration Space Sweep2def generate_workspace(kinematics_fn, joint_limits, resolution):3 """Samples the reachable workspace of the robot."""4 workspace_points = []56 # Nested loops sweep through all possible joint angles7 for j1 in range(joint_limits[0].min, joint_limits[0].max, resolution):8 for j2 in range(joint_limits[1].min, joint_limits[1].max, resolution):9 # Calculate forward kinematics for this configuration10 tcp_pose = kinematics_fn([j1, j2])11 workspace_points.append(tcp_pose.position)1213 return workspace_points
In a dynamic simulation, joint angles are updated continuously over time. The forward kinematics algorithm runs every frame, recomputing the entire pose chain to animate the robot smoothly.
Control Loop:
1# Real-time FK Control Loop2def update_robot(time_t):3 """Animate joint angles over time."""4 import math56 # Sine wave driven joints7 j1 = math.sin(time_t * 1.5) * math.pi/28 j2 = math.cos(time_t * 2.0) * math.pi/39 j3 = math.sin(time_t * 0.5) * math.pi/41011 # Compute FK and update renderer12 poses = calculate_forward_kinematics(dh_params, [j1, j2, j3])13 render_robot(poses)