turtle, util, dragon curve
This commit is contained in:
parent
d2ea5b6df2
commit
c0525511d2
|
@ -1,3 +1,5 @@
|
|||
from .device import Device
|
||||
from .drawing import Drawing
|
||||
from .planner import Planner
|
||||
from .turtle import Turtle
|
||||
from .util import draw
|
||||
|
|
|
@ -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)])
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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