Exit Full View
ZipUp

/Garden/BirdFeeder.foocad

BirdFeeder

A bird feeder for birds that sit upright on a perch while feeding.

Notes

After many years, my first feeder broke - probably due to UV damage, followed by a heavy "handed" bird (or squirrel). It was printed with PLA, but next time, I'll try PETG for the tube, and maybe ASA for the other parts.

I thought about only reprinting the broken tube, but decided to model from scratch. So check out BirdFeeder2.foocad.

FooCAD Source Code
import static uk.co.nickthecoder.foocad.chamferedextrude.v1.ChamferedExtrude.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*

class BirdFeeder : AbstractModel() {
    
    @Custom( about="The internal width of the tube" )
    var diameter = 50

    @Custom( about="The height of the tube" )
    var height = 120

    @Custom( about="The radius of the tube's corners" )
    var radius = 10

    @Custom( about="Which way do the hanging holes face?")
    var sideways = false

    var thickness = 0.8
    var rim = 2
    var rimE = 2

    var holeD = 20
    var holeHeight = 7 + 37 // 37 + pegZ

    @Custom( about="The amount the perch sticks out" )
    var perchOut = 30

    var perchD = 6
    var perchZ = 7

    var innerT = 1.4


    @Custom( about="How easily the parts fit together" )
    var slack = 0.3


    fun profile() = Square( diameter ).center().roundAllCorners(radius)


    fun plainTube() : Shape3d {
        val outside = profile().offset(thickness)

        return ExtrusionBuilder().apply {
                crossSection( outside.offset(rim) )
                forward(rimE)
                crossSection()
                forward(rim*2)
                crossSection( outside )
                forward( height - rim*4 -rimE*2 )
                crossSection()
                forward(rim*2)
                crossSection( outside.offset(rim) )
                forward(rimE)
                crossSection()
                crossSection( outside.offset(-thickness) )
                forward( -height ) 
                crossSection()
            }.buildClosed()
            .bottomTo( thickness )
    }

    fun hangingHole() : Shape3d {
        return Square( 10, 10 ).roundAllCorners(1,3).extrude(100)
            .center()
            .rotateY(90)
            .rotateZ( if (sideways) 90 else 0 )
    }

    /**
        A (transpent) tube where the seeds are stored.
        There is a rim at both ends, to add extra strength, without having to make
        the tube thick in the middle.
    */
    @Piece
    fun tube() : Shape3d {

        val tube = plainTube()
        val hangingHole : Shape3d = hangingHole().topTo(tube.top-10)

        return (tube - hangingHole - feedingHole() - perchHole() )
            .color("White").bottomTo(0)
    }

    @Piece
    fun gromet() : Shape3d {
        val inside = Square( 10 - slack*2 ).center()
        val main = (inside.roundAllCorners(2) - Circle(2)).extrude(5)
        val flat = (inside.offset(2).roundAllCorners(4) - Circle(2)).extrude(0.8)

        return (flat + main).color("Green")
    }

    @Piece
    fun base() : Shape3d {
        val profile : Shape2d= profile()
        val pegHole = perchHole()
        val height = holeHeight - holeD/2+3
        val mainHeight = perchZ+perchD

        val mid = Square( diameter-slack*2 - radius*2, diameter-slack*2 ).center()
        val top = Square( 5, diameter-slack*2 ).center()
        val base = ExtrusionBuilder().apply {
            crossSection( profile.offset(rim+thickness) )
            forward(thickness)
            crossSection()
            crossSection( profile.offset(-slack) )
            forward( mainHeight-thickness )
            crossSection()
        }.build() -
            mid.offset(-innerT).extrude( height ).bottomTo(-1)

        val wedge = ExtrusionBuilder().apply {
            forward( height - innerT )
            crossSection( top.offset(-innerT) )
            forward( -height + mainHeight )
            crossSection( mid.offset(-innerT) )
            crossSection( mid )
            forward( height - mainHeight +innerT)
            crossSection( top )
        }.build()

        return (base + wedge- pegHole).color("Green")
    }

