Author | SHA1 | Message | Date |
---|---|---|---|
|
16e60427d1 | Pincushion experiment | 7 months ago |
|
c19a1e8876 | Start battery case generator | 7 months ago |
|
d0e64ae692 | Add simple turtle graphics generator | 7 months ago |
@@ -0,0 +1,85 @@ | |||
#!/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('--horizontal-finger', default=10.0, type=float, help='Width of horizontal fingers') | |||
parser.add_argument('--vertical-finger', default=5.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=2, type=float, help='Extra padding between holes and wall') | |||
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') | |||
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) | |||
GRID = args.hole+args.padding | |||
RADIUS = args.hole/2 | |||
DIMX = 2*(args.dimension+2)*GRID | |||
DIMY = (args.dimension+2)*GRID | |||
PI3 = math.pi/3 | |||
def draw_grid(cx, cy): | |||
for row in range(-int(args.dimension/2), int((args.dimension+1)/2)): | |||
cyr = cy+GRID*row*math.sin(PI3) | |||
num_col = args.dimension-abs(row) | |||
cxr = cx-.5*GRID*(num_col-1.0) | |||
for col in range(num_col): | |||
print('<circle cx="%.2f" cy="%.2f" r="%.2f" stroke="black" fill="none"/>' % (cxr+col*GRID, cyr, RADIUS)) | |||
def draw_plane(cx, cy, interior=False): | |||
radius = GRID*args.dimension*0.5+args.outside_padding | |||
if not interior: | |||
radius += args.thickness | |||
turtle = svgturtle.SvgTurtle(cx, cy) | |||
turtle.penup() | |||
turtle.forward(radius) | |||
turtle.right(120) | |||
turtle.pendown() | |||
if interior: | |||
leg = (radius-args.horizontal_finger-args.kerf)/2 | |||
for side in range(6): | |||
turtle.forward(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(leg) | |||
turtle.right(60) | |||
else: | |||
for side in range(6): | |||
turtle.forward(radius) | |||
turtle.right(60) | |||
print('<path d="%s" fill="none" stroke="black"/>' % turtle.to_s()) | |||
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(GRID*(0.5*args.dimension+1), GRID*(0.5*args.dimension+1)) | |||
draw_plane(GRID*(0.5*args.dimension+1), GRID*(0.5*args.dimension+1), True) | |||
draw_plane(GRID*(1.5*args.dimension+3), GRID*(0.5*args.dimension+1)) | |||
print('</svg>') |
@@ -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>') |
@@ -0,0 +1,54 @@ | |||
import math | |||
import sys | |||
class SvgTurtle(): | |||
def __init__(self, homex=0, homey=0): | |||
self.homex = homex | |||
self.homey = homey | |||
self.cvtangle = math.tau/360 | |||
self.reset() | |||
def penup(self): | |||
self.pen = False | |||
def pendown(self): | |||
self.pen = True | |||
def forward(self, distance): | |||
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): | |||
self.forward(-distance) | |||
def left(self, angle): | |||
self.right(-angle) | |||
def right(self, angle): | |||
self.heading = (self.heading + angle*self.cvtangle) % math.tau | |||
def to_s(self): | |||
return self.path | |||
def home(self): | |||
self.x = self.homex | |||
self.y = self.homey | |||
self.heading = 0 | |||
def reset(self): | |||
self.path = '' | |||
self.pen = True | |||
self.home() |