FooCAD Source Codeimport static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*
import static uk.co.nickthecoder.foocad.chamferedextrude.v1.ChamferedExtrude.*
/**
Cool down your drinks with chilled stainless steel balls.
You can buy them in bulk but you'll need a container to put them in!
The simplest way to stack them is to print 2 or more sets of
3x2 or 4x3 or 5x4 etc. They then stack by rotating by 90 degrees.
*/
class WhiskeyBallTray2 : Model {
@Custom
var lid = true
@Custom
var across = 2
@Custom
var down = 2
@Custom
var ballSize = 21
@Custom
var thickness = 2
@Custom
var gap = 1.0
@Custom
var pegHeight = 3.0
@Custom
var pegSize = 9.0
@Custom
var earSize = 15
fun arrange( shape : Shape3d ) : Shape3d {
val list = listOf<Shape3d>()
val dx = (ballSize + gap)
for ( y in 0 until down ) {
for ( x in 0 until across ) {
list.add( shape.translate( x * dx , y * dx, 0))
}
}
return Union3d( list )
}
fun pegProfile() : Shape2d {
return PolygonBuilder().apply {
moveTo( pegSize, 0 )
circularArcTo( 0, pegSize, pegSize, false, false )
circularArcTo(-pegSize, 0, pegSize, false, false )
circularArcTo( 0,-pegSize, pegSize, false, false )
circularArcTo( pegSize, 0, pegSize, false, false )
}.build() / Square( pegSize * 1.2 ).center()
}
fun pegs() : Shape3d {
return pegProfile()
.chamferedExtrude( pegHeight, 0, 0.6 )
.repeatX( across - 1, ballSize + gap)
.repeatY( down - 1, ballSize + gap )
.centerXY()
.translateZ( ballSize/2 + thickness - 0.3 )
}
fun holes() : Shape3d {
val slack = 0.2
val zExcess = 1.6
return pegProfile().offset(slack)
.chamferedExtrude( pegHeight + zExcess + 0.01, 0.6, 0 )
.repeatX( across - 1, ballSize + gap)
.repeatY( down - 1, ballSize + gap )
.centerXY()
.translateZ( ballSize/2 + thickness - pegHeight -zExcess )
}
// These are an attempt to prevent the piece lifting off the bed, by
// removing long lengths of plastic, and therefore reducing the force from
// shrinkage when the part cools.
fun notches() : Shape3d {
return Cylinder( ballSize + thickness + 1, 4 ).sides(4)
.repeatX( across - 1, ballSize + gap ).centerX()
.translateY( down / 2 * (ballSize + gap) + thickness + 0 )
.mirrorY().also() +
Cylinder( ballSize + thickness + 1, 4 ).sides(4)
.repeatY( down - 1, ballSize + gap ).centerY()
.translateX( across / 2 * (ballSize + gap) + thickness + 1 )
.mirrorX().also()
}
fun ears() : Shape3d {
return Circle( earSize ).translate( (ballSize + gap)/2 * across, (ballSize + gap)/2 * down )
.mirrorX().also()
.mirrorY().also()
.extrude(0.2)
}
override fun build() : Shape3d {
val profile = Square(
(ballSize+gap) * across + thickness * 2,
(ballSize + gap) * down + thickness * 2
).roundAllCorners( ballSize/2+thickness - 4 ).center()
val solid = profile.chamferedExtrude( ballSize/2 + thickness, 3, 1 )
val holes = arrange( Sphere(ballSize/2).translateZ(ballSize/2 + thickness) ).centerXY()
val result = solid - holes
val withoutEars = if (lid) {
result - holes() - notches()
} else {
result + pegs() - notches()
}
return if (earSize > 0) {
withoutEars + ears()
} else {
withoutEars
}
}
}