Exit Full View
Up

/Garden/BirdTable.foocad

BirdTable

A plan for a wooden bird table.

The "floor" section is not attached to the columns, so the whole top can be lifted up. A plastic, easy clean sheet can go under that part (green on the preview).

The roof is screwed semi-permanently to the columns section. I'll use roofing felt to protect from the rain

In Hindsight

The fact that the floor is a plastic sheet an the whole house lifts up makes cleaning much easier than shop-bought designs. However, it's still trickier than expected. When you lift up the house, and seeds etc. can spill onto the base, and if you don't clear them all out, the house won't fit back on correctly.

My Mum didn't like pidgeons taking over. She tied string half way up the columns, so that only smaller birds could feed.

I created a simpler version (See BirdTable2), which I made for my garden.

Matthew has the original bird table now.

FooCAD Source Code
import static uk.co.nickthecoder.foocad.extras.v1.Extras.*
import static uk.co.nickthecoder.foocad.screws.v1.Screws.*
import static uk.co.nickthecoder.foocad.chamferedextrude.v1.ChamferedExtrude.*
import static uk.co.nickthecoder.foocad.extras.v1.v1.Extras.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*
import static uk.co.nickthecoder.foocad.along.v2.Along.*

class BirdTable : Model {
    

    var perchSize = Vector3(300, 200, 30)

    var margins = Vector2( 20, 20 )

    var columnHeight = 150
    
    var columnCount = Vector2( 4, 3 )

    val batten = Lumber( "batten", 20, 10 ).stockLength(2400)

    val dowel = Lumber( "dowel", Circle(12/2) ).stockLength(2400).color("Ivory")

    fun floor() : Shape3d {
        var cube = Cube( perchSize ).centerXY().previewOnly()

        val battensX = batten.cut( cube.size.x + batten.width() ).label("perchX")
            .alongX().centerX().centerYTo(cube.front)
            .mirrorY().also()
            .brighter()
        val battensY = batten.cut( cube.size.y - batten.width() ).label("perchY")
            .alongY2().bottomTo(0).centerY().centerXTo(cube.left)
            .mirrorX().also()

        val floor = Cube( battensY.size.x, battensX.size.y, 6 ).center()
            .topTo(0).color("Red")

        return (floor + battensX + battensY).color("LightGreen") // + cube
    }


    fun columns() : Shape3d {
        val inside = Cube(
            perchSize.x - margins.x*2,
            perchSize.y - margins.y*2,
            columnHeight
        ).centerXY()
            .previewOnly()

        val column = dowel.cut( inside.size.z ).label("column")

        val xColumns = column
            .repeatX( columnCount.x, inside.size.x/(columnCount.x-1) )
            .centerX()
            .centerYTo(inside.front).mirrorY().also()
        val yColumns = column
            .repeatY( columnCount.y-2, inside.size.y/(columnCount.y-1) )
            .centerY()
            .centerXTo(inside.left).mirrorX().also()

        val battenX = batten.cut( inside.size.x + batten.width()).label("columnsX")
            .label( "columns" )
            .alongX().centerX()
            .centerYTo(inside.front)
            .mirrorY().also()

        val battenY = batten.cut( inside.size.y - batten.width()).label("columnsY")
            .label( "columns" )
            .alongY2().bottomTo(0).centerY()
            .centerXTo(inside.left)
            .mirrorX().also()
            .brighter()

        val battens = (battenX + battenY).topTo(inside.top).also()

        val sheet = Cube( inside.size.x, inside.size.y, 1 ).color("Green")
            .centerXY().bottomTo(inside.bottom)

        return (xColumns + yColumns +battens).color("Ivory") + sheet //+ inside 
    }

    /*
        Note, the front and end rafters should be cut to look nice,
        and the remainer cut to slot ontop of the columns
    */
    fun roof() : Shape3d {

        val cube = Cube( perchSize.x + 40 - margins.x*2, perchSize.y + 40, 10 )
            .centerXY().bottomTo( columnHeight - 10 )
            .previewOnly()

        val triangle = Circle( cube.size.y/2 ).sides(4) / Square( cube.size.x*3 ).centerX()
        val rafter = triangle.extrude( cube.size.z ).alongX()
            .bottomTo(cube.bottom)


        val rafters = rafter.spreadX( 4, cube.size.x ).centerX()

        val overhang = 5
        val hyp = Math.sqrt(2) * rafters.size.z + 14 + overhang
        val board = Cube( cube.size.x, hyp, 10 )
            .rotateX(180+45)
            .centerX()
            .translateZ( rafters.top + 14)
            .color("DarkSeaGreen")

        val trimMitre = MitreJoint( batten, 3 )
        val trim = batten.cut( hyp + 10 ).label( "roof trim" )
            .cutZRatio(trimMitre,0)
            .alongY()
            .rotateX(180+45)
            .topTo( board.top + 8)
            .rightTo( board.left )
            .color("LightGreen")
            .mirrorY().also()
            .mirrorX().also()

        return rafters.color("Ivory") + board + trim //+ cube
    }

