Exit Full View
Up

/Bike/RackMount.foocad

RackMount

The rack on my bike is tricky to mount things onto. It isn't planar, parallel and there aren't stright edges.

The purple "lock" piece holds the preview-only board, preventing it being lifted up. The yellow "rubber" pieces (and the green block) constrain it in the other two axes.

I also played with the idea of creating an intermediate mount. Here, the preview-only board is a semi-permanently attached to the bike, and a box is slid onto this board, and snapped into place by the flexible hook. The red dovetails prevent sideways and vertical motion. The red hooks prevent forward/backward movement.

The advantage of this method : A single box can be attached to more than one bike, by using the same size mount on both bikes (depite the two racks being different sizes).

Disadvantges More weight Extra height Two points of failure rather than one

YMMV. As I have no need to mix and match boxes and bikes, I've chosen the simpler soultion.

FooCAD Source Code
import uk.co.nickthecoder.foocad.extras.v1.*
import static uk.co.nickthecoder.foocad.extras.v1.Extras.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*
import uk.co.nickthecoder.foocad.screws.v3.*
import uk.co.nickthecoder.foocad.smartextrusion.v1.*
import static uk.co.nickthecoder.foocad.smartextrusion.v1.SmartExtrusion.*

class RackMount : Model {

    val barSpacing = 63
    val thickness = 4.0
    val flapWidth = 10
    val flapThickness = 1.2
    
    val clearance = 0.3
    val holeDiameter = 20
    var barDiameter = 12.2
    var smallBarDiameter = 8
    val lumpHeight = smallBarDiameter/2
    val hookLength = 25

    // Adjust to compensate for non-planar rack bars.
    @Custom( about="The thicknesss of the thin part of the rack rubbers" )
    var shim = 0.6

    var rubberThickness = 8

    @Piece
    meth lockTestFit() = lock().translateZ(-0.4)
        .intersection( Cube( 200,200,0.4 ).centerXY() )

    @Piece
    meth lock() : Shape3d {
        val margin = 6
        val width = barSpacing - smallBarDiameter*2 - margin*2
        val length = barSpacing

        val shape = Square( width, length ).center().roundAllCorners(4) +
            Circle( width/2+margin - 1 )
 
        val hole = Circle( holeDiameter/2 )

        val slot = Square( flapWidth + 1, length ).centerX()
            .backTo( hole.front-5 )

        val main = (shape - hole - slot).smartExtrude(thickness)
            .edges( Chamfer(0.4) )

        val lumpShape = Square( width, 5 )
            .centerX()
            .backTo( main.front - 0.5 )
            .roundAllCorners(1)
        val lump = lumpShape.smartExtrude( thickness + lumpHeight + 1 )
            .top( Chamfer(0.4) )
            .bottomTo( 0 )
            
        val flap = Square( flapWidth, - lump.middle.y + hole.front )
            .center().frontTo( lump.middle.y )
            .extrude( flapThickness )

        val result = main + lump + flap

        return result
    }

    @Piece( about="With a locking bar, kept in place by spring steel wire" )
    meth lock3() : Shape3d {
    val margin = 6
        val width = barSpacing - smallBarDiameter*2 - margin*2
        val length = barSpacing

        val extraLength = 5
        val shape = Square( width, length + extraLength ).center().roundAllCorners(4)
            .translateY(extraLength)
 
        val hole = Circle( holeDiameter/2 )

        val main = (shape - hole).smartExtrude(thickness)
            .outsideTop( Chamfer(1.5) )
            .insideTop( Chamfer(0.4) )
            .bottom( Chamfer(0.4) )

        val sides = Square( margin -1 + 4, holeDiameter ).centerY().roundAllCorners(2)
            .leftTo( main.right - 4 )
            .smartExtrude( thickness + lumpHeight + 1 )
                .top( Chamfer(1) )
            .mirrorX().also()

        val bar = Square( width-8, 10 )
            .centerX()
            .backTo( main.front - 0.5 )
            .roundAllCorners(1)
            .smartExtrude( thickness + lumpHeight + 1 )
            .top( Chamfer(1.5) )
            .bottomTo( 0 )
            
        val springHoles1 = Cube( 2, length*2, 2 )
            .centerY()
            .centerZTo( bar.middle.z )
            .leftTo( sides.left + 3 )
            .mirrorX().also()
        
        val springHoles2 = Cube( width + 2, 2, 2 )
            .centerX()
            .centerZTo( bar.middle.z )
            .centerYTo( bar.middle.y )
    

        val result = main + sides + bar - springHoles1 - springHoles2

        return result
    }

