Exit Full View
ZipUp

/Garden/BirdFeeder2.foocad

BirdFeeder2

A simple bird feeder. Goals :

  • Easy to disassemble for cleaning
  • Prevent seeds getting stuck, and going mouldy.

Shop-bought feeders often fail on both of these. Grr.

Print Notes

Don't use PLA - it becomes brittle in moisture, and isn't UV stable.

I used PETG for the tube, and ASA for the other parts. Time will tell how long the PETG will last. Alas, I don't think ASA is transparent, which is why I opted for PETG.

Insert

Low fill-density. 50% ?

FooCAD Source Code
import uk.co.nickthecoder.foocad.compound.v1.*
import static uk.co.nickthecoder.foocad.debug.v1.Debug.*
import static uk.co.nickthecoder.foocad.extras.v1.Extras.*
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.*

class BirdFeeder2 : ModelWithSetup {

    @Custom( about="Width of the tube" )
    var diameter = 50

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

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

    @Custom( about="Size of the feeding hole" )
    var holeDiameter = 20

    var baseHeight = 20
    var thickness = 1.0
    var lipSize = Vector2( 1.5, 5 )
    var clearance = 0.5 // Distance for pieces to easily slot together
    var interferanceFit = 0.2 // Distance for pieces to only just fit together.
    var perchDiameter = 4.75
    var perchExtra = 30
    var holeHeight = 15
    var attachHeight = 6
    var perchHeight = 0
    var hangOffset = 20 // Distance from top to the holes for the wire hanger.
    var lidSize = Vector2( 8, 2) // Height and thickness of the lid

    override fun setup() {
        perchHeight = baseHeight / 2
    }

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

    fun hole() = Circle( holeDiameter/2 ) -
        Square( holeDiameter ).center().backTo( -holeDiameter * 0.4 )

    fun lip() = fatLip( lipSize.y, lipSize.x )


    fun perchShape() = Circle( perchDiameter / 2 ).sides(32) +
        Square( perchDiameter, perchDiameter / 2 ).centerX().backTo(0)

    fun holeAndReinforcement( shape : Shape2d, y : double, outside : bool ) : Shape3d {
        val hole = shape.offset( interferanceFit )
            .extrude( y * 2 + 10 )
            .rotateX(90)
            .centerY()

        val reinforcement = shape.offset( 4 )
            .smartExtrude( lipSize.x )
            .outsideTop( Chamfer( lipSize.x ) )
            .rotateX(90)
            .backTo( if (outside) {-y } else y )

        return reinforcement.mirrorY().also().remove( hole )        
    }

    // Print two.
    // Print with ears (or a brim) to prevent lifting off the bed.
    @Piece
    fun perch() = perchShape().extrude( diameter + perchExtra * 2 )
        .rotateX(90)
        .bottomTo(0)
        .centerXY()
        .color("darkGreen")

    @Piece
    fun connector() = perchShape().extrude( diameter + 4 )
        .rotateX(-90)
        .centerXY()
        .color("darkGreen")

    @Piece
    meth tubePrint() = tube().mirrorZ().rotateZ(90).bottomTo(0)

    @Piece( printable=false )
    meth tube() : Shape3d {

        val tube = profile().outline( 0, thickness)
            .smartExtrude( height )
            .insideTop( lip() )
            //.outsideTop( lip() )
            .outsideBottom( lip() )
            // Allow the base to fit inside and be printable without supports.
            .insideBottom( Chamfer( thickness + lipSize.x ) )

        val feedingHole = holeAndReinforcement( hole(), diameter/2, true )
            .translateZ( holeHeight + holeDiameter/2 )

        val attachment = holeAndReinforcement( perchShape().offset(0.2), diameter/2, true )
            .centerZTo( attachHeight )
            .rotateZ(90)
        
        val hanger = holeAndReinforcement( Circle(2).sides(32), diameter/2, true )
            .translateZ( tube.top - hangOffset )           
            .rotateZ(90)

        return Compound(true).apply {
            + tube
            + attachment
            + feedingHole
            + hanger
        }.build().color("WhiteSmoke")
    }

    @Piece
    fun basePrint() = base().mirrorZ().bottomTo(0)