    @Custom( about="Move the pieces away from each other for an exploded view" )
    var explode = 0

    var pinL = batten.width()*1.8
    var pinD = 5.5

    /**
        Drill holes though the floor and into column sections. Insert these pins to stop the
        top coming off in the wind etc.
    */
    @Piece
    fun pin() : Shape3d {
    
        val cyl = Circle( pinD/2 ).chamferedExtrude( pinL, 0, 1 )
        val flattened = cyl.backTo(pinD*0.75) / Cube( pinD, pinD*0.75, pinL + 2 ).centerX()
        val end = Square( pinD*2, pinD ).roundCorner(3,2).roundCorner(2,2)
            .chamferedExtrude(2,1,0).rotateX(90).centerX().frontTo(0)
       
        return flattened.rotateX(90) + end
        
    }

    /**
        Screw four of them to the corners of the base, and use tent pegs to secure it
        into the ground.
    */
    @Piece
    fun pegClip() : Shape3d {
        val base = Circle( 11 )
            .extrude( 4 )
        val cyl = Cylinder( base.size.z + 4, 8 )
        val hole = countersink( 5,10 ).mirrorZ().bottomTo(-.01)

        return base + cyl - hole
    
    }

     @Piece
     fun perch() = perch( 20 )

    fun perch(foo : double) : Shape3d {
        val width = 8
        val rodD = 13
        val thickness = 3
        val hoop = (Circle( rodD/2 + thickness ) - Circle( rodD/2 ))
            .chamferedExtrude( width,1 ).center()
        val upright = Cylinder( foo, width/2 ).rotateX(90)
            .frontTo(rodD/2+thickness/2)
        val all = hoop + upright
        val half = all / Cube( 100 ).centerXY()

        return half.backTo(-1).mirrorY().also()
    }

    /**
        For a perch in the middle of the table. This will be glued down onto the removable
        plastic mat
    */
    @Piece
    fun perchCenter() : Shape3d {
        val perch : Shape3d = perch(10)
        val width = 8
        val moundD = 30        

        val moundP = PolygonBuilder().apply {
            moveTo(0,0)
            lineBy(moundD/2,0)
            bezierBy(
                Vector2(0,2),
                Vector2(0,5),
                Vector2(-moundD/2 + width/2, 7 )
            )
        }.build()

        val mounds = moundP.revolve(180).rotateX(90)
            .translateY(-1)
            .mirrorY().also()

        return perch + mounds
    }


    @Piece 
    fun foot() : Shape3d {
        val width = 20
        val thick1 = 5
        val thick2 = 10
        val length = 60

        val a = Square( thick1, width + thick2 ).roundCorner(3,5).roundCorner(0,5)
        val b = Square( width , thick2 ).leftTo(a.right).roundCorner(1,5)

        val main = (a+b).chamferedExtrude( length, 1 )
        val screws = countersink(4,10).color("Red").rotateY(-90)
            .repeatZ(2, 30 )
            .centerYTo(a.middle.y+thick1/2)
            .centerZTo(main.middle.z)

        return main - screws

    }

    @Piece
    fun logo() : Shape3d {
        val height = 70
        val profile = SVGParser().parseFile( "birdTable.svg" ).shapes["bird"]
        return profile.center().scale(height/profile.size.y).chamferedExtrude(2.0,0,0.4)
    }

    // On sheds, the finial tends to be a simple diamond shape (made from a scrap of wood).
    // I have a habbit of replacing the diamond with other card suits. My Mum has a heart
    // as the finial on the arbour that I made for her.
    // Here, I use a club.
    // The finial hides the "unsightly" mitre joint of the barge boards at the apex of the roof.
    @Piece
    fun finial() : Shape3d {
        val height = 40
        val profile = SVGParser().parseFile( "birdTable.svg" ).shapes["club"]
        return profile.center().scale(height/profile.size.y).chamferedExtrude(4, 0, 2)
    }

