Analytical kinematics

For some applications it is useful to retrieve more than one solution for an inverse kinematics request, this can be achieved through analytic solvers. For a general 6-DoF robot (industrial arm with 6 revolute joints), up to eight possible solutions can be found. (More, if we include joint positions ± 360 degrees).

The resulting eight solutions have an order. That means that if you call IK on two subsequent frames and compare the 8 configurations of the first frame with the 8 configurations of the second frame at their respective indices, then these configurations are “close” to one another. For this reason, for certain use cases, e.g. for cartesian path planning, it makes sense to keep the order of solutions. This can be achieved by setting the optional parameter keep_order to True. The configurations that are in collision or outside joint boundaries are then not removed from the list of solutions, they are set to None.

We currently support the following robotic arms:

  • Offset-Wrist manipulators:
    • UR3, UR3e

    • UR5, UR5e

    • UR10, UR10e

  • Spherical-Wrist manipulators:
    • Staubli_TX260L

    • ABB_IRB4600_40_255

Inverse kinematics

The inverse kinematics function calculates the joint states required for the end-effector to reach a certain target pose.

Here is an example of using AnalyticalInverseKinematics:

from compas.geometry import Frame
from compas_fab.robots import RobotLibrary
from compas_fab.backends import AnalyticalInverseKinematics

ik = AnalyticalInverseKinematics()
robot = RobotLibrary.ur5()

frame_WCF = Frame((0.381, 0.093, 0.382), (0.371, -0.292, -0.882), (0.113, 0.956, -0.269))

# The solver must be one of the supported PLANNER_BACKENDS
for joint_positions, joint_names in ik.inverse_kinematics(robot, frame_WCF, options={'solver': 'ur5'}):
    print(joint_positions)

The above solutions, could however be in collision. In order to check for collisions, we have to use a client. See below the example for using the PyBulletClient

from compas.geometry import Frame
from compas_fab.backends import AnalyticalInverseKinematics
from compas_fab.backends import PyBulletClient

frame_WCF = Frame((0.381, 0.093, 0.382), (0.371, -0.292, -0.882), (0.113, 0.956, -0.269))


with PyBulletClient(connection_type='direct') as client:
    robot = client.load_ur5(load_geometry=True)

    ik = AnalyticalInverseKinematics(client)
    # set a new IK function
    client.inverse_kinematics = ik.inverse_kinematics

    options = {"solver": "ur5", "check_collision": True, "keep_order": True}

    for solution in robot.iter_inverse_kinematics(frame_WCF, options=options):
        print(solution)

Or, alternatively, use the AnalyticalPyBulletClient:

from compas.geometry import Frame
from compas_fab.backends import AnalyticalPyBulletClient


with AnalyticalPyBulletClient(connection_type='direct') as client:
    robot = client.load_ur5(load_geometry=True)

    frame_WCF = Frame((0.381, 0.093, 0.382), (0.371, -0.292, -0.882), (0.113, 0.956, -0.269))

    options = {"solver": "ur5", "check_collision": True, "keep_order": True}
    # 8 solutions, `None` are those in collision
    for config in robot.iter_inverse_kinematics(frame_WCF, options=options):
        print(config)

We can also use the AnalyticalPyBulletClient to calculate a cartesian path:

import matplotlib.pyplot as plt
from compas.geometry import Frame

from compas_fab.backends import AnalyticalPyBulletClient
from compas_fab.robots import FrameWaypoints

frames_WCF = [
    Frame((0.407, 0.073, 0.320), (0.922, 0.000, 0.388), (0.113, 0.956, -0.269)),
    Frame((0.404, 0.057, 0.324), (0.919, 0.000, 0.394), (0.090, 0.974, -0.210)),
    Frame((0.390, 0.064, 0.315), (0.891, 0.000, 0.454), (0.116, 0.967, -0.228)),
    Frame((0.388, 0.079, 0.309), (0.881, 0.000, 0.473), (0.149, 0.949, -0.278)),
    Frame((0.376, 0.087, 0.299), (0.850, 0.000, 0.528), (0.184, 0.937, -0.296)),
]

with AnalyticalPyBulletClient(connection_type="direct") as client:
    robot = client.load_ur5(load_geometry=True)

    options = {"solver": "ur5", "check_collision": True}
    start_configuration = list(robot.iter_inverse_kinematics(frames_WCF[0], options=options))[-1]
    waypoints = FrameWaypoints(frames_WCF)
    trajectory = robot.plan_cartesian_motion(waypoints, start_configuration=start_configuration, options=options)
    assert trajectory.fraction == 1.0

    j = [c.joint_values for c in trajectory.points]
    plt.plot(j)
    plt.show()