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