Compare commits
19 Commits
30da19afd6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dc45f27141 | |||
| c07dee1dd5 | |||
| 5888485b16 | |||
| cbc847ea42 | |||
| 9fa39a7a34 | |||
| 4c8ca5db03 | |||
| 33e8a83404 | |||
| 2f9c97913e | |||
| e169dfb9f5 | |||
| eeb07bfb44 | |||
| bcf34f8976 | |||
| 144f6d951e | |||
| d2a9a0b8e0 | |||
| bf1433afe7 | |||
| 5dd4fe4117 | |||
| 1159f28855 | |||
| 16e60427d1 | |||
| c19a1e8876 | |||
| d0e64ae692 |
336
battery-case-generator.py
Executable file
336
battery-case-generator.py
Executable file
@@ -0,0 +1,336 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import math
|
||||
import sys
|
||||
import argparse
|
||||
import svgturtle
|
||||
|
||||
parser = argparse.ArgumentParser(description='Generate a hexagonal battery case')
|
||||
parser.add_argument('--dimension', default=5, type=int, help='Grid dimension')
|
||||
parser.add_argument('--height', type=float, help='Height of battery')
|
||||
parser.add_argument('--hole', default='AA', help='Hole diameter (mm or A, AA, AAA)')
|
||||
parser.add_argument('--kerf', default=.1, type=float, help='Kerf')
|
||||
parser.add_argument('--corner-length', default=4, help='Length of stretch corner in material thicknesses')
|
||||
parser.add_argument('--stretch', default=1.05, type=float, help='Reduction factor of stretch material')
|
||||
parser.add_argument('--horizontal-finger', default=5.0, type=float, help='Width of horizontal fingers')
|
||||
parser.add_argument('--vertical-finger', default=15.0, type=float, help='Width of vertical fingers')
|
||||
parser.add_argument('--padding', default=1.5, type=float, help='Padding around holes')
|
||||
parser.add_argument('--outside-padding', default=4.0, type=float, help='Extra padding between holes and wall')
|
||||
parser.add_argument('--extra-height', default=2.0, type=float, help='Extra vertical space')
|
||||
parser.add_argument('--thickness', default=3.0, type=float, help='Thickness of material')
|
||||
parser.add_argument('--lid', default=0.2, type=float, help='How much extra play to give the lid')
|
||||
parser.add_argument('--tooth', default=0.8, type=float, help='How much to round the edges of the teeth')
|
||||
parser.add_argument('--flex-width', default=.5, type=float, help='Spacing (in material thickness) between flex lines')
|
||||
parser.add_argument('--flex-cut', default=5.0, type=float, help='Length (in material thickness) of flex cuts')
|
||||
parser.add_argument('--flex-gap', default=1.0, type=float, help='Gap (in material thickness) between flex cuts')
|
||||
parser.add_argument('--plug-play', default=0.8, type=float, help='How much smaller to make the plug than the hole')
|
||||
parser.add_argument('--verbose', action='store_true', help='Print computed parameter values')
|
||||
args = parser.parse_args()
|
||||
assert (args.dimension % 2) == 1
|
||||
|
||||
BATTERY = {
|
||||
'AAA': [10.5, 44.5],
|
||||
'AA': [14.5, 50.5],
|
||||
'C': [26.2, 50.0],
|
||||
'D': [34.2, 61.5],
|
||||
}
|
||||
|
||||
if args.hole in BATTERY:
|
||||
dim = BATTERY[args.hole]
|
||||
args.hole = dim[0]
|
||||
if args.height == None:
|
||||
args.height = dim[1]
|
||||
else:
|
||||
args.hole = float(args.hole)
|
||||
assert(args.height != None)
|
||||
|
||||
SUITABLE = False
|
||||
while not SUITABLE:
|
||||
args.kerf2 = args.kerf/2
|
||||
args.grid = args.hole+args.padding
|
||||
args.radius = args.hole/2
|
||||
args.corner = args.corner_length*args.thickness
|
||||
args.corner_s = args.corner*args.stretch
|
||||
args.corner_radius = 3*args.corner_s/math.pi
|
||||
args.plug_radius = args.corner_radius-args.thickness
|
||||
args.corner_inset = args.corner_s*math.sqrt(3)/math.pi
|
||||
args.plug_inset = args.plug_radius/math.sqrt(3)
|
||||
args.interior_edge = args.grid*args.dimension*0.5+args.outside_padding
|
||||
args.opening_edge = args.interior_edge-args.thickness
|
||||
args.plug_edge = min(args.opening_edge, .5*math.sqrt(3)*args.interior_edge)-args.plug_play
|
||||
args.disc_radius = 0.45*(math.sqrt(3)-1)*args.plug_edge+args.plug_inset
|
||||
args.exterior_edge = args.interior_edge+2.0*args.thickness
|
||||
args.interior_leg = (args.interior_edge-args.horizontal_finger-args.kerf)/2-args.corner_inset
|
||||
finger_length = args.interior_edge-2.0*args.corner_inset
|
||||
args.n_hor_fingers = max(int(finger_length/args.horizontal_finger/2), 1)
|
||||
args.exterior_leg = (finger_length-(2*args.n_hor_fingers-1)*args.horizontal_finger+args.kerf)/2
|
||||
args.wall_leg = (args.interior_edge-args.corner-(2*args.n_hor_fingers-1)*args.horizontal_finger+args.kerf)/2
|
||||
args.exterior_slot = (args.interior_edge-args.horizontal_finger+args.kerf)/2
|
||||
args.n_ver_fingers = int((args.height+args.extra_height)/args.vertical_finger)
|
||||
top_slot = args.extra_height+args.thickness+0.45*args.height
|
||||
args.slots = [top_slot, top_slot+15.0]
|
||||
if args.exterior_leg > 2:
|
||||
SUITABLE=True
|
||||
else:
|
||||
args.outside_padding += .5
|
||||
args.padding += .2 # Try again with more padding
|
||||
|
||||
if args.verbose:
|
||||
print(args, file=sys.stderr)
|
||||
|
||||
BOX = 2.0*args.exterior_edge+5.0
|
||||
DIMX = 3.0*BOX
|
||||
DIMY = 2.0*BOX+args.height+args.extra_height+3.0*args.thickness+5.0
|
||||
PI3 = math.pi/3
|
||||
|
||||
HOLES = ''
|
||||
SHAPES = ''
|
||||
MARKS = ''
|
||||
|
||||
def draw_grid(cx, cy):
|
||||
global HOLES
|
||||
for row in range(-int(args.dimension/2), int((args.dimension+1)/2)):
|
||||
cyr = cy+args.grid*row*math.sin(PI3)
|
||||
num_col = args.dimension-abs(row)
|
||||
cxr = cx-.5*args.grid*(num_col-1.0)
|
||||
for col in range(num_col):
|
||||
HOLES += '<circle cx="%.2f" cy="%.2f" r="%.2f"/>\n' % (cxr+col*args.grid, cyr, args.radius)
|
||||
|
||||
def draw_disc(cx, cy, layer):
|
||||
global HOLES, SHAPES, MARKS
|
||||
turtle = svgturtle.SvgTurtle(cx, cy)
|
||||
turtle.penup()
|
||||
turtle.forward(args.disc_radius)
|
||||
turtle.pendown()
|
||||
turtle.left(90)
|
||||
turtle.circle(args.disc_radius)
|
||||
turtle.penup()
|
||||
turtle.home()
|
||||
if layer=='disc':
|
||||
HOLES += '<path d="%s"/>\n' % turtle.to_s()
|
||||
else:
|
||||
MARKS += '<path d="%s"/>\n' % turtle.to_s()
|
||||
|
||||
def draw_plane(cx, cy, layer):
|
||||
global HOLES, SHAPES, MARKS
|
||||
if layer=='interior':
|
||||
edge = args.interior_edge
|
||||
elif layer=='opening':
|
||||
edge = args.opening_edge
|
||||
elif layer=='plug' or layer=='plug_mark':
|
||||
edge = args.plug_edge
|
||||
else:
|
||||
edge = args.exterior_edge
|
||||
turtle = svgturtle.SvgTurtle(cx, cy)
|
||||
turtle.penup()
|
||||
if layer=='plug_mark':
|
||||
turtle.right(30)
|
||||
turtle.forward(edge)
|
||||
turtle.right(120)
|
||||
if layer=='interior':
|
||||
turtle.forward(args.corner_inset)
|
||||
turtle.pendown()
|
||||
for side in range(5):
|
||||
turtle.forward(args.interior_leg)
|
||||
turtle.left(90)
|
||||
turtle.forward(args.thickness)
|
||||
turtle.right(90)
|
||||
turtle.forward(args.horizontal_finger+args.kerf)
|
||||
turtle.right(90)
|
||||
turtle.forward(args.thickness)
|
||||
turtle.left(90)
|
||||
turtle.forward(args.interior_leg)
|
||||
turtle.circle(-args.corner_radius, 60)
|
||||
turtle.forward(edge-2.0*args.corner_inset)
|
||||
turtle.circle(-args.corner_radius, 60)
|
||||
elif layer=='plug' or layer=='plug_mark':
|
||||
turtle.pendown()
|
||||
for side in range(3):
|
||||
turtle.forward(0.5*edge)
|
||||
turtle.right(90)
|
||||
turtle.circle(0.5*edge, 120)
|
||||
turtle.right(90)
|
||||
turtle.forward(0.5*edge)
|
||||
turtle.right(60)
|
||||
else:
|
||||
turtle.pendown()
|
||||
for side in range(6):
|
||||
turtle.forward(edge)
|
||||
turtle.right(60)
|
||||
if layer=='plug':
|
||||
HOLES += '<path d="%s"/>\n' % turtle.to_s()
|
||||
elif layer=='plug_mark':
|
||||
MARKS += '<path d="%s"/>\n' % turtle.to_s()
|
||||
else:
|
||||
SHAPES += '<path d="%s"/>\n' % turtle.to_s()
|
||||
turtle.reset()
|
||||
turtle.penup()
|
||||
if layer=='bottom' or layer=='rim':
|
||||
turtle.forward(args.interior_edge)
|
||||
turtle.right(120)
|
||||
turtle.forward(args.corner_inset)
|
||||
for side in range(5):
|
||||
for finger in range(args.n_hor_fingers):
|
||||
turtle.forward(args.exterior_leg if finger == 0 else args.horizontal_finger+args.kerf)
|
||||
turtle.pendown()
|
||||
turtle.left(90)
|
||||
turtle.forward(args.thickness)
|
||||
turtle.right(90)
|
||||
turtle.forward(args.horizontal_finger-args.kerf)
|
||||
turtle.right(90)
|
||||
turtle.forward(args.thickness)
|
||||
turtle.right(90)
|
||||
turtle.forward(args.horizontal_finger-args.kerf)
|
||||
turtle.penup()
|
||||
turtle.right(180)
|
||||
turtle.forward(args.horizontal_finger-args.kerf)
|
||||
turtle.forward(args.exterior_leg)
|
||||
turtle.pendown()
|
||||
turtle.left(90)
|
||||
turtle.forward(args.thickness)
|
||||
turtle.right(90)
|
||||
turtle.circle(-args.corner_radius-args.thickness, 60)
|
||||
turtle.right(90)
|
||||
turtle.forward(args.thickness)
|
||||
turtle.right(90)
|
||||
turtle.circle(args.corner_radius, 60)
|
||||
turtle.penup()
|
||||
turtle.right(180)
|
||||
turtle.circle(-args.corner_radius, 60)
|
||||
turtle.forward(args.interior_edge-2*args.corner_inset)
|
||||
turtle.pendown()
|
||||
turtle.left(90)
|
||||
turtle.forward(args.thickness)
|
||||
turtle.right(90)
|
||||
turtle.circle(-args.corner_radius-args.thickness, 60)
|
||||
turtle.right(90)
|
||||
turtle.forward(args.thickness)
|
||||
turtle.right(90)
|
||||
turtle.circle(args.corner_radius, 60)
|
||||
HOLES += '<path d="%s"/>\n' % turtle.to_s()
|
||||
|
||||
def draw_flex(t, h):
|
||||
global HOLES
|
||||
turtle = svgturtle.SvgTurtle(t.x, t.y)
|
||||
gap = args.flex_gap*args.thickness
|
||||
ncut = max(int((h-gap) // (args.flex_cut*args.thickness)), 1)
|
||||
cut = ((h-gap) / ncut) - gap
|
||||
dx = args.flex_width*args.thickness
|
||||
nlines = int(args.corner // dx)
|
||||
x0 = .5*(args.corner - (nlines-1)*dx)
|
||||
|
||||
turtle.forward(x0)
|
||||
for line in range(nlines):
|
||||
turtle.pendown()
|
||||
if (line % 2) == 0:
|
||||
turtle.right(90)
|
||||
turtle.forward(gap+cut)
|
||||
for section in range(ncut-2):
|
||||
turtle.penup()
|
||||
turtle.forward(gap)
|
||||
turtle.pendown()
|
||||
turtle.forward(gap+2*cut)
|
||||
turtle.penup()
|
||||
turtle.forward(gap)
|
||||
if (ncut % 2) == 0:
|
||||
turtle.pendown()
|
||||
turtle.forward(gap+cut)
|
||||
turtle.penup()
|
||||
turtle.left(90)
|
||||
else:
|
||||
turtle.left(90)
|
||||
if (ncut % 2) == 1:
|
||||
turtle.forward(gap+cut)
|
||||
for section in range(ncut-1-(ncut % 2)):
|
||||
turtle.penup()
|
||||
turtle.forward(gap)
|
||||
turtle.pendown()
|
||||
turtle.forward(gap+2*cut)
|
||||
turtle.penup()
|
||||
turtle.forward(gap)
|
||||
turtle.right(90)
|
||||
turtle.forward(dx)
|
||||
HOLES += '<path d="%s"/>\n' % turtle.to_s()
|
||||
|
||||
def draw_case_h(turtle, h, top):
|
||||
turtle.forward(0.5*args.interior_edge-0.5*args.corner)
|
||||
for side in range(6):
|
||||
turtle.left(90)
|
||||
turtle.forward(args.thickness)
|
||||
turtle.right(90)
|
||||
if top:
|
||||
draw_flex(turtle, h+2*args.thickness)
|
||||
turtle.forward(args.corner)
|
||||
turtle.right(90)
|
||||
turtle.forward(args.thickness)
|
||||
turtle.left(90)
|
||||
if side == 5:
|
||||
break
|
||||
turtle.forward(args.wall_leg-0.5*args.kerf)
|
||||
for finger in range(args.n_hor_fingers):
|
||||
if finger > 0:
|
||||
turtle.forward(args.horizontal_finger-args.kerf)
|
||||
turtle.left(90)
|
||||
turtle.forward(args.thickness)
|
||||
turtle.right(90)
|
||||
turtle.forward(args.horizontal_finger+args.kerf)
|
||||
turtle.right(90)
|
||||
turtle.forward(args.thickness)
|
||||
turtle.left(90)
|
||||
turtle.forward(args.wall_leg-0.5*args.kerf)
|
||||
turtle.forward(0.5*args.interior_edge-0.5*args.corner)
|
||||
|
||||
def draw_case_v(turtle, h):
|
||||
ey = turtle.y+h
|
||||
leg = (h-(args.n_ver_fingers-0.66)*args.vertical_finger)/2.0
|
||||
slope = args.vertical_finger/math.sqrt(18)
|
||||
turtle.forward(leg)
|
||||
for finger in range(args.n_ver_fingers):
|
||||
ty = ey if finger==args.n_ver_fingers-1 else turtle.y+args.vertical_finger
|
||||
turtle.circle(args.tooth, 135)
|
||||
turtle.forward(slope)
|
||||
turtle.circle(-args.tooth, 135)
|
||||
turtle.forward(0.66*args.vertical_finger)
|
||||
turtle.circle(-args.tooth, 135)
|
||||
turtle.forward(slope)
|
||||
turtle.circle(args.tooth, 135)
|
||||
turtle.forward(ty-turtle.y)
|
||||
|
||||
def draw_case(x0, y0, h, slots):
|
||||
global HOLES, SHAPES
|
||||
turtle = svgturtle.SvgTurtle(x0, y0+args.thickness)
|
||||
draw_case_h(turtle, h, True)
|
||||
turtle.right(90)
|
||||
draw_case_v(turtle, h)
|
||||
turtle.right(90)
|
||||
draw_case_h(turtle, h, False)
|
||||
turtle.penup()
|
||||
turtle.left(90)
|
||||
turtle.back(h)
|
||||
turtle.pendown()
|
||||
draw_case_v(turtle, h)
|
||||
SHAPES += '<path d="%s"/>\n' % turtle.to_s()
|
||||
for slot in slots:
|
||||
for side in range(5):
|
||||
x = x0+(side+.5)*args.interior_edge+args.exterior_slot
|
||||
y = y0+slot
|
||||
w = args.horizontal_finger-args.kerf
|
||||
h = args.thickness-args.kerf
|
||||
HOLES += '<rect x="%.2f" y="%.2f" width="%.2f" height="%.2f"/>\n' % (x, y, w, h)
|
||||
|
||||
print('<svg viewBox="0 0 %.2f %.2f" width="%.2fmm" height="%.2fmm" stroke-width="0.1" xmlns="http://www.w3.org/2000/svg">' % (DIMX, DIMY, DIMX, DIMY))
|
||||
draw_grid(0.5*BOX, 0.5*BOX)
|
||||
draw_plane(0.5*BOX, 0.5*BOX, 'interior')
|
||||
draw_grid(1.5*BOX, 0.5*BOX)
|
||||
draw_plane(1.5*BOX, 0.5*BOX, 'interior')
|
||||
draw_plane(0.5*BOX, 1.5*BOX, 'bottom')
|
||||
draw_plane(1.5*BOX, 1.5*BOX, 'rim')
|
||||
draw_plane(1.5*BOX, 1.5*BOX, 'opening')
|
||||
draw_disc(1.5*BOX, 1.5*BOX, 'disc')
|
||||
draw_plane(2.5*BOX, 0.5*BOX, 'plug')
|
||||
draw_plane(2.5*BOX, 1.5*BOX, 'lid')
|
||||
draw_plane(2.5*BOX, 1.5*BOX, 'plug_mark')
|
||||
draw_disc(2.5*BOX, 1.5*BOX, 'disc_mark')
|
||||
draw_case(0.05*args.grid*args.dimension, 2.0*BOX, args.height+args.extra_height+args.thickness, args.slots)
|
||||
print('<g fill="none" stroke="black">', SHAPES, '</g>', '<g fill="none" stroke="red">', HOLES, '</g>', '<g fill="none" stroke="blue">', MARKS, '</g>', sep='\n')
|
||||
print('</svg>')
|
||||
93
pincushion.py
Executable file
93
pincushion.py
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import math
|
||||
import sys
|
||||
|
||||
M=2
|
||||
N=5
|
||||
L=20.0
|
||||
B=2.5
|
||||
|
||||
def frange(*args):
|
||||
"""frange([start, ] end [, step [, mode]]) -> generator
|
||||
|
||||
A float range generator. If not specified, the default start is 0.0
|
||||
and the default step is 1.0.
|
||||
|
||||
Optional argument mode sets whether frange outputs an open or closed
|
||||
interval. mode must be an int. Bit zero of mode controls whether start is
|
||||
included (on) or excluded (off); bit one does the same for end. Hence:
|
||||
|
||||
0 -> open interval (start and end both excluded)
|
||||
1 -> half-open (start included, end excluded)
|
||||
2 -> half open (start excluded, end included)
|
||||
3 -> closed (start and end both included)
|
||||
|
||||
By default, mode=1 and only start is included in the output.
|
||||
"""
|
||||
mode = 1 # Default mode is half-open.
|
||||
n = len(args)
|
||||
if n == 1:
|
||||
args = (0.0, args[0], 1.0)
|
||||
elif n == 2:
|
||||
args = args + (1.0,)
|
||||
elif n == 4:
|
||||
mode = args[3]
|
||||
args = args[0:3]
|
||||
elif n != 3:
|
||||
raise TypeError('frange expects 1-4 arguments, got %d' % n)
|
||||
assert len(args) == 3
|
||||
try:
|
||||
start, end, step = [a + 0.0 for a in args]
|
||||
except TypeError:
|
||||
raise TypeError('arguments must be numbers')
|
||||
if step == 0.0:
|
||||
raise ValueError('step must not be zero')
|
||||
if not isinstance(mode, int):
|
||||
raise TypeError('mode must be an int')
|
||||
if mode & 1:
|
||||
i, x = 0, start
|
||||
else:
|
||||
i, x = 1, start+step
|
||||
if step > 0:
|
||||
if mode & 2:
|
||||
from operator import le as comp
|
||||
else:
|
||||
from operator import lt as comp
|
||||
else:
|
||||
if mode & 2:
|
||||
from operator import ge as comp
|
||||
else:
|
||||
from operator import gt as comp
|
||||
while comp(x, end):
|
||||
yield x
|
||||
i += 1
|
||||
x = start + i*step
|
||||
|
||||
def cushion(x0, y0, hole, space):
|
||||
yoff = .866*space # sqrt(3)/2
|
||||
xodd = list(frange(x0+B, x0+L-B, space, 3))
|
||||
xeven = list(frange(x0+B+.5*space, x0+L-B, space, 3))
|
||||
row = 0
|
||||
for y in frange(y0+B, y0+L-B, yoff, 3):
|
||||
row += 1
|
||||
if (row & 1) == 1:
|
||||
xrow = xodd
|
||||
else:
|
||||
xrow = xeven
|
||||
for x in xrow:
|
||||
print('<circle cx="{}" cy="{}" r="{}" stroke="black" fill="none"/>'.format(x, y, hole))
|
||||
|
||||
|
||||
print('<svg viewBox="-1 -1 101 41" width="102mm" height="42mm" stroke-width="0.1" xmlns="http://www.w3.org/2000/svg">')
|
||||
print('<rect height="{}" width="{}" stroke="black" fill="none"/>'.format(M*L, N*L))
|
||||
for i in range(1,M):
|
||||
print('<line x1="0" y1="{}" x2="{}" y2="{}" stroke="red"/>'.format(i*L, N*L, i*L))
|
||||
for j in range(1,N):
|
||||
print('<line x1="{}" y1="0" x2="{}" y2="{}" stroke="red"/>'.format(j*L, j*L, M*L))
|
||||
HOLE = [0.1, 0.05]
|
||||
SPACE = [2.0, 1.5, 1.0, 0.75, 0.5]
|
||||
for i in range(M):
|
||||
for j in range(N):
|
||||
cushion(j*L, i*L, HOLE[i], SPACE[j])
|
||||
print('</svg>')
|
||||
131
svgturtle.py
Executable file
131
svgturtle.py
Executable file
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
"""svgturtle - simple library to generate SVG paths from turtle graphics style commands"""
|
||||
|
||||
import math
|
||||
import sys
|
||||
|
||||
class SvgTurtle():
|
||||
def __init__(self, homex=0, homey=0):
|
||||
"""Create a new turtle at the given home location, facing right"""
|
||||
self.homex = homex
|
||||
self.homey = homey
|
||||
self.cvtangle = math.tau/360
|
||||
self.reset()
|
||||
|
||||
def penup(self):
|
||||
"""Subsequent movements will be invisible and only affect location and heading"""
|
||||
self.pen = False
|
||||
|
||||
def pendown(self):
|
||||
"""Subsequent movements will be visible and drawn"""
|
||||
self.pen = True
|
||||
|
||||
def forward(self, distance):
|
||||
"""Move forward"""
|
||||
if self.pen and (self.path == ''):
|
||||
self.path = "M %.2f,%.2f" % (self.x,self.y)
|
||||
dx = distance*math.cos(self.heading)
|
||||
dy = distance*math.sin(self.heading)
|
||||
self.x += dx
|
||||
self.y += dy
|
||||
if self.pen:
|
||||
if abs(dy) < .01:
|
||||
self.path += " h %.2f" % dx
|
||||
elif abs(dx) < .01:
|
||||
self.path += " v %.2f" % dy
|
||||
else:
|
||||
self.path += " l %.2f,%.2f" % (dx, dy)
|
||||
elif self.path != '':
|
||||
self.path += " m %.2f, %.2f" % (dx, dy)
|
||||
|
||||
def back(self, distance):
|
||||
"""Move backward"""
|
||||
self.forward(-distance)
|
||||
|
||||
def left(self, angle):
|
||||
"""Turn left by angle specified in degrees"""
|
||||
self.right(-angle)
|
||||
|
||||
def right(self, angle):
|
||||
"""Turn right by angle specified in degrees"""
|
||||
self.heading = (self.heading + angle*self.cvtangle) % math.tau
|
||||
|
||||
def circle(self, radius, extent=360, steps=None):
|
||||
"""Draw a circle or arc spanning extent degrees around a center
|
||||
radius units to the left (if radius is positive) or right
|
||||
(if radius is negative). Use a polygon if steps is specified,
|
||||
otherwise a circle."""
|
||||
if steps:
|
||||
w = 1.0*extent/steps
|
||||
w2 = 0.5*w
|
||||
l = 2.0*radius*math.sin(w2*math.pi/180.0)
|
||||
if radius < 0:
|
||||
l, w, w2 = -l, -w, -w2
|
||||
self.left(w2)
|
||||
for i in range(steps):
|
||||
self.forward(l)
|
||||
self.left(w)
|
||||
self.right(w2)
|
||||
else:
|
||||
if extent>355:
|
||||
self.circle(radius, 355)
|
||||
extent -= 355
|
||||
ra = self.cvtangle*(extent if radius < 0 else -extent)
|
||||
cx = self.x+radius*math.cos(self.heading-.5*math.pi)
|
||||
cy = self.y+radius*math.sin(self.heading-.5*math.pi)
|
||||
th = self.heading+.5*math.pi+ra
|
||||
dx = cx+radius*math.cos(th)-self.x
|
||||
dy = cy+radius*math.sin(th)-self.y
|
||||
lg = 1 if extent >= 180 else 0
|
||||
sw = 0 if radius > 0 else 1
|
||||
if self.pen:
|
||||
if self.path == '':
|
||||
self.path = "M %.2f,%.2f" % (self.x,self.y)
|
||||
self.path += " a %.2f %.2f %.2f %d %d %.2f %.2f" % (radius, radius, extent, lg, sw, dx, dy)
|
||||
elif self.path != '':
|
||||
self.path += " m %.2f, %.2f" % (dx, dy)
|
||||
self.x += dx
|
||||
self.y += dy
|
||||
self.heading = (self.heading + ra) % math.tau
|
||||
|
||||
|
||||
def to_s(self):
|
||||
"""Return the generated path, suitable for the d attribute of an SVG <path> element"""
|
||||
return self.path
|
||||
|
||||
def home(self):
|
||||
"""Reset to the initial position and heading"""
|
||||
self.x = self.homex
|
||||
self.y = self.homey
|
||||
self.heading = 0
|
||||
if self.path != '':
|
||||
self.path += " M %.2f, %.2f" % (self.x, self.y)
|
||||
|
||||
def reset(self):
|
||||
"""Clear the path and return home"""
|
||||
self.path = ''
|
||||
self.pen = True
|
||||
self.home()
|
||||
|
||||
if __name__ == "__main__":
|
||||
turtle = SvgTurtle(50, 100)
|
||||
turtle.left(90)
|
||||
turtle.forward(50)
|
||||
turtle.left(30)
|
||||
turtle.forward(50)
|
||||
turtle.penup()
|
||||
turtle.back(50)
|
||||
turtle.right(60)
|
||||
turtle.pendown()
|
||||
turtle.forward(50)
|
||||
turtle.penup()
|
||||
turtle.back(50)
|
||||
turtle.left(30)
|
||||
turtle.back(50)
|
||||
turtle.right(90)
|
||||
turtle.forward(70)
|
||||
turtle.pendown()
|
||||
turtle.circle(25)
|
||||
print('<svg viewBox="0 0 170 110" xmlns="http://www.w3.org/2000/svg">')
|
||||
print('<path fill="none" stroke="blue" d="%s"/>' % turtle.to_s())
|
||||
print('</svg>')
|
||||
43
svgturtletest.py
Executable file
43
svgturtletest.py
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import svgturtle
|
||||
|
||||
print('<svg viewBox="0 0 2000 2000" xmlns="http://www.w3.org/2000/svg">')
|
||||
turtle_tl = svgturtle.SvgTurtle(500, 750)
|
||||
turtle_tr = svgturtle.SvgTurtle(1500, 750)
|
||||
turtle_bl = svgturtle.SvgTurtle(500, 1000)
|
||||
turtle_br = svgturtle.SvgTurtle(1500, 1000)
|
||||
ANGLE=0
|
||||
for i in range(7):
|
||||
turtle_tl.home()
|
||||
turtle_tl.left(ANGLE)
|
||||
turtle_tl.circle(110+i*50, (i+1)*45, 50)
|
||||
turtle_bl.home()
|
||||
turtle_bl.right(ANGLE)
|
||||
turtle_bl.circle(-(110+i*50), (i+1)*45, 50)
|
||||
turtle_tr.home()
|
||||
turtle_tr.right(ANGLE)
|
||||
turtle_tr.circle(140+i*50, (i+1)*45, 50)
|
||||
turtle_br.home()
|
||||
turtle_br.left(ANGLE)
|
||||
turtle_br.circle(-(140+i*50), (i+1)*45, 50)
|
||||
print('<path fill="none" stroke="black" d="%s %s %s %s"/>' % (turtle_tl.to_s(), turtle_tr.to_s(), turtle_bl.to_s(), turtle_br.to_s()))
|
||||
turtle_tl.reset()
|
||||
turtle_tr.reset()
|
||||
turtle_bl.reset()
|
||||
turtle_br.reset()
|
||||
for i in range(8):
|
||||
turtle_tl.home()
|
||||
turtle_tl.left(ANGLE)
|
||||
turtle_tl.circle(100+i*50, (i+1)*45)
|
||||
turtle_bl.home()
|
||||
turtle_bl.right(ANGLE)
|
||||
turtle_bl.circle(-(100+i*50), (i+1)*45)
|
||||
turtle_tr.home()
|
||||
turtle_tr.right(ANGLE)
|
||||
turtle_tr.circle(130+i*50, (i+1)*45)
|
||||
turtle_br.home()
|
||||
turtle_br.left(ANGLE)
|
||||
turtle_br.circle(-(130+i*50), (i+1)*45)
|
||||
print('<path fill="none" stroke="blue" d="%s %s %s %s"/>' % (turtle_tl.to_s(), turtle_tr.to_s(), turtle_bl.to_s(), turtle_br.to_s()))
|
||||
print('</svg>')
|
||||
Reference in New Issue
Block a user