    @Slice( perimeters=8, topSolidLayers=6, bottomSolidLayers=6 )
    @Piece( about="Relies on a snug fit (and friction) to prevent accidental unlocking" )
    meth lock2() : Shape3d {
        val margin = 6
        val width = barSpacing - smallBarDiameter*2 - margin*2
        val length = barSpacing + 20

        val shape = Square( width, length ).center().roundAllCorners(4) +
            Circle( width/2+margin - 1 )
 
        val hole = Circle( holeDiameter/2 )

        val main = (shape - hole).smartExtrude(thickness)
            .top( Chamfer(0.4) )
            .insideBottom( Chamfer(0.4) )
            .outsideBottom( Chamfer(2.0) )

        // Force the slicer to include extra perimeters (instead if unfill) where strength is most needed.
        val voids = Square( 0.2, 12 )
            .frontTo( hole.back + 0 )
            .translateX( width/3 ).also()
            .centerX()
            .extrude( thickness - 0.6 )
            .centerZTo( main.middle.z )
            .mirrorY().also()
            .color("Red")

        val reinforcements = PolygonBuilder().apply {
                moveTo( 0, main.size.y - 5 )
                lineBy( 0, main.size.y/2 - 20 )
                bezierBy(
                    Vector2( 0, 4 ), Vector2(0, 8),
                    Vector2( 4, 15 )
                )
                bezierBy(
                    Vector2( 0, 8 ), Vector2(0, 4),
                    Vector2( -4, 15 )
                )
                
                lineBy( 0, main.size.y/2 - 20 )

            }.buildPath().thickness( 4 ).centerY()
            //Square( 4, main.size.y - 10 ).roundAllCorners(1).center()
            .smartExtrude( thickness + 1 )
                .top(Chamfer(0.4))
                //.bottom( Fillet(1).reverse() )
            .translateX( width/2 - 2 ).mirrorX().also()
            .bottomTo( main.top - 0.6 )

        val result = main - voids + reinforcements

        return result
    }

    val lockSpacerHeight = 0

    @Piece
    meth lockSpacer() : Shape3d {
        val margin = 6
        val width = barSpacing - smallBarDiameter*2 - margin*2
        val length = barSpacing

        val shape = Circle( width/2+margin - 1 )
 
        val hole = Circle( holeDiameter/2 )

        return (shape - hole).extrude( lockSpacerHeight )
    }

    @Piece
    meth fixture() : Shape3d {

        val capShape = Circle( holeDiameter/2 + 3 )
        val cap = capShape.smartExtrude( thickness )
            .bottom( Chamfer(1) )
        val axel = Circle( holeDiameter/2 - clearance )
            .smartExtrude( thickness + lockSpacerHeight )
                .top( Chamfer(0.3) )
            .bottomTo(cap.top)
        val screwHoles = Countersink().mirrorZ()
            .translateY( holeDiameter/2 - 4 )
            .repeatAroundZ(3)

        return cap + axel - screwHoles
    }

