amt-box/enclosure.py

402 lines
13 KiB
Python

#!/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)