changeset 142:b5c06cc9e0fc

move leg to separate module
author Radomir Dopieralski <sheep-devel@sheep.art.pl>
date Wed, 06 Aug 2014 18:11:43 +0200
parents 999894826513
children b86bba4dc6a2
files leg.py
diffstat 1 files changed, 112 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/leg.py	Wed Aug 06 18:11:43 2014 +0200
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import math
+
+def _solve_triangle(a, b, c):
+    """Calculate an angle of a triangle using the law of cosines."""
+    a, b, c = abs(a), abs(b), abs(c)
+    if a + b < c or a + c < b or b + c < a:
+        raise ValueError("Impossible triangle: %r, %r, %r" % (a, b, c))
+    cos = float(a ** 2 + b ** 2 - c ** 2) / (2 * a * b)
+    return math.acos(cos)
+
+
+def _length(a, b):
+    """Calculate the length of a vector."""
+    return math.sqrt(a ** 2 + b ** 2)
+
+
+def _normalize_degrees(angle):
+    """Convert an angle to range +-180 degree range."""
+    while angle > 180:
+        angle -= 360
+    while angle < -180:
+        angle += 360
+    return angle
+
+
+class Leg(object):
+    """A signle leg, containing 3 servos: hip, knee and ankle, and three
+       connections between them: coxa, femur and tibia.
+    """
+
+    # Lengths of leg segments, in mm
+    coxa = 16.0  # between the hip and the knee
+    femur = 50.0  # between the knee and the ankle
+    tibia = 45.0  # between the ankle and the foot
+    base = 45.0, 45.0  # from the center of body
+    home = 45.0, 45.0, -35.0
+
+    def __init__(self, hip, knee, ankle, front=True):
+        self.hip = hip
+        self.knee = knee
+        self.ankle = ankle
+        self.front = front
+        if self.front:
+            self.y_direction = 1
+        else:
+            self.y_direction = -1
+        self.hip_offset = -45 * self.y_direction
+        self.home = (
+            self.home[0], self.home[1] * self.y_direction, self.home[2])
+        self.position = list(self.home)
+
+    def __repr__(self):
+        return "<Leg (%r, %r, %r)>" % (
+            self.position[0],
+            self.position[1],
+            self.position[2],
+        )
+
+    def __iter__(self):
+        """Iterate over all joints."""
+        return iter([self.hip, self.knee, self.ankle])
+
+    def _inverse_kinematics(self, (x, y, z)):
+        """Calculate the 3 angles of the leg, in radians."""
+        f = _length(x, y) - self.coxa
+        d = min(self.femur + self.tibia, _length(f, z))
+        hip = math.atan2(y, x)
+        knee = _solve_triangle(self.femur, d, self.tibia) - math.atan2(-z, f)
+        ankle = _solve_triangle(self.femur, self.tibia, d)
+        knee = _normalize_degrees(-math.degrees(knee))
+        ankle = _normalize_degrees(math.degrees(ankle) - 90)
+        hip = _normalize_degrees(math.degrees(hip) + self.hip_offset)
+        return hip, knee, ankle
+
+    def move_to(self, position):
+        """Move the foot to the specified point.
+
+        If any of the coordinates is None, no movement will be done in that
+        axis.
+        """
+        position = list(position)
+        for i, coord in enumerate(position):
+            if coord is None:
+                position[i] = self.position[i]
+        self.position = position
+
+    def move_by(self, (dx, dy, dz)):
+        """Move the foot by the specified amount."""
+        x, y, z = self.position
+        self.move_to((x + dx, y + dy, z + dz))
+
+    def rotate(self, degrees):
+        """Rotate the leg around the center of the body horizontally."""
+        radians = math.radians(degrees * self.y_direction)
+        x, y, z = self.position
+        bx = self.base[0] + x
+        by = self.base[1] + y * self.y_direction
+        r = _length(bx, by)
+        a = math.atan2(bx, by)
+        rx = r * math.sin(a + radians) - self.base[0]
+        ry = r * math.cos(a + radians) - self.base[1]
+        self.move_to((rx, ry * self.y_direction, None))
+
+    def update(self, shift):
+        dx, dy, dz = shift
+        x, y, z = self.position
+        hip, knee, ankle = self._inverse_kinematics((x + dx, y + dy, z + dz))
+        self.hip.move_degrees(hip)
+        self.knee.move_degrees(knee)
+        self.ankle.move_degrees(ankle)