/Bike/RackMount.foocad

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.
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
}
}