    @Piece( about="TPU between the box and the metal rack. x6" )
    meth rubber() : Shape3d {
        val tabLength = 13
        val depth = 16
        val thickness = rubberThickness + shim
        val radius = barDiameter/2 + clearance

        val block = Square( tabLength*2 + barDiameter, depth ).center()
            .roundAllCorners(3)
            .smartExtrude( thickness )
            .top( Chamfer(1) )
        val valley = Circle( radius ).extrude( depth + 2 )
            .rotateX(90).centerXY()
            .bottomTo( shim )

        val screwHoles = Countersink()
            .elongated(2)
            .translateX( barDiameter/2 + tabLength/2 )
            .topTo( block.top )
            .mirrorX().also()

        return block - valley - screwHoles 
    }

    @Piece( about="Similar to piece `rubber`, but oriented differently" )
    meth rubberHalf() : Shape3d {
        val tabLength = 13
        val depth = 30
        val thickness = rubberThickness + shim
        val radius = barDiameter/2 + clearance

        val extra = 3
        val block = Square( tabLength + barDiameter + extra, depth ).centerY()
            .leftTo( -barDiameter/2 - extra )
            .roundAllCorners(3)
            .smartExtrude( thickness )
            .top( Chamfer(1) )
        val valley = Circle( radius ).extrude( depth + 2 )
            .rotateX(90).centerXY()
            .bottomTo( shim )

        val screwHoles = Countersink()
            .elongated(2)
            .translateX( barDiameter/2 + tabLength/2 )
            .topTo( block.top )


        return block - valley - screwHoles
    }

    @Piece
    meth shim() = rubber() - Cube( 100 ).centerXY().bottomTo( shim )

    @Piece
    meth shimHalf() = rubberHalf() - Cube( 100 ).centerXY().bottomTo( shim )

    meth rack() : Shape3d {
        val main = Circle(12/2).extrude( Square( 120, 400 ).center().roundAllCorners(30) )

        val smaller = Circle( smallBarDiameter/2 )
            .extrude( Square( barSpacing - smallBarDiameter, 300 ).center().roundAllCorners(20) )
            .bottomTo( main.bottom )

        return main + smaller
    }

    meth mount() : Shape3d {
        val rack = rack()

        return Trapezium( rack.size.x + 40, 440 )
            //.deltaX(20) // If the mount tapered? There are pros and cons.
            .center()
            .roundAllCorners(20)
            .smartExtrude(9)
            .offsetBottom(-5) // Forms a DOVETAIL shape
    }

    @Piece
    meth foot() : Shape3d {
        val height = 20
        val foot = Triangle( 34 ).roundAllCorners(7).backTo(8)
            //Square( 30, 15 ).center().roundAllCorners(5)
            //Circle( 10 )
            .smartExtrude( height )
            .top( Fillet( 2 ) )
            .offsetTop( -1 )

        val hole = Countersink()
            .recess( height - 6 )
            .topTo( foot.top )
            
        return foot - hole
    }

    override fun build() : Shape3d {

        val rack = rack().topTo(0).color("DimGrey")

        val block = Cube( 45, 100, 12 )
            .centerX()
            .translateY(-barSpacing/2)
            .topTo(0)
            .color("Green")
        val mount = mount().color("LightGreen").previewOnly()
            .bottomTo( shim )

        val rubbers = rubber().mirrorZ().topTo( mount.bottom )
            .centerXTo( rack.left + barDiameter/2 )
            .translateY(90)
            .mirrorY().also().mirrorX().also()
        val endRubbers = rubberHalf().rotateZ(-90).mirrorZ().topTo( mount.bottom )
            .translateY( rack.back - barDiameter/2 )
            .mirrorY().also()

        val lock = lock2().mirrorZ().topTo( block.bottom ).color("Purple")
        val fixture = fixture().topTo( block.bottom ).color("Orange")

        val rails = Square( 16, 300 )
            .smartExtrude( 10 )
            .offsetBottom( 5 )
            .leftTo( mount.right - 4 )
            .topTo( mount.top )
            .centerY()
            .mirrorX().also()
            .color("Red")

        return mount + rack +
            rubbers + endRubbers +
            lock.rotateZ(90) + lock.previewOnly() +
            fixture + block
            
    }
}