turtle, util, dragon curve

This commit is contained in:
Michael Fogleman 2017-01-07 22:22:28 -05:00
parent d2ea5b6df2
commit c0525511d2
7 changed files with 220 additions and 15 deletions

View File

@ -1,3 +1,5 @@
from .device import Device
from .drawing import Drawing
from .planner import Planner
from .turtle import Turtle
from .util import draw

View File

@ -1,3 +1,5 @@
from __future__ import division
import time
from math import modf
@ -9,12 +11,16 @@ from .planner import Planner
STEPS_PER_INCH = 2032
STEPS_PER_MM = 80
PEN_UP_POSITION = 60
PEN_UP_SPEED = 150
PEN_UP_DELAY = 100
PEN_DOWN_POSITION = 40
PEN_DOWN_SPEED = 150
PEN_DOWN_DELAY = 100
ACCELERATION = 6
ACCELERATION = 5
MAX_VELOCITY = 3
CORNER_FACTOR = 0.001
CORNER_FACTOR = 0.01
VID_PID = '04D8:FD92'
@ -25,18 +31,44 @@ def find_port():
return None
class Device(object):
def __init__(self):
port = find_port()
if port is None:
raise Exception('cannot find axidraw device')
self.serial = Serial(port, timeout=1)
def __init__(self, **kwargs):
self.steps_per_unit = STEPS_PER_INCH
self.pen_up_position = PEN_UP_POSITION
self.pen_up_speed = PEN_UP_SPEED
self.pen_up_delay = PEN_UP_DELAY
self.pen_down_position = PEN_DOWN_POSITION
self.pen_down_speed = PEN_DOWN_SPEED
self.pen_down_delay = PEN_DOWN_DELAY
self.acceleration = ACCELERATION
self.max_velocity = MAX_VELOCITY
self.corner_factor = CORNER_FACTOR
for k, v in kwargs.items():
setattr(self, k, v)
port = find_port()
if port is None:
raise Exception('cannot find axidraw device')
self.serial = Serial(port, timeout=1)
self.configure()
def configure(self):
servo_min = 7500
servo_max = 28000
pen_up_position = self.pen_up_position / 100
pen_up_position = int(
servo_min + (servo_max - servo_min) * pen_up_position)
pen_down_position = self.pen_down_position / 100
pen_down_position = int(
servo_min + (servo_max - servo_min) * pen_down_position)
self.command('SC', 4, pen_up_position)
self.command('SC', 5, pen_down_position)
self.command('SC', 11, int(self.pen_up_speed * 5))
self.command('SC', 12, int(self.pen_down_speed * 5))
def close(self):
self.serial.close()
def make_planner(self):
return Planner(
self.acceleration, self.max_velocity, self.corner_factor)
@ -71,7 +103,7 @@ class Device(object):
def run_plan(self, plan):
step_ms = 30
step_s = step_ms / 1000.0
step_s = step_ms / 1000
t = 0
ex = 0
ey = 0
@ -91,12 +123,14 @@ class Device(object):
self.run_plan(plan)
def run_drawing(self, drawing):
planner = self.make_planner()
self.pen_up()
position = (0, 0)
for path in drawing.paths:
self.run_path([position, path[0]])
plan = planner.plan(path)
self.pen_down()
self.run_path(path)
self.run_plan(plan)
self.pen_up()
position = path[-1]
self.run_path([position, (0, 0)])

View File

@ -1,5 +1,9 @@
from __future__ import division
from math import sin, cos, radians
from .paths import sort_paths
class Drawing(object):
def __init__(self, paths=None):
self.paths = paths or []
@ -29,8 +33,8 @@ class Drawing(object):
x1, y1, x2, y2 = self.bounds
return y2 - y1
# def sort_paths_greedy(self, reversable=True):
# return Drawing(planner.sort_paths_greedy(self.paths, reversable))
def sort_paths(self, reversable=True):
return Drawing(sort_paths(self.paths, reversable))
# def join_paths(self, tolerance=0.05):
# return Drawing(util.join_paths(self.paths, tolerance))
@ -70,18 +74,21 @@ class Drawing(object):
def origin(self):
return self.move(0, 0, 0, 0)
def center(self, width, height):
return self.move(width / 2, height / 2, 0.5, 0.5)
def rotate_to_fit(self, width, height, step=5):
for angle in range(0, 180, step):
drawing = self.rotate(angle)
if drawing.width <= width and drawing.height <= height:
return drawing.origin()
return drawing.center(width, height)
return None
def scale_to_fit(self, width, height, padding=0):
width -= padding * 2
height -= padding * 2
scale = min(width / self.width, height / self.height)
return self.scale(scale, scale).origin()
return self.scale(scale, scale).center(width, height)
def rotate_and_scale_to_fit(self, width, height, padding=0, step=5):
drawings = []
@ -92,4 +99,4 @@ class Drawing(object):
scale = min(width / drawing.width, height / drawing.height)
drawings.append((scale, drawing))
scale, drawing = max(drawings)
return drawing.scale(scale, scale).origin()
return drawing.scale(scale, scale).center(width, height)

