Пример #1
0
    def ground_clearance(self):
        # returns ground clearance based on current leg positions

        # legs are attached to a top surface of thorax
        # as leg Z in thorax cs is negative when foot is below thorax, ground clearance is assumed to be (max leg Z) - (thorax height)
        THORAX_HEIGHT = m(48)

        z_list = []
        for leg in self.legs.values():
            if not leg.in_swing:  # only legs in stance are considered
                z_list.append(leg.get_node("foot-thorax").z)  # get coordinates of all feet in thorax cs
        if len(z_list) > 0:
            return -1 * max(z_list) - THORAX_HEIGHT
        else:
            return 0  # this generally should not happen, unless hex is flying
Пример #2
0
    def __init__(self, name, parent, mount_descriptor, servo_ids):
        global COXA_LENGTH, FEMUR_LENGTH, TIBIA_LENGTH, TARSUS_LENGTH
        
        self.name = name
        self.parent = parent
        self.mount_descriptor = mount_descriptor

        self.swing_order = 0  # used to keep track of the order in which legs swing. If two legs are ready to swing, priority is given to the one that has been in stance longer

        self.r_was_increasing = False  # indicates whether restrictedness of this leg was increasing before reaching flat plateau

        self.cs = CoordinateSystem(parent.cs, self.name, mount_descriptor.x, mount_descriptor.y, mount_descriptor.z, 0, 0, mount_descriptor.rotz)

        self.segments = dict()
        self.segments["coxa"] = Segment(name=name + "_coxa", servo_id=servo_ids[0],
                                        min_angle=r(-35), neutral_angle=r(0), max_angle=r(35),
                                        length=COXA_LENGTH, parent=self,
                                        leg=self,
                                        offset_in_parent=m(0), mirrored=(mount_descriptor.x < 0), z_rot=True)

        if self.name == "rf":
            self.segments["coxa"].max_angle = r(10)
        elif self.name == "lf":
            self.segments["coxa"].min_angle = r(-10)
        elif self.name == "rr":
            self.segments["coxa"].min_angle = r(-10)
        elif self.name == "lr":
            self.segments["coxa"].max_angle = r(10)

        self.segments["femur"] = Segment(name=name + "_femur", servo_id=servo_ids[1],
                                         min_angle=r(-45), neutral_angle=r(20), max_angle=r(90), length=FEMUR_LENGTH,
                                         parent=self.segments["coxa"], leg=self,
                                         offset_in_parent=COXA_LENGTH)

        self.segments["tibia"] = Segment(name=name + "_tibia", servo_id=servo_ids[2],
                                         min_angle=r(-115), neutral_angle=r(20), max_angle=r(20), length=TIBIA_LENGTH,
                                         parent=self.segments["femur"], leg=self,
                                         offset_in_parent=FEMUR_LENGTH)

        self.segments["tarsus"] = Segment(name=name + "_tarsus", servo_id=servo_ids[3],
                                          min_angle=r(-90), neutral_angle=r(0), max_angle=r(0), length=TARSUS_LENGTH,
                                          parent=self.segments["tibia"], leg=self,
                                          offset_in_parent=TIBIA_LENGTH)

        self.rostral_neighbor = None  # leg in front
        self.caudal_neighbor = None  # leg behind

        self.in_swing = False  # is this leg in swing

        self.swing_z = 0  # target highest Z of current swing (calculated as Z of lift-off point + DEFAULT_SWING_HEIGHT), all in thorax cs

        self.touched_down = False  # whether the leg actually touches ground

        self.wants_to_swing = False  # previously the leg wanted to swing, but was not allowed to do it

        self.landing = False  # leg is forced to land
        self.raising = False  # leg is in initial phase of swing

        coxa_seg = self.segments["coxa"]
        self.home_angle = (coxa_seg.min_angle + coxa_seg.max_angle) / 2
        self.home_position_leg = Position(self.cs)
        self.home_position_thorax = Position(self.cs.parent)

        self.task_cs = CoordinateSystem(parent.cs, self.name + "_tcs", 0, 0, 0, 0, 0, 0)

        self.swings_inwards = False  # foot swings towards thorax

        self._r_ws_inner = None  # restrictedness by inner edge of foot workspace
        self._r_ws_outer = None  # restrictedness by outer edge of foot workspace

        # stored previous values of restrictedness components
        self._r_coxa_prev = 0
        self._r_femur_prev = 0
        self._r_tibia_prev = 0
        self._r_tarsus_prev = 0
        self._r_stretch_prev = 0
        self._r_contract_prev = 0
        self._r_ws_outer_prev = 0
        self._r_ws_inner_prev = 0

        self._r_prev = 0  # previous total restrictedness

        # back-ups for angles and leg node positions
        self.angles_backup = StoredAngles(0, 0, 0, 0)
        self.nodes_backup = dict()

        self.stored_node_positions = dict()  # already calculated coordinates of leg nodes

        # coxa coordinates in leg cs and thorax cs never change
        self.stored_node_positions["coxa"] = Position(self.cs, 0, 0, 0)
        self.stored_node_positions["coxa-thorax"] = self.cs.parent.to_this(self.stored_node_positions["coxa"])
