Exit Full View
ZipUp

/Bottles/BirdSeedContainer.foocad

BirdSeedContainer

Containers for bird seed, and other bird food.

Goals:

  1. Air tight to keep seeds fresh
  2. Easy to refill (from LARGE sacks/buckets of seed)
  3. 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.

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

    }

}