!apt update -y
!apt install ffmpeg -y
import numpy as np
import sympy
import matplotlib.pyplot as plt
from matplotlib.animation import FFMpegWriter
from matplotlib.patches import Circle
from numpy.linalg import solve
from numpy.linalg import inv
# Defines our kinematic tree
class KinematicTreeNode:
    def __init__(self): #Constructor
        self.translation = None
        self.rotation = None
        self.child = None
# Define our rotation matrix
def RotZ(theta):
    c = np.cos(theta)
    s = np.sin(theta)
    return np.array([[c, -s], [s, c]])
# FORWARD KINEMATICS
# Function that returns ALL joint positions relative to the input:
def GetAllJoints(X):
    if X.child == None: # This is the last node in this branch of the tree
        return X.translation
    else:
        returned_joints = GetAllJoints(X.child) # Calls itself to go to the next child
        temp, translate = np.meshgrid(returned_joints[0,:], X.translation)
        transformed_joints = np.matmul(RotZ(X.rotation),returned_joints)+translate
        
        list_of_joints = np.hstack((X.translation, transformed_joints))
        return list_of_joints
# Build the tree
base = KinematicTreeNode()
L1 = KinematicTreeNode()
L2 = KinematicTreeNode()
L3 = KinematicTreeNode()
base.translation = np.array([[0],[0]], dtype=float) # translation to the joint
base.rotation = sympy.pi # rotation of the joint
L1.translation = np.array([[1],[0]], dtype=float)
L1.rotation = -sympy.pi/2
L2.translation = np.array([[1],[0]], dtype=float)
L2.rotation = -sympy.pi/2
L3.translation = np.array([[1],[0]], dtype=float)
L3.rotation = 0
# Loop for Inverse Kinematics with Newton-Raphson
base.child = L1
L1.child = L2
L2.child = L3
L3.child = None
# Step 1: Start with an initial guess
theta_current = np.array([base.rotation, L1.rotation, L2.rotation], dtype=float) 
theta_temp= theta_current
for x_iteration in range(1, 201):
    # Inverse Kinematics:
    # Define a desired position
    x_des = x_iteration/100
    y_des = 1
    theta_des = 0
    done = False
    # Newton-Raphson Search
    # Define our desired END EFFECTOR POSITION
    desired_position = np.array([x_des,y_des,theta_des], dtype=float)
    # Small Epsilon for Finite Difference:
    epsilon = 0.001
    while not done:
        # Step 2: Find f(theta)
        base.rotation = theta_current[0]
        L1.rotation = theta_current[1]
        L2.rotation = theta_current[2]
        mainTheta= base.rotation + L1.rotation + L2.rotation
        joint_locations = GetAllJoints(base)
        end_effector = np.array([ joint_locations[0,-1], joint_locations[1,-1], mainTheta ])
        # Step 3: Find the Jacobian of f w.r.t. theta
        jac = np.array([[0., 0., 0.],[0., 0., 0.],[0., 0., 0.]], dtype=float)
        for j in range(0, 3):
            for k in range(0, 3):
                delta_theta = np.array([[0.],[0.],[0.]])
                delta_theta[k,0] = epsilon
                
                base.rotation = theta_current[0] + delta_theta[0,0]
                L1.rotation = theta_current[1] + delta_theta[1,0]
                L2.rotation = theta_current[2] + delta_theta[2,0]
                mainTheta= base.rotation + L1.rotation + L2.rotation
                joint_locations = GetAllJoints(base)
                delta_end_effector = np.array([ joint_locations[0,-1], joint_locations[1,-1], mainTheta ])
                partial_der = (delta_end_effector - end_effector) / epsilon
                
                jac[j,k] = partial_der[j]
        # Step 4: Find Step Length
        delta_theta_step = np.matmul(inv(jac), (desired_position - end_effector))
        
        for x in range(1, 101):
            hold = theta_current + ((delta_theta_step/100) * x)
            theta_temp= np.vstack(( theta_temp , hold) )
        # Theta current:
        theta_current= theta_current + delta_theta_step
        # Terminating Condition:
        if np.linalg.norm(delta_theta_step) < epsilon:
            done = True