Пример #3
0
    def fill_legs(self):
        # initializes legs and puts them in a dictionary

        # leg mount point is on the top surface of thorax
        self.legs["rf"] = Leg("rf", self, MountDescriptor(m(+182.343), m(-104.843), m(0), r(-045.0)), (101, 102, 103, 19))
        self.legs["rm"] = Leg("rm", self, MountDescriptor(m(0000.000), m(-114.198), m(0), r(-90.00)), (104, 105, 106, 11))
        self.legs["rr"] = Leg("rr", self, MountDescriptor(m(-182.343), m(-084.843), m(0), r(-135.0)), (107, 108, 109, 14))
        self.legs["lf"] = Leg("lf", self, MountDescriptor(m(+182.343), m(+104.843), m(0), r(+045.0)), (116, 117, 118, 13))
        self.legs["lm"] = Leg("lm", self, MountDescriptor(m(0000.000), m(+114.198), m(0), r(+90.00)), (113, 114, 115, 20))
        self.legs["lr"] = Leg("lr", self, MountDescriptor(m(+182.343), m(+084.843), m(0), r(+135.0)), (110, 111, 112, 15))

        self.legs["rf"].rostral_neighbor = self.legs["lf"]
        self.legs["rf"].caudal_neighbor = self.legs["rm"]
        self.legs["rm"].rostral_neighbor = self.legs["rf"]
        self.legs["rm"].caudal_neighbor = self.legs["rr"]
        self.legs["rr"].rostral_neighbor = self.legs["rm"]
        self.legs["rr"].caudal_neighbor = self.legs["lr"]

        self.legs["lf"].rostral_neighbor = self.legs["rf"]
        self.legs["lf"].caudal_neighbor = self.legs["lm"]
        self.legs["lm"].rostral_neighbor = self.legs["lf"]
        self.legs["lm"].caudal_neighbor = self.legs["lr"]
        self.legs["lr"].rostral_neighbor = self.legs["lm"]
        self.legs["lr"].caudal_neighbor = self.legs["rr"]
