Exit Full View
Up

/Bike/PannierRackMount.foocad

PannierRackMount

I want to attach a box, OR a flat piece of plywood to my pannier, and swap them easily. When buying groceries, I use the box, but bulky items will use the "flat bed" plywood.

I never printed this, as I opted for a different solution.

Print Notes

I suggest adding "ears" to prevent warping.

FooCAD Source Code
import static uk.co.nickthecoder.foocad.chamferedextrude.v1.ChamferedExtrude.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*

class PannierRackMount : Model {

    @Custom( about="Size of the bars" )
    var size = Vector3( 200, 14, 8 )

    @Custom( about="Diameter of the rack's tubes (with extra for clearance)" )
    var diameter = 11

    @Custom( about="The distance of the two sides of the rack" )
    var distance = 150

    @Custom( about="The distance of the extra holes" )
    var extraHoleDistance = 100

    @Custom( about="The length of the slot, to allow for variation in the rack's dimensions")
    var play = 3

    @Custom
    var clipThickness = 2

    @Custom
    var clipOffset = 14

    @Custom( about="Diameter for the main bolts" )
    var mainHoleDiameter = 6.5

    @Custom( about="Diameter for the bolts holding the clip" )
    var clipHoleDiameter = 4

    @Custom( about="Diameter of the clips' slots" )
    var clipSlotDiameter = 4

    fun mainHolesLayout( shape : Shape2d ) : Shape2d {
        return shape
            .repeatX( 3, distance/2 ).centerX() +
            shape.translateX( extraHoleDistance/2 ).mirrorX().also()
    }

    fun clipHolesLayout( shape : Shape2d ) : Shape2d {
        return shape
            .translateX(clipOffset)
            .mirrorX().also()
            .translateX( distance/2 )
            .mirrorX().also()
    }

    @Piece
    fun bar() : Shape3d {
        val bar = Square( size.x, size.y )
            .roundAllCorners(size.y/2)
            .center()
            
        // The bumps ensure that there is equal "meat" along the length.
        // For strength, wherever there is a hole, we add a bump.
        val mainHole = Circle( mainHoleDiameter/2 ).center()
        val mainHoles = mainHolesLayout( mainHole )

        val mainBump = Circle( (size.y + mainHoleDiameter)/2 )
        val mainBumps = mainHolesLayout( mainBump )

        val clipBump =  Circle( (size.y + clipHoleDiameter)/2 )
        val clipBumps = clipHolesLayout( clipBump )

        val clipHoles = clipHolesLayout( Circle( clipHoleDiameter/2 ).center() )

        // Force extra perimeters near the holes.
        val cross = Square( size.y * 3, 0.2 ).center()
            .rotate(45)
            .mirrorX().also()
        val forceMorePlastic = ( mainHolesLayout( cross ) + clipHolesLayout( cross ) )
            .extrude(0.2)
            .center()
            .spreadZ( 3, size.z, 0.5 )

        val holes = mainHoles + clipHoles
        val flat = bar + mainBumps + clipBumps
        
        return flat.extrude( size.z ) - forceMorePlastic - holes.chamferedExtrude( size.z, -1, -1 )
    }

    /**
        This isn't load bearing, it only keeps the bars in place.
        So it can be thin.
    */
    @Piece
    fun clip() : Shape3d {
        val extra = 4 + play
        val arcDiameter = diameter + clipThickness
        val profile = PolygonBuilder().apply {
            moveTo(0,0)
            lineBy( clipOffset - arcDiameter/2 + extra, 0 )
            lineBy( 0, diameter / 2 - clipThickness/2 )
            arcBy( Vector2(arcDiameter,0), Vector2(arcDiameter/2,arcDiameter/2), 180, true, false )
            lineBy( 0, -diameter / 2 + clipThickness/2 )
            lineBy( clipOffset - arcDiameter/2 + extra, 0 )
        }.buildPath().thickness( clipThickness )
            .centerX()

        val hole = ( Circle(clipSlotDiameter/2).sides(6).translateX(-play/2) hull Circle(2).sides(6).translateX(play/2))
            .extrude( 100 )
            .center()
            .rotateX(90)
        val holes = hole
            .translate( clipOffset, 0, size.y / 2 )
            .mirrorX().also()

        val tube = Cylinder( 60, diameter/2 )
            .center()
            .translate( 0, diameter/2 - clipThickness/2, size.y/2 )
            .previewOnly()
        return profile.extrude( size.y ) - holes + tube
    }

    @Custom
    var spacerSize = Vector3( 30, 30, 16 )

    /**
        The base of reinfored plastic boxes have fins.
        Glue these spacers to the bottom of the box, so that it rests firmly on the rack mount.
    */
    @Piece
    fun spacer() : Shape3d {
        val shape = Square( spacerSize.x, spacerSize.y ).center()
            .roundAllCorners ( 3 )
        val hole = Circle( mainHoleDiameter / 2 + 2 )

        return shape.extrude( spacerSize.z ) - hole.chamferedExtrude( spacerSize.z, -1 )
    }

    override fun build() : Shape3d {
        val clips = clip()
            .translateX( distance/2 )
            .rotateX(-90)
            .centerY()
            .topTo(-0.1)
            .mirrorX().also()

        return bar().color("Green") + clips
    }
}