/Garden/BirdFeeder.foocad

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