Пример #4
0
class Leg(object):
    COXA_LENGTH = m(42.8)
    FEMUR_LENGTH = m(70)
    TIBIA_LENGTH = m(67.5)
    TARSUS_LENGTH = m(71.3)
    INNER_WS_LIMIT = m(COXA_LENGTH + 70)
    OUTER_WS_LIMIT = m(165)

    def __init__(self, name, parent, mount_descriptor, servo_ids):
        global COXA_LENGTH, FEMUR_LENGTH, TIBIA_LENGTH, TARSUS_LENGTH
        
        self.name = name
        self.parent = parent
        self.mount_descriptor = mount_descriptor

        self.swing_order = 0  # used to keep track of the order in which legs swing. If two legs are ready to swing, priority is given to the one that has been in stance longer

        self.r_was_increasing = False  # indicates whether restrictedness of this leg was increasing before reaching flat plateau

        self.cs = CoordinateSystem(parent.cs, self.name, mount_descriptor.x, mount_descriptor.y, mount_descriptor.z, 0, 0, mount_descriptor.rotz)

        self.segments = dict()
        self.segments["coxa"] = Segment(name=name + "_coxa", servo_id=servo_ids[0],
                                        min_angle=r(-35), neutral_angle=r(0), max_angle=r(35),
                                        length=COXA_LENGTH, parent=self,
                                        leg=self,
                                        offset_in_parent=m(0), mirrored=(mount_descriptor.x < 0), z_rot=True)

        if self.name == "rf":
            self.segments["coxa"].max_angle = r(10)
        elif self.name == "lf":
            self.segments["coxa"].min_angle = r(-10)
        elif self.name == "rr":
            self.segments["coxa"].min_angle = r(-10)
        elif self.name == "lr":
            self.segments["coxa"].max_angle = r(10)

        self.segments["femur"] = Segment(name=name + "_femur", servo_id=servo_ids[1],
                                         min_angle=r(-45), neutral_angle=r(20), max_angle=r(90), length=FEMUR_LENGTH,
                                         parent=self.segments["coxa"], leg=self,
                                         offset_in_parent=COXA_LENGTH)

        self.segments["tibia"] = Segment(name=name + "_tibia", servo_id=servo_ids[2],
                                         min_angle=r(-115), neutral_angle=r(20), max_angle=r(20), length=TIBIA_LENGTH,
                                         parent=self.segments["femur"], leg=self,
                                         offset_in_parent=FEMUR_LENGTH)

        self.segments["tarsus"] = Segment(name=name + "_tarsus", servo_id=servo_ids[3],
                                          min_angle=r(-90), neutral_angle=r(0), max_angle=r(0), length=TARSUS_LENGTH,
                                          parent=self.segments["tibia"], leg=self,
                                          offset_in_parent=TIBIA_LENGTH)

        self.rostral_neighbor = None  # leg in front
        self.caudal_neighbor = None  # leg behind

        self.in_swing = False  # is this leg in swing

        self.swing_z = 0  # target highest Z of current swing (calculated as Z of lift-off point + DEFAULT_SWING_HEIGHT), all in thorax cs

        self.touched_down = False  # whether the leg actually touches ground

        self.wants_to_swing = False  # previously the leg wanted to swing, but was not allowed to do it

        self.landing = False  # leg is forced to land
        self.raising = False  # leg is in initial phase of swing

        coxa_seg = self.segments["coxa"]
        self.home_angle = (coxa_seg.min_angle + coxa_seg.max_angle) / 2
        self.home_position_leg = Position(self.cs)
        self.home_position_thorax = Position(self.cs.parent)

        self.task_cs = CoordinateSystem(parent.cs, self.name + "_tcs", 0, 0, 0, 0, 0, 0)

        self.swings_inwards = False  # foot swings towards thorax

        self._r_ws_inner = None  # restrictedness by inner edge of foot workspace
        self._r_ws_outer = None  # restrictedness by outer edge of foot workspace

        # stored previous values of restrictedness components
        self._r_coxa_prev = 0
        self._r_femur_prev = 0
        self._r_tibia_prev = 0
        self._r_tarsus_prev = 0
        self._r_stretch_prev = 0
        self._r_contract_prev = 0
        self._r_ws_outer_prev = 0
        self._r_ws_inner_prev = 0

        self._r_prev = 0  # previous total restrictedness

        # back-ups for angles and leg node positions
        self.angles_backup = StoredAngles(0, 0, 0, 0)
        self.nodes_backup = dict()

        self.stored_node_positions = dict()  # already calculated coordinates of leg nodes

        # coxa coordinates in leg cs and thorax cs never change
        self.stored_node_positions["coxa"] = Position(self.cs, 0, 0, 0)
        self.stored_node_positions["coxa-thorax"] = self.cs.parent.to_this(self.stored_node_positions["coxa"])

    def __str__(self):
        return self.name

    def update_swings_inwards(self):
        # Indicates whether the swing movement is towards inner limit of workspace

        coxa = self.get_node("coxa")
        current_foot = self.get_node("foot")
        moved_foot = current_foot.clone
        moved_foot.x += .0001 * sind(-1 * self.task_cs.rotz)
        moved_foot.y += .0001 * cosd(-1 * self.task_cs.rotz)

        self.swings_inwards = coxa.distance_to(current_foot) > coxa.distance_to(moved_foot)

    @property
    def joint_angles(self):
        # return joint angles as tuple
        segment_coxa = self.segments["coxa"]
        segment_femur = self.segments["femur"]
        segment_tibia = self.segments["tibia"]
        segment_tarsus = self.segments["tarsus"]

        coxa = segment_coxa.current_angle
        femur = segment_femur.current_angle
        tibia = segment_tibia.current_angle
        tarsus = segment_tarsus.current_angle

        return coxa, femur, tibia, tarsus

    def set_joint_angles(self, coxa, femur, tibia, tarsus):
        # set joint angles with tuple
        segment_coxa = self.segments["coxa"]
        segment_femur = self.segments["femur"]
        segment_tibia = self.segments["tibia"]
        segment_tarsus = self.segments["tarsus"]

        segment_coxa.current_angle = coxa
        segment_femur.current_angle = femur
        segment_tibia.current_angle = tibia
        segment_tarsus.current_angle = tarsus

        self.stored_node_positions.clear()

        self._r_ws_inner = None
        self._r_ws_outer = None

    def store_node(self, name):

        # noinspection PyShadowingNames
        def x_y(l, coxa_angle):
            x = l * sind(-coxa_angle)
            y = l * cosd(-coxa_angle)
            return x, y

        if name == "foot":
            segment_coxa = self.segments["coxa"]
            coxa_len = segment_coxa.length
            coxa_angle = segment_coxa.current_angle

            segment_femur = self.segments["femur"]
            femur_angle = segment_femur.current_angle
            femur_projection_h = segment_femur.length * cosd(femur_angle)
            femur_projection_v = segment_femur.length * sind(femur_angle)

            segment_tibia = self.segments["tibia"]
            tibia_angle = segment_tibia.current_angle
            tibia_projection_h = segment_tibia.length * cosd(femur_angle + tibia_angle)
            tibia_projection_v = segment_tibia.length * sind(femur_angle + tibia_angle)

            segment_tarsus = self.segments["tarsus"]
            tarsus_angle = segment_tarsus.current_angle
            tarsus_projection_h = segment_tarsus.length * sind(90 + (tibia_angle + femur_angle + tarsus_angle) - 180)
            tarsus_projection_v = segment_tarsus.length * cosd(90 + (tibia_angle + femur_angle + tarsus_angle) - 180)

            l = coxa_len + femur_projection_h + tibia_projection_h + tarsus_projection_h
            z = femur_projection_v + tibia_projection_v + tarsus_projection_v
            x, y = x_y(l, coxa_angle)
            node_pos_converted = Position(self.cs)
            node_pos_converted.x, node_pos_converted.y, node_pos_converted.z = x, y, z

        elif name == "foot-odom":
            foot_thorax = self.get_node("foot-thorax")
            node_pos_converted = self.cs.parent.parent.to_this(foot_thorax)

        elif name == "coxa-thorax":
            segment_coxa = self.segments["coxa"]
            node_pos = Position(segment_coxa.cs)
            node_pos_converted = self.cs.parent.to_this(node_pos)

        elif name == "foot-thorax":
            foot = self.get_node("foot")
            node_pos_converted = self.cs.parent.to_this(foot)

        elif name == "tibia-thorax":
            segment_coxa = self.segments["coxa"]
            coxa_len = segment_coxa.length
            coxa_angle = segment_coxa.current_angle

            segment_femur = self.segments["femur"]
            femur_angle = segment_femur.current_angle
            femur_projection_h = segment_femur.length * cosd(femur_angle)
            femur_projection_v = segment_femur.length * sind(femur_angle)

            l = coxa_len + femur_projection_h
            z = femur_projection_v
            x, y = x_y(l, coxa_angle)
            node_pos_converted = Position(self.cs)
            node_pos_converted.x, node_pos_converted.y, node_pos_converted.z = x, y, z

        else:
            node_pos = Position(self.segments[name].cs)
            node_pos_converted = self.cs.to_this(node_pos)

        self.stored_node_positions[name] = node_pos_converted

        return node_pos_converted

    def get_node(self, name):
        try:
            return self.stored_node_positions[name]
        except:
            return self.store_node(name)

    def store_foot_odom(self, foot_thorax=None):
        if foot_thorax is None:
            self.stored_node_positions["foot-odom"] = self.cs.parent.parent.to_this(self.get_node("foot"))
        else:
            self.stored_node_positions["foot-odom"] = foot_thorax.owner.parent.to_this(foot_thorax)

    def calc_joint_angles(self, target_position, best_effort):

        general_calculation = True

        position_leg_cs = self.cs.to_this(target_position)

        angle_coxa = -1 * d(atan2(position_leg_cs.x, position_leg_cs.y))
        femur_to_position_projection = 0

        femur_segment = self.segments["femur"]
        femur_length = femur_segment.length
        tibia_segment = self.segments["tibia"]
        tibia_length = tibia_segment.length
        tarsus_segment = self.segments["tarsus"]
        tarsus_length = tarsus_segment.length

        coxa_segment = self.segments["coxa"]

        if best_effort:
            tgt_tarsus = position_leg_cs.clone
            tgt_tarsus.z += tarsus_length

            femur = self.get_node("femur")

            # check if we can actually reach target_position
            distance = femur.distance_to(tgt_tarsus)

            max_dist = femur_length + tibia_length
            min_dist = third_side(femur_length, tibia_length, 180 + tibia_segment.min_angle)

            dz = femur.z - tgt_tarsus.z

            # are we too far?
            if distance > max_dist:
                # yep, too far
                # calculate femur angle to target
                femur_to_position_projection = sqrt(max_dist ** 2 - dz ** 2)
                general_calculation = False
            elif distance < min_dist:
                # we are too close
                femur_to_position_projection = sqrt(min_dist ** 2 - dz ** 2)
                general_calculation = False
            else:
                # we are not too close and not too far
                general_calculation = True

        if general_calculation:
            # works either when not best_effort or when best_effort and distance is ok
            coxa_to_position_projection = sqrt(position_leg_cs.x ** 2 + position_leg_cs.y ** 2)
            femur_to_position_projection = coxa_to_position_projection - coxa_segment.length

        distance_to_tarsus_top = sqrt(femur_to_position_projection ** 2 + (position_leg_cs.z + tarsus_length) ** 2)

        if position_leg_cs.z + tarsus_length == 0:
            angle_down_to_tarsus_top = 0
        else:
            angle_down_to_tarsus_top = 90 - angle_ab(-1 * (position_leg_cs.z + tarsus_length), distance_to_tarsus_top, femur_to_position_projection)

        angle_femur = angle_ab(femur_length, distance_to_tarsus_top, tibia_length) - angle_down_to_tarsus_top
        angle_tibia = -180 + angle_ac(femur_length, distance_to_tarsus_top, tibia_length)
        angle_tarsus = -180 + 90 - angle_femur - angle_tibia

        if coxa_segment.min_angle > angle_coxa:
            if best_effort:
                angle_coxa = coxa_segment.min_angle
            else:
                raise Exception(self.name + " coxa angle too low")
        elif coxa_segment.max_angle < angle_coxa:
            if best_effort:
                angle_coxa = coxa_segment.max_angle
            else:
                raise Exception(self.name + " coxa angle too high")

        if femur_segment.min_angle > angle_femur:
            if best_effort:
                angle_femur = femur_segment.min_angle
            else:
                raise Exception(self.name + " femur angle too low")
        elif femur_segment.max_angle < angle_femur:
            if best_effort:
                angle_femur = femur_segment.max_angle
            else:
                raise Exception(self.name + " femur angle too high")

        if tibia_segment.min_angle > angle_tibia:
            if best_effort:
                angle_tibia = tibia_segment.min_angle
            else:
                raise Exception(self.name + " tibia angle too low")
        elif tibia_segment.max_angle < angle_tibia:
            if best_effort:
                angle_tibia = tibia_segment.max_angle
            else:
                raise Exception(self.name + " tibia angle too high")

        if tarsus_segment.min_angle > angle_tarsus:
            if best_effort:
                angle_tarsus = tarsus_segment.min_angle
            else:
                raise Exception(self.name + " tarsus angle too low")
        elif tarsus_segment.max_angle < angle_tarsus:
            if best_effort:
                angle_tarsus = tarsus_segment.max_angle
            else:
                raise Exception(self.name + " tarsus angle too high")

        return angle_coxa, angle_femur, angle_tibia, angle_tarsus

    def backup(self):
        # stores current joint angles so that they could be restored
        self.angles_backup = StoredAngles(self.segments["coxa"].current_angle, self.segments["femur"].current_angle, self.segments["tibia"].current_angle, self.segments["tarsus"].current_angle)
        self.nodes_backup = dict()
        for node_name in self.stored_node_positions:
            self.nodes_backup[node_name] = self.stored_node_positions[node_name]

    def rollback(self):
        # restores joint angles to what is stored
        self.set_joint_angles(self.angles_backup.coxa, self.angles_backup.femur, self.angles_backup.tibia, self.angles_backup.tarsus)
        self.stored_node_positions = self.nodes_backup

    def update_home_position(self):
        h_dist_home = (Leg.OUTER_WS_LIMIT + Leg.INNER_WS_LIMIT) / 2
        roll_dist_home = h_dist_home * cosd(self.cs.roty)

        self.home_position_leg = Position(self.cs, roll_dist_home * sind(-self.home_angle), roll_dist_home * cosd(-self.home_angle), 0)
        self.home_position_leg.z = -38

        self.home_position_thorax = self.cs.parent.to_this(self.home_position_leg)

        return self.home_position_leg

    def redefine_task_cs(self, translate_x, translate_y, rotate_z):
        if not (translate_x == 0 and translate_y == 0 and rotate_z == 0):
            # calculate movement vector angle
            current_foot_pos_thorax = self.get_node("foot-thorax")
            odom_cs = self.cs.parent.parent  # leg->thorax->odom
            foot_pos_odom = odom_cs.to_this(current_foot_pos_thorax)
            moved_thorax_cs = self.cs.parent.get_derivative("tmp_thorax_for_tcs_vector", translate_x, translate_y, 0, 0, 0, rotate_z)
            new_foot_pos_thorax = moved_thorax_cs.to_this(foot_pos_odom, caller="redefine_task_cs")

            dx = new_foot_pos_thorax.x - current_foot_pos_thorax.x
            dy = new_foot_pos_thorax.y - current_foot_pos_thorax.y

            angle = d(atan2(dx, dy)) - 180

            home_pos = self.home_position_thorax

            self.task_cs.redefine_tuple(home_pos.tuple, (None, None, -angle))

            self.update_swings_inwards()
        else:
            self.swings_inwards = None

    @property
    def anterior_neighbor(self):
        angle = self.task_cs.rotz
        if -90 < angle < 90:
            return self.rostral_neighbor
        else:
            return self.caudal_neighbor

    @property
    def posterior_neighbor(self):
        angle = self.task_cs.rotz
        if -90 < angle < 90:
            return self.caudal_neighbor
        else:
            return self.rostral_neighbor

    @property
    def can_swing(self):
        return not self.caudal_neighbor.in_swing and not self.rostral_neighbor.in_swing

    # RESTRICTEDNESS -->

    def store_r(self):
        # store restrictedness components

        self._r_coxa_prev = self.r_coxa

        self._r_femur_prev = self.r_femur
        self._r_tibia_prev = self.r_tibia
        self._r_tarsus_prev = self.r_tarsus

        self._r_ws_inner_prev = self.r_ws_inner("store_r/")
        self._r_ws_outer_prev = self.r_ws_outer("store_r/")

        self._r_stretch_prev = self.r_stretch
        self._r_contract_prev = self.r_contract

        self._r_prev = self.r()

    @property
    def r_ws_inner(self):
        if self.r_ws_inner is None:
            self.update_r_ws()
        return self._r_ws_inner

    @property
    def r_ws_outer(self):
        if self.r_ws_outer is None:
            self.update_r_ws()
        return self._r_ws_outer

    @property
    def r_coxa(self):
        lower_limit = self.segments["coxa"].min_angle
        upper_limit = self.segments["coxa"].max_angle
        transition_zone = 20
        parameter = self.segments["coxa"].current_angle

        return exp_r(parameter, lower_limit, upper_limit, transition_zone)

    @property
    def r_femur(self):
        lower_limit = self.segments["femur"].min_angle
        upper_limit = self.segments["femur"].max_angle
        transition_zone = 10
        parameter = self.segments["femur"].current_angle

        return exp_r(parameter, lower_limit, upper_limit, transition_zone)

    @property
    def r_tibia(self):
        lower_limit = self.segments["tibia"].min_angle
        upper_limit = self.segments["tibia"].max_angle
        transition_zone = 10
        parameter = self.segments["tibia"].current_angle

        return exp_r(parameter, lower_limit, upper_limit, transition_zone)

    @property
    def r_tarsus(self):
        lower_limit = self.segments["tarsus"].min_angle
        upper_limit = self.segments["tarsus"].max_angle
        transition_zone = 10
        parameter = self.segments["tarsus"].current_angle

        return exp_r(parameter, lower_limit, upper_limit, transition_zone)

    def update_r_ws(self):
        # changes either when thorax CS is redefined (body moved) or when this leg is moved

        foot_position_odom = self.cs.parent.parent.to_this(self.get_node("foot-thorax"))  # leg->thorax->odom
        coxa_position_odom = self.cs.parent.parent.to_this(self.get_node("coxa-thorax"))  # leg->thorax->odom
        foot_position_odom.z = 0
        coxa_position_odom.z = 0

        transition_zone = 10

        parameter = coxa_position_odom.distance_to(foot_position_odom)

        lower_limit = Leg.INNER_WS_LIMIT
        upper_limit = 100000000
        self._r_ws_inner = exp_r(parameter, lower_limit, upper_limit, transition_zone)

        lower_limit = -100000000
        upper_limit = Leg.OUTER_WS_LIMIT
        self._r_ws_outer = exp_r(parameter, lower_limit, upper_limit, transition_zone)

    @property
    def r_ws(self):
        if self.in_swing:
            if self.swings_inwards:
                return self.r_ws_inner()
            else:
                return self.r_ws_outer()
        else:
            return self.r_ws_outer() + self.r_ws_inner()

    @property
    def r_stretch(self):
        lower_limit = -100000000
        upper_limit = (self.segments["femur"].length + self.segments["tibia"].length) * 0.9
        transition_zone = 5

        try:
            tarsus = self.stored_node_positions["tarsus"]
        except:
            tarsus = self.store_node("tarsus")

        try:
            femur = self.stored_node_positions["femur"]
        except:
            femur = self.store_node("femur")

        parameter = femur.distance_to(tarsus)

        return exp_r(parameter, lower_limit, upper_limit, transition_zone)

    @property
    def r_contract(self):
        lower_limit = third_side(self.segments["femur"].length, self.segments["tibia"].length, 180 + self.segments["tibia"].min_angle)
        upper_limit = 100000000
        transition_zone = 20

        try:
            tarsus = self.stored_node_positions["tarsus"]
        except:
            tarsus = self.store_node("tarsus")

        try:
            femur = self.stored_node_positions["femur"]
        except:
            femur = self.store_node("femur")

        parameter = femur.distance_to(tarsus)

        return exp_r(parameter, lower_limit, upper_limit, transition_zone)

    def r_stance(self):
        return max(self.r_coxa, self.r_femur, self.r_tibia, self.r_tarsus, self.r_ws)

    @property
    def r_swing(self):
        # in most cases growing r_coxa indicates final phase of swing
        # in a case when task_cs is exactly aligned with leg cs, r_coxa will be constant throughout swing, so alternative criterion is needed

        result = 0

        # is task_cs aligned with leg cs?
        tcs_angle = 0 + self.task_cs.rotz
        if tcs_angle > 180:
            tcs_angle -= 360

        if abs(tcs_angle - self.cs.rotz) < 0.0001:
            r_stretch = self.r_stretch
            if r_stretch > self._r_stretch_prev + 0.0001:
                result = max(self.r_tarsus, r_stretch)
        elif abs(tcs_angle + self.cs.rotz) < 0.0001:
            r_contract = self.r_contract
            if r_contract > self._r_contract_prev + 0.0001:
                result = max(self.r_tibia, r_contract)

        else:
            # use growing r_coxa as final swing indicator
            r_coxa = self.r_coxa
            if r_coxa > self._r_coxa_prev + 0.0001:
                r_stretch = self.r_stretch
                if r_stretch <= self._r_stretch_prev:
                    r_stretch = 0
                # r += max(r_coxa, self.r_tarsus, self.r_stretch)#, self.r_ws("r_swing"))
                if self.raising:
                    r_tarsus = 0
                else:
                    r_tarsus = self.r_tarsus

                result = max(r_coxa, r_tarsus, r_stretch)
            else:
                result = 0

        return result

    def r(self):
        if self.in_swing:
            return self.r_swing
        else:
            return self.r_stance()

    def r_change_direction(self, test_r=None):
        # returns 1 when restrictedness is increasing, 2 -- when decreasing, 0 -- when restrictedness is constant
        # if test_r is provided, previous restrictedness is compared to this test_r, otherwise -- to current leg restrictedness

        if test_r is None:
            new_r = self.r
        else:
            new_r = test_r

        if new_r > self._r_prev + 0.0001:
            return 1
        elif new_r < self._r_prev - 0.0001:
            return -1
        else:
            return 0