pinocchio  2.7.0
A fast and flexible implementation of Rigid Body Dynamics algorithms and their analytical derivatives
3) Drag and Drop (aka Inverse kinematics)

Objectives

The main objective of the tutorial is to perform one or several tasks by inverse kinematics, i.e. pseudo inversing a jacobian iteratively until convergence of the task error.

3.0) Technical prerequisites

Robots

We are going to use again the UR5 robot model, however this time mounted as a mobile robot. The source code of the mobile robot is available here. The robot has 3+6 DOF and can move (2 translations + 1 rotation) freely on the plane. Two operation frames have been defined: at the front of the basis, and at the tip of the tool. They are displayed when the robot moves.

Example of how to use the robot is has below.

import pinocchio as pin
from pinocchio.utils import *
from os.path import dirname, join, abspath
from mobilerobot import MobileRobotWrapper
import time
# Starting gepetto server and give a time
import gepetto.corbaserver
gepetto.corbaserver.start_server()
time.sleep(5)
pinocchio_model_dir = join(dirname(dirname(str(abspath(__file__)))), "models")
model_path = join(pinocchio_model_dir, "example-robot-data/robots/ur_description")
mesh_dir = pinocchio_model_dir
urdf_filename = "ur5_gripper.urdf"
urdf_model_path = join(join(model_path, "urdf"), urdf_filename)
robot = MobileRobotWrapper(urdf_model_path, pinocchio_model_dir)
robot.initDisplay(loadModel=True)
NQ, NV = robot.model.nq, robot.model.nv
# create valid random position
q = pin.randomConfiguration(robot.model)
robot.display(q)
IDX_TOOL = 24
IDX_BASIS = 23
pin.framesForwardKinematics(robot.model, robot.data, q)
Mtool = robot.data.oMf[IDX_TOOL]
Mbasis = robot.data.oMf[IDX_BASIS]

3.1) Position the end effector

The first task will be concerned with the end effector. First define a goal placement.

def place(name, M):
robot.viewer.gui.applyConfiguration(name, pin.SE3ToXYZQUAT(M).tolist())
robot.viewer.gui.refresh()
def Rquat(x, y, z, w):
q = pin.Quaternion(x, y, z, w)
q.normalize()
return q.matrix()
Mgoal = pin.SE3(Rquat(.4, .02, -.5, .7), np.array([.2, -.4, .7]))
robot.viewer.gui.addXYZaxis('world/framegoal', [1., 0., 0., 1.], .015, 4)
place('world/framegoal', Mgoal)

The current placement of the tool at configuration q is available as follows:

pin.forwardKinematics(robot.model, robot.data, q) # Compute joint placements
pin.updateFramePlacements(robot.model, robot.data) # Also compute operational frame placements
Mtool = robot.data.oMf[IDX_TOOL] # Get placement from world frame o to frame f oMf

The desired velocity of the tool in tool frame is given by the log:

nu = pin.log(Mtool.inverse() * Mgoal).vector

The tool Jacobian, also in tool frame, is available as follows:

J = pin.computeFrameJacobian(robot.model, robot.data, q, IDX_TOOL)

Pseudoinverse operator is available in numpy.linalg toolbox.

from numpy.linalg import pinv
vq = pinv(J).dot(nu)

The integration of joint velocity vq in configuration q can be done directly (q += vq * dt). More generically, the se3 method integrate can be used:

dt = 0.5
q = pin.integrate(robot.model, q, vq * dt)

Question 1

Implement a for-loop that computes the jacobian and the desired velocity in tool frame, and deduced the joint velocity using the pseudoinverse. At each iteration, also integrate the current velocity and display the robot configuration.

3.2) Position the basis on a line

A line displaying "x=0" is also displayed in Gepetto viewer. Next step is to servo the front of the basis on this line.

Similarly, the distance of the basis frame to the line, with corresponding jacobian, are:

error = Mbasis.translation[0]
J = pin.computeFrameJacobian(robot.model, robot.data, q, IDX_BASIS)[0, :]

Implement a second loop to servo the basis on the line. It becomes interesting when both tasks are performed together. We can do that simply by summing both tasks. For that, the numpy method vstack can be used to make a single error vector stacking the errors of tool and basis tasks, and similarly for the jacobians.

nu = np.vstack([nu1, nu2])
J = np.vstack([J1, J2])

However, it is stronger to move the basis only in the null space of the basis. The null space projector of J1 can be computed using the pseudoinverse. Following the control law performing task 1 and task 2 in the null space of task 1 is:

\(vq_1 = J_1^+ v_1^*\)

\(P_1 = I_9 - J_1^+ J_1\)

\(vq_2 = vq_1 + (J_2 P_1)^+ ( v_2^* - J_2 vq_1)\)

Question 2

Implement two loops: the first one regulate the tool placement alone. When the tool is properly placed, the second regulate the tool placement and the basis position in the null-space of the tool.

utils