Exit Full View
Up

/Games/ClubsHolder.foocad

ClubsHolder

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.

FooCAD Source Code
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)
    }
}