402 lines
13 KiB
Python
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)
|