Exit Full View
Up

/Garden/SquirrelFeeder.foocad

SquirrelFeeder

A variation of BirdFeeder2. Attached to vertical wall/post instead of hanging it.

FooCAD Source Code
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*
import uk.co.nickthecoder.foocad.smartextrusion.v1.*
import static uk.co.nickthecoder.foocad.smartextrusion.v1.SmartExtrusion.*
import uk.co.nickthecoder.foocad.screws.v3.*
import uk.co.nickthecoder.foocad.compound.v1.*
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.*


class SquirrelFeeder : Model {

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

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

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

    @Custom( about="Size of the feeding hole" )
    var holeSize = Vector2(15,15)

    @Custom( about="Size (y & z) of the base (Used by piece `base`, not `perchBase`)" )
    var baseSize = Vector3(66, 140, 8)

    @Custom( about="Size (y & z) of tray for piece `perchBase` (not piece `base`)" )
    var traySize = Vector3(28, 20, 10)

    @Custom( about="Additional height for piece `perchBase` (not used by `base`)" )
    var perchBaseHeight = 4

    @Custom( about="Makes holeDiamter smaller" )
    var holeCoverWidth = 5

    @Custom
    val pocketHanger = PocketHanger(22,3,30).wallThickness(3).standOff(2)

    // Other parameters, not exposed as @Custom

    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 holeHeight = 2
    var attachHeight = 8.5 // Beware! Too small, and it will affect the tube's chamfer.
    var perchDiameter = 4.95
    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
    val attachmentSize = Vector3( 4, 20, 30 )

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

    meth hole() = Circle( holeSize.x/2 ).scaleY(holeSize.y/holeSize.x) -
        Square( holeSize.x ).center().backTo( -holeSize.y * 0.4 )

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


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

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

    @Piece
    meth connector() = perchShape()
        .offset(-interferanceFit)
        .extrude( diameter + 4 )
        .rotateX(90)
        .centerXY()
        .bottomTo(0)
        .color("darkGreen")

