import 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() 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 } }