def midLineExtended(self): line = self.midLine maxxp = max(self.treble.end.x, self.bass.start.x) minxp = min(self.treble.start.x, self.bass.end.x) return linexy( lineIntersection(linexy(vxy(maxxp, 0), vxy(maxxp, 1)), line).point, lineIntersection(linexy(vxy(minxp, 0), vxy(minxp, 1)), line).point )
def calc_virt_frame(): # String spread spread = (inst.bridge.stringDistanceProj - inst.nut.stringDistanceProj) / 2.0 # Bass string length from nut to bridge (include nut offset before zero fret) length = inst.scale.max + inst.nut.offset # Side angle angle = math.asin(spread / length) bass = line(vxy(0, 0), vxy(-length, 0)).rotate(-angle) bass.translateTo(bass.lerpPointAt(-inst.nut.offset)) bridge = line(bass.end, vxy(0, - inst.bridge.stringDistanceProj)) treble = line(bridge.end, vxy(length, 0)).rotate(angle) nut = lineTo(treble.end, bass.start) return FretboardBox(bass, treble, nut, bridge)
def lineIntersection(line1, line2): # ! Credits: justin_c_rounds https://stackoverflow.com/a/60368757/1524027 # if the lines intersect, the result contains the x and y of the intersection # (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point result = Intersection() denominator = ((line2.end.y - line2.start.y) * (line1.end.x - line1.start.x)) - ( (line2.end.x - line2.start.x) * (line1.end.y - line1.start.y)) if denominator == 0: return result a = line1.start.y - line2.start.y b = line1.start.x - line2.start.x numerator1 = ((line2.end.x - line2.start.x) * a) - ( (line2.end.y - line2.start.y) * b) numerator2 = ((line1.end.x - line1.start.x) * a) - ( (line1.end.y - line1.start.y) * b) a = numerator1 / denominator b = numerator2 / denominator # if we cast these lines infinitely in both directions, they intersect here: result.point = vxy(line1.start.x + (a * (line1.end.x - line1.start.x)), line1.start.y + (a * (line1.end.y - line1.start.y))) # if line1 is a segment and line2 is infinite, they intersect if: if 0 < a < 1: result.onLine1 = True # if line2 is a segment and line1 is infinite, they intersect if: if 0 < b < 1: result.onLine2 = True # if line1 and line2 are segments, they intersect if both of the above are true return result
def calc_neck_frame(): x = max(frame.bass.start.x, frame.treble.end.x) perp = linexy(vxy(x, 0), vxy(x, 1)) p = lineIntersection(frame.bass, perp) q = lineIntersection(frame.treble, perp) nut = linexy(p.point, q.point) if inst.neck.joint is NeckJoint.THROUHG: x = min(frame.bass.end.x, frame.treble.start.x) - inst.body.length else: x = min(frame.bass.end.x, frame.treble.start.x) perp = linexy(vxy(x, 0), vxy(x, 1)) p = lineIntersection(frame.bass, perp) q = lineIntersection(frame.treble, perp) bridge = linexy(q.point, p.point) bass = linexy(bridge.end.clone(), nut.start.clone()) treble = linexy(nut.end.clone(), bridge.start.clone()) return FretboardBox(bass, treble, nut, bridge)
def calc_fretboard_frame(): # Bass include nut and margin bassRef = lineTo(fret0.start, frets[1].start) s = bassRef.lerpPointAt(-inst.nut.offset - inst.nut.thickness - inst.fretboard.startMargin) e = lineFrom(fretz.start, bassRef.vector, inst.fretboard.endMargin).end bass = lineTo(s, e) # Treble trebRef = lineTo(fret0.end, frets[1].end) if (inst.nut.position is NutPosition.PARALLEL): s = trebRef.lerpPointAt(-inst.nut.offset - inst.nut.thickness - inst.fretboard.startMargin) e = lineFrom(fretz.end, trebRef.vector, inst.fretboard.endMargin).end treble = lineTo(e, s) else: """if inst.nut.position is NutPosition.PERPENDICULAR""" tmp = lineFrom(bass.start, vxy(0, -1), 100) i = lineIntersection(tmp, trebRef).point or trebRef.lerpPointAt( -inst.nut.offset - inst.nut.thickness - inst.fretboard.startMargin) e = lineFrom(fretz.end, trebRef.vector, inst.fretboard.endMargin).end treble = lineTo(e, i) # Custom cut if (inst.fretboard.cut is FretboardCut.CUSTOM): bass = bass.lerpLineTo(inst.fretboard.cutBassDistance) treble = treble.cloneInverted().lerpLineTo(inst.fretboard.cutTrebleDistance).cloneInverted() elif (inst.fretboard.cut is FretboardCut.PERPENDICULAR): if (inst.fretboard.cutBassDistance): bass = bass.lerpLineTo(inst.fretboard.cutBassDistance) tmp = lineFrom(bass.end, vxy(0, -1), 100) i = lineIntersection(tmp, treble) if (i.point): treble = lineTo(i.point, treble.end) bridge = lineTo(bass.end, treble.start) nut = lineTo(treble.end, bass.start) return FretboardBox(bass, treble, nut, bridge)
def makeTenon(fbd, neckAngleRad, posXY, h, tenonThickness, tenonLength, tenonOffset, joint): if tenonThickness > 0 \ and tenonLength > 0 \ and joint is NeckJoint.SETIN: naLineDir = linexy(vxy(0, 0), angleVxy(math.pi + neckAngleRad, tenonLength)) naAp = geom.vecxz(naLineDir.start) naBp = geom.vecxz(naLineDir.end) refp = geom.vec(posXY, tenonOffset - h + tenonThickness) naSidePs = [ naAp, Vector(naAp.x, naAp.y, naAp.z - tenonThickness), Vector(naBp.x, naBp.y, naBp.z - tenonThickness), naBp, naAp ] naSidePs = [v.add(refp) for v in naSidePs] naSide = Part.Face(Part.makePolygon(naSidePs)).extrude( Vector(0, fbd.neckFrame.bridge.length, 0)) return naSide
def buildFretboardData(inst): """ Create Fretboard reference constructions """ # Virtual String Frame (String projection) def calc_virt_frame(): # String spread spread = (inst.bridge.stringDistanceProj - inst.nut.stringDistanceProj) / 2.0 # Bass string length from nut to bridge (include nut offset before zero fret) length = inst.scale.max + inst.nut.offset # Side angle angle = math.asin(spread / length) bass = line(vxy(0, 0), vxy(-length, 0)).rotate(-angle) bass.translateTo(bass.lerpPointAt(-inst.nut.offset)) bridge = line(bass.end, vxy(0, - inst.bridge.stringDistanceProj)) treble = line(bridge.end, vxy(length, 0)).rotate(angle) nut = lineTo(treble.end, bass.start) return FretboardBox(bass, treble, nut, bridge) virtStrFrame = calc_virt_frame() # Scale Frame def calc_scale_frame(): s = virtStrFrame.bass.lerpPointAt(inst.nut.offset) bass = lineTo(s, virtStrFrame.bass.end) bassPF = fret(inst.fretboard.perpendicularFret, inst.scale.bass) trebPF = fret(inst.fretboard.perpendicularFret, inst.scale.treble) scaleOffset = bassPF - trebPF vtreb = virtStrFrame.treble.cloneInverted() nut = lineTo(vtreb.lerpPointAt(scaleOffset + inst.nut.offset), bass.start) treble = lineTo(vtreb.lerpPointAt(scaleOffset + inst.nut.offset + inst.scale.treble), nut.start) bridge = lineTo(bass.end, treble.start) return FretboardBox(bass, treble, nut, bridge) scaleFrame = calc_scale_frame() bassSideMargin = inst.fretboard.sideMargin + inst.stringSet.last / 2.0 trebSideMargin = inst.fretboard.sideMargin + inst.stringSet.first / 2.0 # Frets def calc_frets(): frets = [] vtreb = scaleFrame.treble.cloneInverted() for i in range(inst.fretboard.frets + 1): s = fret(i, inst.scale.bass) e = fret(i, inst.scale.treble) ps = scaleFrame.bass.lerpPointAt(s) pe = vtreb.lerpPointAt(e) bfret = lineTo(ps, pe) bassMargin = bassSideMargin / math.sin(-bfret.vector.angle()) trebleMargin = trebSideMargin / math.sin(-bfret.vector.angle()) ps = bfret.lerpPointAt(-bassMargin) pe = bfret.lerpPointAt(bfret.vector.length + bassMargin + trebleMargin) frets.append(lineTo(ps, pe)) return frets frets = calc_frets() fret0 = frets[0] fretz = frets[-1] # Fretboard Frame def calc_fretboard_frame(): # Bass include nut and margin bassRef = lineTo(fret0.start, frets[1].start) s = bassRef.lerpPointAt(-inst.nut.offset - inst.nut.thickness - inst.fretboard.startMargin) e = lineFrom(fretz.start, bassRef.vector, inst.fretboard.endMargin).end bass = lineTo(s, e) # Treble trebRef = lineTo(fret0.end, frets[1].end) if (inst.nut.position is NutPosition.PARALLEL): s = trebRef.lerpPointAt(-inst.nut.offset - inst.nut.thickness - inst.fretboard.startMargin) e = lineFrom(fretz.end, trebRef.vector, inst.fretboard.endMargin).end treble = lineTo(e, s) else: """if inst.nut.position is NutPosition.PERPENDICULAR""" tmp = lineFrom(bass.start, vxy(0, -1), 100) i = lineIntersection(tmp, trebRef).point or trebRef.lerpPointAt( -inst.nut.offset - inst.nut.thickness - inst.fretboard.startMargin) e = lineFrom(fretz.end, trebRef.vector, inst.fretboard.endMargin).end treble = lineTo(e, i) # Custom cut if (inst.fretboard.cut is FretboardCut.CUSTOM): bass = bass.lerpLineTo(inst.fretboard.cutBassDistance) treble = treble.cloneInverted().lerpLineTo(inst.fretboard.cutTrebleDistance).cloneInverted() elif (inst.fretboard.cut is FretboardCut.PERPENDICULAR): if (inst.fretboard.cutBassDistance): bass = bass.lerpLineTo(inst.fretboard.cutBassDistance) tmp = lineFrom(bass.end, vxy(0, -1), 100) i = lineIntersection(tmp, treble) if (i.point): treble = lineTo(i.point, treble.end) bridge = lineTo(bass.end, treble.start) nut = lineTo(treble.end, bass.start) return FretboardBox(bass, treble, nut, bridge) frame = calc_fretboard_frame() # Nut Frame def calc_nut_frame(): vtreb = frame.treble.cloneInverted() s = frame.bass.lerpPointAt(inst.fretboard.startMargin) bass = lineFrom(s, frame.bass.vector, inst.nut.thickness) s = vtreb.lerpPointAt(inst.fretboard.startMargin) treble = lineFrom(s, vtreb.vector, inst.nut.thickness).flipDirection() return FretboardBox(bass, treble, lineTo(treble.end, bass.start), lineTo(bass.end, treble.start)) nutFrame = calc_nut_frame() # Adjust ScaleFrame ans virtStrFrame (Center in Fretboard) def adjust_scale_frame(): diff = frets[0].mid().sub(scaleFrame.nut.mid()) return (scaleFrame.translate(diff), virtStrFrame.translate(diff)) (scaleFrame, virtStrFrame) = adjust_scale_frame() # Bridge position line with compensation def cal_bridge_pos(): a = scaleFrame.treble.lerpPointAt(-inst.bridge.trebleCompensation) b = scaleFrame.bass.lerpPointAt(scaleFrame.bass.length + inst.bridge.bassCompensation) return linexy(a, b) bridgePos = cal_bridge_pos() # Neck Frame def calc_neck_frame(): x = max(frame.bass.start.x, frame.treble.end.x) perp = linexy(vxy(x, 0), vxy(x, 1)) p = lineIntersection(frame.bass, perp) q = lineIntersection(frame.treble, perp) nut = linexy(p.point, q.point) if inst.neck.joint is NeckJoint.THROUHG: x = min(frame.bass.end.x, frame.treble.start.x) - inst.body.length else: x = min(frame.bass.end.x, frame.treble.start.x) perp = linexy(vxy(x, 0), vxy(x, 1)) p = lineIntersection(frame.bass, perp) q = lineIntersection(frame.treble, perp) bridge = linexy(q.point, p.point) bass = linexy(bridge.end.clone(), nut.start.clone()) treble = linexy(nut.end.clone(), bridge.start.clone()) return FretboardBox(bass, treble, nut, bridge) neckFrame = calc_neck_frame() fbd = FretboardData(frame, virtStrFrame, scaleFrame, nutFrame, frets, bridgePos, neckFrame) fbd = fbd.translate(vxy(0, 0).sub(neckFrame.nut.mid())) return fbd
def makeHeel(neckd, line, angle, joint, backThickness, topThickness, topOffset, neckPocketDepth, neckPocketLength, jointFret, transitionLength, transitionTension, bodyLength, tenonThickness, tenonLength, tenonOffset, forPocket): """ Create heel shape. Args: fbd : FredboardData line : linexy, reference line """ fbd = neckd.fbd neckAngleRad = deg(angle) if joint is NeckJoint.THROUHG: h = backThickness + topThickness + topOffset else: h = neckPocketDepth + topOffset if forPocket: jointFret = 0 start_p = lineIntersection(fbd.frets[jointFret], line).point start_d = linexy(line.start, start_p).length # Curved Part if not forPocket: transitionJob = Task.execute(heelTransition, neckd, line, start_d, h, transitionLength, transitionTension) xperp = line.lerpLineTo( start_d + transitionLength).perpendicularCounterClockwiseEnd() a = lineIntersection(xperp, fbd.neckFrame.treble).point b = lineIntersection(xperp, fbd.neckFrame.bass).point c = fbd.neckFrame.bridge.end d = fbd.neckFrame.bridge.start # Rect Part def heelBase(): segments = [ Part.LineSegment(geom.vec(b), geom.vec(c)), Part.LineSegment(geom.vec(c), geom.vec(d)), Part.LineSegment(geom.vec(d), geom.vec(a)), Part.LineSegment(geom.vec(a), geom.vec(b)), ] part = Part.Face(Part.Wire(Part.Shape(segments).Edges)).extrude( Vector(0, 0, -100)) return part partJob = Task.execute(heelBase) if not forPocket: transition = transitionJob.get() else: transition = None part = partJob.get() if transition: part = transition.fuse(part) # Neck Angle Cut (Bottom) extrusionDepth = 100 lengthDelta = max(neckPocketLength, (fbd.neckFrame.midLine.length - start_d)) #- inst.neck.transitionLength/2 naLineDir = linexy(vxy(0, 0), angleVxy(neckAngleRad, lengthDelta)) naLineDir = naLineDir.flipDirection().lerpLineTo(naLineDir.length + 30).flipDirection() naAp = geom.vecxz(naLineDir.start) naBp = geom.vecxz(naLineDir.end) refp = geom.vec(fbd.frame.bridge.end, -h).add(Vector(0, -fbd.neckFrame.bridge.length / 2, 0)) if joint is NeckJoint.THROUHG: refp = refp.add( Vector(-bodyLength * math.cos(neckAngleRad), 0, -bodyLength * math.sin(neckAngleRad))) naSidePs = [ naAp, Vector(naAp.x, naAp.y, naAp.z - extrusionDepth), Vector(naBp.x, naBp.y, naBp.z - extrusionDepth), naBp, naAp ] naSidePs = [v.add(refp) for v in naSidePs] naSide = Part.Face(Part.makePolygon(naSidePs)).extrude( Vector(0, fbd.neckFrame.bridge.length * 2, 0)) # Cut bottom part = part.cut(naSide) # Then move and cut top (Remove Top thickness) cutThickness = extrusionDepth * math.cos(neckAngleRad) naSide.translate( Vector((backThickness + cutThickness) * math.sin(neckAngleRad), 0, (backThickness + cutThickness) * math.cos(neckAngleRad))) naSide.translate( Vector((bodyLength - lengthDelta) * math.cos(neckAngleRad), 0, (bodyLength - lengthDelta) * math.sin(neckAngleRad))) part = part.cut(naSide) # Tenon tenon = makeTenon(fbd, neckAngleRad, d, h, tenonThickness + 100 if forPocket else tenonThickness, tenonLength, tenonOffset, joint) if tenon: part = part.fuse(tenon) return part.removeSplitter()
def lerp(self, alpha): return vxy().lerpVectors(self.v1, self.v2, alpha)
def vector(self): return vxy().subVectors(self.v2, self.v1)
def line(start, vector): return linexy(start.clone(), vxy().addVectors(start, vector))