FooCAD Source Codeimport static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*
/**
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 WhiskeyBallTray : Model {
@Custom
var ballSize = 21
@Custom
var across = 3
@Custom
var down = 2
@Custom
var thickness = 2
@Custom
var gap = 1.0
var slack = 0.1
var pegHeight = 3.0
var fakeBrim = true
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 pegShape() : Shape2d {
val circles = Circle( ballSize/2+thickness+slack )
.translate( (ballSize + gap)/2, (ballSize + gap)/2 )
.mirrorX().also().mirrorY().also()
return Square( ballSize ).center() - circles
}
fun peg() : Shape3d {
val ps = pegShape()
val larger = ps.offset(slack+thickness)
return ExtrusionBuilder().apply {
crossSection(larger)
forward(thickness)
crossSection()
crossSection(ps)
forward(pegHeight-0.6) // 0.6 is for a chamfer.
crossSection()
forward(1)
crossSection(ps.offset(-0.6))
}.build() -
// A hole, so that water can drain out
Cylinder( ballSize, 1.5 ).sides(4).center()
}
override fun build() : Shape3d {
val spheres = arrange( Sphere(ballSize/2+thickness) )
val holes = arrange( Sphere(ballSize/2) )
val topHalf = Cube( 1000, 1000, ballSize + 1 ).centerXY()
val flattenBase = Cube( 1000, 1000, 2 ).center()
.translateZ( -ballSize/2 - thickness )
val result = spheres - holes - topHalf - flattenBase
val pegs = if (pegHeight > 0) {
peg().translate( ballSize/2 + gap/2, ballSize/2 + gap/2, ballSize/2 - thickness)
.repeatY(down-1, ballSize + gap)
//.translateY( (ballSize + gap)*(down-2) ).also()
.color( "Green" )
} else {
Cube(0)
}
val hingeWidth = 0.2
val half = (result.translateZ(ballSize/2+ thickness/2) + pegs)
.toOriginX().translateX(hingeWidth)
val hinge = Cube(thickness*2, (ballSize*0.5), 0.4).centerXY()
.translateZ( ballSize / 2 + thickness/2 - 0.4 )
.repeatY( down, ballSize + gap )
.color("Blue")
val brim = if (fakeBrim) {
Cube (2 * across * (ballSize+gap), down * (ballSize+gap), 0.2 ).centerX()
.translateY(-ballSize/2)
} else {
Cube(0)
}
return half.translateX(-half.size.x-hingeWidth).also() + hinge + brim
}
}