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