    fun feedingHole() : Shape3d {
        val radius = holeD/2 + innerT + slack
        // Make the hole easy to print, by making the top angled, then flat
        // so that the overhang is small, then a bridge.
        val profile = Hull2d( Circle( radius ), Square(radius).centerY().rightTo(0) )

        return profile.extrude( 100 ).centerZ().rotateY(90).centerZTo( holeHeight )
    }

    fun perchProfile() = Square( perchD ).center().roundAllCorners(2,1)



    fun perchHole() : Shape3d {
        return perchProfile().extrude( 100 ).center().rotateY(90).centerZTo(perchZ)
    }

    @Piece
    fun perch() : Shape3d {
        return perchProfile().offset(-slack).chamferedExtrude( diameter + perchOut * 2, 0.6 )
            .rotateY(90).bottomTo(0)
            .color("Green")
    }

    @Piece
    fun front() : Shape3d {
        val outerR = holeD/2 + innerT

        val shape = Circle( outerR + 3 )
            .centerXTo( holeHeight )
        val front = shape.extrude( innerT )

        val a = Cylinder( diameter/2 + thickness + innerT - slack, outerR )
            .centerXTo( holeHeight )
        val b = Cylinder( a.size.z, holeD/2 )
            .centerXTo( holeHeight )
            .topTo( a.top - innerT*2 )

        val cutAway = Cube( holeD, holeD, holeD*2 ).centerXY()
            .bottomTo( innerT ).rightTo( holeHeight - holeD*0.3 )

        val hole = Cylinder( diameter, 1 ).centerXTo( holeHeight )
            .bottomTo( b.top + innerT )

        return (front + a - b - cutAway - hole).color("Green") 
    }

    @Piece
    fun lid() : Shape3d {
        val profile : Shape2d= profile().offset( 12 )
        val lid = ExtrusionBuilder().apply {
            crossSection( profile )
            forward( 3 )
            crossSection()
            for ( i in 0..10) {
                forward( 1 )
                crossSection( profile.offset( -10 + 10 * Degrees.sin( 90 -i * 7)))
            }
            forward( diameter*0.3 )
            crossSection( profile.scale(0.05, 0.35) )
        }.build()

        val gSlack = slack + 1
        val groove = (profile().offset(rim+thickness+gSlack) - profile().offset(-gSlack))
            .extrude( rimE * 2)

        val rodHole = Cylinder( 100, 2 ).center().rightTo( - diameter/2-5 )
            .mirrorX().also()

        return (lid - groove - rodHole).color("Green")
    }

    @Piece
    fun allBarTube() : Shape3d {
        val fronts = front().tileX(2,2).toOrigin()
        val gromets = gromet().tileX(2,2).toOrigin()
        val lid : Shape3d = lid().frontTo(0).toOrigin()
        val base : Shape3d = base().toOrigin()
        val perch : Shape3d = perch()
        val perchWithEars = perch + Cylinder( 0.2, 10 ).centerXTo(perch.left)
            .centerXTo(perch.right).also()
        
        val y1 = layoutY(2, lid, gromets.rightTo(lid.right) )   
        val y2 = layoutY(2, base, fronts )
        val x = layoutX(2, y1, y2).leftTo(0)
        return layoutY(-2, x, perchWithEars.leftTo(0) )
    }

    @Piece( printable = false )
    override fun build() : Shape3d {
        val tube : Shape3d = tube().bottomTo(thickness).opacity(0.5)
        val base : Shape3d = base()
        val perch : Shape3d = perch().centerZTo(perchZ+slack).centerX()
        val front : Shape3d = front().rotateY(-90).rightTo( diameter/2 + thickness + innerT )
            //.mirrorX().also()
        val lid : Shape3d = lid().bottomTo( tube.top + 0 )
        val gromet : Shape3d = gromet().rotateY(-90).topTo( tube.top - 8 ).rightTo(diameter/2+thickness*2)
            //.mirrorX().also()
            
        return tube + front + base  + lid + gromet + perch
    }

}