/Bottles/Bottle.foocad

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.
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
}
}
