def test_translate(): p = Pen() p.stroke_mode(1.0) p.move_to((0, 0)) p.turn_to(0) p.line_forward(3) p.arc_left(90, 3) p.turn_left(90) p.move_forward(3) p.fill_mode() p.circle(0.5) p.move_forward(3) p.square(1) p.paper.translate((1, 1)) assert_equal( p.paper.svg_elements(1), [ ( '<path d="M1.0,-1.5 L1.0,-0.5 L4.0,-0.5 A 3.5,3.5 0 0 0 ' '7.5,-4.0 L6.5,-4.0 A 2.5,2.5 0 0 1 4.0,-1.5 L1.0,-1.5 z" ' 'fill="#000000" />' ), ( '<path d="M4.5,-4.0 A 0.5,0.5 0 0 0 3.5,-4.0 ' 'A 0.5,0.5 0 0 0 4.5,-4.0 z" fill="#000000" />' ), ( '<path d="M0.5,-3.5 L1.5,-3.5 L1.5,-4.5 L0.5,-4.5 L0.5,-3.5 z" ' 'fill="#000000" />' ), ] )
def test_mirror_end_slant(): paper = Paper() p = Pen() p.stroke_mode(sqrt2) p.move_to((0, 0)) p.turn_to(-45) p.line_forward(5 * sqrt2, end_slant=45) p.paper.mirror_x(0) paper.merge(p.paper) p = Pen() p.stroke_mode(sqrt2) p.move_to((0, 0)) p.turn_to(45) p.line_forward(5 * sqrt2) paper.merge(p.paper) paper.join_paths() paper.fuse_paths() assert_path_data( paper, 1, 'M-5.5,4.5 L-4.5,5.5 L5.5,-4.5 L4.5,-5.5 L-5.5,4.5 z' )
def test_translate(): p = Pen() p.stroke_mode(1.0) p.move_to((0, 0)) p.turn_to(0) p.line_forward(3) p.arc_left(90, 3) p.turn_left(90) p.move_forward(3) p.fill_mode() p.circle(0.5) p.move_forward(3) p.square(1) p.paper.translate((1, 1)) assert_equal(p.paper.svg_elements(1), [ ('<path d="M1.0,-1.5 L1.0,-0.5 L4.0,-0.5 A 3.5,3.5 0 0 0 ' '7.5,-4.0 L6.5,-4.0 A 2.5,2.5 0 0 1 4.0,-1.5 L1.0,-1.5 z" ' 'fill="#000000" />'), ('<path d="M4.5,-4.0 A 0.5,0.5 0 0 0 3.5,-4.0 ' 'A 0.5,0.5 0 0 0 4.5,-4.0 z" fill="#000000" />'), ('<path d="M0.5,-3.5 L1.5,-3.5 L1.5,-4.5 L0.5,-4.5 L0.5,-3.5 z" ' 'fill="#000000" />'), ])
def test_center_on_xy(): p = Pen() p.stroke_mode(2.0) p.move_to((0, 0)) p.turn_to(0) p.line_forward(4) p.move_to((2, 1)) p.circle(1) p.paper.center_on_x(0) assert_equal(p.paper.svg_elements(0), [ '<path d="M-2,-1 L-2,1 L2,1 L2,-1 L-2,-1 z" fill="#000000" />', '<path d="M2,-1 A 2,2 0 0 0 -2,-1 A 2,2 0 0 0 2,-1 z" fill="#000000" />', ]) p.paper.center_on_y(0) assert_equal(p.paper.svg_elements(1), [ ('<path d="M-2.0,0.0 L-2.0,2.0 L2.0,2.0 L2.0,0.0 L-2.0,0.0 z" ' 'fill="#000000" />'), ('<path d="M2.0,0.0 A 2.0,2.0 0 0 0 -2.0,0.0 ' 'A 2.0,2.0 0 0 0 2.0,0.0 z" fill="#000000" />'), ])
def test_copy_loop(): p = Pen() p.stroke_mode(0.2) def square(): p.turn_to(180) p.line_forward(1) p.turn_left(90) p.line_forward(1) p.turn_left(90) p.line_forward(1) p.turn_left(90) p.line_forward(1) p.move_to((0, 0)) square() p = p.copy(paper=True) p.move_to((2, 0)) square() assert_path_data( p, 1, ( 'M0.1,-0.1 L-1.1,-0.1 L-1.1,1.1 L0.1,1.1 L0.1,-0.1 z ' 'M-0.1,0.1 L-0.1,0.9 L-0.9,0.9 L-0.9,0.1 L-0.1,0.1 z ' 'M2.1,-0.1 L0.9,-0.1 L0.9,1.1 L2.1,1.1 L2.1,-0.1 z ' 'M1.9,0.1 L1.9,0.9 L1.1,0.9 L1.1,0.1 L1.9,0.1 z' ) )
def test_center_on_xy(): p = Pen() p.stroke_mode(2.0) p.move_to((0, 0)) p.turn_to(0) p.line_forward(4) p.move_to((2, 1)) p.circle(1) p.paper.center_on_x(0) assert_equal( p.paper.svg_elements(0), [ '<path d="M-2,-1 L-2,1 L2,1 L2,-1 L-2,-1 z" fill="#000000" />', '<path d="M2,-1 A 2,2 0 0 0 -2,-1 A 2,2 0 0 0 2,-1 z" fill="#000000" />', ] ) p.paper.center_on_y(0) assert_equal( p.paper.svg_elements(1), [ ( '<path d="M-2.0,0.0 L-2.0,2.0 L2.0,2.0 L2.0,0.0 L-2.0,0.0 z" ' 'fill="#000000" />' ), ( '<path d="M2.0,0.0 A 2.0,2.0 0 0 0 -2.0,0.0 ' 'A 2.0,2.0 0 0 0 2.0,0.0 z" fill="#000000" />' ), ] )
def test_fuse_with_joint(): p = Pen() p.stroke_mode(2.0) p.move_to((0, 0)) p.turn_to(180) p.line_forward(5) p.turn_left(90) p.line_forward(5) p.break_stroke() p.move_to((0, 0)) p.turn_to(0) p.line_forward(5) assert_path_data(p, 0, [ 'M0,1 L0,-1 L-6,-1 L-6,5 L-4,5 L-4,1 L0,1 z', 'M0,-1 L0,1 L5,1 L5,-1 L0,-1 z', ]) p.paper.join_paths() p.paper.fuse_paths() assert_path_data(p, 0, 'M-6,5 L-4,5 L-4,1 L5,1 L5,-1 L-6,-1 L-6,5 z')
def test_fuse_with_joint(): p = Pen() p.stroke_mode(2.0) p.move_to((0, 0)) p.turn_to(180) p.line_forward(5) p.turn_left(90) p.line_forward(5) p.break_stroke() p.move_to((0, 0)) p.turn_to(0) p.line_forward(5) assert_path_data( p, 0, [ 'M0,1 L0,-1 L-6,-1 L-6,5 L-4,5 L-4,1 L0,1 z', 'M0,-1 L0,1 L5,1 L5,-1 L0,-1 z', ] ) p.paper.join_paths() p.paper.fuse_paths() assert_path_data( p, 0, 'M-6,5 L-4,5 L-4,1 L5,1 L5,-1 L-6,-1 L-6,5 z' )
def test_copy_loop(): p = Pen() p.stroke_mode(0.2) def square(): p.turn_to(180) p.line_forward(1) p.turn_left(90) p.line_forward(1) p.turn_left(90) p.line_forward(1) p.turn_left(90) p.line_forward(1) p.move_to((0, 0)) square() p = p.copy(paper=True) p.move_to((2, 0)) square() assert_path_data(p, 1, ('M0.1,-0.1 L-1.1,-0.1 L-1.1,1.1 L0.1,1.1 L0.1,-0.1 z ' 'M-0.1,0.1 L-0.1,0.9 L-0.9,0.9 L-0.9,0.1 L-0.1,0.1 z ' 'M2.1,-0.1 L0.9,-0.1 L0.9,1.1 L2.1,1.1 L2.1,-0.1 z ' 'M1.9,0.1 L1.9,0.9 L1.1,0.9 L1.1,0.1 L1.9,0.1 z'))
def test_join_paths_thick(): # Segments join together if possible when join_paths is called. p = Pen() p.stroke_mode(2.0) p.move_to((0, 0)) p.turn_to(0) p.line_forward(5) p.break_stroke() p.turn_left(90) p.line_forward(5) p.paper.join_paths() assert_path_data(p, 0, 'M0,-1 L0,1 L6,1 L6,-5 L4,-5 L4,-1 L0,-1 z')
def draw_template_path(): pen = Pen() pen.stroke_mode(0.05, '#466184') pen.turn_to(0) pen.move_to((0, BOTTOM)) pen.line_forward(10) pen.move_to((0, MIDDLE)) pen.line_forward(10) pen.move_to((0, TOP)) pen.line_forward(10) pen.paper.center_on_x(0) return pen.paper
def test_fuse_paths(): # Create two halves of a stroke in the same direction. p = Pen() p.stroke_mode(sqrt2) p.move_to((-3, 3)) p.turn_to(-45) p.line_forward(3 * sqrt2, start_slant=0) p.line_forward(3 * sqrt2, end_slant=0) p.paper.fuse_paths() assert_path_data(p, 1, ['M-2.0,-3.0 L-4.0,-3.0 L2.0,3.0 L4.0,3.0 L-2.0,-3.0 z'])
def test_join_paths_thick(): # Segments join together if possible when join_paths is called. p = Pen() p.stroke_mode(2.0) p.move_to((0, 0)) p.turn_to(0) p.line_forward(5) p.break_stroke() p.turn_left(90) p.line_forward(5) p.paper.join_paths() assert_path_data( p, 0, 'M0,-1 L0,1 L6,1 L6,-5 L4,-5 L4,-1 L0,-1 z' )
def test_fuse_paths(): # Create two halves of a stroke in the same direction. p = Pen() p.stroke_mode(sqrt2) p.move_to((-3, 3)) p.turn_to(-45) p.line_forward(3 * sqrt2, start_slant=0) p.line_forward(3 * sqrt2, end_slant=0) p.paper.fuse_paths() assert_path_data( p, 1, ['M-2.0,-3.0 L-4.0,-3.0 L2.0,3.0 L4.0,3.0 L-2.0,-3.0 z'] )
def test_join_paths_turn_back_no_joint(): p = Pen() p.stroke_mode(1.0) p.move_to((0, 0)) p.turn_to(0) p.line_forward(10) p.turn_right(180) p.break_stroke() p.line_forward(5) p.paper.join_paths() line1, line2 = p.last_path().segments assert line1.end_joint_illegal assert line2.start_joint_illegal assert_path_data(p, 1, ('M0.0,-0.5 L0.0,0.5 L10.0,0.5 L10.0,-0.5 ' 'L5.0,-0.5 L5.0,0.5 L10.0,0.5 L10.0,-0.5 L0.0,-0.5 z'))
def test_copy_custom_cap(): # Regression test for a bug where doing pen.copy() in a cap function would # break outline drawing. p = Pen() p.stroke_mode(2.0) p.move_to((0, 0)) p.turn_to(0) p.line_forward(5) p.turn_left(90) p.line_forward(5) def copy_cap(pen, end): pen.copy() pen.line_to(end) p.last_segment().end_cap = copy_cap assert_path_data(p, 0, 'M0,-1 L0,1 L6,1 L6,-5 L4,-5 L4,-1 L0,-1 z')
def test_join_and_fuse_simple(): # Create two halves of a stroke in separate directions. p = Pen() p.stroke_mode(sqrt2) p.move_to((0, 0)) p.turn_to(-45) p.line_forward(3 * sqrt2, end_slant=0) p.break_stroke() p.move_to((0, 0)) p.turn_to(-45 + 180) p.line_forward(3 * sqrt2, end_slant=0) p.paper.join_paths() p.paper.fuse_paths() assert_path_data(p, 1, 'M2.0,3.0 L4.0,3.0 L-2.0,-3.0 L-4.0,-3.0 L2.0,3.0 z')
def test_copy_custom_cap(): # Regression test for a bug where doing pen.copy() in a cap function would # break outline drawing. p = Pen() p.stroke_mode(2.0) p.move_to((0, 0)) p.turn_to(0) p.line_forward(5) p.turn_left(90) p.line_forward(5) def copy_cap(pen, end): pen.copy() pen.line_to(end) p.last_segment().end_cap = copy_cap assert_path_data( p, 0, 'M0,-1 L0,1 L6,1 L6,-5 L4,-5 L4,-1 L0,-1 z' )
def test_join_paths_turn_back_no_joint(): p = Pen() p.stroke_mode(1.0) p.move_to((0, 0)) p.turn_to(0) p.line_forward(10) p.turn_right(180) p.break_stroke() p.line_forward(5) p.paper.join_paths() line1, line2 = p.last_path().segments assert line1.end_joint_illegal assert line2.start_joint_illegal assert_path_data( p, 1, ( 'M0.0,-0.5 L0.0,0.5 L10.0,0.5 L10.0,-0.5 ' 'L5.0,-0.5 L5.0,0.5 L10.0,0.5 L10.0,-0.5 L0.0,-0.5 z' ) )
def test_join_and_fuse_simple(): # Create two halves of a stroke in separate directions. p = Pen() p.stroke_mode(sqrt2) p.move_to((0, 0)) p.turn_to(-45) p.line_forward(3 * sqrt2, end_slant=0) p.break_stroke() p.move_to((0, 0)) p.turn_to(-45 + 180) p.line_forward(3 * sqrt2, end_slant=0) p.paper.join_paths() p.paper.fuse_paths() assert_path_data( p, 1, 'M2.0,3.0 L4.0,3.0 L-2.0,-3.0 L-4.0,-3.0 L2.0,3.0 z' )
def test_mirror_end_slant(): paper = Paper() p = Pen() p.stroke_mode(sqrt2) p.move_to((0, 0)) p.turn_to(-45) p.line_forward(5 * sqrt2, end_slant=45) p.paper.mirror_x(0) paper.merge(p.paper) p = Pen() p.stroke_mode(sqrt2) p.move_to((0, 0)) p.turn_to(45) p.line_forward(5 * sqrt2) paper.merge(p.paper) paper.join_paths() paper.fuse_paths() assert_path_data(paper, 1, 'M-5.5,4.5 L-4.5,5.5 L5.5,-4.5 L4.5,-5.5 L-5.5,4.5 z')
def draw(): p = Pen() # Draw sine waves in various widths. for width in [0.01, 0.1, 0.3, 0.5, 0.8, 1.0]: p.stroke_mode(width) func = sine_func_factory( amplitude=1.0, frequency=4 / math.pi, phase=0, ) p.parametric( func, start=0, end=10, step=0.1, ) # Next line. p.turn_to(-90) p.move_forward(1.0 + 2 * width) return p.paper
def test_line_segment_bounds(): # Fill mode segment. p = Pen() p.fill_mode() p.move_to((1, 0)) p.line_to((2, 3)) line = p.last_segment() assert_equal( line.bounds(), Bounds(1, 0, 2, 3) ) # Stroke mode segment. p = Pen() p.stroke_mode(sqrt2) p.move_to((0, 0)) p.line_to((5, 5)) line = p.last_segment() assert_equal( line.bounds(), Bounds(-0.5, -0.5, 5.5, 5.5) )
def test_arc_segment_bounds(): # Arc which occupies its entire circle. p = Pen() p.fill_mode() p.move_to((1, 0)) p.turn_to(90) p.arc_left(359, 1) arc = p.last_segment() assert_equal( arc.bounds(), Bounds(-1, -1, 1, 1) ) # Arc which pushes the boundary only with the endpoints. p = Pen() p.fill_mode() p.move_to((0, 0)) p.turn_to(30) p.move_forward(1) p.turn_left(90) p.arc_left(30, center=(0, 0)) arc = p.last_segment() assert_equal( arc.bounds(), Bounds(0.5, 0.5, sqrt3 / 2, sqrt3 / 2) ) # Arc which pushes the boundary with the middle in one spot. p = Pen() p.fill_mode() p.move_to((0, 0)) p.turn_to(-45) p.move_forward(1) p.turn_left(90) p.arc_left(90, center=(0, 0)) arc = p.last_segment() assert_equal( arc.bounds(), Bounds(sqrt2 / 2, -sqrt2 / 2, 1, sqrt2 / 2) ) # Arc which goes right. p = Pen() p.fill_mode() p.move_to((0, 0)) p.turn_to(45) p.arc_right(90, 3) arc = p.last_segment() assert_equal( arc.bounds(), Bounds(0, 0, 3 * sqrt2, 3 - 1.5 * sqrt2) ) # Arc which pushes the boundary with the middle in two spots. p = Pen() p.fill_mode() p.move_to((0, 0)) p.turn_to(-45) p.move_forward(1) p.turn_left(90) p.arc_left(180, center=(0, 0)) arc = p.last_segment() assert_equal( arc.bounds(), Bounds(-sqrt2 / 2, -sqrt2 / 2, 1, 1) ) # Half circle, right side p = Pen() p.fill_mode() p.move_to((0, 0)) p.turn_to(0) p.arc_right(180, 5) arc = p.last_segment() assert_equal( arc.bounds(), Bounds(0, -10, 5, 0) ) # Thick circle, p = Pen() p.stroke_mode(1.0) p.move_to((0, 0)) p.turn_to(0) p.move_forward(5) p.turn_left(90) p.arc_left(180, 5, start_slant=45) arc = p.last_segment() assert_equal( arc.bounds(), Bounds(-5.5, -0.5314980314970469, 5.5, 5.5) )
import math from canoepaddle import Pen from canoepaddle.heading import Heading, Angle p = Pen() p.paper.override_bounds(-120, -120, 120, 120) p.stroke_mode(1.0, '#15A') p.move_to((0.5, 0.5)) def f(n): a = 12 b = 0.03 c = 0.2 d = 1.5 e = 0.5 wobble = a * math.exp(-b * n) * math.sin(c * n + d * n**e) return ( Angle(-24 + wobble), Angle(24 + wobble), ) center_heading = Heading(90) center = p.position p.turn_to(center_heading) num_layers = 26 for layer in range(num_layers): lo, hi = f(layer)
continue else: point_occupancy[a] += 1 b = random.choice(points) if point_occupancy[b] >= 2: continue else: point_occupancy[b] += 1 yield a, b if __name__ == '__main__': while True: p = Pen() p.stroke_mode(0.01) for a, b in gen_lines(200, 100): p.move_to(a) p.line_to(b) p.break_stroke() try: p.paper.join_paths() except AssertionError: print(p.log()) break else: print(p.paper.format_svg(6, resolution=1000)) break
return numpy.column_stack((t, c, s)) def draw_parametric_func(pen, f, t_range): txy_values = f(t_range) t, x, y = txy_values[0] pen.move_to((x, y)) for t, x, y in txy_values[1:]: pen.line_to((x, y)) mod = t % 1.0 if float_equal(mod, 0) or float_equal(mod, 1.0): pen.circle(0.01) step = 0.01 t_range = numpy.arange(-4 + step, 4, step) pen = Pen() pen.stroke_mode(0.01, 'green') draw_parametric_func(pen, euler_spiral_parametric, t_range) pen.fill_mode('green') pen.move_to((0.5, 0.5)) pen.circle(0.01) pen.move_to((-0.5, -0.5)) pen.circle(0.01) print(pen.paper.format_svg(5, resolution=500)) # TODO: euler spiral solver to end at a particular point. newton-raphson method for root finding convergence?
def draw(): p = Pen() center_radius = 3.0 start_radius = radius = 100 start_width = width = 3.0 ratio = (1 / 2) ** (1/5) series = [] while radius > center_radius / sqrt2: series.append((radius, width)) radius *= ratio width *= ratio p.move_to((0, 0)) for radius, width in series: p.stroke_mode(width, 'black') p.circle(radius) # Parametric conic spirals. p.move_to((0, 0)) def spiral(theta): b = (1 / 2) ** (-2 / math.pi) r = start_radius * (b ** (-theta)) x = r * math.cos(theta) y = r * math.sin(theta) z = start_radius - r return (x, y, z) def spiral_top1(t): x, y, z = spiral(t) return x, y def spiral_top2(t): x, y, z = spiral(t) x = -x y = -y return x, y # Top spirals. p.stroke_mode(start_width, 'black') p.parametric(spiral_top1, 0, 4*math.pi, .1) p.parametric(spiral_top2, 0, 4*math.pi, .1) # Blank out the bottom triangle. p.fill_mode('white') p.move_to((0, 0)) s = start_radius + start_width p.line_to((-s, -s)) p.line_to((+s, -s)) p.line_to((0, 0)) # Horizontal lines for the bottom triangle. for radius, width in series: p.stroke_mode(width, 'black') p.move_to((-radius, -radius)) p.line_to( (+radius, -radius), start_slant=45, end_slant=-45, ) # Front spirals. def spiral_front1(t): x, y, z = spiral(t) return (x, z - start_radius) def spiral_front2(t): x, y, z = spiral(t) x = -x y = -y return (x, z - start_radius) p.move_to((0, 0)) p.stroke_mode(start_width, 'black') p.parametric(spiral_front1, 0, math.pi, .1) p.parametric(spiral_front2, math.pi, 2*math.pi, .1) p.parametric(spiral_front1, 2*math.pi, 3*math.pi, .1) # Fill in the center. p.move_to((0, 0)) p.fill_mode('black') p.circle(center_radius) return p.paper
from canoepaddle import Pen p = Pen() def trefoil(origin, radius, num_leaves, leaf_angle, step=1): p.turn_to(90) points = [] for i in range(num_leaves): p.move_to(origin) p.turn_right(360 / num_leaves) p.move_forward(radius) points.append(p.position) p.move_to(points[0]) for i in range(num_leaves): next_point = points[((i + 1) * step) % num_leaves] p.turn_toward(origin) p.turn_right(leaf_angle / 2) p.arc_to(next_point) p.stroke_mode(1.0, '#a00') trefoil((0, 0), 8, 3, 110) p.outline_mode(1.0, 0.1, '#111') trefoil((0, 0), 8, 3, 110) print(p.paper.format_svg())
return numpy.column_stack((t, c, s)) def draw_parametric_func(pen, f, t_range): txy_values = f(t_range) t, x, y = txy_values[0] pen.move_to((x, y)) for t, x, y in txy_values[1:]: pen.line_to((x, y)) mod = t % 1.0 if float_equal(mod, 0) or float_equal(mod, 1.0): pen.circle(0.01) step = 0.01 t_range = numpy.arange(-4 + step, 4, step) pen = Pen() pen.stroke_mode(0.01, 'green') draw_parametric_func(pen, euler_spiral_parametric, t_range) pen.fill_mode('green') pen.move_to((0.5, 0.5)) pen.circle(0.01) pen.move_to((-0.5, -0.5)) pen.circle(0.01) print(pen.paper.format_svg(5, resolution=500)) #TODO: euler spiral solver to end at a particular point. newton-raphson method for root finding convergence?
from canoepaddle import Pen p = Pen() def arm(inner=1.5, outer=3): p.stroke_mode(1.0) p.move_forward(inner) p.turn_right(90) p.arc_right(200, radius=outer) p.fill_mode() p.circle(0.5) # Makeshift round endcaps. orientation = 70 p.stroke_mode(1.0) p.move_to((0, 0)) p.circle(1.5) p.move_to((0, 0)) p.turn_to(orientation) arm() p.move_to((0, 0)) p.turn_to(180 + orientation) arm() print(p.paper.format_svg())
from canoepaddle import Pen p = Pen() p.stroke_mode(0.15) def trefoil(origin, radius, num_leaves, leaf_angle, step=1): p.turn_to(90) points = [] for i in range(num_leaves): p.move_to(origin) p.turn_right(360 / num_leaves) p.move_forward(radius) points.append(p.position) p.move_to(points[0]) for i in range(num_leaves): next_point = points[((i + 1) * step) % num_leaves] p.turn_toward(origin) p.turn_right(leaf_angle / 2) p.arc_to(next_point) trefoil((-6, 6), 3, 3, 110) trefoil((0, 6), 2.7, 4, 120) trefoil((6, 6), 2.7, 4, 70) trefoil((-6, 0), 2.7, 5, 70) trefoil((0, 0), 2.7, 5, 130) trefoil((6, 0), 2.7, 5, 110, step=2) trefoil((-6, -6), 2.7, 31, 20, step=14) trefoil((0, -6), 3, 8, 120, step=3)
from canoepaddle import Pen p = Pen() p.stroke_mode(0.15) def trefoil(origin, radius, num_leaves, leaf_angle, step=1): p.turn_to(90) points = [] for i in range(num_leaves): p.move_to(origin) p.turn_right(360 / num_leaves) p.move_forward(radius) points.append(p.position) p.move_to(points[0]) for i in range(num_leaves): next_point = points[((i + 1) * step) % num_leaves] p.turn_toward(origin) p.turn_right(leaf_angle / 2) p.arc_to(next_point) trefoil((-6, 6), 3, 3, 110) trefoil((0, 6), 2.7, 4, 120) trefoil((6, 6), 2.7, 4, 70) trefoil((-6, 0), 2.7, 5, 70) trefoil((0, 0), 2.7, 5, 130) trefoil((6, 0), 2.7, 5, 110, step=2) trefoil((-6, -6), 2.7, 31, 20, step=14) trefoil((0, -6), 3, 8, 120, step=3) trefoil((6, -6), 2.7, 30, -90, step=1)
import math from canoepaddle import Pen from canoepaddle.heading import Heading, Angle p = Pen() p.paper.override_bounds(-120, -120, 120, 120) p.stroke_mode(1.0, '#15A') p.move_to((0.5, 0.5)) def f(n): a = 12 b = 0.03 c = 0.2 d = 1.5 e = 0.5 wobble = a * math.exp(-b * n) * math.sin(c * n + d * n**e) return ( Angle(-24 + wobble), Angle(24 + wobble), ) center_heading = Heading(90) center = p.position p.turn_to(center_heading) num_layers = 26 for layer in range(num_layers):