/Games/ClubsHolder.foocad
A wall mounted holder for three juggling clubs. It should be fairly straight forward to adapt this for five clubs (or more).
Each prong is reinforced with a metal rod. The default size is for a 6mm rod.
Uses two keyhole slots to secure it to the wall.
To get the gap and angle correct for your clubs, print out the prongs first, and then fix them with the metal rods to a piece of scrap wood.
You need 1 "back" piece, and 4 "prong" pieces. The prongs should be printed with TPU for that rubbery feel.
However, "back" doesn't fit on my printer, so I had to break it into two piees, with a small cuboid to keep them aligned. So in this case use piece "backPieces" or "backA", "backB" and "connector" if you prefer printing them all separately.
Note the position and size of the prongs should allow the clubs to fan out nicely, without touching each other, and only the center club should hang straight downward.
import static uk.co.nickthecoder.foocad.screws.v1.Screws.* import static uk.co.nickthecoder.foocad.chamferedExtrude.v1.ChamferedExtrude.* import static uk.co.nickthecoder.foocad.chamferedExtrude.v1.ChamferedExtrude.* class ClubsHolder : Model { // The diameter of the metal rod used to secure the prongs to the back. var rodD = 6.0 // The distance between the centers of the prongs var gap = 90 // The angle that the outer prongs descend. var angle = 10 // The curve which connects the four prongs is circular arc. // This is the radius of that circle. If you change gap or angle, // you will need to adjust this as well to get it going (roughly) // through the centers of each prong. var curveR = 450 var slack = 0.5 fun club() : Shape3d { val profile = SVGParser().parseFile( "jugglingClub.svg" ).shapes["club"] return profile.toOrigin().translateX(0.0001).revolve() .rotateX(90) .translate( 0, 120, 40 ) .previewOnly() } fun prong( doc : SVGDocument ) : Shape3d { val baseSize = doc.shapes["size"].size val foo = doc.shapes["profile"].toOrigin() val profile = foo / Square( baseSize.x, foo.size.y - baseSize.y ).translateY(baseSize.y) val hole = Cylinder( profile.size.y - 10, (rodD+slack)/2 ) return profile.toOrigin().translateX(0.0001).revolve() - hole } fun node( doc : SVGDocument ) : Shape3d { val baseSize = doc.shapes["size"].size val nodeProfile = doc.shapes["profile"].toOrigin() / Square( baseSize ) val node = nodeProfile.toOrigin().translateX(0.0001).revolve() return node } fun back( doc : SVGDocument ) : Shape3d { val node : Shape3d = node(doc) val halfNodes = node.translateX( gap/2 ) + node.translateX( gap ).rotateZ(-angle).translateX( gap/2 ) val nodes = halfNodes.mirrorX().also() val hole = Cylinder( 50, (rodD-slack)/2 ).translateZ(0) val holes = ( hole.translateX( gap/2 ) + hole.translateX( gap ).rotateZ(-angle).translateX( gap/2 ) ).mirrorX().also() val dy = holes.size.y - rodD val path = PolygonBuilder().apply { moveTo( -holes.size.x/2 + rodD/2, -dy ) circularArcTo( holes.size.x/2 - rodD/2, -dy, curveR, false, false ) }.buildPath() val moulding : Shape2d = doc.shapes["moulding"].centerX().toOriginY() val line = moulding.extrude(path.to3d()).mirrorZ() val screwHoles = keyholeHanger( 4, 8 ).mirrorZ() .translate( holes.size.x / 2 -14, -10, 0 ) .mirrorX().also() return nodes + line - holes - screwHoles } val doc = SVGParser().parseFile( "clubsHolder.svg" ) @Piece fun back() = back( doc ) @Piece fun backPiece() : Shape3d { val back : Shape3d = back(doc) - Cube( 40, 10, 4 ).centerX().translateY(4) return (back / back.boundingCube().toOriginX()).toOriginY().translateY(1).mirrorY().also() + Cube( 40-slack, 10-slack, 4-slack ) } @Piece fun backA() : Shape3d { val back : Shape3d = back(doc) - Cube( 40, 10, 4 ).centerX().translateY(4) return (back / back.boundingCube().toOriginX()).rotateZ(45) } @Piece fun backB() : Shape3d { val back : Shape3d = back(doc) - Cube( 40, 10, 4 ).centerX().translateY(4) return (back / back.boundingCube().toOriginX().mirrorX()).rotateZ(45) } @Piece fun connector() = Cube( 40-slack, 10-slack, 4-slack ) @Piece fun prong() = prong( doc ) override fun build() : Shape3d { val prong : Shape3d = prong(doc) val club : Shape3d = club() val back : Shape3d = back(doc) return back.translateZ(-back.size.z) + club + club.rotateZ(-10).translate(90,-10,0) + prong.translateX(gap/2) .mirrorX().also() + prong.translateX(gap).rotateZ(-angle).translateX(gap/2) } }