View File

@ -13,7 +13,7 @@ def sort_paths(paths, reversable=True):
points.append((x2, y2, path, True))
index = Index(points)
while index.size > 0:
x, y, path, reverse = index.search(result[-1][-1])
x, y, path, reverse = index.nearest(result[-1][-1])
x1, y1 = path[0]
x2, y2 = path[-1]
index.remove((x1, y1, path, False))

134
axi/turtle.py Normal file
View File

@ -0,0 +1,134 @@
import math
from .drawing import Drawing
def to_degrees(x):
return math.degrees(x) % 360
class Turtle(object):
def __init__(self):
self.reset()
def reset(self):
self.x = 0
self.y = 0
self.h = 0
self.pen = True
self._path = [(self.x, self.y)]
self._paths = []
def clear(self):
self._path = [(self.x, self.y)]
self._paths = []
@property
def paths(self):
paths = list(self._paths)
if len(self._path) > 1:
paths.append(self._path)
return paths
@property
def drawing(self):
return Drawing(self.paths)
def pd(self):
self.pen = True
pendown = down = pd
def pu(self):
self.pen = False
if len(self._path) > 1:
self._paths.append(self._path)
self._path = [(self.x, self.y)]
penup = up = pu
def isdown(self):
return self.pen
def goto(self, x, y=None):
if y is None:
x, y = x
if self.pen:
self._path.append((x, y))
self.x = x
self.y = y
setpos = setposition = goto
def setx(self, x):
self.goto(x, self.y)
def sety(self, x):
self.goto(self.x, y)
def seth(self, heading):
self.h = heading
setheading = seth
def home(self):
self.goto(0, 0)
self.seth(0)
def fd(self, distance):
x = self.x + distance * math.cos(math.radians(self.h))
y = self.y + distance * math.sin(math.radians(self.h))
self.goto(x, y)
forward = fd
def bk(self, distance):
x = self.x - distance * math.cos(math.radians(self.h))
y = self.y - distance * math.sin(math.radians(self.h))
self.goto(x, y)
backward = back = bk
def rt(self, angle):
self.seth(self.h + angle)
right = rt
def lt(self, angle):
self.seth(self.h - angle)
left = lt
def circle(self, radius, extent=None, steps=None):
if extent is None:
extent = 360
if steps is None:
steps = int(round(abs(2 * math.pi * radius * extent / 360)))
steps = max(steps, 4)
cx = self.x + radius * math.cos(math.radians(self.h + 90))
cy = self.y + radius * math.sin(math.radians(self.h + 90))
a1 = to_degrees(math.atan2(self.y - cy, self.x - cx))
a2 = a1 + extent if radius >= 0 else a1 - extent
for i in range(steps):
p = i / float(steps - 1)
a = a1 + (a2 - a1) * p
x = cx + abs(radius) * math.cos(math.radians(a))
y = cy + abs(radius) * math.sin(math.radians(a))
self.goto(x, y)
if radius >= 0:
self.seth(self.h + extent)
else:
self.seth(self.h - extent)
def pos(self):
return (self.x, self.y)
position = pos
def towards(self, x, y=None):
if y is None:
x, y = x
return to_degrees(math.atan2(y - self.y, x - self.x))
def xcor(self):
return self.x
def ycor(self):
return self.y
def heading(self):
return self.h
def distance(self, x, y=None):
if y is None:
x, y = x
return math.hypot(x - self.x, y - self.y)

13
axi/util.py Normal file
View File

@ -0,0 +1,13 @@
from .device import Device
def reset():
d = Device()
d.disable_motors()
d.pen_up()
def draw(drawing):
# TODO: support drawing, list of paths, or single path
d = Device()
d.enable_motors()
d.run_drawing(drawing)
d.disable_motors()

15
examples/dragon_curve.py Normal file
View File

@ -0,0 +1,15 @@
import axi
def main(iteration):
turtle = axi.Turtle()
for i in range(1, 2 ** iteration):
turtle.forward(1)
if (((i & -i) << 1) & i) != 0:
turtle.circle(-1, 90, 36)
else:
turtle.circle(1, 90, 36)
drawing = turtle.drawing.rotate_and_scale_to_fit(11, 8.5, step=90)
axi.draw(drawing)
if __name__ == '__main__':
main(12)