def fromBaseWire(self, wire, height, deform, closed=True): """ Create neck profile section wire """ edge = wire.Edges[0] leftTop = edge.Vertexes[0].Point rightTop = edge.Vertexes[1].Point width = wire.Length widthRef = width / 2 cent = edge.valueAt(widthRef) cent = Vector(cent.x, cent.y, height) curve = Part.BSplineCurve([ leftTop, Vector(cent.x, cent.y - (cent.y - leftTop.y) * deform, cent.z * 0.95), cent, Vector(cent.x, cent.y + (rightTop.y - cent.y) * deform, cent.z * 0.95), rightTop ]) if closed: a, b, c, d = [curve.parameter(p) for p in curve.discretize(4)] segments = [ geom.bspSegment(curve, a, b), geom.bspSegment(curve, b, c), geom.bspSegment(curve, c, d), Part.LineSegment(rightTop, leftTop) ] return Part.Wire(Part.Shape(segments).Edges) else: return curve
def seg(x, z): middle = geom.wireFromPrim( [Part.LineSegment(Vector(x, 200, pos.z), Vector(x, -200, pos.z))]) place(middle, pos, angle) section = geom.sectionSegment(contour, middle) if section: return geom.wireFromPrim([section])
def getDefaultTopTransition(contour, pos, defaultTransitionLength): """Generate default transitionEnd if no custom reference is provided""" line = geom.wireFromPrim( Part.LineSegment(Vector(pos.x + defaultTransitionLength, -150, pos.z), Vector(pos.x + defaultTransitionLength, 150, pos.z))) (d, vs, es) = line.Shape.distToShape(contour.Shape) if d < 1e-5 and len(vs) > 1: return Part.Wire(Part.Shape([Part.LineSegment(vs[0][0], vs[1][0])]))
def getTransitionStart(pos, profile, offset): """Generate the first profile""" profile = profile.wireAt(abs(pos.x - offset), pos + Vector(-offset, 0, 0)) curve = profile.Edges[0] c1, c2, c3 = geom.bspDiscretize(curve.Curve, 3) return geom.wireFromPrim([ c1, c2, c3, Part.LineSegment(Vector(profile.Edges[1].Vertexes[0].Point), Vector(profile.Edges[1].Vertexes[1].Point)) ])
def getPocketsCut(angle, pos, depth): """Create solid to cut pockets from the headstock""" pockets = App.ActiveDocument.getObject('Marz_Headstock_Pockets') if pockets: pockets = pockets.Shape.copy() if angle > 0: pockets.Placement = Placement( pos, Rotation(Vector(0, 1, 0), math.degrees(angle))) else: pockets.Placement = Placement(pos + Vector(0, 0, -depth), Rotation(Vector(0, 1, 0), 0)) return pockets
def __call__(self, width, height): """ Create neck profile section wire """ leftTop = Vector(0, width / 2, 0) rightTop = Vector(0, -width / 2, 0) h = self.getHPoint( width, height) # Vector(-height, width * self.h1Offset/2, 0) hl = Vector(-height * self.h2, width * self.h2Offset / 2, 0) hr = Vector(-height * self.h2, -width * self.h2Offset / 2, 0) points = [leftTop, hl, h, hr, rightTop] curve = Part.BSplineCurve() curve.interpolate(points) endl = Part.LineSegment(points[-1], points[0]) return Part.Wire(Part.Shape([curve, endl]).Edges)
def isTransitionWireValid(ref, wire): """Chech if {wire} is not inside ref or shrinks the loft""" cp = wire.copy() cp.translate(Vector(ref.CenterOfMass.x - wire.CenterOfMass.x, 0, 0)) dist, vectors, edges = ref.distToShape(cp) n = len(edges) return dist > 1e-5 or n == 2 or n == 32
def fretboardSection(c, r, w, t, v): """ Creates a Wire for a Fretboard Loft c: center Vector r: Radius float w: Width float t: Thickness float v: Direction Vector """ # Half angle of the top Arc alpha = math.asin(w/(2*r)) alpha_deg = todeg(alpha) # Guess top Arc arc = Part.makeCircle(r, c, v, -alpha_deg, alpha_deg) # If arc fails: Guess at 90deg if arc.Vertexes[0].Point.z <= 0: arc = Part.makeCircle(r, c, v, 90-alpha_deg, 90+alpha_deg) # If arc fails again: Impossible if arc.Vertexes[0].Point.z <= 0: raise ModelException("Current Fretboard's radius is inconsistent with Fretboard's geometry") # Arc end points a = arc.Vertexes[0].Point b = arc.Vertexes[1].Point # Side fretboard height h = r * math.cos(alpha) - r + t # Fretboard bottom at side a x = Vector(a.x, a.y, a.z -h) # Fretboard bottom at side b d = Vector(b.x, b.y, b.z -h) # Fretboard top at center p = Vector(c.x, c.y, c.z +r) # Finally, the wire: arc(ba) -> seg(ax) -> seg(xd) -> seg(db) return Part.Wire(Part.Shape([ Part.Arc(a,p,b), Part.LineSegment(a,x), Part.LineSegment(x,d), Part.LineSegment(d,b)]).Edges)
def wire(i): l = i * step h = fnHeight(l) point = Vector(points[i].x, points[i].y, points[i].z) if angle != 0: point.z = -l * math.tan(angle) if useProfileTransition: progress = l / length w = fnWidth(i) p = fnProfile.transition(w, h, i, l / length, length, lastHeight) if p: p.Placement = Placement(point, rot) else: w = fnWidth(l) p = fnProfile(w, h) if p: p.Placement = Placement(point, rot) return p
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
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 flatTopCut(pos, depth, transitionLength, voluteOffset): """Create solid to cleanup the top surface""" a = Vector(pos) b = Vector(a.x + transitionLength + depth, a.y, a.z - depth) c = Vector(a.x + 300, b.y, b.z) d = Vector(c.x, c.y, c.z + 2 * depth) e = Vector(a.x - voluteOffset - 5, a.y, d.z) f = Vector(e.x, e.y, a.z) curve = Part.BSplineCurve([ a, Vector(a.x + transitionLength / 2, a.x, a.z), Vector(a.x + transitionLength / 2, a.x, a.z - depth), b ]) l1 = Part.LineSegment(b, c) l2 = Part.LineSegment(c, d) l3 = Part.LineSegment(d, e) l4 = Part.LineSegment(e, f) l5 = Part.LineSegment(f, a) wire = geom.wireFromPrim([curve, l1, l2, l3, l4, l5]) wire.translate(Vector(0, -150, 0)) wire = Part.Face(wire).extrude(Vector(0, 300, 0)) return wire
def getTransitionCurve(startProfile, end, height, hLerp, angle, profile, pos, voluteOffset): """ Generate mid points of the transition. Returns: Vector[] -- List of mid points for transition using bspline curve. """ # Search start point pos = Vector(pos.x - voluteOffset, pos.y, pos.z) # ! TODO: Barrel Slope s = profile.hPointAt(abs(pos.x), pos).Vertexes[0].Point c = end.CenterOfMass length = c.x - pos.x # Horizontal Ref hl = Part.LineSegment( Vector(s), Vector(c.x, c.y, s.z) # ! TODO: Barrel Slope ) # Vertical Ref vbz = c.z - height - end.Length hbx = length * hLerp # Bezier curve = Part.BSplineCurve( [hl.value(0), hl.value(hbx), Vector(c.x, s.y, vbz)]) # Nearest end vertex a = end.Edges[0].Vertexes[0].Point b = end.Edges[0].Vertexes[1].Point p = a if a.x < b.x else b limit = p.x # Generate points points = [w for w in curve.discretize(20) if w.x > s.x and w.x < limit] return points, vbz, hbx
def makeTransition(edge, fnProfile, fnWidth, fnHeight, steps=10, limits=None, solid=True, ruled=True, useProfileTransition=False, angle=0, lastHeight=40): with traceTime("Prepare transition geometry"): curve = edge.Curve points = edge.discretize(Number=steps + 1) direction = curve.Direction length = edge.Length step = length / steps rot = Rotation(Vector(0, 0, 1), direction) def wire(i): l = i * step h = fnHeight(l) point = Vector(points[i].x, points[i].y, points[i].z) if angle != 0: point.z = -l * math.tan(angle) if useProfileTransition: progress = l / length w = fnWidth(i) p = fnProfile.transition(w, h, i, l / length, length, lastHeight) if p: p.Placement = Placement(point, rot) else: w = fnWidth(l) p = fnProfile(w, h) if p: p.Placement = Placement(point, rot) return p wires = [wire(i) for i in range(steps + 1)] wires = [w for w in wires if w is not None] with traceTime("Make transition solid"): loft = Part.makeLoft(wires, solid, not useProfileTransition) if limits: with traceTime("Apply transition limits"): loft = limits.common(loft) return loft
def voluteCutCylinder(radius, end, thickness, angle): """Generate solid to cut from the bottom of the construction""" length = end.Length pnt = end.Edges[0].Curve.value(-5) pnt = Vector(pnt.x, pnt.y, pnt.z - thickness * math.cos(angle)) pnt = Vector(pnt.x - radius * math.sin(angle), pnt.y, pnt.z - radius * math.cos(angle)) cyl = Part.makeCylinder(radius, length + 10, pnt, end.Edges[0].Curve.Direction) pnt2 = pnt + end.Edges[0].Curve.Direction * (length + 10) rec = Part.makePolygon([ pnt + Vector(-radius, 0, 0), pnt2 + Vector(-radius, 0, 0), pnt2 + Vector(radius, 0, 0), pnt + Vector(radius, 0, 0), pnt + Vector(-radius, 0, 0) ]) rec = Part.Face(rec) rec = rec.extrude(Vector(0, 0, -max(radius, 200))) solid = cyl.fuse(rec) return solid
def angledTopCut(pos, angle, voluteOffset): """Create solid to cleanup the top surface""" a = Vector(pos) c = Vector(a.x + 300 * math.cos(angle), a.y, a.z - 300 * math.sin(angle)) d = Vector(c.x, c.y, abs(c.z)) e = Vector(a.x - voluteOffset - 5, a.y, d.z) f = Vector(e.x, e.y, a.z) l1 = Part.LineSegment(a, c) l2 = Part.LineSegment(c, d) l3 = Part.LineSegment(d, e) l4 = Part.LineSegment(e, f) l5 = Part.LineSegment(f, a) wire = geom.wireFromPrim([l1, l2, l3, l4, l5]) wire.translate(Vector(0, -150, 0)) solid = Part.Face(wire).extrude(Vector(0, 300, 0)) return solid
def createConstructionShapesParts(self): placement = App.Placement() placement.Rotation.Q = (0,0,0,1) placement.Base = Vector(0,0,0) def createInUI(): group = getUIGroup(UIGroup_XLines) for suffix, points, color in self.createConstructionShapes(): part = findDraftByLabel(suffix) if part is None: wire = Draft.makeWire(points, placement=placement, face=False) wire.Label = suffix Draft.autogroup(wire) obj = findDraftByLabel(wire.Label) obj.ViewObject.LineColor = color group.addObject(obj) UIThread.run(createInUI)
def getDefaultTop(pos, width, length, profile, angle, transitionLength): """Generate default contour and transitionEnd if no custom reference is provided""" startWidth = profile.widthAt(pos.x) a = Vector(pos.x, pos.y - startWidth / 2, pos.z) b = Vector(pos.x, pos.y + startWidth / 2, pos.z) c = Vector(b.x + transitionLength, b.y, b.z) d = Vector(c.x, pos.y + width / 2, c.z) e = Vector(d.x + length, d.y, d.z) f = Vector(e.x, e.y - width, e.z) g = Vector(f.x - length, f.y, f.z) h = Vector(g.x, a.y, g.z) l1 = Part.LineSegment(a, b) c2 = Part.BSplineCurve([b, c, d]) l3 = Part.LineSegment(d, e) l4 = Part.LineSegment(e, f) l5 = Part.LineSegment(f, g) c6 = Part.BSplineCurve([g, h, a]) contour = geom.wireFromPrim([l1, c2, l3, l4, l5, c6]) transition = geom.wireFromPrim([Part.LineSegment(g, d)]) return (place(contour, pos, angle), place(transition, pos, angle))
def makeInlays(fbd, thickness, inlayDepth): line = fbd.scaleFrame.midLine shapes = [] with traceTime("Prepare inlay pockets geometry"): for i in range(len(fbd.frets)): inlay = App.ActiveDocument.getObject(f"Marz_FInlay_Fret{i}") if inlay: ishape = inlay.Shape.copy() ishape.translate(fretPos(i, line, thickness)) shapes.append(ishape) comp = None with traceTime("Build inlay pockets substractive solid"): if shapes: comp = Part.makeCompound(shapes).extrude(Vector(0, 0, -inlayDepth-1)) return comp
def headstock(self, neckd, line): params = self.instrument.headStock profile = getNeckProfile(neckd.profileName) boundProfile = hs.BoundProfile(profile, neckd.fbd.widthAt, neckd.thicknessAt) pos = Vector(line.start.x, line.start.y, 0) return hs.build(pos, deg(params.angle), boundProfile, params.thickness, params.transitionParamHorizontal, params.voluteRadius, params.voluteOffset, params.depth, params.topTransitionLength, params.width, params.length, indirectDependencies={ 'svg': self.instrument.internal.headstockImport })
def extrudeBlank(top, thickness, angle, end, pos): """Generate base plate solid with transition space removed""" center = end.CenterOfMass extrusion = Vector(0, 0, -thickness * math.cos(angle)) blank = Part.Face(top).extrude(extrusion) a = end.Edges[0].Vertexes[0].Point b = end.Edges[0].Vertexes[1].Point if a.y < b.y: a, b = b, a points = [ Vector(pos.x, -100, 5), Vector(pos.x, 100, 5), Vector(a.x, a.y, 5), Vector(b.x, b.y, 5), Vector(pos.x, -100, 5) ] wire = Part.Wire(Part.makePolygon(points)) wire.fixWire() cut = Part.Face(wire).extrude(Vector(0, 0, -200)) return blank.cut(cut)
def voluteCutFlat(pos, thickness, depth, angle, voluteOffset): """Generate solid to cut from the bottom of the construction""" width = 150 length = 300 pol = Part.makePolygon([ Vector(pos.x - voluteOffset, -width, pos.z), Vector(pos.x - voluteOffset, width, pos.z), Vector(pos.x + length * math.cos(angle), width, pos.z - length * math.sin(angle)), Vector(pos.x + length * math.cos(angle), -width, pos.z - length * math.sin(angle)), Vector(pos.x - voluteOffset, -width, pos.z) ]) height = (thickness + (0 if angle > 0 else depth)) * math.cos(angle) height = height - voluteOffset * math.sin(angle) pol.translate(Vector(0, 0, -height)) wire = geom.wireFromPrim(pol) solid = Part.Face(wire).extrude(Vector(0, 0, -400)) return solid
def heelTransition(neckd, line, startd, h, transitionLength, transitionTension): """ Create transition from neck to heel shape. Args: neckd : NeckData line : linexy, reference line startd : starting point distance from line.start h : Heel height """ with traceTime("Make Heel Transition"): if transitionLength <= 0 or transitionTension <= 0: return None trline = linexy(line.lerpPointAt(startd), line.lerpPointAt(startd + transitionLength * 2)) length = trline.length Transition = transitionDatabase[neckd.transitionFunction] transition = Transition(neckd.widthAt, neckd.thicknessAt, transitionTension, transitionTension, startd, length) profile = getNeckProfile(neckd.profileName) wire = Part.Wire( Part.LineSegment(geom.vec(trline.start), geom.vec(trline.end)).toShape()) steps = int(trline.length / 4) + 1 limit = geom.extrusion(neckd.fbd.neckFrame.polygon, 0, Vector(0, 0, -h)) tr = geom.makeTransition(wire.Edges[0], profile, transition.width, transition.height, steps=steps, limits=limit, ruled=False) return tr
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 vecyz(v, x=0): return Vector(x, v.x, v.y)
def vecxz(v, y=0): return Vector(v.x, y, v.y)
def extrusion(vs, z, dir): return face(vs, z).extrude(Vector(dir[0], dir[1], dir[2]))
def vec(v, z=0): """Convert vxy to Vector""" return Vector(v.x, v.y, z)
def place(shape, pos, angle): """Porition a shape to pos and rotation""" shape.translate(pos) shape.rotate(Vector(0, 0, 0), Vector(0, 1, 0), math.degrees(angle)) return shape
def getHPoint(self, width, height): return Vector(-height, width * self.h1Offset / 2, 0)