Exit Full View
Up

/Bike/PannierBoxFixture.foocad

PannierBoxFixture

Helps attach a "heavy duty" plastic box to my bike's pannier. If the box is hit (or the bike falls over), it is likely that the clips will break. I prefer the clips break than the box. This has happened more than once, when my bike fell over. I keep spares on me!

The bottom of the box is not flat, and instead has reenforcing fins. So for these fixtures to touch the bottom of the box, there is a block built into the model.

The shape of the clip is similar to the [PannierClip], but in this case, I've created it progamatically, rather than using an SVG diagram.

I'm using M5 brass inserts, which are melted into the plastic with a soldering iron.

Print Notes

PETG is preferable, as it has a bit of spring to it.

PLA is more rigid, and therefore breaks more readily.

Version

V1 I made the "block" larger than needed.

FooCAD Source Code
import static uk.co.nickthecoder.foocad.arrange.v1.Arrange.*
import static uk.co.nickthecoder.foocad.chamferedextrude.v1.ChamferedExtrude.*

class PannierBoxFixture : Model {
    
    val toHole = 16 // 27
    var pannierWidth = 166
    var clipDiameter = 12
    var thickness = 3
    var clipDepth = 12
    //var tabDepth = 18
    var holeDiameter = 6.5

    // The size while it is printed, i.e, Z is the "thickness".
    // The original use Y=20mm
    var blockSize = Vector3( 20, 15, 18 )

    var spacerThickness = 3.0
    
    var washerSize = Vector2( 16, 6 )

    @Custom(required=false) 
    var boltHeadSize = Vector2(14, 4)


    @Piece
    @Slice( brimWidth=4 )
    fun mirror() : Shape3d = one().mirrorX()

    @Piece
    @Slice( brimWidth=4 )
    fun one() : Shape3d {
        val profile = clip(clipDiameter-thickness/2, toHole)
        val wedge = Cube( 100 ).centerY().rotateX(-45-90).translateZ(clipDepth)

        val hole = Cylinder( 100, holeDiameter/2 )
            .rotateX(-90)
            .translate(toHole, 0, blockSize.z/2 )

        val block = Cube( blockSize.x, blockSize.y, blockSize.z ).centerXTo(hole.middle.x)frontTo( profile.back - thickness )

        val insert =
        (
            Circle( blockSize.z/2 ).sides(6).roundAllCorners(2) +
            Square( 10, blockSize.z ).center() -
            Circle( holeDiameter ).sides(6)
        ).extrude( holeDiameter )
        .rotateX(-90)
        .translate(toHole, 0, blockSize.z/2).backTo(profile.back - thickness )
            
        return (
            profile.extrude(clipDepth) +
            (profile.extrude(blockSize.z) intersection wedge)
        )  + block - hole // + wedge.previewOnly()
    }

    // Print using TPU, and glue to the block, to give a rubbery feel.
    // This make help protect the fixture and the box on rough terrain.
    // Use the Tile extension to print 4 at once.
    @Piece
    fun spacer() : Shape3d {
        return Cube( blockSize.x, blockSize.z, spacerThickness ).centerXY() -
            Cylinder( 100, holeDiameter / 2 ).center()
    }


    // Use the Tile extension to print 4 at once.
    @Piece
    @Slice( brimWidth=4, perimeters=6 )
    fun washer() : Shape3d {
        return (Circle( washerSize.x/2 ) - Circle( holeDiameter/2 )).extrude( boltHeadSize.y + 1 ) -
            Cylinder( 100, boltHeadSize.x / 2 ).translateZ( 1 )
    }

    // I printed this using TPU. Use the Tile extension to print 4 at once.
    @Piece
    fun washerCap() : Shape3d {
        val t = 2
        val innerD = washerSize.x + 0.6
        val outerD = innerD + t
        return Circle( outerD /2 ).chamferedExtrude( washerSize.y + t, 0.8, 0 ) - 
            Cylinder( 100, innerD / 2 ).translateZ( t )
    }

    @Slice( brimWidth=4, perimeters=6 )
    override fun build() : Shape3d = arrange( 3, one(), mirror() )

    fun clip(left : double, right : double) : Shape2d {
  
        val scale = clipDiameter + thickness
        val a = Vector2( -0.5, 0.5 ) * scale
        val b = Vector2( -0.38, 0.36 ) * scale
        val c = Vector2( -0.5, 0 ) * scale
        val delta1 = Vector2( 0.1, 0 ) * scale
        val delta2 = Vector2( 0, -0.09 ) * scale
        val delta3 = Vector2( 0, 0.25 ) * scale

        val path = PolygonBuilder().apply {
            moveTo( -left, a.y )
            lineTo( a )
            //lineTo( b )
            bezierByTo( delta1, delta2, b )
            //lineTo( c )
            bezierByTo( delta2, -delta3, c )
            circularArcTo( c.mirrorX(), 0.5*scale, true, true )
            //lineTo( b.mirrorX() )
            bezierByTo( delta3, -delta2, b.mirrorX() )
            //lineTo( a.mirrorX() )
            bezierByTo( -delta2, delta1, a.mirrorX() )
            lineBy( right, 0 )
        }.buildPath()
        val circles = Circle( thickness/2 ).centerTo(path.points[0]) +
            Circle( thickness/2 ).centerTo(path.points[path.points.size()-1])
        return path.thickness( thickness ) + circles

    }
}