#!/usr/bin/python3 import argparse parser = argparse.ArgumentParser(description='AMT Box') parser.add_argument('--material-thickness', type=float, default=17.4, help='Plywood/MDF thickness (mm)') parser.add_argument('--internal-height', type=float, default=350-18-18, help='Speaker internal height (mm)') parser.add_argument('--internal-width', type=float, default=200-18-18, help='Speaker internal width (mm)') parser.add_argument('--internal-depth', type=float, default=300, help='Speaker internal depth (mm)') parser.add_argument('--support-placement', type=float, default=160, help='Internal support placement (mm)') parser.add_argument('--support-width', type=float, default=80, help='Internal support width (mm)') # This is for Wavecor WF146WA05/06 parser.add_argument('--woofer-placement', type=float, default=200-18, help='Woofer placement (mm)') parser.add_argument('--woofer-cutout-diameter', type=float, default=119, help='Woofer cutout diameter (mm)') parser.add_argument('--woofer-mount-count', type=float, default=4, help='Count of woofer mounting holes (mm)') parser.add_argument('--woofer-mount-diameter', type=float, default=138, help='Diameter of woofer mounting holes (mm)') # Dynavox AMT-1 parser.add_argument('--tweeter-diameter', type=float, default=66, help='Tweeter diameter (mm)') parser.add_argument('--tweeter-placement', type=float, default=80-18, help='Tweeter placement (mm)') parser.add_argument('--tweeter-mount-count', type=float, default=4, help='Count of tweeter mounting holes (mm)') parser.add_argument('--tweeter-mount-diameter', type=float, default=79.165, help='Diameter of tweeter mounting holes (mm)') # Bass reflex port parser.add_argument('--port-enable', action="store_true", help="Enable cutting hole for bass reflex port") parser.add_argument('--port-mirror', action="store_true", help="Mirror port placement") parser.add_argument('--port-diameter', type=float, default=68, help='Bass reflex port diameter (mm)') parser.add_argument('--port-placement', type=float, default=80, help='Bass reflex port placement (mm)') parser.add_argument('--joint', type=float, default=5, help='Joint thickness (mm)') parser.add_argument('--joint-margin', type=float, default=0.2, help='Joint error margin (mm)') parser.add_argument('--safe-height', type=float, default=3, help='Safe height for moving the bit') parser.add_argument('--cut-bit-diameter', type=float, default=3, help='Cutting bit diameter (mm)') parser.add_argument('--bridge-thickness', type=float, default=2, help='Bridge thickness, set to 150% of plywood layer depth (mm)') parser.add_argument('--bridge-width', type=float, default=8, help='Bridge width (mm)') parser.add_argument('--pocket-bit-diameter', type=float, default=3, help='Pocket milling bit diameter (mm)') parser.add_argument('--pass-depth', type=float, default=6, help='Pocket milling depth per pass (mm)') parser.add_argument('--workpiece', default="front", help='Which side to generate (front, back, side, cap, support)') parser.add_argument('--swap-axes', action="store_true", help="Swap axes") GCODE_TOOL_CHANGE = """ G00 Z50.00000 (Retract) T2 M5 (Spindle stop.) G04 P%.1f (MSG, Change tool bit to drill size 2mm) M6 (Tool change.) M0 (Temporary machine stop.) M3 (Spindle on clockwise.) """ args = parser.parse_args() external_width = args.internal_width + 2*args.material_thickness external_height = args.internal_height + 2*args.material_thickness external_depth = args.internal_depth + 2*args.material_thickness bridge_depth = args.material_thickness - args.bridge_thickness print("; Plywood thickness:", args.material_thickness) print("; External dimensions: %.2fx%.2fx%.2f (mm)" % (external_width, external_height, external_depth)) print("; Internal volume: %.1fL" % (args.internal_width*args.internal_height*args.internal_depth*10**-6)) print("; Woofer diameter:", args.woofer_cutout_diameter) print("; Tweeter diameter:", args.tweeter_diameter) print("; Bass reflex port diameter:", args.port_diameter) print("; Bridge depth:", bridge_depth) # 2159 mm/min, 18k RPM and 3x6mm passes. GCODE_HEADER = """ G94 ( mm per min feed rate ) G21 ( metric system ) G90 ( absolute coordinates ) F2000 ( feedrate ) S20000 ( spindle rpm ) T1 G00 Z3.00000 ( retract ) X0 Y0 G64 P0.05080 ( set maximum deviation from commanded toolpath ) M3 ( spindle on clockwise ) G04 P0 ( dwell for no time -- G64 should not smooth over this point ) """ GCODE_FOOTER = """ G04 P0 ( dwell for no time -- G64 should not smooth over this point ) G00 Z50.000 ( retract ) M5 ( Spindle off. ) G04 P2.000000 M9 ( Coolant off. ) M2 ( Program end. ) """ import math # Primitives are swap aware def goto(x,y): if args.swap_axes: x, y = y, x print("X%03.02f Y%03.02f" % (x,y)) def drill(depth): print("G01 Z-%03.02f" % depth) def arc(x,y,r): cmd = 2 if args.swap_axes: x, y = y, x cmd = 3 print("G%02d X%03.02f Y%03.02f R%03.02f" % (cmd, x, y, r)) def retract(): print("G00 Z%03.02f ( retract )" % args.safe_height) # Higher level functions def mounting_holes(cx, cy, radius, count=4, offset=45): retract() for j in range(count): a = j*math.pi*2/count+offset*math.pi/180 goto(cx+math.cos(a)*radius, cy+math.sin(a)*radius) drill(8) retract() def cutout_circle(cx, cy, radius): retract() mr = args.cut_bit_diameter / 2 goto(cx+(radius-mr)*0.8, cy+(radius-mr)*0.6) # arc algab siit r = radius-mr dz = 0 while dz < args.material_thickness: dz += args.pass_depth if dz > args.material_thickness: dz = args.material_thickness+args.cut_bit_diameter/2 drill(dz) bridging = dz > bridge_depth # Right if bridging: drill(dz) arc(cx+r*0.8, cy-r*0.6, r) # kuhu jouab arc if bridging: drill(bridge_depth) # Bottom arc(cx+r*0.6, cy-r*0.8, r) # kuhu jouab arc if bridging: drill(dz) arc(cx-r*0.6, cy-r*0.8, r) # kuhu jouab arc if bridging: drill(bridge_depth) # Left arc(cx-r*0.8, cy-r*0.6, r) # arc algab siit if bridging: drill(dz) arc(cx-r*0.8, cy+r*0.6, r) # kuhu jouab arc if bridging: drill(bridge_depth) # Top arc(cx-r*0.6, cy+r*0.8, r) # arc algab siit if bridging: drill(dz) arc(cx+r*0.6, cy+r*0.8, r) # kuhu jouab arc if bridging: drill(bridge_depth) arc(cx+r*0.8, cy+r*0.6, r) def cutout_rect(x1, y1, x2, y2, outer=True): retract() if x2 < x1: x1, x2 = x2, x1 if y2 < y1: y1, y2 = y2, y1 w = x2-x1 h = y2-y1 radius = args.cut_bit_diameter / 2 if not outer: radius = - radius dz = 0 goto(x1-radius, y1-radius) while dz < args.material_thickness: dz += args.pass_depth if dz > args.material_thickness: dz = args.material_thickness+args.cut_bit_diameter/2 drill(dz) bridging = dz > bridge_depth for r in [radius]: # (radius*2, radius): # Left goto(x1-r, y1-r) if bridging: goto(x1-r, y1+h/2-args.bridge_width/2) drill(bridge_depth) goto(x1-r, y1+h/2+args.bridge_width/2) drill(dz) # Bottom goto(x1-r, y2+r) if bridging: goto(x1+w/2-args.bridge_width/2, y2+r) drill(bridge_depth) goto(x1+w/2+args.bridge_width/2, y2+r) drill(dz) # Right goto(x2+r, y2+r) if bridging: goto(x2+r, y1+h/2+args.bridge_width/2) drill(bridge_depth) goto(x2+r, y1+h/2-args.bridge_width/2) drill(dz) # Top goto(x2+r, y1-r) if bridging: goto(x1+w/2+args.bridge_width/2, y1-r) drill(bridge_depth) goto(x1+w/2-args.bridge_width/2, y1-r) drill(dz) goto(x1-r, y1-r) def pocket_rect(x1, y1, x2, y2, depth): retract() dia = args.pocket_bit_diameter r = dia/2 if x2 < x1: x1, x2 = x2, x1 if y2 < y1: y1, y2 = y2, y1 w = x2-x1 h = y2-y1 se = w if w < h else h # shortest edge goto(x1+r, y1+r) dz = 0 while dz < depth: dz += args.pass_depth if dz > depth: dz = depth drill(dz) dx = r while dx <= se / 2: goto(x1+dx, y1+dx) goto(x1+dx, y2-dx) goto(x2-dx, y2-dx) goto(x2-dx, y1+dx) goto(x1+dx, y1+dx) dx += 0.5*dia print(GCODE_HEADER) if args.workpiece == "front": print(GCODE_TOOL_CHANGE % 2) mounting_holes( external_width / 2, args.woofer_placement+args.material_thickness, args.woofer_mount_diameter / 2, args.woofer_mount_count) mounting_holes( external_width / 2, args.tweeter_placement+args.material_thickness, args.tweeter_mount_diameter / 2, args.tweeter_mount_count) print(GCODE_TOOL_CHANGE % 3) cutout_circle( external_width / 2, args.woofer_placement+args.material_thickness, args.woofer_cutout_diameter / 2) cutout_circle( external_width / 2, args.tweeter_placement+args.material_thickness, args.tweeter_diameter / 2) elif args.workpiece in ("side"): cutout_rect(0, 0, args.internal_depth, external_height) # Joint pocket bottom pocket_rect( -args.pocket_bit_diameter/2, args.material_thickness - args.joint, args.internal_depth + args.pocket_bit_diameter / 2, args.material_thickness, args.joint + args.joint_margin) # Joint pocket top pocket_rect( -args.pocket_bit_diameter/2, external_height - args.material_thickness + args.joint, args.internal_depth + args.pocket_bit_diameter / 2, external_height - args.material_thickness, args.joint + args.joint_margin) # Joint pocket support pocket_rect( args.internal_depth / 2 - args.support_width / 2 - args.pocket_bit_diameter / 2, args.support_placement, args.internal_depth / 2 + args.support_width / 2 + args.pocket_bit_diameter / 2, args.support_placement + args.joint, args.joint + args.joint_margin) # Bass reflex port if args.port_enable: cutout_circle( (2 if args.port_mirror else 1) * args.internal_depth / 3.0, args.port_placement, args.port_diameter / 2) if args.workpiece == "cap": pocket_rect( -args.joint, -args.joint-args.pocket_bit_diameter, 0, args.internal_depth+args.pocket_bit_diameter+args.joint, args.material_thickness-args.joint) pocket_rect( -args.pocket_bit_diameter, -args.joint, args.internal_width+args.pocket_bit_diameter, 0, args.material_thickness-args.joint) pocket_rect( args.internal_width, -args.joint-args.pocket_bit_diameter, args.internal_width+args.joint, args.internal_depth+args.pocket_bit_diameter+args.joint, args.material_thickness-args.joint) pocket_rect( -args.pocket_bit_diameter, args.internal_depth, args.internal_width+args.pocket_bit_diameter, args.internal_depth+args.joint, args.material_thickness-args.joint) cutout_rect( -args.joint, -args.joint, args.joint+args.internal_width, args.joint+args.internal_depth) if args.workpiece == "support": cutout_rect( -args.joint, 0, args.joint+args.internal_width, args.support_width) pocket_rect( -args.joint, -args.pocket_bit_diameter/2, 0, args.support_width+args.pocket_bit_diameter, args.material_thickness-args.joint) pocket_rect( args.internal_width, -args.pocket_bit_diameter / 2, args.internal_width + args.joint, args.support_width + args.pocket_bit_diameter, args.material_thickness - args.joint) # Wire terminals if args.workpiece == "back": retract() goto(external_width / 2 - 15, 80) drill(args.material_thickness) retract() goto(external_width / 2 + 15, 80) drill(args.material_thickness) retract() if args.workpiece in ("front", "back"): # Joint pocket bottom pocket_rect( args.material_thickness-args.joint-args.pocket_bit_diameter/2, args.material_thickness-args.joint, external_width-args.material_thickness+args.joint+args.pocket_bit_diameter/2, args.material_thickness, args.joint + args.joint_margin) # Joint pocket top pocket_rect( args.material_thickness-args.joint-args.pocket_bit_diameter/2, external_height - args.material_thickness+args.joint, external_width-args.material_thickness+args.joint+args.pocket_bit_diameter/2, external_height - args.material_thickness, args.joint + args.joint_margin) # Cutout cutout_rect(0, 0, external_width, external_height) print(GCODE_FOOTER)