mithi/hexapod-robot-simulator

View on GitHub
hexapod/ik_solver/ik_solver.py

Summary

Maintainability
B
5 hrs
Test Coverage
# Please look at the discussion of the Inverse Kinematics algorithm
# As detailed in the README of this directory

from copy import deepcopy
import numpy as np
from settings import ASSERTION_ENABLED, ALPHA_MAX_ANGLE
from hexapod.ik_solver.helpers import (
    BODY_ON_GROUND_ALERT_MSG,
    COXIA_ON_GROUND_ALERT_MSG,
    cant_reach_alert_msg,
    body_contact_shoved_on_ground,
    legs_too_short,
    beta_gamma_not_in_range,
    angle_above_limit,
    might_sanity_leg_lengths_check,
    might_sanity_beta_gamma_check,
    might_print_ik,
    might_print_points,
)
from hexapod.points import (
    Vector,
    length,
    add_vectors,
    scalar_multiply,
    vector_from_to,
    get_unit_vector,
    is_triangle,
    project_vector_onto_plane,
    angle_between,
    angle_opposite_of_last_side,
)
from hexapod.ik_solver.shared import (
    update_hexapod_points,
    find_twist_frame,
    compute_twist_wrt_to_world,
)
from hexapod.const import HEXAPOD_POSE


# Please checkout the definition of the
# inverse_kinematics_update function
# as described in hexapod.ik_solver.ik_solver2
poses = deepcopy(HEXAPOD_POSE)


