import static uk.co.nickthecoder.foocad.layout.v1.Debug.* import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.* import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.* import static uk.co.nickthecoder.foocad.labelled.v1.Labelled.* class JugglingClub : Model { // The wall thickness. Ensure you have enough "perimeters" in your slicer // so that it doesn't infill (unless that is what you want!) var thickness = 1.9 // The total length of the club. var length = 420 // The height of lip forming the joins between the sections. var join = 10 // The gap between the two parts where they join. // Use epoxy resin at the join, so the slack shouldn't be too minimal. // Also not that the joint @Custom var slack = 0.3 var piece = "one" // The thickness of the base @Custom var baseThickness = 8.0 @Custom var stopLength = 60 @Custom var spacer = 8 @Custom var handleLength = 200 // Use this to print the handle in multiple parts - if your printer is too short // or the handle is too unstable to print in one length. @Custom var handlePieces = 1 // The is the size of the hole. Include some slack by making it slightly // larger than you rod's actual diameter. @Custom var rodDiameter = 18.6 // For an 18mm dowel. @Custom // Number of sides for the revolution. var sides = 90 val svgFile = "jugglingClub.svg" var doc = SVGParser().parseFile( svgFile ) @Piece fun onePiece() : Shape3d { val profile = doc.shapes["club"].toOrigin().translateX(0.001) val inside = profile.offset(-thickness) val hollow = (profile - inside).revolve().sides(sides) - Cylinder( inside.size.y, thickness*2 ).translateZ(thickness) val base = (profile / Square( profile.size.x + 2, baseThickness )) .revolve().sides(sides) - Cylinder.hole( baseThickness, rodDiameter/2 ).translateZ(baseThickness*0.7) return hollow + base } @Piece fun head() : Shape3d { val y = doc.shapes["a"].size.y + doc.shapes["b"].size.y val x = Math.max(doc.shapes["a"].size.x, doc.shapes["b"].size.x) val main = onePiece() / Cube( x, x, y ).centerXY() return main } @Piece fun headA() : Shape3d { val size = doc.shapes["a"].size val main = onePiece() / Cube( size.x, size.x, size.y ).centerXY() val radius = main.size.x/2 // Causes the join to tilt inwards slightly, which fits the profile of // part "b" better. val tilt = 1.3 // Create a "lip" for part "b" to fit around. The end is tapered, // and there is "slack" (the lip is smaller than part "b"'s inside) // and there's a chamfer so that there is no overhang. val lip = PolygonBuilder().apply { moveTo(0, -thickness*2-slack) lineTo(0, 0) lineTo(-slack,0) lineTo(-slack*tilt, join*0.7) lineTo(-slack-thickness/2, join) lineTo(-slack*tilt-thickness,join) lineTo(-slack-thickness,-thickness) }.build().translate(radius - thickness,size.y).revolve().sides(sides) val screwHole = Cylinder( 100, 1.5 ).center() + Cylinder( 5, 5, 0) return main + lip - screwHole } @Piece fun headB() : Shape3d { val sizeA = doc.shapes["a"].size val sizeB = doc.shapes["b"].size val main = onePiece().translateZ(-sizeA.y) / (Cube( sizeB.x, sizeB.x, sizeB.y ).centerXY()) return main } @Piece fun handle() : Shape3d { val radius = doc.shapes["handle"].size.x return ( Circle( radius ).sides(60) - Circle( rodDiameter / 2 ).sides(40) ).extrude( handleLength / handlePieces ) } @Piece fun spacer() : Shape3d { val radius = doc.shapes["handle"].size.x return ( Circle( radius ).sides(60) - Circle( rodDiameter / 2 ).sides(40) ).extrude( spacer ) } @Piece fun cap() : Shape3d { val profile = doc.shapes["cap"].mirrorY().toOrigin().translateX(0.001) val cap = profile.revolve().sides(sides) val hole = Cylinder( 3, 4 ) return cap - hole } var washerHeight = 8 var washerDiameter = 6 @Piece fun end1() : Shape3d { val profile = doc.shapes["club"] val mask = doc.shapes["end1"] val solid = (profile / mask).mirrorY().toOrigin().translateX(0.001).revolve().sides(sides) val hole = Cylinder.hole( solid.size.z, rodDiameter/2 ).translateZ( washerHeight/2 ) val washerHole = Cylinder( mask.size.y, washerDiameter/2 + 0.25 ).translateZ( -0.01 ) return solid - hole - washerHole } @Piece fun end2() : Shape3d { val profile = doc.shapes["club"] val mask = doc.shapes["end2"] val solid = (profile / mask).mirrorY().toOrigin().translateX(0.001).revolve().sides(sides) val hole = Cylinder.hole( mask.size.y - washerHeight/2, rodDiameter/2 ).translateZ( -0.01 ) val washerHole = Cylinder( mask.size.y, washerDiameter/2 + 0.25 ).translateZ( 0.01 ) return (solid - hole - washerHole).mirrorZ().toOriginZ() } @Piece fun end3() : Shape3d { val profile = doc.shapes["club"] val mask = doc.shapes["end3"] val solid = (profile / mask).toOrigin().translateX(0.001).revolve().sides(sides) return solid } // The endWasher goes inside "end1" and "end2" so that when you screw in the screw, // the endWasher is pushed firmly against the wooden rod without squashing // the TPU of the end piece. @Piece fun endWasher() : Shape3d { val baseT = 3 return ( Cylinder( baseT, rodDiameter/2 ).sides(40) + // Wide part Cylinder( washerHeight, washerDiameter/2 ).sides(12).translateZ(baseT) - // Tube Cylinder( 5, 5, 0 ).sides(20).translateZ(-0.01) - // ChamferedHead Cylinder( 50, 2 ).sides(12) // Hole for screw ) } @Piece fun stop( ) : Shape3d { var profile = doc.shapes["stop"].mirrorY().toOrigin().translateX(0.001) profile = profile.scale(1, stopLength / profile.size.y ) val lip = Cylinder( join, profile.size.x-thickness-slack ).translateZ(profile.size.y) val hole = Cylinder( 300, rodDiameter/2 + slack ).center() val grubHoles = ( Cylinder( 10, 1.5 ).center().rotateY(90) + Cylinder( 5, 5, 0 ).rotateY(90).translateX(-lip.size.x/2) ).translateZ(profile.size.y + join/2).repeatAroundZ(3) return profile.revolve().sides(sides) + lip - hole - grubHoles } @Piece fun exploded() : Shape3d { // The "standard" configuration, with a single piece handle, // and single piece head. // Also shown are the optional pieces "spacer" and "end3" handlePieces = 1 val normal = layoutZ( 2, end3().mirrorZ().toOriginZ().color("DimGray"), endWasher().color("Purple"), end2().mirrorZ().color("DimGray"), end1().color("DimGray"), handle().tileZ(handlePieces,2), spacer().color("DimGray"), stop().color("Purple"), head().mirrorZ().color("Purple"), cap().color("DimGray") ) // Head in twp parts. Handle in 3 parts. No "end3" nor "spacer". handlePieces = 3 val twoPiece = layoutZ( 2, endWasher().color("Purple"), end2().mirrorZ().color("DimGray"), end1().color("DimGray"), handle().tileZ(handlePieces,2), stop().color("Purple"), headB().mirrorZ().color("Purple"), headA().mirrorZ().color("Purple"), cap().color("DimGray") ) return onePiece().translateX(-200) + normal + twoPiece.translateX( 200 ) } @Piece fun inspect() : Shape3d { val headA : Shape3d = headA() val all = headA + headB().translateZ(93) + stop().mirrorZ().translateZ(268) return all / Cube( 100, 1, length*2 ).center() } override fun build() = onePiece() }