theta= theta_temp
#Define Joint Locations:
x_Base = np.zeros(len(theta))
y_Base = np.zeros(len(theta))
x_L1 = np.zeros(len(theta))
y_L1 = np.zeros(len(theta))
x_L2 = np.zeros(len(theta))
y_L2 = np.zeros(len(theta))
x_L3 = np.zeros(len(theta))
y_L3 = np.zeros(len(theta))
for p in range(0, len(theta)): 
    base.rotation = theta[p, 0]
    L1.rotation =  theta[p, 1]
    L2.rotation =  theta[p, 2]
    joint_locations = GetAllJoints(base)
    x_Base[p] = joint_locations[0,0]
    y_Base[p] = joint_locations[1,0]
    x_L1[p] = joint_locations[0,1]
    y_L1[p] = joint_locations[1,1]
    x_L2[p] = joint_locations[0,2]
    y_L2[p] = joint_locations[1,2]
    x_L3[p] = joint_locations[0,3]
    y_L3[p] = joint_locations[1,3]
#Define Time and Theta
dt = 0.001
sim_time = 5
t_vec = np.arange(0, sim_time, dt)
sim_length = len(t_vec)
#Setting Up Animation
fig, ax = plt.subplots()
p1, = ax.plot([], [], color='black')
ax.axis('equal')
ax.set_xlim([-4, 4])
ax.set_xlabel('X')  # X Label
ax.set_ylabel('Y')  # Y Label
ax.set_title('Question 2b Animation')
video_title = "Q2bAnimation"
# Initialize joints:
base_joint = Circle((0, 0), radius=0.1, color='black', zorder=10)
joint_1 = Circle((0, 0), radius=0.1, color='red', zorder=10)
joint_2 = Circle((0, 0), radius=0.1, color='green', zorder=10)
joint_3 = Circle((0, 0), radius=0.1, color='blue', zorder=10)
ax.add_patch(base_joint)
ax.add_patch(joint_1)
ax.add_patch(joint_2)
ax.add_patch(joint_3)
# Setup Animation Writer:
FPS = 30
sample_rate = int(1 / (dt * FPS))
dpi = 300
writerObj = FFMpegWriter(fps=FPS)
theta1 = np.zeros(int (sim_length/sample_rate)+1)
theta2 =  np.zeros(int (sim_length/sample_rate)+1)
theta3 =  np.zeros(int (sim_length/sample_rate)+1)
count=0
# Plot and Create Animation:
with writerObj.saving(fig, video_title+".mp4", dpi):
    for i in range(0, sim_length, sample_rate):
        # Draw Arms:
        align= int( len(theta)/sim_length*i )
        x_data_points = [x_Base[align], x_L1[align], x_L2[align], x_L3[align]]
        y_data_points = [y_Base[align], y_L1[align], y_L2[align], y_L3[align]]
        p1.set_data(x_data_points, y_data_points)
        # Draw Joints:
        joint_1.center = x_L1[align], y_L1[align]
        joint_2.center = x_L2[align], y_L2[align]
        joint_3.center = x_L3[align], y_L3[align]
        # Update Drawing:
        fig.canvas.draw()
        # Grab and Save Frame:
        writerObj.grab_frame()
        # Store Angles
        theta1[count] = theta[align, 0]
        theta2[count] =  theta[align, 1]
        theta3[count] =  theta[align, 2]
        count= count + 1
plt.clf()
count1 = np.arange(0, 5, 5/152)
count2 =  np.arange(0, 5, 5/152)
count3 =  np.arange(0, 5, 5/152)
# Plot angles over time
fig, ax = plt.subplots()
ax.plot(count1, theta1, color='blue')
ax.plot(count2, theta2, color='red')
ax.plot(count3, theta3, color='yellow')
plt.legend(['Theta 1','Theta 2','Theta 3'])
plt.show()
from IPython.display import Video
Video("/work/Q2bAnimation.mp4", embed=True, width=640, height=480)