Exit Full View
ZipUp

/Bottles/Bottle.foocad

Bottle

A parametric (general purpose) bottle with a screw cap.

For a round bottle (rather than the default rounded square), set round to half of the width.

Print with PETG (not PLA if you want it to be water tight).

IMPORTANT

Layer lines are a peferct breeding ground for bateria, so 3D prints should NOT be used for food or drink.

FooCAD Source Code
import uk.co.nickthecoder.foocad.cup.v1.*
import static uk.co.nickthecoder.foocad.cup.v1.Cup.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*
import static uk.co.nickthecoder.foocad.circular.v1.Circular.*
import uk.co.nickthecoder.foocad.cup.v1.*
import static uk.co.nickthecoder.foocad.cup.v1.Cup.*
import uk.co.nickthecoder.foocad.smartextrusion.v1.*
import static uk.co.nickthecoder.foocad.smartextrusion.v1.SmartExtrusion.*
import uk.co.nickthecoder.foocad.threaded.v2.*

class Bottle : Model {

    @Custom
    var size = Vector3( 80, 80, 150 )

    @Custom
    var throatDiameter = 40

    @Custom
    var wallThickness = 1.8

    @Custom
    var baseThickness = 2.0

    @Custom
    var capThickness = 2.0

    @Custom
    var round = 20

    @Custom
    var bottomFillet = 10

    @Custom
    var neckHeight = 50

    @Custom
    val capHeight = 14

    @Custom
    val funnelSize = Vector3( 120, 160, 100 )//Vector3( size.x, size.y, Math.max(size.x, size.y) )

    meth capThread() = Thread( throatDiameter + 8, 3 )
        .rodChamfer(1)


    @Piece
    @Slice( topSolidLayers=10, bottomSolidLayers=10, perimeters=4 ) // Solid!
    meth cap() : Shape3d {
        val thread = capThread()
        val solid = Circle( throatDiameter/2 + 6 )
            .cup( capHeight, 6 ).baseThickness(capThickness)
            .outsideBottom( ProfileEdge.roundedChamfer(3) )
            .insideBottom( Chamfer(capThickness) )
            .outsideTop( Chamfer(1) )

        val hole = thread.threadedHole( solid.top - capThickness*1.5 )
            .chamferStart(false)
            .chamferEnd(true)
            .bottomTo( capThickness*1.5 )

        return solid - hole
    }

    @Piece
    @Slice( brimWidth=5, brimWidthInterior=5, seamPosition="random" )
    meth body() : Shape3d {

        val bodyHeight = size.z - capHeight - neckHeight
        val thread = capThread()
        val diameter = Math.min( size.x, size.y )
    
        val size2d = Vector2( size.x, size.y )
        val squarish = Square( size.x, size.y ).center().roundAllCorners(round, 20)

        val main = squarish
            .cup( bodyHeight, wallThickness ).baseThickness( baseThickness )
            .bottom( roundedChamfer(bottomFillet) )

        val steps = 10
        val transD = thread.coreRadius() *2 - 1.5
        val neck = extrudeRoundedSquareToCircle( neckHeight, size2d, round, transD )
            .bottomTo( main.top )
        val insideNeck = extrudeRoundedSquareToCircle( neckHeight, size2d, round, transD, -wallThickness )
            .bottomTo( main.top )

        val threaded = (
                thread.threadedRod( capHeight - wallThickness ) -
                Circle( thread.coreRadius() - 4 )
                    .smartExtrude( capHeight + 0.02 )
                        .bottom( Chamfer( 2 ).reverse() )
                    .bottomTo(-0.01)
            ).bottomTo( neck.top - 1 )

        return main + neck + threaded - insideNeck
    }

    @Piece
    @Slice( brimWidth=5, brimWidthInterior=5, seamPosition="random" )
    meth funnel() : Shape3d {
        val neckDiameter = capThread().coreRadius() * 2 - wallThickness
        val size2d = Vector2( funnelSize.x, funnelSize.y )

        val cap = cap().bottomTo( funnelSize.z - capThickness*1.5 )
        val capAngle = Circle( cap.size.x/2 - 0.55 )
            .smartExtrude( 10 )
            .offsetBottom( -10 )
            .topTo( cap.bottom +1 )
        val lip = (Circle( throatDiameter/2 - 1 ) - Circle( throatDiameter/2 - 3 ))
            .smartExtrude( cap.size.z - 2 )
                .outsideBottom( Chamfer(5).reverse() )
                .insideBottom( Chamfer(4) )
                .outsideTop( Chamfer(1) )
            .bottomTo( cap.bottom )        

        val main = extrudeRoundedSquareToCircle( funnelSize.z, size2d, round, neckDiameter )
        val insideMain = extrudeRoundedSquareToCircle( funnelSize.z, size2d, round, neckDiameter, -wallThickness )

        return main + cap + capAngle- insideMain + lip 
    }

    func extrudeRoundedSquareToCircle( height : double, startSize : Vector2, startRound : double, endDiameter : double, offset : double ) : Shape3d {
        val steps = 20
        return ExtrusionBuilder().apply {
            for ( step in 0 .. steps ) {
                val t = step/steps
                crossSection( roundedSquareToCircle( startSize, startRound, endDiameter, t).offset(offset) )
                forward( height/steps )
            }
        }.build()
    }
    func extrudeRoundedSquareToCircle( height : double, startSize : Vector2, startRound : double, endDiameter : double ) : Shape3d {
        return extrudeRoundedSquareToCircle( height, startSize, startRound, endDiameter, 0 )
    }

    func roundedSquareToCircle( startSize : Vector2, startRound : double, endDiameter : double, t : double ) : Shape2d {
        val s = Eases.EASE_IN_OUT.ease(t)

        val x = startSize.x*(1-s) + endDiameter*s
        val y = startSize.y*(1-s) + endDiameter*s

        val endRound = endDiameter/2 - 0.1
        val round = startRound *(1-s) + endRound*s

        return Square( x, y ).center().roundAllCorners(round, 20)
    }


    @Piece( printable=false )
    override fun build() : Shape3d {
        val body = body().color("Green")
        val cap = cap().rotateX(180).topTo( body.top + baseThickness*1.5 )

        return body + cap

    }

}