    @Piece
    fun roofAttachment() : Shape3d {
        val angle = Degrees.atan(144/(326/2)) // Using the size of the roof to calculate the angle
        val width = 16
        val size = Vector2(50,10)
        val profile =( Square(size).roundCorner(1,5).backTo(0).rotate(angle))/Square(100)

        val main = profile.extrude( width )

        val hole1 = countersink(4,8,10,30).rotateX(-90)
            .backTo(15).centerZTo( width / 2 ).centerXTo(12)

        val hole2 = countersink(4,8).centerYTo(40).rotateY(90)
            .centerZTo(width/2).rightTo(10+1).rotateZ(angle-90)

        return main - hole1 - hole2
    }

    /**
        Screw this to the base's edge, and then add a wooden dowel for small birds to perch on
        before hopping onto the table.
        Fingers crossed it will stop the gready pigeons eating all the food!
    */
    @Piece
    fun frontPerch() : Shape3d {
        val length = 80
        val width = 8
        val rodD = 13
        val thickness = 3
        val hoop = (Circle( rodD/2 + thickness ) - Circle( rodD/2 ))
            .chamferedExtrude( width,1 ).center()
        val upright = Cylinder( length, width/2 ).rotateX(90)
            .frontTo(rodD/2+thickness/2)

        val foot = Square( 30, 10 ).chamferedExtrude(width,1).center()
            .frontTo(upright.back-1)
            .rotateY(90)

        val hole = countersink(4,8).color("Red").rotateY(-90)
            .leftTo(foot.left).centerYTo( foot.middle.y )
            .translateZ(8)
            .mirrorZ().also()

        val all = hoop + upright + foot - hole
        val half = all / Cube( 200 ).centerXY()

        return half.rightTo(-1).mirrorX().also()
    }

    // I don't want pidgeons standing on the roof, shitting on my lovely bird table!
    // The pigeons can stand on this pointy boy :-( Fingers crossed they will find a
    // more comfy perch elsewhere!
    // I drilled holes in the ends, and nailed it to the bird table.
    @Piece
    fun roofRidge() : Shape3d {
        val angle = 90
        val length = 490/3 // Table is 490 long, but I will print it in 3 sections

        val pattern1 = Circle(17).sides(4).centerYTo(-3) / Square( 20 ).centerX()

        val support = Square(20,2).centerY().rotate(-angle/2).mirrorX().also().extrude(length)
            .rotateX(90).bottomTo(-2).centerY() / Cube(length+2).centerXY()

        val repeat = length ~/ pattern1.size.x

        val hole = Circle( 3 ).translateY(5)
        val pattern = (pattern1 - hole).tileX(repeat)

        val ridge = pattern.scaleX( length / pattern.size.x )
            .extrude(2).centerZ()
            .rotateX(90).rotateZ(90).centerY().bottomTo( support.size.z -2 )

        return support + ridge
    }

    // I printed roofRidge in 3 pieces, but I can use simple angle sections to glue them together.
    @Piece
    fun roofRidgeJoint() :Shape3d {
        return (Square(15) - Square(13)).extrude(40).rotateY(90).bottomTo(0)
    }

    override fun build() : Shape3d {
        
        val floor : Shape3d = floor()
        val columns : Shape3d = columns().translateZ(explode*2)
        val roof : Shape3d = roof().translateZ(explode*3)

        val pins = pin().color("pink")
            .backTo( floor.back + 2 )
            .centerZTo( batten.thickness()/2 )
            .repeatX( 2, floor.size.x / 2 ).centerX()
            .mirrorY().also()
        
        val roofRidge : Shape3d = roofRidge().rotateZ(90)
            .rightTo( roof.right )
            .bottomTo( roof.top - 10 )
            .color("Ivory")

        val logo : Shape3d = logo().rotateX(90).rotateZ(90)
            .leftTo(roof.right-10)
            .centerZTo( roof.middle.z -15)
            .translateY(-25)
            .color("Black")

        val finial : Shape3d = finial().rotateZ(90).rotateY(90)
            .leftTo( roof.right )
            .topTo( roof.top +5 )
            .color("Black")


        return floor + columns + roof +  pins + roofRidge + logo + finial
    }
}