def inverse_kinematics_update(hexapod, ik_parameters):

    tx = ik_parameters["percent_x"] * hexapod.mid
    ty = ik_parameters["percent_y"] * hexapod.side
    tz = ik_parameters["percent_z"] * hexapod.tibia
    rotx, roty, rotz = (
        ik_parameters["rot_x"],
        ik_parameters["rot_y"],
        ik_parameters["rot_z"],
    )

    hexapod.update_stance(ik_parameters["hip_stance"], ik_parameters["leg_stance"])
    hexapod.detach_body_rotate_and_translate(rotx, roty, rotz, tx, ty, tz)

    if body_contact_shoved_on_ground(hexapod):
        raise Exception(BODY_ON_GROUND_ALERT_MSG)

    x_axis = Vector(1, 0, 0)
    legs_up_in_the_air = []

    for i in range(hexapod.LEG_COUNT):
        leg_name = hexapod.legs[i].name
        body_contact = hexapod.body.vertices[i]
        foot_tip = hexapod.legs[i].foot_tip()
        body_to_foot_vector = vector_from_to(body_contact, foot_tip)

        # find the coxia vector which is the vector
        # from body contact point to joint between coxia and femur limb
        projection = project_vector_onto_plane(body_to_foot_vector, hexapod.z_axis)
        unit_coxia_vector = get_unit_vector(projection)
        coxia_vector = scalar_multiply(unit_coxia_vector, hexapod.coxia)

        # coxia point / joint is the point connecting the coxia and tibia limbs
        coxia_point = add_vectors(body_contact, coxia_vector)
        if coxia_point.z < foot_tip.z:
            raise Exception(COXIA_ON_GROUND_ALERT_MSG)

        # *******************
        # 1. Compute p0, p1 and (possible) p3 wrt leg frame
        # *******************
        p0 = Vector(0, 0, 0)
        p1 = Vector(hexapod.coxia, 0, 0)

        # Find p3 aka foot tip (ground contact) with respect to the local leg frame
        rho = angle_between(unit_coxia_vector, body_to_foot_vector)
        body_to_foot_length = length(body_to_foot_vector)
        p3x = body_to_foot_length * np.cos(np.radians(rho))
        p3z = -body_to_foot_length * np.sin(np.radians(rho))
        p3 = Vector(p3x, 0, p3z)

        # *******************
        # 2. Compute p2, beta, gamma and final p3 wrt leg frame
        # *******************

        # These values are needed to compute
        # p2 aka tibia joint (point between femur limb and tibia limb)
        coxia_to_foot_vector2d = vector_from_to(p1, p3)
        d = length(coxia_to_foot_vector2d)

        # If we can form this triangle this means
        # we can probably can reach the target ground contact point
        if is_triangle(hexapod.tibia, hexapod.femur, d):
            # .................................
            # CASE A: a triangle can be formed with
            # coxia to foot vector, hexapod's femur and tibia
            # .................................
            theta = angle_opposite_of_last_side(d, hexapod.femur, hexapod.tibia)
            phi = angle_between(coxia_to_foot_vector2d, x_axis)

            beta = theta - phi  # case 1 or 2
            if p3.z > 0:  # case 3
                beta = theta + phi

            z_ = hexapod.femur * np.sin(np.radians(beta))
            x_ = p1.x + hexapod.femur * np.cos(np.radians(beta))

            p2 = Vector(x_, 0, z_)
            femur_vector = vector_from_to(p1, p2)
            tibia_vector = vector_from_to(p2, p3)
            gamma = 90 - angle_between(femur_vector, tibia_vector)

            if p2.z < p3.z:
                raise Exception(cant_reach_alert_msg(leg_name, "blocking"))
        else:
            # .................................
            # CASE B: It's impossible to reach target ground point
            # .................................
            if d + hexapod.tibia < hexapod.femur:
                raise Exception(cant_reach_alert_msg(leg_name, "femur"))
            if d + hexapod.femur < hexapod.tibia:
                raise Exception(cant_reach_alert_msg(leg_name, "tibia"))

            # Then hexapod.femur + hexapod.tibia < d:
            legs_up_in_the_air.append(leg_name)
            LEGS_TOO_SHORT, alert_msg = legs_too_short(legs_up_in_the_air)
            if LEGS_TOO_SHORT:
                raise Exception(alert_msg)

            femur_tibia_direction = get_unit_vector(coxia_to_foot_vector2d)
            femur_vector = scalar_multiply(femur_tibia_direction, hexapod.femur)
            p2 = add_vectors(p1, femur_vector)
            tibia_vector = scalar_multiply(femur_tibia_direction, hexapod.tibia)
            p3 = add_vectors(p2, tibia_vector)

            # Find beta and gamma
            gamma = 0.0
            leg_x_axis = Vector(1, 0, 0)
            beta = angle_between(leg_x_axis, femur_vector)
            if femur_vector.z < 0:
                beta = -beta

        # Final p1, p2, p3, beta and gamma computed at this point
        not_within_range, alert_msg = beta_gamma_not_in_range(beta, gamma, leg_name)
        if not_within_range:
            raise Exception(alert_msg)

        # *******************
        # 3. Compute alpha and twist_frame
        # Find frame used to twist the leg frame wrt to hexapod's body contact point's x axis
        # *******************
        alpha, twist_frame = find_twist_frame(hexapod, unit_coxia_vector)
        alpha = compute_twist_wrt_to_world(alpha, hexapod.body.COXIA_AXES[i])
        alpha_limit, alert_msg = angle_above_limit(
            alpha, ALPHA_MAX_ANGLE, leg_name, "(alpha/coxia)"
        )

        if alpha_limit:
            raise Exception(alert_msg)

        # *******************
        # 4. Update hexapod points and finally update the pose
        # *******************
        points = [p0, p1, p2, p3]
        might_print_points(points, leg_name)

        # Convert points from local leg coordinate frame to world coordinate frame
        for point in points:
            point.update_point_wrt(twist_frame)
            if ASSERTION_ENABLED:
                assert hexapod.body_rotation_frame is not None, "No rotation frame!"
            point.update_point_wrt(hexapod.body_rotation_frame)
            point.move_xyz(body_contact.x, body_contact.y, body_contact.z)

        might_sanity_leg_lengths_check(hexapod, leg_name, points)
        might_sanity_beta_gamma_check(beta, gamma, leg_name, points)

        # Update hexapod's points to what we computed
        update_hexapod_points(hexapod, i, points)

        poses[i]["coxia"] = alpha
        poses[i]["femur"] = beta
        poses[i]["tibia"] = gamma

    might_print_ik(poses, ik_parameters, hexapod)
    return poses, hexapod