turtle, util, dragon curve
This commit is contained in:
parent
d2ea5b6df2
commit
c0525511d2
|
@ -1,3 +1,5 @@
|
||||||
from .device import Device
|
from .device import Device
|
||||||
from .drawing import Drawing
|
from .drawing import Drawing
|
||||||
from .planner import Planner
|
from .planner import Planner
|
||||||
|
from .turtle import Turtle
|
||||||
|
from .util import draw
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import division
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from math import modf
|
from math import modf
|
||||||
|
@ -9,12 +11,16 @@ from .planner import Planner
|
||||||
STEPS_PER_INCH = 2032
|
STEPS_PER_INCH = 2032
|
||||||
STEPS_PER_MM = 80
|
STEPS_PER_MM = 80
|
||||||
|
|
||||||
|
PEN_UP_POSITION = 60
|
||||||
|
PEN_UP_SPEED = 150
|
||||||
PEN_UP_DELAY = 100
|
PEN_UP_DELAY = 100
|
||||||
|
PEN_DOWN_POSITION = 40
|
||||||
|
PEN_DOWN_SPEED = 150
|
||||||
PEN_DOWN_DELAY = 100
|
PEN_DOWN_DELAY = 100
|
||||||
|
|
||||||
ACCELERATION = 6
|
ACCELERATION = 5
|
||||||
MAX_VELOCITY = 3
|
MAX_VELOCITY = 3
|
||||||
CORNER_FACTOR = 0.001
|
CORNER_FACTOR = 0.01
|
||||||
|
|
||||||
VID_PID = '04D8:FD92'
|
VID_PID = '04D8:FD92'
|
||||||
|
|
||||||
|
@ -25,18 +31,44 @@ def find_port():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
class Device(object):
|
class Device(object):
|
||||||
def __init__(self):
|
def __init__(self, **kwargs):
|
||||||
port = find_port()
|
|
||||||
if port is None:
|
|
||||||
raise Exception('cannot find axidraw device')
|
|
||||||
self.serial = Serial(port, timeout=1)
|
|
||||||
self.steps_per_unit = STEPS_PER_INCH
|
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_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.pen_down_delay = PEN_DOWN_DELAY
|
||||||
self.acceleration = ACCELERATION
|
self.acceleration = ACCELERATION
|
||||||
self.max_velocity = MAX_VELOCITY
|
self.max_velocity = MAX_VELOCITY
|
||||||
self.corner_factor = CORNER_FACTOR
|
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):
|
def make_planner(self):
|
||||||
return Planner(
|
return Planner(
|
||||||
self.acceleration, self.max_velocity, self.corner_factor)
|
self.acceleration, self.max_velocity, self.corner_factor)
|
||||||
|
@ -71,7 +103,7 @@ class Device(object):
|
||||||
|
|
||||||
def run_plan(self, plan):
|
def run_plan(self, plan):
|
||||||
step_ms = 30
|
step_ms = 30
|
||||||
step_s = step_ms / 1000.0
|
step_s = step_ms / 1000
|
||||||
t = 0
|
t = 0
|
||||||
ex = 0
|
ex = 0
|
||||||
ey = 0
|
ey = 0
|
||||||
|
@ -91,12 +123,14 @@ class Device(object):
|
||||||
self.run_plan(plan)
|
self.run_plan(plan)
|
||||||
|
|
||||||
def run_drawing(self, drawing):
|
def run_drawing(self, drawing):
|
||||||
|
planner = self.make_planner()
|
||||||
self.pen_up()
|
self.pen_up()
|
||||||
position = (0, 0)
|
position = (0, 0)
|
||||||
for path in drawing.paths:
|
for path in drawing.paths:
|
||||||
self.run_path([position, path[0]])
|
self.run_path([position, path[0]])
|
||||||
|
plan = planner.plan(path)
|
||||||
self.pen_down()
|
self.pen_down()
|
||||||
self.run_path(path)
|
self.run_plan(plan)
|
||||||
self.pen_up()
|
self.pen_up()
|
||||||
position = path[-1]
|
position = path[-1]
|
||||||
self.run_path([position, (0, 0)])
|
self.run_path([position, (0, 0)])
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
from __future__ import division
|
||||||
|
|
||||||
from math import sin, cos, radians
|
from math import sin, cos, radians
|
||||||
|
|
||||||
|
from .paths import sort_paths
|
||||||
|
|
||||||
class Drawing(object):
|
class Drawing(object):
|
||||||
def __init__(self, paths=None):
|
def __init__(self, paths=None):
|
||||||
self.paths = paths or []
|
self.paths = paths or []
|
||||||
|
@ -29,8 +33,8 @@ class Drawing(object):
|
||||||
x1, y1, x2, y2 = self.bounds
|
x1, y1, x2, y2 = self.bounds
|
||||||
return y2 - y1
|
return y2 - y1
|
||||||
|
|
||||||
# def sort_paths_greedy(self, reversable=True):
|
def sort_paths(self, reversable=True):
|
||||||
# return Drawing(planner.sort_paths_greedy(self.paths, reversable))
|
return Drawing(sort_paths(self.paths, reversable))
|
||||||
|
|
||||||
# def join_paths(self, tolerance=0.05):
|
# def join_paths(self, tolerance=0.05):
|
||||||
# return Drawing(util.join_paths(self.paths, tolerance))
|
# return Drawing(util.join_paths(self.paths, tolerance))
|
||||||
|
@ -70,18 +74,21 @@ class Drawing(object):
|
||||||
def origin(self):
|
def origin(self):
|
||||||
return self.move(0, 0, 0, 0)
|
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):
|
def rotate_to_fit(self, width, height, step=5):
|
||||||
for angle in range(0, 180, step):
|
for angle in range(0, 180, step):
|
||||||
drawing = self.rotate(angle)
|
drawing = self.rotate(angle)
|
||||||
if drawing.width <= width and drawing.height <= height:
|
if drawing.width <= width and drawing.height <= height:
|
||||||
return drawing.origin()
|
return drawing.center(width, height)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def scale_to_fit(self, width, height, padding=0):
|
def scale_to_fit(self, width, height, padding=0):
|
||||||
width -= padding * 2
|
width -= padding * 2
|
||||||
height -= padding * 2
|
height -= padding * 2
|
||||||
scale = min(width / self.width, height / self.height)
|
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):
|
def rotate_and_scale_to_fit(self, width, height, padding=0, step=5):
|
||||||
drawings = []
|
drawings = []
|
||||||
|
@ -92,4 +99,4 @@ class Drawing(object):
|
||||||
scale = min(width / drawing.width, height / drawing.height)
|
scale = min(width / drawing.width, height / drawing.height)
|
||||||
drawings.append((scale, drawing))
|
drawings.append((scale, drawing))
|
||||||
scale, drawing = max(drawings)
|
scale, drawing = max(drawings)
|
||||||
return drawing.scale(scale, scale).origin()
|
return drawing.scale(scale, scale).center(width, height)
|
||||||
|
|
|
@ -13,7 +13,7 @@ def sort_paths(paths, reversable=True):
|
||||||
points.append((x2, y2, path, True))
|
points.append((x2, y2, path, True))
|
||||||
index = Index(points)
|
index = Index(points)
|
||||||
while index.size > 0:
|
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]
|
x1, y1 = path[0]
|
||||||
x2, y2 = path[-1]
|
x2, y2 = path[-1]
|
||||||
index.remove((x1, y1, path, False))
|
index.remove((x1, y1, path, False))
|
||||||
|
|
|
@ -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)
|
|
@ -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()
|
|
@ -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)
|
Loading…
Reference in New Issue