Source

pi-a-sketch / piasketch.py

#!/usr/bin/env python
""" piasketch - The Pi-A-Sketch program draws a few circles
It is also a basic library for steppers controlling an X/Y device.

The main functions are line(x,y) and circle(radius).
"""
import RPi.GPIO as gpio
import time

PINS_A = [4, 25, 24, 23]
PINS_B = [22, 21, 18, 17]
PINS = PINS_A + PINS_B
SEQA = [(4, ), (4, 25), (25, ), (25, 24), (24, ), (24, 23), (23, ), (23, 4)]
RSEQA = SEQA[::-1][1:] + SEQA[::-1][:1]
SEQB = [(22, ), (22, 21), (21, ), (21, 18), (18, ), (18, 17), (17, ), (17, 22)]
RSEQB = SEQB[::-1][1:] + SEQB[::-1][:1]

CORRECTION = 22


def setallpins():
    """
    Before we can use them, we need to set the pins for both motors
    """
    gpio.setmode(gpio.BCM)
    for pin in PINS:
        gpio.setup(pin, gpio.OUT)


def stepper(sequence, pins, delay=0.002):
    """
    One full step, based on an ordered sequence and corresponding pins
    """
    for step in sequence:
        for pin in pins:
            gpio.output(pin, gpio.HIGH if pin in step else gpio.LOW)
        time.sleep(delay)


def move(steps, axis="x"):
    """
    Move a certain number of steps on a specific axis. sign for direction
    """
    (seq, pins) = (SEQA, PINS_A) if axis == "x" else (SEQB, PINS_B)
    if steps < 0:
        steps = -steps
        seq = RSEQA if axis == "x" else RSEQB
    for _ in range(steps):
        stepper(seq, pins)


def line(x1, y1):
    """
    Bresenham's line algorithm from rosetta code (ADA/Python),
    adapted to relative positioning. It will be drawn from current
    position to x1,y1. Negative number goes in other direction.
    """
    dx = abs(x1)
    dy = abs(y1)
    x, y = 0, 0
    px, py = x, y
    sx = -1 if 0 > x1 else 1
    sy = -1 if 0 > y1 else 1
    if dx > dy:
        err = dx / 2.0
        while x != x1:
            if x - px is not 0:
                move(x - px)
            if y - py is not 0:
                move(y - py, "y")
            px, py = x, y
            err -= dy
            if err < 0:
                y += sy
                err += dx
            x += sx
    else:
        err = dy / 2.0
        while y != y1:
            if x - px is not 0:
                move(x - px)
            if y - py is not 0:
                move(y - py, "y")
            px, py = x, y
            err -= dx
            if err < 0:
                x += sx
                err += dy
            y += sy
    if x - px is not 0:
        move(x - px)
    if y - py is not 0:
        move(y - py, "y")


def _circlepoints(radius):
    """
    mid point circle drawing algorithm - basically Bresenham's.
    It was converted for use with relative. Cant use the bitmap
    approach of the original algorithm. Points are sequential
    clockwise. It works, but it is ugly. I only had a few
    minutes to write this. It needs to be cleaned up.
    """
    points = []
    segment = []
    for seg in range(8):
        x0, y0 = 0, -radius
        f = 1 - radius
        ddf_x, ddf_y = 1, -2 * radius
        x, y = 0, radius

        segment = []
        while x < y:
            if seg == 0:
                segment.append((x0 + x, y0 + y))
            elif seg == 1:
                segment.append((x0 + y, y0 + x))
            elif seg == 2:
                segment.append((x0 + y, y0 - x))
            elif seg == 3:
                segment.append((x0 + x, y0 - y))
            elif seg == 4:
                segment.append((x0 - x, y0 - y))
            elif seg == 5:
                segment.append((x0 - y, y0 - x))
            elif seg == 6:
                segment.append((x0 - y, y0 + x))
            elif seg == 7:
                segment.append((x0 - x, y0 + y))

            if f >= 0:
                y -= 1
                ddf_y += 2
                f += ddf_y
            x += 1
            ddf_x += 2
            f += ddf_x
        if seg % 2:
            points.extend(segment[::-1])
        else:
            points.extend(segment)
    return points


def circle(radius):
    """
    Draw a circle of specified radius. It is not centered. It will start
    drawing a circle at its current position, going right and down.
    """
    points = _circlepoints(radius)
    #print points
    dx, dy = 1, 1
    px, py = 0, 0
    counter = 0
    total = len(points)
    quarter = total / 4
    #print total, quarter
    for (x, y) in points:
        counter += 1
        dx, dy = x - px, y - py
        px, py = x, y
        # print dx, dy
        if dx is not 0:
            move(dx)
        if dy is not 0:
            move(dy, "y")
        if counter == quarter:
            move(-CORRECTION)
        elif counter == quarter * 2:
            move(CORRECTION, "y")
        elif counter == quarter * 3:
            move(CORRECTION)
        elif counter == total:
            move(-CORRECTION, "y")


def main():
    """
    As a standalone app, draw a few circles
    """
    setallpins()
    #line(-50, -200)
    #line(5,0)
    #circle(350)
    for radius in range(100,400,25):
        circle(radius)
    gpio.cleanup()

if __name__ == "__main__":
    main()