Exit Full View
Up

/New/WhiskyBallTray.foocad

WhiskyBallTray
FooCAD Source Code
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<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

    }
}