#!/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 += '\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 += '\n' % turtle.to_s() else: MARKS += '\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 += '\n' % turtle.to_s() elif layer=='plug_mark': MARKS += '\n' % turtle.to_s() else: SHAPES += '\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 += '\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 += '\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 += '\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 += '\n' % (x, y, w, h) print('' % (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('', SHAPES, '', '', HOLES, '', '', MARKS, '', sep='\n') print('')