3 Commits

3 changed files with 232 additions and 0 deletions
Split View
  1. +85
    -0
      battery-case-generator.py
  2. +93
    -0
      pincushion.py
  3. +54
    -0
      svgturtle.py

+ 85
- 0
battery-case-generator.py View File

@@ -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>')

+ 93
- 0
pincushion.py View 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>')

+ 54
- 0
svgturtle.py View File

@@ -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()

Loading…
Cancel
Save