    @Piece( printable = false )
    fun base() : Shape3d {
        val profile = profile().offset( lipSize.x )
        val ring = profile.outline( 0, thickness)
        val lip = lip()

        val base = ring.smartExtrude( baseHeight )
            .insideTop( lip )
            .insideBottom( lip )

        val insideChamfer = thickness + lipSize.x + clearance
        val inside = Cup( profile.offset(-insideChamfer), holeHeight - 2, thickness )
            .outsideTop( Chamfer( -insideChamfer, insideChamfer ) )
            .mirrorZ()
            .bottomTo( base.top )

        val attachment = holeAndReinforcement( perchShape(), base.right -insideChamfer - thickness, false )
            .translateZ( base.top + attachHeight )
            .rotateZ(90)

        val perchHole = holeAndReinforcement( perchShape(), base.size.y/2, true )
            .translateZ( base.top - perchHeight )

        return Compound(true).apply {
            + base
            + inside
            + perchHole
            + attachment
        }.build().color("darkGreen")
    }

    @Piece
    fun insert() : Shape3d {

        val floorShape = profile().offset(-thickness-clearance)
        val floor = floorShape.extrude(thickness)
        val size = floor.size.x

        val archShape = Circle( holeDiameter/2 )
                .frontTo( 2 ) hull
            Square( holeDiameter, 1 ).centerX() hull
            Square( 1 ).centerX()
                .backTo( holeDiameter * 1.2 )

        val lipSize = 3
        val archThickness = 1.2
        val arch = archShape.outline(archThickness, 0)
            .smartExtrude( floor.size.y )
            .outside( fatLip(2.4,2) )
            .rotateX(90).centerY()
            .bottomTo(-lipSize-archThickness/2)
    
        val ledge = Cube( floorShape.right - archShape.right, size/2, 7 )
            .leftTo( archShape.right )
            //.centerY()
            .rotateX(25)
            .mirrorZ().translateZ( 7 )
            .mirrorX().also()
            .mirrorY().also()

        val slope = PolygonBuilder().apply {
            moveTo( 0,0 )
            lineTo( size, 0 )
            lineTo( size, 10 )
            lineTo( size * 0.7, thickness )
            lineTo( size * 0.3, thickness )
            lineTo( 0, 10 )
        }.build()
            .extrude( size )
            .rotateX(90)
            .centerXY()

        val insertHoleSize = Vector2( 10, 12 )
        val hole = Cube( diameter, insertHoleSize.x, insertHoleSize.y )
            .center()
            .frontTo( -size/2 + 3 )
            .bottomTo( thickness )
        val holes = hole.mirrorY().also()

        val dividerChamfer = 4
        val divider = archShape
            .cup( thickness/2 + dividerChamfer, 0 )
            .baseThickness(thickness/2)
            .insideBottom( Chamfer(dividerChamfer) )
            .rotateX(90)
            .frontTo( hole.back )
            .mirrorY().also()
            .bottomTo(0)            
        
        val envelope = floorShape.extrude( arch.size.z )

        return (floor + divider + arch + ledge - holes + slope)
            .intersection( envelope )
            //.color( "darkGreen" )
    }


    @Piece
    fun lidPrint() = lid().mirrorZ().bottomTo(0)

    @Piece( printable=false )
    fun lid() : Shape3d {
        val profile = profile().offset( lidSize.y + clearance )

        val lid = profile.cup( lidSize.x, lidSize.y )
            .outsideBottom( Chamfer( lidSize.y - 1 ) )
            .outsideTop( Chamfer( 0.6 ) )
        return lid.color( "darkGreen" )
    }

    @Piece
    fun notTube() : Shape3d {
        return basePrint().leftTo(1).frontTo(1) +
            insert().leftTo(1).backTo(-1) +
            lid().rightTo(-1)
    }

    @Piece( printable = false )
    override fun build() = Compound().apply {

        val base = base()
        val insert = insert().bottomTo( base.top +0.5 )
        val tube = tube().bottomTo( baseHeight + clearance )
        val perch = perch()
        val lid = lid().mirrorZ().topTo( tube.top + lidSize.y + clearance )

        + tube
        + base
        + lid
        + insert
        //+ perch.rotateZ(90).centerZTo( tube.bottom + attachHeight )
        + perch.centerZTo( perchHeight )
    }.build()

}