/Garden/BirdFeeder2.foocad

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.
import static uk.co.nickthecoder.foocad.round.v1.Round.*
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 : Model {
@Custom( about="Width of the tube" )
var diameter = 60
@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
// Other parameters, not exposed as @Custom
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.95
var perchExtra = 30
var holeHeight = 15
var attachHeight = 8.5 // Beware! Too small, and it will affect the tube's chamfer.
var perchHeight = 10
var hangOffset = 20 // Distance from top to the holes for the wire hanger.
var lidSize = Vector2( 8, 2) // Height and thickness of the lid
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
.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()
.offset(-interferanceFit)
.extrude( diameter + perchExtra * 2 )
.rotateX(90)
.bottomTo(0)
.centerXY()
.color("darkGreen")
@Piece
fun connector() = perchShape()
.offset(-interferanceFit)
.extrude( diameter + 4 )
.rotateX(90)
.centerXY()
.bottomTo(0)
.color("darkGreen")
@Piece
fun hanger() : Shape3d {
val thickness = 3.5
val radius = diameter/2 + lidSize.y + thickness/2 + 1
val height = 80
val path = PolygonBuilder().apply {
moveTo( 0, 2 )
lineBy( 2, -2 )
lineBy( 10, 0 )
bezierBy( Vector2( 0, height/3 ), Vector2(-radius, 0), Vector2( - radius, height ) )
bezierBy( Vector2( -radius, 0 ), Vector2( 0, -height/3 ), Vector2( -radius, -height ) )
lineBy( 10, 0 )
lineBy( 2, 2 )
}.buildPath().centerX()
return path.thickness( thickness, true, true )
.smartExtrude( thickness )
.edges( Chamfer(0.5) )
.color( "Gray" )
}
@Piece
meth tube() = tubeUpright().mirrorZ().rotateZ(90).bottomTo(0)
@Piece( print="tube" )
meth tubeUpright() : 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 base() = baseUpright().mirrorZ().bottomTo(0)
@Piece( print="base" )
fun baseUpright() : Shape3d {
val sunken = 0 // Lifts the insert up/down.
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 - sunken, 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( 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 extraHeight = 3
val archShape = Circle( holeDiameter/2 )
.frontTo( extraHeight ) hull
Square( holeDiameter, 1 ).centerX() hull
Square( 1 ).centerX()
.backTo( holeDiameter * 1.2 + extraHeight )
val archThickness = 1.2
val arch = archShape.outline(archThickness, 0)
.smartExtrude( floor.size.y )
.outside( fatLip(2.4,2) )
.rotateX(90).centerY()
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 lid() = lidUpright().mirrorZ().bottomTo(0)
@Piece( print="lid" )
fun lidUpright() : 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( printable = false )
override fun build() = Compound().apply {
val base = baseUpright()
val insert = insert().bottomTo( base.top )
val tube = tubeUpright().bottomTo( baseHeight + clearance )//.previewOnly()
val perch = perch().centerZTo( perchHeight )
val connector = connector().rotateZ(90).centerZTo( tube.bottom + attachHeight )
val lid = lidUpright().mirrorZ().topTo( tube.top + lidSize.y + clearance )
val hanger = hanger().centerZ().rotateX(90).translateZ( tube.top - hangOffset )
+ tube
+ base
+ lid
+ insert
+ connector
+ perch
+ hanger
}.build()
}
