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