/Bottles/BirdSeedContainer.foocad

Containers for bird seed, and other bird food.
Goals:
- Air tight to keep seeds fresh
- Easy to refill (from LARGE sacks/buckets of seed)
- Easy to pour seed into bird feeders
I buy large tubs/sacks of bird food, and want to decant the food into smaller containers. I put out fresh food every other day, and I don't want to lug the large tubs/sacks every time.
Fast print, but with layer height override of 0.2mm.
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 BirdSeedContainer : Model {
@Custom
var diameter = 120
@Custom
var height = 250
@Custom
var throatDiameter = 40
@Custom
var wallThickness = 1.8
@Custom
var baseThickness = 2.0
@Custom
var round = 26
@Custom
var bodyFillet = 20
@Custom
var neckHeight = 36
@Custom
var capText = ""
val capHeight = 14
val baseHeight = 14
meth capThread() = Thread( throatDiameter + 8, 3 )
.rodChamfer(1)
meth baseThread() = Thread( diameter - 6, 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, 5 ).baseThickness(baseThickness)
.outsideBottom( ProfileEdge.roundedChamfer(3) )
.insideBottom( Chamfer(baseThickness) )
.outsideTop( Chamfer(1) )
val hole = thread.threadedHole( solid.top - baseThickness*1.5 )
.chamferStart(false)
.chamferEnd(true)
.bottomTo( baseThickness*1.5 )
val text = Text( capText ).center().mirrorX()
.smartExtrude( 0.6 )
.bottom( Chamfer(0.2).reverse() )
.bottomTo( -0.01 )
return solid - hole - text
}
@Piece
@Slice( topSolidLayers=10, bottomSolidLayers=10, perimeters=4 ) // Solid!
meth base() : Shape3d {
val thread = baseThread()
val solid = Circle( diameter/2 )
.cup( baseHeight, 7 ).baseThickness(baseThickness)
.outsideBottom( ProfileEdge.roundedChamfer(5) )
.insideBottom( Chamfer(baseThickness) )
.outsideTop( Chamfer(1) )
val hole = thread.threadedHole( solid.top - baseThickness*1.5 )
.chamferStart(false)
.bottomTo( baseThickness*1.5 )
return solid - hole
}
@Piece
@Slice( brimWidth=5, brimWidthInterior=5, seamPosition="random" )
meth body() : Shape3d {
val bodyHeight = height - baseHeight - capHeight - neckHeight
val baseThread = baseThread()
val base = baseThread.threadedRod( baseHeight-1 )
.chamferTop( false )
val insideBase = Circle( baseThread.coreRadius() - wallThickness*2 )
.smartExtrude( baseHeight-1 + 0.02 )
.bottom( Chamfer(0.5).reverse() )
.top( Chamfer(wallThickness+1).reverse() )
.bottomTo( -0.01 )
val circle = Circle( diameter/2 )
val squarish = Square( diameter ).center().roundAllCorners(round, 20)
val bottomFillet = (diameter/2-round)*1 + 2
val middle = squarish
.smartExtrude( bodyHeight )
.top( roundedChamfer( bodyFillet ) )
.bottom( roundedChamfer(bottomFillet) )
.bottomTo( base.top - 2 )
val insideMiddle = squarish.offset(-wallThickness)
.smartExtrude( bodyHeight )
.top( roundedChamfer( bodyFillet ) )
.bottom( roundedChamfer(bottomFillet) )
.bottomTo( base.top - 2 )
val transition = circle
.smartExtrude( bottomFillet )
.bottom( ProfileEdge.roundedChamfer(9) )
.bottomTo( middle.bottom )
val insideTransition = circle.offset(-wallThickness)
.smartExtrude( bottomFillet )
.bottom( ProfileEdge.roundedChamfer(11) )
.bottomTo( middle.bottom )
val capThread = capThread()
val neckSteps = 20
val neck = ExtrusionBuilder().apply {
for ( step in 0 until neckSteps ) {
val t = step/neckSteps
crossSection( neckShape(t) )
forward( neckHeight/neckSteps )
}
crossSection( neckShape(1) )
forward(2)
crossSection( Circle(capThread.coreRadius()) )
}.build()
.bottomTo( middle.top )
val insideNeck = ExtrusionBuilder().apply {
val steps = 10
for ( step in 0 until neckSteps ) {
val t = step/neckSteps
crossSection( neckShape(t).offset(-wallThickness) )
forward( neckHeight/neckSteps )
}
forward(2)
crossSection( neckShape(1).offset(-wallThickness) )
}.build()
.bottomTo( middle.top )
val forCap = capThread.threadedRod( capHeight-1 )
.bottomTo( neck.top-1 )
val insideForCap = Circle( throatDiameter/2 )
.smartExtrude( capHeight )
.bottomTo( neck.top - 1.01 )
// insides
val insides = insideMiddle + insideTransition + insideBase + insideNeck + insideForCap
return base + middle + transition + neck + forCap - insides
}
meth neckShape( t : double ) : Shape2d {
val startDiameter = diameter - bodyFillet
val endDiameter = throatDiameter + wallThickness*2
val s = Math.pow(Math.sin( t*Math.PI/2 ), 1.1)
val d = startDiameter*(1-s) + endDiameter*s
val startRound = round - bodyFillet/2 + 0.5
val endRound = endDiameter/2 - 0.1
val round = startRound *(1-t) + endRound*t
return Square( d ).center().roundAllCorners(round, 20)
}
@Piece
meth tray() : Shape3d {
val height = 10
val diameter = base().size.x + 0.4
val thickness = 2
val clearance = 0.2
val margin = 2
val circles = Circle( diameter/2 + clearance )
.repeatX(3, diameter + thickness*2 + clearance*2)
.repeatY(2, diameter + thickness*2 + clearance*2)
.center()
val outside = circles.boundingSquare().offset(thickness + margin)
val fill = (outside.roundAllCorners(20) - circles).toPolygon()
.smartExtrude( height )
.outsideBottom( Chamfer(1) )
.top( Chamfer(1) )
return fill
}
@Piece( printable=false )
override fun build() : Shape3d {
val base = base()
val body = body().bottomTo( base.top ).bottomTo( baseThickness*1.5 ).color("Green")
val cap = cap().rotateX(180).topTo( body.top + baseThickness*1.5 )
return base + body + cap
}
}
