Skip to content

Commit abd78c9

Browse files
authored
Merge pull request AtsushiSakai#95 from daniel-s-ingram/master
Inverse kinematics for n-link arm
2 parents 7b40b62 + d57b406 commit abd78c9

File tree

2 files changed

+164
-0
lines changed

2 files changed

+164
-0
lines changed

RoboticArm/NLinkArm.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""
2+
Class for controlling and plotting an arm with an arbitrary number of links.
3+
4+
Author: Daniel Ingram
5+
"""
6+
7+
import numpy as np
8+
import matplotlib.pyplot as plt
9+
10+
class NLinkArm(object):
11+
def __init__(self, link_lengths, joint_angles, goal):
12+
self.n_links = len(link_lengths)
13+
if self.n_links is not len(joint_angles):
14+
raise ValueError()
15+
16+
self.link_lengths = np.array(link_lengths)
17+
self.joint_angles = np.array(joint_angles)
18+
self.points = [[0, 0] for _ in range(self.n_links+1)]
19+
20+
self.lim = sum(link_lengths)
21+
self.goal = np.array(goal).T
22+
23+
self.fig = plt.figure()
24+
self.fig.canvas.mpl_connect('button_press_event', self.click)
25+
26+
plt.ion()
27+
plt.show()
28+
29+
self.update_points()
30+
31+
def update_joints(self, joint_angles):
32+
self.joint_angles = joint_angles
33+
self.update_points()
34+
35+
def update_points(self):
36+
for i in range(1, self.n_links+1):
37+
self.points[i][0] = self.points[i-1][0] + self.link_lengths[i-1]*np.cos(np.sum(self.joint_angles[:i]))
38+
self.points[i][1] = self.points[i-1][1] + self.link_lengths[i-1]*np.sin(np.sum(self.joint_angles[:i]))
39+
40+
self.end_effector = np.array(self.points[self.n_links]).T
41+
self.plot()
42+
43+
def plot(self):
44+
plt.cla()
45+
46+
for i in range(self.n_links+1):
47+
if i is not self.n_links:
48+
plt.plot([self.points[i][0], self.points[i+1][0]], [self.points[i][1], self.points[i+1][1]], 'r-')
49+
plt.plot(self.points[i][0], self.points[i][1], 'ko')
50+
51+
plt.plot(self.goal[0], self.goal[1], 'gx')
52+
53+
plt.plot([self.end_effector[0], self.goal[0]], [self.end_effector[1], self.goal[1]], 'g--')
54+
55+
plt.xlim([-self.lim, self.lim])
56+
plt.ylim([-self.lim, self.lim])
57+
plt.draw()
58+
plt.pause(0.0001)
59+
60+
def click(self, event):
61+
self.goal = np.array([event.xdata, event.ydata]).T
62+
self.plot()

RoboticArm/n_link_arm_ik.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""
2+
Inverse kinematics for an n-link arm using the Jacobian inverse method
3+
4+
Author: Daniel Ingram (daniel-s-ingram)
5+
"""
6+
import matplotlib.pyplot as plt
7+
import numpy as np
8+
9+
from NLinkArm import NLinkArm
10+
11+
#Simulation parameters
12+
Kp = 2
13+
dt = 0.1
14+
N_LINKS = 10
15+
N_ITERATIONS = 10000
16+
17+
#States
18+
WAIT_FOR_NEW_GOAL = 1
19+
MOVING_TO_GOAL = 2
20+
21+
def n_link_arm_ik():
22+
"""
23+
Creates an arm using the NLinkArm class and uses its inverse kinematics
24+
to move it to the desired position.
25+
"""
26+
link_lengths = [1]*N_LINKS
27+
joint_angles = np.array([0]*N_LINKS)
28+
goal_pos = [N_LINKS, 0]
29+
arm = NLinkArm(link_lengths, joint_angles, goal_pos)
30+
state = WAIT_FOR_NEW_GOAL
31+
solution_found = False
32+
while True:
33+
old_goal = goal_pos
34+
goal_pos = arm.goal
35+
end_effector = arm.end_effector
36+
errors, distance = distance_to_goal(end_effector, goal_pos)
37+
38+
#State machine to allow changing of goal before current goal has been reached
39+
if state is WAIT_FOR_NEW_GOAL:
40+
if distance > 0.1 and not solution_found:
41+
joint_goal_angles, solution_found = inverse_kinematics(link_lengths, joint_angles, goal_pos)
42+
if not solution_found:
43+
print("Solution could not be found.")
44+
state = WAIT_FOR_NEW_GOAL
45+
arm.goal = end_effector
46+
elif solution_found:
47+
state = MOVING_TO_GOAL
48+
elif state is MOVING_TO_GOAL:
49+
if distance > 0.1 and (old_goal is goal_pos):
50+
joint_angles = joint_angles + Kp*ang_diff(joint_goal_angles, joint_angles)*dt
51+
else:
52+
state = WAIT_FOR_NEW_GOAL
53+
solution_found = False
54+
55+
arm.update_joints(joint_angles)
56+
57+
58+
def inverse_kinematics(link_lengths, joint_angles, goal_pos):
59+
"""
60+
Calculates the inverse kinematics using the Jacobian inverse method.
61+
"""
62+
for iteration in range(N_ITERATIONS):
63+
current_pos = forward_kinematics(link_lengths, joint_angles)
64+
errors, distance = distance_to_goal(current_pos, goal_pos)
65+
if distance < 0.1:
66+
print("Solution found in %d iterations." % iteration)
67+
return joint_angles, True
68+
J = jacobian_inverse(link_lengths, joint_angles)
69+
joint_angles = joint_angles + np.matmul(J, errors)
70+
return joint_angles, False
71+
72+
def forward_kinematics(link_lengths, joint_angles):
73+
x = y = 0
74+
for i in range(1, N_LINKS+1):
75+
x += link_lengths[i-1]*np.cos(np.sum(joint_angles[:i]))
76+
y += link_lengths[i-1]*np.sin(np.sum(joint_angles[:i]))
77+
return np.array([x, y]).T
78+
79+
def jacobian_inverse(link_lengths, joint_angles):
80+
J = np.zeros((2, N_LINKS))
81+
for i in range(N_LINKS):
82+
J[0, i] = 0
83+
J[1, i] = 0
84+
for j in range(i, N_LINKS):
85+
J[0, i] -= link_lengths[j]*np.sin(np.sum(joint_angles[:j]))
86+
J[1, i] += link_lengths[j]*np.cos(np.sum(joint_angles[:j]))
87+
88+
return np.linalg.pinv(J)
89+
90+
def distance_to_goal(current_pos, goal_pos):
91+
x_diff = goal_pos[0] - current_pos[0]
92+
y_diff = goal_pos[1] - current_pos[1]
93+
return np.array([x_diff, y_diff]).T, np.math.sqrt(x_diff**2 + y_diff**2)
94+
95+
def ang_diff(theta1, theta2):
96+
"""
97+
Returns the difference between two angles in the range -pi to +pi
98+
"""
99+
return (theta1 - theta2 + np.pi)%(2*np.pi) - np.pi
100+
101+
if __name__ == '__main__':
102+
n_link_arm_ik()

0 commit comments

Comments
 (0)