    @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() )
            .outsideBottom( lip() )

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

        // Use offset to ensure a tighter fit.
        val baseAttachment = holeAndReinforcement( perchShape().offset(-clearance*0.25), diameter/2, true )
            .centerZTo( attachHeight )
            .rotateZ(90)
        
        val attachment = pocketHanger
            .rotateZ(90)
            .topTo( height -10 )
            .rightTo( -diameter/2 )

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

    @Piece
    meth base() : Shape3d {
        val sunken = 0 // Lifts the insert up/down.
        val profile = profile().offset(-thickness-clearance)
        val baseProfile = Square( baseSize.x, baseSize.y )
            .center().roundAllCorners(radius)

        val base = baseProfile.smartExtrude( baseSize.z )
            .top( Fillet( 2 ) )
            .bottom( Chamfer( 2 ) )

        val top = profile.extrude(1)
            .bottomTo( base.top )

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

        val hollow = Square( baseProfile.size.x, baseProfile.back - profile.back )
            .center()
            .offset( -6 )
            .roundAllCorners( radius - 6 )
            .frontTo( baseProfile.front + 6 )
            .smartExtrude( base.size.z - 2 )
                .top( Fillet( -2, 2 ) )
                .bottom( Fillet( 4 ) )
            .topTo( base.top + 0.1 )
            .mirrorY().also()

        val insert = insert()
            .bottomTo( base.top )

        return Compound(true).apply {
            + base
            + top
            + insert
            - hollow
        }.build().color("darkGreen")
    }

    @Piece( about="An alternate base instead of piece `base`")
    meth perchBase() : Shape3d {
        val sunken = 0 // Lifts the insert up/down.

        val profile = profile().offset(-thickness-clearance)

        val insert = insert().bottomTo(perchBaseHeight)

        val underInsert =  profile().offset( lipSize.x )
            .extrude( perchBaseHeight )

        val attachment = holeAndReinforcement( perchShape(), insert.right, false )
            .translateZ( underInsert.top + attachHeight )
            .rotateZ(90)

        val hollowProfile1 = Square( traySize.x, traySize.y )
            .center()
            .backTo( insert.front-2 )
            .roundAllCorners( 4 )
        val hollowProfile = hollowProfile1 hull hollowProfile1.mirrorY()

        val tubeExclusion = Cube( diameter + (lipSize.x + clearance)*2 )
                .centerXY().bottomTo( perchBaseHeight )

        val raised = hollowProfile.offset(6)
                .smartExtrude( traySize.z + perchBaseHeight )
                .top( Fillet( 3 ) )

        val hollow = hollowProfile1
            .smartExtrude( traySize.z + 0.01 )
                .top( Fillet( -2, 2 ) )
                .bottom( Fillet( 4 ) )
            .bottomTo( perchBaseHeight )
            .mirrorY().also()
        val drainHoles = Cylinder( 10, 1.5 )
            .bottomTo(-1)
            .centerYTo( underInsert.front - traySize.y/2 )
            .mirrorY().also()

        val perches = Square( traySize.x + 30, 6 )
            .centerY()
            .roundAllCorners(1)
            .smartExtrude( 6 )
            .top( Fillet( 3 ) )
            .bottom( Chamfer(1) )
            .repeatAroundZ( 2, -90 )
            .translateY( hollow.back - 100 )
            .mirrorY().also()

        return Compound().apply {
            + perches
            + underInsert
            + raised
            - tubeExclusion
            + insert
            - hollow
            - drainHoles
        }.build().color("darkGreen")
    }

    meth insert() : Shape3d {
        val profile = profile().offset( -thickness -clearance )
        val base = profile.extrude(holeHeight + 2)
        val hump = profile
            .smartExtrude( 14 )
            .scaleTop( Vector2(1, 0.05) )
            .bottomTo( base.top )
        val angle = profile.extrude(20) -
            Cube( profile.size.x * 2 )
                .centerXY()
                .rotateY(25)
        val angles = angle.mirrorX().also()
        val attachmentHole = perchShape().offset(clearance)
            .extrude( profile.size.x + 10 )
            .center()
            .rotateZ(90)
            .rotateY(90)
            .translateZ( attachHeight )

        return base + hump + angles - attachmentHole
    }

    @Piece
    meth lid() = lidUpright().mirrorZ().bottomTo(0)

    @Piece( print="lid" )
    meth 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
    meth attachment() = pocketHanger.attachment()

    meth attachmentProfile() = Square( attachmentSize.x, attachmentSize.y*2 )
        .center()
        .roundCorner( 3, attachmentSize.x, 1 )
        .roundCorner( 0, attachmentSize.x, 1 )
        .scaleY(0.5)

    @Piece
    fun holeCover() : Shape3d {
        val profile = hole().offset(2)
        val outside = ExtrusionBuilder().apply {
            crossSection( profile )
            forward(1)
            crossSection()
            crossSection( -1 )
            forward( 3 )
            crossSection()
        }.build()
        val inside = profile.offset( -holeCoverWidth )
            .smartExtrude( outside.size.z + 0.02 )
            .chamfer(-1,1)
            .translateZ(-0.01)

        return outside - inside
    }

    // My original print's hole size was too big, and too much food spilled out.
    // Instead of reprinting the tube, I made this smaller part to restrict the flow.
    @Piece( about="Restricts the size of the opening to adjusts the flow of food." )
    meth shutter() : Shape3d {
        val insideWidth = diameter - thickness * 2 - clearance*2
        val base = Cube( holeSize.x+4, insideWidth, 15 )
            .centerXY()
        val base2 = Cube( insideWidth, 15, 2 )
            .centerXY()

        val post = (Cylinder(6, 4) remove (Cylinder(30,1.5).center()))
            .leftTo( base.right )
            
        val foo = Cube( base.size.x+2, base.size.y-4, base.size.z )
            .centerXY().bottomTo(2)

        return Compound(true).apply {
            + (base + base2 - foo)
            + post
            + post.mirrorX()

        }.build()
    }

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

        val base = base()
        val perchBase = perchBase()
            .translateZ( baseSize.z - perchBaseHeight )
        val tube = tubeUpright().bottomTo( baseSize.z ).previewOnly()
        val connector = connector().rotateZ(90).centerZTo( tube.bottom + attachHeight + clearance )
        val lid = lidUpright().mirrorZ().topTo( tube.top + lidSize.y + clearance )
        val attachment = attachment()
            .rotateZ(90).rotateY(-90)
            .rightTo( -diameter/2)
            .centerZTo( tube.middle.z + 20 )
        val shutter = shutter()
            .bottomTo(29)
        
        + tube
        //+ base
        + perchBase
        //+ lid
        + connector
        + attachment
        //+ shutter

    }.build()

}