Exit Full View
Up

/New/WhiskyBallTray2.foocad

WhiskyBallTray2
FooCAD Source Code
import 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
        }

    }
}