Skip to content

Commit 4e5005e

Browse files
committed
Add IK animation and visualize IK for parallel goals and configs
1 parent 403e1a6 commit 4e5005e

File tree

2 files changed

+62
-38
lines changed

2 files changed

+62
-38
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ Compared to other IK libraries, these are the typical advantages over them:
174174
- batched in both goal specification and retries from different starting configurations
175175
- goal orientation in addition to goal position
176176

177+
![IK](https://i.imgur.com/QgaUME9.gif)
178+
177179
See `tests/test_inverse_kinematics.py` for usage, but generally what you need is below:
178180
```python
179181
full_urdf = os.path.join(search_path, urdf)

tests/test_inverse_kinematics.py

+60-38
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
from timeit import default_timer as timer
33

4+
import numpy as np
45
import torch
56

67
import pytorch_kinematics as pk
@@ -55,7 +56,8 @@ def test_jacobian_follower():
5556
goal_pos = goal[..., :3, 3]
5657
goal_rot = pk.matrix_to_euler_angles(goal[..., :3, :3], "XYZ")
5758

58-
ik = pk.PseudoInverseIK(chain, max_iterations=30, num_retries=10,
59+
num_retries = 10
60+
ik = pk.PseudoInverseIK(chain, max_iterations=30, num_retries=num_retries,
5961
joint_limits=lim.T,
6062
early_stopping_any_converged=True,
6163
early_stopping_no_improvement="all",
@@ -85,53 +87,73 @@ def test_jacobian_follower():
8587
p.setAdditionalSearchPath(search_path)
8688

8789
yaw = 90
88-
pitch = -55
89-
dist = 1.
90-
target = goal_pos[0].cpu().numpy()
90+
pitch = -65
91+
# dist = 1.
92+
dist = 2.4
93+
target = np.array([2., 1.5, 0])
9194
p.resetDebugVisualizerCamera(dist, yaw, pitch, target)
9295

93-
p.loadURDF("plane.urdf", [0, 0, 0], useFixedBase=True)
96+
plane_id = p.loadURDF("plane.urdf", [0, 0, 0], useFixedBase=True)
97+
p.changeVisualShape(plane_id, -1, rgbaColor=[0.3, 0.3, 0.3, 1])
98+
99+
# make 1 per retry with positional offsets
100+
robots = []
101+
num_robots = 16
102+
# 4x4 grid position offset
103+
offset = 1.0
94104
m = rob_tf.get_matrix()
95105
pos = m[0, :3, 3]
96106
rot = m[0, :3, :3]
97107
quat = pk.matrix_to_quaternion(rot)
98108
pos = pos.cpu().numpy()
99109
rot = pk.wxyz_to_xyzw(quat).cpu().numpy()
100-
armId = p.loadURDF(urdf, basePosition=pos, baseOrientation=rot, useFixedBase=True)
101110

102-
_make_robot_translucent(armId, alpha=0.6)
103-
# p.resetBasePositionAndOrientation(armId, [0, 0, 0], [0, 0, 0, 1])
104-
# draw goal
105-
# place a translucent sphere at the goal
111+
for i in range(num_robots):
112+
this_offset = np.array([i % 4 * offset, i // 4 * offset, 0])
113+
armId = p.loadURDF(urdf, basePosition=pos + this_offset, baseOrientation=rot, useFixedBase=True)
114+
# _make_robot_translucent(armId, alpha=0.6)
115+
robots.append({"id": armId, "offset": this_offset, "pos": pos})
116+
106117
show_max_num_retries_per_goal = 10
107-
for goal_num in range(M):
108-
# draw cone to indicate pose instead of sphere
109-
visId = p.createVisualShape(p.GEOM_MESH, fileName="meshes/cone.obj", meshScale=1.0,
110-
rgbaColor=[0., 1., 0., 0.5])
111-
# visId = p.createVisualShape(p.GEOM_SPHERE, radius=0.05, rgbaColor=[0., 1., 0., 0.5])
112-
r = goal_rot[goal_num]
113-
xyzw = pk.wxyz_to_xyzw(pk.matrix_to_quaternion(pk.euler_angles_to_matrix(r, "XYZ")))
114-
goalId = p.createMultiBody(baseMass=0, baseVisualShapeIndex=visId,
115-
basePosition=goal_pos[goal_num].cpu().numpy(),
116-
baseOrientation=xyzw.cpu().numpy())
117-
118-
solutions = sol.solutions[goal_num]
119-
# sort based on if they converged
120-
converged = sol.converged[goal_num]
121-
idx = torch.argsort(converged.to(int), descending=True)
122-
solutions = solutions[idx]
123-
124-
# print how many retries converged for this one
125-
print("Goal %d converged %d / %d" % (goal_num, converged.sum(), converged.numel()))
126-
127-
for i, q in enumerate(solutions):
128-
if i > show_max_num_retries_per_goal:
129-
break
130-
for dof in range(q.shape[0]):
131-
p.resetJointState(armId, dof, q[dof])
132-
input("Press enter to continue")
133-
134-
p.removeBody(goalId)
118+
119+
goals = []
120+
# draw cone to indicate pose instead of sphere
121+
visId = p.createVisualShape(p.GEOM_MESH, fileName="meshes/cone.obj", meshScale=1.0,
122+
rgbaColor=[0., 1., 0., 0.5])
123+
for i in range(num_robots):
124+
goals.append(p.createMultiBody(baseMass=0, baseVisualShapeIndex=visId))
125+
126+
try:
127+
import window_recorder
128+
with window_recorder.WindowRecorder(save_dir="."):
129+
# batch over goals with num_robots
130+
for j in range(0, M, num_robots):
131+
this_selection = slice(j, j + num_robots)
132+
r = goal_rot[this_selection]
133+
xyzw = pk.wxyz_to_xyzw(pk.matrix_to_quaternion(pk.euler_angles_to_matrix(r, "XYZ")))
134+
135+
solutions = sol.solutions[this_selection, :, :]
136+
converged = sol.converged[this_selection, :]
137+
138+
# print how many retries converged for this one
139+
print("Goal %d to %d converged %d / %d" % (j, j + num_robots, converged.sum(), converged.numel()))
140+
141+
# outer loop over retries, inner loop over goals (for each robot shown in parallel)
142+
for ii in range(num_retries):
143+
if ii > show_max_num_retries_per_goal:
144+
break
145+
for jj in range(num_robots):
146+
p.resetBasePositionAndOrientation(goals[jj],
147+
goal_pos[j + jj].cpu().numpy() + robots[jj]["offset"],
148+
xyzw[jj].cpu().numpy())
149+
armId = robots[jj]["id"]
150+
q = solutions[jj, ii, :]
151+
for dof in range(q.shape[0]):
152+
p.resetJointState(armId, dof, q[dof])
153+
154+
input("Press enter to continue")
155+
except ImportError:
156+
print("pip install window_recorder")
135157

136158
while True:
137159
p.stepSimulation()

0 commit comments

Comments
 (0)