Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- """
- Parametric Fender-style bolt-on mandolin neck in CadQuery.
- Units: millimeters.
- Default design intent:
- - 13.875 in / 352.425 mm mandolin scale
- - 22 frets
- - ~10.25 in / 260.35 mm nut-to-pocket-end neck length
- - Fender-style flat bolt-on heel
- - Radiused fretboard, default 12 in / 304.8 mm radius
- - Simple non-angled solid headstock with 8 tuner holes
- Run:
- python mandolin_fender_style_neck.py
- Exports:
- mandolin_bolt_on_neck.step
- mandolin_bolt_on_neck.stl
- mandolin_neck_pocket_cutter.step
- CadQuery install example:
- conda install -c conda-forge cadquery
- """
- from __future__ import annotations
- import math
- from dataclasses import dataclass
- import cadquery as cq
- from cadquery import Vector, Edge, Wire, Solid
- MM_PER_INCH = 25.4
- @dataclass(frozen=True)
- class Params:
- # Core mandolin layout
- scale: float = 13.875 * MM_PER_INCH # 352.425 mm
- frets: int = 22
- nut_to_heel_end: float = 10.25 * MM_PER_INCH # 260.35 mm
- # Fretboard
- fretboard_radius: float = 12.0 * MM_PER_INCH # 12 in radius
- fretboard_thickness_center: float = 5.5
- fretboard_overhang_after_last_fret: float = 6.0
- nut_width: float = 29.0
- fretboard_end_width: float = 38.0
- fret_slot_width: float = 0.55
- fret_slot_depth: float = 1.25
- # Neck shaft and heel
- neck_top_z: float = 16.0 # underside to board bottom at nut/shaft
- nut_neck_width: float = 26.0
- shaft_end_width: float = 35.0
- heel_width: float = 39.0 # pocket should be slightly bigger than this
- heel_length: float = 55.0
- heel_thickness: float = 19.0 # flat heel thickness before fretboard
- heel_pilot_dia: float = 2.4
- heel_pilot_depth: float = 14.0
- # Headstock, deliberately simple so you can reshape later
- headstock_length: float = 88.0
- headstock_width: float = 48.0
- headstock_thickness: float = 12.0
- tuner_hole_dia: float = 6.2
- # Nut
- nut_thickness_x: float = 3.0
- nut_height: float = 6.5
- nut_depth_y: float = 31.0
- # Export quality
- stl_tolerance: float = 0.05
- stl_angular_tolerance: float = 0.15
- P = Params()
- def fret_position(scale: float, fret_no: int) -> float:
- """Distance from nut to fret, equal temperament."""
- return scale - (scale / (2 ** (fret_no / 12.0)))
- def lerp(a: float, b: float, t: float) -> float:
- return a + (b - a) * t
- def width_at_x(x: float, x0: float, x1: float, w0: float, w1: float) -> float:
- t = max(0.0, min(1.0, (x - x0) / (x1 - x0)))
- return lerp(w0, w1, t)
- def fretboard_section_wire(x: float, width: float, thickness_center: float, radius: float) -> Wire:
- """Closed Y/Z wire at length position X. Flat bottom, radiused top."""
- half = width / 2.0
- sag = radius - math.sqrt(radius * radius - half * half)
- edge_z = thickness_center - sag
- p1 = Vector(x, -half, 0.0)
- p2 = Vector(x, half, 0.0)
- p3 = Vector(x, half, edge_z)
- p4 = Vector(x, -half, edge_z)
- crown = Vector(x, 0.0, thickness_center)
- edges = [
- Edge.makeLine(p1, p2),
- Edge.makeLine(p2, p3),
- Edge.makeThreePointArc(p3, crown, p4),
- Edge.makeLine(p4, p1),
- ]
- return Wire.assembleEdges(edges)
- def neck_back_section_wire(x: float, width: float, top_z: float, center_back_z: float = 0.0) -> Wire:
- """Closed Y/Z wire for a simple C-ish neck back with flat top."""
- half = width / 2.0
- shoulder_z = top_z * 0.35
- shoulder_y = half * 0.92
- p1 = Vector(x, -half, top_z)
- p2 = Vector(x, half, top_z)
- p3 = Vector(x, shoulder_y, shoulder_z)
- p4 = Vector(x, -shoulder_y, shoulder_z)
- back = Vector(x, 0.0, center_back_z)
- edges = [
- Edge.makeLine(p1, p2),
- Edge.makeLine(p2, p3),
- Edge.makeThreePointArc(p3, back, p4),
- Edge.makeLine(p4, p1),
- ]
- return Wire.assembleEdges(edges)
- def make_fretboard(p: Params) -> cq.Workplane:
- last_fret_x = fret_position(p.scale, p.frets)
- fb_len = min(p.nut_to_heel_end, last_fret_x + p.fretboard_overhang_after_last_fret)
- sections = []
- for x in [0, fb_len * 0.25, fb_len * 0.5, fb_len * 0.75, fb_len]:
- width = width_at_x(x, 0, fb_len, p.nut_width, p.fretboard_end_width)
- sections.append(fretboard_section_wire(x, width, p.fretboard_thickness_center, p.fretboard_radius))
- solid = Solid.makeLoft(sections)
- board = cq.Workplane("XY").add(solid).translate((0, 0, p.neck_top_z))
- # Cut fret slots down through the radius. These are shallow visual/locator grooves,
- # not prescriptive luthier fret tang dimensions.
- for n in range(1, p.frets + 1):
- x = fret_position(p.scale, n)
- if x > fb_len:
- continue
- w = width_at_x(x, 0, fb_len, p.nut_width, p.fretboard_end_width) + 4.0
- cutter = (
- cq.Workplane("XY")
- .box(p.fret_slot_width, w, p.fret_slot_depth, centered=(True, True, True))
- .translate((x, 0, p.neck_top_z + p.fretboard_thickness_center - p.fret_slot_depth / 2.0 + 0.2))
- )
- board = board.cut(cutter)
- # Dot markers as small shallow inlay discs. These intersect the curved top slightly.
- marker_frets = [5, 7, 10, 12, 15, 17, 19]
- for n in marker_frets:
- if n >= p.frets:
- continue
- x1 = fret_position(p.scale, n)
- x2 = fret_position(p.scale, n + 1)
- xm = (x1 + x2) / 2.0
- dots_y = [-5.0, 5.0] if n == 12 else [0.0]
- for y in dots_y:
- dot = (
- cq.Workplane("XY")
- .center(xm, y)
- .circle(1.6)
- .extrude(0.7)
- .translate((0, 0, p.neck_top_z + p.fretboard_thickness_center - 0.25))
- )
- board = board.union(dot)
- return board
- def make_neck_core(p: Params) -> cq.Workplane:
- shaft_end_x = p.nut_to_heel_end - p.heel_length
- sections = []
- for x in [0, shaft_end_x * 0.33, shaft_end_x * 0.66, shaft_end_x]:
- width = width_at_x(x, 0, shaft_end_x, p.nut_neck_width, p.shaft_end_width)
- sections.append(neck_back_section_wire(x, width, p.neck_top_z))
- shaft = cq.Workplane("XY").add(Solid.makeLoft(sections))
- heel = (
- cq.Workplane("XY")
- .box(p.heel_length, p.heel_width, p.heel_thickness, centered=(False, True, False))
- .translate((shaft_end_x, 0, 0))
- )
- neck = shaft.union(heel)
- # Fender-like 4 pilot holes in the heel bottom. These are pilot holes in the neck,
- # not body clearance holes.
- screw_xs = [shaft_end_x + p.heel_length * 0.34, shaft_end_x + p.heel_length * 0.74]
- screw_ys = [-p.heel_width * 0.27, p.heel_width * 0.27]
- for x in screw_xs:
- for y in screw_ys:
- pilot = (
- cq.Workplane("XY")
- .center(x, y)
- .circle(p.heel_pilot_dia / 2.0)
- .extrude(p.heel_pilot_depth)
- )
- neck = neck.cut(pilot)
- return neck
- def make_headstock(p: Params) -> cq.Workplane:
- L = p.headstock_length
- W = p.headstock_width
- t = p.headstock_thickness
- # Simple Fender-ish paddle outline, non-angled. Nut is at x=0, headstock extends negative.
- outline = [
- (-L, -W * 0.30),
- (-L * 0.82, -W * 0.48),
- (-L * 0.25, -W * 0.44),
- (0, -15.0),
- (0, 15.0),
- (-L * 0.18, W * 0.32),
- (-L * 0.78, W * 0.45),
- (-L, W * 0.30),
- ]
- hs = cq.Workplane("XY").polyline(outline).close().extrude(t)
- # Blend transition into nut end with a small rectangular tongue.
- tongue = cq.Workplane("XY").box(10.0, 28.0, t, centered=(False, True, False)).translate((-5.0, 0, 0))
- hs = hs.union(tongue)
- # Eight inline tuner holes. Change to 4+4 if you prefer a Gibson/mandolin look.
- start_x = -L + 14.0
- spacing = 8.8
- y = W * 0.20
- for i in range(8):
- x = start_x + i * spacing
- hole = cq.Workplane("XY").center(x, y).circle(p.tuner_hole_dia / 2.0).extrude(t + 1.0)
- hs = hs.cut(hole)
- return hs
- def make_nut(p: Params) -> cq.Workplane:
- return (
- cq.Workplane("XY")
- .box(p.nut_thickness_x, p.nut_depth_y, p.nut_height, centered=(False, True, False))
- .translate((-p.nut_thickness_x, 0, p.neck_top_z))
- )
- def make_pocket_cutter(p: Params) -> cq.Workplane:
- """Body-side helper: neck pocket cutter/block, with a small fit clearance."""
- clearance = 0.25
- depth = p.heel_thickness
- shaft_end_x = p.nut_to_heel_end - p.heel_length
- return (
- cq.Workplane("XY")
- .box(p.heel_length + 0.5, p.heel_width + clearance * 2.0, depth, centered=(False, True, False))
- .translate((shaft_end_x - 0.25, 0, 0))
- )
- def build_model(p: Params = P) -> cq.Workplane:
- neck = make_neck_core(p)
- fretboard = make_fretboard(p)
- headstock = make_headstock(p)
- nut = make_nut(p)
- return neck.union(fretboard).union(headstock).union(nut)
- if __name__ == "__main__":
- model = build_model(P)
- pocket = make_pocket_cutter(P)
- cq.exporters.export(model, "mandolin_bolt_on_neck.step")
- cq.exporters.export(model, "mandolin_bolt_on_neck.stl", tolerance=P.stl_tolerance, angularTolerance=P.stl_angular_tolerance)
- cq.exporters.export(pocket, "mandolin_neck_pocket_cutter.step")
- print("Exported mandolin_bolt_on_neck.step")
- print("Exported mandolin_bolt_on_neck.stl")
- print("Exported mandolin_neck_pocket_cutter.step")
- print(f"Scale: {P.scale:.3f} mm")
- print(f"Nut to heel/pocket end: {P.nut_to_heel_end:.3f} mm")
- print(f"Pocket end to nominal bridge: {P.scale - P.nut_to_heel_end:.3f} mm")
- print(f"Fretboard radius: {P.fretboard_radius:.3f} mm")
Add Comment
Please, Sign In to add comment